-
Wuttke, Joachim authored
PyPersistTest now compares dat and ref YAML tree entry by entry, allowing for a tolerance for floating-point numbers. Still to do: - allow for "VARIABILITY" anywhere in the tree - generate YAML output from Fit tests.
Wuttke, Joachim authoredPyPersistTest now compares dat and ref YAML tree entry by entry, allowing for a tolerance for floating-point numbers. Still to do: - allow for "VARIABILITY" anywhere in the tree - generate YAML output from Fit tests.
PyPersistenceTest.cpp 8.04 KiB
// ************************************************************************** //
//
// BornAgain: simulate and fit scattering at grazing incidence
//
//! @file Tests/Functional/PyCore/persistence/PyPersistenceTest.cpp
//! @brief Implements class PyPersistenceTest
//!
//! @homepage http://www.bornagainproject.org
//! @license GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2015
//! @authors Scientific Computing Group at MLZ Garching
//! @authors C. Durniak, M. Ganeva, G. Pospelov, W. Van Herck, J. Wuttke
//
// ************************************************************************** //
#include "PyPersistenceTest.h"
#include "BAPython.h"
#include "FileSystem.h"
#include "GISASSimulation.h"
#include "IntensityDataFunctions.h"
#include "IntensityDataIOFactory.h"
#include "PythonFormatting.h"
#include "SimulationFactory.h"
#include "Numeric.h"
#include "Utils.h"
#include <yaml-cpp/yaml.h>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <sstream>
#include <map>
#include <string>
PyPersistenceTest::PyPersistenceTest(
const std::string& directory, const std::string& name)
: IReferencedTest(name, "persistence test on script "+name, 2e-10)
, m_directory(directory)
{}
void PyPersistenceTest::runTest()
{
// Set output data filename stem, and remove old output files
std::string dat_stem = FileSystem::GetJoinPath(PYPERSIST_OUT_DIR, getName());
for (const std::string& fname: FileSystem::glob(dat_stem+".*.*")) {
std::remove( fname.c_str() );
std::cout << "Removed old output " << fname.c_str() << ".\n";
}
// Run Python script
std::string py_filename = FileSystem::GetJoinPath(m_directory, getName() + ".py");
std::string command = std::string("PYTHONPATH=") + BUILD_LIB_DIR + " " +
BORNAGAIN_PYTHON_EXE + " " + py_filename + " " + dat_stem;
std::cout << command << std::endl/*sic*/; // flush output before calling std::system
int ret = std::system(command.c_str());
if (ret!=0) {
std::cerr << "Command returned non-zero value " << ret << ".\n";
m_result = FAILED;
return;
}
// Glob simulation results
std::map<const std::string, const std::string> dat;
std::string dat_pattern = dat_stem + ".*.*";
for (const std::string& fpath: FileSystem::glob(dat_pattern)) {
std::vector<std::string> fname_segments =
Utils::String::split(FileSystem::filename(fpath), ".");
dat.insert(make_pair(fname_segments[1]+"."+fname_segments[2], fpath));
}
if (dat.size()==0) {
std::cerr << "There is no test output of form " << dat_pattern << "\n";
m_result = FAILED;
return;
}
// Glob reference files
std::string ref_stem = FileSystem::GetJoinPath(PYPERSIST_REF_DIR, getName());
std::map<const std::string, const std::string> ref;
for (const std::string& fpath: FileSystem::glob(ref_stem+".*.*")) {
std::vector<std::string> fname_segments =
Utils::String::split(FileSystem::filename(fpath), ".");
ref.insert(make_pair(fname_segments[1]+"."+fname_segments[2], fpath));
}
// Compare file lists
m_result = SUCCESS;
for (auto const& it: dat) {
if (ref.find(it.first)==ref.end()) {
std::cerr << "For test output " << it.second
<< " there is no reference file in " << PYPERSIST_REF_DIR << "\n";
m_result = FAILED;
}
}
for (auto const& it: ref) {
if (dat.find(it.first)==dat.end()) {
std::cerr << "For reference file " << it.second
<< " there is no test output in " << PYPERSIST_OUT_DIR << "\n";
m_result = FAILED;
}
}
if (m_result==FAILED)
return;
// Compare file pairs
for (auto const& it: dat)
if (!compareFilePair( it.second, ref[it.first] ) )
m_result = FAILED_DIFF;
}
//! Returns true if test output and reference file agree.
bool PyPersistenceTest::compareFilePair(
const std::string& dat_fpath, const std::string& ref_fpath) const
{
std::cout << "Comparing dat='" << dat_fpath << "' with ref='" << ref_fpath << "':\n";
const std::string extension = Utils::String::split(FileSystem::filename(dat_fpath), ".")[2];
if ( extension=="int" )
return compareIntensityPair( dat_fpath, ref_fpath );
if ( extension=="yaml" )
return compareYamlPair( dat_fpath, ref_fpath );
std::cerr << "Failed: Unsupported file type '" << extension << "'.\n";
return false;
}
//! Returns true if intensity maps from test output and reference file agree reasonably.
bool PyPersistenceTest::compareIntensityPair(
const std::string& dat_fpath, const std::string& ref_fpath) const
{
const OutputData<double>* dat = IntensityDataIOFactory::readOutputData( dat_fpath );
const OutputData<double>* ref = IntensityDataIOFactory::readOutputData( ref_fpath );
return compareIntensityMaps(*dat, *ref)==SUCCESS;
}
//! Returns true if YAML files from test output and reference agree.
bool PyPersistenceTest::compareYamlPair(
const std::string& dat_fpath, const std::string& ref_fpath) const
{
std::fstream fdat(dat_fpath);
std::fstream fref(ref_fpath);
return compareYamlNode( YAML::Load(fdat), YAML::Load(fref) );
}
//! Returns true if all entries of the two YAML files agree.
//! Floating-point entries must agree within a certain tolerance.
//! The investigation is performed recursively, iterating over all lists and maps.
bool PyPersistenceTest::compareYamlNode(const YAML::Node& dat, const YAML::Node& ref) const
{
// std::cout << "DEBUG: " << dat.as<std::string>() << " vs " << ref.as<std::string>() << "\n";
if (dat.Type() != ref.Type()) {
std::cerr << "YAML node type differs: " << dat.Type() << " vs " << ref.Type() << "\n";
return false;
}
if (dat.Type()==YAML::NodeType::Undefined)
throw std::runtime_error("Invalid node type 'Undefined' in YAML tree");
else if (dat.Type()==YAML::NodeType::Null)
throw std::runtime_error("Invalid node type 'Null' in YAML tree");
else if (dat.Type()==YAML::NodeType::Sequence) {
auto it_dat=dat.begin();
auto it_ref=ref.begin();
for (size_t idx=0; ; ++it_dat, ++it_ref, ++idx) {
if (it_dat==dat.end() || it_ref==ref.end()) {
if (!(it_dat==dat.end() && it_ref==ref.end())) {
std::cerr << "lists have different length\n";
return false;
}
return true; // regular exit
}
// now recurse into the node
if (!compareYamlNode(*it_dat, *it_ref)) {
std::cerr << "at list position " << idx << "\n";
return false;
}
}
} else if (dat.Type()==YAML::NodeType::Map) {
auto it_dat=dat.begin();
auto it_ref=ref.begin();
for ( ; ; ++it_dat, ++it_ref) {
if (it_dat==dat.end() || it_ref==ref.end()) {
if (!(it_dat==dat.end() && it_ref==ref.end())) {
std::cerr << "maps have different length\n";
return false;
}
return true; // regular exit
}
// now recurse into the node
if (!compareYamlNode(it_dat->second, it_ref->second)) {
std::cerr << "at map entry [" << it_dat->first << "]\n";
return false;
}
}
} else if (dat.Type()==YAML::NodeType::Scalar) {
if (dat.as<std::string>() == ref.as<std::string>())
return true;
try {
if (!Numeric::areAlmostEqual( dat.as<double>(), ref.as<double>(), 1e-10 )) {
std::cerr << "numbers differ: " << dat << " vs " << ref << "\n";
return false;
}
return true;
} catch(...) {
std::cerr << "scalar entries differ: '" << dat << "' vs '" << ref << "'\n";
return false;
}
} else
throw std::runtime_error("Bug in PyPersistenceTest::compareYamlNode: unexpected node type");
return true;
}