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 (C) 1992-2020 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_group.h>
32#include <pcb_text.h>
33#include <common.h>
37
38BOOST_AUTO_TEST_SUITE( GroupSaveLoad )
39
40// The tests below set up a test case with a spec for the set of groups to create.
41// A group can contain members from this list of candidates.
43{
53 REMOVED_TEXT, // Text not added to board
59 NAME_GROUP3_DUP, // Group with name identical to NAME_GROUP3
60 REMOVED_GROUP, // Group not added to board
62};
63
64// The objects associated with item REMOVED_TEXT and REMOVED_GROUP are not added to the board,
65// so they are not cleaned up when the board is deleted. These pointers stores the objects
66// so they can be deleted once they are done being used.
67static PCB_TEXT* s_removedText = nullptr;
68static PCB_GROUP* s_removedGroup = nullptr;
69
70
71/*
72 * Takes a vector of group specifications for groups to create.
73 * Each group is a vector of which ItemTypes to put in the group.
74 * The first group corresponds to GROUP0, the second to GROUP1, and os on.
75 */
76std::unique_ptr<BOARD> createBoard( const std::vector<std::vector<ItemType>>& spec )
77{
78 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
79 std::vector<BOARD_ITEM*> items;
80
81 // Create text items and add to board.
82 for( int idx = 0; idx <= REMOVED_TEXT; idx++ )
83 {
84 PCB_TEXT* textItem = new PCB_TEXT( board.get() );
85 textItem->SetText( wxString::Format( _( "some text-%d" ), idx ) );
86
87 // Don't add REMOVED_TEXT to the board
88 if( idx < REMOVED_TEXT )
89 board->Add( textItem );
90
91 items.push_back( textItem );
92 }
93
94 // Create groups
95 for( int idx = 0; idx < ( NUM_ITEMS - GROUP0 ); idx++ )
96 {
97 PCB_GROUP* gr = new PCB_GROUP( board.get() );
98
99 if( idx >= ( NAME_GROUP3 - GROUP0 ) )
100 {
101 wxString name = wxString::Format( _( "group-%d" ),
102 ( idx == ( NAME_GROUP3_DUP - GROUP0 ) ) ? 3 : idx );
103 gr->SetName( name );
104 BOOST_CHECK_EQUAL( gr->GetName(), name );
105 }
106
107 items.push_back( gr );
108 }
109
110 std::bitset<NUM_ITEMS> used;
111
112 // Populate groups based on spec
113 for( int offset = 0; offset < ( NUM_ITEMS - GROUP0 ); offset++ )
114 {
115 int groupIdx = GROUP0 + offset;
116
117 PCB_GROUP* group = static_cast<PCB_GROUP*>( items[groupIdx] );
118
119 if( offset < spec.size() )
120 {
121 const std::vector<ItemType>& groupSpec = spec[offset];
122
123 for( ItemType item : groupSpec )
124 {
125 used.set( static_cast<size_t>( item ) );
126 group->AddItem( items[item] );
127 }
128
129 BOOST_CHECK_EQUAL( group->GetItems().size(), groupSpec.size() );
130 board->Add( group );
131 }
132 else if( groupIdx != REMOVED_GROUP && used.test( groupIdx ) )
133 {
134 // This group is used in another group, so it must be on the board
135 board->Add( group );
136 }
137 else if( groupIdx != REMOVED_GROUP )
138 {
139 // If the group isn't used, delete it
140 delete group;
141 }
142 }
143
144 // Delete the removed text item if it isn't used
145 if( used.test( REMOVED_TEXT ) )
146 s_removedText = static_cast<PCB_TEXT*>( items[REMOVED_TEXT] );
147 else
148 delete items[REMOVED_TEXT];
149
150 // Delete the removed group item if it isn't used
151 if( used.test( REMOVED_GROUP ) )
152 s_removedGroup = static_cast<PCB_GROUP*>( items[REMOVED_GROUP] );
153 else
154 delete items[REMOVED_GROUP];
155
156 BOOST_TEST_CHECKPOINT( "Returning fresh board" );
157 return board;
158}
159
160
161// Check if two groups are identical by comparing the fields (by Uuid).
162void testGroupEqual( const PCB_GROUP& group1, const PCB_GROUP& group2 )
163{
164 BOOST_CHECK_EQUAL( group1.m_Uuid.AsString(), group2.m_Uuid.AsString() );
165 BOOST_CHECK_EQUAL( group1.GetName(), group2.GetName() );
166
167 const std::unordered_set<BOARD_ITEM*>& items1 = group1.GetItems();
168 const std::unordered_set<BOARD_ITEM*>& items2 = group2.GetItems();
169
170 BOOST_CHECK_EQUAL( items1.size(), items2.size() );
171
172 // Test that the sets items1 and items2 are identical, by checking m_Uuid
173 for( BOARD_ITEM* item1 : items1 )
174 {
175 auto cmp = [&]( BOARD_ITEM* elem )
176 {
177 return elem->m_Uuid.AsString() == item1->m_Uuid.AsString();
178 };
179
180 auto item2 = std::find_if( items2.begin(), items2.end(), cmp );
181
182 BOOST_CHECK( item2 != items2.end() );
183 }
184}
185
186
187// Check if two GROUPS are identical by comparing the groups in each of them.
188void testGroupsEqual( const GROUPS& groups1, const GROUPS& groups2 )
189{
190 BOOST_CHECK_EQUAL( groups1.size(), groups2.size() );
191
192 for( PCB_GROUP* group1 : groups1 )
193 {
194 auto cmp = [&]( BOARD_ITEM* elem )
195 {
196 return elem->m_Uuid.AsString() == group1->m_Uuid.AsString();
197 };
198
199 auto group2 = std::find_if( groups2.begin(), groups2.end(), cmp );
200
201 BOOST_CHECK( group2 != groups2.end() );
202
203 testGroupEqual( *group1, **group2 );
204 }
205}
206
207
208/*
209 * Create board based on spec, save it to a file, load it, and make sure the
210 * groups in the resulting board are the same as the groups we started with.
211 */
212void testSaveLoad( const std::vector<std::vector<ItemType>>& spec )
213{
214 std::unique_ptr<BOARD> board1 = createBoard( spec );
215 auto path = std::filesystem::temp_directory_path() / "group_saveload_tst.kicad_pcb";
216 ::KI_TEST::DumpBoardToFile( *board1, path.string() );
217
218 std::unique_ptr<BOARD> board2 = ::KI_TEST::ReadBoardFromFileOrStream( path.string() );
219 testGroupsEqual( board1->Groups(), board2->Groups() );
220}
221
222
223// Test saving & loading of a few configurations
224BOOST_AUTO_TEST_CASE( HealthyGroups )
225{
226 // Test board with no groups
227 testSaveLoad( {} );
228
229 // Single group
230 testSaveLoad( { { TEXT0 } } );
231 testSaveLoad( { { TEXT0, TEXT1 } } );
232
233 // Two groups
234 testSaveLoad( { { TEXT0, TEXT1 }, { TEXT2, TEXT3 } } );
235 testSaveLoad( { { TEXT0, TEXT1 }, { TEXT2, GROUP0 } } );
236
237 // Subgroups by no cycle
238 testSaveLoad( { { TEXT0, GROUP1 }, { TEXT2 }, { TEXT3, GROUP0 } } );
239 testSaveLoad( { { TEXT0 }, { TEXT2 }, { GROUP1, GROUP0 } } );
240 testSaveLoad( { { TEXT0 }, { TEXT1 }, { TEXT2, NAME_GROUP3 }, { TEXT3 } } );
241 testSaveLoad( { { TEXT0 }, { TEXT1 }, { TEXT2, NAME_GROUP3 }, { TEXT3, GROUP0 } } );
242 testSaveLoad( { { TEXT0 }, { TEXT1 }, { TEXT2 }, { TEXT3 }, { NAME_GROUP3, GROUP0 } } );
243}
244
245
246BOOST_AUTO_TEST_CASE( InvalidGroups )
247{
248 // A cycle
249 std::unique_ptr<BOARD> board1 = createBoard( { { TEXT0, GROUP1 }, { TEXT2, GROUP0 } } );
250 BOOST_CHECK_EQUAL( board1->GroupsSanityCheck(), "Cycle detected in group membership" );
251
252 // More complex cycle
253 board1 = createBoard( { { TEXT0, GROUP1 }, { TEXT1 }, { TEXT2, NAME_GROUP4 },
254 { TEXT3, GROUP2 }, { TEXT4, NAME_GROUP3 } } );
255 BOOST_CHECK_EQUAL( board1->GroupsSanityCheck(), "Cycle detected in group membership" );
256
257 // Delete the removed group since the test is over
258 board1.reset( nullptr );
259 delete s_removedGroup;
260 s_removedGroup = nullptr;
261
262 // Delete the removed text since the test is over
263 board1.reset( nullptr );
264 delete s_removedText;
265 s_removedText = nullptr;
266}
267
268
const char * name
Definition: DXF_plotter.cpp:57
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:79
const KIID m_Uuid
Definition: eda_item.h:489
virtual void SetText(const wxString &aText)
Definition: eda_text.cpp:182
wxString AsString() const
Definition: kiid.cpp:238
A set of BOARD_ITEMs (i.e., without duplicates).
Definition: pcb_group.h:52
std::unordered_set< BOARD_ITEM * > & GetItems()
Definition: pcb_group.h:69
void SetName(const wxString &aName)
Definition: pcb_group.h:67
wxString GetName() const
Definition: pcb_group.h:66
The common library.
#define _(s)
BOOST_AUTO_TEST_CASE(HealthyGroups)
ItemType
@ 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()