mirror of
https://github.com/pocoproject/poco.git
synced 2025-04-23 00:48:52 +02:00
237 lines
7.6 KiB
C++
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
|