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