QA: Make a separate unit test utils library, COLOR4D tests

This includes:

* Linkage against the Boost unit test libs
* Configuration of the Boost libs
* A place for common generics "extras" for unit test harnesses
  including
    * A simple way to allow "expected-failure" tests (without
      breaking Boost < 1.58, e.g. Ubuntu LTS)
    * Moving some simple numeric predicates from the geom tests
      to the utils library.

Expand unit test docs to describe the expected failures macro.

Add a few COLOR4D tests, including one with expected failures due
to a pre-existing bug. This will be fixed in a follow-up commit.
This commit is contained in:
John Beard 2018-11-07 13:55:20 +00:00 committed by Wayne Stambaugh
parent 0980f7232c
commit ee819216e2
10 changed files with 554 additions and 60 deletions

View File

@ -70,6 +70,50 @@ messages inside tested functions (i.e. where you don't have access to the Boost
unit test headers). These will always be printed, so take care
to remove them before committing, or they'll show up when KiCad runs normally!
### Expected failures {#expected-failures}
Sometimes, it is helpful to check in tests that do not pass. However, it is bad
practise to intentionally check in commits that break builds (which is what
happens if you cause `make test` to fail).
Boost provides a method of declaring that some specific tests are allowed to fail.
This syntax is not consistently available in all supported Boost versions, so you
should use the following construct:
```
#include <unit_test_utils/unit_test_utils.h>
// On platforms with older boosts, the test will be excluded entirely
#ifdef HAVE_EXPECTED_FAILURES
// Declare a test case with 1 "allowed" failure (out of 2, in this case)
BOOST_AUTO_TEST_CASE( SomeTest, *boost::unit_test::expected_failures( 1 ) )
{
BOOST_CHECK_EQUAL( 1, 1 );
// This check fails, but does not cause a test suite failure
BOOST_CHECK_EQUAL( 1, 2 );
// Further failures *would* be a test suit failure
}
#endif
```
When run, this produces output somewhat like this:
```
qa/common/test_mytest.cpp(123): error: in "MyTests/SomeTest": check 1 == 2 has failed [1 != 2
*** No errors detected
```
And the unit test executable returns `0` (success).
Checking in a failing test is a strictly temporary situation, used to illustrate
the triggering of a bug prior to fixing it. This is advantageous, not only from
a "project history" perspective, but also to ensure that the test you write to
catch the bug in question does, in fact, catch the bug in the first place.
## Python modules {#python-tests}
The Pcbnew Python modules have some test programs in the `qa` directory.

View File

@ -14,6 +14,7 @@ endif()
# common QA helpers
add_subdirectory( qa_utils )
add_subdirectory( unit_test_utils )
add_subdirectory( common )
add_subdirectory( shape_poly_set_refactor )

View File

@ -19,12 +19,8 @@
# or you may write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
find_package(Boost COMPONENTS unit_test_framework REQUIRED)
find_package( wxWidgets 3.0.0 COMPONENTS gl aui adv html core net base xml stc REQUIRED )
add_definitions(-DBOOST_TEST_DYN_LINK)
add_executable( qa_common
# This is needed for the global mock objects
common_mocks.cpp
@ -39,6 +35,7 @@ add_executable( qa_common
../../common/colors.cpp
../../common/observable.cpp
test_color4d.cpp
test_hotkey_store.cpp
test_title_block.cpp
test_utf8.cpp
@ -50,7 +47,6 @@ include_directories(
${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/polygon
${Boost_INCLUDE_DIR}
${INC_AFTER}
)
@ -60,7 +56,8 @@ target_link_libraries( qa_common
polygon
bitmaps
gal
${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}
qa_utils
unit_test_utils
${wxWidgets_LIBRARIES}
)

View File

@ -26,55 +26,18 @@
#include <math.h>
#include <geometry/seg.h>
#include <geometry/shape_line_chain.h>
#include <geometry/shape_poly_set.h>
#include <unit_test_utils/numeric.h>
/**
* @brief Utility functions for testing geometry functions.
*/
namespace GEOM_TEST
{
/**
* @brief Check if a value is within a tolerance of a nominal value
*
* @return value is in [aNominal - aError, aNominal + aError]
*/
template<typename T>
bool IsWithin( T aValue, T aNominal, T aError )
{
return ( aValue >= aNominal - aError )
&& ( aValue <= aNominal + aError );
}
/**
* @brief Check if a value is within a tolerance of a nominal value,
* with different allowances for errors above and below.
*
* @return value is in [aNominal - aErrorBelow, aNominal + aErrorAbove]
*/
template<typename T>
bool IsWithin( T aValue, T aNominal, T aErrorAbove, T aErrorBelow )
{
return ( aValue >= aNominal - aErrorBelow )
&& ( aValue <= aNominal + aErrorAbove );
}
/**
* @brief value is in range [aNominal - aErrorBelow, aNominal]
*/
template<typename T>
bool IsWithinAndBelow( T aValue, T aNominal, T aErrorBelow )
{
return IsWithin( aValue, aNominal, 0, aErrorBelow );
}
/**
* @brief value is in range [aNominal, aNominal + aErrorAbove]
*/
template<typename T>
bool IsWithinAndAbove( T aValue, T aNominal, T aErrorAbove )
{
return IsWithin( aValue, aNominal, aErrorAbove, 0 );
}
/**
* @brief Geometric quadrants, from top-right, anti-clockwise
*
@ -163,7 +126,7 @@ bool ArePerpendicular( const VECTOR2<T>& a, const VECTOR2<T>& b, double aToleran
angle -= M_PI;
}
return IsWithin( angle, M_PI / 2.0, aTolerance );
return KI_TEST::IsWithin( angle, M_PI / 2.0, aTolerance );
}
/**

View File

@ -24,6 +24,8 @@
#include <boost/test/unit_test.hpp>
#include <boost/test/test_case_template.hpp>
#include <unit_test_utils/unit_test_utils.h>
#include <geometry/shape_poly_set.h>
#include <geometry/shape_line_chain.h>
@ -51,26 +53,24 @@ BOOST_FIXTURE_TEST_SUITE( Fillet, FilletFixture )
void TestFilletSegmentConstraints( const SEG& aSeg, VECTOR2I aRadCentre,
int aRadius, int aError )
{
using namespace GEOM_TEST;
const auto diffA = aRadCentre - aSeg.A;
const auto diffB = aRadCentre - aSeg.B;
const auto diffC = aRadCentre - aSeg.Center();
// Check 1: radii (error of 1 for rounding)
BOOST_CHECK_PREDICATE( IsWithinAndBelow<int>,
( diffA.EuclideanNorm() )( aRadius )( 1 ) );
BOOST_CHECK_PREDICATE( IsWithinAndBelow<int>,
( diffB.EuclideanNorm() )( aRadius )( 1 ) );
BOOST_CHECK_PREDICATE(
KI_TEST::IsWithinAndBelow<int>, ( diffA.EuclideanNorm() )( aRadius )( 1 ) );
BOOST_CHECK_PREDICATE(
KI_TEST::IsWithinAndBelow<int>, ( diffB.EuclideanNorm() )( aRadius )( 1 ) );
// Check 2: Mid-point error
BOOST_CHECK_PREDICATE( IsWithinAndBelow<int>,
( diffC.EuclideanNorm() )( aRadius )( aError + 1 ) );
BOOST_CHECK_PREDICATE(
KI_TEST::IsWithinAndBelow<int>, ( diffC.EuclideanNorm() )( aRadius )( aError + 1 ) );
// Check 3: Mid-point -> radius centre perpendicular
const auto perpendularityMaxError = ( M_PI / 2 ) / 10;
BOOST_CHECK_PREDICATE( ArePerpendicular<int>,
( diffC )( aSeg.A - aSeg.B )( perpendularityMaxError ) );
BOOST_CHECK_PREDICATE( GEOM_TEST::ArePerpendicular<int>,
( diffC )( aSeg.A - aSeg.B )( perpendularityMaxError ) );
}

289
qa/common/test_color4d.cpp Normal file
View File

@ -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 <boost/test/test_case_template.hpp>
#include <boost/test/unit_test.hpp>
#include <unit_test_utils/numeric.h>
#include <unit_test_utils/unit_test_utils.h>
#include <gal/color4d.h>
#ifdef WX_COMPATIBILITY
#include <wx/colour.h>
#endif
// All these tests are of a class in KIGFX
using namespace KIGFX;
/**
* Checks if a COLOR4D is close enough to another
*/
bool pred_colour_is_near( const COLOR4D& aCol, const COLOR4D aOther, double aTol )
{
return KI_TEST::IsWithin<double>( aCol.r, aOther.r, aTol )
&& KI_TEST::IsWithin<double>( aCol.g, aOther.g, aTol )
&& KI_TEST::IsWithin<double>( aCol.b, aOther.b, aTol )
&& KI_TEST::IsWithin<double>( aCol.a, aOther.a, aTol );
}
/**
* Checks if a COLOR4D is close enough to a given RGB char value
*/
bool pred_colour_is_near_hex(
const COLOR4D& aCol, unsigned char r, unsigned char g, unsigned char b, unsigned char a )
{
const double tol = 0.5 / 255.0; // One bit of quantised error
return KI_TEST::IsWithin<double>( aCol.r, r / 255.0, tol )
&& KI_TEST::IsWithin<double>( aCol.g, g / 255.0, tol )
&& KI_TEST::IsWithin<double>( aCol.b, b / 255.0, tol )
&& KI_TEST::IsWithin<double>( aCol.a, a / 255.0, tol );
}
/**
* Declares a struct as the Boost test fixture.
*/
BOOST_AUTO_TEST_SUITE( Color4D )
/**
* Check basic setting and getting of values
*/
BOOST_AUTO_TEST_CASE( BasicOps )
{
const auto c = COLOR4D{ 0.4, 0.5, 0.6, 0.7 };
BOOST_CHECK_EQUAL( c.r, 0.4 );
BOOST_CHECK_EQUAL( c.g, 0.5 );
BOOST_CHECK_EQUAL( c.b, 0.6 );
BOOST_CHECK_EQUAL( c.a, 0.7 );
const auto copied = c;
// Test equality
BOOST_CHECK_EQUAL( c, copied );
const auto c2 = COLOR4D{ 0.1, 0.2, 0.3, 0.4 };
// Test inequality
BOOST_CHECK_NE( c, c2 );
}
/**
* Test case data for a test that takes a colour and a scalar factor
* and returns a result.
*/
struct COLOR_SCALAR_CASE
{
COLOR4D start;
double factor;
COLOR4D expected;
};
/**
* Check inversion
*/
BOOST_AUTO_TEST_CASE( Invert )
{
// Inverts RGB, A is the same
static const std::vector<COLOR_SCALAR_CASE> cases = {
{ { 0.0, 0.25, 1.0, 1.0 }, 0.0, { 1.0, 0.75, 0.0, 1.0 } },
};
for( const auto& c : cases )
{
auto col = c.start;
const auto inverted = col.Inverted();
BOOST_CHECK_EQUAL( inverted, c.expected );
// Test in-place function
col.Invert();
BOOST_CHECK_EQUAL( col, c.expected );
}
}
/**
* Check inversion
*/
BOOST_AUTO_TEST_CASE( Brighten )
{
static const std::vector<COLOR_SCALAR_CASE> cases = {
{ { 0.0, 0.0, 0.0, 1.0 }, 0.5, { 0.5, 0.5, 0.5, 1.0 } },
{ { 0.0, 0.5, 1.0, 1.0 }, 0.5, { 0.5, 0.75, 1.0, 1.0 } },
};
for( const auto& c : cases )
{
auto col = c.start;
const auto brightened = col.Brightened( c.factor );
BOOST_CHECK_EQUAL( brightened, c.expected );
// Test in-place function
col.Brighten( c.factor );
BOOST_CHECK_EQUAL( col, c.expected );
}
}
/**
* Check darken
*/
BOOST_AUTO_TEST_CASE( Darken )
{
static const std::vector<COLOR_SCALAR_CASE> cases = {
{ { 0.0, 0.0, 0.0, 1.0 }, 0.5, { 0.0, 0.0, 0.0, 1.0 } },
{ { 1.0, 1.0, 1.0, 1.0 }, 0.5, { 0.5, 0.5, 0.5, 1.0 } },
};
for( const auto& c : cases )
{
auto col = c.start;
const auto brightened = col.Darkened( c.factor );
BOOST_CHECK_EQUAL( brightened, c.expected );
// Test in-place function
col.Darken( c.factor );
BOOST_CHECK_EQUAL( col, c.expected );
}
}
/**
* Check alpha setting
*/
BOOST_AUTO_TEST_CASE( WithAlpha )
{
static const std::vector<COLOR_SCALAR_CASE> cases = {
{ { 0.0, 0.0, 0.0, 1.0 }, 0.5, { 0.0, 0.0, 0.0, 0.5 } },
{ { 0.0, 0.5, 1.0, 1.0 }, 0.5, { 0.0, 0.5, 1.0, 0.5 } },
};
for( const auto& c : cases )
{
auto col = c.start;
const auto with_alpha = col.WithAlpha( c.factor );
BOOST_CHECK_EQUAL( with_alpha, c.expected );
}
// Note: If COLOR4D::WithAlpha raised an exception, we could check
// the bounds-checking with BOOST_REQUIRE_THROW,
// but it assert()s, so we can't.
}
struct FROM_HSV_TO_HEX_CASE
{
double h;
double s;
double v;
unsigned char r;
unsigned char g;
unsigned char b;
};
/**
* Check FromHSV
*/
BOOST_AUTO_TEST_CASE( FromHsv )
{
static const std::vector<FROM_HSV_TO_HEX_CASE> cases = {
{ 90.0, 0.5, 0.5, 96, 128, 64 },
};
for( const auto& c : cases )
{
auto col = COLOR4D{};
col.FromHSV( c.h, c.s, c.v );
const unsigned char alpha = 0xFF;
BOOST_CHECK_PREDICATE( pred_colour_is_near_hex, ( col )( c.r )( c.g )( c.b )( alpha ) );
}
}
#ifdef WX_COMPATIBILITY
struct WX_CONV_CASE
{
wxColour wx;
COLOR4D c4d;
};
static std::vector<WX_CONV_CASE> wx_conv_cases = {
{ { 0x00, 0x00, 0x00, 0x00 }, { 0.0, 0.0, 0.0, 0.0 } },
{ { 0x66, 0x80, 0x99, 0xB3 }, { 0.4, 0.5, 0.6, 0.7 } },
{ { 0xFF, 0xFF, 0xFF, 0xFF }, { 1.0, 1.0, 1.0, 1.0 } },
{ { 0xFF, 0x00, 0x00, 0xFF }, { 0.999, 0.001, 0.0, 1.0 } },
};
#ifdef HAVE_EXPECTED_FAILURES
/**
* Check conversion to WxColour
*/
BOOST_AUTO_TEST_CASE( ToWx, *boost::unit_test::expected_failures( 3 ) )
{
for( const auto& c : wx_conv_cases )
{
wxColour wx_col = c.c4d.ToColour();
// A hack, but avoids having to define a custom operator<<
BOOST_CHECK_EQUAL( wx_col.Red(), c.wx.Red() );
BOOST_CHECK_EQUAL( wx_col.Green(), c.wx.Green() );
BOOST_CHECK_EQUAL( wx_col.Blue(), c.wx.Blue() );
BOOST_CHECK_EQUAL( wx_col.Alpha(), c.wx.Alpha() );
}
}
#endif // HAVE_EXPECTED_FAILURES
/**
* Check conversion from WxColour
*/
BOOST_AUTO_TEST_CASE( FromWx )
{
const double tol = 0.5 / 255.0; // One bit of quantised error
for( const auto& c : wx_conv_cases )
{
const auto col = COLOR4D{ c.wx };
BOOST_CHECK_PREDICATE( pred_colour_is_near, ( col )( c.c4d )( tol ) );
}
}
#endif // WX_COMPATIBILITY
BOOST_AUTO_TEST_SUITE_END()

View File

@ -0,0 +1,47 @@
# 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
# The unit_test_utils library is a simple helper library to collate
# utilities that are generically useful for unit test executables.
#
# 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)
set(SRCS
unit_test_utils.cpp
)
add_library( unit_test_utils STATIC ${SRCS} )
target_link_libraries( unit_test_utils PUBLIC
${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}
)
target_include_directories( unit_test_utils PUBLIC
include
${Boost_INCLUDE_DIR}
)
target_compile_definitions( unit_test_utils PUBLIC
BOOST_TEST_DYN_LINK
)

View File

@ -0,0 +1,74 @@
/*
* 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
*/
/**
* @file numeric.h
* Numerical test predicates.
*/
#ifndef NUMERIC__H
#define NUMERIC__H
namespace KI_TEST
{
/**
* Check if a value is within a tolerance of a nominal value
*
* @return value is in [aNominal - aError, aNominal + aError]
*/
template <typename T> bool IsWithin( T aValue, T aNominal, T aError )
{
return ( aValue >= aNominal - aError ) && ( aValue <= aNominal + aError );
}
/**
* Check if a value is within a tolerance of a nominal value,
* with different allowances for errors above and below.
*
* @return value is in [aNominal - aErrorBelow, aNominal + aErrorAbove]
*/
template <typename T> bool IsWithinBounds( T aValue, T aNominal, T aErrorAbove, T aErrorBelow )
{
return ( aValue >= aNominal - aErrorBelow ) && ( aValue <= aNominal + aErrorAbove );
}
/**
* value is in range [aNominal - aErrorBelow, aNominal]
*/
template <typename T> bool IsWithinAndBelow( T aValue, T aNominal, T aErrorBelow )
{
return IsWithinBounds( aValue, aNominal, 0, aErrorBelow );
}
/**
* value is in range [aNominal, aNominal + aErrorAbove]
*/
template <typename T> bool IsWithinAndAbove( T aValue, T aNominal, T aErrorAbove )
{
return IsWithinBounds( aValue, aNominal, aErrorAbove, 0 );
}
} // namespace KI_TEST
#endif // NUMERIC__H

View File

@ -0,0 +1,53 @@
/*
* 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
*/
#ifndef UNIT_TEST_UTILS__H
#define UNIT_TEST_UTILS__H
#include <functional>
/**
* If HAVE_EXPECTED_FAILURES is defined, this means that
* boost::unit_test::expected_failures is available.
*
* Wrap expected-to-fail tests with this to prevent them being compiled
* on platforms with older (<1.59) Boost versions.
*
* This can be removed when our minimum boost version is 1.59 or higher.
*/
#if BOOST_VERSION >= 105900
#define HAVE_EXPECTED_FAILURES
#endif
/**
* BOOST_TEST, while extremely handy, is not available in Boost < 1.59.
* Undef it here to prevent use. Using it can cause older packaging like
* Ubuntu LTS (which is on Boost 1.58) to fail.
*
* Use BOOST_CHECK_{EQUAL,NE,etc} instead.
*
* This can be removed when our minimum boost version is 1.59 or higher.
*/
#undef BOOST_TEST
#endif // UNIT_TEST_UTILS__H

View File

@ -0,0 +1,26 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-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
*/
/*
* Nothing here yet, but CMake requires *something*.
*/