KiCad PCB EDA Suite
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 <string>
27 
28 #include <boost/filesystem.hpp>
29 #include <board.h>
30 #include <footprint.h>
31 #include <pcb_group.h>
32 #include <pcb_text.h>
33 #include <common.h>
37 
38 BOOST_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.
67 static PCB_TEXT* s_removedText = nullptr;
68 static 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  */
76 std::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).
162 void 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.
188 void 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  */
212 void testSaveLoad( const std::vector<std::vector<ItemType>>& spec )
213 {
214  std::unique_ptr<BOARD> board1 = createBoard( spec );
215  auto path = boost::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
224 BOOST_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 
246 BOOST_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 
269 BOOST_AUTO_TEST_SUITE_END()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:49
A set of BOARD_ITEMs (i.e., without duplicates).
Definition: pcb_group.h:50
void testGroupsEqual(const GROUPS &groups1, const GROUPS &groups2)
wxString AsString() const
Definition: kiid.cpp:218
static PCB_GROUP * s_removedGroup
static PCB_TEXT * s_removedText
Construction utilities for PCB tests.
std::unordered_set< BOARD_ITEM * > & GetItems()
Definition: pcb_group.h:68
BOOST_AUTO_TEST_CASE(HealthyGroups)
BOOST_CHECK(v2.Cross(v1)==1)
virtual void SetText(const wxString &aText)
Definition: eda_text.cpp:124
wxString GetName() const
Definition: pcb_group.h:65
void DumpBoardToFile(BOARD &board, const std::string &aFilename)
Utility function to simply write a Board out to a file.
#define _(s)
void testSaveLoad(const std::vector< std::vector< ItemType >> &spec)
void testGroupEqual(const PCB_GROUP &group1, const PCB_GROUP &group2)
const KIID m_Uuid
Definition: eda_item.h:475
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
const char * name
Definition: DXF_plotter.cpp:56
std::unique_ptr< BOARD > createBoard(const std::vector< std::vector< ItemType >> &spec)
The common library.
General utilities for PCB file IO for QA programs.
std::unique_ptr< BOARD > ReadBoardFromFileOrStream(const std::string &aFilename, std::istream &aFallback)
Read a board from a file, or another stream, as appropriate.
ItemType
void SetName(const wxString &aName)
Definition: pcb_group.h:66