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
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)
const std::string FormatAsString()
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.
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
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:38
static const char Default[]
the name of the default NETCLASS
Definition netclass.h:40
void SetClearance(int aClearance)
Definition netclass.h:125
void SetDescription(const wxString &aDesc)
Definition netclass.h:120
int GetClearance() const
Definition netclass.h:123
void SetTrackWidth(int aWidth)
Definition netclass.h:133
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...
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.
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
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_shape.h:72
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")