KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_annotation_refdes_tracker_units.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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
21#include "eeschema_test_utils.h"
22
23#include <lib_id.h>
24#include <refdes_tracker.h>
25#include <sch_reference_list.h>
26#include <sch_symbol.h>
27
28#include <memory>
29
32{
33 std::string m_value;
34 std::string m_libName;
35 int m_unit;
36};
37
39{
40 std::string m_caseName;
41 std::string m_testRefPrefix;
42 std::string m_testRefValue;
43 std::string m_testRefLibName;
44 std::map<int, std::vector<EXISTING_REF>> m_refNumberMap;
45 std::vector<int> m_requiredUnits;
48 std::vector<std::string> m_trackerPreloads;
49};
50
52{
53protected:
54 void runTestCase( const REFDES_UNITS_TEST_CASE& testCase );
55
58 SCH_REFERENCE createTestReference( const std::string& aRefPrefix, const std::string& aValue, int aUnit,
59 const std::string& aLibName = "TestPart" )
60 {
61 SCH_SYMBOL* symbol = m_symbols.emplace_back( std::make_unique<SCH_SYMBOL>() ).get();
62 symbol->SetLibId( LIB_ID( wxEmptyString, aLibName ) );
63
65 SCH_REFERENCE ref( symbol, path );
66 ref.SetRef( aRefPrefix );
67 ref.SetValue( aValue );
68 ref.SetUnit( aUnit );
69
70 return ref;
71 }
72
73private:
74 std::vector<std::unique_ptr<SCH_SYMBOL>> m_symbols;
75};
76
78{
79 BOOST_TEST_INFO_SCOPE( testCase.m_caseName );
80
81 REFDES_TRACKER tracker;
82 tracker.SetReuseRefDes( false );
83
84 for( const std::string& ref : testCase.m_trackerPreloads )
85 tracker.Insert( ref );
86
87 SCH_REFERENCE testRef = createTestReference( testCase.m_testRefPrefix, testCase.m_testRefValue, 1,
88 testCase.m_testRefLibName );
89
90 std::map<int, std::vector<SCH_REFERENCE>> refNumberMap;
91
92 for( const auto& [refNum, existing] : testCase.m_refNumberMap )
93 {
94 std::vector<SCH_REFERENCE> refs;
95
96 for( const EXISTING_REF& e : existing )
97 refs.push_back( createTestReference( testCase.m_testRefPrefix, e.m_value, e.m_unit, e.m_libName ) );
98
99 refNumberMap[refNum] = refs;
100 }
101
102 int result = tracker.GetNextRefDesForUnits( testRef, refNumberMap, testCase.m_requiredUnits,
103 testCase.m_minValue );
104
106 BOOST_CHECK_GE( result, testCase.m_minValue );
107
108 const std::string resultRefDes = testCase.m_testRefPrefix + std::to_string( result );
109 auto selected = testCase.m_refNumberMap.find( result );
110
111 if( selected == testCase.m_refNumberMap.end() )
112 {
113 // A freshly allocated number is reserved, and must not have been preloaded as used
114 BOOST_CHECK( tracker.Contains( resultRefDes ) );
115 BOOST_CHECK( std::find( testCase.m_trackerPreloads.begin(), testCase.m_trackerPreloads.end(),
116 resultRefDes ) == testCase.m_trackerPreloads.end() );
117 }
118 else
119 {
120 // Sharing an occupied number is only legal when every requested unit is genuinely free on
121 // a part with the same library item and value
122 for( int unit : testCase.m_requiredUnits )
123 {
124 if( unit < 0 )
125 continue;
126
127 for( const EXISTING_REF& e : selected->second )
128 {
129 BOOST_CHECK( e.m_unit != unit );
132 }
133 }
134 }
135}
136
137static const std::vector<REFDES_UNITS_TEST_CASE> refdesUnitsTestCases = {
138 {
139 "Completely unused reference - empty units",
140 "U", "LM358", "OpAmp_Dual",
141 {}, // no existing references for any refdes
142 {}, // no required units, so any brand new refdes is acceptable
143 1, // min value
144 1, // expected U1
145 {} // no preloaded refdes
146 },
147 {
148 "Completely unused reference - with units",
149 "U", "LM358", "OpAmp_Dual",
150 {}, // no existing references for any refdes
151 { 1, 2 }, // need units 1 and 2
152 1, // min value
153 1, // expected U1
154 {} // no preloaded refdes
155 },
156 {
157 "Skip currently in use reference",
158 "U", "LM358", "OpAmp_Dual",
159 { { 1, { { "LM358", "OpAmp_Dual", 1 } } } }, // U1 unit 1 in use
160 { 1, 2 }, // need units 1 and 2
161 1, // min value
162 2, // expected U2, U1 conflicts on unit 1
163 {}
164 },
165 {
166 "Units available in currently used reference",
167 "U", "LM358", "OpAmp_Dual",
168 { { 1, { { "LM358", "OpAmp_Dual", 3 }, { "LM358", "OpAmp_Dual", 4 } } } }, // U1 units 3,4 in use
169 { 1, 2 }, // need units 1 and 2, both free on U1
170 1, // min value
171 1, // expected U1, units 1,2 are free
172 {}
173 },
174 {
175 "Different value conflict",
176 "U", "LM358", "OpAmp_Dual",
177 { { 1, { { "LM741", "OpAmp_Dual", 1 } } } }, // U1 has a different value
178 { 1 }, // need unit 1
179 1, // min value
180 2, // expected U2, can't share U1 with a different value
181 {}
182 },
183 {
184 "Previously used reference in tracker",
185 "U", "LM358", "OpAmp_Dual",
186 {}, // no currently used references
187 { 1 }, // need unit 1
188 1, // min value
189 2, // expected U2, U1 was previously used
190 { "U1" } // U1 preloaded in tracker
191 },
192 {
193 "Min value higher than available",
194 "U", "LM358", "OpAmp_Dual",
195 { { 5, { { "LM358", "OpAmp_Dual", 1 } } } }, // U5 unit 1 in use
196 { 2 }, // need unit 2
197 10, // min value
198 10, // expected U10, U5 has unit 2 free but min value forces 10
199 {}
200 },
201 {
202 "Negative units filtered out",
203 "U", "LM358", "OpAmp_Dual",
204 {}, // no existing references
205 { -1, 1, -5, 2 }, // mix of negative and positive units
206 1, // min value
207 1, // expected U1, only units 1,2 are considered
208 {}
209 },
210 {
211 "Complex scenario with gaps",
212 "IC", "74HC00", "Logic_Gate",
213 { { 2, { { "74HC00", "Logic_Gate", 1 } } }, // IC2 unit 1 used
214 { 4, { { "74HC00", "Logic_Gate", 2 } } } }, // IC4 unit 2 used
215 { 1, 3 }, // need units 1 and 3
216 1, // min value
217 3, // expected IC3, IC1 was previously used, IC2 conflicts on unit 1
218 { "IC1" } // IC1 preloaded in tracker
219 },
220
221 // Conflict vectors preserved from the removed ValidateUnitConflictDetection table, now decided
222 // by REFDES_TRACKER::areUnitsAvailable instead of a copy of it
223 {
224 "Units available - no conflicts",
225 "U", "LM358", "OpAmp_Dual",
226 { { 1, { { "LM358", "OpAmp_Dual", 3 }, { "LM358", "OpAmp_Dual", 4 } } } }, // U1 units 3,4 in use
227 { 1, 2 }, // need units 1 and 2
228 1, // min value
229 1, // expected U1, requested units don't conflict with existing units
230 {}
231 },
232 {
233 "Units conflict - same unit requested",
234 "U", "LM358", "OpAmp_Dual",
235 { { 1, { { "LM358", "OpAmp_Dual", 1 }, { "LM358", "OpAmp_Dual", 2 } } } }, // U1 units 1,2 in use
236 { 2, 3 }, // need units 2 and 3
237 1, // min value
238 2, // expected U2, unit 2 is already in use on U1
239 {}
240 },
241 {
242 "Value mismatch - can't share reference",
243 "R", "1k", "Resistor",
244 { { 1, { { "2k", "Resistor", 1 } } } }, // R1 has a different value
245 { 2 }, // need unit 2
246 1, // min value
247 2, // expected R2, can't share a refdes with a different value
248 {}
249 },
250 {
251 "Library mismatch - can't share reference",
252 "U", "LM358", "OpAmp_Dual",
253 { { 1, { { "LM358", "OpAmp_Single", 1 } } } }, // U1 has a different library part
254 { 2 }, // need unit 2
255 1, // min value
256 2, // expected U2, can't share a refdes with a different library part
257 {}
258 },
259 {
260 "Empty existing units - should be available",
261 "IC", "74HC00", "Logic_Gate",
262 {}, // no existing units to conflict with
263 { 1, 2, 3, 4 }, // requesting all 4 units
264 1, // min value
265 1, // expected IC1
266 {}
267 },
268 {
269 "Negative units filtered out with occupied neighbour",
270 "U", "LM324", "OpAmp_Quad",
271 { { 1, { { "LM324", "OpAmp_Quad", 2 } } } }, // U1 unit 2 in use
272 { -1, 1, -5, 3 }, // only units 1,3 are considered, neither conflicts
273 1, // min value
274 1, // expected U1
275 {}
276 },
277 {
278 "All units conflict",
279 "U", "LM324", "OpAmp_Quad",
280 { { 1, { { "LM324", "OpAmp_Quad", 1 }, { "LM324", "OpAmp_Quad", 2 },
281 { "LM324", "OpAmp_Quad", 3 }, { "LM324", "OpAmp_Quad", 4 } } } }, // U1 all units in use
282 { 1, 2, 3, 4 }, // requesting all units
283 1, // min value
284 2, // expected U2, all requested units are already in use on U1
285 {}
286 },
287 {
288 "Partial conflict with mixed values",
289 "R", "1k", "Resistor",
290 { { 1, { { "1k", "Resistor", 1 } } } }, // R1 unit 1 in use, same value
291 { 1, 2 }, // need units 1 and 2
292 1, // min value
293 2, // expected R2, unit 1 conflicts even with a matching value
294 {}
295 },
296 {
297 "Complex multi-unit scenario",
298 "U", "LM339", "Comparator_Quad",
299 { { 1, { { "LM339", "Comparator_Quad", 1 }, { "LM339", "Comparator_Quad", 3 } } } }, // U1 units 1,3 in use
300 { 2, 4 }, // need units 2 and 4, neither conflicts with existing 1,3
301 1, // min value
302 1, // expected U1
303 {}
304 }
305};
306
307BOOST_FIXTURE_TEST_SUITE( RefDesTrackerUnits, TEST_REFDES_TRACKER_UNITS )
308
309BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_BasicCases )
310{
311 for( const REFDES_UNITS_TEST_CASE& testCase : refdesUnitsTestCases )
312 runTestCase( testCase );
313}
314
315BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_EdgeCases )
316{
317 REFDES_TRACKER tracker;
318
319 // Test empty required units vector - should find completely unused reference
320 SCH_REFERENCE testRef = createTestReference( "R", "1k", 1 );
321 std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
322 std::vector<int> emptyUnits;
323
324 tracker.SetReuseRefDes( false );
325 int result = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
327 BOOST_CHECK( tracker.Contains( "R1" ) );
328
329 // Test with some references already in tracker
330 tracker.Insert( "R3" );
331 result = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
332 BOOST_CHECK_EQUAL( result, 2 ); // Should skip R1 (already inserted above) and get R2
333
334 // Test with negative units (should be filtered out)
335 std::vector<int> mixedUnits = {-1, 1, -5, 2};
336 SCH_REFERENCE testRef2 = createTestReference( "C", "100nF", 1 );
337 result = tracker.GetNextRefDesForUnits( testRef2, emptyMap, mixedUnits, 1 );
339}
340
341BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_UsagePattern )
342{
343 REFDES_TRACKER tracker;
344
345 // Demonstrate actual usage pattern for GetNextRefDesForUnits with our test helper
346 SCH_REFERENCE testRef = createTestReference( "U", "LM358", 1 );
347
348 // Create map of currently used references
349 std::map<int, std::vector<SCH_REFERENCE>> refNumberMap;
350 refNumberMap[1] = { createTestReference("U", "LM358", 1),
351 createTestReference("U", "LM358", 2) }; // U1 has units 1,2 used
352 refNumberMap[3] = { createTestReference("U", "LM358", 1) }; // U3 has unit 1 used
353
354 // Specify required units for new symbol
355 std::vector<int> requiredUnits = {1, 2};
356
357 tracker.SetReuseRefDes( false );
358
359 // Get next available reference number
360 int nextRefNum = tracker.GetNextRefDesForUnits( testRef, refNumberMap, requiredUnits, 1 );
361
362 // Should return 2 (U2) since U1 conflicts (units 1,2 already used) and U2 is available
363 BOOST_CHECK_EQUAL( nextRefNum, 2 );
364 BOOST_CHECK( tracker.Contains( "U2" ) );
365
366 // Test case where units are available in existing reference
367 std::vector<int> requiredUnits2 = {3, 4}; // These should be available in U1
368 refNumberMap[3] = { createTestReference("U", "LM358", 1) }; // U1 only has unit 1 and 2 used
369
370 nextRefNum = tracker.GetNextRefDesForUnits( testRef, refNumberMap, requiredUnits2, 1 );
371 BOOST_CHECK_EQUAL( nextRefNum, 1 ); // U1 should work since units 3,4 are available
372
373 // Test different value conflict
374 SCH_REFERENCE differentValueRef = createTestReference( "U", "LM741", 1 );
375 refNumberMap[4] = { createTestReference("U", "LM358", 1) }; // U4 has different value
376
377 nextRefNum = tracker.GetNextRefDesForUnits( differentValueRef, refNumberMap, {1}, 4 );
378 BOOST_CHECK_EQUAL( nextRefNum, 5 ); // Should skip U4 due to value conflict
379}
380
381BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_ThreadSafety )
382{
383 REFDES_TRACKER tracker( true ); // Enable thread safety
384
385 // Test that GetNextRefDesForUnits works with thread safety enabled
386 SCH_REFERENCE testRef = createTestReference( "U", "LM358", 1 );
387 std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
388 std::vector<int> requiredUnits = {1, 2};
389
390 tracker.SetReuseRefDes( false );
391 int result = tracker.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
393 BOOST_CHECK( tracker.Contains( "U1" ) );
394
395 // Test multiple calls work correctly with thread safety
396 result = tracker.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
398 BOOST_CHECK( tracker.Contains( "U2" ) );
399
400 // Test with conflicts and thread safety
401 std::map<int, std::vector<SCH_REFERENCE>> conflictMap;
402 conflictMap[3] = { createTestReference("U", "LM358", 1) }; // U3 unit 1 in use
403
404 result = tracker.GetNextRefDesForUnits( testRef, conflictMap, requiredUnits, 1 );
405 BOOST_CHECK_EQUAL( result, 4 ); // Should skip U1,U2 (in tracker) and U3 (conflicted)
406}
407
408BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_Integration )
409{
410 REFDES_TRACKER tracker;
411
412 // Test that GetNextRefDesForUnits properly integrates with existing Insert/Contains
413 tracker.Insert( "U1" );
414 tracker.Insert( "U3" );
415
416 BOOST_CHECK( tracker.Contains( "U1" ) );
417 BOOST_CHECK( tracker.Contains( "U3" ) );
418 BOOST_CHECK( !tracker.Contains( "U2" ) );
419
420 // Test GetNextRefDesForUnits with preloaded tracker
421 SCH_REFERENCE testRef = createTestReference( "U", "LM358", 1 );
422 std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
423 std::vector<int> requiredUnits = {1, 2};
424
425 tracker.SetReuseRefDes( false );
426
427 // Should get U2 since U1 is already in tracker (preloaded) and U3 is also preloaded
428 int next = tracker.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
430 BOOST_CHECK( tracker.Contains( "U2" ) );
431
432 // Test with higher minimum value
433 next = tracker.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 5 );
435 BOOST_CHECK( tracker.Contains( "U5" ) );
436
437 // Test integration with serialization
438 std::string serialized = tracker.Serialize();
439 REFDES_TRACKER tracker2;
440 BOOST_CHECK( tracker2.Deserialize( serialized ) );
441
442 // Verify deserialized tracker has the same state
443 BOOST_CHECK( tracker2.Contains( "U1" ) );
444 BOOST_CHECK( tracker2.Contains( "U2" ) );
445 BOOST_CHECK( tracker2.Contains( "U3" ) );
446 BOOST_CHECK( tracker2.Contains( "U5" ) );
447
448 tracker2.SetReuseRefDes( false );
449
450 // GetNextRefDesForUnits should work with deserialized tracker
451 next = tracker2.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
452 BOOST_CHECK_EQUAL( next, 4 ); // Should get U4 (first available after U1,U2,U3,U5)
453}
454
455BOOST_AUTO_TEST_CASE( Serialization_WithTrackedReferences )
456{
457 REFDES_TRACKER tracker;
458
459 // Add some references using both Insert and GetNextRefDesForUnits
460 tracker.Insert( "R1" );
461 tracker.Insert( "R3" );
462
463 tracker.SetReuseRefDes( false );
464
465 // Use GetNextRefDesForUnits to get next reference
466 SCH_REFERENCE testRef = createTestReference( "R", "1k", 1 );
467 std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
468 std::vector<int> requiredUnits = {1};
469
470 int next = tracker.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
471 BOOST_CHECK_EQUAL( next, 2 ); // Should get R2
472
473 // Test with different prefix
474 SCH_REFERENCE capacitorRef = createTestReference( "C", "100nF", 1 );
475 next = tracker.GetNextRefDesForUnits( capacitorRef, emptyMap, requiredUnits, 5 );
476 BOOST_CHECK_EQUAL( next, 5 ); // Should get C5
477
478 // Test serialization
479 std::string serialized = tracker.Serialize();
480 BOOST_CHECK( !serialized.empty() );
481
482 // Test deserialization
483 REFDES_TRACKER tracker2;
484 BOOST_CHECK( tracker2.Deserialize( serialized ) );
485
486 BOOST_CHECK( tracker2.Contains( "R1" ) );
487 BOOST_CHECK( tracker2.Contains( "R2" ) );
488 BOOST_CHECK( tracker2.Contains( "R3" ) );
489 BOOST_CHECK( tracker2.Contains( "C5" ) );
490
491 tracker2.SetReuseRefDes( false );
492
493 // Test GetNextRefDesForUnits with deserialized tracker
494 next = tracker2.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
495 BOOST_CHECK_EQUAL( next, 4 ); // Next reference should be R4
496
497 // Test with unit conflicts after deserialization
498 std::map<int, std::vector<SCH_REFERENCE>> conflictMap;
499 conflictMap[4] = { createTestReference("R", "2k", 1) }; // R4 different value
500
501 next = tracker2.GetNextRefDesForUnits( testRef, conflictMap, requiredUnits, 1 );
502 BOOST_CHECK_EQUAL( next, 5 ); // Should skip R4 due to value conflict
503}
504
A generic fixture for loading schematics and associated settings for qa tests.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
Class to efficiently track reference designators and provide next available designators.
bool Deserialize(const std::string &aData)
Deserialize tracker data from string representation.
int GetNextRefDesForUnits(const SCH_REFERENCE &aRef, const std::map< int, std::vector< SCH_REFERENCE > > &aRefNumberMap, const std::vector< int > &aRequiredUnits, int aMinValue)
Get the next available reference designator number for multi-unit symbols.
void SetReuseRefDes(bool aReuse)
bool Insert(const std::string &aRefDes)
Insert a reference designator into the tracker.
std::string Serialize() const
Serialize the tracker data to a compact string representation.
bool Contains(const std::string &aRefDes) const
Check if a reference designator exists in the tracker.
A helper to define a symbol's reference designator in a schematic.
void SetValue(const wxString &aValue)
void SetRef(const wxString &aReference)
void SetUnit(int aUnit)
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Schematic symbol object.
Definition sch_symbol.h:69
void SetLibId(const LIB_ID &aName)
void runTestCase(const REFDES_UNITS_TEST_CASE &testCase)
std::vector< std::unique_ptr< SCH_SYMBOL > > m_symbols
SCH_REFERENCE createTestReference(const std::string &aRefPrefix, const std::string &aValue, int aUnit, const std::string &aLibName="TestPart")
SCH_REFERENCE holds a raw pointer to its symbol and CompareLibName dereferences it,...
CITER next(CITER it)
Definition ptree.cpp:120
One existing reference occupying a reference number.
std::map< int, std::vector< EXISTING_REF > > m_refNumberMap
BOOST_AUTO_TEST_CASE(GetNextRefDesForUnits_BasicCases)
static const std::vector< REFDES_UNITS_TEST_CASE > refdesUnitsTestCases
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE_END()
std::string path
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")