[Osi-managers] Osi unittest

Stefan Vigerske stefan at math.hu-berlin.de
Fri Apr 1 05:43:04 EDT 2011


Hi,

I recently commited some changes to the Osi unittest which hopefully 
make it easier to figure out where a test was failing.
https://projects.coin-or.org/Osi/changeset/1676/trunk

There is now a global instantiation OsiUnitTest::outcomes of the new 
class TestOutcomes which is used to collect outcomes of tests. Each 
outcome consists of
- a component name (in most cases the Osi-interface name),
- a test name
- a test condition that was checked, or a test message
- a severity level:
   - NOTE is to pass messages, e.g., about skipped tests
   - PASSED is for tests have been passed successfully
   - WARNING is for warnings
   - ERROR is for errors
- a flag "expected", which make not much sense for PASSED, but for the
   others one can indicate whether, e.g., the failure of a test was
   expected (sometimes used for OsiVol)
- the name of the file where the test is coded
- the linenumber where the test is coded

Currently only the tests in OsiCommonTests and the 
OsiGlpkSolverInterfaceTest make use of this object, other 
OsiXyzSolverInterfaceTest still need to be adapted.

There are some macros that intend to make life easier.
One set of macros is for testing whether a condition is true and 
generating adding the corresponding TestOutcome to the TestOutcomes 
object. E.g., if before you had
   if( a != b ) {
     failureMessage(solverName, "a should be b");
     ++errCnt;
   }
then now you can write
  OSIUNITTEST_ASSERT_ERROR(a == b, ++errCnt, solverName, "test a and b");

This addes a TestOutcome with the following entries:
- component name is what is stored in the string solverName
   (alternatively, you can pass a OsiSolverInterface object here)
- test name is "test a and b"
- test condition is "a == b"
- severity level is PASSED if a == b, and ERROR otherwise
- expected is not set
- for filename __FILE__ is used
- for linenumber __LINE__ is used
Further, the failureMessage function is called and the code given in the 
second argument (++errCnt) is executed.
The errCnt is actually redundant now, but kept for backward compatibility.

There are similar macros like
- OSIUNITTEST_ASSERT_WARNING to specify if severity should be WARNING
   if the test fails
- OSIUNITTEST_CATCH_SEVERITY_EXPECTED with additional arguments to
   specify the severity and "expected" flag

The second set of macros is to test whether a code snippet produces an 
exception. E.g., if before you had something like
   try {
     si.initialSolve();
   } catch (CoinError& e) {
     std::string errmsg;
     errmsg = "initialSolve threw CoinError: " + e.message();
     failureMessage(*si, "test initial solve", errmsg.c_str());
     ++errCnt;
     return errCnt;
   }
then now you can write
   OSIUNITTEST_CATCH_ERROR(si.initialSolve(), return ++errCnt, si,
                                          "test initial solve");

This adds a TestOutcome with the following entries:
- component name is obtained via si.getStrParam(OsiSolverName);
- test name is "test initial solve"
- test condition is
   - "si.initialSolve() threw CoinError: "+e.message() if a CoinError
      was thrown
   - "si.initialSolve() throw unknown exception" if some other exception
      was thrown
   - "si.initialSolve() did not threw exception" if no exception was
      thrown
- severity level is PASSED or ERROR
- expected is not set
- filename is __FILE__
- linenumber is __LINE__
Additionally, if an exception is catched, a failure message is printed 
and the code "return ++errCnt;" is printed.

To print the outcomes of the unittest, one can do
   OsiUnitTest::outcomes.print();
Per default, this currently prints only collected notes, warnings, and 
errors and a statistic on number of outcomes for each severity level.

E.g., for Osi unittest if Glpk is available I get
NOTE      glpk      testSimplexAPI
  (expected)         skipped test
 
../../../../Osi/src/OsiCommonTest/OsiSimplexAPITest.cpp:740
WARNING   glpk      testHintParam: hint 1 sense 1 strength 3
                     if (si->setHintParam(key,sense,strength)) { ret = 
(si->getHintParam(key,post_sense,post_strength) == true) && 
(post_strength == strength) && (post_sense == sense); } threw CoinError: 
glpk does not support exclusive use of dual simplex
 
../../../../Osi/src/OsiCommonTest/OsiSolverInterfaceTest.cpp:1996
NOTE      glpk      testDualRays: getDualRays is implemented
                     hasGetDualRays
 
../../../../Osi/src/OsiCommonTest/OsiSolverInterfaceTest.cpp:3633

Severity NOTE      :    2  thereof expected:    1
Severity PASSED    : 1507  thereof expected:    0
Severity WARNING   :    4  thereof expected:    0
Severity ERROR     :    0  thereof expected:    0

(it looks better on a wide screen :-)).

To get the number of errors for one severity level, you can do
int nerrors;
int nerrors_expected; 
OsiUnitTest::outcomes.getCountBySeverity(OsiUnitTest::TestOutcome::ERROR, nerrors, 
nerrors_expected);

Currently, unitTest reports success iff nerrors == nerrors_expected.


Further, there is a new global variable OsiUnitTest::verbosity to 
specify verbosity of unittest output.
Default (=0) is to print only minimal output, i.e., messages on failed 
tests (this is not fully implemented yet). Level 1 is to print more 
information on errors. And Level 2 prints also information on passed 
tests, also in the output of TestOutcomes::print().
The verbosity level can be set via an additional parameter -verbosity to 
the unittest executable.


I replaced most assert(...) in the common unit tests by things like 
OSIUNITTEST_ASSERT_ERROR(.., ++errCnt; return, ...);

As a consequence, I took out the #ifdef NDEBUG #undef NDEBUG #endif.
At least to me this did not look like clean code.

Stefan



More information about the Osi-managers mailing list