KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_erc_text_var_issue24442.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one at
18 * http://www.gnu.org/licenses/
19 */
20
23
24#include <advanced_config.h>
26#include <sch_marker.h>
27#include <sch_field.h>
28#include <sch_symbol.h>
29#include <map>
30#include <sch_screen.h>
31#include <sch_sheet.h>
32#include <tuple>
33#include <schematic.h>
34#include <erc/erc_settings.h>
35#include <erc/erc.h>
36#include <erc/erc_exclusion.h>
37#include <api/schematic/schematic_rules.pb.h>
38#include <json_common.h>
40#include <locale_io.h>
41#include <scoped_set_reset.h>
42
43
45{
46 void RefreshTextChecks( bool aRebuild )
47 {
48 if( aRebuild )
49 m_schematic->RebuildConnectivity();
50 else
51 m_schematic->Connectivity().Recalculate( *m_schematic );
52
53 if( ADVANCED_CFG::GetCfg().m_ConnectivityEngine )
54 m_schematic->Connectivity().PrepareTextChecks( *m_schematic );
55 }
56
58 std::unique_ptr<SCHEMATIC> m_schematic;
59};
60
61
62// Verifies that ${ERC_ERROR ...} and ${ERC_WARNING ...} tokens trigger an
63// ERC violation regardless of position in the text. Prior to the fix the
64// regex was anchored with `^...$` so only text starting with the token would
65// fire, breaking placeholder patterns like "Fill in: ${ERC_ERROR ...}".
67{
69 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
70 SCOPED_SET_RESET restore( enabled, enabled );
71
72 for( bool useEngine : { false, true } )
73 {
74 BOOST_TEST_CONTEXT( "engine=" << useEngine )
75 {
76 enabled = useEngine;
77 KI_TEST::LoadSchematic( m_settingsManager, "issue24442", m_schematic );
78
79 if( useEngine )
80 RefreshTextChecks( true );
81
82 ERC_SETTINGS& settings = m_schematic->ErcSettings();
83
84 // Silence noise from other ERC checks; we care only about generic ERC
85 // text-variable violations here.
86 for( int ii = 0; ii < ERCE_LAST; ++ii )
88
92
93 ERC_TESTER tester( m_schematic.get() );
94 tester.TestTextVars( nullptr );
95
96 SHEETLIST_ERC_ITEMS_PROVIDER errors( m_schematic.get() );
98
99 int genericErrors = 0;
100 int genericWarnings = 0;
101 int unresolvedVars = 0;
102 std::vector<wxString> errorMessages;
103 std::vector<wxString> warningMessages;
104
105 for( int ii = 0; ii < errors.GetCount(); ++ii )
106 {
107 std::shared_ptr<RC_ITEM> rc = errors.GetItem( ii );
108
109 if( rc->GetErrorCode() == ERCE_GENERIC_ERROR )
110 {
111 genericErrors++;
112 errorMessages.push_back( rc->GetErrorMessage( false ) );
113 }
114 else if( rc->GetErrorCode() == ERCE_GENERIC_WARNING )
115 {
116 genericWarnings++;
117 warningMessages.push_back( rc->GetErrorMessage( false ) );
118 }
119 else if( rc->GetErrorCode() == ERCE_UNRESOLVED_VARIABLE )
120 {
121 unresolvedVars++;
122 }
123 }
124
125 // The fixture schematic contains two ${ERC_ERROR ...} texts (one at start,
126 // one mid-string), two ${ERC_WARNING ...} texts (start + middle), and one
127 // escaped \${ERC_ERROR ...} that must NOT count.
128 BOOST_CHECK_EQUAL( genericErrors, 2 );
129 BOOST_CHECK_EQUAL( genericWarnings, 2 );
130
131 // The four matched markers must not double-report as unresolved variables;
132 // The escaped literal should also not trigger an unresolved variable warning
133 // as that's not likely the user's expectation.
134 BOOST_CHECK_EQUAL( unresolvedVars, 0 );
135
136 auto containsMsg =
137 []( const std::vector<wxString>& aList, const wxString& aNeedle )
138 {
139 for( const wxString& msg : aList )
140 {
141 if( msg.Contains( aNeedle ) )
142 return true;
143 }
144
145 return false;
146 };
147
148 BOOST_CHECK( containsMsg( errorMessages, "start_of_text" ) );
149 BOOST_CHECK( containsMsg( errorMessages, "placeholder_text" ) );
150 BOOST_CHECK( containsMsg( warningMessages, "this_is_warning" ) );
151 BOOST_CHECK( containsMsg( warningMessages, "embedded_warning" ) );
152 BOOST_CHECK( !containsMsg( errorMessages, "not_a_real_error" ) );
153 }
154 }
155}
156
157
158BOOST_FIXTURE_TEST_CASE( ERCTextFieldsUseCapturedSharedSources, ERC_TEXT_VAR_FIXTURE )
159{
160 LOCALE_IO locale;
161 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
162 SCOPED_SET_RESET restore( enabled, true );
163 KI_TEST::LoadSchematic( m_settingsManager, "legacy_hierarchy/legacy_hierarchy", m_schematic );
164 std::vector<SCH_SHEET_PATH> paths;
165
166 for( const SCH_SHEET_PATH& path : m_schematic->Hierarchy() )
167 {
168 if( path.LastScreen()->GetFileName().EndsWith( "ampli_ht.kicad_sch" ) )
169 paths.push_back( path );
170 }
171
172 BOOST_REQUIRE_EQUAL( paths.size(), 2u );
173 SCH_SCREEN& screen = *paths[0].LastScreen();
174 BOOST_REQUIRE( &screen == paths[1].LastScreen() );
175 SCH_SYMBOL* symbol = nullptr;
176
177 for( SCH_ITEM* item : screen.Items().OfType( SCH_SYMBOL_T ) )
178 {
179 symbol = static_cast<SCH_SYMBOL*>( item );
180 break;
181 }
182
183 BOOST_REQUIRE( symbol );
184 const wxString name( "Capture assertion" );
185 symbol->AddField( SCH_FIELD( symbol, FIELD_T::USER, name ) );
186 SCH_FIELD* field = symbol->GetField( name );
187 BOOST_REQUIRE( field );
188 const VECTOR2I position = symbol->GetPosition();
189 auto collect = [&]()
190 {
191 ERC_TESTER tester( m_schematic.get() );
192 tester.TestTextVars( nullptr );
193 std::map<KIID_PATH, std::tuple<KIID, int, wxString, VECTOR2I>> found;
194 std::vector<SCH_MARKER*> markers;
195
196 for( SCH_ITEM* item : screen.Items().OfType( SCH_MARKER_T ) )
197 {
198 auto* marker = static_cast<SCH_MARKER*>( item );
199 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
200
201 if( error->GetMainItemID() == field->m_Uuid || error->GetMainItemID() == symbol->m_Uuid )
202 {
203 BOOST_REQUIRE( error->IsSheetSpecific() );
204 BOOST_CHECK( error->MainItemHasSheetPath() );
205
206 if( error->MainItemHasSheetPath() )
207 BOOST_CHECK( error->GetMainItemSheetPath().PathRef() == error->GetSpecificSheetPath().PathRef() );
208
209 BOOST_CHECK( found.emplace( error->GetSpecificSheetPath().PathRef(),
210 std::make_tuple( error->GetMainItemID(), error->GetErrorCode(),
211 error->GetErrorMessage( false ), marker->GetPosition() ) ).second );
212 }
213
214 markers.push_back( marker );
215 }
216
217 for( SCH_MARKER* marker : markers )
218 screen.DeleteItem( marker );
219
220 return found;
221 };
222
223 for( int change = 0; change < 2; ++change )
224 {
225 BOOST_TEST_CONTEXT( "shared field source edit=" << change )
226 {
227 field->SetText( "${ERC_WARNING Captured}" );
228 field->SetPosition( position );
229 RefreshTextChecks( true );
230 const auto before = collect();
231 BOOST_REQUIRE_EQUAL( before.size(), 2u );
232
233 for( const SCH_SHEET_PATH& path : paths )
234 {
235 BOOST_REQUIRE( before.contains( path.PathRef() ) );
236 BOOST_CHECK( std::get<0>( before.at( path.PathRef() ) ) == field->m_Uuid );
237 BOOST_CHECK_EQUAL( std::get<1>( before.at( path.PathRef() ) ), ERCE_GENERIC_WARNING );
238 m_schematic->SetCurrentSheet( path );
239 BOOST_CHECK( collect() == before );
240 }
241
242 if( change == 0 )
243 field->SetText( "${ConnectivityCaptureMissingVariable}" );
244 else
245 field->SetPosition( position + VECTOR2I( 25400000, 25400000 ) );
246
248 RefreshTextChecks( false );
249 const auto after = collect();
250 BOOST_REQUIRE_EQUAL( after.size(), 2u );
251
252 for( const SCH_SHEET_PATH& path : paths )
253 {
254 BOOST_REQUIRE( after.contains( path.PathRef() ) );
255 const auto& error = after.at( path.PathRef() );
256 BOOST_CHECK( std::get<0>( error ) == ( change == 0 ? symbol->m_Uuid : field->m_Uuid ) );
257 BOOST_CHECK_EQUAL( std::get<1>( error ),
259 BOOST_CHECK( std::get<3>( error ) == field->GetPosition() );
260 }
261
262 BOOST_CHECK( after != before );
263 RefreshTextChecks( true );
264 BOOST_CHECK( collect() == after );
265 enabled = false;
266 RefreshTextChecks( true );
267 BOOST_CHECK( collect() == after );
268 enabled = true;
269 }
270 }
271
272 auto targetMarkers = [&]()
273 {
274 std::vector<SCH_MARKER*> result;
275
276 for( SCH_ITEM* item : screen.Items().OfType( SCH_MARKER_T ) )
277 {
278 auto* marker = static_cast<SCH_MARKER*>( item );
279 const KIID& owner = marker->GetRCItem()->GetMainItemID();
280
281 if( owner == field->m_Uuid || owner == symbol->m_Uuid )
282 result.push_back( marker );
283 }
284
285 return result;
286 };
287 auto& exclusions = m_schematic->ErcSettings().m_ErcExclusions;
288
289 for( const wxString& raw : { wxString( "${ERC_WARNING Captured}" ), wxString( "${ERC_ERROR Captured}" ),
290 wxString( "${ConnectivityCaptureMissingVariable}" ) } )
291 {
292 field->SetText( raw );
293
294 for( int format : { 0, 1, 2 } )
295 {
296 BOOST_TEST_CONTEXT( "text=" << raw << "; exclusion format=" << format )
297 {
298 const bool legacyString = format == 2;
299 auto& legacyExclusions = m_schematic->ErcSettings().m_ErcExclusionsLegacy;
300 exclusions.clear();
301 legacyExclusions.clear();
302 enabled = true;
303 RefreshTextChecks( true );
304 ERC_TESTER tester( m_schematic.get() );
305 tester.TestTextVars( nullptr );
306 BOOST_REQUIRE_EQUAL( targetMarkers().size(), 2u );
307
308 for( SCH_MARKER* marker : targetMarkers() )
309 {
310 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
311 BOOST_REQUIRE( error->IsSheetSpecific() );
312
313 if( error->GetSpecificSheetPath().PathRef() != paths[0].PathRef() )
314 continue;
315
316 error->SetItemsSheetPaths( paths[0] );
317 auto proto = ERC_EXCLUSION::FromMarker( *marker ).ToProto();
318
319 if( format == 1 )
320 proto.mutable_marker()->clear_main_item_sheet_path();
321
322 auto exclusion = ERC_EXCLUSION::FromProto( proto );
323 exclusion.SetComment( "Retained text exclusion" );
324
325 if( legacyString )
326 {
327 BOOST_REQUIRE_GT( proto.marker().items_size(), 0 );
328 const wxString data = wxString::Format( wxS( "%s|%d|%d|%s|%s|%s|%s|" ),
329 error->GetSettingsKey(), marker->GetPosition().x, marker->GetPosition().y,
330 wxString::FromUTF8( proto.marker().items( 0 ).value() ),
331 proto.marker().has_child()
332 ? wxString::FromUTF8( proto.marker().child().text_value() )
333 : niluuid.AsString(),
334 error->GetSpecificSheetPath().PathRef().AsString(),
335 error->GetMainItemSheetPath().PathRef().AsString() );
336 legacyExclusions.emplace( data, exclusion.GetComment() );
337 }
338 else
339 {
340 const nlohmann::json saved = exclusion;
341 exclusions.insert( saved.get<ERC_EXCLUSION>() );
342 }
343 }
344
345 BOOST_REQUIRE_EQUAL( exclusions.size(), legacyString ? 0u : 1u );
346 BOOST_REQUIRE_EQUAL( legacyExclusions.size(), legacyString ? 1u : 0u );
347 const auto savedExclusions = exclusions;
348 const auto savedLegacyExclusions = legacyExclusions;
349
350 for( SCH_MARKER* marker : targetMarkers() )
351 screen.DeleteItem( marker );
352
353 for( bool backend : { true, false } )
354 {
355 BOOST_TEST_CONTEXT( "new engine=" << backend )
356 {
357 enabled = backend;
358 exclusions = savedExclusions;
359 legacyExclusions = savedLegacyExclusions;
360 RefreshTextChecks( true );
361 ERC_TESTER rescan( m_schematic.get() );
362 rescan.TestTextVars( nullptr );
363 m_schematic->ResolveERCExclusionsPostUpdate();
364 BOOST_CHECK( legacyExclusions.empty() );
365 BOOST_CHECK_EQUAL( m_schematic->GetUnresolvedERCExclusionCount(), 0u );
366 BOOST_REQUIRE_EQUAL( targetMarkers().size(), 2u );
367 size_t excluded = 0;
368
369 for( SCH_MARKER* marker : targetMarkers() )
370 {
371 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
372 const bool expected = error->GetSpecificSheetPath().PathRef() == paths[0].PathRef();
373 BOOST_CHECK_EQUAL( marker->IsExcluded(), expected );
374
375 if( marker->IsExcluded() )
376 {
377 ++excluded;
378 BOOST_CHECK_EQUAL( marker->GetComment(), wxString( "Retained text exclusion" ) );
379 }
380
381 screen.DeleteItem( marker );
382 }
383
384 BOOST_CHECK_EQUAL( excluded, 1u );
385 }
386 }
387 }
388 }
389 }
390
391 for( bool backend : { true, false } )
392 {
393 BOOST_TEST_CONTEXT( "retired shared text path backend=" << backend )
394 {
395 enabled = backend;
396 field->SetText( "${ERC_WARNING Retained shared field}" );
397 m_schematic->SetCurrentSheet( m_schematic->Hierarchy().front() );
398 RefreshTextChecks( true );
399 const auto before = collect();
400 BOOST_REQUIRE_EQUAL( before.size(), 2u );
401 SCH_SHEET_PATH parent = paths[0];
402 SCH_SHEET* removed = parent.Last();
403 parent.pop_back();
404 BOOST_REQUIRE( parent.LastScreen()->CheckIfOnDrawList( removed ) );
405 parent.LastScreen()->Remove( removed );
406 std::unique_ptr<SCH_SHEET> undoOwnedSheet( removed );
407 m_schematic->RefreshHierarchy();
408 auto expected = before;
409 expected.erase( paths[0].PathRef() );
410 BOOST_REQUIRE_EQUAL( expected.size(), 1u );
411 BOOST_CHECK( collect() == expected );
412
413 if( backend )
414 RefreshTextChecks( false );
415 else
416 RefreshTextChecks( true );
417
418 BOOST_CHECK( collect() == expected );
419 RefreshTextChecks( true );
420 BOOST_CHECK( collect() == expected );
421 parent.LastScreen()->Append( undoOwnedSheet.release() );
422 m_schematic->RefreshHierarchy();
423
424 if( backend )
425 RefreshTextChecks( false );
426 else
427 RefreshTextChecks( true );
428
429 BOOST_CHECK( collect() == before );
430 RefreshTextChecks( true );
431 BOOST_CHECK( collect() == before );
432 }
433 }
434}
const char * name
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
const KIID m_Uuid
Definition eda_item.h:599
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
Container for an ERC exclusion, which is a SCH_MARKER plus an optional comment.
const kiapi::schematic::ErcExclusion & ToProto() const
static ERC_EXCLUSION FromMarker(const SCH_MARKER &aMarker)
static ERC_EXCLUSION FromProto(const kiapi::schematic::ErcExclusion &aMessage)
Container for ERC settings.
std::map< int, SEVERITY > m_ERCSeverities
Runs the electrical rules checks and adds a SCH_MARKER for each violation.
Definition erc.h:60
void TestTextVars(DS_PROXY_VIEW_ITEM *aDrawingSheet)
Check for any unresolved text variable references.
Definition erc.cpp:648
Definition kiid.h:46
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
VECTOR2I GetPosition() const override
void SetPosition(const VECTOR2I &aPosition) override
void SetText(const wxString &aText) override
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
void BumpConnectivityRevision(KICAD_T aChangedType=TYPE_NOT_INIT)
Increment the connectivity revisions of this screen.
bool Remove(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
Remove aItem from the schematic associated with this screen.
bool CheckIfOnDrawList(const SCH_ITEM *aItem) const
void DeleteItem(SCH_ITEM *aItem)
Remove aItem from the linked list and deletes the object.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
SCH_SCREEN * LastScreen()
SCH_SHEET * Last() const
Return a pointer to the last SCH_SHEET of the list.
void pop_back()
Forwarded method from std::vector.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
Schematic symbol object.
Definition sch_symbol.h:73
VECTOR2I GetPosition() const override
Definition sch_symbol.h:895
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add a field to the symbol.
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
RAII class that sets an value at construction and resets it to the original value at destruction.
An implementation of the RC_ITEM_LIST interface which uses the global SHEETLIST to fulfill the contra...
int GetCount(int aSeverity=-1) const override
void SetSeverities(int aSeverities) override
std::shared_ptr< RC_ITEM > GetItem(int aIndex) const override
Retrieve a RC_ITEM by index.
@ ERCE_LAST
@ ERCE_UNRESOLVED_VARIABLE
A text variable could not be resolved.
@ ERCE_GENERIC_ERROR
@ ERCE_GENERIC_WARNING
KIID niluuid(0)
void LoadSchematic(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< SCHEMATIC > &aSchematic)
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_IGNORE
std::vector< FAB_LAYER_COLOR > dummy
std::unique_ptr< SCHEMATIC > m_schematic
@ USER
The field ID hasn't been set yet; field is invalid.
BOOST_FIXTURE_TEST_CASE(ERCTextVarIssue24442, ERC_TEXT_VAR_FIXTURE)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
std::string path
VECTOR3I expected(15, 30, 45)
BOOST_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_MARKER_T
Definition typeinfo.h:154
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708