From 8297ab24e4c785cdb391603374b94ced90f2dddc Mon Sep 17 00:00:00 2001 From: John Beard Date: Fri, 11 Jan 2019 22:03:45 +0000 Subject: [PATCH] DRC: Break out courtyard overlap function Introduce the concept of a DRC_PROVIDER which allows to separate the various DRC functions to their own areas. This allows, amongst other things, a slimmer core DRC class, and allows DRC functions to be separately testable. The courtyard DRCs (overlap, missing and malformed) are the first victims, so instrumentation can be added to this function. Add some unit tests on this DRC function, as well a few re-usable PCB-based utility functions in a library (qa_pcbnew_utils) that could be shared between unit tests and other utilities. --- common/CMakeLists.txt | 1 + pcbnew/CMakeLists.txt | 2 + pcbnew/drc.cpp | 105 +--- pcbnew/drc.h | 4 +- pcbnew/drc/courtyard_overlap.cpp | 168 ++++++ pcbnew/drc/courtyard_overlap.h | 44 ++ pcbnew/drc/drc_provider.cpp | 44 ++ pcbnew/drc/drc_provider.h | 84 +++ qa/CMakeLists.txt | 1 + qa/pcbnew/CMakeLists.txt | 46 +- qa/pcbnew/board_test_utils.cpp | 53 ++ qa/pcbnew/board_test_utils.h | 63 +++ qa/pcbnew/drc/drc_test_utils.cpp | 46 ++ qa/pcbnew/drc/drc_test_utils.h | 63 +++ qa/pcbnew/drc/test_drc_courtyard_invalid.cpp | 341 ++++++++++++ qa/pcbnew/drc/test_drc_courtyard_overlap.cpp | 499 ++++++++++++++++++ qa/pcbnew_utils/CMakeLists.txt | 85 +++ qa/pcbnew_utils/board_construction_utils.cpp | 112 ++++ qa/pcbnew_utils/board_file_utils.cpp | 53 ++ .../pcbnew_utils/board_construction_utils.h | 89 ++++ .../include/pcbnew_utils/board_file_utils.h | 67 +++ qa/unit_test_utils/CMakeLists.txt | 6 +- .../include/unit_test_utils/unit_test_utils.h | 81 +++ 23 files changed, 1924 insertions(+), 133 deletions(-) create mode 100644 pcbnew/drc/courtyard_overlap.cpp create mode 100644 pcbnew/drc/courtyard_overlap.h create mode 100644 pcbnew/drc/drc_provider.cpp create mode 100644 pcbnew/drc/drc_provider.h create mode 100644 qa/pcbnew/board_test_utils.cpp create mode 100644 qa/pcbnew/board_test_utils.h create mode 100644 qa/pcbnew/drc/drc_test_utils.cpp create mode 100644 qa/pcbnew/drc/drc_test_utils.h create mode 100644 qa/pcbnew/drc/test_drc_courtyard_invalid.cpp create mode 100644 qa/pcbnew/drc/test_drc_courtyard_overlap.cpp create mode 100644 qa/pcbnew_utils/CMakeLists.txt create mode 100644 qa/pcbnew_utils/board_construction_utils.cpp create mode 100644 qa/pcbnew_utils/board_file_utils.cpp create mode 100644 qa/pcbnew_utils/include/pcbnew_utils/board_construction_utils.h create mode 100644 qa/pcbnew_utils/include/pcbnew_utils/board_file_utils.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 33c98c74bc..5935f720fd 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -415,6 +415,7 @@ set( COMMON_SRCS add_library( common STATIC ${COMMON_SRCS} ) add_dependencies( common version_header ) target_link_libraries( common + bitmaps gal ${Boost_LIBRARIES} ${CURL_LIBRARIES} diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index f20df851ce..a64da3bb5a 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -212,7 +212,9 @@ set( PCBNEW_MICROWAVE_SRCS ) set( PCBNEW_DRC_SRCS + drc/courtyard_overlap.cpp drc/drc_marker_factory.cpp + drc/drc_provider.cpp ) set( PCBNEW_CLASS_SRCS diff --git a/pcbnew/drc.cpp b/pcbnew/drc.cpp index 5c4a400b4b..6808636fc3 100644 --- a/pcbnew/drc.cpp +++ b/pcbnew/drc.cpp @@ -58,6 +58,8 @@ #include #include +#include + void DRC::ShowDRCDialog( wxWindow* aParent ) { bool show_dlg_modal = true; @@ -1416,105 +1418,10 @@ bool DRC::doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, int x_li } -bool DRC::doFootprintOverlappingDrc() +void DRC::doFootprintOverlappingDrc() { - // Detects missing (or malformed) footprint courtyard, - // and for footprint with courtyard, courtyards overlap. - wxString msg; - bool success = true; + DRC_COURTYARD_OVERLAP drc_overlap( + m_markerFactory, [&]( MARKER_PCB* aMarker ) { addMarkerToPcb( aMarker ); } ); - // Update courtyard polygons, and test for missing courtyard definition: - for( MODULE* footprint = m_pcb->m_Modules; footprint; footprint = footprint->Next() ) - { - wxPoint pos = footprint->GetPosition(); - bool is_ok = footprint->BuildPolyCourtyard(); - - if( !is_ok && m_pcb->GetDesignSettings().m_ProhibitOverlappingCourtyards ) - { - addMarkerToPcb( m_markerFactory.NewMarker( - pos, footprint, DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT ) ); - success = false; - } - - if( !m_pcb->GetDesignSettings().m_RequireCourtyards ) - continue; - - if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 && - footprint->GetPolyCourtyardBack().OutlineCount() == 0 && - is_ok ) - { - addMarkerToPcb( m_markerFactory.NewMarker( - pos, footprint, DRCE_MISSING_COURTYARD_IN_FOOTPRINT ) ); - success = false; - } - } - - if( !m_pcb->GetDesignSettings().m_ProhibitOverlappingCourtyards ) - return success; - - // Now test for overlapping on top layer: - SHAPE_POLY_SET courtyard; // temporary storage of the courtyard of current footprint - - for( MODULE* footprint = m_pcb->m_Modules; footprint; footprint = footprint->Next() ) - { - if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 ) - continue; // No courtyard defined - - for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) - { - if( candidate->GetPolyCourtyardFront().OutlineCount() == 0 ) - continue; // No courtyard defined - - courtyard.RemoveAllContours(); - courtyard.Append( footprint->GetPolyCourtyardFront() ); - - // Build the common area between footprint and the candidate: - courtyard.BooleanIntersection( candidate->GetPolyCourtyardFront(), - SHAPE_POLY_SET::PM_FAST ); - - // If no overlap, courtyard is empty (no common area). - // Therefore if a common polygon exists, this is a DRC error - if( courtyard.OutlineCount() ) - { - //Overlap between footprint and candidate - VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); - addMarkerToPcb( m_markerFactory.NewMarker( wxPoint( pos.x, pos.y ), footprint, - candidate, DRCE_OVERLAPPING_FOOTPRINTS ) ); - success = false; - } - } - } - - // Test for overlapping on bottom layer: - for( MODULE* footprint = m_pcb->m_Modules; footprint; footprint = footprint->Next() ) - { - if( footprint->GetPolyCourtyardBack().OutlineCount() == 0 ) - continue; // No courtyard defined - - for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) - { - if( candidate->GetPolyCourtyardBack().OutlineCount() == 0 ) - continue; // No courtyard defined - - courtyard.RemoveAllContours(); - courtyard.Append( footprint->GetPolyCourtyardBack() ); - - // Build the common area between footprint and the candidate: - courtyard.BooleanIntersection( candidate->GetPolyCourtyardBack(), - SHAPE_POLY_SET::PM_FAST ); - - // If no overlap, courtyard is empty (no common area). - // Therefore if a common polygon exists, this is a DRC error - if( courtyard.OutlineCount() ) - { - //Overlap between footprint and candidate - VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); - addMarkerToPcb( m_markerFactory.NewMarker( wxPoint( pos.x, pos.y ), footprint, - candidate, DRCE_OVERLAPPING_FOOTPRINTS ) ); - success = false; - } - } - } - - return success; + drc_overlap.RunDRC( *m_pcb ); } diff --git a/pcbnew/drc.h b/pcbnew/drc.h index 012d135e5c..83c4a810e4 100644 --- a/pcbnew/drc.h +++ b/pcbnew/drc.h @@ -342,10 +342,8 @@ private: /** * Test for footprint courtyard overlaps. - * - * @return bool - false if DRC error or true if OK */ - bool doFootprintOverlappingDrc(); + void doFootprintOverlappingDrc(); //--------------------------------------------------- diff --git a/pcbnew/drc/courtyard_overlap.cpp b/pcbnew/drc/courtyard_overlap.cpp new file mode 100644 index 0000000000..4b383cb171 --- /dev/null +++ b/pcbnew/drc/courtyard_overlap.cpp @@ -0,0 +1,168 @@ +/** + * @file drc_marker_functions.cpp + */ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2010 Dick Hollenbeck, dick@softplc.com + * Copyright (C) 2004-2017 Jean-Pierre Charras, jp.charras at wanadoo.fr + * Copyright (C) 2018 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 + + +/** + * Flag to enable courtyard DRC debug tracing. + * + * Use "KICAD_DRC_COURTYARD" to enable. + * + * @ingroup trace_env_vars + */ +static const wxChar* DRC_COURTYARD_TRACE = wxT( "KICAD_DRC_COURTYARD" ); + + +DRC_COURTYARD_OVERLAP::DRC_COURTYARD_OVERLAP( + const DRC_MARKER_FACTORY& aMarkerFactory, MARKER_HANDLER aMarkerHandler ) + : DRC_PROVIDER( aMarkerFactory, aMarkerHandler ) +{ +} + + +bool DRC_COURTYARD_OVERLAP::RunDRC( BOARD& aBoard ) const +{ + wxLogTrace( DRC_COURTYARD_TRACE, "Running DRC: Courtyard" ); + + // Detects missing (or malformed) footprint courtyard, + // and for footprint with courtyard, courtyards overlap. + wxString msg; + bool success = true; + + const DRC_MARKER_FACTORY& marker_factory = GetMarkerFactory(); + + // Update courtyard polygons, and test for missing courtyard definition: + for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) + { + wxPoint pos = footprint->GetPosition(); + bool is_ok = footprint->BuildPolyCourtyard(); + + if( !is_ok && aBoard.GetDesignSettings().m_ProhibitOverlappingCourtyards ) + { + auto marker = std::unique_ptr( marker_factory.NewMarker( + pos, footprint, DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT ) ); + HandleMarker( std::move( marker ) ); + success = false; + } + + if( !aBoard.GetDesignSettings().m_RequireCourtyards ) + continue; + + if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 + && footprint->GetPolyCourtyardBack().OutlineCount() == 0 && is_ok ) + { + auto marker = std::unique_ptr( marker_factory.NewMarker( + pos, footprint, DRCE_MISSING_COURTYARD_IN_FOOTPRINT ) ); + HandleMarker( std::move( marker ) ); + success = false; + } + } + + if( !aBoard.GetDesignSettings().m_ProhibitOverlappingCourtyards ) + return success; + + wxLogTrace( DRC_COURTYARD_TRACE, "Checking for courtyard overlap" ); + + // Now test for overlapping on top layer: + SHAPE_POLY_SET courtyard; // temporary storage of the courtyard of current footprint + + for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) + { + if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 ) + continue; // No courtyard defined + + for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) + { + if( candidate->GetPolyCourtyardFront().OutlineCount() == 0 ) + continue; // No courtyard defined + + courtyard.RemoveAllContours(); + courtyard.Append( footprint->GetPolyCourtyardFront() ); + + // Build the common area between footprint and the candidate: + courtyard.BooleanIntersection( + candidate->GetPolyCourtyardFront(), SHAPE_POLY_SET::PM_FAST ); + + // If no overlap, courtyard is empty (no common area). + // Therefore if a common polygon exists, this is a DRC error + if( courtyard.OutlineCount() ) + { + //Overlap between footprint and candidate + VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); + auto marker = std::unique_ptr( + marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate, + DRCE_OVERLAPPING_FOOTPRINTS ) ); + HandleMarker( std::move( marker ) ); + success = false; + } + } + } + + // Test for overlapping on bottom layer: + for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) + { + if( footprint->GetPolyCourtyardBack().OutlineCount() == 0 ) + continue; // No courtyard defined + + for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) + { + if( candidate->GetPolyCourtyardBack().OutlineCount() == 0 ) + continue; // No courtyard defined + + courtyard.RemoveAllContours(); + courtyard.Append( footprint->GetPolyCourtyardBack() ); + + // Build the common area between footprint and the candidate: + courtyard.BooleanIntersection( + candidate->GetPolyCourtyardBack(), SHAPE_POLY_SET::PM_FAST ); + + // If no overlap, courtyard is empty (no common area). + // Therefore if a common polygon exists, this is a DRC error + if( courtyard.OutlineCount() ) + { + //Overlap between footprint and candidate + VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); + auto marker = std::unique_ptr( + marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate, + DRCE_OVERLAPPING_FOOTPRINTS ) ); + HandleMarker( std::move( marker ) ); + success = false; + } + } + } + + return success; +} diff --git a/pcbnew/drc/courtyard_overlap.h b/pcbnew/drc/courtyard_overlap.h new file mode 100644 index 0000000000..3e54618567 --- /dev/null +++ b/pcbnew/drc/courtyard_overlap.h @@ -0,0 +1,44 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see change_log.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 + */ + + +#ifndef DRC_COURTYARD_OVERLAP__H +#define DRC_COURTYARD_OVERLAP__H + +#include + +#include + +/** + * A class that provides the courtyard-based DRC checks. + */ +class DRC_COURTYARD_OVERLAP : public DRC_PROVIDER +{ +public: + DRC_COURTYARD_OVERLAP( + const DRC_MARKER_FACTORY& aMarkerFactory, MARKER_HANDLER aMarkerHandler ); + + bool RunDRC( BOARD& aBoard ) const override; +}; + +#endif // DRC_COURTYARD_OVERLAP__H \ No newline at end of file diff --git a/pcbnew/drc/drc_provider.cpp b/pcbnew/drc/drc_provider.cpp new file mode 100644 index 0000000000..6b7d0c9d62 --- /dev/null +++ b/pcbnew/drc/drc_provider.cpp @@ -0,0 +1,44 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 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 + + +DRC_PROVIDER::DRC_PROVIDER( const DRC_MARKER_FACTORY& aMarkerMaker, MARKER_HANDLER aMarkerHandler ) + : m_marker_factory( aMarkerMaker ), m_marker_handler( aMarkerHandler ) +{ +} + + +const DRC_MARKER_FACTORY& DRC_PROVIDER::GetMarkerFactory() const +{ + return m_marker_factory; +} + + +void DRC_PROVIDER::HandleMarker( std::unique_ptr aMarker ) const +{ + // The marker hander currently takes a raw pointer, + // but it also assumes ownership + m_marker_handler( aMarker.release() ); +} diff --git a/pcbnew/drc/drc_provider.h b/pcbnew/drc/drc_provider.h new file mode 100644 index 0000000000..65080d0445 --- /dev/null +++ b/pcbnew/drc/drc_provider.h @@ -0,0 +1,84 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see change_log.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 + */ + + +#ifndef DRC_PROVIDER__H +#define DRC_PROVIDER__H + +#include +#include + +#include + +#include + + +/** + * Class that represents a DRC "provider" which runs some + * DRC functions over a #BOARD and spits out #PCB_MARKERs as needed. + */ +class DRC_PROVIDER +{ +public: + /** + * A callable that can handle a single generated PCB_MARKER + */ + using MARKER_HANDLER = std::function; + + // This class can be handled by base pointer + virtual ~DRC_PROVIDER() + { + } + + /** + * Runs this provider against the given PCB with + * configured options (if any). + * + * Note: Board is non-const, as some DRC functions modify the board + * (e.g. zone fill or polygon coalescing) + */ + virtual bool RunDRC( BOARD& aBoard ) const = 0; + +protected: + DRC_PROVIDER( const DRC_MARKER_FACTORY& aMarkerMaker, MARKER_HANDLER aMarkerHandler ); + + /** + * Access to the stored reference to a marker constructor + */ + const DRC_MARKER_FACTORY& GetMarkerFactory() const; + + /** + * Pass a given marker to the marker handler + */ + void HandleMarker( std::unique_ptr aMarker ) const; + +private: + + /// A marker generator to make markers in the right context + const DRC_MARKER_FACTORY& m_marker_factory; + + /// The handler for any generated markers + MARKER_HANDLER m_marker_handler; +}; + +#endif // DRC_PROVIDER__H \ No newline at end of file diff --git a/qa/CMakeLists.txt b/qa/CMakeLists.txt index 1c774d8385..1f22f174ab 100644 --- a/qa/CMakeLists.txt +++ b/qa/CMakeLists.txt @@ -14,6 +14,7 @@ endif() # Shared QA helper libraries add_subdirectory( qa_utils ) +add_subdirectory( pcbnew_utils ) add_subdirectory( unit_test_utils ) # Unit tests diff --git a/qa/pcbnew/CMakeLists.txt b/qa/pcbnew/CMakeLists.txt index abaaa9f1f9..e8cca0356c 100644 --- a/qa/pcbnew/CMakeLists.txt +++ b/qa/pcbnew/CMakeLists.txt @@ -19,14 +19,14 @@ # or you may write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +if( BUILD_GITHUB_PLUGIN ) + set( GITHUB_PLUGIN_LIBRARIES github_plugin ) +endif() add_executable( qa_pcbnew # A single top to load the pcnew kiface # ../../common/single_top.cpp - # The main test entry points - test_module.cpp - # stuff from common due to...units? ../../common/eda_text.cpp @@ -34,42 +34,35 @@ add_executable( qa_pcbnew ../../common/colors.cpp ../../common/observable.cpp + # The main test entry points + test_module.cpp + + # testing utility routines + board_test_utils.cpp + drc/drc_test_utils.cpp + + # test compilation units (start test_) test_graphics_import_mgr.cpp test_pad_naming.cpp + drc/test_drc_courtyard_invalid.cpp + drc/test_drc_courtyard_overlap.cpp + # Older CMakes cannot link OBJECT libraries # https://cmake.org/pipermail/cmake/2013-November/056263.html $ ) -if( BUILD_GITHUB_PLUGIN ) - set( GITHUB_PLUGIN_LIBRARIES github_plugin ) -endif() - -set_source_files_properties( ../common/single_top.cpp pcbnew.cpp PROPERTIES - COMPILE_DEFINITIONS "TOP_FRAME=FRAME_PCB;PGM_DATA_FILE_EXT=\"kicad_pcb\";BUILD_KIWAY_DLL" -) - -include_directories( BEFORE ${INC_BEFORE} ) -include_directories( - ${CMAKE_SOURCE_DIR} - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/polygon - ${CMAKE_SOURCE_DIR}/pcbnew - ${CMAKE_SOURCE_DIR}/common - ${CMAKE_SOURCE_DIR}/pcbnew/router - ${CMAKE_SOURCE_DIR}/pcbnew/tools - ${CMAKE_SOURCE_DIR}/pcbnew/dialogs - ${INC_AFTER} -) - target_link_libraries( qa_pcbnew + qa_pcbnew_utils 3d-viewer connectivity pcbcommon pnsrouter pcad2kicadpcb + bitmaps common + pcbcommon legacy_wx polygon bitmaps @@ -86,11 +79,6 @@ target_link_libraries( qa_pcbnew ${PCBNEW_EXTRA_LIBS} # -lrt must follow Boost ) -# we need to pretend to be something to appease the units code -target_compile_definitions( qa_pcbnew - PRIVATE PCBNEW -) - add_test( NAME pcbnew COMMAND qa_pcbnew ) \ No newline at end of file diff --git a/qa/pcbnew/board_test_utils.cpp b/qa/pcbnew/board_test_utils.cpp new file mode 100644 index 0000000000..0aca168e2c --- /dev/null +++ b/qa/pcbnew/board_test_utils.cpp @@ -0,0 +1,53 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 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 "board_test_utils.h" + +#include + +// For the temp directory logic: can be std::filesystem in C++17 +#include +#include + + +namespace KI_TEST +{ + +BOARD_DUMPER::BOARD_DUMPER() : m_dump_boards( std::getenv( "KICAD_TEST_DUMP_BOARD_FILES" ) ) +{ +} + + +void BOARD_DUMPER::DumpBoardToFile( BOARD& aBoard, const std::string& aName ) const +{ + if( !m_dump_boards ) + return; + + auto path = boost::filesystem::temp_directory_path() / aName; + path += ".kicad_pcb"; + + BOOST_TEST_MESSAGE( "Dumping board file: " << path.string() ); + ::KI_TEST::DumpBoardToFile( aBoard, path.string() ); +} + +} // namespace KI_TEST \ No newline at end of file diff --git a/qa/pcbnew/board_test_utils.h b/qa/pcbnew/board_test_utils.h new file mode 100644 index 0000000000..1cce18371a --- /dev/null +++ b/qa/pcbnew/board_test_utils.h @@ -0,0 +1,63 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 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 + */ + +/** + * @file board_test_utils.h + * General utilities for PCB tests + */ + +#ifndef QA_PCBNEW_BOARD_TEST_UTILS__H +#define QA_PCBNEW_BOARD_TEST_UTILS__H + +#include + +class BOARD; +class BOARD_ITEM; + + +namespace KI_TEST +{ +/** + * A helper that contains logic to assist in dumping boards to + * disk depending on some environment variables. + * + * This is useful when setting up or verifying unit tests that work on BOARD + * objects. + * + * To dump files set the KICAD_TEST_DUMP_BOARD_FILES environment variable. + * Files will be written to the system temp directory (/tmp on Linux, or as set + * by $TMP and friends). + */ +class BOARD_DUMPER +{ +public: + BOARD_DUMPER(); + + void DumpBoardToFile( BOARD& aBoard, const std::string& aName ) const; + + const bool m_dump_boards; +}; + +} // namespace KI_TEST + +#endif // QA_PCBNEW_BOARD_TEST_UTILS__H \ No newline at end of file diff --git a/qa/pcbnew/drc/drc_test_utils.cpp b/qa/pcbnew/drc/drc_test_utils.cpp new file mode 100644 index 0000000000..b5455f4297 --- /dev/null +++ b/qa/pcbnew/drc/drc_test_utils.cpp @@ -0,0 +1,46 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 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 "drc_test_utils.h" + + +std::ostream& operator<<( std::ostream& os, const MARKER_PCB& aMarker ) +{ + const auto& reporter = aMarker.GetReporter(); + os << "MARKER_PCB[\n"; + os << " type=" << reporter.GetErrorCode() << " (" << reporter.GetErrorText() << ")" + << "\n"; + os << "]"; + return os; +} + + +namespace KI_TEST +{ + +bool IsDrcMarkerOfType( const MARKER_PCB& aMarker, int aErrorCode ) +{ + return aMarker.GetReporter().GetErrorCode() == aErrorCode; +} + +} // namespace KI_TEST \ No newline at end of file diff --git a/qa/pcbnew/drc/drc_test_utils.h b/qa/pcbnew/drc/drc_test_utils.h new file mode 100644 index 0000000000..8e1c3428ae --- /dev/null +++ b/qa/pcbnew/drc/drc_test_utils.h @@ -0,0 +1,63 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 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 + */ + +/** + * @file drc_test_utils.h + * General utilities for DRC-related PCB tests + */ + +#ifndef QA_PCBNEW_DRC_TEST_UTILS__H +#define QA_PCBNEW_DRC_TEST_UTILS__H + +#include + +#include + +/** + * Define a stream function for logging #MARKER_PCB test assertions. + * + * This has to be in the same namespace as #MARKER_PCB + * + * Note: this assumes there is not a operator<< for this type in the main + * Pcbnew library. If one is introduced there, this one should be removed. + * + * TODO: convert to boost_test_print_type when Boost minver > 1.64. This + * will keep testing logging and application-level operator<< implementations + * separate, as they should be. + */ +std::ostream& operator<<( std::ostream& os, const MARKER_PCB& aMarker ); + + +namespace KI_TEST +{ +/** + * Predicate for testing the type of a DRC marker + * @param aMarker the marker to test + * @param aErrorCode the expected DRC violation code + * @return true if the marker has this code + */ +bool IsDrcMarkerOfType( const MARKER_PCB& aMarker, int aErrorCode ); + +} // namespace KI_TEST + +#endif // QA_PCBNEW_DRC_TEST_UTILS__H \ No newline at end of file diff --git a/qa/pcbnew/drc/test_drc_courtyard_invalid.cpp b/qa/pcbnew/drc/test_drc_courtyard_invalid.cpp new file mode 100644 index 0000000000..4f9be1fc6c --- /dev/null +++ b/qa/pcbnew/drc/test_drc_courtyard_invalid.cpp @@ -0,0 +1,341 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see CHANGELOG.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 "../board_test_utils.h" +#include "drc_test_utils.h" + + +struct COURTYARD_TEST_FIXTURE +{ + const KI_TEST::BOARD_DUMPER m_dumper; +}; + + +BOOST_FIXTURE_TEST_SUITE( DrcCourtyardInvalid, COURTYARD_TEST_FIXTURE ) + + +/* + * A simple mock module with a set of courtyard rectangles and some other + * information + */ +struct COURTYARD_INVALID_TEST_MODULE +{ + /// Module Ref-Des (for identifying DRC errors) + std::string m_refdes; + /// List of segments that will be placed on the courtyard + std::vector m_segs; + /// Module position + VECTOR2I m_pos; +}; + + +struct COURTYARD_INVALID_INFO +{ + std::string m_refdes; + + int m_drc_error_code; +}; + + +std::ostream& operator<<( std::ostream& os, const COURTYARD_INVALID_INFO& aInvalid ) +{ + os << "COURTYARD_INVALID_INFO[ " << aInvalid.m_refdes; + os << ", code: " << aInvalid.m_drc_error_code << "]"; + return os; +} + + +struct COURTYARD_INVALID_CASE +{ + std::string m_case_name; + + std::vector m_mods; + + std::vector m_exp_errors; +}; + +// clang-format off +static const std::vector courtyard_invalid_cases = +{ + { + // Empty board has no modules to be invalid + "empty board", + {}, + {}, + }, + { + "single mod, no courtyard", + { + { + "U1", + {}, // Empty courtyard layer + { 0, 0 }, + }, + }, + { // one error: the module has no courtyard + { + "U1", + DRCE_MISSING_COURTYARD_IN_FOOTPRINT, + }, + }, + }, + { + "single mod, unclosed courtyard", + { + { + "U1", + { // Unclosed polygon + { { 0, 0 }, { 0, Millimeter2iu( 10 ) } }, + { { 0, Millimeter2iu( 10 ) }, { Millimeter2iu( 10 ), Millimeter2iu( 10 ) } }, + }, + { 0, 0 }, + }, + }, + { // one error: the module has malformed courtyard + { + "U1", + DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT, + }, + }, + }, + { + "single mod, disjoint courtyard", + { + { + "U1", + { // Unclosed polygon - two disjoint segments + { { 0, 0 }, { 0, Millimeter2iu( 10 ) } }, + { { Millimeter2iu( 10 ), 0 }, { Millimeter2iu( 10 ), Millimeter2iu( 10 ) } }, + }, + { 0, 0 }, + }, + }, + { // one error: the module has malformed courtyard + { + "U1", + DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT, + }, + }, + }, + { + "two mods, one OK, one malformed", + { + { + "U1", + { // Closed polygon - triangle + { + { 0, 0 }, + { 0, Millimeter2iu( 10 ) }, + }, + { + { 0, Millimeter2iu( 10 ) }, + { Millimeter2iu( 10 ), Millimeter2iu( 10 ) } + }, + { + { Millimeter2iu( 10 ), Millimeter2iu( 10 ) }, + { 0, 0 } + }, + }, + { 0, 0 }, + }, + { + "U2", + { // Un-Closed polygon - one seg + { + { 0, 0 }, + { 0, Millimeter2iu( 10 ) }, + }, + }, + { 0, 0 }, + }, + }, + { // one error: the second module has malformed courtyard + { + "U2", + DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT, + }, + }, + }, +}; +// clang-format on + + +/** + * Construct a #MODULE to use in a courtyard test from a #COURTYARD_TEST_MODULE + * definition. + */ +std::unique_ptr MakeInvalidCourtyardTestModule( + BOARD& aBoard, const COURTYARD_INVALID_TEST_MODULE& aMod ) +{ + auto module = std::make_unique( &aBoard ); + + for( const auto& seg : aMod.m_segs ) + { + const PCB_LAYER_ID layer = F_CrtYd; // aRect.m_front ? F_CrtYd : B_CrtYd; + const int width = Millimeter2iu( 0.1 ); + + KI_TEST::DrawSegment( *module, seg, width, layer ); + } + + module->SetReference( aMod.m_refdes ); + + // As of 2019-01-17, this has to go after adding the courtyards, + // or all the poly sets are empty when DRC'd + module->SetPosition( (wxPoint) aMod.m_pos ); + + return module; +} + + +std::unique_ptr MakeBoard( const std::vector& aMods ) +{ + auto board = std::make_unique(); + + for( const auto& mod : aMods ) + { + auto module = MakeInvalidCourtyardTestModule( *board, mod ); + + board->Add( module.release() ); + } + + return board; +} + + +/** + * Get a #BOARD_DESIGN_SETTINGS object that will cause DRC to + * check for courtyard invalidity + */ +static BOARD_DESIGN_SETTINGS GetOverlapCheckDesignSettings() +{ + BOARD_DESIGN_SETTINGS des_settings; + + // do the overlap tests - that's a different test, but if not set, + // the invalid courtyard checks don't run either + des_settings.m_ProhibitOverlappingCourtyards = true; + + // we will also check for missing courtyards here + des_settings.m_RequireCourtyards = true; + + return des_settings; +} + + +/** + * Check if a #MARKER_PCB is described by a particular #COURTYARD_INVALID_INFO + * object. + */ +static bool InvalidMatchesExpected( + BOARD& aBoard, const MARKER_PCB& aMarker, const COURTYARD_INVALID_INFO& aInvalid ) +{ + const DRC_ITEM& reporter = aMarker.GetReporter(); + + const MODULE* item_a = dynamic_cast( reporter.GetMainItem( &aBoard ) ); + + // This one is more than just a mis-match! + if( reporter.HasSecondItem() ) + { + BOOST_WARN_MESSAGE( false, "Expected no auxiliary item for invalid courtyard DRC." ); + return false; + } + + if( item_a->GetReference() != aInvalid.m_refdes ) + { + return false; + } + + if( reporter.GetErrorCode() != aInvalid.m_drc_error_code ) + { + return false; + } + + return true; +} + + +/** + * Check that the produced markers match the expected. This does NOT + * check ordering, as that is not part of the contract of the DRC function. + * + * @param aMarkers list of markers produced by the DRC + * @param aCollisions list of expected collisions + */ +static void CheckInvalidsMatchExpected( BOARD& aBoard, + const std::vector>& aMarkers, + const std::vector& aExpInvalids ) +{ + KI_TEST::CheckUnorderedMatches( aExpInvalids, aMarkers, + [&]( const COURTYARD_INVALID_INFO& aInvalid, + const std::unique_ptr& aMarker ) { + return InvalidMatchesExpected( aBoard, *aMarker, aInvalid ); + } ); +} + + +void DoCourtyardInvalidTest( + const COURTYARD_INVALID_CASE& aCase, const KI_TEST::BOARD_DUMPER& aDumper ) +{ + DRC_MARKER_FACTORY marker_factory; + + auto board = MakeBoard( aCase.m_mods ); + + // Dump if env var set + aDumper.DumpBoardToFile( *board, aCase.m_case_name ); + + board->SetDesignSettings( GetOverlapCheckDesignSettings() ); + + // list of markers to collect + std::vector> markers; + + DRC_COURTYARD_OVERLAP drc_overlap( marker_factory, [&]( MARKER_PCB* aMarker ) { + markers.push_back( std::unique_ptr( aMarker ) ); + } ); + + drc_overlap.RunDRC( *board ); + + CheckInvalidsMatchExpected( *board, markers, aCase.m_exp_errors ); +} + + +BOOST_AUTO_TEST_CASE( InvalidCases ) +{ + for( const auto& c : courtyard_invalid_cases ) + { + BOOST_TEST_CONTEXT( c.m_case_name ) + { + DoCourtyardInvalidTest( c, m_dumper ); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/qa/pcbnew/drc/test_drc_courtyard_overlap.cpp b/qa/pcbnew/drc/test_drc_courtyard_overlap.cpp new file mode 100644 index 0000000000..6b03d32427 --- /dev/null +++ b/qa/pcbnew/drc/test_drc_courtyard_overlap.cpp @@ -0,0 +1,499 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see CHANGELOG.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 "../board_test_utils.h" +#include "drc_test_utils.h" + +/** + * Simple definition of a rectangle, can be rounded + */ +struct RECT_DEFINITION +{ + VECTOR2I m_centre; + VECTOR2I m_size; + int m_corner_rad; + + // On front or back layer (the exact layer is context-dependent) + bool m_front; +}; + + +/* + * A simple mock module with a set of courtyard rectangles and some other + * information + */ +struct COURTYARD_TEST_MODULE +{ + std::string m_refdes; + std::vector m_rects; + VECTOR2I m_pos; +}; + + +/* + * Struct holding information about a courtyard collision + */ +struct COURTYARD_COLLISION +{ + // The two colliding parts + std::string m_refdes_a; + std::string m_refdes_b; +}; + + +std::ostream& operator<<( std::ostream& os, const COURTYARD_COLLISION& aColl ) +{ + os << "COURTYARD_COLLISION[ " << aColl.m_refdes_a << " -> " << aColl.m_refdes_b << "]"; + return os; +} + + +/** + * A complete courtyard overlap test case: a name, the board modules list + * and the expected collisions. + */ +struct COURTYARD_OVERLAP_TEST_CASE +{ + std::string m_case_name; + + // The modules in the test case + std::vector m_mods; + + // The expected number of collisions + std::vector m_collisions; +}; + + +/** + * Add a rectangular courtyard outline to a module. + */ +void AddRectCourtyard( MODULE& aMod, const RECT_DEFINITION& aRect ) +{ + const PCB_LAYER_ID layer = aRect.m_front ? F_CrtYd : B_CrtYd; + + const int width = Millimeter2iu( 0.1 ); + + KI_TEST::DrawRect( aMod, aRect.m_centre, aRect.m_size, aRect.m_corner_rad, width, layer ); +} + + +/** + * Construct a #MODULE to use in a courtyard test from a #COURTYARD_TEST_MODULE + * definition. + */ +std::unique_ptr MakeCourtyardTestModule( BOARD& aBoard, const COURTYARD_TEST_MODULE& aMod ) +{ + auto module = std::make_unique( &aBoard ); + + for( const auto& rect : aMod.m_rects ) + { + AddRectCourtyard( *module, rect ); + } + + module->SetReference( aMod.m_refdes ); + + // As of 2019-01-17, this has to go after adding the courtyards, + // or all the poly sets are empty when DRC'd + module->SetPosition( (wxPoint) aMod.m_pos ); + + return module; +} + +/** + * Make a board for courtyard testing. + * + * @param aMods the list of module definitions to add to the board + */ +std::unique_ptr MakeBoard( const std::vector& aMods ) +{ + auto board = std::make_unique(); + + for( const auto& mod : aMods ) + { + auto module = MakeCourtyardTestModule( *board, mod ); + + board->Add( module.release() ); + } + + return board; +} + + +struct COURTYARD_TEST_FIXTURE +{ + const KI_TEST::BOARD_DUMPER m_dumper; +}; + + +BOOST_FIXTURE_TEST_SUITE( DrcCourtyardOverlap, COURTYARD_TEST_FIXTURE ) + +// clang-format off +static std::vector courtyard_cases = { + { + "empty board", + {}, // no modules + {}, // no collisions + }, + { + "single empty mod", + { + { + "U1", + {}, // no courtyard + { 0, 0 }, // at origin + }, + }, + {}, // no collisions + }, + { + // A single module can't overlap itself + "single mod, single courtyard", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + }, + {}, // no collisions + }, + { + "two modules, no overlap", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { Millimeter2iu( 3 ), Millimeter2iu( 1 ) }, // One module is far from the other + }, + }, + {}, // no collisions + }, + { + "two modules, touching, no overlap", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { Millimeter2iu( 1 ), Millimeter2iu( 0 ) }, // Just touching + }, + }, + {}, // Touching means not colliding + }, + { + "two modules, overlap", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { Millimeter2iu( 0.5 ), Millimeter2iu( 0 ) }, // Partial overlap + }, + }, + { + { "U1", "U2" }, // These two collide + }, + }, + { + "two modules, overlap, different sides", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + false, + }, + }, + { 0, 0 }, // complete overlap + }, + }, + {}, // but on different sides + }, + { + "two modules, multiple courtyards, overlap", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + { + { Millimeter2iu( 2 ), Millimeter2iu( 0 ) }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, // complete overlap with one of the others + }, + }, + { + { "U1", "U2" }, + }, + }, + { + // The courtyards do not overlap, but their bounding boxes do + "two modules, no overlap, bbox overlap", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + Millimeter2iu( 0.5 ), + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { Millimeter2iu( 0.9 ), Millimeter2iu( 0.9 ) }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + Millimeter2iu( 0.5 ), + true, + }, + }, + { 0, 0 }, + }, + }, + {}, + }, +}; +// clang-format on + + +/** + * Check if a #MARKER_PCB is described by a particular #COURTYARD_COLLISION + * object. + */ +static bool CollisionMatchesExpected( + BOARD& aBoard, const MARKER_PCB& aMarker, const COURTYARD_COLLISION& aCollision ) +{ + const DRC_ITEM& reporter = aMarker.GetReporter(); + + const MODULE* item_a = dynamic_cast( reporter.GetMainItem( &aBoard ) ); + const MODULE* item_b = dynamic_cast( reporter.GetAuxiliaryItem( &aBoard ) ); + + // cant' find the items! + if( !item_a || !item_b ) + return false; + + const bool ref_match_aa_bb = ( item_a->GetReference() == aCollision.m_refdes_a ) + && ( item_b->GetReference() == aCollision.m_refdes_b ); + + const bool ref_match_ab_ba = ( item_a->GetReference() == aCollision.m_refdes_b ) + && ( item_b->GetReference() == aCollision.m_refdes_a ); + + // Doesn't matter which way around it is, but both have to match somehow + return ref_match_aa_bb || ref_match_ab_ba; +} + + +/** + * Check that the produced markers match the expected. This does NOT + * check ordering, as that is not part of the contract of the DRC function. + * + * @param aMarkers list of markers produced by the DRC + * @param aCollisions list of expected collisions + */ +static void CheckCollisionsMatchExpected( BOARD& aBoard, + const std::vector>& aMarkers, + const std::vector& aExpCollisions ) +{ + for( const auto& marker : aMarkers ) + { + BOOST_CHECK_PREDICATE( + KI_TEST::IsDrcMarkerOfType, ( *marker )( DRCE_OVERLAPPING_FOOTPRINTS ) ); + } + + KI_TEST::CheckUnorderedMatches( aExpCollisions, aMarkers, + [&]( const COURTYARD_COLLISION& aColl, const std::unique_ptr& aMarker ) { + return CollisionMatchesExpected( aBoard, *aMarker, aColl ); + } ); +} + + +/** + * Get a #BOARD_DESIGN_SETTINGS object that will cause DRC to + * check for courtyard overlaps + */ +static BOARD_DESIGN_SETTINGS GetOverlapCheckDesignSettings() +{ + BOARD_DESIGN_SETTINGS des_settings; + des_settings.m_ProhibitOverlappingCourtyards = true; + + // we might not always have courtyards - that's a separate test + des_settings.m_RequireCourtyards = false; + + return des_settings; +} + + +/** + * Run a single courtyard overlap testcase + * @param aCase The testcase to run. + */ +static void DoCourtyardOverlapTest( + const COURTYARD_OVERLAP_TEST_CASE& aCase, const KI_TEST::BOARD_DUMPER& aDumper ) +{ + DRC_MARKER_FACTORY marker_factory; + + auto board = MakeBoard( aCase.m_mods ); + + // Dump if env var set + aDumper.DumpBoardToFile( *board, aCase.m_case_name ); + + board->SetDesignSettings( GetOverlapCheckDesignSettings() ); + + // list of markers to collect + std::vector> markers; + + DRC_COURTYARD_OVERLAP drc_overlap( marker_factory, [&]( MARKER_PCB* aMarker ) { + markers.push_back( std::unique_ptr( aMarker ) ); + } ); + + drc_overlap.RunDRC( *board ); + + CheckCollisionsMatchExpected( *board, markers, aCase.m_collisions ); +} + + +BOOST_AUTO_TEST_CASE( OverlapCases ) +{ + for( const auto& c : courtyard_cases ) + { + BOOST_TEST_CONTEXT( c.m_case_name ) + { + DoCourtyardOverlapTest( c, m_dumper ); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/qa/pcbnew_utils/CMakeLists.txt b/qa/pcbnew_utils/CMakeLists.txt new file mode 100644 index 0000000000..f67eb70111 --- /dev/null +++ b/qa/pcbnew_utils/CMakeLists.txt @@ -0,0 +1,85 @@ +# This program source code file is part of KiCad, a free EDA CAD application. +# +# Copyright (C) 2019 KiCad Developers, see CHANGELOG.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 + +# Pcbnew-related auxiliary functions that are useful for QA purposes +# (both unit tests ans utility programs) + +# This is a massive hack in the CMake as I have no idea how to get this to +# link against pcbnew/pcbcommon/etc and still allow a dependency to also link +# THis should really be an add_library() and all the variables instead set using +# target_*() functions. + +set( QA_PCBNEW_UTILS_SRCS + + ${CMAKE_CURRENT_SOURCE_DIR}/board_construction_utils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/board_file_utils.cpp +) + +add_library( qa_pcbnew_utils STATIC ${QA_PCBNEW_UTILS_SRCS} ) + + +target_include_directories( qa_pcbnew_utils PUBLIC BEFORE ${INC_BEFORE} ) + +target_include_directories( qa_pcbnew_utils PUBLIC + include + + # Paths for pcbnew lib usage (should really be in pcbnew/common + # target_include_directories and made PUBLIC) + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/polygon + ${CMAKE_SOURCE_DIR}/pcbnew + ${CMAKE_SOURCE_DIR}/common + ${CMAKE_SOURCE_DIR}/pcbnew/router + ${CMAKE_SOURCE_DIR}/pcbnew/tools + ${CMAKE_SOURCE_DIR}/pcbnew/dialogs + ${INC_AFTER} +) + +# target_link_libraries( qa_pcbnew_utils PUBLIC +# 3d-viewer +# connectivity +# pcbcommon +# pnsrouter +# pcad2kicadpcb +# bitmaps +# common +# pcbcommon +# legacy_wx +# polygon +# bitmaps +# gal +# qa_utils +# lib_dxf +# idf3 +# unit_test_utils +# ${wxWidgets_LIBRARIES} +# ${GITHUB_PLUGIN_LIBRARIES} +# ${GDI_PLUS_LIBRARIES} +# ${PYTHON_LIBRARIES} +# ${Boost_LIBRARIES} # must follow GITHUB +# ${PCBNEW_EXTRA_LIBS} # -lrt must follow Boost +#) + +# # we need to pretend to be something to appease the units code +target_compile_definitions( qa_pcbnew_utils + PUBLIC PCBNEW +) \ No newline at end of file diff --git a/qa/pcbnew_utils/board_construction_utils.cpp b/qa/pcbnew_utils/board_construction_utils.cpp new file mode 100644 index 0000000000..46e1e43c9f --- /dev/null +++ b/qa/pcbnew_utils/board_construction_utils.cpp @@ -0,0 +1,112 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 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 + + +namespace KI_TEST +{ + +void DrawSegment( MODULE& aMod, const SEG& aSeg, int aWidth, PCB_LAYER_ID aLayer ) +{ + auto seg = std::make_unique( &aMod, STROKE_T::S_SEGMENT ); + + seg->SetStart0( (wxPoint) aSeg.A ); + seg->SetEnd0( (wxPoint) aSeg.B ); + + seg->SetWidth( aWidth ); + seg->SetLayer( aLayer ); + + aMod.GraphicalItemsList().PushBack( seg.release() ); +} + + +void DrawPolyline( + MODULE& aMod, const std::vector& aPts, int aWidth, PCB_LAYER_ID aLayer ) +{ + for( unsigned i = 0; i < aPts.size() - 1; ++i ) + { + DrawSegment( aMod, { aPts[i], aPts[i + 1] }, aWidth, aLayer ); + } +} + + +void DrawArc( MODULE& aMod, const VECTOR2I& aCentre, const VECTOR2I& aStart, double aAngle, + int aWidth, PCB_LAYER_ID aLayer ) +{ + auto seg = std::make_unique( &aMod, STROKE_T::S_ARC ); + + seg->SetStart0( (wxPoint) aCentre ); + seg->SetEnd0( (wxPoint) aStart ); + seg->SetAngle( aAngle * 10 ); + + seg->SetWidth( aWidth ); + seg->SetLayer( aLayer ); + + aMod.GraphicalItemsList().PushBack( seg.release() ); +} + + +void DrawRect( MODULE& aMod, const VECTOR2I& aPos, const VECTOR2I& aSize, int aRadius, int aWidth, + PCB_LAYER_ID aLayer ) +{ + const auto x_r = aPos.x + aSize.x / 2; + const auto x_l = aPos.x - aSize.x / 2; + const auto y_t = aPos.y + aSize.y / 2; + const auto y_b = aPos.y - aSize.y / 2; + + const auto xin_r = x_r - aRadius; + const auto xin_l = x_l + aRadius; + const auto yin_t = y_t - aRadius; + const auto yin_b = y_b + aRadius; + + // If non-zero (could be zero if it's a stadium shape) + if( xin_l != xin_r ) + { + DrawSegment( aMod, { { xin_l, y_t }, { xin_r, y_t } }, aWidth, aLayer ); + DrawSegment( aMod, { { xin_l, y_b }, { xin_r, y_b } }, aWidth, aLayer ); + } + + if( yin_b != yin_t ) + { + DrawSegment( aMod, { { x_l, yin_b }, { x_l, yin_t } }, aWidth, aLayer ); + DrawSegment( aMod, { { x_r, yin_b }, { x_r, yin_t } }, aWidth, aLayer ); + } + + if( aRadius > 0 ) + { + DrawArc( aMod, { xin_r, yin_t }, { x_r, yin_t }, 90, aWidth, aLayer ); + DrawArc( aMod, { xin_l, yin_t }, { xin_l, y_t }, 90, aWidth, aLayer ); + DrawArc( aMod, { xin_l, yin_b }, { x_l, yin_b }, 90, aWidth, aLayer ); + DrawArc( aMod, { xin_r, yin_b }, { xin_r, y_b }, 90, aWidth, aLayer ); + } +} + +} // namespace KI_TEST diff --git a/qa/pcbnew_utils/board_file_utils.cpp b/qa/pcbnew_utils/board_file_utils.cpp new file mode 100644 index 0000000000..c278c22edb --- /dev/null +++ b/qa/pcbnew_utils/board_file_utils.cpp @@ -0,0 +1,53 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 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 + +// For PCB parsing +#include +#include +#include + +#include + +namespace KI_TEST +{ + +void DumpBoardToFile( BOARD& board, const std::string& aFilename ) +{ + PCB_IO io; + io.Save( aFilename, &board ); +} + + +std::unique_ptr ReadBoardItemFromFile( const std::string& aFilename ) +{ + FILE_LINE_READER reader( aFilename ); + + PCB_PARSER parser; + parser.SetLineReader( &reader ); + + return std::unique_ptr( parser.Parse() ); +} + +} // namespace KI_TEST \ No newline at end of file diff --git a/qa/pcbnew_utils/include/pcbnew_utils/board_construction_utils.h b/qa/pcbnew_utils/include/pcbnew_utils/board_construction_utils.h new file mode 100644 index 0000000000..fa0229e75f --- /dev/null +++ b/qa/pcbnew_utils/include/pcbnew_utils/board_construction_utils.h @@ -0,0 +1,89 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 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 + */ + +/** + * @file board_construction_utils.h + * Construction utilities for PCB tests + */ + +#ifndef QA_PCBNEW_BOARD_CONSTRUCTION_UTILS__H +#define QA_PCBNEW_BOARD_CONSTRUCTION_UTILS__H + +#include + +#include +#include + +class MODULE; +class SEG; + + +namespace KI_TEST +{ + +/** + * Draw a segment in the given module. + * @param aMod The module to add the segment to + * @param aSeg The segment geometry + * @param aWidth The width of the segment + * @param aLayer The layer to draw on + */ +void DrawSegment( MODULE& aMod, const SEG& aSeg, int aWidth, PCB_LAYER_ID aLayer ); + +/** + * Draw a polyline - a set of linked segments + * @param aMod The module to add the segment to + * @param aPts The polyline points + * @param aWidth The width of the segments + * @param aLayer The layer to draw on + */ +void DrawPolyline( + MODULE& aMod, const std::vector& aPts, int aWidth, PCB_LAYER_ID aLayer ); + +/** + * Draw an arc on a module + * @param aMod The module to add the segment to + * @param aCentre The arc centre + * @param aStart The arc start point + * @param aAngle The arc angle (degrees, NOT deci-degrees) + * @param aWidth The width of the arc segment + * @param aLayer The layer to draw on + */ +void DrawArc( MODULE& aMod, const VECTOR2I& aCentre, const VECTOR2I& aStart, double aAngle, + int aWidth, PCB_LAYER_ID aLayer ); + +/** + * Draw a rectangle on a module + * @param aMod The module to add the rectangle to + * @param aPos Rectangle centre point + * @param aSize Rectangle size (x, y) + * @param aRadius Corner radius (0 for a normal rect) + * @param aWidth Line width + * @param aLayer Layer to draw on + */ +void DrawRect( MODULE& aMod, const VECTOR2I& aPos, const VECTOR2I& aSize, int aRadius, int aWidth, + PCB_LAYER_ID aLayer ); + +} // namespace KI_TEST + +#endif // QA_PCBNEW_BOARD_CONSTRUCTION_UTILS__H \ No newline at end of file diff --git a/qa/pcbnew_utils/include/pcbnew_utils/board_file_utils.h b/qa/pcbnew_utils/include/pcbnew_utils/board_file_utils.h new file mode 100644 index 0000000000..8543487fd0 --- /dev/null +++ b/qa/pcbnew_utils/include/pcbnew_utils/board_file_utils.h @@ -0,0 +1,67 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 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 + */ + + +#ifndef QA_PCBNEW_UTILS_BOARD_FILE_UTILS__H +#define QA_PCBNEW_UTILS_BOARD_FILE_UTILS__H + +#include +#include + +class BOARD; +class BOARD_ITEM; + +/** + * @file board_file_utils.h + * General utilities for PCB file IO for QA programs + */ +namespace KI_TEST +{ +/** + * Utility function to simply write a Board out to a file. + * + * Helps debug tests and utility programs by making it easy to quickly + * write to disk without directly using the PCB_IO API. + * + * Note: The aBoard param is non-const because PCB_IO::Save demands it + * and I am not confident a const_cast will be a true assurance. + * + * @param aBoard the board to write out + * @param aFilename the filename to write to + */ +void DumpBoardToFile( BOARD& aBoard, const std::string& aFilename ); + +/** + * Utility function to read a #BOARD_ITEM (probably a #MODULE or a #BOARD) + * from a file. + * + * Helps when writing tests or utilities that can be fed an external file. + * + * @param aFilename the file to read in + * @returns a new #BOARD_ITEM, which is nullptr if the read or parse failed. + */ +std::unique_ptr ReadBoardItemFromFile( const std::string& aFilename ); + +} // namespace KI_TEST + +#endif // QA_PCBNEW_UTILS_BOARD_FILE_UTILS__H \ No newline at end of file diff --git a/qa/unit_test_utils/CMakeLists.txt b/qa/unit_test_utils/CMakeLists.txt index 3fbfa05b86..8ad23bb172 100644 --- a/qa/unit_test_utils/CMakeLists.txt +++ b/qa/unit_test_utils/CMakeLists.txt @@ -25,9 +25,9 @@ # Code that is useful for QA purposes outside of the unit-testing context # belongs in qa_utils. -find_package(Boost COMPONENTS unit_test_framework REQUIRED) +find_package( Boost COMPONENTS unit_test_framework filesystem REQUIRED ) -set(SRCS +set( SRCS unit_test_utils.cpp ) @@ -35,6 +35,8 @@ add_library( unit_test_utils STATIC ${SRCS} ) target_link_libraries( unit_test_utils PUBLIC ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} ) target_include_directories( unit_test_utils PUBLIC diff --git a/qa/unit_test_utils/include/unit_test_utils/unit_test_utils.h b/qa/unit_test_utils/include/unit_test_utils/unit_test_utils.h index 160d01d55a..e6523ea96c 100644 --- a/qa/unit_test_utils/include/unit_test_utils/unit_test_utils.h +++ b/qa/unit_test_utils/include/unit_test_utils/unit_test_utils.h @@ -28,6 +28,7 @@ #include #include +#include /** * If HAVE_EXPECTED_FAILURES is defined, this means that @@ -77,4 +78,84 @@ #endif +namespace KI_TEST +{ +/** + * Check that a container of "found" objects matches a container of "expected" + * objects. This means that: + * + * * Every "expected" object is "found" + * * Every "found" object is "expected" + * + * This is a very generic function: all you need are two containers of any type + * and a function to check if a given "found" object corresponds to a given + * "expected object". Conditions: + * + * * The expected object type needs operator<< + * * The expected object container does not contain multiple references to the + * same object. + * * Identical values are also can't be present as the predicate can't tell which + * one to match up. + * + * Not needed: + * + * * Equality or ordering operators + * + * This is a slightly more complex way of doing it that, say, sorting both + * lists and checking element-by-element matches. However, it can tell you + * exactly which objects are problematic, as well as a simple go/no-go. + * + *@param aExpected a container of "expected" items, usually from a test case + *@param aMatched a container of "found" items, usually the result of some + * routine under test + *@param aMatchPredicate a predicate that determines if a given "found" object + * matches a given "expected" object. + */ +template using EXP_OBJ = typename EXP_CONT::value_type; +template using FOUND_OBJ = typename FOUND_CONT::value_type; +template +using MATCH_PRED = std::function; +template +void CheckUnorderedMatches( + const EXP_CONT& aExpected, const FOUND_CONT& aFound, MATCH_PRED aMatchPredicate ) +{ + using EXP_OBJ = typename EXP_CONT::value_type; + + // set of object we've already found + std::set matched; + + // fill the set of object that match + for( const auto& found : aFound ) + { + for( const auto& expected : aExpected ) + { + if( aMatchPredicate( expected, found ) ) + { + matched.insert( &expected ); + break; + } + } + } + + // first check every expected object was "found" + for( const EXP_OBJ& exp : aExpected ) + { + BOOST_CHECK_MESSAGE( matched.count( &exp ) > 0, "Expected item was not found. Expected: \n" + << exp ); + } + + // check every "found" object was expected + for( const EXP_OBJ* found : matched ) + { + const bool was_expected = + std::find_if( aExpected.begin(), aExpected.end(), + [found]( const EXP_OBJ& aObj ) { return &aObj == found; } ) + != aExpected.end(); + + BOOST_CHECK_MESSAGE( was_expected, "Found item was not expected. Found: \n" << *found ); + } +} + +} // namespace KI_TEST + #endif // UNIT_TEST_UTILS__H \ No newline at end of file