KiCad PCB EDA Suite
Loading...
Searching...
No Matches
group_saveload.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 (C) 2020 Joshua Redstone redstone at gmail.com
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <bitset>
26#include <filesystem>
27#include <string>
28
29#include <board.h>
30#include <footprint.h>
31#include <pcb_generator.h>
32#include <pcb_group.h>
33#include <pcb_text.h>
34#include <pcb_track.h>
36#include <common.h>
40
41BOOST_AUTO_TEST_SUITE( GroupSaveLoad )
42
43// The tests below set up a test case with a spec for the set of groups to create.
44// A group can contain members from this list of candidates.
46{
56 REMOVED_TEXT, // Text not added to board
62 NAME_GROUP3_DUP, // Group with name identical to NAME_GROUP3
63 REMOVED_GROUP, // Group not added to board
65};
66
67// The objects associated with item REMOVED_TEXT and REMOVED_GROUP are not added to the board,
68// so they are not cleaned up when the board is deleted. These pointers stores the objects
69// so they can be deleted once they are done being used.
70static PCB_TEXT* s_removedText = nullptr;
71static PCB_GROUP* s_removedGroup = nullptr;
72
73
74/*
75 * Takes a vector of group specifications for groups to create.
76 * Each group is a vector of which ItemTypes to put in the group.
77 * The first group corresponds to GROUP0, the second to GROUP1, and os on.
78 */
79std::unique_ptr<BOARD> createBoard( const std::vector<std::vector<ItemType>>& spec )
80{
81 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
82 std::vector<BOARD_ITEM*> items;
83
84 // Create text items and add to board.
85 for( int idx = 0; idx <= REMOVED_TEXT; idx++ )
86 {
87 PCB_TEXT* textItem = new PCB_TEXT( board.get() );
88 textItem->SetText( wxString::Format( _( "some text-%d" ), idx ) );
89
90 // Don't add REMOVED_TEXT to the board
91 if( idx < REMOVED_TEXT )
92 board->Add( textItem );
93
94 items.push_back( textItem );
95 }
96
97 // Create groups
98 for( int idx = 0; idx < ( NUM_ITEMS - GROUP0 ); idx++ )
99 {
100 PCB_GROUP* gr = new PCB_GROUP( board.get() );
101
102 if( idx >= ( NAME_GROUP3 - GROUP0 ) )
103 {
104 wxString name = wxString::Format( _( "group-%d" ),
105 ( idx == ( NAME_GROUP3_DUP - GROUP0 ) ) ? 3 : idx );
106 gr->SetName( name );
108 }
109
110 items.push_back( gr );
111 }
112
113 std::bitset<NUM_ITEMS> used;
114
115 // Populate groups based on spec
116 for( int offset = 0; offset < ( NUM_ITEMS - GROUP0 ); offset++ )
117 {
118 int groupIdx = GROUP0 + offset;
119
120 PCB_GROUP* group = static_cast<PCB_GROUP*>( items[groupIdx] );
121
122 if( offset < spec.size() )
123 {
124 const std::vector<ItemType>& groupSpec = spec[offset];
125
126 for( ItemType item : groupSpec )
127 {
128 used.set( static_cast<size_t>( item ) );
129 group->AddItem( items[item] );
130 }
131
132 BOOST_CHECK_EQUAL( group->GetItems().size(), groupSpec.size() );
133 board->Add( group );
134 }
135 else if( groupIdx != REMOVED_GROUP && used.test( groupIdx ) )
136 {
137 // This group is used in another group, so it must be on the board
138 board->Add( group );
139 }
140 else if( groupIdx != REMOVED_GROUP )
141 {
142 // If the group isn't used, delete it
143 delete group;
144 }
145 }
146
147 // Delete the removed text item if it isn't used
148 if( used.test( REMOVED_TEXT ) )
149 s_removedText = static_cast<PCB_TEXT*>( items[REMOVED_TEXT] );
150 else
151 delete items[REMOVED_TEXT];
152
153 // Delete the removed group item if it isn't used
154 if( used.test( REMOVED_GROUP ) )
155 s_removedGroup = static_cast<PCB_GROUP*>( items[REMOVED_GROUP] );
156 else
157 delete items[REMOVED_GROUP];
158
159 BOOST_TEST_CHECKPOINT( "Returning fresh board" );
160 return board;
161}
162
163
164// Check if two groups are identical by comparing the fields (by Uuid).
165void testGroupEqual( const PCB_GROUP& group1, const PCB_GROUP& group2 )
166{
167 BOOST_CHECK_EQUAL( group1.m_Uuid.AsString(), group2.m_Uuid.AsString() );
168 BOOST_CHECK_EQUAL( group1.GetName(), group2.GetName() );
169
170 const std::unordered_set<EDA_ITEM*>& items1 = group1.GetItems();
171 const std::unordered_set<EDA_ITEM*>& items2 = group2.GetItems();
172
173 BOOST_CHECK_EQUAL( items1.size(), items2.size() );
174
175 // Test that the sets items1 and items2 are identical, by checking m_Uuid
176 for( EDA_ITEM* item1 : items1 )
177 {
178 auto cmp = [&]( EDA_ITEM* elem )
179 {
180 return elem->m_Uuid.AsString() == item1->m_Uuid.AsString();
181 };
182
183 auto item2 = std::find_if( items2.begin(), items2.end(), cmp );
184
185 BOOST_CHECK( item2 != items2.end() );
186 }
187}
188
189
190// Check if two GROUPS are identical by comparing the groups in each of them.
191void testGroupsEqual( const GROUPS& groups1, const GROUPS& groups2 )
192{
193 BOOST_CHECK_EQUAL( groups1.size(), groups2.size() );
194
195 for( PCB_GROUP* group1 : groups1 )
196 {
197 auto cmp = [&]( BOARD_ITEM* elem )
198 {
199 return elem->m_Uuid.AsString() == group1->m_Uuid.AsString();
200 };
201
202 auto group2 = std::find_if( groups2.begin(), groups2.end(), cmp );
203
204 BOOST_CHECK( group2 != groups2.end() );
205
206 testGroupEqual( *group1, **group2 );
207 }
208}
209
210
211/*
212 * Create board based on spec, save it to a file, load it, and make sure the
213 * groups in the resulting board are the same as the groups we started with.
214 */
215void testSaveLoad( const std::vector<std::vector<ItemType>>& spec )
216{
217 std::unique_ptr<BOARD> board1 = createBoard( spec );
218 auto path = std::filesystem::temp_directory_path() / "group_saveload_tst.kicad_pcb";
219 ::KI_TEST::DumpBoardToFile( *board1, path.string() );
220
221 std::unique_ptr<BOARD> board2 = ::KI_TEST::ReadBoardFromFileOrStream( path.string() );
222 testGroupsEqual( board1->Groups(), board2->Groups() );
223}
224
225
226// Test saving & loading of a few configurations.
227// Groups with fewer than 2 members are not saved, so all round-trip tests
228// use groups with at least 2 members.
229BOOST_AUTO_TEST_CASE( HealthyGroups )
230{
231 // Test board with no groups
232 testSaveLoad( {} );
233
234 // Single group with 2 members
235 testSaveLoad( { { TEXT0, TEXT1 } } );
236
237 // Two groups
238 testSaveLoad( { { TEXT0, TEXT1 }, { TEXT2, TEXT3 } } );
239 testSaveLoad( { { TEXT0, TEXT1 }, { TEXT2, GROUP0 } } );
240
241 // Subgroups with 2+ members each
242 testSaveLoad( { { TEXT0, TEXT1, GROUP1 }, { TEXT2, TEXT3 }, { TEXT4, GROUP0 } } );
243 testSaveLoad( { { TEXT0, TEXT1 }, { TEXT2, TEXT3 }, { GROUP1, GROUP0 } } );
244 testSaveLoad( { { TEXT0, TEXT1 }, { TEXT2, TEXT3 }, { TEXT4, NAME_GROUP3 }, { TEXT5, TEXT6 } } );
245}
246
247
248BOOST_AUTO_TEST_CASE( SingleMemberGroupsNotSaved )
249{
250 std::unique_ptr<BOARD> board1 = createBoard( { { TEXT0 } } );
251 auto path = std::filesystem::temp_directory_path() / "group_saveload_tst.kicad_pcb";
252 ::KI_TEST::DumpBoardToFile( *board1, path.string() );
253
254 std::unique_ptr<BOARD> board2 = ::KI_TEST::ReadBoardFromFileOrStream( path.string() );
255 BOOST_CHECK_EQUAL( board2->Groups().size(), 0u );
256}
257
258
259BOOST_AUTO_TEST_CASE( InvalidGroups )
260{
261 // A cycle
262 std::unique_ptr<BOARD> board1 = createBoard( { { TEXT0, GROUP1 }, { TEXT2, GROUP0 } } );
263 BOOST_CHECK_EQUAL( board1->GroupsSanityCheck(), "Cycle detected in group membership" );
264
265 // More complex cycle
266 board1 = createBoard( { { TEXT0, GROUP1 }, { TEXT1 }, { TEXT2, NAME_GROUP4 },
267 { TEXT3, GROUP2 }, { TEXT4, NAME_GROUP3 } } );
268 BOOST_CHECK_EQUAL( board1->GroupsSanityCheck(), "Cycle detected in group membership" );
269
270 // Delete the removed group since the test is over
271 board1.reset( nullptr );
272 delete s_removedGroup;
273 s_removedGroup = nullptr;
274
275 // Delete the removed text since the test is over
276 board1.reset( nullptr );
277 delete s_removedText;
278 s_removedText = nullptr;
279}
280
281
287BOOST_AUTO_TEST_CASE( DeepCloneGroupMembership )
288{
289 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
290
291 PCB_TEXT* text0 = new PCB_TEXT( board.get() );
292 text0->SetText( wxT( "child-0" ) );
293 board->Add( text0 );
294
295 PCB_TEXT* text1 = new PCB_TEXT( board.get() );
296 text1->SetText( wxT( "child-1" ) );
297 board->Add( text1 );
298
299 PCB_GROUP* group = new PCB_GROUP( board.get() );
300 group->SetName( wxT( "TestGroup" ) );
301 group->AddItem( text0 );
302 group->AddItem( text1 );
303 board->Add( group );
304
305 BOOST_CHECK_EQUAL( group->GetItems().size(), 2 );
306
307 PCB_GROUP* deepCopy = group->DeepClone();
308
309 // DeepClone preserves the UUID
310 BOOST_CHECK_EQUAL( deepCopy->m_Uuid.AsString(), group->m_Uuid.AsString() );
311 BOOST_CHECK_EQUAL( deepCopy->GetName(), group->GetName() );
312 BOOST_CHECK_EQUAL( deepCopy->GetItems().size(), 2 );
313
314 // The cloned group's children must be different objects from the originals
315 for( EDA_ITEM* clonedChild : deepCopy->GetItems() )
316 {
317 BOOST_CHECK( clonedChild != text0 );
318 BOOST_CHECK( clonedChild != text1 );
319 }
320
321 // Children must NOT be the same pointers as the original group's children
322 for( EDA_ITEM* clonedChild : deepCopy->GetItems() )
323 {
324 bool foundInOriginal = group->GetItems().count( clonedChild ) > 0;
325 BOOST_CHECK_MESSAGE( !foundInOriginal,
326 "DeepClone child should not be in original group's m_items" );
327 }
328
329 // Round-trip: add the deep clone and its children to a temp board, save, reload
330 std::unique_ptr<BOARD> tempBoard = std::make_unique<BOARD>();
331 tempBoard->Add( deepCopy );
332
333 deepCopy->RunOnChildren(
334 [&]( BOARD_ITEM* child )
335 {
336 tempBoard->Add( child, ADD_MODE::APPEND, false );
337 },
339
340 auto path = std::filesystem::temp_directory_path() / "group_deepclone_tst.kicad_pcb";
341 ::KI_TEST::DumpBoardToFile( *tempBoard, path.string() );
342
343 std::unique_ptr<BOARD> reloaded = ::KI_TEST::ReadBoardFromFileOrStream( path.string() );
344
345 BOOST_CHECK_EQUAL( reloaded->Groups().size(), 1 );
346
347 if( !reloaded->Groups().empty() )
348 {
349 PCB_GROUP* loadedGroup = static_cast<PCB_GROUP*>( reloaded->Groups().front() );
350 BOOST_CHECK_EQUAL( loadedGroup->GetItems().size(), 2 );
351 BOOST_CHECK_EQUAL( loadedGroup->GetName(), wxT( "TestGroup" ) );
352 }
353}
354
355
361BOOST_AUTO_TEST_CASE( DeepCloneNestedGeneratorMembership )
362{
363 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
364
365 PCB_TEXT* text = new PCB_TEXT( board.get() );
366 text->SetText( wxT( "generated-child" ) );
367 board->Add( text );
368
369 PCB_TUNING_PATTERN* nested = new PCB_TUNING_PATTERN( board.get() );
370 nested->AddItem( text );
371 board->Add( nested );
372
373 PCB_TUNING_PATTERN* root = new PCB_TUNING_PATTERN( board.get() );
374 root->AddItem( nested );
375 board->Add( root );
376
377 PCB_GENERATOR* deepCopy = root->DeepClone();
378
379 BOOST_CHECK_EQUAL( deepCopy->GetItems().size(), 1 );
380
381 EDA_ITEM* clonedNestedRaw = *deepCopy->GetItems().begin();
382 BOOST_CHECK( clonedNestedRaw != nested );
383 BOOST_CHECK_EQUAL( clonedNestedRaw->Type(), PCB_GENERATOR_T );
384
385 PCB_GENERATOR* clonedNested = static_cast<PCB_GENERATOR*>( clonedNestedRaw );
386 BOOST_CHECK_EQUAL( clonedNested->GetItems().size(), 1 );
387
388 for( EDA_ITEM* member : clonedNested->GetItems() )
389 {
390 BOOST_CHECK( member != text );
391 }
392
393 // Clean up the deep copy tree (not owned by any board)
394 EDA_ITEM* clonedText = *clonedNested->GetItems().begin();
395 delete clonedText;
396 delete clonedNested;
397 delete deepCopy;
398}
399
400
const char * name
Construction utilities for PCB tests.
General utilities for PCB file IO for QA programs.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:54
wxString GetName() const
Definition eda_group.h:51
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:27
void SetName(const wxString &aName)
Definition eda_group.h:52
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:99
const KIID m_Uuid
Definition eda_item.h:527
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:111
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:284
wxString AsString() const
Definition kiid.cpp:244
PCB_GENERATOR * DeepClone() const
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:53
void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const override
Invoke a function on all children.
The common library.
#define _(s)
@ RECURSE
Definition eda_item.h:52
BOOST_AUTO_TEST_CASE(HealthyGroups)
@ REMOVED_TEXT
@ TEXT5
@ TEXT8
@ TEXT0
@ NAME_GROUP3_DUP
@ TEXT3
@ TEXT4
@ GROUP1
@ REMOVED_GROUP
@ NAME_GROUP4
@ TEXT6
@ NAME_GROUP3
@ TEXT1
@ TEXT2
@ NUM_ITEMS
@ GROUP2
@ GROUP0
@ TEXT7
void testGroupEqual(const PCB_GROUP &group1, const PCB_GROUP &group2)
void testGroupsEqual(const GROUPS &groups1, const GROUPS &groups2)
std::unique_ptr< BOARD > createBoard(const std::vector< std::vector< ItemType > > &spec)
static PCB_GROUP * s_removedGroup
void testSaveLoad(const std::vector< std::vector< ItemType > > &spec)
static PCB_TEXT * s_removedText
std::unique_ptr< BOARD > ReadBoardFromFileOrStream(const std::string &aFilename, std::istream &aFallback)
Read a board from a file, or another stream, as appropriate.
void DumpBoardToFile(BOARD &board, const std::string &aFilename)
Utility function to simply write a Board out to a file.
Class to handle a set of BOARD_ITEMs.
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
std::string path
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:91