John Beard d9a243d98c QA: Use data-driven tests in a few places
This means that you can run just a single case from the command line,
for example:

   `qa_pcbnew -t DRCCopperSliver/_0`

The case is also automatically printed as context.

This means that when isolating a specific defect in one case,
you can run only that one case, which is useful under debuggers
and also when using slow tools like valgrind.

Not all possibe tests are doing this, but they're easy to do
when needed (i.e. when you want to run just one case)
2025-01-24 20:13:19 +08:00

689 lines
17 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 CERN
* @author Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
* Copyright The 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 <qa_utils/wx_utils/unit_test_utils.h>
#include <boost/test/data/test_case.hpp>
#include <geometry/seg.h>
namespace
{
/**
* Predicate to check expected collision between two segments
* @param aSegA the first #SEG
* @param aSegB the second #SEG
* @param aClearance the collision clearance
* @param aExp expected collision
* @return does the distance calculated agree?
*/
bool SegCollideCorrect( const SEG& aSegA, const SEG& aSegB, int aClearance, bool aExp )
{
const bool AtoB = aSegA.Collide( aSegB, aClearance );
const bool BtoA = aSegB.Collide( aSegA, aClearance );
const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
if( AtoB != BtoA )
{
std::stringstream ss;
ss << "Segment collision is not the same in both directions: expected " << aExp << ", got "
<< AtoB << " & " << BtoA;
BOOST_TEST_INFO( ss.str() );
}
else if( !ok )
{
std::stringstream ss;
ss << "Collision incorrect: expected " << aExp << ", got " << AtoB;
BOOST_TEST_INFO( ss.str() );
}
return ok;
}
/**
* Predicate to check expected distance between two segments
* @param aSegA the first #SEG
* @param aSegB the second #SEG
* @param aExp expected distance
* @return does the distance calculated agree?
*/
bool SegDistanceCorrect( const SEG& aSegA, const SEG& aSegB, int aExp )
{
const int AtoB = aSegA.Distance( aSegB );
const int BtoA = aSegB.Distance( aSegA );
bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
if( AtoB != BtoA )
{
std::stringstream ss;
ss << "Segment distance is not the same in both directions: expected " << aExp << ", got "
<< AtoB << " & " << BtoA;
BOOST_TEST_INFO( ss.str() );
}
else if( !ok )
{
std::stringstream ss;
ss << "Distance incorrect: expected " << aExp << ", got " << AtoB;
BOOST_TEST_INFO( ss.str() );
}
// Sanity check: the collision should be consistent with the distance
ok = ok && SegCollideCorrect( aSegA, aSegB, 0, aExp == 0 );
return ok;
}
/**
* Predicate to check expected distance between a segment and a point
* @param aSegA the segment
* @param aVec the vector (point)
* @param aExp expected distance
* @return does the distance calculated agree?
*/
bool SegVecDistanceCorrect( const SEG& aSeg, const VECTOR2I& aVec, int aExp )
{
const SEG::ecoord squaredDistance = aSeg.SquaredDistance( aVec );
BOOST_REQUIRE( squaredDistance >= 0 );
const int dist = aSeg.Distance( aVec );
bool ok = ( dist == aExp );
if( !ok )
{
std::stringstream ss;
ss << "Distance incorrect: expected " << aExp << ", got " << dist;
BOOST_TEST_INFO( ss.str() );
}
return ok;
}
/**
* Predicate to check expected collision between two segments
* @param aSegA the first #SEG
* @param sSegB the second #SEG
* @param aExp expected collinearity
* @return does the collinearity calculated agree?
*/
bool SegCollinearCorrect( const SEG& aSegA, const SEG& aSegB, bool aExp )
{
const bool AtoB = aSegA.Collinear( aSegB );
const bool BtoA = aSegB.Collinear( aSegA );
const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
if( AtoB != BtoA )
{
std::stringstream ss;
ss << "Segment collinearity is not the same in both directions: expected " << aExp
<< ", got " << AtoB << " & " << BtoA;
BOOST_TEST_INFO( ss.str() );
}
else if( !ok )
{
std::stringstream ss;
ss << "Collinearity incorrect: expected " << aExp << ", got " << AtoB;
BOOST_TEST_INFO( ss.str() );
}
return ok;
}
/**
* Predicate to check expected parallelism between two segments
* @param aSegA the first #SEG
* @param sSegB the second #SEG
* @param aExp expected parallelism: true = segments are parallel
* false = segments are not parallel
* @return does the parallelism calculated agree?
*/
bool SegParallelCorrect( const SEG& aSegA, const SEG& aSegB, bool aExp )
{
const bool AtoB = aSegA.ApproxParallel( aSegB );
const bool BtoA = aSegB.ApproxParallel( aSegA );
const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
if( AtoB != BtoA )
{
std::stringstream ss;
ss << "Segment parallelism is not the same in both directions: expected " << aExp
<< ", got AtoB: " << AtoB << " BtoA:" << BtoA;
BOOST_TEST_INFO( ss.str() );
}
else if( !ok )
{
std::stringstream ss;
ss << "Parallelism incorrect: expected " << aExp << ", got " << AtoB;
BOOST_TEST_INFO( ss.str() );
}
return ok;
}
/**
* Predicate to check expected perpendicularity between two segments
* @param aSegA the first #SEG
* @param sSegB the second #SEG
* @param aExp expected perpendicularity: true = segments are perpendicular
* false = segments are not perpendicular
* @return does the perpendicularity calculated agree?
*/
bool SegPerpendicularCorrect( const SEG& aSegA, const SEG& aSegB, bool aExp )
{
const bool AtoB = aSegA.ApproxPerpendicular( aSegB );
const bool BtoA = aSegB.ApproxPerpendicular( aSegA );
const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
if( AtoB != BtoA )
{
std::stringstream ss;
ss << "Segment perpendicularity is not the same in both directions: expected " << aExp
<< ", got AtoB: " << AtoB << " BtoA:" << BtoA;
BOOST_TEST_INFO( ss.str() );
}
else if( !ok )
{
std::stringstream ss;
ss << "Perpendicularity incorrect: expected " << aExp << ", got " << AtoB;
BOOST_TEST_INFO( ss.str() );
}
return ok;
}
} // namespace
BOOST_AUTO_TEST_SUITE( Segment )
/**
* Checks whether the construction of a segment referencing external points works
* and that the endpoints can be modified as normal points.
*/
BOOST_AUTO_TEST_CASE( EndpointCtorMod )
{
const VECTOR2I pointA{ 10, 20 };
const VECTOR2I pointB{ 100, 200 };
// Build a segment referencing the previous points
SEG segment( pointA, pointB );
BOOST_CHECK_EQUAL( pointA, VECTOR2I( 10, 20 ) );
BOOST_CHECK_EQUAL( pointB, VECTOR2I( 100, 200 ) );
// Modify the ends of the segments
segment.A += VECTOR2I( 10, 10 );
segment.B += VECTOR2I( 100, 100 );
// Check that the ends in segment are modified
BOOST_CHECK_EQUAL( segment.A, VECTOR2I( 20, 30 ) );
BOOST_CHECK_EQUAL( segment.B, VECTOR2I( 200, 300 ) );
}
struct SEG_SEG_DISTANCE_CASE : public KI_TEST::NAMED_CASE
{
SEG m_seg_a;
SEG m_seg_b;
int m_exp_dist;
};
// clang-format off
static const std::vector<SEG_SEG_DISTANCE_CASE> seg_seg_dist_cases = {
{
"Parallel, 10 apart",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 10 }, { 10, 10 } },
10,
},
{
"Non-parallel, 10 apart",
{ { 0, -5 }, { 10, 0 } },
{ { 0, 10 }, { 10, 10 } },
10,
},
{
"Co-incident",
{ { 0, 0 }, { 30, 0 } },
{ { 10, 0 }, { 20, 0 } },
0,
},
{
"Crossing",
{ { 0, -10 }, { 0, 10 } },
{ { -20, 0 }, { 20, 0 } },
0,
},
{
"T-junction",
{ { 0, -10 }, { 0, 10 } },
{ { -20, 0 }, { 0, 0 } },
0,
},
{
"T-junction (no touch)",
{ { 0, -10 }, { 0, 10 } },
{ { -20, 0 }, { -2, 0 } },
2,
},
};
// clang-format on
BOOST_DATA_TEST_CASE( SegSegDistance, boost::unit_test::data::make( seg_seg_dist_cases ), c )
{
BOOST_CHECK_PREDICATE( SegDistanceCorrect, ( c.m_seg_a )( c.m_seg_b )( c.m_exp_dist ) );
}
struct SEG_VECTOR_DISTANCE_CASE : public KI_TEST::NAMED_CASE
{
SEG m_seg;
VECTOR2I m_vec;
int m_exp_dist;
};
// clang-format off
static const std::vector<SEG_VECTOR_DISTANCE_CASE> seg_vec_dist_cases = {
{
"On endpoint",
{ { 0, 0 }, { 10, 0 } },
{ 0, 0 },
0,
},
{
"On segment",
{ { 0, 0 }, { 10, 0 } },
{ 3, 0 },
0,
},
{
"At side",
{ { 0, 0 }, { 10, 0 } },
{ 3, 2 },
2,
},
{
"At end (collinear)",
{ { 0, 0 }, { 10, 0 } },
{ 12, 0 },
2,
},
{
"At end (not collinear)",
{ { 0, 0 }, { 1000, 0 } },
{ 1000 + 200, 200 },
282, // sqrt(200^2 + 200^2) = 282.8, rounded to nearest
},
{
"Issue 18473 (inside hit with rounding error)",
{ { 187360000, 42510000 }, { 105796472, 42510000 } },
{ 106645000, 42510000 },
0,
},
{
"Straight line x distance",
{ { 187360000, 42510000 }, { 105796472, 42510000 } },
{ 197360000, 42510000 },
10000000,
},
{
"Straight line -x distance",
{ { 187360000, 42510000 }, { 105796472, 42510000 } },
{ 104796472, 42510000 },
1000000,
},
};
// clang-format on
BOOST_DATA_TEST_CASE( SegVecDistance, boost::unit_test::data::make( seg_vec_dist_cases ), c )
{
BOOST_CHECK_PREDICATE( SegVecDistanceCorrect, ( c.m_seg )( c.m_vec )( c.m_exp_dist ) );
}
/**
* Test cases for collisions (with clearance, for no clearance,
* it's just a SEG_SEG_DISTANCE_CASE of 0)
*/
struct SEG_SEG_COLLIDE_CASE : public KI_TEST::NAMED_CASE
{
SEG m_seg_a;
SEG m_seg_b;
int m_clearance;
bool m_exp_coll;
};
// clang-format off
static const std::vector<SEG_SEG_COLLIDE_CASE> seg_seg_coll_cases = {
{
"Parallel, 10 apart, 5 clear",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 10 }, { 10, 10 } },
5,
false,
},
{
"Parallel, 10 apart, 10 clear",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 10 }, { 10, 10 } },
10,
false,
},
{
"Parallel, 10 apart, 11 clear",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 10 }, { 10, 10 } },
11,
true,
},
{
"T-junction, 2 apart, 2 clear",
{ { 0, -10 }, { 0, 0 } },
{ { -20, 0 }, { -2, 0 } },
2,
false,
},
{
"T-junction, 2 apart, 3 clear",
{ { 0, -10 }, { 0, 0 } },
{ { -20, 0 }, { -2, 0 } },
3,
true,
},
};
// clang-format on
BOOST_DATA_TEST_CASE( SegSegCollision, boost::unit_test::data::make( seg_seg_coll_cases ), c )
{
BOOST_CHECK_PREDICATE( SegCollideCorrect,
( c.m_seg_a )( c.m_seg_b )( c.m_clearance )( c.m_exp_coll ) );
}
/**
* Struct to hold general cases for collinearity, parallelism and perpendicularity
*/
struct SEG_SEG_BOOLEAN_CASE : public KI_TEST::NAMED_CASE
{
SEG m_seg_a;
SEG m_seg_b;
bool m_exp_result;
};
// clang-format off
/**
* Test cases for collinearity
*/
static const std::vector<SEG_SEG_BOOLEAN_CASE> seg_vec_collinear_cases = {
{
"coincident",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 0 }, { 10, 0 } },
true,
},
{
"end-to-end",
{ { 0, 0 }, { 10, 0 } },
{ { 10, 0 }, { 20, 0 } },
true,
},
{
"In segment",
{ { 0, 0 }, { 10, 0 } },
{ { 4, 0 }, { 7, 0 } },
true,
},
{
"At side, parallel",
{ { 0, 0 }, { 10, 0 } },
{ { 4, 1 }, { 7, 1 } },
false,
},
{
"crossing",
{ { 0, 0 }, { 10, 0 } },
{ { 5, -5 }, { 5, 5 } },
false,
},
};
// clang-format on
BOOST_DATA_TEST_CASE( SegSegCollinear, boost::unit_test::data::make( seg_vec_collinear_cases ), c )
{
BOOST_CHECK_PREDICATE( SegCollinearCorrect, ( c.m_seg_a )( c.m_seg_b )( c.m_exp_result ) );
}
// clang-format off
/**
* Test cases for parallelism
*/
static const std::vector<SEG_SEG_BOOLEAN_CASE> seg_vec_parallel_cases = {
{
"coincident",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 0 }, { 10, 0 } },
true,
},
{
"end-to-end",
{ { 0, 0 }, { 10, 0 } },
{ { 10, 0 }, { 20, 0 } },
true,
},
{
"In segment",
{ { 0, 0 }, { 10, 0 } },
{ { 4, 0 }, { 7, 0 } },
true,
},
{
"At side, parallel",
{ { 0, 0 }, { 10, 0 } },
{ { 4, 1 }, { 7, 1 } },
true,
},
{
"crossing",
{ { 0, 0 }, { 10, 0 } },
{ { 5, -5 }, { 5, 5 } },
false,
},
};
// clang-format on
BOOST_DATA_TEST_CASE( SegSegParallel, boost::unit_test::data::make( seg_vec_parallel_cases ), c )
{
BOOST_CHECK_PREDICATE( SegParallelCorrect, ( c.m_seg_a )( c.m_seg_b )( c.m_exp_result ) );
}
// clang-format off
/**
* Test cases for perpendicularity
*/
static const std::vector<SEG_SEG_BOOLEAN_CASE> seg_vec_perpendicular_cases = {
{
"coincident",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 0 }, { 10, 0 } },
false,
},
{
"end-to-end",
{ { 0, 0 }, { 10, 0 } },
{ { 10, 0 }, { 20, 0 } },
false,
},
{
"In segment",
{ { 0, 0 }, { 10, 0 } },
{ { 4, 0 }, { 7, 0 } },
false,
},
{
"At side, parallel",
{ { 0, 0 }, { 10, 0 } },
{ { 4, 1 }, { 7, 1 } },
false,
},
{
"crossing 45 deg",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 0 }, { 5, 5 } },
false,
},
{
"very nearly perpendicular",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 0 }, { 1, 10 } },
true, //allow error margin of 1 IU
},
{
"not really perpendicular",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 0 }, { 3, 10 } },
false,
},
{
"perpendicular",
{ { 0, 0 }, { 10, 0 } },
{ { 0, 0 }, { 0, 10 } },
true,
},
{
"perpendicular not intersecting",
{ { 0, 0 }, { 10, 0 } },
{ { 15, 5 }, { 15, 10 } },
true,
},
};
// clang-format on
BOOST_DATA_TEST_CASE( SegSegPerpendicular,
boost::unit_test::data::make( seg_vec_perpendicular_cases ), c )
{
BOOST_CHECK_PREDICATE( SegPerpendicularCorrect, ( c.m_seg_a )( c.m_seg_b )( c.m_exp_result ) );
}
/**
* Struct to hold cases for operations with a #SEG, and a #VECTOR2I
*/
struct SEG_VEC_CASE : public KI_TEST::NAMED_CASE
{
SEG m_seg;
VECTOR2I m_vec;
};
// clang-format off
/**
* Test cases to create segments passing through a point
*/
static const std::vector<SEG_VEC_CASE> segment_and_point_cases = {
{
"Horizontal: point on edge of seg",
{ { 0, 0 }, { 10, 0 } },
{ 0, 0 },
},
{
"Horizontal: point in middle of seg",
{ { 0, 0 }, { 10, 0 } },
{ 5, 0 },
},
{
"Horizontal: point outside seg",
{ { 0, 0 }, { 10, 0 } },
{ 20, 20 },
},
{
"Vertical: point on edge of seg",
{ { 0, 0 }, { 0, 10 } },
{ 0, 0 },
},
{
"Vertical: point in middle of seg",
{ { 0, 0 }, { 0, 10 } },
{ 0, 5 },
},
{
"Vertical: point outside seg",
{ { 0, 0 }, { 0, 10 } },
{ 20, 20 },
},
};
// clang-format on
BOOST_DATA_TEST_CASE( SegCreateParallel, boost::unit_test::data::make( segment_and_point_cases ),
c )
{
const SEG perpendicular = c.m_seg.ParallelSeg( c.m_vec );
BOOST_CHECK_PREDICATE( SegParallelCorrect, (perpendicular) ( c.m_seg )( true ) );
BOOST_CHECK_PREDICATE( SegVecDistanceCorrect, (perpendicular) ( c.m_vec )( 0 ) );
}
BOOST_DATA_TEST_CASE( SegCreatePerpendicular,
boost::unit_test::data::make( segment_and_point_cases ), c )
{
const SEG perpendicular = c.m_seg.PerpendicularSeg( c.m_vec );
BOOST_CHECK_PREDICATE( SegPerpendicularCorrect, (perpendicular) ( c.m_seg )( true ) );
BOOST_CHECK_PREDICATE( SegVecDistanceCorrect, (perpendicular) ( c.m_vec )( 0 ) );
}
BOOST_AUTO_TEST_CASE( LineDistance )
{
SEG seg( { 0, 0 }, { 10, 0 } );
BOOST_TEST( seg.LineDistance( { 5, 0 } ) == 0 );
BOOST_TEST( seg.LineDistance( { 5, 8 } ) == 8 );
}
BOOST_AUTO_TEST_CASE( LineDistanceSided )
{
SEG seg( { 0, 0 }, { 10, 0 } );
BOOST_TEST( seg.LineDistance( { 5, 8 }, true ) == 8 );
BOOST_TEST( seg.LineDistance( { 5, -8 }, true ) == -8 );
}
BOOST_AUTO_TEST_SUITE_END()