2020-08-11 19:37:07 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2020 Joshua Redstone redstone at gmail.com
|
|
|
|
* Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, you may find one here:
|
|
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
#include <bitset>
|
|
|
|
#include <string>
|
2020-08-11 19:37:07 +00:00
|
|
|
|
|
|
|
#include <boost/filesystem.hpp>
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <board.h>
|
|
|
|
#include <footprint.h>
|
2020-10-04 23:34:59 +00:00
|
|
|
#include <pcb_text.h>
|
2020-08-11 19:37:07 +00:00
|
|
|
#include <common.h>
|
|
|
|
#include <pcbnew_utils/board_construction_utils.h>
|
|
|
|
#include <pcbnew_utils/board_file_utils.h>
|
|
|
|
#include <unit_test_utils/unit_test_utils.h>
|
|
|
|
|
|
|
|
BOOST_AUTO_TEST_SUITE( GroupSaveLoad )
|
|
|
|
|
|
|
|
// The tests below set up a test case with a spec for the set of groups to create.
|
|
|
|
// A group can contain members from this list of candidates.
|
|
|
|
enum ItemType
|
|
|
|
{
|
|
|
|
TEXT0,
|
|
|
|
TEXT1,
|
|
|
|
TEXT2,
|
|
|
|
TEXT3,
|
|
|
|
TEXT4,
|
|
|
|
TEXT5,
|
|
|
|
TEXT6,
|
|
|
|
TEXT7,
|
|
|
|
TEXT8,
|
2020-09-16 01:02:57 +00:00
|
|
|
REMOVED_TEXT, // Text not added to board
|
2020-08-11 19:37:07 +00:00
|
|
|
GROUP0,
|
|
|
|
GROUP1,
|
|
|
|
GROUP2,
|
|
|
|
NAME_GROUP3,
|
|
|
|
NAME_GROUP4,
|
|
|
|
NAME_GROUP3_DUP, // Group with name identical to NAME_GROUP3
|
2020-09-16 01:02:57 +00:00
|
|
|
REMOVED_GROUP, // Group not added to board
|
2020-08-11 19:37:07 +00:00
|
|
|
NUM_ITEMS
|
|
|
|
};
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
// The objects associated with item REMOVED_TEXT and REMOVED_GROUP are not added to the board,
|
|
|
|
// so they are not cleaned up when the board is deleted. These pointers stores the objects
|
|
|
|
// so they can be deleted once they are done being used.
|
2020-10-04 23:34:59 +00:00
|
|
|
static PCB_TEXT* s_removedText = nullptr;
|
2020-09-16 01:02:57 +00:00
|
|
|
static PCB_GROUP* s_removedGroup = nullptr;
|
|
|
|
|
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
/*
|
|
|
|
* Takes a vector of group specifications for groups to create.
|
|
|
|
* Each group is a vector of which ItemTypes to put in the group.
|
|
|
|
* The first group corresponds to GROUP0, the second to GROUP1, and os on.
|
|
|
|
*/
|
2020-09-16 01:02:57 +00:00
|
|
|
std::unique_ptr<BOARD> createBoard( const std::vector<std::vector<ItemType>>& spec )
|
2020-08-11 19:37:07 +00:00
|
|
|
{
|
2020-09-16 01:02:57 +00:00
|
|
|
std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
|
|
|
|
std::vector<BOARD_ITEM*> items;
|
|
|
|
|
|
|
|
// Create text items and add to board.
|
|
|
|
for( int idx = 0; idx <= REMOVED_TEXT; idx++ )
|
|
|
|
{
|
2020-10-04 23:34:59 +00:00
|
|
|
PCB_TEXT* textItem = new PCB_TEXT( board.get() );
|
2020-09-16 01:02:57 +00:00
|
|
|
textItem->SetText( wxString::Format( _( "some text-%d" ), idx ) );
|
|
|
|
|
|
|
|
// Don't add REMOVED_TEXT to the board
|
|
|
|
if( idx < REMOVED_TEXT )
|
|
|
|
board->Add( textItem );
|
|
|
|
|
|
|
|
items.push_back( textItem );
|
|
|
|
}
|
2020-08-11 19:37:07 +00:00
|
|
|
|
|
|
|
// Create groups
|
2020-09-16 01:02:57 +00:00
|
|
|
for( int idx = 0; idx < ( NUM_ITEMS - GROUP0 ); idx++ )
|
2020-08-11 19:37:07 +00:00
|
|
|
{
|
2020-09-16 01:02:57 +00:00
|
|
|
PCB_GROUP* gr = new PCB_GROUP( board.get() );
|
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
if( idx >= ( NAME_GROUP3 - GROUP0 ) )
|
|
|
|
{
|
2020-09-16 01:02:57 +00:00
|
|
|
wxString name = wxString::Format( _( "group-%d" ),
|
|
|
|
( idx == ( NAME_GROUP3_DUP - GROUP0 ) ) ? 3 : idx );
|
2020-08-11 19:37:07 +00:00
|
|
|
gr->SetName( name );
|
|
|
|
BOOST_CHECK_EQUAL( gr->GetName(), name );
|
|
|
|
}
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
items.push_back( gr );
|
2020-08-11 19:37:07 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
std::bitset<NUM_ITEMS> used;
|
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
// Populate groups based on spec
|
2020-09-16 01:02:57 +00:00
|
|
|
for( int offset = 0; offset < ( NUM_ITEMS - GROUP0 ); offset++ )
|
2020-08-11 19:37:07 +00:00
|
|
|
{
|
2020-09-16 01:02:57 +00:00
|
|
|
int groupIdx = GROUP0 + offset;
|
|
|
|
|
|
|
|
PCB_GROUP* group = static_cast<PCB_GROUP*>( items[groupIdx] );
|
|
|
|
|
|
|
|
if( offset < spec.size() )
|
2020-08-11 19:37:07 +00:00
|
|
|
{
|
2020-09-16 01:02:57 +00:00
|
|
|
const std::vector<ItemType>& groupSpec = spec[offset];
|
|
|
|
|
|
|
|
for( ItemType item : groupSpec )
|
2020-08-11 19:37:07 +00:00
|
|
|
{
|
2020-09-16 01:02:57 +00:00
|
|
|
used.set( static_cast<size_t>( item ) );
|
2020-09-16 09:50:59 +00:00
|
|
|
group->AddItem( items[item] );
|
2020-08-11 19:37:07 +00:00
|
|
|
}
|
2020-09-16 01:02:57 +00:00
|
|
|
|
|
|
|
BOOST_CHECK_EQUAL( group->GetItems().size(), groupSpec.size() );
|
|
|
|
board->Add( group );
|
|
|
|
}
|
|
|
|
else if( groupIdx != REMOVED_GROUP && used.test( groupIdx ) )
|
|
|
|
{
|
|
|
|
// This group is used in another group, so it must be on the board
|
|
|
|
board->Add( group );
|
|
|
|
}
|
|
|
|
else if( groupIdx != REMOVED_GROUP )
|
|
|
|
{
|
|
|
|
// If the group isn't used, delete it
|
|
|
|
delete group;
|
2020-08-11 19:37:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
// Delete the removed text item if it isn't used
|
|
|
|
if( used.test( REMOVED_TEXT ) )
|
2020-10-04 23:34:59 +00:00
|
|
|
s_removedText = static_cast<PCB_TEXT*>( items[REMOVED_TEXT] );
|
2020-09-16 01:02:57 +00:00
|
|
|
else
|
|
|
|
delete items[REMOVED_TEXT];
|
|
|
|
|
|
|
|
// Delete the removed group item if it isn't used
|
|
|
|
if( used.test( REMOVED_GROUP ) )
|
|
|
|
s_removedGroup = static_cast<PCB_GROUP*>( items[REMOVED_GROUP] );
|
|
|
|
else
|
|
|
|
delete items[REMOVED_GROUP];
|
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
BOOST_TEST_CHECKPOINT( "Returning fresh board" );
|
2020-09-16 01:02:57 +00:00
|
|
|
return board;
|
2020-08-11 19:37:07 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
// Check if two groups are identical by comparing the fields (by Uuid).
|
2020-08-12 11:23:30 +00:00
|
|
|
void testGroupEqual( const PCB_GROUP& group1, const PCB_GROUP& group2 )
|
2020-08-11 19:37:07 +00:00
|
|
|
{
|
|
|
|
BOOST_CHECK_EQUAL( group1.m_Uuid.AsString(), group2.m_Uuid.AsString() );
|
|
|
|
BOOST_CHECK_EQUAL( group1.GetName(), group2.GetName() );
|
|
|
|
|
2020-09-25 17:37:03 +00:00
|
|
|
const std::unordered_set<BOARD_ITEM*>& items1 = group1.GetItems();
|
|
|
|
const std::unordered_set<BOARD_ITEM*>& items2 = group2.GetItems();
|
2020-09-16 01:02:57 +00:00
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
BOOST_CHECK_EQUAL( items1.size(), items2.size() );
|
2020-09-16 01:02:57 +00:00
|
|
|
|
|
|
|
// Test that the sets items1 and items2 are identical, by checking m_Uuid
|
|
|
|
for( BOARD_ITEM* item1 : items1 )
|
2020-08-11 19:37:07 +00:00
|
|
|
{
|
2020-09-16 01:02:57 +00:00
|
|
|
auto cmp = [&]( BOARD_ITEM* elem )
|
|
|
|
{
|
|
|
|
return elem->m_Uuid.AsString() == item1->m_Uuid.AsString();
|
|
|
|
};
|
|
|
|
|
|
|
|
auto item2 = std::find_if( items2.begin(), items2.end(), cmp );
|
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
BOOST_CHECK( item2 != items2.end() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
// Check if two GROUPS are identical by comparing the groups in each of them.
|
|
|
|
void testGroupsEqual( const GROUPS& groups1, const GROUPS& groups2 )
|
|
|
|
{
|
|
|
|
BOOST_CHECK_EQUAL( groups1.size(), groups2.size() );
|
2020-09-16 01:02:57 +00:00
|
|
|
|
|
|
|
for( PCB_GROUP* group1 : groups1 )
|
2020-08-11 19:37:07 +00:00
|
|
|
{
|
2020-09-16 01:02:57 +00:00
|
|
|
auto cmp = [&]( BOARD_ITEM* elem )
|
|
|
|
{
|
|
|
|
return elem->m_Uuid.AsString() == group1->m_Uuid.AsString();
|
|
|
|
};
|
|
|
|
|
|
|
|
auto group2 = std::find_if( groups2.begin(), groups2.end(), cmp );
|
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
BOOST_CHECK( group2 != groups2.end() );
|
2020-09-16 01:02:57 +00:00
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
testGroupEqual( *group1, **group2 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
/*
|
|
|
|
* Create board based on spec, save it to a file, load it, and make sure the
|
|
|
|
* groups in the resulting board are the same as the groups we started with.
|
|
|
|
*/
|
|
|
|
void testSaveLoad( const std::vector<std::vector<ItemType>>& spec )
|
|
|
|
{
|
2020-09-16 01:02:57 +00:00
|
|
|
std::unique_ptr<BOARD> board1 = createBoard( spec );
|
2020-08-11 19:37:07 +00:00
|
|
|
auto path = boost::filesystem::temp_directory_path() / "group_saveload_tst.kicad_pcb";
|
2020-09-16 01:02:57 +00:00
|
|
|
::KI_TEST::DumpBoardToFile( *board1, path.string() );
|
|
|
|
|
|
|
|
std::unique_ptr<BOARD> board2 = ::KI_TEST::ReadBoardFromFileOrStream( path.string() );
|
|
|
|
testGroupsEqual( board1->Groups(), board2->Groups() );
|
2020-08-11 19:37:07 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
// Test saving & loading of a few configurations
|
2020-09-16 01:02:57 +00:00
|
|
|
BOOST_AUTO_TEST_CASE( HealthyGroups )
|
2020-08-11 19:37:07 +00:00
|
|
|
{
|
|
|
|
// Test board with no groups
|
|
|
|
testSaveLoad( {} );
|
|
|
|
|
|
|
|
// Single group
|
|
|
|
testSaveLoad( { { TEXT0 } } );
|
|
|
|
testSaveLoad( { { TEXT0, TEXT1 } } );
|
|
|
|
|
|
|
|
// Two groups
|
|
|
|
testSaveLoad( { { TEXT0, TEXT1 }, { TEXT2, TEXT3 } } );
|
|
|
|
testSaveLoad( { { TEXT0, TEXT1 }, { TEXT2, GROUP0 } } );
|
|
|
|
|
|
|
|
// Subgroups by no cycle
|
|
|
|
testSaveLoad( { { TEXT0, GROUP1 }, { TEXT2 }, { TEXT3, GROUP0 } } );
|
|
|
|
testSaveLoad( { { TEXT0 }, { TEXT2 }, { GROUP1, GROUP0 } } );
|
|
|
|
testSaveLoad( { { TEXT0 }, { TEXT1 }, { TEXT2, NAME_GROUP3 }, { TEXT3 } } );
|
|
|
|
testSaveLoad( { { TEXT0 }, { TEXT1 }, { TEXT2, NAME_GROUP3 }, { TEXT3, GROUP0 } } );
|
|
|
|
testSaveLoad( { { TEXT0 }, { TEXT1 }, { TEXT2 }, { TEXT3 }, { NAME_GROUP3, GROUP0 } } );
|
|
|
|
}
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE( InvalidGroups )
|
2020-08-11 19:37:07 +00:00
|
|
|
{
|
|
|
|
// A cycle
|
2020-09-16 01:02:57 +00:00
|
|
|
std::unique_ptr<BOARD> board1 = createBoard( { { TEXT0, GROUP1 }, { TEXT2, GROUP0 } } );
|
|
|
|
BOOST_CHECK_EQUAL( board1->GroupsSanityCheck(), "Cycle detected in group membership" );
|
2020-08-11 19:37:07 +00:00
|
|
|
|
|
|
|
// More complex cycle
|
2020-09-16 01:02:57 +00:00
|
|
|
board1 = createBoard( { { TEXT0, GROUP1 }, { TEXT1 }, { TEXT2, NAME_GROUP4 },
|
|
|
|
{ TEXT3, GROUP2 }, { TEXT4, NAME_GROUP3 } } );
|
|
|
|
BOOST_CHECK_EQUAL( board1->GroupsSanityCheck(), "Cycle detected in group membership" );
|
2020-08-11 19:37:07 +00:00
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
// Delete the removed group since the test is over
|
|
|
|
board1.reset( nullptr );
|
|
|
|
delete s_removedGroup;
|
|
|
|
s_removedGroup = nullptr;
|
2020-08-11 19:37:07 +00:00
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
// Delete the removed text since the test is over
|
|
|
|
board1.reset( nullptr );
|
|
|
|
delete s_removedText;
|
|
|
|
s_removedText = nullptr;
|
2020-08-11 19:37:07 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 01:02:57 +00:00
|
|
|
|
2020-08-11 19:37:07 +00:00
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|