KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_net_settings.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
20#include <boost/test/unit_test.hpp>
21
23#include <netclass.h>
26
27#include <wx/filename.h>
28#include <wx/stdpaths.h>
29#include <wx/utils.h>
30
31#include <fstream>
32
33
34BOOST_AUTO_TEST_SUITE( NetSettingsTests )
35
36
37// Regression guard for the dirty-check used by the project save framework.
38// Without m_netChainClasses included in operator==, edits to chain-class
39// assignments returned "no change" and were silently dropped on close.
40BOOST_AUTO_TEST_CASE( ChainClassAssignmentAffectsEquality )
41{
42 NET_SETTINGS a( nullptr, "" );
43 NET_SETTINGS b( nullptr, "" );
44
45 BOOST_CHECK( a == b );
46
47 a.SetNetChainClass( wxS( "CHAIN_A" ), wxS( "Default" ) );
48
49 BOOST_CHECK( a != b );
50
51 b.SetNetChainClass( wxS( "CHAIN_A" ), wxS( "Default" ) );
52
53 BOOST_CHECK( a == b );
54}
55
56
57// Verify that differing chain-class values (same chain key) compare unequal,
58// and that clearing an assignment via empty class string restores equality.
59BOOST_AUTO_TEST_CASE( ChainClassValueAndClearAffectEquality )
60{
61 NET_SETTINGS a( nullptr, "" );
62 NET_SETTINGS b( nullptr, "" );
63
64 a.SetNetChainClass( wxS( "CHAIN_A" ), wxS( "Default" ) );
65 b.SetNetChainClass( wxS( "CHAIN_A" ), wxS( "HighSpeed" ) );
66
67 BOOST_CHECK( a != b );
68
69 b.SetNetChainClass( wxS( "CHAIN_A" ), wxS( "Default" ) );
70
71 BOOST_CHECK( a == b );
72
73 a.SetNetChainClass( wxS( "CHAIN_A" ), wxString() );
74
75 BOOST_CHECK( a != b );
76
77 b.SetNetChainClass( wxS( "CHAIN_A" ), wxString() );
78
79 BOOST_CHECK( a == b );
80}
81
82
83// Asymmetric-size guard for the std::equal calls in operator==. A 3-iterator
84// std::equal returns true when the LHS is a prefix of the RHS, which silently
85// dropped one-sided edits.
86BOOST_AUTO_TEST_CASE( AssignmentSizeDifferencesAffectEquality )
87{
88 NET_SETTINGS a( nullptr, "" );
89 NET_SETTINGS b( nullptr, "" );
90
91 b.SetNetclassLabelAssignment( wxS( "NET_A" ), { wxS( "Default" ) } );
92
93 BOOST_CHECK( a != b );
94 BOOST_CHECK( b != a );
95}
96
97
98// Returns true if the effective netclass for aNetName resolves to a netclass named aExpected,
99// either directly or as a constituent of a composite effective netclass.
100static bool resolvesToNetclass( NET_SETTINGS& aSettings, const wxString& aNetName,
101 const wxString& aExpected )
102{
103 std::shared_ptr<NETCLASS> resolved = aSettings.GetEffectiveNetClass( aNetName );
104
105 if( !resolved )
106 return false;
107
108 if( resolved->GetName() == aExpected )
109 return true;
110
111 for( NETCLASS* constituent : resolved->GetConstituentNetclasses() )
112 {
113 if( constituent && constituent->GetName() == aExpected )
114 return true;
115 }
116
117 return false;
118}
119
120
121// Chain-derived netclass pattern assignments must contribute to effective netclass resolution
122// alongside user-authored patterns, but must be cleanable independently so stale chain entries
123// do not persist across netlist updates.
124BOOST_AUTO_TEST_CASE( ChainPatternAssignmentResolvesAndClears )
125{
126 NET_SETTINGS settings( nullptr, "" );
127
128 std::shared_ptr<NETCLASS> highSpeed = std::make_shared<NETCLASS>( wxS( "HighSpeed" ), false );
129 std::map<wxString, std::shared_ptr<NETCLASS>> classes;
130 classes[wxS( "HighSpeed" )] = highSpeed;
131 settings.SetNetclasses( classes );
132
133 settings.SetChainPatternAssignment( NET_CHAIN_SOURCE::SCHEMATIC, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) );
134
135 BOOST_CHECK( resolvesToNetclass( settings, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) ) );
136
138
139 BOOST_CHECK( !resolvesToNetclass( settings, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) ) );
140}
141
142
143// Clearing chain-derived patterns must not disturb user-authored patterns.
144BOOST_AUTO_TEST_CASE( ClearChainPatternAssignmentsLeavesUserPatterns )
145{
146 NET_SETTINGS settings( nullptr, "" );
147
148 std::shared_ptr<NETCLASS> highSpeed = std::make_shared<NETCLASS>( wxS( "HighSpeed" ), false );
149 std::shared_ptr<NETCLASS> power = std::make_shared<NETCLASS>( wxS( "Power" ), false );
150 std::map<wxString, std::shared_ptr<NETCLASS>> classes;
151 classes[wxS( "HighSpeed" )] = highSpeed;
152 classes[wxS( "Power" )] = power;
153 settings.SetNetclasses( classes );
154
155 settings.SetNetclassPatternAssignment( wxS( "VCC_*" ), wxS( "Power" ) );
156 settings.SetChainPatternAssignment( NET_CHAIN_SOURCE::SCHEMATIC, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) );
157
158 BOOST_CHECK( resolvesToNetclass( settings, wxS( "VCC_3V3" ), wxS( "Power" ) ) );
159 BOOST_CHECK( resolvesToNetclass( settings, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) ) );
160
162
163 BOOST_CHECK( resolvesToNetclass( settings, wxS( "VCC_3V3" ), wxS( "Power" ) ) );
164 BOOST_CHECK( !resolvesToNetclass( settings, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) ) );
165}
166
167
168// The schematic and the board share one NET_SETTINGS and each derives chain patterns from the
169// membership only it can see. A rebuild by one must leave the other's entries alone, otherwise a
170// board resync before DRC or save reverts the schematic's chain netclasses.
171BOOST_AUTO_TEST_CASE( ChainPatternAssignmentSourcesAreIndependent )
172{
173 NET_SETTINGS settings( nullptr, "" );
174
175 std::shared_ptr<NETCLASS> highSpeed = std::make_shared<NETCLASS>( wxS( "HighSpeed" ), false );
176 std::shared_ptr<NETCLASS> power = std::make_shared<NETCLASS>( wxS( "Power" ), false );
177 std::map<wxString, std::shared_ptr<NETCLASS>> classes;
178 classes[wxS( "HighSpeed" )] = highSpeed;
179 classes[wxS( "Power" )] = power;
180 settings.SetNetclasses( classes );
181
182 settings.SetChainPatternAssignment( NET_CHAIN_SOURCE::SCHEMATIC, wxS( "DDR_DQ0" ),
183 wxS( "HighSpeed" ) );
184 settings.SetChainPatternAssignment( NET_CHAIN_SOURCE::BOARD, wxS( "VCC_3V3" ),
185 wxS( "Power" ) );
186
187 BOOST_REQUIRE( resolvesToNetclass( settings, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) ) );
188 BOOST_REQUIRE( resolvesToNetclass( settings, wxS( "VCC_3V3" ), wxS( "Power" ) ) );
189
191
192 BOOST_CHECK( resolvesToNetclass( settings, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) ) );
193 BOOST_CHECK( !resolvesToNetclass( settings, wxS( "VCC_3V3" ), wxS( "Power" ) ) );
195 BOOST_CHECK( !settings.HasChainPatternAssignments( NET_CHAIN_SOURCE::BOARD ) );
196
198
199 BOOST_CHECK( !resolvesToNetclass( settings, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) ) );
200}
201
202
203// ClearNetChainClasses must remove all chain->class entries.
204BOOST_AUTO_TEST_CASE( ClearNetChainClassesRemovesAllEntries )
205{
206 NET_SETTINGS settings( nullptr, "" );
207
208 settings.SetNetChainClass( wxS( "CHAIN_A" ), wxS( "Default" ) );
209 settings.SetNetChainClass( wxS( "CHAIN_B" ), wxS( "HighSpeed" ) );
210
211 BOOST_CHECK_EQUAL( settings.GetNetChainClasses().size(), 2u );
212
213 settings.ClearNetChainClasses();
214
215 BOOST_CHECK( settings.GetNetChainClasses().empty() );
216 BOOST_CHECK( settings.GetNetChainClass( wxS( "CHAIN_A" ) ).IsEmpty() );
217}
218
219
220// m_netClassChainPatternAssignments is derived state rebuilt from m_netChainNetClasses plus the
221// current chain membership. It must NOT contribute to operator==, otherwise a no-op netlist
222// rebuild marks the project dirty even when the user made no edit. The persisted
223// m_netChainNetClasses map (covered below) is the source of truth for equality.
224BOOST_AUTO_TEST_CASE( ChainPatternAssignmentExcludedFromEquality )
225{
226 NET_SETTINGS a( nullptr, "" );
227 NET_SETTINGS b( nullptr, "" );
228
229 std::shared_ptr<NETCLASS> highSpeed = std::make_shared<NETCLASS>( wxS( "HighSpeed" ), false );
230 std::shared_ptr<NETCLASS> power = std::make_shared<NETCLASS>( wxS( "Power" ), false );
231 std::map<wxString, std::shared_ptr<NETCLASS>> classes;
232 classes[wxS( "HighSpeed" )] = highSpeed;
233 classes[wxS( "Power" )] = power;
234
235 a.SetNetclasses( classes );
236 b.SetNetclasses( classes );
237
238 BOOST_CHECK( a == b );
239
240 a.SetChainPatternAssignment( NET_CHAIN_SOURCE::SCHEMATIC, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) );
241
242 BOOST_CHECK( a == b );
243
244 a.SetChainPatternAssignment( NET_CHAIN_SOURCE::SCHEMATIC, wxS( "VCC_3V3" ), wxS( "Power" ) );
245 b.SetChainPatternAssignment( NET_CHAIN_SOURCE::BOARD, wxS( "OTHER" ), wxS( "HighSpeed" ) );
246
247 BOOST_CHECK( a == b );
248
250
251 BOOST_CHECK( a == b );
252}
253
254
255// Persisted-state regression guard for the "net_chain_classes" JSON key. In-memory
256// equality tests cannot detect a renamed/missing JSON key or a broken read lambda --
257// those failures present as silent loss of every chain->class assignment on project
258// reopen. Exercise the full Store -> serialize -> parse -> Load round trip so any
259// drift in the JSON contract fails the suite immediately.
260BOOST_AUTO_TEST_CASE( NetChainClassesJsonRoundTrip )
261{
262 NET_SETTINGS source( nullptr, "" );
263
264 source.SetNetChainClass( wxS( "CHAIN_A" ), wxS( "Default" ) );
265 source.SetNetChainClass( wxS( "CHAIN_B" ), wxS( "HighSpeed" ) );
266 source.SetNetChainClass( wxS( "CHAIN_WITH_SPACES and: punctuation!" ), wxS( "Power" ) );
267 source.SetNetChainClass( wxS( "Unicode_éèê" ), wxS( "RF_µwave" ) );
268
269 BOOST_REQUIRE( source.Store() );
270
271 // Round-trip through the text form so a renamed key, broken serializer, or broken
272 // parser all surface as a test failure. FormatAsString -> parse mirrors what
273 // SaveToFile / LoadFromFile do on disk.
274 std::string serialized = source.FormatAsString();
275 nlohmann::json reparsed = nlohmann::json::parse( serialized );
276
277 BOOST_REQUIRE( reparsed.contains( "net_chain_classes" ) );
278 BOOST_REQUIRE( reparsed["net_chain_classes"].is_object() );
279
280 NET_SETTINGS sink( nullptr, "" );
281
282 // Seed the sink with a stale entry so a no-op reader would still leave it behind;
283 // the read lambda must clear() before applying the loaded map.
284 sink.SetNetChainClass( wxS( "STALE_CHAIN" ), wxS( "Default" ) );
285
286 JSON_SETTINGS_INTERNALS reparsedInternals;
287 static_cast<nlohmann::json&>( reparsedInternals ) = reparsed;
288 sink.Internals()->CloneFrom( reparsedInternals );
289 sink.Load();
290
291 BOOST_CHECK( sink.GetNetChainClasses().size() == source.GetNetChainClasses().size() );
292 BOOST_CHECK( sink.GetNetChainClasses() == source.GetNetChainClasses() );
293 BOOST_CHECK( sink.GetNetChainClass( wxS( "CHAIN_A" ) ) == wxS( "Default" ) );
294 BOOST_CHECK( sink.GetNetChainClass( wxS( "CHAIN_B" ) ) == wxS( "HighSpeed" ) );
295 BOOST_CHECK( sink.GetNetChainClass( wxS( "CHAIN_WITH_SPACES and: punctuation!" ) )
296 == wxS( "Power" ) );
297 BOOST_CHECK( sink.GetNetChainClass( wxS( "Unicode_éèê" ) ) == wxS( "RF_µwave" ) );
298 BOOST_CHECK( sink.GetNetChainClass( wxS( "STALE_CHAIN" ) ).IsEmpty() );
299 BOOST_CHECK( source == sink );
300}
301
302
303// An empty m_netChainClasses must serialize as an empty object (not be omitted, not be
304// emitted as null) and round-trip back to an empty map without leaving stale entries.
305BOOST_AUTO_TEST_CASE( NetChainClassesJsonRoundTripEmpty )
306{
307 NET_SETTINGS source( nullptr, "" );
308
309 BOOST_REQUIRE( source.Store() );
310
311 std::string serialized = source.FormatAsString();
312 nlohmann::json reparsed = nlohmann::json::parse( serialized );
313
314 BOOST_REQUIRE( reparsed.contains( "net_chain_classes" ) );
315 BOOST_CHECK( reparsed["net_chain_classes"].is_object() );
316 BOOST_CHECK( reparsed["net_chain_classes"].empty() );
317
318 NET_SETTINGS sink( nullptr, "" );
319
320 sink.SetNetChainClass( wxS( "STALE" ), wxS( "Default" ) );
321
322 JSON_SETTINGS_INTERNALS reparsedInternals;
323 static_cast<nlohmann::json&>( reparsedInternals ) = reparsed;
324 sink.Internals()->CloneFrom( reparsedInternals );
325 sink.Load();
326
327 BOOST_CHECK( sink.GetNetChainClasses().empty() );
328}
329
330
331// Empty class strings are how chain assignments are cleared (SetNetChainClass with "")
332// and the writer must not emit them. An attacker-supplied or hand-edited JSON file
333// that contains an empty string value must also be ignored on load, matching the
334// in-memory clear semantics tested in ChainClassValueAndClearAffectEquality.
335BOOST_AUTO_TEST_CASE( NetChainClassesJsonDropsEmptyValues )
336{
337 NET_SETTINGS source( nullptr, "" );
338
339 source.SetNetChainClass( wxS( "KEEP" ), wxS( "Default" ) );
340
341 BOOST_REQUIRE( source.Store() );
342
343 nlohmann::json reparsed = nlohmann::json::parse( source.FormatAsString() );
344
345 BOOST_REQUIRE( reparsed.contains( "net_chain_classes" ) );
346 BOOST_REQUIRE_EQUAL( reparsed["net_chain_classes"].size(), 1u );
347
348 // Inject a hand-crafted empty-value entry to confirm the reader rejects it.
349 reparsed["net_chain_classes"]["EMPTY"] = "";
350
351 NET_SETTINGS sink( nullptr, "" );
352
353 JSON_SETTINGS_INTERNALS reparsedInternals;
354 static_cast<nlohmann::json&>( reparsedInternals ) = reparsed;
355 sink.Internals()->CloneFrom( reparsedInternals );
356 sink.Load();
357
358 BOOST_CHECK_EQUAL( sink.GetNetChainClasses().size(), 1u );
359 BOOST_CHECK( sink.GetNetChainClass( wxS( "KEEP" ) ) == wxS( "Default" ) );
360 BOOST_CHECK( sink.GetNetChainClass( wxS( "EMPTY" ) ).IsEmpty() );
361}
362
363
364// Without m_netChainNetClasses in operator==, assigning a netclass to a chain reported "no
365// change" and the project was never written, so the assignment vanished on reopen.
366BOOST_AUTO_TEST_CASE( ChainNetclassAssignmentAffectsEquality )
367{
368 NET_SETTINGS a( nullptr, "" );
369 NET_SETTINGS b( nullptr, "" );
370
371 a.SetNetChainNetClass( wxS( "CHAIN_A" ), wxS( "HighSpeed" ) );
372
373 BOOST_CHECK( a != b );
374
375 b.SetNetChainNetClass( wxS( "CHAIN_A" ), wxS( "Power" ) );
376
377 BOOST_CHECK( a != b );
378
379 b.SetNetChainNetClass( wxS( "CHAIN_A" ), wxS( "HighSpeed" ) );
380
381 BOOST_CHECK( a == b );
382
383 a.SetNetChainNetClass( wxS( "CHAIN_A" ), wxString() );
384
385 BOOST_CHECK( a != b );
386
388
389 BOOST_CHECK( a == b );
390}
391
392
393// Persisted-state regression guard for the "net_chain_netclasses" JSON key. This map is the
394// only record of a chain's netclass on the board side, so a renamed key or broken lambda
395// presents as the netclass resolving until the next reload and then silently reverting.
396BOOST_AUTO_TEST_CASE( ChainNetclassesJsonRoundTrip )
397{
398 NET_SETTINGS source( nullptr, "" );
399
400 source.SetNetChainNetClass( wxS( "CHAIN_A" ), wxS( "HighSpeed" ) );
401 source.SetNetChainNetClass( wxS( "Unicode_éèê" ), wxS( "RF_µwave" ) );
402
403 BOOST_REQUIRE( source.Store() );
404
405 nlohmann::json reparsed = nlohmann::json::parse( source.FormatAsString() );
406
407 BOOST_REQUIRE( reparsed.contains( "net_chain_netclasses" ) );
408 BOOST_REQUIRE( reparsed["net_chain_netclasses"].is_object() );
409
410 // A hand-crafted empty value must be dropped, matching SetNetChainNetClass's clear
411 // semantics.
412 reparsed["net_chain_netclasses"]["EMPTY"] = "";
413
414 NET_SETTINGS sink( nullptr, "" );
415
416 // A no-op reader would leave this behind; the read lambda must clear() first.
417 sink.SetNetChainNetClass( wxS( "STALE_CHAIN" ), wxS( "Default" ) );
418
419 JSON_SETTINGS_INTERNALS reparsedInternals;
420 static_cast<nlohmann::json&>( reparsedInternals ) = reparsed;
421 sink.Internals()->CloneFrom( reparsedInternals );
422 sink.Load();
423
424 BOOST_CHECK( sink.GetNetChainNetClasses() == source.GetNetChainNetClasses() );
425 BOOST_CHECK( sink.GetNetChainNetClass( wxS( "CHAIN_A" ) ) == wxS( "HighSpeed" ) );
426 BOOST_CHECK( sink.GetNetChainNetClass( wxS( "Unicode_éèê" ) ) == wxS( "RF_µwave" ) );
427 BOOST_CHECK( sink.GetNetChainNetClass( wxS( "EMPTY" ) ).IsEmpty() );
428 BOOST_CHECK( sink.GetNetChainNetClass( wxS( "STALE_CHAIN" ) ).IsEmpty() );
429 BOOST_CHECK( source == sink );
430}
431
432
433// A disk-backed stand-in for the project file. The merge that decides whether a removed key
434// survives runs against the parent's on-disk baseline, which the FormatAsString round trips
435// above never populate.
437{
438public:
439 TEST_PROJECT_SETTINGS( const wxString& aFilename ) :
440 JSON_SETTINGS( aFilename, SETTINGS_LOC::NONE, 0, true, true, true )
441 {
442 }
443};
444
445
446// NESTED_SETTINGS flushes into the parent with a merge, which adds and updates but never deletes.
447// Without SetClearUnknownKeys() on the chain maps, a removed or renamed chain stays in the project
448// file and reappears with its old netclass on the next reload.
449BOOST_AUTO_TEST_CASE( RemovedChainNetclassIsDroppedFromProjectFile )
450{
451 wxFileName tempDir( wxStandardPaths::Get().GetTempDir(), wxEmptyString );
452 wxString tempName = wxString::Format( wxT( "kicad_qa_25065_%ld" ), wxGetProcessId() );
453 tempDir.AppendDir( tempName );
454
455 BOOST_REQUIRE( wxFileName::Mkdir( tempDir.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
456
457 wxFileName tempFile( tempDir.GetPath(), wxT( "project.json" ) );
458
459 {
460 TEST_PROJECT_SETTINGS parent( tempFile.GetFullPath() );
461 parent.SetManager( reinterpret_cast<SETTINGS_MANAGER*>( 1 ) );
462
463 NET_SETTINGS netSettings( &parent, "net_settings" );
464
465 netSettings.SetNetChainClass( wxS( "CHAIN_A" ), wxS( "DDR" ) );
466 netSettings.SetNetChainClass( wxS( "CHAIN_B" ), wxS( "DDR" ) );
467 netSettings.SetNetChainNetClass( wxS( "CHAIN_A" ), wxS( "HighSpeed" ) );
468 netSettings.SetNetChainNetClass( wxS( "CHAIN_B" ), wxS( "Power" ) );
469
470 parent.ReleaseNestedSettings( &netSettings );
471
472 BOOST_REQUIRE( parent.SaveToFile( wxEmptyString, true ) );
473 }
474
475 // Reopening from disk is what gives the parent its baseline; without it the merge has nothing
476 // to resurrect and the defect stays invisible.
477 {
478 TEST_PROJECT_SETTINGS parent( tempFile.GetFullPath() );
479 parent.SetManager( reinterpret_cast<SETTINGS_MANAGER*>( 1 ) );
480
481 BOOST_REQUIRE( parent.LoadFromFile() );
482
483 NET_SETTINGS netSettings( &parent, "net_settings" );
484
485 BOOST_REQUIRE( netSettings.LoadFromFile() );
486 BOOST_REQUIRE_EQUAL( netSettings.GetNetChainNetClasses().size(), 2u );
487 BOOST_REQUIRE_EQUAL( netSettings.GetNetChainClasses().size(), 2u );
488
489 netSettings.SetNetChainNetClass( wxS( "CHAIN_B" ), wxString() );
490 netSettings.SetNetChainClass( wxS( "CHAIN_B" ), wxString() );
491
492 parent.ReleaseNestedSettings( &netSettings );
493
494 // Deliberately unchecked: resurrected keys make the payload match the bytes already on
495 // disk, so the no-op-rewrite guard declines the write. Assert on the file instead.
496 parent.SaveToFile();
497 }
498
499 std::ifstream in( tempFile.GetFullPath().fn_str(), std::ios::in | std::ios::binary );
500 std::string contents( ( std::istreambuf_iterator<char>( in ) ),
501 std::istreambuf_iterator<char>() );
502
503 nlohmann::json onDisk = nlohmann::json::parse( contents );
504
505 BOOST_REQUIRE( onDisk.contains( "net_settings" ) );
506
507 const nlohmann::json& netChainNetClasses = onDisk["net_settings"]["net_chain_netclasses"];
508 const nlohmann::json& netChainClasses = onDisk["net_settings"]["net_chain_classes"];
509
510 BOOST_CHECK( netChainNetClasses.contains( "CHAIN_A" ) );
511 BOOST_CHECK( !netChainNetClasses.contains( "CHAIN_B" ) );
512 BOOST_CHECK( netChainClasses.contains( "CHAIN_A" ) );
513 BOOST_CHECK( !netChainClasses.contains( "CHAIN_B" ) );
514
515 wxRemoveFile( tempFile.GetFullPath() );
516 wxFileName::Rmdir( tempDir.GetPath() );
517}
518
519
520// In-place mutations to the default netclass (the UI's normal edit path goes
521// GetDefaultNetclass()->SetClearance(...) and does not swap the shared_ptr)
522// must propagate to operator==, otherwise editing default clearance / track
523// width during a session was silently dropped on close.
524BOOST_AUTO_TEST_CASE( DefaultNetclassInPlaceEditAffectsEquality )
525{
526 NET_SETTINGS a( nullptr, "" );
527 NET_SETTINGS b( nullptr, "" );
528
529 BOOST_CHECK( a == b );
530
531 // Use a value distinct from DEFAULT_CLEARANCE so the SetClearance call is a
532 // real change.
533 a.GetDefaultNetclass()->SetClearance( 350000 );
534
535 BOOST_CHECK( a != b );
536
537 b.GetDefaultNetclass()->SetClearance( 350000 );
538
539 BOOST_CHECK( a == b );
540
541 a.GetDefaultNetclass()->SetTrackWidth( 254000 );
542
543 BOOST_CHECK( a != b );
544}
545
546
547// Replacing the default netclass shared_ptr entirely (the persistence-layer
548// load path) must also flip equality.
549BOOST_AUTO_TEST_CASE( DefaultNetclassReplacementAffectsEquality )
550{
551 NET_SETTINGS a( nullptr, "" );
552 NET_SETTINGS b( nullptr, "" );
553
554 std::shared_ptr<NETCLASS> replacement = std::make_shared<NETCLASS>( NETCLASS::Default, true );
555 replacement->SetTrackWidth( 300000 );
556
557 a.SetDefaultNetclass( replacement );
558
559 BOOST_CHECK( a != b );
560}
561
562
563// A named netclass parameter edit (in-place via the map's shared_ptr) must
564// flip equality. Without the deep field comparison in operator==, the std::equal
565// over m_netClasses would only check pointer identity and miss the edit.
566BOOST_AUTO_TEST_CASE( NamedNetclassParameterEditAffectsEquality )
567{
568 NET_SETTINGS a( nullptr, "" );
569 NET_SETTINGS b( nullptr, "" );
570
571 std::shared_ptr<NETCLASS> highSpeedA = std::make_shared<NETCLASS>( wxS( "HighSpeed" ), false );
572 std::shared_ptr<NETCLASS> highSpeedB = std::make_shared<NETCLASS>( wxS( "HighSpeed" ), false );
573
574 a.SetNetclass( wxS( "HighSpeed" ), highSpeedA );
575 b.SetNetclass( wxS( "HighSpeed" ), highSpeedB );
576
577 BOOST_CHECK( a == b );
578
579 highSpeedA->SetClearance( 100000 );
580
581 BOOST_CHECK( a != b );
582
583 highSpeedB->SetClearance( 100000 );
584
585 BOOST_CHECK( a == b );
586}
587
588
589// CopyFrom should produce an instance that compares equal to the source under
590// the new content-aware operator==, regardless of underlying shared_ptr
591// addresses. Deep verification: spot-check fields that exercise the JSON
592// round-trip used by CopyFrom.
593BOOST_AUTO_TEST_CASE( CopyFromProducesEqualInstance )
594{
595 NET_SETTINGS source( nullptr, "" );
596
597 source.GetDefaultNetclass()->SetClearance( 250000 );
598 source.GetDefaultNetclass()->SetTrackWidth( 200000 );
599 source.GetDefaultNetclass()->SetDescription( wxS( "Project default" ) );
600
601 std::shared_ptr<NETCLASS> highSpeed = std::make_shared<NETCLASS>( wxS( "HighSpeed" ), false );
602 highSpeed->SetClearance( 150000 );
603 highSpeed->SetTrackWidth( 127000 );
604 std::map<wxString, std::shared_ptr<NETCLASS>> classes;
605 classes[wxS( "HighSpeed" )] = highSpeed;
606 source.SetNetclasses( classes );
607
608 source.SetNetclassPatternAssignment( wxS( "DDR_*" ), wxS( "HighSpeed" ) );
609 source.SetNetclassLabelAssignment( wxS( "EXACT_NET" ), { wxS( "HighSpeed" ) } );
610 source.SetNetChainClass( wxS( "DDR_BUS" ), wxS( "HighSpeed" ) );
611
612 NET_SETTINGS sink( nullptr, "" );
613 sink.CopyFrom( source );
614
615 BOOST_CHECK( source == sink );
616
617 // Verify the deep copy detached pointer ownership: editing source after
618 // CopyFrom must not affect sink.
619 source.GetDefaultNetclass()->SetClearance( 999999 );
620
621 BOOST_CHECK( source != sink );
623}
624
625
626// CopyFrom must drop pre-existing state in the sink rather than merging --
627// otherwise stale netclasses / pattern assignments from a previous load
628// would leak through.
629BOOST_AUTO_TEST_CASE( CopyFromDropsPreExistingSinkState )
630{
631 NET_SETTINGS source( nullptr, "" );
632 source.GetDefaultNetclass()->SetClearance( 100000 );
633
634 NET_SETTINGS sink( nullptr, "" );
635 sink.GetDefaultNetclass()->SetClearance( 999999 );
636
637 std::shared_ptr<NETCLASS> stale = std::make_shared<NETCLASS>( wxS( "StaleClass" ), false );
638 std::map<wxString, std::shared_ptr<NETCLASS>> staleClasses;
639 staleClasses[wxS( "StaleClass" )] = stale;
640 sink.SetNetclasses( staleClasses );
641
642 sink.SetNetclassPatternAssignment( wxS( "STALE_*" ), wxS( "StaleClass" ) );
643 sink.SetNetChainClass( wxS( "STALE_CHAIN" ), wxS( "StaleClass" ) );
644
645 sink.CopyFrom( source );
646
647 BOOST_CHECK( source == sink );
649 BOOST_CHECK( sink.GetNetclasses().empty() );
650 BOOST_CHECK( sink.GetNetclassPatternAssignments().empty() );
651 BOOST_CHECK( sink.GetNetChainClasses().empty() );
652}
653
654
655// Derived caches in the sink must be dropped, otherwise GetEffectiveNetClass
656// can return a pre-CopyFrom cached result that doesn't match the new source.
657BOOST_AUTO_TEST_CASE( CopyFromClearsStaleDerivedCaches )
658{
659 NET_SETTINGS sink( nullptr, "" );
660
661 std::shared_ptr<NETCLASS> stalePower = std::make_shared<NETCLASS>( wxS( "StalePower" ), false );
662 std::map<wxString, std::shared_ptr<NETCLASS>> staleClasses;
663 staleClasses[wxS( "StalePower" )] = stalePower;
664 sink.SetNetclasses( staleClasses );
665 sink.SetNetclassPatternAssignment( wxS( "VCC_*" ), wxS( "StalePower" ) );
666
667 // Warm the effective-class cache for a net that resolves via the stale pattern.
668 BOOST_REQUIRE( sink.GetEffectiveNetClass( wxS( "VCC_3V3" ) ) != nullptr );
669 BOOST_REQUIRE( sink.HasEffectiveNetClass( wxS( "VCC_3V3" ) ) );
670
671 NET_SETTINGS source( nullptr, "" );
672
673 sink.CopyFrom( source );
674
675 // The stale-pattern cache entry must NOT survive; the post-CopyFrom effective
676 // class for VCC_3V3 should resolve to the default since the source has no
677 // patterns or named classes.
678 BOOST_CHECK( !sink.HasEffectiveNetClass( wxS( "VCC_3V3" ) ) );
679}
680
681
682// Chain-derived pattern assignments are not persisted and must be cleared on
683// CopyFrom -- otherwise the sink's prior chain mappings leak past the swap.
684BOOST_AUTO_TEST_CASE( CopyFromClearsStaleChainDerivedPatterns )
685{
686 NET_SETTINGS sink( nullptr, "" );
687 std::shared_ptr<NETCLASS> highSpeed = std::make_shared<NETCLASS>( wxS( "HighSpeed" ), false );
688 std::map<wxString, std::shared_ptr<NETCLASS>> classes;
689 classes[wxS( "HighSpeed" )] = highSpeed;
690 sink.SetNetclasses( classes );
691 sink.SetChainPatternAssignment( NET_CHAIN_SOURCE::SCHEMATIC, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) );
692
693 BOOST_REQUIRE( resolvesToNetclass( sink, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) ) );
694
695 NET_SETTINGS source( nullptr, "" );
696
697 sink.CopyFrom( source );
698
699 BOOST_CHECK( !resolvesToNetclass( sink, wxS( "DDR_DQ0" ), wxS( "HighSpeed" ) ) );
700}
701
702
703// Self-assignment guard.
704BOOST_AUTO_TEST_CASE( CopyFromSelfIsNoop )
705{
706 NET_SETTINGS settings( nullptr, "" );
707 settings.GetDefaultNetclass()->SetClearance( 200000 );
708
709 settings.CopyFrom( settings );
710
711 BOOST_CHECK_EQUAL( settings.GetDefaultNetclass()->GetClearance(), 200000 );
712}
713
714
715// Regression guard for issue 24823: a sheet rename must retarget netclass pattern and net color
716// assignments from the old sheet path to the new one.
717BOOST_AUTO_TEST_CASE( RenameNetPathPrefixRetargetsAssignments )
718{
719 NET_SETTINGS settings( nullptr, "" );
720
721 std::shared_ptr<NETCLASS> highSpeed = std::make_shared<NETCLASS>( wxS( "HighSpeed" ), false );
722 std::map<wxString, std::shared_ptr<NETCLASS>> classes;
723 classes[wxS( "HighSpeed" )] = highSpeed;
724 settings.SetNetclasses( classes );
725
726 settings.SetNetclassPatternAssignment( wxS( "/Sheet1/CLK" ), wxS( "HighSpeed" ) );
727 settings.SetNetclassPatternAssignment( wxS( "/Sheet1/DATA*" ), wxS( "HighSpeed" ) );
728 settings.SetNetclassPatternAssignment( wxS( "/Sheet1/Sub/EN" ), wxS( "HighSpeed" ) );
729 settings.SetNetclassPatternAssignment( wxS( "/Other/RST" ), wxS( "HighSpeed" ) );
730 settings.SetNetclassPatternAssignment( wxS( "/Sheet10/BUS" ), wxS( "HighSpeed" ) );
731 settings.SetNetColorAssignment( wxS( "/Sheet1/CLK" ), KIGFX::COLOR4D( 1.0, 0.0, 0.0, 1.0 ) );
732
733 BOOST_REQUIRE( resolvesToNetclass( settings, wxS( "/Sheet1/CLK" ), wxS( "HighSpeed" ) ) );
734 BOOST_REQUIRE( !resolvesToNetclass( settings, wxS( "/Sheet2/CLK" ), wxS( "HighSpeed" ) ) );
735
736 BOOST_CHECK( settings.RenameNetPathPrefix( wxS( "/Sheet1/" ), wxS( "/Sheet2/" ) ) );
737
738 // Nets under the renamed sheet now resolve at the new path, including the wildcard and the
739 // deeper net, and no longer at the old one.
740 BOOST_CHECK( resolvesToNetclass( settings, wxS( "/Sheet2/CLK" ), wxS( "HighSpeed" ) ) );
741 BOOST_CHECK( resolvesToNetclass( settings, wxS( "/Sheet2/DATA0" ), wxS( "HighSpeed" ) ) );
742 BOOST_CHECK( resolvesToNetclass( settings, wxS( "/Sheet2/Sub/EN" ), wxS( "HighSpeed" ) ) );
743 BOOST_CHECK( !resolvesToNetclass( settings, wxS( "/Sheet1/CLK" ), wxS( "HighSpeed" ) ) );
744
745 // Unrelated and same-prefix-sibling patterns are untouched.
746 BOOST_CHECK( resolvesToNetclass( settings, wxS( "/Other/RST" ), wxS( "HighSpeed" ) ) );
747 BOOST_CHECK( resolvesToNetclass( settings, wxS( "/Sheet10/BUS" ), wxS( "HighSpeed" ) ) );
748
749 const std::map<wxString, KIGFX::COLOR4D>& colors = settings.GetNetColorAssignments();
750 BOOST_CHECK( colors.count( wxS( "/Sheet2/CLK" ) ) == 1 );
751 BOOST_CHECK( colors.count( wxS( "/Sheet1/CLK" ) ) == 0 );
752
753 BOOST_CHECK( !settings.RenameNetPathPrefix( wxS( "/Nope/" ), wxS( "/Nada/" ) ) );
754}
755
756
757BOOST_AUTO_TEST_CASE( SharedParserRejectsMalformedBoundsWithoutExpansion )
758{
759 BOOST_CHECK( !NET_SETTINGS::ParseBusVector( "D[..2]", nullptr, nullptr ) );
760 BOOST_CHECK( !NET_SETTINGS::ParseBusVector( "D[1..]", nullptr, nullptr ) );
761 BOOST_CHECK( !NET_SETTINGS::ParseBusVector( "D[1..2", nullptr, nullptr ) );
762 BOOST_CHECK( !NET_SETTINGS::ParseBusVector( "D[1..999999999999999999999999999999]", nullptr, nullptr ) );
763}
764
765
766BOOST_AUTO_TEST_CASE( SharedParserExpandsLargestVectorIndex )
767{
768 const long last = std::numeric_limits<long>::max();
769 const wxString text = wxString::Format( "D[%ld..%ld]", last - 1, last );
770 std::vector<wxString> members;
771 BOOST_REQUIRE( NET_SETTINGS::ParseBusVector( text, nullptr, &members ) );
772 BOOST_REQUIRE_EQUAL( members.size(), 2 );
773 BOOST_CHECK_EQUAL( members[1], wxString::Format( "D%ld", last ) );
774}
775
776
777BOOST_AUTO_TEST_CASE( SharedParserPreservesPadding )
778{
779 std::vector<wxString> members;
780 BOOST_REQUIRE( NET_SETTINGS::ParseBusVector( "D[01..03]", nullptr, &members ) );
781 BOOST_REQUIRE_EQUAL( members.size(), 3 );
782 BOOST_CHECK_EQUAL( members[0], wxString( "D01" ) );
783 BOOST_CHECK_EQUAL( members[2], wxString( "D03" ) );
784}
785
786
787BOOST_AUTO_TEST_CASE( SharedParserGroupPrefixBoundaryUsesSourceSpelling )
788{
789 size_t boundary = 99;
790 BOOST_REQUIRE( NET_SETTINGS::ParseBusGroup( "I^{2}C\\ BUS{A}", nullptr, nullptr, &boundary ) );
791 BOOST_CHECK_EQUAL( boundary, wxString( "I^{2}C\\ BUS" ).length() );
792 BOOST_REQUIRE( NET_SETTINGS::ParseBusGroup( "{A}", nullptr, nullptr, &boundary ) );
793 BOOST_CHECK_EQUAL( boundary, 0 );
794 boundary = 99;
795 BOOST_CHECK( !NET_SETTINGS::ParseBusGroup( "G{A", nullptr, nullptr, &boundary ) );
796 BOOST_CHECK_EQUAL( boundary, 99 );
797}
798
799
800// A composite held by an item outlives a netclass edit until resync, so it must keep its
801// constituents alive or GetName() reads freed names
802BOOST_AUTO_TEST_CASE( CompositeOwnsConstituents )
803{
804 NET_SETTINGS settings( nullptr, "" );
805 std::weak_ptr<NETCLASS> weakSignal;
806
807 {
808 std::shared_ptr<NETCLASS> signal = std::make_shared<NETCLASS>( wxS( "Signal" ), false );
809 weakSignal = signal;
810 settings.SetNetclass( wxS( "Signal" ), signal );
811 }
812
813 settings.SetNetclassLabelAssignment( wxS( "N1" ), { wxS( "Signal" ) } );
814
815 std::shared_ptr<NETCLASS> composite = settings.GetEffectiveNetClass( wxS( "N1" ) );
816
817 BOOST_REQUIRE_EQUAL( composite->GetConstituentNetclasses().size(), 2u );
818
819 settings.ClearNetclasses();
820
821 BOOST_REQUIRE( !weakSignal.expired() );
822 BOOST_CHECK_EQUAL( composite->GetName(), wxString( "Signal,Default" ) );
823
824 composite.reset();
825
826 BOOST_CHECK( weakSignal.expired() );
827}
828
829
void CloneFrom(const JSON_SETTINGS_INTERNALS &aOther)
virtual bool LoadFromFile(const wxString &aDirectory="")
Loads the backing file from disk and then calls Load()
virtual void Load()
Updates the parameters of this object based on the current JSON document contents.
void SetManager(SETTINGS_MANAGER *aManager)
JSON_SETTINGS_INTERNALS * Internals()
void ReleaseNestedSettings(NESTED_SETTINGS *aSettings)
Saves and frees a nested settings object, if it exists within this one.
JSON_SETTINGS(const wxString &aFilename, SETTINGS_LOC aLocation, int aSchemaVersion)
virtual bool SaveToFile(const wxString &aDirectory="", bool aForce=false)
Calls Store() and then writes the contents of the JSON document to a file.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
bool LoadFromFile(const wxString &aDirectory="") override
Loads the JSON document from the parent and then calls Load()
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:44
static const char Default[]
the name of the default NETCLASS
Definition netclass.h:46
void SetClearance(int aClearance)
Definition netclass.h:144
int GetClearance() const
Definition netclass.h:142
void SetTrackWidth(int aWidth)
Definition netclass.h:152
NET_SETTINGS stores various net-related settings in a project context.
const std::map< wxString, wxString > & GetNetChainNetClasses() const
void SetNetChainClass(const wxString &aChain, const wxString &aClass)
Assign a net chain to a named class (used by inNetChainClass() DRC scope).
void SetNetChainNetClass(const wxString &aChain, const wxString &aNetclass)
Assign the netclass a net chain applies to all of its member nets.
std::shared_ptr< NETCLASS > GetEffectiveNetClass(const wxString &aNetName)
Fetches the effective (may be aggregate) netclass for the given net name.
wxString GetNetChainClass(const wxString &aChain) const
Look up the class assigned to a chain. Empty string means "no class".
bool HasEffectiveNetClass(const wxString &aNetName) const
Determines if an effective netclass for the given net name has been cached.
bool RenameNetPathPrefix(const wxString &aOldPrefix, const wxString &aNewPrefix)
Retarget netclass patterns and net colors after a path prefix changes (sheet rename).
void SetNetclasses(const std::map< wxString, std::shared_ptr< NETCLASS > > &netclasses)
Sets all netclass Calling this method will reset the effective netclass calculation caches.
void SetNetclassLabelAssignment(const wxString &netName, const std::set< wxString > &netclasses)
Sets a net name to netclasses assignment Calling user is responsible for resetting the effective netc...
void ClearNetclasses()
Clears all netclasses Calling this method will reset the effective netclass calculation caches.
wxString GetNetChainNetClass(const wxString &aChain) const
Look up the netclass a chain applies to its members. Empty string means "none".
void SetNetclassPatternAssignment(const wxString &pattern, const wxString &netclass)
Sets a netclass pattern assignment Calling this method will reset the effective netclass calculation ...
const std::map< wxString, std::shared_ptr< NETCLASS > > & GetNetclasses() const
Gets all netclasses.
static bool ParseBusGroup(const wxString &aGroup, wxString *name, std::vector< wxString > *aMemberList, size_t *aPrefixEnd=nullptr)
Parse a bus group label into the name and a list of components.
std::shared_ptr< NETCLASS > GetDefaultNetclass() const
Gets the default netclass for the project.
void SetChainPatternAssignment(NET_CHAIN_SOURCE aSource, const wxString &pattern, const wxString &netclass)
Sets a chain-derived netclass pattern assignment owned by aSource.
const std::map< wxString, wxString > & GetNetChainClasses() const
static bool ParseBusVector(const wxString &aBus, wxString *aName, std::vector< wxString > *aMemberList)
Parse a bus vector (e.g.
const std::map< wxString, KIGFX::COLOR4D > & GetNetColorAssignments() const
Gets all net name to color assignments.
void ClearChainPatternAssignments(NET_CHAIN_SOURCE aSource)
Clears the chain-derived pattern assignments owned by aSource, leaving the other source's entries in ...
void CopyFrom(NET_SETTINGS &aOther)
Deep-copy the persisted contents of aOther into this instance.
std::vector< std::pair< std::unique_ptr< EDA_COMBINED_MATCHER >, wxString > > & GetNetclassPatternAssignments()
Gets the netclass pattern assignments.
void ClearNetChainClasses()
Removes all chain-to-class assignments.
void ClearNetChainNetClasses()
Removes all chain-to-netclass assignments.
bool HasChainPatternAssignments(NET_CHAIN_SOURCE aSource) const
Returns true if aSource has contributed any chain-derived pattern assignment.
void SetNetclass(const wxString &netclassName, std::shared_ptr< NETCLASS > &netclass)
Sets the given netclass Calling user is responsible for resetting the effective netclass calculation ...
void SetDefaultNetclass(std::shared_ptr< NETCLASS > netclass)
Sets the default netclass for the project Calling user is responsible for resetting the effective net...
void SetNetColorAssignment(const wxString &netName, const KIGFX::COLOR4D &color)
Sets a net to color assignment Calling user is responsible for resetting the effective netclass calcu...
TEST_PROJECT_SETTINGS(const wxString &aFilename)
static bool empty(const wxTextEntryBase *aCtrl)
@ NONE
Definition eda_fill.h:42
std::string source
SETTINGS_LOC
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(ChainClassAssignmentAffectsEquality)
static bool resolvesToNetclass(NET_SETTINGS &aSettings, const wxString &aNetName, const wxString &aExpected)
BOOST_CHECK_EQUAL(result, "25.4")