Collect together and rigorously test all our ad-hoc path functions.

pull/216/head
whitequark 2017-03-09 14:44:32 +00:00
parent 60f85f5a39
commit 335c217114
8 changed files with 674 additions and 10 deletions

View File

@ -33,10 +33,12 @@ set(libslvs_SOURCES
expr.cpp
constraint.cpp
constrainteq.cpp
system.cpp)
system.cpp
platform/platform.cpp)
set(libslvs_HEADERS
solvespace.h)
solvespace.h
platform/platform.h)
add_library(slvs SHARED
${libslvs_SOURCES}
@ -139,6 +141,7 @@ set(solvespace_core_HEADERS
sketch.h
solvespace.h
ui.h
platform/platform.h
render/render.h
render/gl2shader.h
srf/surface.h)
@ -180,6 +183,7 @@ set(solvespace_core_SOURCES
undoredo.cpp
util.cpp
view.cpp
platform/platform.cpp
render/render.cpp
render/render2d.cpp
srf/boolean.cpp

335
src/platform/platform.cpp Normal file
View File

@ -0,0 +1,335 @@
//-----------------------------------------------------------------------------
// Common platform-dependent functionality.
//
// Copyright 2017 whitequark
//-----------------------------------------------------------------------------
#if defined(__APPLE__)
# include <CoreFoundation/CFString.h>
#endif
#include "solvespace.h"
#if defined(WIN32)
# include <windows.h>
#else
# include <unistd.h>
#endif
namespace SolveSpace {
using namespace Platform;
//-----------------------------------------------------------------------------
// Utility functions.
//-----------------------------------------------------------------------------
static std::vector<std::string> Split(const std::string &joined, char separator) {
std::vector<std::string> parts;
size_t oldpos = 0, pos = 0;
while(true) {
oldpos = pos;
pos = joined.find(separator, pos);
if(pos == std::string::npos) break;
parts.push_back(joined.substr(oldpos, pos - oldpos));
pos += 1;
}
if(oldpos != joined.length() - 1) {
parts.push_back(joined.substr(oldpos));
}
return parts;
}
static std::string Concat(const std::vector<std::string> &parts, char separator) {
std::string joined;
bool first = true;
for(auto &part : parts) {
if(!first) joined += separator;
joined += part;
first = false;
}
return joined;
}
//-----------------------------------------------------------------------------
// Path manipulation.
//-----------------------------------------------------------------------------
#if defined(WIN32)
const char SEPARATOR = '\\';
#else
const char SEPARATOR = '/';
#endif
Path Path::From(std::string raw) {
Path path = { raw };
return path;
}
Path Path::CurrentDirectory() {
#if defined(WIN32)
// On Windows, ssfopen needs an absolute UNC path proper, so get that.
std::wstring rawW;
rawW.resize(GetCurrentDirectoryW(0, NULL));
DWORD length = GetCurrentDirectoryW((int)rawW.length(), &rawW[0]);
ssassert(length > 0 && length == rawW.length() - 1, "Cannot get current directory");
rawW.resize(length);
return From(Narrow(rawW));
#else
char *raw = getcwd(NULL, 0);
ssassert(raw != NULL, "Cannot get current directory");
Path path = From(raw);
free(raw);
return path;
#endif
}
std::string Path::FileName() const {
std::string fileName = raw;
size_t slash = fileName.rfind(SEPARATOR);
if(slash != std::string::npos) {
fileName = fileName.substr(slash + 1);
}
return fileName;
}
std::string Path::FileStem() const {
std::string baseName = FileName();
size_t dot = baseName.rfind('.');
if(dot != std::string::npos) {
baseName = baseName.substr(0, dot);
}
return baseName;
}
std::string Path::Extension() const {
size_t dot = raw.rfind('.');
if(dot != std::string::npos) {
return raw.substr(dot + 1);
}
return "";
}
bool Path::HasExtension(std::string theirExt) const {
std::string ourExt = Extension();
std::transform(ourExt.begin(), ourExt.end(), ourExt.begin(), ::tolower);
std::transform(theirExt.begin(), theirExt.end(), theirExt.begin(), ::tolower);
return ourExt == theirExt;
}
Path Path::WithExtension(std::string ext) const {
Path withExt = *this;
size_t dot = withExt.raw.rfind('.');
if(dot != std::string::npos) {
withExt.raw.erase(dot);
}
withExt.raw += ".";
withExt.raw += ext;
return withExt;
}
static void FindPrefix(const std::string &raw, size_t *pos) {
*pos = std::string::npos;
#if defined(WIN32)
if(raw.size() >= 7 && raw[2] == '?' && raw[3] == '\\' &&
isalpha(raw[4]) && raw[5] == ':' && raw[6] == '\\') {
*pos = 7;
} else if(raw.size() >= 3 && isalpha(raw[0]) && raw[1] == ':' && raw[2] == '\\') {
*pos = 3;
} else if(raw.size() >= 2 && raw[0] == '\\' && raw[1] == '\\') {
size_t slashAt = raw.find('\\', 2);
if(slashAt != std::string::npos) {
*pos = raw.find('\\', slashAt + 1);
}
}
#else
if(raw.size() >= 1 && raw[0] == '/') {
*pos = 1;
}
#endif
}
bool Path::IsAbsolute() const {
size_t pos;
FindPrefix(raw, &pos);
return pos != std::string::npos;
}
// Removes one component from the end of the path.
// Returns an empty path if the path consists only of a root.
Path Path::Parent() const {
Path parent = { raw };
if(!parent.raw.empty() && parent.raw.back() == SEPARATOR) {
parent.raw.pop_back();
}
size_t slash = parent.raw.rfind(SEPARATOR);
if(slash != std::string::npos) {
parent.raw = parent.raw.substr(0, slash + 1);
} else {
parent.raw.clear();
}
if(IsAbsolute() && !parent.IsAbsolute()) {
return From("");
}
return parent;
}
// Concatenates a component to this path.
// Returns an empty path if this path or the component is empty.
Path Path::Join(const std::string &component) const {
ssassert(component.find(SEPARATOR) == std::string::npos,
"Use the Path::Join(const Path &) overload to append an entire path");
return Join(Path::From(component));
}
// Concatenates a relative path to this path.
// Returns an empty path if either path is empty, or the other path is absolute.
Path Path::Join(const Path &other) const {
if(IsEmpty() || other.IsEmpty() || other.IsAbsolute()) {
return From("");
}
Path joined = { raw };
if(joined.raw.back() != SEPARATOR) {
joined.raw += SEPARATOR;
}
joined.raw += other.raw;
return joined;
}
// Expands the "." and ".." components in this path.
// On Windows, additionally prepends the UNC prefix to absolute paths without one.
// Returns an empty path if a ".." component would escape from the root.
Path Path::Expand(bool fromCurrentDirectory) const {
Path source;
Path expanded;
if(fromCurrentDirectory && !IsAbsolute()) {
source = CurrentDirectory().Join(*this);
} else {
source = *this;
}
size_t splitAt;
FindPrefix(source.raw, &splitAt);
if(splitAt != std::string::npos) {
expanded.raw = source.raw.substr(0, splitAt);
} else {
splitAt = 0;
}
std::vector<std::string> expandedComponents;
for(std::string component : Split(source.raw.substr(splitAt), SEPARATOR)) {
if(component == ".") {
// skip
} else if(component == "..") {
if(!expandedComponents.empty()) {
expandedComponents.pop_back();
} else {
return From("");
}
} else if(!component.empty()) {
expandedComponents.push_back(component);
}
}
if(expanded.IsEmpty()) {
if(expandedComponents.empty()) {
expandedComponents.push_back(".");
}
expanded = From(Concat(expandedComponents, SEPARATOR));
} else if(!expandedComponents.empty()) {
expanded = expanded.Join(From(Concat(expandedComponents, SEPARATOR)));
}
#if defined(WIN32)
if(expanded.IsAbsolute() && expanded.raw.substr(0, 2) != "\\\\") {
expanded.raw = "\\\\?\\" + expanded.raw;
}
#endif
return expanded;
}
static std::string FilesystemNormalize(const std::string &str) {
#if defined(WIN32)
std::wstring strW = Widen(str);
std::transform(strW.begin(), strW.end(), strW.begin(), towlower);
return Narrow(strW);
#elif defined(__APPLE__)
CFMutableStringRef cfStr =
CFStringCreateMutableCopy(NULL, 0,
CFStringCreateWithBytesNoCopy(NULL, (const UInt8*)str.data(), str.size(),
kCFStringEncodingUTF8, /*isExternalRepresentation=*/false, kCFAllocatorNull));
CFStringLowercase(cfStr, NULL);
std::string normalizedStr;
normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr));
CFStringGetFileSystemRepresentation(cfStr, &normalizedStr[0], normalizedStr.size());
return normalizedStr;
#else
return str;
#endif
}
bool Path::Equals(const Path &other) const {
return FilesystemNormalize(raw) == FilesystemNormalize(other.raw);
}
// Returns a relative path from a given base path.
// Returns an empty path if any of the paths is not absolute, or
// if they belong to different roots, or
// if they cannot be expanded.
Path Path::RelativeTo(const Path &base) const {
Path expanded = Expand();
Path baseExpanded = base.Expand();
if(!(expanded.IsAbsolute() && baseExpanded.IsAbsolute())){
return From("");
}
size_t splitAt;
FindPrefix(expanded.raw, &splitAt);
size_t baseSplitAt;
FindPrefix(baseExpanded.raw, &baseSplitAt);
if(FilesystemNormalize(expanded.raw.substr(0, splitAt)) !=
FilesystemNormalize(baseExpanded.raw.substr(0, splitAt))) {
return From("");
}
std::vector<std::string> components =
Split(expanded.raw.substr(splitAt), SEPARATOR);
std::vector<std::string> baseComponents =
Split(baseExpanded.raw.substr(baseSplitAt), SEPARATOR);
size_t common;
for(common = 0; common < baseComponents.size() &&
common < components.size(); common++) {
if(FilesystemNormalize(baseComponents[common]) !=
FilesystemNormalize(components[common])) {
break;
}
}
std::vector<std::string> resultComponents;
for(size_t i = common; i < baseComponents.size(); i++) {
resultComponents.push_back("..");
}
resultComponents.insert(resultComponents.end(),
components.begin() + common, components.end());
if(resultComponents.empty()) {
resultComponents.push_back(".");
}
return From(Concat(resultComponents, SEPARATOR));
}
Path Path::FromPortable(const std::string &repr) {
return From(Concat(Split(repr, '/'), SEPARATOR));
}
std::string Path::ToPortable() const {
ssassert(!IsAbsolute(), "absolute paths cannot be made portable");
return Concat(Split(raw, SEPARATOR), '/');
}
}

51
src/platform/platform.h Normal file
View File

@ -0,0 +1,51 @@
//-----------------------------------------------------------------------------
// Common platform-dependent functionality.
//
// Copyright 2017 whitequark
//-----------------------------------------------------------------------------
#ifndef SOLVESPACE_PLATFORM_H
#define SOLVESPACE_PLATFORM_H
namespace Platform {
// A filesystem path, respecting the conventions of the current platform.
// Transformation functions return an empty path on error.
class Path {
public:
std::string raw;
static Path From(std::string raw);
static Path CurrentDirectory();
void Clear() { raw.clear(); }
bool Equals(const Path &other) const;
bool IsEmpty() const { return raw.empty(); }
bool IsAbsolute() const;
bool HasExtension(std::string ext) const;
std::string FileName() const;
std::string FileStem() const;
std::string Extension() const;
Path WithExtension(std::string ext) const;
Path Parent() const;
Path Join(const std::string &component) const;
Path Join(const Path &other) const;
Path Expand(bool fromCurrentDirectory = false) const;
Path RelativeTo(const Path &base) const;
// Converting to and from a platform-independent representation
// (conventionally, the Unix one).
static Path FromPortable(const std::string &repr);
std::string ToPortable() const;
};
struct PathLess {
bool operator()(const Path &a, const Path &b) const { return a.raw < b.raw; }
};
}
#endif

View File

@ -141,6 +141,9 @@ enum class ContextCommand : uint32_t;
//================
// From the platform-specific code.
#include "platform/platform.h"
#if defined(WIN32)
#define PATH_SEP "\\"
#else

View File

@ -13,6 +13,7 @@ set(testsuite_SOURCES
harness.cpp
core/expr/test.cpp
core/locale/test.cpp
core/path/test.cpp
constraint/points_coincident/test.cpp
constraint/pt_pt_distance/test.cpp
constraint/pt_plane_distance/test.cpp

245
test/core/path/test.cpp Normal file
View File

@ -0,0 +1,245 @@
#include "harness.h"
using Platform::Path;
#if defined(WIN32)
#define S "\\"
#define R "C:"
#define U "\\\\?\\C:"
#else
#define S "/"
#define R ""
#define U ""
#endif
TEST_CASE(from_raw) {
Path path = Path::From("/foo");
CHECK_EQ_STR(path.raw, "/foo");
}
#if defined(WIN32) || defined(__APPLE__)
TEST_CASE(equals_win32_apple) {
CHECK_TRUE(Path::From(R S "foo").Equals(Path::From(R S "foo")));
CHECK_TRUE(Path::From(R S "foo").Equals(Path::From(R S "FOO")));
CHECK_FALSE(Path::From(R S "foo").Equals(Path::From(R S "bar")));
}
#else
TEST_CASE(equals_unix) {
CHECK_TRUE(Path::From(R S "foo").Equals(Path::From(R S "foo")));
CHECK_FALSE(Path::From(R S "foo").Equals(Path::From(R S "FOO")));
CHECK_FALSE(Path::From(R S "foo").Equals(Path::From(R S "bar")));
}
#endif
#if defined(WIN32)
TEST_CASE(is_absolute_win32) {
CHECK_TRUE(Path::From("c:\\foo").IsAbsolute());
CHECK_TRUE(Path::From("\\\\?\\c:\\").IsAbsolute());
CHECK_TRUE(Path::From("\\\\server\\share\\").IsAbsolute());
CHECK_FALSE(Path::From("c:/foo").IsAbsolute());
CHECK_FALSE(Path::From("c:foo").IsAbsolute());
CHECK_FALSE(Path::From("\\\\?").IsAbsolute());
CHECK_FALSE(Path::From("\\\\server\\").IsAbsolute());
CHECK_FALSE(Path::From("\\\\?\\c:").IsAbsolute());
CHECK_FALSE(Path::From("\\\\server\\share").IsAbsolute());
CHECK_FALSE(Path::From("foo").IsAbsolute());
CHECK_FALSE(Path::From("/foo").IsAbsolute());
}
#else
TEST_CASE(is_absolute_unix) {
CHECK_TRUE(Path::From("/foo").IsAbsolute());
CHECK_FALSE(Path::From("c:/foo").IsAbsolute());
CHECK_FALSE(Path::From("c:\\foo").IsAbsolute());
CHECK_FALSE(Path::From("\\\\?\\foo").IsAbsolute());
CHECK_FALSE(Path::From("c:foo").IsAbsolute());
CHECK_FALSE(Path::From("foo").IsAbsolute());
}
#endif
TEST_CASE(has_extension) {
CHECK_TRUE(Path::From("foo.bar").HasExtension("bar"));
CHECK_TRUE(Path::From("foo.bar").HasExtension("BAR"));
CHECK_TRUE(Path::From("foo.bAr").HasExtension("BaR"));
CHECK_TRUE(Path::From("foo.bar").HasExtension("bar"));
CHECK_FALSE(Path::From("foo.bar").HasExtension("baz"));
}
TEST_CASE(file_name) {
CHECK_EQ_STR(Path::From("foo").FileName(), "foo");
CHECK_EQ_STR(Path::From("foo" S "bar").FileName(), "bar");
}
TEST_CASE(file_stem) {
CHECK_EQ_STR(Path::From("foo").FileStem(), "foo");
CHECK_EQ_STR(Path::From("foo" S "bar").FileStem(), "bar");
CHECK_EQ_STR(Path::From("foo.ext").FileStem(), "foo");
CHECK_EQ_STR(Path::From("foo" S "bar.ext").FileStem(), "bar");
}
TEST_CASE(extension) {
CHECK_EQ_STR(Path::From("foo").Extension(), "");
CHECK_EQ_STR(Path::From("foo.bar").Extension(), "bar");
CHECK_EQ_STR(Path::From("foo.bar.baz").Extension(), "baz");
}
TEST_CASE(with_extension) {
CHECK_EQ_STR(Path::From("foo.bar").WithExtension("baz").raw, "foo.baz");
CHECK_EQ_STR(Path::From("foo").WithExtension("baz").raw, "foo.baz");
}
TEST_CASE(parent) {
Path path;
path = Path::From("foo" S "bar");
CHECK_EQ_STR(path.Parent().raw, "foo" S);
path = Path::From("foo" S "bar" S);
CHECK_EQ_STR(path.Parent().raw, "foo" S);
path = Path::From(R S "foo" S "bar");
CHECK_EQ_STR(path.Parent().raw, R S "foo" S);
path = Path::From(R S "foo");
CHECK_EQ_STR(path.Parent().raw, R S);
path = Path::From("");
CHECK_TRUE(path.Parent().IsEmpty());
path = Path::From("foo");
CHECK_TRUE(path.Parent().IsEmpty());
path = Path::From("foo" S);
CHECK_TRUE(path.Parent().IsEmpty());
path = Path::From(R S);
CHECK_TRUE(path.Parent().IsEmpty());
}
#if defined(WIN32)
TEST_CASE(parent_win32) {
Path path;
path = Path::From(U S);
CHECK_TRUE(path.Parent().IsEmpty());
}
#endif
TEST_CASE(join) {
Path path;
path = Path::From("foo");
CHECK_EQ_STR(path.Join(Path::From("bar")).raw, "foo" S "bar");
path = Path::From("foo" S);
CHECK_EQ_STR(path.Join(Path::From("bar")).raw, "foo" S "bar");
path = Path::From("");
CHECK_TRUE(path.Join(Path::From("bar")).IsEmpty());
path = Path::From("foo");
CHECK_TRUE(path.Join(Path::From("")).IsEmpty());
path = Path::From("foo");
CHECK_TRUE(path.Join(Path::From(R S "bar")).IsEmpty());
}
TEST_CASE(expand) {
Path path;
path = Path::From("foo");
CHECK_EQ_STR(path.Expand().raw, "foo");
path = Path::From("foo" S "bar");
CHECK_EQ_STR(path.Expand().raw, "foo" S "bar");
path = Path::From("foo" S);
CHECK_EQ_STR(path.Expand().raw, "foo");
path = Path::From("foo" S ".");
CHECK_EQ_STR(path.Expand().raw, "foo");
path = Path::From("foo" S "." S "bar");
CHECK_EQ_STR(path.Expand().raw, "foo" S "bar");
path = Path::From("foo" S ".." S "bar");
CHECK_EQ_STR(path.Expand().raw, "bar");
path = Path::From("foo" S "..");
CHECK_EQ_STR(path.Expand().raw, ".");
path = Path::From(R S "foo" S "..");
CHECK_EQ_STR(path.Expand().raw, U S);
path = Path::From(R S);
CHECK_EQ_STR(path.Expand().raw, U S);
path = Path::From(R S "..");
CHECK_TRUE(path.Expand().IsEmpty());
path = Path::From(R S ".." S "foo");
CHECK_TRUE(path.Expand().IsEmpty());
path = Path::From("..");
CHECK_TRUE(path.Expand().IsEmpty());
}
#if defined(WIN32)
TEST_CASE(expand_win32) {
Path path;
path = Path::From(R S "foo");
CHECK_EQ_STR(path.Expand().raw, U S "foo");
path = Path::From(U S "foo");
CHECK_EQ_STR(path.Expand().raw, U S "foo");
}
#endif
TEST_CASE(expand_from_cwd) {
Path cwd = Path::CurrentDirectory().Expand();
Path path;
path = Path::From(R S "foo");
CHECK_EQ_STR(path.Expand(/*fromCurrentDirectory=*/true).raw,
U S "foo");
path = Path::From("foo" S "bar");
CHECK_EQ_STR(path.Expand(/*fromCurrentDirectory=*/true).raw,
cwd.raw + S "foo" S "bar");
path = Path::From(".." S "bar");
CHECK_EQ_STR(path.Expand(/*fromCurrentDirectory=*/true).raw,
cwd.Parent().raw + "bar");
}
TEST_CASE(relative_to) {
Path base;
base = Path::From(R S "foo" S "bar");
CHECK_EQ_STR(Path::From(R S "foo").RelativeTo(base).raw,
"..");
base = Path::From(R S "foo" S "bar");
CHECK_EQ_STR(Path::From(R S "foo" S "baz").RelativeTo(base).raw,
".." S "baz");
base = Path::From(R S "foo" S "bar");
CHECK_EQ_STR(Path::From(R S "foo" S "bar" S "quux").RelativeTo(base).raw,
"quux");
base = Path::From(R S "foo" S "bar");
CHECK_EQ_STR(Path::From(R S "foo" S "bar").RelativeTo(base).raw,
".");
base = Path::From("foo");
CHECK_TRUE(Path::From(R S "foo" S "bar").RelativeTo(base).IsEmpty());
base = Path::From(R S "foo" S "bar");
CHECK_TRUE(Path::From("foo").RelativeTo(base).IsEmpty());
}
#if defined(WIN32)
TEST_CASE(relative_to_win32) {
Path base;
base = Path::From("C:\\foo");
CHECK_EQ_STR(Path::From("\\\\?\\C:\\bar").RelativeTo(base).raw,
"..\\bar");
base = Path::From("C:\\foo");
CHECK_EQ_STR(Path::From("c:\\FOO").RelativeTo(base).raw,
".");
base = Path::From("C:\\foo");
CHECK_TRUE(Path::From("D:\\bar").RelativeTo(base).IsEmpty());
CHECK_TRUE(Path::From("\\\\server\\share\\bar").RelativeTo(base).IsEmpty());
}
#elif defined(__APPLE__)
TEST_CASE(relative_to_apple) {
Path base;
base = Path::From("/users/foo");
CHECK_EQ_STR(Path::From("/Users/FOO").RelativeTo(base).raw,
".");
}
#else
TEST_CASE(relative_to_unix) {
Path base;
base = Path::From("/users/foo");
CHECK_EQ_STR(Path::From("/Users/FOO").RelativeTo(base).raw,
"../../Users/FOO");
}
#endif
TEST_CASE(from_portable) {
CHECK_EQ_STR(Path::FromPortable("foo/bar").raw, "foo" S "bar");
}
TEST_CASE(to_portable) {
CHECK_EQ_STR(Path::From("foo" S "bar").ToPortable(), "foo/bar");
}

View File

@ -174,10 +174,25 @@ std::string Test::Helper::GetAssetPath(std::string testFile, std::string assetNa
return PathSepUnixToPlatform(HostRoot() + "/" + testFile + assetName);
}
bool Test::Helper::CheckTrue(const char *file, int line, const char *expr, bool result) {
if(!RecordCheck(result)) {
PrintFailure(file, line,
ssprintf("(%s) == %s", expr, result ? "true" : "false"));
bool Test::Helper::CheckBool(const char *file, int line, const char *expr, bool value,
bool reference) {
if(!RecordCheck(value == reference)) {
std::string msg = ssprintf("(%s) = %s ≠ %s", expr,
value ? "true" : "false",
reference ? "true" : "false");
PrintFailure(file, line, msg);
return false;
} else {
return true;
}
}
bool Test::Helper::CheckEqualString(const char *file, int line, const char *valueExpr,
const std::string &value, const std::string &reference) {
if(!RecordCheck(value == reference)) {
std::string msg = ssprintf("(%s) = \"%s\"\"%s\"", valueExpr,
value.c_str(), reference.c_str());
PrintFailure(file, line, msg);
return false;
} else {
return true;
@ -188,7 +203,8 @@ bool Test::Helper::CheckEqualEpsilon(const char *file, int line, const char *val
double value, double reference) {
bool result = fabs(value - reference) < LENGTH_EPS;
if(!RecordCheck(result)) {
std::string msg = ssprintf("(%s) = %.4g ≉ %.4g", valueExpr, value, reference);
std::string msg = ssprintf("(%s) = %.4g ≉ %.4g", valueExpr,
value, reference);
PrintFailure(file, line, msg);
return false;
} else {

View File

@ -5,8 +5,9 @@
//-----------------------------------------------------------------------------
#include "solvespace.h"
// Hack... we should rename the one in ui.h instead.
// Hack... we should rename the ones in ui.h instead.
#undef CHECK_TRUE
#undef CHECK_FALSE
namespace SolveSpace {
namespace Test {
@ -21,7 +22,10 @@ public:
std::string GetAssetPath(std::string testFile, std::string assetName,
std::string mangle = "");
bool CheckTrue(const char *file, int line, const char *expr, bool result);
bool CheckBool(const char *file, int line, const char *expr,
bool value, bool reference);
bool CheckEqualString(const char *file, int line, const char *valueExpr,
const std::string &value, const std::string &reference);
bool CheckEqualEpsilon(const char *file, int line, const char *valueExpr,
double value, double reference);
bool CheckLoad(const char *file, int line, const char *fixture);
@ -52,7 +56,12 @@ using namespace SolveSpace;
static void Test_##name(Test::Helper *helper) // { ... }
#define CHECK_TRUE(cond) \
do { if(!helper->CheckTrue(__FILE__, __LINE__, #cond, cond)) return; } while(0)
do { if(!helper->CheckBool(__FILE__, __LINE__, #cond, cond, true)) return; } while(0)
#define CHECK_FALSE(cond) \
do { if(!helper->CheckBool(__FILE__, __LINE__, #cond, cond, false)) return; } while(0)
#define CHECK_EQ_STR(value, reference) \
do { if(!helper->CheckEqualString(__FILE__, __LINE__, \
#value, value, reference)) return; } while(0)
#define CHECK_EQ_EPS(value, reference) \
do { if(!helper->CheckEqualEpsilon(__FILE__, __LINE__, \
#value, value, reference)) return; } while(0)