From dc71a73178f1cfd5c43f542e259e9fa2e37e0441 Mon Sep 17 00:00:00 2001 From: John Beard Date: Tue, 8 Jan 2019 17:10:14 +0000 Subject: [PATCH] QA: Add unit test of SHAPE_ARC Test a few "centre-point-angle" cases and add some generic geom code for testing vectors and boxes are within tolerance (since rounding often creeps in). Much of the arc-checking code will be useful to other construction methods (e.g. point-point-point). There are expected failures for the bbox code when the arc passes though, but does not end on, a quadrant point (0, 90, 180, 270). This is due to a defective implementation of SHAPE_ARC::BBox() that does not take into account the quadrant points. This will be fixed as a follow-up. --- qa/common/CMakeLists.txt | 1 + qa/common/geometry/test_shape_arc.cpp | 289 ++++++++++++++++++ .../include/unit_test_utils/geometry.h | 49 +++ 3 files changed, 339 insertions(+) create mode 100644 qa/common/geometry/test_shape_arc.cpp create mode 100644 qa/unit_test_utils/include/unit_test_utils/geometry.h diff --git a/qa/common/CMakeLists.txt b/qa/common/CMakeLists.txt index 8e409f4116..ab5bdcd268 100644 --- a/qa/common/CMakeLists.txt +++ b/qa/common/CMakeLists.txt @@ -46,6 +46,7 @@ set( common_srcs libeval/test_numeric_evaluator.cpp geometry/test_fillet.cpp + geometry/test_shape_arc.cpp view/test_zoom_controller.cpp ) diff --git a/qa/common/geometry/test_shape_arc.cpp b/qa/common/geometry/test_shape_arc.cpp new file mode 100644 index 0000000000..26b82234a8 --- /dev/null +++ b/qa/common/geometry/test_shape_arc.cpp @@ -0,0 +1,289 @@ +/* + * 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 + + +BOOST_AUTO_TEST_SUITE( ShapeArc ) + +/** + * All properties of an arc (depending on how it's constructed, some of these + * might be the same as the constructor params) + */ +struct ARC_PROPERTIES +{ + VECTOR2I m_center_point; + VECTOR2I m_start_point; + VECTOR2I m_end_point; + double m_center_angle; + double m_start_angle; + double m_end_angle; + int m_radius; + BOX2I m_bbox; +}; + +/** + * Check a #SHAPE_ARC against a given set of geometric properties + */ +static void CheckArcGeom( const SHAPE_ARC& aArc, const ARC_PROPERTIES& aProps ) +{ + // Angular error - not this can get quite large for very small arcs, + // as the integral position rounding has a relatively greater effect + const double angle_tol_deg = 0.01; + + // Position error - rounding to nearest integer + const int pos_tol = 1; + + BOOST_CHECK_PREDICATE( KI_TEST::IsVecWithinTol, + ( aProps.m_start_point )( aProps.m_start_point )( pos_tol ) ); + BOOST_CHECK_PREDICATE( + KI_TEST::IsVecWithinTol, ( aArc.GetP1() )( aProps.m_end_point )( pos_tol ) ); + BOOST_CHECK_PREDICATE( KI_TEST::IsVecWithinTol, + ( aArc.GetCenter() )( aProps.m_center_point )( pos_tol ) ); + BOOST_CHECK_PREDICATE( KI_TEST::IsWithin, + ( aArc.GetCentralAngle() )( aProps.m_center_angle )( angle_tol_deg ) ); + BOOST_CHECK_PREDICATE( KI_TEST::IsWithin, + ( aArc.GetStartAngle() )( aProps.m_start_angle )( angle_tol_deg ) ); + BOOST_CHECK_PREDICATE( KI_TEST::IsWithin, + ( aArc.GetEndAngle() )( aProps.m_end_angle )( angle_tol_deg ) ); + BOOST_CHECK_PREDICATE( + KI_TEST::IsWithin, ( aArc.GetRadius() )( aProps.m_radius )( pos_tol ) ); + + /// Check the chord agrees + const auto chord = aArc.GetChord(); + + BOOST_CHECK_PREDICATE( + KI_TEST::IsVecWithinTol, ( chord.A )( aProps.m_start_point )( pos_tol ) ); + BOOST_CHECK_PREDICATE( + KI_TEST::IsVecWithinTol, ( chord.B )( aProps.m_end_point )( pos_tol ) ); + + /// All arcs are solid + BOOST_CHECK_EQUAL( aArc.IsSolid(), true ); + + BOOST_CHECK_PREDICATE( + KI_TEST::IsBoxWithinTol, ( aArc.BBox() )( aProps.m_bbox )( pos_tol ) ); + + /// Collisions will be checked elsewhere. +} + +/** + * Check an arcs geometry and other class functions + */ +static void CheckArc( const SHAPE_ARC& aArc, const ARC_PROPERTIES& aProps ) +{ + // Check the original arc + CheckArcGeom( aArc, aProps ); + + // Test the Clone function (also tests copy-ctor) + std::unique_ptr new_shape{ aArc.Clone() }; + + BOOST_CHECK_EQUAL( new_shape->Type(), SH_ARC ); + + SHAPE_ARC* new_arc = dynamic_cast( new_shape.get() ); + + BOOST_REQUIRE( new_arc != nullptr ); + + /// Should have identical geom props + CheckArcGeom( *new_arc, aProps ); +} + +/** + * Check correct handling of filter strings (as used by WX) + */ +BOOST_AUTO_TEST_CASE( NullCtor ) +{ + auto arc = SHAPE_ARC(); + + BOOST_CHECK_EQUAL( arc.GetWidth(), 0 ); + + static ARC_PROPERTIES null_props{ + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + 0, + 0, + 0, + 0, + }; + + CheckArc( arc, null_props ); +} + +/** + * Info to set up an arc by centre, start point and angle + * + * In future there may be more ways to set this up, so keep it separate + */ +struct ARC_CENTRE_PT_ANGLE +{ + VECTOR2I m_center_point; + VECTOR2I m_start_point; + double m_center_angle; +}; + +struct ARC_CPA_CASE +{ + /// The text context name + std::string m_ctx_name; + + /// Geom of the arc + ARC_CENTRE_PT_ANGLE m_geom; + + /// Arc line width + int m_width; + + /// Expected properties + ARC_PROPERTIES m_properties; +}; + +static const std::vector arc_cases = { + { + "C(0,0) 180 + 90 degree", + { + { 0, 0 }, + { -100, 0 }, + 90, + }, + 0, + { + { 0, 0 }, + { -100, 0 }, + { 0, -100 }, + 90, + 180, + 270, + 100, + { { -100, -100 }, { 100, 100 } }, + }, + }, + { + "C(100,200) 0 - 30 degree", + { + { 100, 200 }, + { 300, 200 }, + -30, + }, + 0, + { + { 100, 200 }, + { 300, 200 }, + { 273, 100 }, // 200 * sin(30) = 100, 200* cos(30) = 173 + -30, + 0, + 330, + 200, + { { 100, 100 }, { 200, 100 } }, + }, + }, + { + // This is a "fan shape" which includes the top quadrant point, + // so it exercises the bounding box code (centre and end points + // do not contain the top quadrant) + "C(0,0) 30 + 120 degree", + { + { 0, 0 }, + { 17320, 10000 }, + 120, + }, + 0, + { + { 0, 0 }, + { 17320, 10000 }, + { -17320, 10000 }, // 200 * sin(30) = 100, 200* cos(30) = 173 + 120, + 30, + 150, + 20000, + // bbox defined by: centre, top quadrant point, two endpoints + { { -17320, 0 }, { 17320 * 2, 20000 } }, + }, + }, + { + // An arc that covers three quadrant points (L/R, bottom) + "C(0,0) 150 + 240 degree", + { + { 0, 0 }, + { -17320, 10000 }, + 240, + }, + 0, + { + { 0, 0 }, + { -17320, 10000 }, + { 17320, 10000 }, + 240, + 150, + 30, + 20000, + // bbox defined by: L/R quads, bottom quad and start/end + { { -20000, -20000 }, { 40000, 30000 } }, + }, + }, + { + // Same as above but reverse direction + "C(0,0) 30 - 300 degree", + { + { 0, 0 }, + { 17320, 10000 }, + -240, + }, + 0, + { + { 0, 0 }, + { 17320, 10000 }, + { -17320, 10000 }, + -240, + 30, + 150, + 20000, + // bbox defined by: L/R quads, bottom quad and start/end + { { -20000, -20000 }, { 40000, 30000 } }, + }, + }, +}; + +#ifdef HAVE_EXPECTED_FAILURES +// One of the bbox tests fails + +BOOST_AUTO_TEST_CASE( BasicCPAGeom, *boost::unit_test::expected_failures( 6 ) ) +{ + for( const auto& c : arc_cases ) + { + BOOST_TEST_CONTEXT( c.m_ctx_name ) + { + + const auto this_arc = SHAPE_ARC{ c.m_geom.m_center_point, c.m_geom.m_start_point, + c.m_geom.m_center_angle, c.m_width }; + + CheckArc( this_arc, c.m_properties ); + } + } +} + +#endif // HAVE_EXPECTED_FAILURES + +BOOST_AUTO_TEST_SUITE_END() diff --git a/qa/unit_test_utils/include/unit_test_utils/geometry.h b/qa/unit_test_utils/include/unit_test_utils/geometry.h new file mode 100644 index 0000000000..cc10cd3814 --- /dev/null +++ b/qa/unit_test_utils/include/unit_test_utils/geometry.h @@ -0,0 +1,49 @@ + +#ifndef QA_UNIT_TEST_UTILS_GEOM__H +#define QA_UNIT_TEST_UTILS_GEOM__H + +#include +#include + +#include +#include + +/** + * Printer for BOX2I type + */ +template <> struct BOOST_PRINT::print_log_value +{ + void operator()( std::ostream& os, const BOX2I& aBox ) + { + os << "BOX[ " << aBox.GetOrigin() << " + " << aBox.GetSize() << " ]"; + } +}; + + +namespace KI_TEST +{ + +/** + * Check that both x and y of a vector are within expected error + */ +template +bool IsVecWithinTol( const VEC& aVec, const VEC& aExp, typename VEC::coord_type aTol ) +{ + return IsWithin( aVec.x, aExp.x, aTol ) + && IsWithin( aVec.y, aExp.y, aTol ); +} + +/** + * Check that a box is close enough to another box + */ +template +bool IsBoxWithinTol( const BOX& aBox, const BOX& aExp, typename BOX::coord_type aTol ) +{ + using VEC = VECTOR2; + return IsVecWithinTol( aBox.GetPosition(), aExp.GetPosition(), aTol ) + && IsVecWithinTol( aBox.GetSize(), aExp.GetSize(), aTol * 2 ); +} + +} // namespace KI_TEST + +#endif // QA_UNIT_TEST_UTILS_GEOM__H \ No newline at end of file