Commit dc61ad0d authored by Dave Moxey's avatar Dave Moxey

Added MetricFile class and associated sha1 code.

Added command line options to Tester to enable generation of one, more or all metrics.
Removed Test namespace from TestData.
parent 3234eff3
SET(TESTER_SOURCES
Metric.cpp
MetricFile.cpp
MetricL2.cpp
MetricLInf.cpp
MetricRegex.cpp
TestData.cpp
Tester.cpp
sha1.cpp
)
SET(TESTER_HEADERS
Metric.h
MetricFile.h
MetricL2.h
MetricLInf.h
MetricRegex.h
TestData.h
Tester.h
sha1.h
)
ADD_DEFINITIONS(-DBUILD_PATH="${CMAKE_BINARY_DIR}")
......@@ -23,6 +27,7 @@ TARGET_LINK_LIBRARIES(Tester
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_REGEX_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY}
optimized ${TINYXML_LIB} debug ${TINYXML_LIB}
)
ADD_DEPENDENCIES(Tester boost tinyxml)
......
......@@ -52,7 +52,8 @@ namespace Nektar
/**
* @brief Constructor.
*/
Metric::Metric(TiXmlElement *metric)
Metric::Metric(TiXmlElement *metric, bool generate) :
m_metric(metric), m_generate(generate)
{
if (!metric->Attribute("id"))
{
......@@ -71,6 +72,14 @@ namespace Nektar
*/
bool Metric::Test(std::istream& pStdout, std::istream& pStderr)
{
return v_Test(pStdout, pStderr);
if (m_generate)
{
v_Generate(pStdout, pStderr);
return true;
}
else
{
return v_Test(pStdout, pStderr);
}
}
}
......@@ -46,19 +46,26 @@ namespace Nektar
class Metric
{
public:
Metric(TiXmlElement *metric);
Metric(TiXmlElement *metric, bool generate);
/// Perform the test, given the standard output and error streams
bool Test (std::istream& pStdout, std::istream& pStderr);
bool Test (std::istream& pStdout, std::istream& pStderr);
/// Perform the test, given the standard output and error streams
void Generate (std::istream& pStdout, std::istream& pStderr);
protected:
/// Stores the ID of this metric.
int m_id;
/// Stores the type of this metric (uppercase).
std::string m_type;
///
bool m_generate;
TiXmlElement *m_metric;
virtual bool v_Test (std::istream& pStdout, std::istream& pStderr) = 0;
virtual bool v_Test (std::istream& pStdout,
std::istream& pStderr) = 0;
virtual void v_Generate (std::istream& pStdout,
std::istream& pSrderr) = 0;
};
/// A shared pointer to an EquationSystem object
......@@ -66,8 +73,8 @@ namespace Nektar
/// Datatype of the NekFactory used to instantiate classes derived from the
/// Advection class.
typedef LibUtilities::NekFactory<std::string, Metric, TiXmlElement*>
MetricFactory;
typedef LibUtilities::NekFactory<std::string, Metric, TiXmlElement*, bool>
MetricFactory;
MetricFactory& GetMetricFactory();
}
......
///////////////////////////////////////////////////////////////////////////////
//
// File: MetricFile.cpp
//
// For more information, please see: http://www.nektar.info
//
// The MIT License
//
// Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
// Department of Aeronautics, Imperial College London (UK), and Scientific
// Computing and Imaging Institute, University of Utah (USA).
//
// License for the specific language governing rights and limitations under
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// Description: Implementation of the LInf metric.
//
///////////////////////////////////////////////////////////////////////////////
#include <iterator>
#include <fstream>
#include <vector>
#include <MetricFile.h>
#include <boost/algorithm/string.hpp>
#include <sha1.h>
namespace Nektar
{
std::string MetricFile::type = GetMetricFactory().
RegisterCreatorFunction("FILE", MetricFile::create);
MetricFile::MetricFile(TiXmlElement *metric, bool generate) :
Metric(metric, generate)
{
TiXmlElement *file = metric->FirstChildElement("file");
ASSERTL0(file, "Missing file tag for file metric!");
while (file)
{
std::string filename, sha1hash;
if (file->Attribute("filename"))
{
filename = file->Attribute("filename");
}
else
{
ASSERTL0(false, "Missing filename for file tag!");
}
if (!m_generate)
{
TiXmlElement *sha1 = file->FirstChildElement("sha1");
ASSERTL0(sha1, "Missing SHA1 hash for file "+filename);
sha1hash = sha1->GetText();
ASSERTL0(sha1hash.size() == 40,
"Incorrect length for SHA1 hash");
}
m_filehash[filename] = sha1hash;
file = file->NextSiblingElement("file");
}
}
std::string MetricFile::CalculateHash(std::string filename)
{
// Open file.
std::ifstream testFile(filename.c_str(), std::ios::binary);
ASSERTL0(testFile.is_open(), "Error opening file "+filename);
// Read in file contents.
std::vector<char> fileContents(
(std::istreambuf_iterator<char>(testFile)),
std::istreambuf_iterator<char>());
// Calculate SHA1 hash.
unsigned char hash[20];
char strhash[41];
sha1::calc((void *)&fileContents[0], fileContents.size(), hash);
sha1::toHexString(hash, strhash);
// Close file and return string containing SHA1 hash.
testFile.close();
return std::string(strhash);
}
bool MetricFile::v_Test(std::istream& pStdout, std::istream& pStderr)
{
std::map<std::string, std::string>::iterator it;
bool success = true;
for (it = m_filehash.begin(); it != m_filehash.end(); ++it)
{
std::string filehash = CalculateHash(it->first);
if (!boost::iequals(filehash, it->second))
{
std::cerr << "Failed SHA1 hash test." << std::endl;
std::cerr << " Expected: " << it->second
<< std::endl;
std::cerr << " Result: " << filehash << std::endl;
success = false;
}
}
return success;
}
void MetricFile::v_Generate(std::istream& pStdout, std::istream& pStderr)
{
std::map<std::string, std::string>::iterator it;
bool success = true;
// Update SHA1 hashes.
for (it = m_filehash.begin(); it != m_filehash.end(); ++it)
{
std::string filehash = CalculateHash(it->first);
m_filehash[it->first] = filehash;
}
// Write new XML structure.
TiXmlElement *file = m_metric->FirstChildElement("file");
while (file)
{
std::string filename = file->Attribute("filename");
file->Clear();
TiXmlElement *sha1 = new TiXmlElement("sha1");
ASSERTL0(m_filehash.count(filename) != 0,
"Couldn't find file " + filename + " in list of calculated"
"hashes");
sha1->LinkEndChild(new TiXmlText(m_filehash[filename]));
file->LinkEndChild(sha1);
file = file->NextSiblingElement("file");
}
}
}
///////////////////////////////////////////////////////////////////////////////
//
// File: MetricFile.h
//
// For more information, please see: http://www.nektar.info
//
// The MIT License
//
// Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
// Department of Aeronautics, Imperial College London (UK), and Scientific
// Computing and Imaging Institute, University of Utah (USA).
//
// License for the specific language governing rights and limitations under
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// Description: Definition of the LInf metric.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef NEKTAR_TESTS_METRICLINF_H
#define NEKTAR_TESTS_METRICLINF_H
#include <Metric.h>
#include <map>
namespace Nektar
{
class MetricFile : public Metric
{
public:
static MetricSharedPtr create(TiXmlElement *metric, bool generate)
{
return MetricSharedPtr(new MetricFile(metric, generate));
}
static std::string type;
protected:
MetricFile(TiXmlElement *metric, bool generate);
std::string CalculateHash(std::string filename);
virtual bool v_Test (std::istream& pStdout, std::istream& pStderr);
virtual void v_Generate(std::istream& pStdout, std::istream& pStderr);
/// Stores filenames to perform hash on.
std::map<std::string, std::string> m_filehash;
};
}
#endif
......@@ -40,16 +40,21 @@ namespace Nektar
std::string MetricL2::type = GetMetricFactory().
RegisterCreatorFunction("L2", MetricL2::create);
MetricL2::MetricL2(TiXmlElement *metric) : MetricRegex(metric)
// Guess default tolerance for generation routine.
std::string MetricL2::defaultTolerance = "1e-12";
MetricL2::MetricL2(TiXmlElement *metric, bool generate) :
MetricRegex(metric, generate)
{
// Set up the regular expression. This (optionally) matches a variable
// name if it exists: first field is variable name, second field is L2
// error.
m_regex = "^L 2 error\\s*(?:\\(variable "
"(\\w+)\\))?\\s*:\\s*([+-]?\\d.+\\d|0).*";
// Find the L2 error to match against.
TiXmlElement *value = metric->FirstChildElement("value");
ASSERTL0(value || m_generate, "Missing value tag for L2 metric!");
while (value)
{
// Set up a match with two fields which correspond with the
......@@ -71,13 +76,67 @@ namespace Nektar
val.m_useTolerance = true;
val.m_tolerance = atof(value->Attribute("tolerance"));
std::vector<MetricRegexFieldValue> tmp(2);
tmp[0] = var;
tmp[1] = val;
m_matches.push_back(tmp);
if (!m_generate)
{
std::vector<MetricRegexFieldValue> tmp(2);
tmp[0] = var;
tmp[1] = val;
m_matches.push_back(tmp);
}
else
{
m_varTolerance[var.m_value] = value->Attribute("tolerance");
}
value = value->NextSiblingElement("value");
}
}
void MetricL2::v_Generate(std::istream& pStdout, std::istream& pStderr)
{
// Run MetricRegex to generate matches.
MetricRegex::v_Generate(pStdout, pStderr);
// First remove all existing values.
m_metric->Clear();
// Now create new values.
for (int i = 0; i < m_matches.size(); ++i)
{
ASSERTL0(m_matches[i].size() == 2,
"Wrong number of matches for regular expression.");
bool tolSet = false;
std::string tol = MetricL2::defaultTolerance;
TiXmlElement *value = new TiXmlElement("value");
// See if there is a tolerance found already for this variable
// (including empty variables).
std::map<std::string,std::string>::iterator it =
m_varTolerance.find(m_matches[i][0].m_value);
if (it != m_varTolerance.end())
{
tol = it->second;
tolSet = true;
}
if (m_matches[i][0].m_value.size() > 0)
{
value->SetAttribute("variable", m_matches[i][0].m_value);
if (m_matches[i][0].m_value == "p" && !tolSet)
{
// Set lower tolerance for pressure fields automatically if
// we haven't already got a tolerance from the existing
// file.
tol = "1e-8";
}
}
value->SetAttribute("tolerance", tol);
value->LinkEndChild(new TiXmlText(m_matches[i][1].m_value));
m_metric->LinkEndChild(value);
}
}
}
......@@ -36,6 +36,7 @@
#ifndef NEKTAR_TESTS_METRICL2_H
#define NEKTAR_TESTS_METRICL2_H
#include <map>
#include <MetricRegex.h>
namespace Nektar
......@@ -43,16 +44,20 @@ namespace Nektar
class MetricL2 : public MetricRegex
{
public:
static MetricSharedPtr create(TiXmlElement *metric)
static MetricSharedPtr create(TiXmlElement *metric, bool generate)
{
return MetricSharedPtr(new MetricL2(metric));
return MetricSharedPtr(new MetricL2(metric, generate));
}
static std::string type;
static std::string defaultTolerance;
protected:
MetricL2(TiXmlElement *metric);
MetricL2(TiXmlElement *metric, bool generate);
std::map<std::string, std::string> m_varTolerance;
virtual void v_Generate(std::istream& pStdout, std::istream& pStderr);
};
}
......
......@@ -40,25 +40,31 @@ namespace Nektar
std::string MetricLInf::type = GetMetricFactory().
RegisterCreatorFunction("LINF", MetricLInf::create);
MetricLInf::MetricLInf(TiXmlElement *metric) : MetricRegex(metric)
// Guess default tolerance for generation routine.
std::string MetricLInf::defaultTolerance = "1e-12";
MetricLInf::MetricLInf(TiXmlElement *metric, bool generate) :
MetricRegex(metric, generate)
{
// Set up the regular expression. This (optionally) matches a variable
// name if it exists: first field is variable name, second field is L2
// error.
m_regex = "^L inf error\\s*(?:\\(variable "
m_regex = "^L inf\\w+ error\\s*(?:\\(variable "
"(\\w+)\\))?\\s*:\\s*([+-]?\\d.+\\d|0).*";
// Find the L2 error to match against.
TiXmlElement *value = metric->FirstChildElement("value");
ASSERTL0(value || m_generate, "Missing value tag for LInf metric!");
while (value)
{
// Set up a match with two fields which correspond with the
// subexpression above. The first is the variable name, second is
// the L2 error.
ASSERTL0(value->Attribute("tolerance"),
"Missing tolerance in LInf metric");
"Missing tolerance in L2 metric");
ASSERTL0(value->GetText() || value->GetText() == "",
"Missing value in LInf metric.");
"Missing value in L2 metric.");
MetricRegexFieldValue var;
if (value->Attribute("variable"))
......@@ -71,14 +77,70 @@ namespace Nektar
val.m_useTolerance = true;
val.m_tolerance = atof(value->Attribute("tolerance"));
std::vector<MetricRegexFieldValue> tmp(2);
tmp[0] = var;
tmp[1] = val;
m_matches.push_back(tmp);
if (!m_generate)
{
std::vector<MetricRegexFieldValue> tmp(2);
tmp[0] = var;
tmp[1] = val;
m_matches.push_back(tmp);
}
else
{
m_varTolerance[var.m_value] = value->Attribute("tolerance");
}
value = value->NextSiblingElement("value");
}
}
void MetricLInf::v_Generate(std::istream& pStdout, std::istream& pStderr)
{
// Run MetricRegex to generate matches.
MetricRegex::v_Generate(pStdout, pStderr);
// First remove all existing values.
m_metric->Clear();
ASSERTL0(m_matches.size() > 0,
"Unable to find L infinity norm in output!");
// Now create new values.
for (int i = 0; i < m_matches.size(); ++i)
{
ASSERTL0(m_matches[i].size() == 2,
"Wrong number of matches for regular expression.");
bool tolSet = false;
std::string tol = MetricLInf::defaultTolerance;
TiXmlElement *value = new TiXmlElement("value");
// See if there is a tolerance found already for this variable
// (including empty variables).
std::map<std::string,std::string>::iterator it =
m_varTolerance.find(m_matches[i][0].m_value);
if (it != m_varTolerance.end())
{
tol = it->second;
tolSet = true;
}
if (m_matches[i][0].m_value.size() > 0)
{
value->SetAttribute("variable", m_matches[i][0].m_value);
if (m_matches[i][0].m_value == "p" && !tolSet)
{
// Set lower tolerance for pressure fields automatically if
// we haven't already got a tolerance from the existing
// file.
tol = "1e-8";
}
}
value->SetAttribute("tolerance", tol);
value->LinkEndChild(new TiXmlText(m_matches[i][1].m_value));
m_metric->LinkEndChild(value);
}
}
}
......@@ -43,16 +43,20 @@ namespace Nektar
class MetricLInf : public MetricRegex
{
public:
static MetricSharedPtr create(TiXmlElement *metric)
static MetricSharedPtr create(TiXmlElement *metric, bool generate)
{
return MetricSharedPtr(new MetricLInf(metric));
return MetricSharedPtr(new MetricLInf(metric, generate));
}
static std::string type;
static std::string defaultTolerance;
protected:
MetricLInf(TiXmlElement *metric);
MetricLInf(TiXmlElement *metric, bool generate);
std::map<std::string, std::string> m_varTolerance;
virtual void v_Generate(std::istream& pStdout, std::istream& pStderr);
};
}
......
......@@ -47,7 +47,8 @@ namespace Nektar
/**
* @brief Constructor.
*/
MetricRegex::MetricRegex(TiXmlElement *metric) : Metric(metric)
MetricRegex::MetricRegex(TiXmlElement *metric, bool generate) :
Metric(metric, generate)
{
// If we are a derived class, do nothing
if (m_type != "REGEX")
......@@ -61,7 +62,12 @@ namespace Nektar
ASSERTL0(regex->GetText(), "Failed to get text");
m_regex = regex->GetText();
// Parse matching values
// Parse matching values if not generating.
if (m_generate)
{
return;
}
TiXmlElement *matches = metric->FirstChildElement("matches");
ASSERTL0(matches, "No matches defined.");
TiXmlElement *match = matches->FirstChildElement("match");
......@@ -90,7 +96,6 @@ namespace Nektar
}
}
/**
* @brief Test output against a regex expression and set of matches.
*/
......@@ -98,9 +103,10 @@ namespace Nektar
{
ASSERTL0(m_matches.size(), "No test conditions defined for Regex.");
bool success = true;
boost::cmatch matches;
std::vector<MetricRegexFieldValue> &okValues = m_matches[0];
int nMatch = m_matches.size();
bool success = true;
boost::cmatch matches;
// Process output file line by line searching for regex matches
std::string line;
......@@ -112,7 +118,7 @@ namespace Nektar
// Error if no fields in regex then throw an error.
if (matches.size() == 1)
{
cout << "No test sections in regex!" << endl;
cerr << "No test sections in regex!" << endl;
return false;
}
......@@ -120,7 +126,7 @@ namespace Nektar
for (int i = 1; i < matches.size(); ++i)
{
std::string match(matches[i].first, matches[i].second);
if (okValues[i-1].m_useTolerance)
{
double val = fabs(boost::lexical_cast<double>(
......@@ -157,6 +163,87 @@ namespace Nektar
}
}
if (m_matches.size() != 0)
{
cerr << "Expected " << nMatch << " matches but only found "
<< (nMatch - m_matches.size()) << "!" << endl;
success = false;
}
return success;
}
/**
* @brief Test output against a regex expression and set of matches.
*/
void MetricRegex::v_Generate(std::istream& pStdout, std::istream& pStderr)
{
boost::cmatch matches;
// Process output file line by line searching for regex matches
std::string line;
while (getline(pStdout, line))
{
// Test to see if we have a match on this line.
if (boost::regex_match(line.c_str(), matches, m_regex))
{
// Error if no fields in regex then throw an error.
ASSERTL0(matches.size() != 1, "No test sections in regex!");
vector<MetricRegexFieldValue> okValues;
for (int i = 1; i < matches.size(); ++i)
{
// Create new field.
MetricRegexFieldValue okValue;
okValue.m_useTolerance = false;
okValue.m_value = std::string(matches[i].first,
matches[i].second);