KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_pcb_dynamic_properties.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
24
25#include <board.h>
26#include <footprint.h>
27#include <pcb_field.h>
28
29#include <algorithm>
30#include <vector>
31
32
33using namespace KICAD_DIFF;
34
35
36static const PROPERTY_DELTA* findDelta( const std::vector<PROPERTY_DELTA>& aDeltas, const wxString& aName )
37{
38 for( const PROPERTY_DELTA& d : aDeltas )
39 {
40 if( d.name == aName )
41 return &d;
42 }
43
44 return nullptr;
45}
46
47
48BOOST_AUTO_TEST_SUITE( PcbDynamicProperties )
49
50
51BOOST_AUTO_TEST_CASE( FootprintDynamicPropertyOrder )
52{
53 BOARD board;
54 FOOTPRINT fp( &board );
55
56 fp.Add( new PCB_FIELD( &fp, FIELD_T::USER, wxS( "Supplier" ) ) );
57 fp.Add( new PCB_FIELD( &fp, FIELD_T::USER, wxS( "MPN" ) ) );
58 fp.Add( new PCB_FIELD( &fp, FIELD_T::USER, wxS( "Color" ) ) );
59
60 PCB_FIELD* privateField = new PCB_FIELD( &fp, FIELD_T::USER, wxS( "Secret" ) );
61 privateField->SetPrivate( true );
62 fp.Add( privateField );
63
64 std::vector<PROPERTY_BASE*> dynamicProps = fp.GetDynamicProperties();
65
66 std::vector<wxString> names;
67 names.reserve( dynamicProps.size() );
68
69 for( PROPERTY_BASE* prop : dynamicProps )
70 names.push_back( prop->Name() );
71
72 BOOST_CHECK( std::ranges::none_of( names,
73 []( const wxString& n ) { return n == wxS( "Secret" ); } ) );
74
75 bool seenUser = false;
76 wxString lastUser;
77 std::vector<wxString> userNames;
78
79 for( const wxString& name : names )
80 {
81 PCB_FIELD* field = fp.GetField( name );
82
83 if( field && field->IsMandatory() )
84 {
85 BOOST_CHECK_MESSAGE( !seenUser,
86 "Mandatory field '" + name + "' appears after a user field" );
87 }
88 else
89 {
90 if( field )
91 BOOST_CHECK( !field->IsPrivate() );
92
93 if( seenUser )
94 {
95 BOOST_CHECK_MESSAGE( lastUser.CmpNoCase( name ) <= 0,
96 "User fields not sorted: " + lastUser + " before " + name );
97 }
98
99 lastUser = name;
100 userNames.push_back( name );
101 seenUser = true;
102 }
103 }
104
105 const std::vector<wxString> expectedUser = { wxS( "Color" ), wxS( "MPN" ), wxS( "Supplier" ) };
106 BOOST_CHECK( userNames == expectedUser );
107}
108
109
110BOOST_AUTO_TEST_CASE( DynamicSkipsStaticFields )
111{
112 BOARD board;
113 FOOTPRINT fp( &board );
114
116 const std::vector<PROPERTY_BASE*>& staticProps = pm.GetProperties( TYPE_HASH( FOOTPRINT ) );
117
118 std::vector<wxString> staticNames;
119
120 for( PROPERTY_BASE* staticProp : staticProps )
121 staticNames.push_back( staticProp->Name() );
122
123 for( PROPERTY_BASE* dynamic : fp.GetDynamicProperties() )
124 {
125 BOOST_CHECK_MESSAGE( std::ranges::find( staticNames, dynamic->Name() ) == staticNames.end(),
126 "Dynamic property '" + dynamic->Name() + "' duplicates a statically-registered property" );
127 }
128}
129
130
131BOOST_AUTO_TEST_CASE( FootprintFieldGetterAbsentVsEmpty )
132{
133 BOARD boardA;
134 BOARD boardB;
135 FOOTPRINT fpA( &boardA );
136 FOOTPRINT fpB( &boardB );
137
138 // Field absent on A, present on B -> delta with before=NONE, after=STRING.
139 PCB_FIELD* fieldB = new PCB_FIELD( &fpB, FIELD_T::USER, wxS( "Supplier" ) );
140 fieldB->SetText( wxS( "ACME" ) );
141 fpB.Add( fieldB );
142
143 std::vector<PROPERTY_DELTA> deltas = DiffItemProperties( &fpA, &fpB );
144 const PROPERTY_DELTA* d = findDelta( deltas, wxS( "Supplier" ) );
145
146 BOOST_REQUIRE( d );
147 BOOST_CHECK( d->before.GetType() == DIFF_VALUE::T::NONE );
148 BOOST_CHECK( d->after.GetType() == DIFF_VALUE::T::STRING );
149
150 // Field empty on A, non-empty on B -> delta with before=STRING, after=STRING.
151 PCB_FIELD* fieldA = new PCB_FIELD( &fpA, FIELD_T::USER, wxS( "Supplier" ) );
152 fieldA->SetText( wxS( "" ) );
153 fpA.Add( fieldA );
154
155 std::vector<PROPERTY_DELTA> deltas2 = DiffItemProperties( &fpA, &fpB );
156 const PROPERTY_DELTA* d2 = findDelta( deltas2, wxS( "Supplier" ) );
157
158 BOOST_REQUIRE( d2 );
159 BOOST_CHECK( d2->before.GetType() == DIFF_VALUE::T::STRING );
160 BOOST_CHECK( d2->after.GetType() == DIFF_VALUE::T::STRING );
161}
162
163
164BOOST_AUTO_TEST_CASE( ApplyResolvesDynamicFieldProperty )
165{
166 BOARD boardSrc;
167 BOARD boardTgt;
168 FOOTPRINT src( &boardSrc );
169 FOOTPRINT tgt( &boardTgt );
170
171 PCB_FIELD* srcField = new PCB_FIELD( &src, FIELD_T::USER, wxS( "MPN" ) );
172 srcField->SetText( wxS( "12345" ) );
173 src.Add( srcField );
174
175 PCB_FIELD* tgtField = new PCB_FIELD( &tgt, FIELD_T::USER, wxS( "MPN" ) );
176 tgtField->SetText( wxS( "original" ) );
177 tgt.Add( tgtField );
178
180 res.name = wxS( "MPN" );
181 res.kind = PROP_RES::OURS;
182
183 PROPERTY_APPLY_COUNTS counts = ApplyPropertyResolutions( &tgt, { res }, &src, nullptr, nullptr );
184
185 BOOST_CHECK_EQUAL( counts.applied, 1u );
186 BOOST_CHECK_EQUAL( counts.failed, 0u );
187
188 PCB_FIELD* resolved = tgt.GetField( wxS( "MPN" ) );
189 BOOST_REQUIRE( resolved );
190 BOOST_CHECK_EQUAL( resolved->GetText(), wxS( "12345" ) );
191}
192
193
194BOOST_AUTO_TEST_CASE( ApplyFailsForNonexistentField )
195{
196 BOARD boardTgt;
197 BOARD boardSrc;
198 FOOTPRINT tgt( &boardTgt );
199 FOOTPRINT src( &boardSrc );
200
202 res.name = wxS( "NoSuchField" );
203 res.kind = PROP_RES::OURS;
204
205 PROPERTY_APPLY_COUNTS counts = ApplyPropertyResolutions( &tgt, { res }, &src, nullptr, nullptr );
206
207 BOOST_CHECK_EQUAL( counts.applied, 0u );
208 BOOST_CHECK_EQUAL( counts.failed, 1u );
209}
210
211
212BOOST_AUTO_TEST_CASE( DynamicPropertyCacheStable )
213{
214 BOARD board;
215 FOOTPRINT fp( &board );
216
217 fp.Add( new PCB_FIELD( &fp, FIELD_T::USER, wxS( "MPN" ) ) );
218
219 PROPERTY_BASE* first = nullptr;
220 PROPERTY_BASE* second = nullptr;
221
222 for( PROPERTY_BASE* p : fp.GetDynamicProperties() )
223 {
224 if( p->Name() == wxS( "MPN" ) )
225 first = p;
226 }
227
228 for( PROPERTY_BASE* p : fp.GetDynamicProperties() )
229 {
230 if( p->Name() == wxS( "MPN" ) )
231 second = p;
232 }
233
234 BOOST_REQUIRE( first );
235 BOOST_REQUIRE( second );
236
237 // The per-object cache must return the same descriptor; recreating it would
238 // invalidate pointers callers (e.g. the properties panel) hold onto.
239 BOOST_CHECK_EQUAL( first, second );
240}
241
242
243BOOST_AUTO_TEST_CASE( DynamicPropertyLookupCaseInsensitive )
244{
245 BOARD board;
246 FOOTPRINT fp( &board );
247
248 fp.Add( new PCB_FIELD( &fp, FIELD_T::USER, wxS( "MPN" ) ) );
249
251
252 PROPERTY_BASE* lower = pm.GetProperty( &fp, wxS( "mpn" ) );
253 PROPERTY_BASE* upper = pm.GetProperty( &fp, wxS( "MPN" ) );
254
255 BOOST_REQUIRE( lower );
256 BOOST_REQUIRE( upper );
257 BOOST_CHECK_EQUAL( lower, upper );
258
259 // The manager's dynamic lookup must agree with the object's own cache.
260 PROPERTY_BASE* dyn = nullptr;
261
262 for( PROPERTY_BASE* p : fp.GetDynamicProperties() )
263 {
264 if( p->Name() == wxS( "MPN" ) )
265 dyn = p;
266 }
267
268 BOOST_REQUIRE( dyn );
269 BOOST_CHECK_EQUAL( lower, dyn );
270}
271
272
273BOOST_AUTO_TEST_CASE( CustomPropertiesExposedAsDynamic )
274{
275 BOARD board;
276 FOOTPRINT fp( &board );
277
278 fp.SetCustomProperty( wxS( "MPN" ), wxS( "12345" ) );
279 fp.SetCustomProperty( wxS( "Supplier" ), wxS( "ACME" ) );
280
281 PROPERTY_BASE* mpn = nullptr;
282 PROPERTY_BASE* supplier = nullptr;
283
284 for( PROPERTY_BASE* p : fp.GetDynamicProperties() )
285 {
286 if( p->Name() == wxS( "MPN" ) )
287 mpn = p;
288 else if( p->Name() == wxS( "Supplier" ) )
289 supplier = p;
290 }
291
292 BOOST_REQUIRE( mpn );
293 BOOST_REQUIRE( supplier );
294
295 wxAny v = fp.Get( mpn );
296 wxString text;
297
298 BOOST_REQUIRE( v.GetAs( &text ) );
299 BOOST_CHECK_EQUAL( text, wxS( "12345" ) );
300
301 wxAny newVal( wxString( wxS( "99999" ) ) );
302 BOOST_CHECK( fp.Set( supplier, newVal ) );
303 BOOST_CHECK_EQUAL( fp.GetCustomProperties().at( wxS( "Supplier" ) ), wxS( "99999" ) );
304}
305
306
const char * name
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void SetCustomProperty(const wxString &aKey, const wxString &aValue)
Definition eda_item.h:255
const std::map< wxString, wxString > & GetCustomProperties() const
Custom user-defined string key/value properties attached to this item.
Definition eda_item.h:242
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:118
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:231
PCB_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this footprint.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
std::vector< PROPERTY_BASE * > GetDynamicProperties() const override
Return dynamically-computed properties specific to this object instance (e.g.
wxAny Get(PROPERTY_BASE *aProperty) const
bool Set(PROPERTY_BASE *aProperty, wxAny &aValue, bool aNotify=true)
bool IsMandatory() const
void SetPrivate(bool aPrivate)
Definition pcb_field.h:81
bool IsPrivate() const
Definition pcb_field.h:80
Provide class metadata.Helper macro to map type hashes to names.
const std::vector< PROPERTY_BASE * > & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
static PROPERTY_MANAGER & Instance()
PROPERTY_BASE * GetProperty(TYPE_ID aType, const wxString &aProperty) const
Return a property for a specific type.
std::vector< PROPERTY_DELTA > DiffItemProperties(const INSPECTABLE *aBefore, const INSPECTABLE *aAfter)
Enumerate the property deltas between two items of the same dynamic type.
PROPERTY_APPLY_COUNTS ApplyPropertyResolutions(INSPECTABLE *aTarget, const std::vector< PROPERTY_RESOLUTION > &aProps, const INSPECTABLE *aOurs, const INSPECTABLE *aTheirs, const INSPECTABLE *aAncestor)
Apply per-property merge resolutions to aTarget, sourcing OURS/THEIRS/ANCESTOR values from the matchi...
#define TYPE_HASH(x)
Definition property.h:74
Applied/failed tallies from ApplyPropertyResolutions, folded into a caller's report.
Single (name, before, after) triple for one mutated property on an item.
@ USER
The field ID hasn't been set yet; field is invalid.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I res
BOOST_AUTO_TEST_CASE(FootprintDynamicPropertyOrder)
static const PROPERTY_DELTA * findDelta(const std::vector< PROPERTY_DELTA > &aDeltas, const wxString &aName)
BOOST_CHECK_EQUAL(result, "25.4")