Add a simple harness for automated, headless testing.
This commit alters the build system substantially; it adds another platform, `headless`, that provides stubs in place of all GUI functions, and provides a library `solvespace_headless` alongside the main executable. To cut down build times, only the few files that have #if defined(HEADLESS) are built twice for the executable and the library; the rest is grouped into a new `solvespace_cad` library. It is not usable on its own and just serves for grouping. This commit also gates the tests behind a -DENABLE_TESTS=ON CMake option, ON by default (but suggested as OFF in the README so that people don't ever have to install cairo to build the executable.) The tests introduced in this commit are (so far) rudimentary, although functional, and they serve as a stepping point towards introducing coverage analysis.pull/33/head
parent
977a0b8e6d
commit
5e63d8301e
|
@ -1,22 +1,10 @@
|
|||
# .gitattributes for SolveSpace
|
||||
|
||||
# Set default behaviour, in case users don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files we want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.cpp text
|
||||
*.h text
|
||||
*.txt text
|
||||
|
||||
# Declare files that will always have CRLF line endings on checkout.
|
||||
*.sln text eol=crlf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.gz binary
|
||||
*.ico binary
|
||||
*.jpg binary
|
||||
*.lib binary
|
||||
*.png binary
|
||||
|
||||
# end .gitattributes
|
||||
*.slvs binary
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/CMakeCache.txt
|
||||
/build*/
|
||||
/test/**/*.diff.*
|
||||
/test/**/*.curr.*
|
||||
*.trace # OpenGL apitrace files
|
||||
/debian/tmp/
|
||||
/debian/*.log
|
||||
|
|
|
@ -2,5 +2,7 @@
|
|||
|
||||
if echo $TRAVIS_TAG | grep ^v; then BUILD_TYPE=RelWithDebInfo; else BUILD_TYPE=Debug; fi
|
||||
|
||||
export BUILD_TYPE
|
||||
dpkg-buildpackage -b -us -uc
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_C_COMPILER=gcc-5 -DCMAKE_CXX_COMPILER=g++-5 -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
make VERBOSE=1
|
||||
|
|
|
@ -27,6 +27,8 @@ set(solvespace_VERSION_MAJOR 3)
|
|||
set(solvespace_VERSION_MINOR 0)
|
||||
string(SUBSTRING "${GIT_COMMIT_HASH}" 0 8 solvespace_GIT_HASH)
|
||||
|
||||
set(ENABLE_TESTS ON CACHE BOOL "Whether the test suite will be built and run")
|
||||
|
||||
if(NOT WIN32 AND NOT APPLE)
|
||||
set(GUI gtk2 CACHE STRING "GUI toolkit to use (one of: gtk2 gtk3)")
|
||||
endif()
|
||||
|
@ -93,6 +95,7 @@ if(WIN32)
|
|||
PNG_PNG_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/libpng)
|
||||
list(APPEND PNG_PNG_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/libpng)
|
||||
|
||||
if(ENABLE_TESTS)
|
||||
message(STATUS "Using in-tree pixman")
|
||||
add_vendored_subdirectory(extlib/pixman)
|
||||
set(PIXMAN_FOUND YES)
|
||||
|
@ -103,9 +106,10 @@ if(WIN32)
|
|||
message(STATUS "Using in-tree cairo")
|
||||
add_vendored_subdirectory(extlib/cairo)
|
||||
set(CAIRO_FOUND YES)
|
||||
set(CAIRO_LIBRARY cairo)
|
||||
set(CAIRO_LIBRARIES cairo)
|
||||
set(CAIRO_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/cairo/src)
|
||||
list(APPEND CAIRO_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/extlib/cairo/src)
|
||||
endif()
|
||||
|
||||
if(NOT MINGW)
|
||||
message(STATUS "Using prebuilt SpaceWare")
|
||||
|
@ -122,10 +126,15 @@ elseif(APPLE)
|
|||
find_package(PNG REQUIRED)
|
||||
find_package(Freetype REQUIRED)
|
||||
|
||||
find_library(APPKIT_LIBRARY AppKit REQUIRED)
|
||||
find_library(CAIRO_LIBRARY cairo REQUIRED)
|
||||
if(ENABLE_TESTS)
|
||||
find_library(CAIRO_LIBRARIES cairo REQUIRED)
|
||||
find_path(CAIRO_INCLUDE_DIRS cairo.h PATH_SUFFIXES cairo)
|
||||
endif()
|
||||
|
||||
find_library(APPKIT_LIBRARY AppKit REQUIRED)
|
||||
else() # Linux and compatible systems
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
find_package(Backtrace)
|
||||
find_package(SpaceWare)
|
||||
|
||||
|
@ -133,12 +142,13 @@ else() # Linux and compatible systems
|
|||
find_package(PNG REQUIRED)
|
||||
find_package(Freetype REQUIRED)
|
||||
|
||||
# Use freedesktop's pkg-config to locate everything else.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
if(ENABLE_TESTS)
|
||||
pkg_check_modules(CAIRO REQUIRED cairo)
|
||||
endif()
|
||||
|
||||
pkg_check_modules(FONTCONFIG REQUIRED fontconfig)
|
||||
pkg_check_modules(JSONC REQUIRED json-c)
|
||||
pkg_check_modules(FREETYPE REQUIRED freetype2)
|
||||
pkg_check_modules(CAIRO REQUIRED cairo)
|
||||
|
||||
set(HAVE_GTK TRUE)
|
||||
if(GUI STREQUAL "gtk3")
|
||||
|
@ -192,15 +202,18 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang
|
|||
set(WARNING_FLAGS "${WARNING_FLAGS} -Werror=switch")
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")
|
||||
|
||||
if(WIN32)
|
||||
set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -l0")
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")
|
||||
|
||||
# components
|
||||
|
||||
add_subdirectory(res)
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(exposed)
|
||||
if(ENABLE_TESTS)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
|
27
README.md
27
README.md
|
@ -26,12 +26,12 @@ Building on Linux
|
|||
### Building for Linux
|
||||
|
||||
You will need CMake, libpng, zlib, json-c, fontconfig, freetype, gtkmm 2.4,
|
||||
pangomm 1.4, OpenGL and OpenGL GLU.
|
||||
pangomm 1.4, OpenGL and OpenGL GLU. To build tests, you will need cairo.
|
||||
On a Debian derivative (e.g. Ubuntu) these can be installed with:
|
||||
|
||||
apt-get install libpng-dev libjson-c-dev libfreetype6-dev \
|
||||
libfontconfig1-dev libgtkmm-2.4-dev libpangomm-1.4-dev \
|
||||
libgl-dev libglu-dev cmake
|
||||
libcairo2-dev libgl-dev libglu-dev cmake
|
||||
|
||||
Before building, check out the necessary submodules:
|
||||
|
||||
|
@ -41,7 +41,7 @@ After that, build SolveSpace as following:
|
|||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake .. -DENABLE_TESTS=OFF
|
||||
make
|
||||
sudo make install
|
||||
|
||||
|
@ -63,15 +63,17 @@ After that, build 32-bit SolveSpace as following:
|
|||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw32.cmake ..
|
||||
make solvespace
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw32.cmake \
|
||||
-DENABLE_TESTS=OFF
|
||||
make
|
||||
|
||||
Or, build 64-bit SolveSpace as following:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake ..
|
||||
make solvespace
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake \
|
||||
-DENABLE_TESTS=OFF
|
||||
make
|
||||
|
||||
The application is built as `build/src/solvespace.exe`.
|
||||
|
||||
|
@ -80,10 +82,11 @@ Space Navigator support will not be available.
|
|||
Building on Mac OS X
|
||||
--------------------
|
||||
|
||||
You will need XCode tools, CMake, libpng and Freetype. Assuming you use
|
||||
You will need XCode tools, CMake, libpng and Freetype. To build tests, you
|
||||
will need cairo. Assuming you use
|
||||
[homebrew][], these can be installed with:
|
||||
|
||||
brew install cmake libpng freetype
|
||||
brew install cmake libpng freetype cairo
|
||||
|
||||
XCode has to be installed via AppStore; it requires a free Apple ID.
|
||||
|
||||
|
@ -95,7 +98,7 @@ After that, build SolveSpace as following:
|
|||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake .. -DENABLE_TESTS=OFF
|
||||
make
|
||||
|
||||
The app bundle is built in `build/src/solvespace.app`.
|
||||
|
@ -123,7 +126,7 @@ Visual Studio install. Then, run the following in cmd or PowerShell:
|
|||
git submodule update --init
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G "NMake Makefiles"
|
||||
cmake .. -G "NMake Makefiles" -DENABLE_TESTS=OFF
|
||||
nmake
|
||||
|
||||
### MSVC build
|
||||
|
@ -137,7 +140,7 @@ in bash:
|
|||
git submodule update --init
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake .. -DENABLE_TESTS=OFF
|
||||
make
|
||||
|
||||
[cmakewin]: http://www.cmake.org/download/#latest
|
||||
|
|
|
@ -6,9 +6,12 @@ before_build:
|
|||
- cd build
|
||||
- set tag=x%APPVEYOR_REPO_TAG_NAME%
|
||||
- if %tag:~,2% == xv (set BUILD_TYPE=RelWithDebInfo) else (set BUILD_TYPE=Debug)
|
||||
- cmake -G"Visual Studio 12" -T v120_xp -DCMAKE_BUILD_TYPE=%BUILD_TYPE% ..
|
||||
- cmake -G"Visual Studio 12" -T v120_xp ..
|
||||
build_script:
|
||||
- msbuild "src\solvespace.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
- msbuild "test\solvespace_testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
test_script:
|
||||
- test\%BUILD_TYPE%\solvespace_testsuite.exe
|
||||
artifacts:
|
||||
- path: build\src\Debug\solvespace.exe
|
||||
name: solvespace.exe
|
||||
|
|
|
@ -72,7 +72,8 @@ endif()
|
|||
|
||||
if(WIN32)
|
||||
set(platform_SOURCES
|
||||
platform/w32main.cpp)
|
||||
platform/w32main.cpp
|
||||
render/rendergl1.cpp)
|
||||
|
||||
set(platform_LIBRARIES
|
||||
comctl32
|
||||
|
@ -83,7 +84,8 @@ elseif(APPLE)
|
|||
|
||||
set(platform_SOURCES
|
||||
platform/cocoamain.mm
|
||||
render/rendergl.cpp)
|
||||
render/rendergl.cpp
|
||||
render/rendergl1.cpp)
|
||||
|
||||
set(platform_BUNDLED_LIBS
|
||||
${PNG_LIBRARIES}
|
||||
|
@ -94,10 +96,10 @@ elseif(APPLE)
|
|||
elseif(HAVE_GTK)
|
||||
set(platform_SOURCES
|
||||
platform/gtkmain.cpp
|
||||
render/rendergl.cpp)
|
||||
render/rendergl.cpp
|
||||
render/rendergl1.cpp)
|
||||
|
||||
set(platform_LIBRARIES
|
||||
${Backtrace_LIBRARIES}
|
||||
${SPACEWARE_LIBRARIES})
|
||||
|
||||
foreach(pkg_config_lib GTKMM JSONC FONTCONFIG)
|
||||
|
@ -107,9 +109,9 @@ elseif(HAVE_GTK)
|
|||
endforeach()
|
||||
endif()
|
||||
|
||||
# solvespace executable
|
||||
# solvespace library
|
||||
|
||||
set(solvespace_HEADERS
|
||||
set(solvespace_cad_HEADERS
|
||||
config.h
|
||||
dsc.h
|
||||
expr.h
|
||||
|
@ -120,18 +122,15 @@ set(solvespace_HEADERS
|
|||
render/render.h
|
||||
srf/surface.h)
|
||||
|
||||
set(solvespace_SOURCES
|
||||
set(solvespace_cad_SOURCES
|
||||
bsp.cpp
|
||||
clipboard.cpp
|
||||
confscreen.cpp
|
||||
constraint.cpp
|
||||
constrainteq.cpp
|
||||
describescreen.cpp
|
||||
draw.cpp
|
||||
drawconstraint.cpp
|
||||
drawentity.cpp
|
||||
entity.cpp
|
||||
export.cpp
|
||||
exportstep.cpp
|
||||
exportvector.cpp
|
||||
expr.cpp
|
||||
|
@ -147,20 +146,16 @@ set(solvespace_SOURCES
|
|||
polygon.cpp
|
||||
resource.cpp
|
||||
request.cpp
|
||||
solvespace.cpp
|
||||
style.cpp
|
||||
system.cpp
|
||||
textscreens.cpp
|
||||
textwin.cpp
|
||||
toolbar.cpp
|
||||
ttf.cpp
|
||||
undoredo.cpp
|
||||
util.cpp
|
||||
view.cpp
|
||||
render/render.cpp
|
||||
render/rendergl1.cpp
|
||||
render/render2d.cpp
|
||||
render/rendercairo.cpp
|
||||
srf/boolean.cpp
|
||||
srf/curve.cpp
|
||||
srf/merge.cpp
|
||||
|
@ -170,25 +165,38 @@ set(solvespace_SOURCES
|
|||
srf/surfinter.cpp
|
||||
srf/triangulate.cpp)
|
||||
|
||||
add_executable(solvespace WIN32 MACOSX_BUNDLE
|
||||
${libslvs_HEADERS}
|
||||
${libslvs_SOURCES}
|
||||
set(solvespace_cad_gl_SOURCES
|
||||
confscreen.cpp
|
||||
draw.cpp
|
||||
export.cpp
|
||||
solvespace.cpp
|
||||
textwin.cpp)
|
||||
|
||||
add_library(solvespace_cad STATIC
|
||||
${util_SOURCES}
|
||||
${solvespace_cad_HEADERS}
|
||||
${solvespace_cad_SOURCES})
|
||||
|
||||
target_link_libraries(solvespace_cad
|
||||
dxfrw
|
||||
${ZLIB_LIBRARY}
|
||||
${PNG_LIBRARY}
|
||||
${FREETYPE_LIBRARY}
|
||||
${Backtrace_LIBRARIES})
|
||||
|
||||
# solvespace gui executable
|
||||
|
||||
add_executable(solvespace WIN32 MACOSX_BUNDLE
|
||||
${solvespace_cad_gl_SOURCES}
|
||||
${platform_SOURCES}
|
||||
${solvespace_HEADERS}
|
||||
${solvespace_SOURCES}
|
||||
$<TARGET_PROPERTY:resources,EXTRA_SOURCES>)
|
||||
|
||||
add_dependencies(solvespace
|
||||
resources)
|
||||
|
||||
target_link_libraries(solvespace
|
||||
dxfrw
|
||||
solvespace_cad
|
||||
${OPENGL_LIBRARIES}
|
||||
${ZLIB_LIBRARY}
|
||||
${PNG_LIBRARY}
|
||||
${FREETYPE_LIBRARY}
|
||||
${CAIRO_LIBRARY}
|
||||
${platform_LIBRARIES})
|
||||
|
||||
if(WIN32 AND NOT MINGW)
|
||||
|
@ -229,21 +237,22 @@ if(NOT WIN32)
|
|||
BUNDLE DESTINATION .)
|
||||
endif()
|
||||
|
||||
# valgrind
|
||||
# solvespace headless library
|
||||
|
||||
add_custom_target(solvespace-valgrind
|
||||
valgrind
|
||||
--tool=memcheck
|
||||
--verbose
|
||||
--track-fds=yes
|
||||
--log-file=vg.%p.out
|
||||
--num-callers=50
|
||||
--error-limit=no
|
||||
--read-var-info=yes
|
||||
--leak-check=full
|
||||
--leak-resolution=high
|
||||
--show-reachable=yes
|
||||
--track-origins=yes
|
||||
--malloc-fill=0xac
|
||||
--free-fill=0xde
|
||||
$<TARGET_FILE:solvespace>)
|
||||
set(headless_SOURCES
|
||||
platform/headless.cpp
|
||||
render/rendercairo.cpp)
|
||||
|
||||
add_library(solvespace_headless STATIC EXCLUDE_FROM_ALL
|
||||
${solvespace_cad_gl_SOURCES}
|
||||
${headless_SOURCES})
|
||||
|
||||
target_compile_definitions(solvespace_headless
|
||||
PRIVATE -DHEADLESS)
|
||||
|
||||
target_include_directories(solvespace_headless
|
||||
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(solvespace_headless
|
||||
solvespace_cad
|
||||
${CAIRO_LIBRARIES})
|
||||
|
|
|
@ -302,12 +302,14 @@ void TextWindow::ShowConfiguration() {
|
|||
Printf(false, "%Ba %d %Fl%Ll%f[change]%E",
|
||||
SS.autosaveInterval, &ScreenChangeAutosaveInterval);
|
||||
|
||||
#if !defined(HEADLESS)
|
||||
const char *gl_vendor, *gl_renderer, *gl_version;
|
||||
OpenGl1Renderer::GetIdent(&gl_vendor, &gl_renderer, &gl_version);
|
||||
Printf(false, "");
|
||||
Printf(false, " %Ftgl vendor %E%s", gl_vendor);
|
||||
Printf(false, " %Ft renderer %E%s", gl_renderer);
|
||||
Printf(false, " %Ft version %E%s", gl_version);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool TextWindow::EditControlDoneForConfiguration(const char *s) {
|
||||
|
|
|
@ -710,6 +710,7 @@ void GraphicsWindow::Draw(Canvas *canvas) {
|
|||
}
|
||||
|
||||
void GraphicsWindow::Paint() {
|
||||
#if !defined(HEADLESS)
|
||||
havePainted = true;
|
||||
|
||||
auto renderStartTime = std::chrono::high_resolution_clock::now();
|
||||
|
@ -786,4 +787,5 @@ void GraphicsWindow::Paint() {
|
|||
}
|
||||
|
||||
canvas.EndFrame();
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -1086,11 +1086,12 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename,
|
|||
// rendering the view in the usual way and then copying the pixels.
|
||||
//-----------------------------------------------------------------------------
|
||||
void SolveSpaceUI::ExportAsPngTo(const std::string &filename) {
|
||||
#if !defined(HEADLESS)
|
||||
// No guarantee that the back buffer contains anything valid right now,
|
||||
// so repaint the scene. And hide the toolbar too.
|
||||
bool prevShowToolbar = SS.showToolbar;
|
||||
SS.showToolbar = false;
|
||||
#ifndef WIN32
|
||||
#if !defined(WIN32)
|
||||
GlOffscreen offscreen;
|
||||
offscreen.Render((int)SS.GW.width, (int)SS.GW.height, [&] {
|
||||
SS.GW.Paint();
|
||||
|
@ -1109,10 +1110,11 @@ void SolveSpaceUI::ExportAsPngTo(const std::string &filename) {
|
|||
}
|
||||
if(f) fclose(f);
|
||||
|
||||
#ifndef WIN32
|
||||
#if !defined(WIN32)
|
||||
offscreen.Clear();
|
||||
#endif
|
||||
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
32
src/file.cpp
32
src/file.cpp
|
@ -734,34 +734,6 @@ static void PathSepNormalize(std::string &filename)
|
|||
}
|
||||
}
|
||||
|
||||
static std::string PathSepPlatformToUNIX(const std::string &filename)
|
||||
{
|
||||
#if defined(WIN32)
|
||||
std::string result = filename;
|
||||
for(size_t i = 0; i < result.length(); i++) {
|
||||
if(result[i] == '\\')
|
||||
result[i] = '/';
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
return filename;
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::string PathSepUNIXToPlatform(const std::string &filename)
|
||||
{
|
||||
#if defined(WIN32)
|
||||
std::string result = filename;
|
||||
for(size_t i = 0; i < result.length(); i++) {
|
||||
if(result[i] == '/')
|
||||
result[i] = '\\';
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
return filename;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SolveSpaceUI::ReloadAllImported(bool canCancel)
|
||||
{
|
||||
std::map<std::string, std::string> linkMap;
|
||||
|
@ -791,7 +763,7 @@ bool SolveSpaceUI::ReloadAllImported(bool canCancel)
|
|||
|
||||
// In a newly created group we only have an absolute path.
|
||||
if(!g->linkFileRel.empty()) {
|
||||
std::string rel = PathSepUNIXToPlatform(g->linkFileRel);
|
||||
std::string rel = PathSepUnixToPlatform(g->linkFileRel);
|
||||
std::string fromRel = MakePathAbsolute(SS.saveFile, rel);
|
||||
FILE *test = ssfopen(fromRel, "rb");
|
||||
if(test) {
|
||||
|
@ -812,7 +784,7 @@ try_load_file:
|
|||
// Record the linked file's name relative to our filename;
|
||||
// if the entire tree moves, then everything will still work
|
||||
std::string rel = MakePathRelative(SS.saveFile, g->linkFile);
|
||||
g->linkFileRel = PathSepPlatformToUNIX(rel);
|
||||
g->linkFileRel = PathSepPlatformToUnix(rel);
|
||||
} else {
|
||||
// We're not yet saved, so can't make it absolute.
|
||||
// This will only be used for display purposes, as SS.saveFile
|
||||
|
|
|
@ -0,0 +1,273 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Our main() function for the headless (no OpenGL) test runner.
|
||||
//
|
||||
// Copyright 2016 whitequark
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "solvespace.h"
|
||||
#include <cairo.h>
|
||||
|
||||
namespace SolveSpace {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Settings
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class Setting {
|
||||
public:
|
||||
enum class Type {
|
||||
Undefined,
|
||||
Int,
|
||||
Float,
|
||||
String
|
||||
};
|
||||
|
||||
Type type;
|
||||
int valueInt;
|
||||
float valueFloat;
|
||||
std::string valueString;
|
||||
|
||||
void CheckType(Type expectedType) {
|
||||
ssassert(type == Setting::Type::Undefined ||
|
||||
type == expectedType, "Wrong setting type");
|
||||
type = expectedType;
|
||||
}
|
||||
};
|
||||
|
||||
std::map<std::string, Setting> settings;
|
||||
|
||||
void CnfFreezeInt(uint32_t val, const std::string &key) {
|
||||
Setting &setting = settings[key];
|
||||
setting.CheckType(Setting::Type::Int);
|
||||
setting.valueInt = val;
|
||||
}
|
||||
uint32_t CnfThawInt(uint32_t val, const std::string &key) {
|
||||
if(settings.find(key) != settings.end()) {
|
||||
Setting &setting = settings[key];
|
||||
setting.CheckType(Setting::Type::Int);
|
||||
val = setting.valueInt;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void CnfFreezeFloat(float val, const std::string &key) {
|
||||
Setting &setting = settings[key];
|
||||
setting.CheckType(Setting::Type::Float);
|
||||
setting.valueFloat = val;
|
||||
}
|
||||
float CnfThawFloat(float val, const std::string &key) {
|
||||
if(settings.find(key) != settings.end()) {
|
||||
Setting &setting = settings[key];
|
||||
setting.CheckType(Setting::Type::Float);
|
||||
val = setting.valueFloat;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void CnfFreezeString(const std::string &val, const std::string &key) {
|
||||
Setting &setting = settings[key];
|
||||
setting.CheckType(Setting::Type::String);
|
||||
setting.valueString = val;
|
||||
}
|
||||
std::string CnfThawString(const std::string &val, const std::string &key) {
|
||||
std::string ret = val;
|
||||
if(settings.find(key) != settings.end()) {
|
||||
Setting &setting = settings[key];
|
||||
setting.CheckType(Setting::Type::String);
|
||||
ret = setting.valueString;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Timers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SetTimerFor(int milliseconds) {
|
||||
}
|
||||
void SetAutosaveTimerFor(int minutes) {
|
||||
}
|
||||
void ScheduleLater() {
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Graphics window
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void GetGraphicsWindowSize(int *w, int *h) {
|
||||
*w = *h = 600;
|
||||
}
|
||||
|
||||
void InvalidateGraphics() {
|
||||
}
|
||||
|
||||
std::shared_ptr<Pixmap> framebuffer;
|
||||
bool antialias = true;
|
||||
void PaintGraphics() {
|
||||
const Camera &camera = SS.GW.GetCamera();
|
||||
|
||||
std::shared_ptr<Pixmap> pixmap = std::make_shared<Pixmap>();
|
||||
pixmap->format = Pixmap::Format::BGRA;
|
||||
pixmap->width = camera.width;
|
||||
pixmap->height = camera.height;
|
||||
pixmap->stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, camera.width);
|
||||
pixmap->data = std::vector<uint8_t>(pixmap->stride * pixmap->height);
|
||||
cairo_surface_t *surface =
|
||||
cairo_image_surface_create_for_data(&pixmap->data[0], CAIRO_FORMAT_RGB24,
|
||||
pixmap->width, pixmap->height, pixmap->stride);
|
||||
cairo_t *context = cairo_create(surface);
|
||||
|
||||
CairoRenderer canvas;
|
||||
canvas.camera = camera;
|
||||
canvas.lighting = SS.GW.GetLighting();
|
||||
canvas.chordTolerance = SS.chordTol;
|
||||
canvas.context = context;
|
||||
canvas.antialias = antialias;
|
||||
|
||||
SS.GW.Draw(&canvas);
|
||||
canvas.CullOccludedStrokes();
|
||||
canvas.OutputInPaintOrder();
|
||||
|
||||
pixmap->ConvertTo(Pixmap::Format::RGBA);
|
||||
framebuffer = pixmap;
|
||||
|
||||
canvas.Clear();
|
||||
|
||||
cairo_surface_destroy(surface);
|
||||
cairo_destroy(context);
|
||||
}
|
||||
|
||||
void SetCurrentFilename(const std::string &filename) {
|
||||
}
|
||||
void ToggleFullScreen() {
|
||||
}
|
||||
bool FullScreenIsActive() {
|
||||
return false;
|
||||
}
|
||||
void ShowGraphicsEditControl(int x, int y, int fontHeight, int minWidthChars,
|
||||
const std::string &val) {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
void HideGraphicsEditControl() {
|
||||
}
|
||||
bool GraphicsEditControlIsVisible() {
|
||||
return false;
|
||||
}
|
||||
void ToggleMenuBar() {
|
||||
}
|
||||
bool MenuBarIsVisible() {
|
||||
return false;
|
||||
}
|
||||
void AddContextMenuItem(const char *label, ContextCommand cmd) {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
void CreateContextSubmenu() {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
ContextCommand ShowContextMenu() {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
void EnableMenuByCmd(Command cmd, bool enabled) {
|
||||
}
|
||||
void CheckMenuByCmd(Command cmd, bool checked) {
|
||||
}
|
||||
void RadioMenuByCmd(Command cmd, bool selected) {
|
||||
}
|
||||
void RefreshRecentMenus() {
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Text window
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void ShowTextWindow(bool visible) {
|
||||
}
|
||||
void GetTextWindowSize(int *w, int *h) {
|
||||
*w = *h = 100;
|
||||
}
|
||||
void InvalidateText() {
|
||||
}
|
||||
void MoveTextScrollbarTo(int pos, int maxPos, int page) {
|
||||
}
|
||||
void SetMousePointerToHand(bool is_hand) {
|
||||
}
|
||||
void ShowTextEditControl(int x, int y, const std::string &val) {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
void HideTextEditControl() {
|
||||
}
|
||||
bool TextEditControlIsVisible() {
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Dialogs
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool GetOpenFile(std::string *filename, const std::string &activeOrEmpty,
|
||||
const FileFilter filters[]) {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
bool GetSaveFile(std::string *filename, const std::string &activeOrEmpty,
|
||||
const FileFilter filters[]) {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
DialogChoice SaveFileYesNoCancel() {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
DialogChoice LoadAutosaveYesNo() {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
DialogChoice LocateImportedFileYesNoCancel(const std::string &filename,
|
||||
bool canCancel) {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
void DoMessageBox(const char *message, int rows, int cols, bool error) {
|
||||
dbp("%s box: %s", error ? "error" : "message", message);
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
void OpenWebsite(const char *url) {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Resources
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
std::vector<std::string> GetFontFiles() {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string resourceDir;
|
||||
const void *LoadResource(const std::string &name, size_t *size) {
|
||||
static std::map<std::string, std::vector<uint8_t>> cache;
|
||||
|
||||
auto it = cache.find(name);
|
||||
if(it == cache.end()) {
|
||||
std::string path = resourceDir + "/" + name;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
FILE *f = ssfopen(PathSepUnixToPlatform(path).c_str(), "rb");
|
||||
ssassert(f != NULL, "Cannot open resource");
|
||||
fseek(f, 0, SEEK_END);
|
||||
data.resize(ftell(f));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fread(&data[0], 1, data.size(), f);
|
||||
fclose(f);
|
||||
|
||||
cache.emplace(name, std::move(data));
|
||||
it = cache.find(name);
|
||||
}
|
||||
|
||||
*size = (*it).second.size();
|
||||
return &(*it).second[0];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Application lifecycle
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void ExitNow() {
|
||||
ssassert(false, "Not implemented");
|
||||
}
|
||||
|
||||
}
|
|
@ -65,6 +65,16 @@ bool PathEqual(const std::string &a, const std::string &b)
|
|||
#endif
|
||||
}
|
||||
|
||||
std::string PathSepPlatformToUnix(const std::string &filename)
|
||||
{
|
||||
return filename;
|
||||
}
|
||||
|
||||
std::string PathSepUnixToPlatform(const std::string &filename)
|
||||
{
|
||||
return filename;
|
||||
}
|
||||
|
||||
FILE *ssfopen(const std::string &filename, const char *mode)
|
||||
{
|
||||
ssassert(filename.length() == strlen(filename.c_str()),
|
||||
|
|
|
@ -97,6 +97,26 @@ bool PathEqual(const std::string &a, const std::string &b)
|
|||
|
||||
}
|
||||
|
||||
std::string PathSepPlatformToUnix(const std::string &filename)
|
||||
{
|
||||
std::string result = filename;
|
||||
for(size_t i = 0; i < result.length(); i++) {
|
||||
if(result[i] == '\\')
|
||||
result[i] = '/';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string PathSepUnixToPlatform(const std::string &filename)
|
||||
{
|
||||
std::string result = filename;
|
||||
for(size_t i = 0; i < result.length(); i++) {
|
||||
if(result[i] == '/')
|
||||
result[i] = '\\';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
FILE *ssfopen(const std::string &filename, const char *mode)
|
||||
{
|
||||
// Prepend \\?\ UNC prefix unless already an UNC path.
|
||||
|
|
|
@ -277,6 +277,8 @@ public:
|
|||
class CairoRenderer : public SurfaceRenderer {
|
||||
public:
|
||||
cairo_t *context;
|
||||
// Renderer configuration.
|
||||
bool antialias;
|
||||
// Renderer state.
|
||||
struct {
|
||||
hStroke hcs;
|
||||
|
|
|
@ -44,7 +44,11 @@ void CairoRenderer::SelectStroke(hStroke hcs) {
|
|||
cairo_set_dash(context, dashes.data(), dashes.size(), 0);
|
||||
cairo_set_source_rgba(context, color.redF(), color.greenF(), color.blueF(),
|
||||
color.alphaF());
|
||||
cairo_set_antialias(context, CAIRO_ANTIALIAS_BEST);
|
||||
if(antialias) {
|
||||
cairo_set_antialias(context, CAIRO_ANTIALIAS_GRAY);
|
||||
} else {
|
||||
cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
void CairoRenderer::MoveTo(Vector p) {
|
||||
|
|
|
@ -95,6 +95,42 @@ RgbaColor Pixmap::GetPixel(size_t x, size_t y) const {
|
|||
ssassert(false, "Unexpected resource format");
|
||||
}
|
||||
|
||||
void Pixmap::SetPixel(size_t x, size_t y, RgbaColor color) {
|
||||
uint8_t *pixel = &data[y * stride + x * GetBytesPerPixel()];
|
||||
|
||||
switch(format) {
|
||||
case Format::RGBA:
|
||||
pixel[0] = color.red;
|
||||
pixel[1] = color.green;
|
||||
pixel[2] = color.blue;
|
||||
pixel[3] = color.alpha;
|
||||
break;
|
||||
|
||||
case Format::RGB:
|
||||
pixel[0] = color.red;
|
||||
pixel[1] = color.green;
|
||||
pixel[2] = color.blue;
|
||||
break;
|
||||
|
||||
case Format::BGRA:
|
||||
pixel[0] = color.blue;
|
||||
pixel[1] = color.green;
|
||||
pixel[2] = color.red;
|
||||
pixel[3] = color.alpha;
|
||||
break;
|
||||
|
||||
case Format::BGR:
|
||||
pixel[0] = color.blue;
|
||||
pixel[1] = color.green;
|
||||
pixel[2] = color.red;
|
||||
break;
|
||||
|
||||
case Format::A:
|
||||
pixel[0] = color.alpha;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Pixmap::ConvertTo(Format newFormat) {
|
||||
switch(format) {
|
||||
case Format::RGBA:
|
||||
|
@ -215,6 +251,14 @@ exit:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Pixmap> Pixmap::ReadPng(const std::string &filename, bool flip) {
|
||||
FILE *f = ssfopen(filename.c_str(), "rb");
|
||||
if(!f) return NULL;
|
||||
std::shared_ptr<Pixmap> pixmap = ReadPng(f, flip);
|
||||
fclose(f);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
bool Pixmap::WritePng(FILE *f, bool flip) {
|
||||
int colorType;
|
||||
bool bgr;
|
||||
|
@ -262,6 +306,29 @@ exit:
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Pixmap::WritePng(const std::string &filename, bool flip) {
|
||||
FILE *f = ssfopen(filename.c_str(), "wb");
|
||||
if(!f) return false;
|
||||
bool success = WritePng(f, flip);
|
||||
fclose(f);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Pixmap::Equals(const Pixmap &other) const {
|
||||
if(format != other.format || width != other.width || height != other.height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t rowLength = width * GetBytesPerPixel();
|
||||
for(size_t y = 0; y < height; y++) {
|
||||
if(memcmp(&data[y * stride], &other.data[y * other.stride], rowLength)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Pixmap> Pixmap::Create(Format format, size_t width, size_t height) {
|
||||
std::shared_ptr<Pixmap> pixmap = std::make_shared<Pixmap>();
|
||||
pixmap->format = format;
|
||||
|
|
|
@ -36,12 +36,16 @@ public:
|
|||
static std::shared_ptr<Pixmap> FromPng(const uint8_t *data, size_t size, bool flip = false);
|
||||
|
||||
static std::shared_ptr<Pixmap> ReadPng(FILE *f, bool flip = false);
|
||||
static std::shared_ptr<Pixmap> ReadPng(const std::string &filename, bool flip = false);
|
||||
bool WritePng(FILE *f, bool flip = false);
|
||||
bool WritePng(const std::string &filename, bool flip = false);
|
||||
|
||||
size_t GetBytesPerPixel() const;
|
||||
RgbaColor GetPixel(size_t x, size_t y) const;
|
||||
bool Equals(const Pixmap &other) const;
|
||||
|
||||
void ConvertTo(Format newFormat);
|
||||
void SetPixel(size_t x, size_t y, RgbaColor color);
|
||||
};
|
||||
|
||||
class BitmapFont {
|
||||
|
|
|
@ -13,8 +13,10 @@ Sketch SolveSpace::SK = {};
|
|||
std::string SolveSpace::RecentFile[MAX_RECENT] = {};
|
||||
|
||||
void SolveSpaceUI::Init() {
|
||||
#if !defined(HEADLESS)
|
||||
// Check that the resource system works.
|
||||
dbp("%s", LoadString("banner.txt").data());
|
||||
#endif
|
||||
|
||||
SS.tangentArcRadius = 10.0;
|
||||
|
||||
|
|
|
@ -150,6 +150,8 @@ enum class ContextCommand : uint32_t;
|
|||
extern const bool FLIP_FRAMEBUFFER;
|
||||
|
||||
bool PathEqual(const std::string &a, const std::string &b);
|
||||
std::string PathSepPlatformToUnix(const std::string &filename);
|
||||
std::string PathSepUnixToPlatform(const std::string &filename);
|
||||
FILE *ssfopen(const std::string &filename, const char *mode);
|
||||
void ssremove(const std::string &filename);
|
||||
|
||||
|
|
|
@ -718,6 +718,7 @@ bool TextWindow::DrawOrHitTestColorPicker(UiCanvas *uiCanvas, DrawOrHitHow how,
|
|||
}
|
||||
|
||||
void TextWindow::Paint() {
|
||||
#if !defined(HEADLESS)
|
||||
int width, height;
|
||||
GetTextWindowSize(&width, &height);
|
||||
|
||||
|
@ -857,6 +858,7 @@ void TextWindow::Paint() {
|
|||
DrawOrHitTestColorPicker(&uiCanvas, PAINT, false, 0, 0);
|
||||
|
||||
canvas.EndFrame();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(testsuite_SOURCES
|
||||
harness.cpp
|
||||
request/line_segment/test.cpp)
|
||||
|
||||
add_executable(solvespace_testsuite
|
||||
${testsuite_SOURCES})
|
||||
|
||||
target_link_libraries(solvespace_testsuite
|
||||
solvespace_headless)
|
||||
|
||||
add_custom_target(solvespace-test ALL
|
||||
COMMAND $<TARGET_FILE:solvespace_testsuite>
|
||||
COMMENT "Testing SolveSpace"
|
||||
VERBATIM)
|
|
@ -0,0 +1,320 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Our harness for running test cases, and reusable checks.
|
||||
//
|
||||
// Copyright 2016 whitequark
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "harness.h"
|
||||
#include <regex>
|
||||
#if defined(WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace SolveSpace {
|
||||
// These are defined in headless.cpp, and aren't exposed in solvespace.h.
|
||||
extern std::string resourceDir;
|
||||
extern bool antialias;
|
||||
extern std::shared_ptr<Pixmap> framebuffer;
|
||||
}
|
||||
|
||||
// The paths in __FILE__ are from the build system, but defined(WIN32) returns
|
||||
// the value for the host system.
|
||||
#define BUILD_PATH_SEP (__FILE__[0]=='/' ? '/' : '\\')
|
||||
#define HOST_PATH_SEP PATH_SEP
|
||||
|
||||
static std::string BuildRoot() {
|
||||
static std::string rootDir;
|
||||
if(!rootDir.empty()) return rootDir;
|
||||
|
||||
rootDir = __FILE__;
|
||||
rootDir.erase(rootDir.rfind(BUILD_PATH_SEP) + 1);
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
static std::string HostRoot() {
|
||||
static std::string rootDir;
|
||||
if(!rootDir.empty()) return rootDir;
|
||||
|
||||
// No especially good way to do this, so let's assume somewhere up from
|
||||
// the current directory there's our repository, with CMakeLists.txt, and
|
||||
// pivot from there.
|
||||
#if defined(WIN32)
|
||||
wchar_t currentDirW[MAX_PATH];
|
||||
GetCurrentDirectoryW(MAX_PATH, currentDirW);
|
||||
rootDir = Narrow(currentDirW);
|
||||
#else
|
||||
rootDir = ".";
|
||||
#endif
|
||||
|
||||
// We're never more than four levels deep.
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
std::string listsPath = rootDir;
|
||||
listsPath += HOST_PATH_SEP;
|
||||
listsPath += "CMakeLists.txt";
|
||||
FILE *f = ssfopen(listsPath, "r");
|
||||
if(f) {
|
||||
fclose(f);
|
||||
rootDir += HOST_PATH_SEP;
|
||||
rootDir += "test";
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
if(rootDir[0] == '.') {
|
||||
rootDir += HOST_PATH_SEP;
|
||||
rootDir += "..";
|
||||
} else {
|
||||
rootDir.erase(rootDir.rfind(HOST_PATH_SEP));
|
||||
}
|
||||
}
|
||||
|
||||
ssassert(false, "Couldn't locate repository root");
|
||||
}
|
||||
|
||||
enum class Color {
|
||||
Red,
|
||||
Green,
|
||||
DarkGreen
|
||||
};
|
||||
|
||||
static std::string Colorize(Color color, std::string input) {
|
||||
#if !defined(WIN32)
|
||||
if(isatty(fileno(stdout))) {
|
||||
switch(color) {
|
||||
case Color::Red:
|
||||
return "\e[1;31m" + input + "\e[0m";
|
||||
case Color::Green:
|
||||
return "\e[1;32m" + input + "\e[0m";
|
||||
case Color::DarkGreen:
|
||||
return "\e[36m" + input + "\e[0m";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return input;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> ReadFile(std::string path) {
|
||||
std::vector<uint8_t> data;
|
||||
FILE *f = ssfopen(path.c_str(), "rb");
|
||||
if(f) {
|
||||
fseek(f, 0, SEEK_END);
|
||||
data.resize(ftell(f));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fread(&data[0], 1, data.size(), f);
|
||||
fclose(f);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
bool Test::Helper::RecordCheck(bool success) {
|
||||
checkCount++;
|
||||
if(!success) failCount++;
|
||||
return success;
|
||||
}
|
||||
|
||||
void Test::Helper::PrintFailure(const char *file, int line, std::string msg) {
|
||||
std::string shortFile = file;
|
||||
shortFile.erase(0, BuildRoot().size());
|
||||
fprintf(stderr, "test%c%s:%d: FAILED: %s\n",
|
||||
BUILD_PATH_SEP, shortFile.c_str(), line, msg.c_str());
|
||||
}
|
||||
|
||||
std::string Test::Helper::GetAssetPath(std::string testFile, std::string assetName,
|
||||
std::string mangle) {
|
||||
if(!mangle.empty()) {
|
||||
assetName.insert(assetName.rfind('.'), "." + mangle);
|
||||
}
|
||||
testFile.erase(0, BuildRoot().size());
|
||||
testFile.erase(testFile.rfind(BUILD_PATH_SEP) + 1);
|
||||
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"));
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Test::Helper::CheckLoad(const char *file, int line, const char *fixture) {
|
||||
std::string fixturePath = GetAssetPath(file, fixture);
|
||||
|
||||
FILE *f = ssfopen(fixturePath.c_str(), "rb");
|
||||
bool fixtureExists = (f != NULL);
|
||||
if(f) fclose(f);
|
||||
|
||||
bool result = fixtureExists &&
|
||||
SS.LoadFromFile(fixturePath) && SS.ReloadAllImported(/*canCancel=*/false);
|
||||
if(!RecordCheck(result)) {
|
||||
PrintFailure(file, line,
|
||||
ssprintf("loading file '%s'", fixturePath.c_str()));
|
||||
return false;
|
||||
} else {
|
||||
SS.AfterNewFile();
|
||||
SS.GW.offset = {};
|
||||
SS.GW.scale = 10.0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Test::Helper::CheckSave(const char *file, int line, const char *reference) {
|
||||
std::string refPath = GetAssetPath(file, reference),
|
||||
outPath = GetAssetPath(file, reference, "out");
|
||||
if(!RecordCheck(SS.SaveToFile(outPath))) {
|
||||
PrintFailure(file, line,
|
||||
ssprintf("saving file '%s'", refPath.c_str()));
|
||||
return false;
|
||||
} else {
|
||||
std::vector<uint8_t> refData = ReadFile(refPath),
|
||||
outData = ReadFile(outPath);
|
||||
if(!RecordCheck(refData == outData)) {
|
||||
PrintFailure(file, line, "savefile doesn't match reference");
|
||||
return false;
|
||||
}
|
||||
|
||||
ssremove(outPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Test::Helper::CheckRender(const char *file, int line, const char *reference) {
|
||||
PaintGraphics();
|
||||
|
||||
std::string refPath = GetAssetPath(file, reference),
|
||||
outPath = GetAssetPath(file, reference, "out"),
|
||||
diffPath = GetAssetPath(file, reference, "diff");
|
||||
|
||||
std::shared_ptr<Pixmap> refPixmap = Pixmap::ReadPng(refPath.c_str(), /*flip=*/true);
|
||||
if(!RecordCheck(refPixmap && refPixmap->Equals(*framebuffer))) {
|
||||
framebuffer->WritePng(outPath.c_str(), /*flip=*/true);
|
||||
|
||||
if(!refPixmap) {
|
||||
PrintFailure(file, line, "reference render not present");
|
||||
return false;
|
||||
}
|
||||
|
||||
ssassert(refPixmap->format == framebuffer->format, "Expected buffer formats to match");
|
||||
if(refPixmap->width != framebuffer->width ||
|
||||
refPixmap->height != framebuffer->height) {
|
||||
PrintFailure(file, line, "render doesn't match reference; dimensions differ");
|
||||
} else {
|
||||
std::shared_ptr<Pixmap> diffPixmap =
|
||||
Pixmap::Create(refPixmap->format, refPixmap->width, refPixmap->height);
|
||||
|
||||
int diffPixelCount = 0;
|
||||
for(size_t j = 0; j < refPixmap->height; j++) {
|
||||
for(size_t i = 0; i < refPixmap->width; i++) {
|
||||
if(!refPixmap->GetPixel(i, j).Equals(framebuffer->GetPixel(i, j))) {
|
||||
diffPixelCount++;
|
||||
diffPixmap->SetPixel(i, j, RgbaColor::From(255, 0, 0, 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diffPixmap->WritePng(diffPath.c_str(), /*flip=*/true);
|
||||
std::string message =
|
||||
ssprintf("render doesn't match reference; %d (%.2f%%) pixels differ",
|
||||
diffPixelCount,
|
||||
100.0 * diffPixelCount / (refPixmap->width * refPixmap->height));
|
||||
PrintFailure(file, line, message);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
ssremove(outPath);
|
||||
ssremove(diffPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid global constructors; using a global static vector instead of a local one
|
||||
// breaks MinGW for some obscure reason.
|
||||
static std::vector<Test::Case> *testCasesPtr;
|
||||
int Test::Case::Register(Test::Case testCase) {
|
||||
static std::vector<Test::Case> testCases;
|
||||
testCases.push_back(testCase);
|
||||
testCasesPtr = &testCases;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#if defined(WIN32)
|
||||
_set_abort_behavior(0, _WRITE_ABORT_MSG);
|
||||
InitHeaps();
|
||||
#endif
|
||||
|
||||
std::regex filter(".*");
|
||||
if(argc == 1) {
|
||||
} else if(argc == 2) {
|
||||
filter = argv[1];
|
||||
} else {
|
||||
fprintf(stderr, "Usage: %s [test filter regex]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
resourceDir = HostRoot();
|
||||
resourceDir.erase(resourceDir.rfind(HOST_PATH_SEP) + 1);
|
||||
resourceDir += "res";
|
||||
|
||||
// Different Cairo versions have different antialiasing algorithms.
|
||||
antialias = false;
|
||||
|
||||
// Wreck order dependencies between tests!
|
||||
std::random_shuffle(testCasesPtr->begin(), testCasesPtr->end());
|
||||
|
||||
auto testStartTime = std::chrono::steady_clock::now();
|
||||
size_t ranTally = 0, skippedTally = 0, checkTally = 0, failTally = 0;
|
||||
for(Test::Case &testCase : *testCasesPtr) {
|
||||
std::string testCaseName = testCase.fileName;
|
||||
testCaseName.erase(0, BuildRoot().size());
|
||||
testCaseName.erase(testCaseName.rfind(BUILD_PATH_SEP));
|
||||
testCaseName += BUILD_PATH_SEP + testCase.caseName;
|
||||
|
||||
std::smatch filterMatch;
|
||||
if(!std::regex_search(testCaseName, filterMatch, filter)) {
|
||||
skippedTally += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
SS.Init();
|
||||
|
||||
Test::Helper helper = {};
|
||||
testCase.fn(&helper);
|
||||
|
||||
SK.Clear();
|
||||
SS.Clear();
|
||||
|
||||
ranTally += 1;
|
||||
checkTally += helper.checkCount;
|
||||
failTally += helper.failCount;
|
||||
if(helper.checkCount == 0) {
|
||||
fprintf(stderr, " %s test %s (empty)\n",
|
||||
Colorize(Color::Red, "??").c_str(),
|
||||
Colorize(Color::DarkGreen, testCaseName).c_str());
|
||||
} else if(helper.failCount > 0) {
|
||||
fprintf(stderr, " %s test %s\n",
|
||||
Colorize(Color::Red, "NG").c_str(),
|
||||
Colorize(Color::DarkGreen, testCaseName).c_str());
|
||||
} else {
|
||||
fprintf(stderr, " %s test %s\n",
|
||||
Colorize(Color::Green, "OK").c_str(),
|
||||
Colorize(Color::DarkGreen, testCaseName).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
auto testEndTime = std::chrono::steady_clock::now();
|
||||
std::chrono::duration<double> testTime = testEndTime - testStartTime;
|
||||
|
||||
if(failTally > 0) {
|
||||
fprintf(stderr, "Failure! %u checks failed\n",
|
||||
(unsigned)failTally);
|
||||
return 1;
|
||||
} else {
|
||||
fprintf(stderr, "Success! %u test cases (%u skipped), %u checks, %.3fs\n",
|
||||
(unsigned)ranTally, (unsigned)skippedTally,
|
||||
(unsigned)checkTally, testTime.count());
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Our harness for running test cases, and reusable checks.
|
||||
//
|
||||
// Copyright 2016 whitequark
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "solvespace.h"
|
||||
|
||||
// Hack... we should rename the one in ui.h instead.
|
||||
#undef CHECK_TRUE
|
||||
|
||||
namespace SolveSpace {
|
||||
namespace Test {
|
||||
|
||||
class Helper {
|
||||
public:
|
||||
size_t checkCount;
|
||||
size_t failCount;
|
||||
|
||||
bool RecordCheck(bool success);
|
||||
void PrintFailure(const char *file, int line, std::string msg);
|
||||
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 CheckLoad(const char *file, int line, const char *fixture);
|
||||
bool CheckSave(const char *file, int line, const char *reference);
|
||||
bool CheckRender(const char *file, int line, const char *fixture);
|
||||
};
|
||||
|
||||
class Case {
|
||||
public:
|
||||
std::string fileName;
|
||||
std::string caseName;
|
||||
std::function<void(Helper *)> fn;
|
||||
|
||||
static int Register(Case testCase);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
using namespace SolveSpace;
|
||||
|
||||
#define TEST_CASE(name) \
|
||||
static void Test_##name(Test::Helper *); \
|
||||
static Test::Case TestCase_##name = { __FILE__, #name, Test_##name }; \
|
||||
static int TestReg_##name = Test::Case::Register(TestCase_##name); \
|
||||
static void Test_##name(Test::Helper *helper) // { ... }
|
||||
|
||||
#define CHECK_TRUE(cond) \
|
||||
do { if(!helper->CheckTrue(__FILE__, __LINE__, #cond, cond)) return; } while(0)
|
||||
#define CHECK_LOAD(fixture) \
|
||||
do { if(!helper->CheckLoad(__FILE__, __LINE__, fixture)) return; } while(0)
|
||||
#define CHECK_SAVE(fixture) \
|
||||
do { if(!helper->CheckSave(__FILE__, __LINE__, fixture)) return; } while(0)
|
||||
#define CHECK_RENDER(reference) \
|
||||
do { if(!helper->CheckRender(__FILE__, __LINE__, reference)) return; } while(0)
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,278 @@
|
|||
±²³SolveSpaceREVa
|
||||
|
||||
|
||||
Group.h.v=00000001
|
||||
Group.type=5000
|
||||
Group.name=#references
|
||||
Group.skipFirst=0
|
||||
Group.predef.swapUV=0
|
||||
Group.predef.negateU=0
|
||||
Group.predef.negateV=0
|
||||
Group.visible=1
|
||||
Group.suppress=0
|
||||
Group.relaxConstraints=0
|
||||
Group.allDimsReference=0
|
||||
Group.scale=1.00000000000000000000
|
||||
Group.remap={
|
||||
}
|
||||
Group.impFile=
|
||||
Group.impFileRel=
|
||||
AddGroup
|
||||
|
||||
Group.h.v=00000002
|
||||
Group.type=5001
|
||||
Group.order=1
|
||||
Group.name=sketch-in-plane
|
||||
Group.activeWorkplane.v=80020000
|
||||
Group.subtype=6000
|
||||
Group.skipFirst=0
|
||||
Group.predef.q.w=1.00000000000000000000
|
||||
Group.predef.origin.v=00010001
|
||||
Group.predef.swapUV=0
|
||||
Group.predef.negateU=0
|
||||
Group.predef.negateV=0
|
||||
Group.visible=1
|
||||
Group.suppress=0
|
||||
Group.relaxConstraints=0
|
||||
Group.allDimsReference=0
|
||||
Group.scale=1.00000000000000000000
|
||||
Group.remap={
|
||||
}
|
||||
Group.impFile=
|
||||
Group.impFileRel=
|
||||
AddGroup
|
||||
|
||||
Param.h.v.=00010010
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010011
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010012
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010020
|
||||
Param.val=1.00000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010021
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010022
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010023
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020010
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020011
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020012
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020020
|
||||
Param.val=0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020021
|
||||
Param.val=0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020022
|
||||
Param.val=0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020023
|
||||
Param.val=0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030010
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030011
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030012
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030020
|
||||
Param.val=0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030021
|
||||
Param.val=-0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030022
|
||||
Param.val=-0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030023
|
||||
Param.val=-0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00040010
|
||||
Param.val=-5.00000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00040011
|
||||
Param.val=5.00000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00040013
|
||||
Param.val=5.00000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00040014
|
||||
Param.val=5.00000000000000000000
|
||||
AddParam
|
||||
|
||||
Request.h.v=00000001
|
||||
Request.type=100
|
||||
Request.group.v=00000001
|
||||
Request.construction=0
|
||||
AddRequest
|
||||
|
||||
Request.h.v=00000002
|
||||
Request.type=100
|
||||
Request.group.v=00000001
|
||||
Request.construction=0
|
||||
AddRequest
|
||||
|
||||
Request.h.v=00000003
|
||||
Request.type=100
|
||||
Request.group.v=00000001
|
||||
Request.construction=0
|
||||
AddRequest
|
||||
|
||||
Request.h.v=00000004
|
||||
Request.type=200
|
||||
Request.workplane.v=80020000
|
||||
Request.group.v=00000002
|
||||
Request.construction=0
|
||||
AddRequest
|
||||
|
||||
Entity.h.v=00010000
|
||||
Entity.type=10000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00010001
|
||||
Entity.normal.v=00010020
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00010001
|
||||
Entity.type=2000
|
||||
Entity.construction=0
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00010020
|
||||
Entity.type=3000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00010001
|
||||
Entity.actNormal.w=1.00000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00020000
|
||||
Entity.type=10000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00020001
|
||||
Entity.normal.v=00020020
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00020001
|
||||
Entity.type=2000
|
||||
Entity.construction=0
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00020020
|
||||
Entity.type=3000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00020001
|
||||
Entity.actNormal.w=0.50000000000000000000
|
||||
Entity.actNormal.vx=0.50000000000000000000
|
||||
Entity.actNormal.vy=0.50000000000000000000
|
||||
Entity.actNormal.vz=0.50000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00030000
|
||||
Entity.type=10000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00030001
|
||||
Entity.normal.v=00030020
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00030001
|
||||
Entity.type=2000
|
||||
Entity.construction=0
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00030020
|
||||
Entity.type=3000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00030001
|
||||
Entity.actNormal.w=0.50000000000000000000
|
||||
Entity.actNormal.vx=-0.50000000000000000000
|
||||
Entity.actNormal.vy=-0.50000000000000000000
|
||||
Entity.actNormal.vz=-0.50000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00040000
|
||||
Entity.type=11000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00040001
|
||||
Entity.point[1].v=00040002
|
||||
Entity.workplane.v=80020000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00040001
|
||||
Entity.type=2001
|
||||
Entity.construction=0
|
||||
Entity.workplane.v=80020000
|
||||
Entity.actPoint.x=-5.00000000000000000000
|
||||
Entity.actPoint.y=5.00000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00040002
|
||||
Entity.type=2001
|
||||
Entity.construction=0
|
||||
Entity.workplane.v=80020000
|
||||
Entity.actPoint.x=5.00000000000000000000
|
||||
Entity.actPoint.y=5.00000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=80020000
|
||||
Entity.type=10000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=80020002
|
||||
Entity.normal.v=80020001
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=80020001
|
||||
Entity.type=3010
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=80020002
|
||||
Entity.actNormal.w=1.00000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=80020002
|
||||
Entity.type=2012
|
||||
Entity.construction=0
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
±²³SolveSpaceREVa
|
||||
|
||||
|
||||
Group.h.v=00000001
|
||||
Group.type=5000
|
||||
Group.name=#references
|
||||
Group.color=ff000000
|
||||
Group.skipFirst=0
|
||||
Group.predef.swapUV=0
|
||||
Group.predef.negateU=0
|
||||
Group.predef.negateV=0
|
||||
Group.visible=1
|
||||
Group.suppress=0
|
||||
Group.relaxConstraints=0
|
||||
Group.allowRedundant=0
|
||||
Group.allDimsReference=0
|
||||
Group.scale=1.00000000000000000000
|
||||
Group.remap={
|
||||
}
|
||||
AddGroup
|
||||
|
||||
Group.h.v=00000002
|
||||
Group.type=5001
|
||||
Group.order=1
|
||||
Group.name=sketch-in-plane
|
||||
Group.activeWorkplane.v=80020000
|
||||
Group.color=ff000000
|
||||
Group.subtype=6000
|
||||
Group.skipFirst=0
|
||||
Group.predef.q.w=1.00000000000000000000
|
||||
Group.predef.origin.v=00010001
|
||||
Group.predef.swapUV=0
|
||||
Group.predef.negateU=0
|
||||
Group.predef.negateV=0
|
||||
Group.visible=1
|
||||
Group.suppress=0
|
||||
Group.relaxConstraints=0
|
||||
Group.allowRedundant=0
|
||||
Group.allDimsReference=0
|
||||
Group.scale=1.00000000000000000000
|
||||
Group.remap={
|
||||
}
|
||||
AddGroup
|
||||
|
||||
Param.h.v.=00010010
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010011
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010012
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010020
|
||||
Param.val=1.00000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010021
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010022
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00010023
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020010
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020011
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020012
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020020
|
||||
Param.val=0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020021
|
||||
Param.val=0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020022
|
||||
Param.val=0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00020023
|
||||
Param.val=0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030010
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030011
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030012
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030020
|
||||
Param.val=0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030021
|
||||
Param.val=-0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030022
|
||||
Param.val=-0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00030023
|
||||
Param.val=-0.50000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00040010
|
||||
Param.val=-5.00000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00040011
|
||||
Param.val=5.00000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00040013
|
||||
Param.val=5.00000000000000000000
|
||||
AddParam
|
||||
|
||||
Param.h.v.=00040014
|
||||
Param.val=5.00000000000000000000
|
||||
AddParam
|
||||
|
||||
Request.h.v=00000001
|
||||
Request.type=100
|
||||
Request.group.v=00000001
|
||||
Request.construction=0
|
||||
AddRequest
|
||||
|
||||
Request.h.v=00000002
|
||||
Request.type=100
|
||||
Request.group.v=00000001
|
||||
Request.construction=0
|
||||
AddRequest
|
||||
|
||||
Request.h.v=00000003
|
||||
Request.type=100
|
||||
Request.group.v=00000001
|
||||
Request.construction=0
|
||||
AddRequest
|
||||
|
||||
Request.h.v=00000004
|
||||
Request.type=200
|
||||
Request.workplane.v=80020000
|
||||
Request.group.v=00000002
|
||||
Request.construction=0
|
||||
AddRequest
|
||||
|
||||
Entity.h.v=00010000
|
||||
Entity.type=10000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00010001
|
||||
Entity.normal.v=00010020
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00010001
|
||||
Entity.type=2000
|
||||
Entity.construction=0
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00010020
|
||||
Entity.type=3000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00010001
|
||||
Entity.actNormal.w=1.00000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00020000
|
||||
Entity.type=10000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00020001
|
||||
Entity.normal.v=00020020
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00020001
|
||||
Entity.type=2000
|
||||
Entity.construction=0
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00020020
|
||||
Entity.type=3000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00020001
|
||||
Entity.actNormal.w=0.50000000000000000000
|
||||
Entity.actNormal.vx=0.50000000000000000000
|
||||
Entity.actNormal.vy=0.50000000000000000000
|
||||
Entity.actNormal.vz=0.50000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00030000
|
||||
Entity.type=10000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00030001
|
||||
Entity.normal.v=00030020
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00030001
|
||||
Entity.type=2000
|
||||
Entity.construction=0
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00030020
|
||||
Entity.type=3000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00030001
|
||||
Entity.actNormal.w=0.50000000000000000000
|
||||
Entity.actNormal.vx=-0.50000000000000000000
|
||||
Entity.actNormal.vy=-0.50000000000000000000
|
||||
Entity.actNormal.vz=-0.50000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00040000
|
||||
Entity.type=11000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=00040001
|
||||
Entity.point[1].v=00040002
|
||||
Entity.workplane.v=80020000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00040001
|
||||
Entity.type=2001
|
||||
Entity.construction=0
|
||||
Entity.workplane.v=80020000
|
||||
Entity.actPoint.x=-5.00000000000000000000
|
||||
Entity.actPoint.y=5.00000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=00040002
|
||||
Entity.type=2001
|
||||
Entity.construction=0
|
||||
Entity.workplane.v=80020000
|
||||
Entity.actPoint.x=5.00000000000000000000
|
||||
Entity.actPoint.y=5.00000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=80020000
|
||||
Entity.type=10000
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=80020002
|
||||
Entity.normal.v=80020001
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=80020001
|
||||
Entity.type=3010
|
||||
Entity.construction=0
|
||||
Entity.point[0].v=80020002
|
||||
Entity.actNormal.w=1.00000000000000000000
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
||||
Entity.h.v=80020002
|
||||
Entity.type=2012
|
||||
Entity.construction=0
|
||||
Entity.actVisible=1
|
||||
AddEntity
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#include "harness.h"
|
||||
|
||||
TEST_CASE(load_v20) {
|
||||
CHECK_LOAD("line_segment_v20.slvs");
|
||||
CHECK_RENDER("line_segment.png");
|
||||
}
|
||||
|
||||
TEST_CASE(roundtrip_v21) {
|
||||
CHECK_LOAD("line_segment_v21.slvs");
|
||||
CHECK_RENDER("line_segment.png");
|
||||
CHECK_SAVE("line_segment_v21.slvs");
|
||||
}
|
||||
|
||||
TEST_CASE(migrate_v20_to_v21) {
|
||||
CHECK_LOAD("line_segment_v20.slvs");
|
||||
CHECK_SAVE("line_segment_v21.slvs");
|
||||
}
|
Loading…
Reference in New Issue