KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_altium_parser_sch.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
24
26
28#include <eeschema/sch_text.h>
29#include <layer_ids.h>
31#include <validators.h>
32#include <wx/filename.h>
33
34#include <set>
35#include <vector>
36
37// Function declarations of private methods to test
38int ReadKiCadUnitFrac( const std::map<wxString, wxString>& aProps,
39 const wxString& aKey );
40
42 ASCH_RECORD_ORIENTATION orientation );
43
44void AdjustTextForSymbolOrientation( SCH_TEXT* aText, const ASCH_SYMBOL& aSymbol );
45
46wxString AltiumWrapBusLabel( const wxString& aText );
47
48wxString AltiumDeriveSheetName( const wxString& aFilename, const std::set<wxString>& aExistingNames );
49
50
55
56
60BOOST_FIXTURE_TEST_SUITE( AltiumParserSch, ALTIUM_PARSER_SCH_FIXTURE )
61
63{
64 wxString input;
65 wxString input_frac;
67};
68
72static const std::vector<ALTIUM_TO_KICAD_UNIT_FRAC_CASE> altium_to_kicad_unit_frac = {
73 // Some simple values
74 { "0", "0", 0 },
75 { "1", "0", 2540 },
76 { "2", "0", 5080 },
77 { "-1", "0", -2540 },
78 { "-2", "0", -5080 },
79 // Decimal Places
80 { "0", "1", 0 },
81 { "0", "10", 0 },
82 { "0", "100", 0 },
83 { "0", "1000", 30 },
84 { "0", "10000", 250 },
85 { "1", "10000", 2790 },
86 { "0", "-1", 0 },
87 { "0", "-10", 0 },
88 { "0", "-100", 0 },
89 { "0", "-1000", -30 },
90 { "0", "-10000", -250 },
91 { "-1", "-10000", -2790 },
92 // Edge Cases
93 // Clamp bigger values
94 // imperial rounded units as input
95 // metric rounded units as input
96};
97
101BOOST_AUTO_TEST_CASE( PropertiesReadKiCadUnitFracConversation )
102{
103 for( const auto& c : altium_to_kicad_unit_frac )
104 {
106 wxString::Format( wxT( "%s FRAC %s -> %i" ), c.input, c.input_frac, c.exp_result ) )
107 {
108 std::map<wxString, wxString> properties = { { "TEST", c.input },
109 { "TEST_FRAC", c.input_frac } };
110
111 int result = ReadKiCadUnitFrac( properties, "TEST" );
112
113 // These are all valid
114 BOOST_CHECK_EQUAL( result, c.exp_result );
115 }
116 }
117}
118
119
121{
122 wxString input;
123 wxString expected;
124};
125
126static const std::vector<SHEET_NAME_SANITIZE_CASE> sheet_name_sanitize_cases = {
127 { wxT( "SimpleSheet" ), wxT( "SimpleSheet" ) },
128 { wxT( "POWER PROTECTION/MONITORING" ), wxT( "POWER PROTECTION_MONITORING" ) },
129 { wxT( "A/B/C" ), wxT( "A_B_C" ) },
130 { wxT( "/" ), wxT( "_" ) },
131 { wxT( "no_slash_here" ), wxT( "no_slash_here" ) },
132 { wxT( "trailing/" ), wxT( "trailing_" ) },
133 { wxT( "/leading" ), wxT( "_leading" ) },
134};
135
140BOOST_AUTO_TEST_CASE( SheetNameSlashSanitization )
141{
142 for( const auto& c : sheet_name_sanitize_cases )
143 {
144 BOOST_TEST_CONTEXT( wxString::Format( wxT( "'%s' -> '%s'" ), c.input, c.expected ) )
145 {
146 wxString sanitized = c.input;
147 sanitized.Replace( wxT( "/" ), wxT( "_" ) );
148
149 BOOST_CHECK_EQUAL( sanitized, c.expected );
150
151 wxString validationError = GetFieldValidationErrorMessage( FIELD_T::SHEET_NAME,
152 sanitized );
153 BOOST_CHECK_MESSAGE( validationError.empty(),
154 wxString::Format( wxT( "Sanitized name '%s' failed validation: %s" ),
155 sanitized, validationError ) );
156 }
157 }
158}
159
163BOOST_AUTO_TEST_CASE( SheetNameSlashRejection )
164{
165 wxString invalidName = wxT( "POWER PROTECTION/MONITORING" );
166 wxString validationError = GetFieldValidationErrorMessage( FIELD_T::SHEET_NAME, invalidName );
167
168 BOOST_CHECK_MESSAGE( (!validationError.empty()) ,
169 wxString::Format( "Sheet name with '/' should fail validation" ) );
170}
171
177BOOST_AUTO_TEST_CASE( ExtensionlessFilenameDetection )
178{
179 // Filenames WITH extensions should be detected
180 BOOST_CHECK( wxFileName( wxT( "6_power.SchDoc" ) ).HasExt() );
181 BOOST_CHECK( wxFileName( wxT( "my_sheet.SCHDOC" ) ).HasExt() );
182
183 // Filenames WITHOUT extensions should not be detected
184 BOOST_CHECK( !wxFileName( wxT( "6_power" ) ).HasExt() );
185 BOOST_CHECK( !wxFileName( wxT( "POWER MANAGEMENT" ) ).HasExt() );
186 BOOST_CHECK( !wxFileName( wxT( "" ) ).HasExt() );
187}
188
189
194BOOST_AUTO_TEST_CASE( ExtensionlessBaseNameMatching )
195{
196 wxFileName sheetFn( wxT( "6_power" ) );
197 bool extensionless = !sheetFn.HasExt();
198
199 BOOST_CHECK( extensionless );
200
201 wxFileName candidateA( wxT( "6_power.SchDoc" ) );
202 wxFileName candidateB( wxT( "6_POWER.SCHDOC" ) );
203 wxFileName candidateC( wxT( "7_other.SchDoc" ) );
204 wxFileName candidateD( wxT( "6_power.txt" ) );
205
206 // Should match same base name with .SchDoc extension (case-insensitive on both parts)
207 auto matches = [&]( const wxFileName& candidate )
208 {
209 return candidate.GetFullName().IsSameAs( sheetFn.GetFullName(), false )
210 || ( extensionless
211 && !sheetFn.GetName().empty()
212 && candidate.GetName().IsSameAs( sheetFn.GetName(), false )
213 && candidate.GetExt().IsSameAs( wxT( "SchDoc" ), false ) );
214 };
215
216 BOOST_CHECK( matches( candidateA ) );
217 BOOST_CHECK( matches( candidateB ) ); // Case-insensitive match on both name and ext
218 BOOST_CHECK( !matches( candidateC ) ); // Different base name
219 BOOST_CHECK( !matches( candidateD ) ); // Wrong extension
220}
221
222
227BOOST_AUTO_TEST_CASE( SheetNameDerivationFromFilename )
228{
229 auto deriveName = []( const wxString& aFilePath, const std::set<wxString>& aExistingNames )
230 {
231 wxString baseName = wxFileName( aFilePath ).GetName();
232 baseName.Replace( wxT( "/" ), wxT( "_" ) );
233
234 wxString sheetName = baseName;
235
236 for( int ii = 1; aExistingNames.count( sheetName ); ++ii )
237 sheetName = baseName + wxString::Format( wxT( "_%d" ), ii );
238
239 return sheetName;
240 };
241
242 BOOST_CHECK_EQUAL( deriveName( wxT( "6_power.SchDoc" ), {} ), wxT( "6_power" ) );
243 BOOST_CHECK_EQUAL( deriveName( wxT( "POWER.SchDoc" ), {} ), wxT( "POWER" ) );
244
245 // Deduplication when a name already exists
246 std::set<wxString> existing = { wxT( "6_power" ) };
247 BOOST_CHECK_EQUAL( deriveName( wxT( "6_power.SchDoc" ), existing ), wxT( "6_power_1" ) );
248
249 // Multiple duplicates
250 existing.insert( wxT( "6_power_1" ) );
251 BOOST_CHECK_EQUAL( deriveName( wxT( "6_power.SchDoc" ), existing ), wxT( "6_power_2" ) );
252}
253
254
255static ASCH_SYMBOL MakeTestSymbol( int aOrientation, bool aMirrored )
256{
257 std::map<wxString, wxString> props;
258 props[wxT( "RECORD" )] = wxT( "1" );
259 props[wxT( "ORIENTATION" )] = wxString::Format( wxT( "%d" ), aOrientation );
260 props[wxT( "ISMIRRORED" )] = aMirrored ? wxT( "T" ) : wxT( "F" );
261 props[wxT( "CURRENTPARTID" )] = wxT( "1" );
262 props[wxT( "PARTCOUNT" )] = wxT( "2" );
263 props[wxT( "DISPLAYMODECOUNT" )] = wxT( "1" );
264 props[wxT( "LOCATION.X" )] = wxT( "0" );
265 props[wxT( "LOCATION.Y" )] = wxT( "0" );
266 return ASCH_SYMBOL( props );
267}
268
269
270// Simulates the angle/justification portion of what OrientAndMirrorSymbolItems
271// does at render time. Position is not relevant for this test.
272static void SimulateRenderTransform( SCH_TEXT* aText, int aAltiumOrientation, bool aMirrored )
273{
274 int nRotations = ( aAltiumOrientation + 1 ) % 4;
275
276 for( int i = 0; i < nRotations; i++ )
277 aText->Rotate90( false );
278
279 if( aMirrored )
280 {
281 if( aText->GetTextAngle().IsHorizontal() )
282 aText->FlipHJustify();
283 else
284 aText->SetVertJustify( static_cast<GR_TEXT_V_ALIGN_T>( -aText->GetVertJustify() ) );
285 }
286}
287
288
298
299
300static const std::vector<TEXT_ORIENT_CASE> text_orient_cases = {
301 // Horizontal text, left-justified, each symbol orientation
310
311 // Vertical text, left-justified, each symbol orientation
320
321 // Horizontal text, right-justified, each symbol orientation
330
331 // Mirrored cases
340
341 // LEFTWARDS text flips H-justify via SetTextPositioning
346
347 // DOWNWARDS text flips H-justify via SetTextPositioning
352};
353
354
360BOOST_AUTO_TEST_CASE( TextOrientationCompensation )
361{
362 for( size_t idx = 0; idx < text_orient_cases.size(); idx++ )
363 {
364 const TEXT_ORIENT_CASE& c = text_orient_cases[idx];
365
366 BOOST_TEST_CONTEXT( wxString::Format(
367 wxT( "case %zu: textOri=%d justification=%d symOri=%d mirror=%d" ),
368 idx, (int) c.textOrientation, (int) c.justification,
369 c.altiumSymOrientation, c.mirrored ? 1 : 0 ) )
370 {
371 SCH_TEXT textItem( { 0, 0 }, wxT( "TEST" ), LAYER_DEVICE );
372
374
376
377 AdjustTextForSymbolOrientation( &textItem, sym );
378
380
381 BOOST_CHECK_EQUAL( textItem.GetTextAngle(), c.expectedAngle );
382 BOOST_CHECK_EQUAL( textItem.GetHorizJustify(), c.expectedHJustify );
383 }
384 }
385}
386
387
393BOOST_AUTO_TEST_CASE( BusLabelWrapping )
394{
395 BOOST_CHECK_EQUAL( AltiumWrapBusLabel( wxT( "I2C" ) ), wxT( "{I2C}" ) );
396 BOOST_CHECK_EQUAL( AltiumWrapBusLabel( wxT( "SPI" ) ), wxT( "{SPI}" ) );
397 BOOST_CHECK_EQUAL( AltiumWrapBusLabel( wxT( "MOSI" ) ), wxT( "{MOSI}" ) );
398
399 // Already-formatted bus labels pass through unchanged (no double wrapping).
400 BOOST_CHECK_EQUAL( AltiumWrapBusLabel( wxT( "{SPI}" ) ), wxT( "{SPI}" ) );
401 BOOST_CHECK_EQUAL( AltiumWrapBusLabel( wxT( "DATA[0..7]" ) ), wxT( "DATA[0..7]" ) );
402 BOOST_CHECK_EQUAL( AltiumWrapBusLabel( wxT( "I2C{I2C}" ) ), wxT( "I2C{I2C}" ) );
403
404 // A net name with spaces or commas must stay a single bus member, not fan out into
405 // several. The wrapped form must parse back to exactly one member.
406 for( const wxString& name : { wxT( "Net 1" ), wxT( "A,B" ) } )
407 {
408 wxString wrapped = AltiumWrapBusLabel( name );
409 std::vector<wxString> members;
410
411 BOOST_CHECK( NET_SETTINGS::ParseBusGroup( wrapped, nullptr, &members ) );
412 BOOST_CHECK_EQUAL( members.size(), 1u );
413 }
414}
415
416
422BOOST_AUTO_TEST_CASE( SheetNameDerivation )
423{
424 BOOST_CHECK_EQUAL( AltiumDeriveSheetName( wxT( "power.SchDoc" ), {} ), wxT( "power" ) );
425 BOOST_CHECK_EQUAL( AltiumDeriveSheetName( wxT( "my_sheet.SchDoc" ), {} ), wxT( "my_sheet" ) );
426 BOOST_CHECK_EQUAL( AltiumDeriveSheetName( wxT( "" ), {} ), wxT( "Sheet" ) );
427
428 std::set<wxString> existing = { wxT( "power" ) };
429 BOOST_CHECK_EQUAL( AltiumDeriveSheetName( wxT( "power.SchDoc" ), existing ), wxT( "power_1" ) );
430
431 existing.insert( wxT( "power_1" ) );
432 BOOST_CHECK_EQUAL( AltiumDeriveSheetName( wxT( "power.SchDoc" ), existing ), wxT( "power_2" ) );
433
434 BOOST_CHECK_EQUAL( AltiumDeriveSheetName( wxT( "" ), { wxT( "Sheet" ) } ), wxT( "Sheet_1" ) );
435}
436
437
const char * name
ASCH_RECORD_ORIENTATION
ASCH_LABEL_JUSTIFICATION
bool IsHorizontal() const
Definition eda_angle.h:142
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:89
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:412
void FlipHJustify()
Definition eda_text.h:229
virtual EDA_ANGLE GetTextAngle() const
Definition eda_text.h:168
GR_TEXT_V_ALIGN_T GetVertJustify() const
Definition eda_text.h:224
static bool ParseBusGroup(const wxString &aGroup, wxString *name, std::vector< wxString > *aMemberList)
Parse a bus group label into the name and a list of components.
virtual void Rotate90(bool aClockwise)
Definition sch_text.cpp:257
static bool empty(const wxTextEntryBase *aCtrl)
static constexpr EDA_ANGLE ANGLE_VERTICAL
Definition eda_angle.h:408
static constexpr EDA_ANGLE ANGLE_HORIZONTAL
Definition eda_angle.h:407
@ LAYER_DEVICE
Definition layer_ids.h:464
Declares the struct as the Boost test fixture.
ASCH_RECORD_ORIENTATION textOrientation
ASCH_LABEL_JUSTIFICATION justification
GR_TEXT_H_ALIGN_T expectedHJustify
void AdjustTextForSymbolOrientation(SCH_TEXT *aText, const ASCH_SYMBOL &aSymbol)
static const std::vector< ALTIUM_TO_KICAD_UNIT_FRAC_CASE > altium_to_kicad_unit_frac
A list of valid internal unit conversation factors.
static const std::vector< TEXT_ORIENT_CASE > text_orient_cases
int ReadKiCadUnitFrac(const std::map< wxString, wxString > &aProps, const wxString &aKey)
static const std::vector< SHEET_NAME_SANITIZE_CASE > sheet_name_sanitize_cases
static ASCH_SYMBOL MakeTestSymbol(int aOrientation, bool aMirrored)
BOOST_AUTO_TEST_CASE(PropertiesReadKiCadUnitFracConversation)
Test conversation from Altium internal units into KiCad internal units using properties with FRAC.
wxString AltiumDeriveSheetName(const wxString &aFilename, const std::set< wxString > &aExistingNames)
static void SimulateRenderTransform(SCH_TEXT *aText, int aAltiumOrientation, bool aMirrored)
wxString AltiumWrapBusLabel(const wxString &aText)
void SetTextPositioning(EDA_TEXT *text, ASCH_LABEL_JUSTIFICATION justification, ASCH_RECORD_ORIENTATION orientation)
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
GR_TEXT_V_ALIGN_T
This is API surface mapped to common.types.VertialAlignment.
wxString GetFieldValidationErrorMessage(FIELD_T aFieldId, const wxString &aValue)
Return the error message if aValue is invalid for aFieldId.
Custom text control validator definitions.