poco/CppUnit/include/CppUnit/DataDrivenTestCaller.h
2007-07-13 19:43:30 +00:00

237 lines
7.6 KiB
C++

//
// DataDrivenTestCaller.h
//
// $Id: //poco/Main/CppUnit/include/CppUnit/DataDrivenTestCaller.h#3 $
//
#ifndef CppUnit_DataDrivenTestCaller_INCLUDED
#define CppUnit_DataDrivenTestCaller_INCLUDED
#include "CppUnit/CppUnit.h"
#include "CppUnit/TestCase.h"
#include "CppUnit/TestSuite.h"
#include "CppUnit/CppUnitException.h"
#include <iostream>
#include <fstream>
#include "Poco/File.h"
#include "Poco/Path.h"
#include "Poco/Exception.h"
#include <algorithm>
#include <functional>
#include <map>
namespace CppUnit {
/*
* A data driven test case defines the fixture to run a single test with varying input streams.
* A data driven test is assumed to take an input stream and write its result to an output stream
* which can then be compared to a reference output.
* To do data driven testing
* 1) implement a subclass of TestCase
* 2) define instance variables that store the state of the fixture
* 3) initialize the fixture state by overriding setUp
* 4) clean-up after a test by overriding tearDown.
*
* Each test runs in its own fixture so there
* can be no side effects among test runs.
*
* Each test method must implement the following method signature:
*
* void dotest(std::istream& in, std::ostream& out)
*
* Add the test in the suite() method of your test case with the CppUnit_addDataTest macro:
*
* CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("WebWidgetsTest");
* CppUnit_addDataTest(pSuite, WebWidgetsTest, doTest, "tests/", "temp/");
*
* CppUnit_addDataTest expects 5 input parameters:
* - the first is always a pointer to the suite where the tests are added
* - the second parameter is the name of your test class
* - the third parameter is the test method
* - the fourth parameter contains the directory where the tests are stored. Note that
* we assume a certain directory structure inside the tests folder.
* Input files are stored in the subfolder tests/data, the reference results are stored
* in the subfolder tests/results. The matching of reference to input data happens automatically via the file
* base name. For each input a separate test is inserted with the base name as the name of the test.
* - the last parameter defines where we should generate temporary files
*
* Note that the test framework guarantees that input and output stream for the test
* method are valid and open. It will also perform the comparision (i.e. does the produced output matches
* the reference output) and close the input and outout streams.
*
*/
template <class Fixture>
class DataDrivenTestCaller: public TestCase
{
REFERENCEOBJECT (DataDrivenTestCaller)
typedef void (Fixture::*TestMethod)(std::istream& in, std::ostream& out);
public:
DataDrivenTestCaller(const std::string& name, TestMethod test, const Poco::File& in, const Poco::File& out, const Poco::File& expectedResult):
TestCase(name),
_test(test),
_fixture(new Fixture(name)),
_in(in),
_out(out),
_result(expectedResult)
{
}
protected:
void runTest()
{
std::ifstream in(_in.path().c_str(), std::ios::binary);
if (!in)
throw Poco::FileNotFoundException("Failed to open file " + _in.path());
std::ofstream out(_out.path().c_str(), std::ios::binary);
if (!out)
throw Poco::CreateFileException("Failed to open file for writing " + _out.path());
try
{
(_fixture.get()->*_test)(in, out);
}
catch(Poco::Exception&)
{
// maybe an exception is wanted
// compare with ref file
if (_result.exists() && _result.getSize() > 0)
{
in.close();
out.close();
throw;
}
}
in.close();
out.close();
// now compare
std::ifstream inRes(_out.path().c_str(), std::ios::binary);
std::ifstream refRes(_result.path().c_str(), std::ios::binary);
if (!inRes || !refRes)
throw Poco::FileNotFoundException("Can't compare with reference file. Reference result missing or file not created " + _out.path());
while (!inRes.eof())
{
int c = inRes.get();
int c2 = refRes.get();
if (c != c2)
throw Poco::DataException("Created file does not match reference file" + _out.path());
}
}
void setUp()
{
_fixture.get()->setUp();
}
void tearDown()
{
_fixture.get()->tearDown();
}
public:
static void addDirectoryTest(TestSuite* pSuite, typename DataDrivenTestCaller::TestMethod method, const std::string& rootDir, const Poco::Path& tempD)
/// rootDir contains the name of the root directory (not a path). We will start searching for the root dir in the
/// current directory and go up until we are at the root level
/// The root dir must contain a subfolder /data and /results. tempDir will be used to store temporary files
{
Poco::Path dir(rootDir);
std::string cwd(Poco::Path::current());
Poco::Path cwdPath(cwd);
Poco::Path p(cwdPath, dir);
p.makeFile();
bool found = false;
while (!found && p.depth() > 0)
{
Poco::File f(p);
if (f.exists())
found = true;
else
p.popDirectory();
}
if (!found)
return;
Poco::Path data(p, "data");
data.makeDirectory();
Poco::File dataDir(data);
if (!dataDir.exists())
return;
Poco::Path results(p, "results");
results.makeDirectory();
Poco::File resDir(results);
if (!resDir.exists())
return;
Poco::Path temp(tempD);
Poco::File tempDir(temp);
temp.makeDirectory();
tempDir.createDirectories();
std::vector<std::string> inputFiles;
dataDir.list(inputFiles);
std::vector<std::string> referenceFiles;
resDir.list(referenceFiles);
std::sort(inputFiles.begin(), inputFiles.end(), std::less<std::string>());
std::sort(referenceFiles.begin(), referenceFiles.end(), std::less<std::string>());
typedef std::pair<Poco::File, Poco::File> InputRef;
std::map<std::string, InputRef> pairs;
// now map input to reference via basename
std::vector<std::string>::const_iterator itIn = inputFiles.begin();
std::vector<std::string>::const_iterator itInEnd = inputFiles.end();
for (; itIn != itInEnd; ++itIn)
{
std::cout << *itIn << std::endl;
Poco::Path inTmp(data, *itIn);
Poco::File inFTmp(inTmp);
if (!inTmp.getBaseName().empty())
pairs.insert(std::make_pair(inTmp.getBaseName(), std::make_pair(inFTmp, Poco::File())));
}
std::vector<std::string>::const_iterator itRef = referenceFiles.begin();
std::vector<std::string>::const_iterator itRefEnd = referenceFiles.end();
for (; itRef != itRefEnd; ++itRef)
{
Poco::Path refTmp(results, *itRef);
Poco::File refFTmp(refTmp);
std::map<std::string, InputRef>::iterator it = pairs.find(refTmp.getBaseName());
if (it != pairs.end())
it->second.second = refFTmp;
}
std::map<std::string, InputRef>::const_iterator itP = pairs.begin();
std::map<std::string, InputRef>::const_iterator itPEnd = pairs.end();
for (; itP != itPEnd; ++itP)
{
std::string fileName;
if (!itP->second.second.path().empty())
{
Poco::Path refTmp(itP->second.second.path());
fileName = refTmp.getFileName();
}
else
fileName = itP->first + ".tmp";
Poco::Path outTmp(temp, fileName);
Poco::File outFTmp(outTmp);
pSuite->addTest(new DataDrivenTestCaller(itP->first, method, itP->second.first, outFTmp, itP->second.second));
}
}
private:
TestMethod _test;
std::auto_ptr<Fixture> _fixture;
Poco::File _in;
Poco::File _out;
Poco::File _result;
};
} // namespace CppUnit
#define CppUnit_addDataTest(suite, cls, mth, rootDir, tempDir) \
CppUnit::DataDrivenTestCaller<cls>::addDirectoryTest(suite, &cls::mth, rootDir, tempDir)
#endif // DataDrivenTestCaller_INCLUDED