/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019-2024 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CHECK_ENUM_CLASS_EQUAL( L, R ) \ BOOST_CHECK_EQUAL( static_cast( L ), static_cast( R ) ) namespace KI_TEST { BOARD_DUMPER::BOARD_DUMPER() : m_dump_boards( true ) { } void BOARD_DUMPER::DumpBoardToFile( BOARD& aBoard, const std::string& aName ) const { if( !m_dump_boards ) return; auto path = std::filesystem::temp_directory_path() / aName; path += ".kicad_pcb"; BOOST_TEST_MESSAGE( "Dumping board file: " << path.string() ); ::KI_TEST::DumpBoardToFile( aBoard, path.string() ); } void LoadBoard( SETTINGS_MANAGER& aSettingsManager, const wxString& aRelPath, std::unique_ptr& aBoard ) { if( aBoard ) { aBoard->SetProject( nullptr ); aBoard = nullptr; } std::string absPath = GetPcbnewTestDataDir() + aRelPath.ToStdString(); wxFileName projectFile( absPath + ".kicad_pro" ); wxFileName legacyProject( absPath + ".pro" ); std::string boardPath = absPath + ".kicad_pcb"; wxFileName rulesFile( absPath + ".kicad_dru" ); if( projectFile.Exists() ) aSettingsManager.LoadProject( projectFile.GetFullPath() ); else if( legacyProject.Exists() ) aSettingsManager.LoadProject( legacyProject.GetFullPath() ); BOOST_TEST_MESSAGE( "Loading board file: " << boardPath ); try { aBoard = ReadBoardFromFileOrStream( boardPath ); } catch( const IO_ERROR& ioe ) { BOOST_TEST_ERROR( ioe.What() ); } BOOST_REQUIRE( aBoard ); if( projectFile.Exists() || legacyProject.Exists() ) aBoard->SetProject( &aSettingsManager.Prj() ); auto m_DRCEngine = std::make_shared( aBoard.get(), &aBoard->GetDesignSettings() ); if( rulesFile.Exists() ) m_DRCEngine->InitEngine( rulesFile ); else m_DRCEngine->InitEngine( wxFileName() ); aBoard->GetDesignSettings().m_DRCEngine = m_DRCEngine; aBoard->BuildListOfNets(); aBoard->BuildConnectivity(); } BOARD_ITEM& RequireBoardItemWithTypeAndId( const BOARD& aBoard, KICAD_T aItemType, const KIID& aID ) { BOARD_ITEM* item = aBoard.GetItem( aID ); BOOST_REQUIRE( item ); BOOST_REQUIRE_EQUAL( item->Type(), aItemType ); return *item; } void LoadAndTestBoardFile( const wxString aRelativePath, bool aRoundtrip, std::function aBoardTestFunction, std::optional aExpectedBoardVersion ) { const std::string absBoardPath = KI_TEST::GetPcbnewTestDataDir() + aRelativePath.ToStdString() + ".kicad_pcb"; BOOST_TEST_MESSAGE( "Loading board to test: " << absBoardPath ); std::unique_ptr board1 = KI_TEST::ReadBoardFromFileOrStream( absBoardPath ); // Should load - if it doesn't we're done for BOOST_REQUIRE( board1 ); BOOST_TEST_MESSAGE( "Testing loaded board" ); aBoardTestFunction( *board1 ); // If we care about the board version, check it now - but not after a roundtrip // (as the version will be updated to the current version) if( aExpectedBoardVersion ) { BOOST_CHECK_EQUAL( board1->GetFileFormatVersionAtLoad(), *aExpectedBoardVersion ); } if( aRoundtrip ) { const auto savePath = std::filesystem::temp_directory_path() / ( aRelativePath.ToStdString() + ".kicad_pcb" ); KI_TEST::DumpBoardToFile( *board1, savePath.string() ); std::unique_ptr board2 = KI_TEST::ReadBoardFromFileOrStream( savePath.string() ); // Should load again BOOST_REQUIRE( board2 ); BOOST_TEST_MESSAGE( "Testing roundtripped (saved/reloaded) file" ); aBoardTestFunction( *board2 ); } } void FillZones( BOARD* m_board ) { TOOL_MANAGER toolMgr; toolMgr.SetEnvironment( m_board, nullptr, nullptr, nullptr, nullptr ); KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL(); toolMgr.RegisterTool( dummyTool ); BOARD_COMMIT commit( dummyTool ); ZONE_FILLER filler( m_board, &commit ); std::vector toFill; for( ZONE* zone : m_board->Zones() ) toFill.push_back( zone ); if( filler.Fill( toFill, false, nullptr ) ) commit.Push( _( "Fill Zone(s)" ), SKIP_UNDO | SKIP_SET_DIRTY | ZONE_FILL_OP | SKIP_CONNECTIVITY ); m_board->BuildConnectivity(); } #define TEST( a, b ) \ { \ if( a != b ) \ return a < b; \ } #define TEST_PT( a, b ) \ { \ if( a.x != b.x ) \ return a.x < b.x; \ if( a.y != b.y ) \ return a.y < b.y; \ } struct kitest_cmp_drawings { FOOTPRINT::cmp_drawings fp_comp; bool operator()( const BOARD_ITEM* itemA, const BOARD_ITEM* itemB ) const { TEST( itemA->Type(), itemB->Type() ); if( itemA->GetLayerSet() != itemB->GetLayerSet() ) return itemA->GetLayerSet().Seq() < itemB->GetLayerSet().Seq(); if( itemA->Type() == PCB_TEXT_T ) { const PCB_TEXT* textA = static_cast( itemA ); const PCB_TEXT* textB = static_cast( itemB ); TEST_PT( textA->GetPosition(), textB->GetPosition() ); TEST( textA->GetTextAngle(), textB->GetTextAngle() ); } return fp_comp( itemA, itemB ); } }; void CheckFootprint( const FOOTPRINT* expected, const FOOTPRINT* fp ) { CHECK_ENUM_CLASS_EQUAL( expected->Type(), fp->Type() ); // TODO: validate those informations match the importer BOOST_CHECK_EQUAL( expected->GetPosition(), fp->GetPosition() ); BOOST_CHECK_EQUAL( expected->GetOrientation(), fp->GetOrientation() ); BOOST_CHECK_EQUAL( expected->GetReference(), fp->GetReference() ); BOOST_CHECK_EQUAL( expected->GetValue(), fp->GetValue() ); BOOST_CHECK_EQUAL( expected->GetLibDescription(), fp->GetLibDescription() ); BOOST_CHECK_EQUAL( expected->GetKeywords(), fp->GetKeywords() ); BOOST_CHECK_EQUAL( expected->GetAttributes(), fp->GetAttributes() ); BOOST_CHECK_EQUAL( expected->GetFlag(), fp->GetFlag() ); //BOOST_CHECK_EQUAL( expected->GetProperties(), fp->GetProperties() ); BOOST_CHECK_EQUAL( expected->GetTypeName(), fp->GetTypeName() ); // simple test if count matches BOOST_CHECK_EQUAL( expected->Fields().size(), fp->Fields().size() ); BOOST_CHECK_EQUAL( expected->Pads().size(), fp->Pads().size() ); BOOST_CHECK_EQUAL( expected->GraphicalItems().size(), fp->GraphicalItems().size() ); BOOST_CHECK_EQUAL( expected->Zones().size(), fp->Zones().size() ); BOOST_CHECK_EQUAL( expected->Groups().size(), fp->Groups().size() ); BOOST_CHECK_EQUAL( expected->Models().size(), fp->Models().size() ); std::set expectedPads( expected->Pads().begin(), expected->Pads().end() ); std::set fpPads( fp->Pads().begin(), fp->Pads().end() ); for( auto itExpected = expectedPads.begin(), itFp = fpPads.begin(); itExpected != expectedPads.end() && itFp != fpPads.end(); itExpected++, itFp++ ) { CheckFpPad( *itExpected, *itFp ); } std::set expectedGraphicalItems( expected->GraphicalItems().begin(), expected->GraphicalItems().end() ); std::set fpGraphicalItems( fp->GraphicalItems().begin(), fp->GraphicalItems().end() ); for( auto itExpected = expectedGraphicalItems.begin(), itFp = fpGraphicalItems.begin(); itExpected != expectedGraphicalItems.end() && itFp != fpGraphicalItems.end(); itExpected++, itFp++ ) { BOOST_CHECK_EQUAL( ( *itExpected )->Type(), ( *itFp )->Type() ); switch( ( *itExpected )->Type() ) { case PCB_TEXT_T: { const PCB_TEXT* expectedText = static_cast( *itExpected ); const PCB_TEXT* text = static_cast( *itFp ); CheckFpText( expectedText, text ); break; } case PCB_SHAPE_T: { const PCB_SHAPE* expectedShape = static_cast( *itExpected ); const PCB_SHAPE* shape = static_cast( *itFp ); CheckFpShape( expectedShape, shape ); break; } case PCB_DIM_ALIGNED_T: case PCB_DIM_LEADER_T: case PCB_DIM_CENTER_T: case PCB_DIM_RADIAL_T: case PCB_DIM_ORTHOGONAL_T: // TODO break; default: BOOST_ERROR( "KICAD_T not known" ); break; } } std::set expectedZones( expected->Zones().begin(), expected->Zones().end() ); std::set fpZones( fp->Zones().begin(), fp->Zones().end() ); for( auto itExpected = expectedZones.begin(), itFp = fpZones.begin(); itExpected != expectedZones.end() && itFp != fpZones.end(); itExpected++, itFp++ ) { CheckFpZone( *itExpected, *itFp ); } // TODO: Groups // Use FootprintNeedsUpdate as sanity check (which should do the same thing as our manually coded checks) // If we get the reporter working, and COMPARE_FLAGS::DRC is enough for us, we can remove the old code BOOST_CHECK( !const_cast(expected)->FootprintNeedsUpdate(fp, BOARD_ITEM::COMPARE_FLAGS::DRC, nullptr) ); } void CheckFpPad( const PAD* expected, const PAD* pad ) { BOOST_TEST_CONTEXT( "Assert PAD with KIID=" << expected->m_Uuid.AsString() ) { CHECK_ENUM_CLASS_EQUAL( expected->Type(), pad->Type() ); BOOST_CHECK_EQUAL( expected->GetNumber(), pad->GetNumber() ); CHECK_ENUM_CLASS_EQUAL( expected->GetAttribute(), pad->GetAttribute() ); CHECK_ENUM_CLASS_EQUAL( expected->GetProperty(), pad->GetProperty() ); CHECK_ENUM_CLASS_EQUAL( expected->GetShape(), pad->GetShape() ); BOOST_CHECK_EQUAL( expected->IsLocked(), pad->IsLocked() ); BOOST_CHECK_EQUAL( expected->GetPosition(), pad->GetPosition() ); BOOST_CHECK_EQUAL( expected->GetSize(), pad->GetSize() ); BOOST_CHECK_EQUAL( expected->GetOrientation(), pad->GetOrientation() ); BOOST_CHECK_EQUAL( expected->GetDelta(), pad->GetDelta() ); BOOST_CHECK_EQUAL( expected->GetOffset(), pad->GetOffset() ); BOOST_CHECK_EQUAL( expected->GetDrillSize(), pad->GetDrillSize() ); CHECK_ENUM_CLASS_EQUAL( expected->GetDrillShape(), pad->GetDrillShape() ); BOOST_CHECK_EQUAL( expected->GetLayerSet(), pad->GetLayerSet() ); BOOST_CHECK_EQUAL( expected->GetNetCode(), pad->GetNetCode() ); BOOST_CHECK_EQUAL( expected->GetPinFunction(), pad->GetPinFunction() ); BOOST_CHECK_EQUAL( expected->GetPinType(), pad->GetPinType() ); BOOST_CHECK_EQUAL( expected->GetPadToDieLength(), pad->GetPadToDieLength() ); BOOST_CHECK_EQUAL( expected->GetLocalSolderMaskMargin().value_or( 0 ), pad->GetLocalSolderMaskMargin().value_or( 0 ) ); BOOST_CHECK_EQUAL( expected->GetLocalSolderPasteMargin().value_or( 0 ), pad->GetLocalSolderPasteMargin().value_or( 0 ) ); BOOST_CHECK_EQUAL( expected->GetLocalSolderPasteMarginRatio().value_or( 0 ), pad->GetLocalSolderPasteMarginRatio().value_or( 0 ) ); BOOST_CHECK_EQUAL( expected->GetLocalClearance().value_or( 0 ), pad->GetLocalClearance().value_or( 0 ) ); CHECK_ENUM_CLASS_EQUAL( expected->GetLocalZoneConnection(), pad->GetLocalZoneConnection() ); BOOST_CHECK_EQUAL( expected->GetThermalSpokeWidth(), pad->GetThermalSpokeWidth() ); BOOST_CHECK_EQUAL( expected->GetThermalSpokeAngle(), pad->GetThermalSpokeAngle() ); BOOST_CHECK_EQUAL( expected->GetThermalGap(), pad->GetThermalGap() ); BOOST_CHECK_EQUAL( expected->GetRoundRectRadiusRatio(), pad->GetRoundRectRadiusRatio() ); BOOST_CHECK_EQUAL( expected->GetChamferRectRatio(), pad->GetChamferRectRatio() ); BOOST_CHECK_EQUAL( expected->GetChamferPositions(), pad->GetChamferPositions() ); BOOST_CHECK_EQUAL( expected->GetRemoveUnconnected(), pad->GetRemoveUnconnected() ); BOOST_CHECK_EQUAL( expected->GetKeepTopBottom(), pad->GetKeepTopBottom() ); // TODO: did we check everything for complex pad shapes? CHECK_ENUM_CLASS_EQUAL( expected->GetAnchorPadShape(), pad->GetAnchorPadShape() ); CHECK_ENUM_CLASS_EQUAL( expected->GetCustomShapeInZoneOpt(), pad->GetCustomShapeInZoneOpt() ); BOOST_CHECK_EQUAL( expected->GetPrimitives().size(), pad->GetPrimitives().size() ); if( expected->GetPrimitives().size() == pad->GetPrimitives().size() ) { for( size_t i = 0; i < expected->GetPrimitives().size(); ++i ) { CheckFpShape( expected->GetPrimitives().at( i ).get(), pad->GetPrimitives().at( i ).get() ); } } } } void CheckFpText( const PCB_TEXT* expected, const PCB_TEXT* text ) { BOOST_TEST_CONTEXT( "Assert PCB_TEXT with KIID=" << expected->m_Uuid.AsString() ) { CHECK_ENUM_CLASS_EQUAL( expected->Type(), text->Type() ); BOOST_CHECK_EQUAL( expected->IsLocked(), text->IsLocked() ); BOOST_CHECK_EQUAL( expected->GetText(), text->GetText() ); BOOST_CHECK_EQUAL( expected->GetPosition(), text->GetPosition() ); BOOST_CHECK_EQUAL( expected->GetTextAngle(), text->GetTextAngle() ); BOOST_CHECK_EQUAL( expected->IsKeepUpright(), text->IsKeepUpright() ); BOOST_CHECK_EQUAL( expected->GetLayerSet(), text->GetLayerSet() ); BOOST_CHECK_EQUAL( expected->IsVisible(), text->IsVisible() ); BOOST_CHECK_EQUAL( expected->GetTextSize(), text->GetTextSize() ); BOOST_CHECK_EQUAL( expected->GetLineSpacing(), text->GetLineSpacing() ); BOOST_CHECK_EQUAL( expected->GetTextThickness(), text->GetTextThickness() ); BOOST_CHECK_EQUAL( expected->IsBold(), text->IsBold() ); BOOST_CHECK_EQUAL( expected->IsItalic(), text->IsItalic() ); BOOST_CHECK_EQUAL( expected->GetHorizJustify(), text->GetHorizJustify() ); BOOST_CHECK_EQUAL( expected->GetVertJustify(), text->GetVertJustify() ); BOOST_CHECK_EQUAL( expected->IsMirrored(), text->IsMirrored() ); BOOST_CHECK_EQUAL( expected->GetFontName(), text->GetFontName() ); // TODO: bold/italic setting? // TODO: render cache? } } void CheckFpShape( const PCB_SHAPE* expected, const PCB_SHAPE* shape ) { BOOST_TEST_CONTEXT( "Assert PCB_SHAPE with KIID=" << expected->m_Uuid.AsString() ) { CHECK_ENUM_CLASS_EQUAL( expected->Type(), shape->Type() ); CHECK_ENUM_CLASS_EQUAL( expected->GetShape(), shape->GetShape() ); BOOST_CHECK_EQUAL( expected->IsLocked(), shape->IsLocked() ); BOOST_CHECK_EQUAL( expected->GetStart(), shape->GetStart() ); BOOST_CHECK_EQUAL( expected->GetEnd(), shape->GetEnd() ); if( expected->GetShape() == SHAPE_T::ARC ) { // center and position might differ as they are calculated from start/mid/end -> compare mid instead BOOST_CHECK_EQUAL( expected->GetArcMid(), shape->GetArcMid() ); } else { BOOST_CHECK_EQUAL( expected->GetCenter(), shape->GetCenter() ); BOOST_CHECK_EQUAL( expected->GetPosition(), shape->GetPosition() ); } BOOST_CHECK_EQUAL( expected->GetBezierC1(), shape->GetBezierC1() ); BOOST_CHECK_EQUAL( expected->GetBezierC2(), shape->GetBezierC2() ); CheckShapePolySet( &expected->GetPolyShape(), &shape->GetPolyShape() ); BOOST_CHECK_EQUAL( expected->GetLayerSet(), shape->GetLayerSet() ); BOOST_CHECK_EQUAL( expected->GetStroke().GetWidth(), shape->GetStroke().GetWidth() ); CHECK_ENUM_CLASS_EQUAL( expected->GetStroke().GetLineStyle(), shape->GetStroke().GetLineStyle() ); CHECK_ENUM_CLASS_EQUAL( expected->GetFillMode(), shape->GetFillMode() ); } } void CheckFpZone( const ZONE* expected, const ZONE* zone ) { BOOST_TEST_CONTEXT( "Assert ZONE with KIID=" << expected->m_Uuid.AsString() ) { CHECK_ENUM_CLASS_EQUAL( expected->Type(), zone->Type() ); BOOST_CHECK_EQUAL( expected->IsLocked(), zone->IsLocked() ); BOOST_CHECK_EQUAL( expected->GetNetCode(), zone->GetNetCode() ); BOOST_CHECK_EQUAL( expected->GetAssignedPriority(), zone->GetAssignedPriority() ); CHECK_ENUM_CLASS_EQUAL( expected->GetPadConnection(), zone->GetPadConnection() ); BOOST_CHECK_EQUAL( expected->GetLocalClearance().value_or( 0 ), zone->GetLocalClearance().value_or( 0 ) ); BOOST_CHECK_EQUAL( expected->GetMinThickness(), zone->GetMinThickness() ); BOOST_CHECK_EQUAL( expected->GetLayerSet(), zone->GetLayerSet() ); BOOST_CHECK_EQUAL( expected->IsFilled(), zone->IsFilled() ); CHECK_ENUM_CLASS_EQUAL( expected->GetFillMode(), zone->GetFillMode() ); BOOST_CHECK_EQUAL( expected->GetHatchThickness(), zone->GetHatchThickness() ); BOOST_CHECK_EQUAL( expected->GetHatchGap(), zone->GetHatchGap() ); BOOST_CHECK_EQUAL( expected->GetHatchOrientation(), zone->GetHatchOrientation() ); BOOST_CHECK_EQUAL( expected->GetHatchSmoothingLevel(), zone->GetHatchSmoothingLevel() ); BOOST_CHECK_EQUAL( expected->GetHatchSmoothingValue(), zone->GetHatchSmoothingValue() ); BOOST_CHECK_EQUAL( expected->GetHatchBorderAlgorithm(), zone->GetHatchBorderAlgorithm() ); BOOST_CHECK_EQUAL( expected->GetHatchHoleMinArea(), zone->GetHatchHoleMinArea() ); BOOST_CHECK_EQUAL( expected->GetThermalReliefGap(), zone->GetThermalReliefGap() ); BOOST_CHECK_EQUAL( expected->GetThermalReliefSpokeWidth(), zone->GetThermalReliefSpokeWidth() ); BOOST_CHECK_EQUAL( expected->GetCornerSmoothingType(), zone->GetCornerSmoothingType() ); BOOST_CHECK_EQUAL( expected->GetCornerRadius(), zone->GetCornerRadius() ); CHECK_ENUM_CLASS_EQUAL( expected->GetIslandRemovalMode(), zone->GetIslandRemovalMode() ); BOOST_CHECK_EQUAL( expected->GetMinIslandArea(), zone->GetMinIslandArea() ); BOOST_CHECK_EQUAL( expected->GetIsRuleArea(), zone->GetIsRuleArea() ); BOOST_CHECK_EQUAL( expected->GetDoNotAllowCopperPour(), zone->GetDoNotAllowCopperPour() ); BOOST_CHECK_EQUAL( expected->GetDoNotAllowVias(), zone->GetDoNotAllowVias() ); BOOST_CHECK_EQUAL( expected->GetDoNotAllowTracks(), zone->GetDoNotAllowTracks() ); BOOST_CHECK_EQUAL( expected->GetDoNotAllowPads(), zone->GetDoNotAllowPads() ); BOOST_CHECK_EQUAL( expected->GetDoNotAllowFootprints(), zone->GetDoNotAllowFootprints() ); BOOST_CHECK_EQUAL( expected->GetZoneName(), zone->GetZoneName() ); CHECK_ENUM_CLASS_EQUAL( expected->GetTeardropAreaType(), zone->GetTeardropAreaType() ); BOOST_CHECK_EQUAL( expected->GetZoneName(), zone->GetZoneName() ); CheckShapePolySet( expected->Outline(), zone->Outline() ); // TODO: filled zones } } void CheckShapePolySet( const SHAPE_POLY_SET* expected, const SHAPE_POLY_SET* polyset ) { BOOST_TEST_CONTEXT( "Assert SHAPE_POLY_SET" ) { BOOST_CHECK_EQUAL( expected->OutlineCount(), polyset->OutlineCount() ); BOOST_CHECK_EQUAL( expected->TotalVertices(), polyset->TotalVertices() ); if( expected->OutlineCount() != polyset->OutlineCount() ) return; // don't check the rest if( expected->TotalVertices() != polyset->TotalVertices() ) return; // don't check the rest // TODO: check all outlines and holes (just checking outlines for now) for( int i = 0; i < expected->OutlineCount(); ++i ) { BOOST_TEST_CONTEXT( "Outline " << i ) { BOOST_CHECK_EQUAL( expected->Outline( i ).ArcCount(), polyset->Outline( i ).ArcCount() ); BOOST_CHECK_EQUAL( expected->Outline( i ).PointCount(), polyset->Outline( i ).PointCount() ); if( expected->Outline( i ).PointCount() != polyset->Outline( i ).PointCount() ) return; // don't check the rest for( int j = 0; j < expected->Outline( i ).PointCount(); ++j ) { BOOST_CHECK_EQUAL( expected->Outline( i ).GetPoint( j ), polyset->Outline( i ).GetPoint( j ) ); } } } } } } // namespace KI_TEST