Collect together and rigorously test all our ad-hoc path functions.
parent
60f85f5a39
commit
335c217114
|
@ -33,10 +33,12 @@ set(libslvs_SOURCES
|
||||||
expr.cpp
|
expr.cpp
|
||||||
constraint.cpp
|
constraint.cpp
|
||||||
constrainteq.cpp
|
constrainteq.cpp
|
||||||
system.cpp)
|
system.cpp
|
||||||
|
platform/platform.cpp)
|
||||||
|
|
||||||
set(libslvs_HEADERS
|
set(libslvs_HEADERS
|
||||||
solvespace.h)
|
solvespace.h
|
||||||
|
platform/platform.h)
|
||||||
|
|
||||||
add_library(slvs SHARED
|
add_library(slvs SHARED
|
||||||
${libslvs_SOURCES}
|
${libslvs_SOURCES}
|
||||||
|
@ -139,6 +141,7 @@ set(solvespace_core_HEADERS
|
||||||
sketch.h
|
sketch.h
|
||||||
solvespace.h
|
solvespace.h
|
||||||
ui.h
|
ui.h
|
||||||
|
platform/platform.h
|
||||||
render/render.h
|
render/render.h
|
||||||
render/gl2shader.h
|
render/gl2shader.h
|
||||||
srf/surface.h)
|
srf/surface.h)
|
||||||
|
@ -180,6 +183,7 @@ set(solvespace_core_SOURCES
|
||||||
undoredo.cpp
|
undoredo.cpp
|
||||||
util.cpp
|
util.cpp
|
||||||
view.cpp
|
view.cpp
|
||||||
|
platform/platform.cpp
|
||||||
render/render.cpp
|
render/render.cpp
|
||||||
render/render2d.cpp
|
render/render2d.cpp
|
||||||
srf/boolean.cpp
|
srf/boolean.cpp
|
||||||
|
|
|
@ -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), '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
|
@ -141,6 +141,9 @@ enum class ContextCommand : uint32_t;
|
||||||
|
|
||||||
//================
|
//================
|
||||||
// From the platform-specific code.
|
// From the platform-specific code.
|
||||||
|
|
||||||
|
#include "platform/platform.h"
|
||||||
|
|
||||||
#if defined(WIN32)
|
#if defined(WIN32)
|
||||||
#define PATH_SEP "\\"
|
#define PATH_SEP "\\"
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -13,6 +13,7 @@ set(testsuite_SOURCES
|
||||||
harness.cpp
|
harness.cpp
|
||||||
core/expr/test.cpp
|
core/expr/test.cpp
|
||||||
core/locale/test.cpp
|
core/locale/test.cpp
|
||||||
|
core/path/test.cpp
|
||||||
constraint/points_coincident/test.cpp
|
constraint/points_coincident/test.cpp
|
||||||
constraint/pt_pt_distance/test.cpp
|
constraint/pt_pt_distance/test.cpp
|
||||||
constraint/pt_plane_distance/test.cpp
|
constraint/pt_plane_distance/test.cpp
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
|
@ -174,10 +174,25 @@ std::string Test::Helper::GetAssetPath(std::string testFile, std::string assetNa
|
||||||
return PathSepUnixToPlatform(HostRoot() + "/" + testFile + assetName);
|
return PathSepUnixToPlatform(HostRoot() + "/" + testFile + assetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Test::Helper::CheckTrue(const char *file, int line, const char *expr, bool result) {
|
bool Test::Helper::CheckBool(const char *file, int line, const char *expr, bool value,
|
||||||
if(!RecordCheck(result)) {
|
bool reference) {
|
||||||
PrintFailure(file, line,
|
if(!RecordCheck(value == reference)) {
|
||||||
ssprintf("(%s) == %s", expr, result ? "true" : "false"));
|
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;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
|
@ -188,7 +203,8 @@ bool Test::Helper::CheckEqualEpsilon(const char *file, int line, const char *val
|
||||||
double value, double reference) {
|
double value, double reference) {
|
||||||
bool result = fabs(value - reference) < LENGTH_EPS;
|
bool result = fabs(value - reference) < LENGTH_EPS;
|
||||||
if(!RecordCheck(result)) {
|
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);
|
PrintFailure(file, line, msg);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
#include "solvespace.h"
|
#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_TRUE
|
||||||
|
#undef CHECK_FALSE
|
||||||
|
|
||||||
namespace SolveSpace {
|
namespace SolveSpace {
|
||||||
namespace Test {
|
namespace Test {
|
||||||
|
@ -21,7 +22,10 @@ public:
|
||||||
std::string GetAssetPath(std::string testFile, std::string assetName,
|
std::string GetAssetPath(std::string testFile, std::string assetName,
|
||||||
std::string mangle = "");
|
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,
|
bool CheckEqualEpsilon(const char *file, int line, const char *valueExpr,
|
||||||
double value, double reference);
|
double value, double reference);
|
||||||
bool CheckLoad(const char *file, int line, const char *fixture);
|
bool CheckLoad(const char *file, int line, const char *fixture);
|
||||||
|
@ -52,7 +56,12 @@ using namespace SolveSpace;
|
||||||
static void Test_##name(Test::Helper *helper) // { ... }
|
static void Test_##name(Test::Helper *helper) // { ... }
|
||||||
|
|
||||||
#define CHECK_TRUE(cond) \
|
#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) \
|
#define CHECK_EQ_EPS(value, reference) \
|
||||||
do { if(!helper->CheckEqualEpsilon(__FILE__, __LINE__, \
|
do { if(!helper->CheckEqualEpsilon(__FILE__, __LINE__, \
|
||||||
#value, value, reference)) return; } while(0)
|
#value, value, reference)) return; } while(0)
|
||||||
|
|
Loading…
Reference in New Issue