mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-20 03:43:52 +00:00
Feature/re enable tests (#10503)
* re-enable tests * Add comprehensive testing guide for OrcaSlicer in CLAUDE.md * fix build errors on Win * fix appimage errors
This commit is contained in:
34
CLAUDE.md
34
CLAUDE.md
@@ -81,25 +81,35 @@ build_release_vs2022.bat slicer
|
|||||||
- Linux builds use Ninja generator
|
- Linux builds use Ninja generator
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
Tests are located in the `tests/` directory and use multiple testing frameworks. Test structure:
|
Tests are located in the `tests/` directory and use the Catch2 testing framework. Test structure:
|
||||||
- `tests/libslic3r/` - Core library tests (Catch2 framework)
|
- `tests/libslic3r/` - Core library tests (21 test files)
|
||||||
- `tests/libnest2d/` - 2D nesting algorithm tests
|
- Geometry processing, algorithms, file formats (STL, 3MF, AMF)
|
||||||
- `tests/fff_print/` - FFF printing tests
|
- Polygon operations, clipper utilities, Voronoi diagrams
|
||||||
- `tests/sla_print/` - SLA printing tests
|
- `tests/fff_print/` - Fused Filament Fabrication tests (12 test files)
|
||||||
- `tests/t/` - Legacy Perl-based unit tests (.t files)
|
- Slicing algorithms, G-code generation, print mechanics
|
||||||
- `tests/lib/` - Perl test library modules
|
- Fill patterns, extrusion, support material
|
||||||
- `tests/xs/` - XS (C/Perl interface) related tests
|
- `tests/sla_print/` - Stereolithography tests (4 test files)
|
||||||
|
- SLA-specific printing algorithms, support generation
|
||||||
|
- `tests/libnest2d/` - 2D nesting algorithm tests
|
||||||
|
- `tests/slic3rutils/` - Utility function tests
|
||||||
- `tests/sandboxes/` - Experimental/sandbox test code
|
- `tests/sandboxes/` - Experimental/sandbox test code
|
||||||
|
|
||||||
Run tests after building:
|
Run all tests after building:
|
||||||
```bash
|
```bash
|
||||||
cd build && ctest
|
cd build && ctest
|
||||||
```
|
```
|
||||||
|
|
||||||
Legacy Perl tests can be run individually:
|
Run tests with verbose output:
|
||||||
```bash
|
```bash
|
||||||
cd tests/t
|
cd build && ctest --output-on-failure
|
||||||
perl support.t # example test file
|
```
|
||||||
|
|
||||||
|
Run individual test suites:
|
||||||
|
```bash
|
||||||
|
# From build directory
|
||||||
|
./tests/libslic3r/libslic3r_tests
|
||||||
|
./tests/fff_print/fff_print_tests
|
||||||
|
./tests/sla_print/sla_print_tests
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|||||||
@@ -134,13 +134,13 @@ endif ()
|
|||||||
|
|
||||||
# Proposal for C++ unit tests and sandboxes
|
# Proposal for C++ unit tests and sandboxes
|
||||||
option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF)
|
option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF)
|
||||||
option(SLIC3R_BUILD_TESTS "Build unit tests" OFF)
|
option(BUILD_TESTS "Build unit tests" OFF)
|
||||||
option(ORCA_TOOLS "Build Orca tools" OFF)
|
option(ORCA_TOOLS "Build Orca tools" OFF)
|
||||||
|
|
||||||
if (IS_CROSS_COMPILE)
|
if (IS_CROSS_COMPILE)
|
||||||
message("Detected cross compilation setup. Tests and encoding checks will be forcedly disabled!")
|
message("Detected cross compilation setup. Tests and encoding checks will be forcedly disabled!")
|
||||||
set(SLIC3R_PERL_XS OFF CACHE BOOL "" FORCE)
|
set(SLIC3R_PERL_XS OFF CACHE BOOL "" FORCE)
|
||||||
set(SLIC3R_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
set(BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Print out the SLIC3R_* cache options
|
# Print out the SLIC3R_* cache options
|
||||||
@@ -534,6 +534,7 @@ find_package(TBB REQUIRED)
|
|||||||
|
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
find_package(CURL REQUIRED)
|
find_package(CURL REQUIRED)
|
||||||
|
find_package(Freetype REQUIRED)
|
||||||
|
|
||||||
|
|
||||||
add_library(libcurl INTERFACE)
|
add_library(libcurl INTERFACE)
|
||||||
@@ -813,7 +814,7 @@ if(SLIC3R_BUILD_SANDBOXES)
|
|||||||
add_subdirectory(sandboxes)
|
add_subdirectory(sandboxes)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(SLIC3R_BUILD_TESTS)
|
if(BUILD_TESTS)
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -822,11 +823,6 @@ if (NOT WIN32 AND NOT APPLE)
|
|||||||
configure_file(${LIBDIR}/dev-utils/platform/unix/build_appimage.sh.in ${CMAKE_CURRENT_BINARY_DIR}/build_appimage.sh USE_SOURCE_PERMISSIONS @ONLY)
|
configure_file(${LIBDIR}/dev-utils/platform/unix/build_appimage.sh.in ${CMAKE_CURRENT_BINARY_DIR}/build_appimage.sh USE_SOURCE_PERMISSIONS @ONLY)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(BUILD_BBS_TEST_TOOLS "Build bbs test tools" OFF)
|
|
||||||
if(BUILD_BBS_TEST_TOOLS)
|
|
||||||
add_subdirectory(bbs_test_tools)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
# Resources install target, configure fhs.hpp on UNIX
|
# Resources install target, configure fhs.hpp on UNIX
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ SCRIPT_PATH=$(dirname "$(readlink -f "${0}")")
|
|||||||
pushd "${SCRIPT_PATH}" > /dev/null
|
pushd "${SCRIPT_PATH}" > /dev/null
|
||||||
|
|
||||||
function usage() {
|
function usage() {
|
||||||
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-h][-i][-j N][-p][-r][-s][-u][-l]"
|
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-h][-i][-j N][-p][-r][-s][-t][-u][-l][-L]"
|
||||||
echo " -1: limit builds to one core (where possible)"
|
echo " -1: limit builds to one core (where possible)"
|
||||||
echo " -j N: limit builds to N cores (where possible)"
|
echo " -j N: limit builds to N cores (where possible)"
|
||||||
echo " -b: build in debug mode"
|
echo " -b: build in debug mode"
|
||||||
@@ -19,16 +19,19 @@ function usage() {
|
|||||||
echo " -p: boost ccache hit rate by disabling precompiled headers (default: ON)"
|
echo " -p: boost ccache hit rate by disabling precompiled headers (default: ON)"
|
||||||
echo " -r: skip RAM and disk checks (low RAM compiling)"
|
echo " -r: skip RAM and disk checks (low RAM compiling)"
|
||||||
echo " -s: build the Orca Slicer (optional)"
|
echo " -s: build the Orca Slicer (optional)"
|
||||||
|
echo " -t: build tests (optional)"
|
||||||
echo " -u: install system dependencies (asks for sudo password; build prerequisite)"
|
echo " -u: install system dependencies (asks for sudo password; build prerequisite)"
|
||||||
echo " -l: use Clang instead of GCC (default: GCC)"
|
echo " -l: use Clang instead of GCC (default: GCC)"
|
||||||
|
echo " -L: use ld.lld as linker (if available)"
|
||||||
echo "For a first use, you want to './${SCRIPT_NAME} -u'"
|
echo "For a first use, you want to './${SCRIPT_NAME} -u'"
|
||||||
echo " and then './${SCRIPT_NAME} -dsi'"
|
echo " and then './${SCRIPT_NAME} -dsi'"
|
||||||
|
echo "To build with tests: './${SCRIPT_NAME} -st' or './${SCRIPT_NAME} -dst'"
|
||||||
}
|
}
|
||||||
|
|
||||||
SLIC3R_PRECOMPILED_HEADERS="ON"
|
SLIC3R_PRECOMPILED_HEADERS="ON"
|
||||||
|
|
||||||
unset name
|
unset name
|
||||||
while getopts ":1j:bcCdhiprsul" opt ; do
|
while getopts ":1j:bcCdhiprstulL" opt ; do
|
||||||
case ${opt} in
|
case ${opt} in
|
||||||
1 )
|
1 )
|
||||||
export CMAKE_BUILD_PARALLEL_LEVEL=1
|
export CMAKE_BUILD_PARALLEL_LEVEL=1
|
||||||
@@ -63,12 +66,18 @@ while getopts ":1j:bcCdhiprsul" opt ; do
|
|||||||
s )
|
s )
|
||||||
BUILD_ORCA="1"
|
BUILD_ORCA="1"
|
||||||
;;
|
;;
|
||||||
|
t )
|
||||||
|
BUILD_TESTS="1"
|
||||||
|
;;
|
||||||
u )
|
u )
|
||||||
export UPDATE_LIB="1"
|
export UPDATE_LIB="1"
|
||||||
;;
|
;;
|
||||||
l )
|
l )
|
||||||
USE_CLANG="1"
|
USE_CLANG="1"
|
||||||
;;
|
;;
|
||||||
|
L )
|
||||||
|
USE_LLD="1"
|
||||||
|
;;
|
||||||
* )
|
* )
|
||||||
echo "Unknown argument '${opt}', aborting."
|
echo "Unknown argument '${opt}', aborting."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -151,6 +160,18 @@ if [[ -n "${USE_CLANG}" ]] ; then
|
|||||||
export CMAKE_C_CXX_COMPILER_CLANG="-DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++"
|
export CMAKE_C_CXX_COMPILER_CLANG="-DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Configure use of ld.lld as the linker when requested
|
||||||
|
export CMAKE_LLD_LINKER_ARGS=""
|
||||||
|
if [[ -n "${USE_LLD}" ]] ; then
|
||||||
|
if command -v ld.lld >/dev/null 2>&1 ; then
|
||||||
|
LLD_BIN=$(command -v ld.lld)
|
||||||
|
export CMAKE_LLD_LINKER_ARGS="-DCMAKE_LINKER=${LLD_BIN} -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld -DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld -DCMAKE_MODULE_LINKER_FLAGS=-fuse-ld=lld"
|
||||||
|
else
|
||||||
|
echo "Error: ld.lld not found. Please install the 'lld' package (e.g., sudo apt install lld) or omit -L."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -n "${BUILD_DEPS}" ]] ; then
|
if [[ -n "${BUILD_DEPS}" ]] ; then
|
||||||
echo "Configuring dependencies..."
|
echo "Configuring dependencies..."
|
||||||
BUILD_ARGS="${DEPS_EXTRA_BUILD_ARGS} -DDEP_WX_GTK3=ON"
|
BUILD_ARGS="${DEPS_EXTRA_BUILD_ARGS} -DDEP_WX_GTK3=ON"
|
||||||
@@ -162,7 +183,7 @@ if [[ -n "${BUILD_DEPS}" ]] ; then
|
|||||||
if [[ -n "${BUILD_DEBUG}" ]] ; then
|
if [[ -n "${BUILD_DEBUG}" ]] ; then
|
||||||
# build deps with debug and release else cmake will not find required sources
|
# build deps with debug and release else cmake will not find required sources
|
||||||
mkdir -p deps/build/release
|
mkdir -p deps/build/release
|
||||||
CMAKE_CMD="cmake ${CMAKE_C_CXX_COMPILER_CLANG} -S deps -B deps/build/release -DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} -G Ninja -DDESTDIR=${SCRIPT_PATH}/deps/build/destdir -DDEP_DOWNLOAD_DIR=${SCRIPT_PATH}/deps/DL_CACHE ${COLORED_OUTPUT} ${BUILD_ARGS}"
|
CMAKE_CMD="cmake ${CMAKE_C_CXX_COMPILER_CLANG} ${CMAKE_LLD_LINKER_ARGS} -S deps -B deps/build/release -DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} -G Ninja -DDESTDIR=${SCRIPT_PATH}/deps/build/destdir -DDEP_DOWNLOAD_DIR=${SCRIPT_PATH}/deps/DL_CACHE ${COLORED_OUTPUT} ${BUILD_ARGS}"
|
||||||
echo "${CMAKE_CMD}"
|
echo "${CMAKE_CMD}"
|
||||||
${CMAKE_CMD}
|
${CMAKE_CMD}
|
||||||
cmake --build deps/build/release
|
cmake --build deps/build/release
|
||||||
@@ -170,7 +191,7 @@ if [[ -n "${BUILD_DEPS}" ]] ; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# If this isn't in one quote, then empty variables can add two single quotes and mess up argument parsing for cmake.
|
# If this isn't in one quote, then empty variables can add two single quotes and mess up argument parsing for cmake.
|
||||||
CMAKE_CMD="cmake -S deps -B deps/build ${CMAKE_C_CXX_COMPILER_CLANG} -G Ninja ${COLORED_OUTPUT} ${BUILD_ARGS}"
|
CMAKE_CMD="cmake -S deps -B deps/build ${CMAKE_C_CXX_COMPILER_CLANG} ${CMAKE_LLD_LINKER_ARGS} -G Ninja ${COLORED_OUTPUT} ${BUILD_ARGS}"
|
||||||
echo "${CMAKE_CMD}"
|
echo "${CMAKE_CMD}"
|
||||||
${CMAKE_CMD}
|
${CMAKE_CMD}
|
||||||
cmake --build deps/build
|
cmake --build deps/build
|
||||||
@@ -190,21 +211,33 @@ if [[ -n "${BUILD_ORCA}" ]] ; then
|
|||||||
else
|
else
|
||||||
BUILD_ARGS="${BUILD_ARGS} -DBBL_RELEASE_TO_PUBLIC=1 -DBBL_INTERNAL_TESTING=0"
|
BUILD_ARGS="${BUILD_ARGS} -DBBL_RELEASE_TO_PUBLIC=1 -DBBL_INTERNAL_TESTING=0"
|
||||||
fi
|
fi
|
||||||
|
if [[ -n "${BUILD_TESTS}" ]] ; then
|
||||||
|
BUILD_ARGS="${BUILD_ARGS} -DBUILD_TESTS=ON"
|
||||||
|
fi
|
||||||
|
|
||||||
CMAKE_CMD="cmake -S . -B build ${CMAKE_C_CXX_COMPILER_CLANG} -G Ninja Multi-Config \
|
echo "Configuring OrcaSlicer..."
|
||||||
|
cmake -S . -B build ${CMAKE_C_CXX_COMPILER_CLANG} ${CMAKE_LLD_LINKER_ARGS} -G "Ninja Multi-Config" \
|
||||||
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
|
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
|
||||||
-DCMAKE_PREFIX_PATH=${SCRIPT_PATH}/deps/build/destdir/usr/local \
|
-DCMAKE_PREFIX_PATH=${SCRIPT_PATH}/deps/build/destdir/usr/local \
|
||||||
-DSLIC3R_STATIC=1 \
|
-DSLIC3R_STATIC=1 \
|
||||||
-DORCA_TOOLS=ON \
|
-DORCA_TOOLS=ON \
|
||||||
${COLORED_OUTPUT} \
|
${COLORED_OUTPUT} \
|
||||||
${BUILD_ARGS}"
|
${BUILD_ARGS}
|
||||||
echo "${CMAKE_CMD}"
|
echo "${CMAKE_CMD}"
|
||||||
${CMAKE_CMD}
|
${CMAKE_CMD}
|
||||||
echo "done"
|
echo "done"
|
||||||
echo "Building OrcaSlicer ..."
|
echo "Building OrcaSlicer ..."
|
||||||
cmake --build build --target OrcaSlicer
|
if [[ -n "${BUILD_DEBUG}" ]] ; then
|
||||||
|
cmake --build build --config Debug --target OrcaSlicer
|
||||||
|
else
|
||||||
|
cmake --build build --config Release --target OrcaSlicer
|
||||||
|
fi
|
||||||
echo "Building OrcaSlicer_profile_validator .."
|
echo "Building OrcaSlicer_profile_validator .."
|
||||||
cmake --build build --target OrcaSlicer_profile_validator
|
if [[ -n "${BUILD_DEBUG}" ]] ; then
|
||||||
|
cmake --build build --config Debug --target OrcaSlicer_profile_validator
|
||||||
|
else
|
||||||
|
cmake --build build --config Release --target OrcaSlicer_profile_validator
|
||||||
|
fi
|
||||||
./scripts/run_gettext.sh
|
./scripts/run_gettext.sh
|
||||||
echo "done"
|
echo "done"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ public:
|
|||||||
size_t last = svg_layers_.size() > 1 ? svg_layers_.size() : 0;
|
size_t last = svg_layers_.size() > 1 ? svg_layers_.size() : 0;
|
||||||
|
|
||||||
for (auto &lyr : svg_layers_) {
|
for (auto &lyr : svg_layers_) {
|
||||||
boost::filesystem::ofstream out(filepath, std::fstream::out);
|
std::ofstream out(filepath.string(), std::fstream::out);
|
||||||
if (out.is_open()) out << lyr;
|
if (out.is_open()) out << lyr;
|
||||||
if (lyrc == last && !finished_) out << "\n</svg>\n";
|
if (lyrc == last && !finished_) out << "\n</svg>\n";
|
||||||
out.flush();
|
out.flush();
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ modules:
|
|||||||
-DSLIC3R_FHS=ON \
|
-DSLIC3R_FHS=ON \
|
||||||
-DSLIC3R_GTK=3 \
|
-DSLIC3R_GTK=3 \
|
||||||
-DSLIC3R_STATIC=ON \
|
-DSLIC3R_STATIC=ON \
|
||||||
-DSLIC3R_BUILD_TESTS=OFF \
|
-DBUILD_TESTS=OFF \
|
||||||
-DSLIC3R_DESKTOP_INTEGRATION=OFF \
|
-DSLIC3R_DESKTOP_INTEGRATION=OFF \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DFLATPAK=ON \
|
-DFLATPAK=ON \
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
export ARCH=$(uname -m)
|
||||||
|
|
||||||
if [ -f /.dockerenv ] ; then # Only run if inside of a Docker Container
|
if [ -f /.dockerenv ] ; then # Only run if inside of a Docker Container
|
||||||
../appimagetool.AppImage --appimage-extract-and-run . $([ ! -z "${container}" ] && echo '--appimage-extract-and-run')
|
../appimagetool.AppImage --appimage-extract-and-run . $([ ! -z "${container}" ] && echo '--appimage-extract-and-run')
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -29,7 +29,17 @@ echo -n "[9/9] Generating Linux app..."
|
|||||||
|
|
||||||
# copy Resources
|
# copy Resources
|
||||||
cp -Rf ../resources package/resources
|
cp -Rf ../resources package/resources
|
||||||
cp -f src/@SLIC3R_APP_CMD@ package/bin/@SLIC3R_APP_CMD@
|
|
||||||
|
# Find and copy the @SLIC3R_APP_CMD@ binary from Multi-Config build
|
||||||
|
if [ -f "src/Release/@SLIC3R_APP_CMD@" ]; then
|
||||||
|
cp -f src/Release/@SLIC3R_APP_CMD@ package/bin/@SLIC3R_APP_CMD@
|
||||||
|
elif [ -f "src/@SLIC3R_APP_CMD@" ]; then
|
||||||
|
# Fallback for single-config builds
|
||||||
|
cp -f src/@SLIC3R_APP_CMD@ package/bin/@SLIC3R_APP_CMD@
|
||||||
|
else
|
||||||
|
echo "Error: @SLIC3R_APP_CMD@ binary not found in any configuration directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
# remove unneeded po from resources
|
# remove unneeded po from resources
|
||||||
## find package/resources/localization -name "*.po" -type f -delete ## FIXME: DD - do we need this?
|
## find package/resources/localization -name "*.po" -type f -delete ## FIXME: DD - do we need this?
|
||||||
|
|
||||||
|
|||||||
@@ -581,7 +581,9 @@ target_link_libraries(libslic3r
|
|||||||
)
|
)
|
||||||
|
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
target_link_libraries(libslic3r freetype)
|
# Link freetype for OCCT dependency (CAD operations need font rendering)
|
||||||
|
target_link_libraries(libslic3r ${FREETYPE_LIBRARIES})
|
||||||
|
target_link_libraries(libslic3r OpenSSL::Crypto)
|
||||||
if (NOT APPLE)
|
if (NOT APPLE)
|
||||||
target_link_libraries(libslic3r fontconfig)
|
target_link_libraries(libslic3r fontconfig)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
961
tests/CLAUDE.md
Normal file
961
tests/CLAUDE.md
Normal file
@@ -0,0 +1,961 @@
|
|||||||
|
# CLAUDE.md - Testing Guide for OrcaSlicer
|
||||||
|
|
||||||
|
This guide provides comprehensive instructions for Claude Code when writing, maintaining, and understanding tests in the OrcaSlicer codebase.
|
||||||
|
|
||||||
|
## ⚠️ CRITICAL RULES - MUST FOLLOW
|
||||||
|
|
||||||
|
### 1. **SECTIONS IN LOOPS - NEVER REUSE NAMES**
|
||||||
|
❌ **WRONG**: Will cause unpredictable behavior
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Bad loop sections") {
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
SECTION("Same name") { // WRONG! Same name used multiple times
|
||||||
|
REQUIRE(i >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **CORRECT**: Use DYNAMIC_SECTION or incorporate counter
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Good loop sections") {
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
DYNAMIC_SECTION("Section " << i) { // Unique name per iteration
|
||||||
|
REQUIRE(i >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **THREAD SAFETY - ASSERTIONS ARE NOT THREAD-SAFE**
|
||||||
|
❌ **WRONG**: Will cause undefined behavior or crashes
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Multi-threaded test") {
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
threads.emplace_back([]() {
|
||||||
|
REQUIRE(some_calculation() == expected); // NOT THREAD-SAFE!
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **CORRECT**: Synchronize results, test on main thread
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Multi-threaded test") {
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
std::atomic<int> passed{0};
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
threads.emplace_back([&passed]() {
|
||||||
|
if (some_calculation() == expected) {
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& t : threads) t.join();
|
||||||
|
REQUIRE(passed == 4); // Test results on main thread
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **EXPRESSION DECOMPOSITION - AVOID BINARY OPERATORS**
|
||||||
|
❌ **WRONG**: Cannot decompose properly
|
||||||
|
```cpp
|
||||||
|
REQUIRE(a > 0 && b < 10); // Shows "false" on failure, not individual values
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **CORRECT**: Split into separate assertions
|
||||||
|
```cpp
|
||||||
|
REQUIRE(a > 0);
|
||||||
|
REQUIRE(b < 10); // Each shows individual values on failure
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. **FLOATING POINT - NEVER USE APPROX**
|
||||||
|
❌ **WRONG**: Approx is deprecated and asymmetric
|
||||||
|
```cpp
|
||||||
|
REQUIRE(calculated_value == Catch::Approx(expected)); // Deprecated!
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **CORRECT**: Use floating point matchers
|
||||||
|
```cpp
|
||||||
|
REQUIRE_THAT(calculated_value, WithinAbs(expected, 0.001));
|
||||||
|
REQUIRE_THAT(calculated_value, WithinRel(expected, 0.01)); // 1% tolerance
|
||||||
|
REQUIRE_THAT(calculated_value, WithinULP(expected, 4)); // 4 ULPs apart
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. **TEST ORDERING - ALWAYS USE RANDOM ORDER**
|
||||||
|
✅ **REQUIRED**: For CI/CD and development
|
||||||
|
```bash
|
||||||
|
# Essential flags for running tests
|
||||||
|
./tests --order rand --warn NoAssertions
|
||||||
|
|
||||||
|
# For test sharding (parallel execution), share random seed
|
||||||
|
./tests --order rand --shard-index 0 --shard-count 3 --rng-seed 0xBEEF
|
||||||
|
./tests --order rand --shard-index 1 --shard-count 3 --rng-seed 0xBEEF
|
||||||
|
./tests --order rand --shard-index 2 --shard-count 3 --rng-seed 0xBEEF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overview of OrcaSlicer's Testing Framework
|
||||||
|
|
||||||
|
OrcaSlicer uses **Catch2 v2** as its primary testing framework. The test suite is organized into several modules that mirror the project's architectural components:
|
||||||
|
|
||||||
|
> **Note**: OrcaSlicer currently uses Catch2 v2 (based on `#include <catch2/catch.hpp>` includes). Some features mentioned in this guide are only available in v3 and marked accordingly.
|
||||||
|
|
||||||
|
### Test Structure
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── CMakeLists.txt # Main test configuration
|
||||||
|
├── catch_main.hpp # Custom test reporter
|
||||||
|
├── libslic3r/ # Core library tests (21 test files)
|
||||||
|
├── fff_print/ # FFF printing tests (12 test files)
|
||||||
|
├── sla_print/ # SLA printing tests (4 test files)
|
||||||
|
├── libnest2d/ # 2D nesting tests
|
||||||
|
├── slic3rutils/ # Utility tests
|
||||||
|
├── data/ # Test data files and meshes
|
||||||
|
└── catch2/ # Catch2 framework files
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Integration
|
||||||
|
- Tests are built using CMake with `catch_discover_tests()` integration
|
||||||
|
- Each test module creates a separate executable (e.g., `libslic3r_tests`, `fff_print_tests`)
|
||||||
|
- Test data directory is available via `TEST_DATA_DIR` preprocessor definition
|
||||||
|
- Custom verbose console reporter provides detailed test output
|
||||||
|
|
||||||
|
## Test Suite Organization
|
||||||
|
|
||||||
|
### libslic3r Tests
|
||||||
|
Core slicing engine tests covering:
|
||||||
|
- **Geometry operations**: Points, polygons, lines, Voronoi diagrams
|
||||||
|
- **File formats**: STL, 3MF, AMF parsing and validation
|
||||||
|
- **Algorithms**: Clipper operations, mesh boolean operations, optimization
|
||||||
|
- **Configuration**: Print settings validation and parsing
|
||||||
|
- **Utilities**: String processing, time utilities, data structures
|
||||||
|
|
||||||
|
### fff_print Tests
|
||||||
|
Fused Filament Fabrication specific tests:
|
||||||
|
- **G-code generation**: Writer functionality, cooling, lift/unlift
|
||||||
|
- **Slicing algorithms**: Layer generation, infill patterns
|
||||||
|
- **Print mechanics**: Flow calculations, extrusion, support material
|
||||||
|
- **Model processing**: Print objects, skirt/brim generation
|
||||||
|
|
||||||
|
### sla_print Tests
|
||||||
|
Stereolithography specific tests:
|
||||||
|
- **SLA print processing**: Layer curing, support generation
|
||||||
|
- **Raycast operations**: Light path calculations
|
||||||
|
- **Test utilities**: SLA-specific helper functions
|
||||||
|
|
||||||
|
## Writing New Tests - Best Practices
|
||||||
|
|
||||||
|
### File Organization
|
||||||
|
1. **Naming Convention**: `test_<feature>.cpp` (e.g., `test_geometry.cpp`)
|
||||||
|
2. **Header Structure**: Include `<catch2/catch.hpp>` first, then relevant headers
|
||||||
|
3. **Namespace Usage**: Use `using namespace Slic3r;` for convenience
|
||||||
|
4. **File Placement**: Add to appropriate test directory and update CMakeLists.txt
|
||||||
|
|
||||||
|
### Test Naming and Structure
|
||||||
|
```cpp
|
||||||
|
#include <catch2/catch.hpp>
|
||||||
|
#include "libslic3r/Point.hpp"
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
TEST_CASE("Feature description", "[category_tag]") {
|
||||||
|
// Test implementation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tagging System
|
||||||
|
Use descriptive tags for test categorization:
|
||||||
|
- `[Geometry]` - Geometric operations and calculations
|
||||||
|
- `[GCodeWriter]` - G-code generation functionality
|
||||||
|
- `[Config]` - Configuration and settings tests
|
||||||
|
- `[FileFormat]` - File I/O operations (STL, 3MF, etc.)
|
||||||
|
- `[Algorithm]` - Core algorithms and processing
|
||||||
|
- `[Performance]` - Performance benchmarks (if applicable)
|
||||||
|
|
||||||
|
## Catch2 Features Guide
|
||||||
|
|
||||||
|
### Basic Assertions
|
||||||
|
```cpp
|
||||||
|
// Primary assertions - stop test on failure
|
||||||
|
REQUIRE(expression);
|
||||||
|
REQUIRE_FALSE(expression);
|
||||||
|
|
||||||
|
// Continuing assertions - continue test after failure
|
||||||
|
CHECK(expression);
|
||||||
|
CHECK_FALSE(expression);
|
||||||
|
|
||||||
|
// Non-failing checks - record result but don't fail test
|
||||||
|
CHECK_NOFAIL(expression); // Useful for assumptions that might be violated
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exception Testing
|
||||||
|
```cpp
|
||||||
|
// Verify no exception is thrown
|
||||||
|
REQUIRE_NOTHROW(function_call());
|
||||||
|
|
||||||
|
// Verify any exception is thrown
|
||||||
|
REQUIRE_THROWS(risky_function());
|
||||||
|
|
||||||
|
// Verify specific exception type
|
||||||
|
REQUIRE_THROWS_AS(function_call(), SpecificException);
|
||||||
|
|
||||||
|
// Verify exception message
|
||||||
|
REQUIRE_THROWS_WITH(function_call(), "Expected error message");
|
||||||
|
|
||||||
|
// Verify exception with matchers (for partial matching)
|
||||||
|
REQUIRE_THROWS_MATCHES(function_call(), SpecificException,
|
||||||
|
Catch::Matchers::Message("contains this"));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complex Assertions with Matchers
|
||||||
|
```cpp
|
||||||
|
#include <catch2/matchers/catch_matchers.hpp>
|
||||||
|
|
||||||
|
// String matchers
|
||||||
|
using Catch::Matchers::StartsWith;
|
||||||
|
using Catch::Matchers::EndsWith;
|
||||||
|
using Catch::Matchers::ContainsSubstring; // Note: v2 uses "Contains"
|
||||||
|
using Catch::Matchers::Equals;
|
||||||
|
using Catch::Matchers::Matches; // Regex matching
|
||||||
|
|
||||||
|
REQUIRE_THAT(result_string, StartsWith("Expected prefix"));
|
||||||
|
REQUIRE_THAT(result_string, ContainsSubstring("middle part"));
|
||||||
|
REQUIRE_THAT(result_string, Matches(".*pattern.*"));
|
||||||
|
|
||||||
|
// Floating point matchers - ALWAYS use these instead of Approx!
|
||||||
|
using Catch::Matchers::WithinAbs;
|
||||||
|
using Catch::Matchers::WithinRel;
|
||||||
|
using Catch::Matchers::WithinULP;
|
||||||
|
|
||||||
|
REQUIRE_THAT(float_value, WithinAbs(expected, 0.001)); // Absolute tolerance
|
||||||
|
REQUIRE_THAT(float_value, WithinRel(expected, 0.01)); // Relative tolerance (1%)
|
||||||
|
REQUIRE_THAT(float_value, WithinULP(expected, 4)); // ULP difference (requires IEEE-754)
|
||||||
|
|
||||||
|
// Combining matchers
|
||||||
|
REQUIRE_THAT(value, WithinRel(expected, 0.001) || WithinAbs(0.0, 0.000001));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sections for Test Organization
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Complex feature testing", "[Feature]") {
|
||||||
|
// Common setup code
|
||||||
|
SomeObject obj;
|
||||||
|
|
||||||
|
SECTION("First scenario") {
|
||||||
|
// Specific test case
|
||||||
|
REQUIRE(obj.method1() == expected_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Second scenario") {
|
||||||
|
// Another test case with same setup
|
||||||
|
REQUIRE(obj.method2() == other_expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### BDD-Style Tests
|
||||||
|
Use for complex scenarios and user story testing:
|
||||||
|
|
||||||
|
> **Note**: BDD macros are aliases for TEST_CASE and SECTION with prefixed names
|
||||||
|
```cpp
|
||||||
|
SCENARIO("User performs complex operation", "[UserStory]") {
|
||||||
|
GIVEN("A specific setup condition") {
|
||||||
|
GCodeWriter writer;
|
||||||
|
// Setup code
|
||||||
|
|
||||||
|
WHEN("User performs action") {
|
||||||
|
auto result = writer.some_operation();
|
||||||
|
|
||||||
|
THEN("Expected outcome occurs") {
|
||||||
|
REQUIRE(result.size() > 0);
|
||||||
|
|
||||||
|
AND_WHEN("Follow-up action occurs") {
|
||||||
|
auto next_result = writer.next_operation();
|
||||||
|
|
||||||
|
THEN("Final outcome is correct") {
|
||||||
|
REQUIRE(next_result == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Generators for Parameterized Tests
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Function works with various inputs", "[Algorithm]") {
|
||||||
|
auto test_value = GENERATE(1, 3, 5, 7, 11, 13);
|
||||||
|
|
||||||
|
REQUIRE(is_odd(test_value));
|
||||||
|
REQUIRE(test_value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range-based generators
|
||||||
|
TEST_CASE("Range testing", "[Algorithm]") {
|
||||||
|
auto i = GENERATE(range(1, 10)); // 1 to 9
|
||||||
|
REQUIRE(process_value(i) > i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using variables in generators (requires GENERATE_COPY or GENERATE_REF)
|
||||||
|
TEST_CASE("Generator with variables", "[Algorithm]") {
|
||||||
|
std::vector<int> values = {1, 2, 3, 4, 5};
|
||||||
|
auto test_value = GENERATE_REF(from_range(values)); // Use GENERATE_REF for references
|
||||||
|
|
||||||
|
REQUIRE(test_value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom generators
|
||||||
|
TEST_CASE("Random values", "[Algorithm]") {
|
||||||
|
auto random_int = GENERATE(take(100, random(-1000, 1000))); // 100 random values
|
||||||
|
REQUIRE(process_random_value(random_int));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Fixtures
|
||||||
|
```cpp
|
||||||
|
class GeometryFixture {
|
||||||
|
public:
|
||||||
|
Point origin{0, 0};
|
||||||
|
Point unit_x{1, 0};
|
||||||
|
Point unit_y{0, 1};
|
||||||
|
|
||||||
|
mutable double tolerance = EPSILON; // Use mutable for data that might change
|
||||||
|
};
|
||||||
|
|
||||||
|
// Standard fixture - new instance per test run
|
||||||
|
TEST_CASE_METHOD(GeometryFixture, "Point operations", "[Geometry]") {
|
||||||
|
REQUIRE(origin.distance_to(unit_x) == 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persistent fixture - single instance for entire test case (v2.12.0+)
|
||||||
|
TEST_CASE_PERSISTENT_FIXTURE(GeometryFixture, "Persistent operations", "[Geometry]") {
|
||||||
|
static int call_count = 0;
|
||||||
|
++call_count;
|
||||||
|
INFO("This fixture persists across sections, call: " << call_count);
|
||||||
|
|
||||||
|
SECTION("First section") {
|
||||||
|
REQUIRE(origin.distance_to(unit_x) == 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Second section") {
|
||||||
|
REQUIRE(origin.distance_to(unit_y) == 1.0);
|
||||||
|
// call_count will be 2 here with persistent fixture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template fixtures for type-parameterized tests
|
||||||
|
template<typename T>
|
||||||
|
class NumericFixture {
|
||||||
|
public:
|
||||||
|
T zero = T{0};
|
||||||
|
T one = T{1};
|
||||||
|
};
|
||||||
|
|
||||||
|
TEMPLATE_TEST_CASE_METHOD(NumericFixture, "Numeric operations", "[Template]", int, float, double) {
|
||||||
|
REQUIRE(TestType{} == this->zero);
|
||||||
|
REQUIRE(TestType{1} == this->one);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Testing Features
|
||||||
|
|
||||||
|
#### Logging and Information Macros
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Advanced logging", "[Logging]") {
|
||||||
|
INFO("This info persists until end of scope");
|
||||||
|
|
||||||
|
SECTION("Section A") {
|
||||||
|
INFO("Section A specific info");
|
||||||
|
CAPTURE(some_variable, another_var); // Captures variable names and values
|
||||||
|
CHECK(some_condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Section B") {
|
||||||
|
UNSCOPED_INFO("This survives beyond its scope"); // v2.7.0+
|
||||||
|
CHECK(other_condition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning and explicit control
|
||||||
|
TEST_CASE("Explicit test control", "[Control]") {
|
||||||
|
WARN("This warns but doesn't fail the test");
|
||||||
|
|
||||||
|
if (precondition_not_met) {
|
||||||
|
// SKIP("Reason"); // v3.3.0+ only, not available in v2
|
||||||
|
SUCCEED("Test cannot run due to precondition"); // v2 alternative
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (critical_failure) {
|
||||||
|
FAIL("Critical condition failed"); // Fails and stops test
|
||||||
|
}
|
||||||
|
|
||||||
|
SUCCEED("Reached successful completion"); // Explicit success marker
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Static Assertions (Compile-time Testing)
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Compile-time checks", "[Static]") {
|
||||||
|
STATIC_REQUIRE(sizeof(int) >= 4); // Checked at compile time
|
||||||
|
STATIC_REQUIRE_FALSE(std::is_void_v<int>);
|
||||||
|
|
||||||
|
// For traits and template metaprogramming
|
||||||
|
STATIC_CHECK(std::is_trivially_copyable_v<Point>); // v3.0.1+
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Conditional Testing
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Conditional blocks", "[Conditional]") {
|
||||||
|
int value = get_test_value();
|
||||||
|
|
||||||
|
// These record the expression but don't count as test failures (v3.0.1+)
|
||||||
|
CHECKED_IF(value > 0) {
|
||||||
|
// This block runs if value > 0
|
||||||
|
REQUIRE(value <= 100);
|
||||||
|
} CHECKED_ELSE(value > 0) {
|
||||||
|
// This block runs if value <= 0
|
||||||
|
REQUIRE(value >= -100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Benchmarking (v2.9.0+)
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Performance testing", "[Benchmark]") {
|
||||||
|
// Simple benchmarking
|
||||||
|
BENCHMARK("Algorithm performance") {
|
||||||
|
return expensive_algorithm();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Advanced benchmarking with setup
|
||||||
|
BENCHMARK_ADVANCED("Advanced benchmark")(Catch::Benchmark::Chronometer meter) {
|
||||||
|
std::vector<int> data = setup_test_data(); // Setup not measured
|
||||||
|
|
||||||
|
meter.measure([&] {
|
||||||
|
return process_data(data); // Only this is measured
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## OrcaSlicer-Specific Testing Patterns
|
||||||
|
|
||||||
|
### Geometry Testing
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Line operations", "[Geometry]") {
|
||||||
|
Line line{{100000, 0}, {0, 0}};
|
||||||
|
Line parallel{{200000, 0}, {0, 0}};
|
||||||
|
|
||||||
|
REQUIRE(line.parallel_to(line));
|
||||||
|
REQUIRE(line.parallel_to(parallel));
|
||||||
|
|
||||||
|
// Test with epsilon tolerance
|
||||||
|
Line rotated(parallel);
|
||||||
|
rotated.rotate(0.9 * EPSILON, {0, 0});
|
||||||
|
REQUIRE(line.parallel_to(rotated));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Testing
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Config loading", "[Config]") {
|
||||||
|
DynamicPrintConfig config;
|
||||||
|
std::string config_path = std::string(TEST_DATA_DIR) + "/test_config/sample.ini";
|
||||||
|
|
||||||
|
REQUIRE_NOTHROW(config.load_from_ini(config_path));
|
||||||
|
REQUIRE(config.has("layer_height"));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### File I/O Testing
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("STL file parsing", "[FileFormat]") {
|
||||||
|
std::string stl_path = std::string(TEST_DATA_DIR) + "/test_stl/20mmbox.stl";
|
||||||
|
|
||||||
|
TriangleMesh mesh;
|
||||||
|
REQUIRE_NOTHROW(mesh.ReadSTLFile(stl_path.c_str()));
|
||||||
|
REQUIRE(!mesh.empty());
|
||||||
|
REQUIRE(mesh.volume() > 0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### G-code Generation Testing
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("G-code writer functionality", "[GCodeWriter]") {
|
||||||
|
GCodeWriter writer;
|
||||||
|
|
||||||
|
// Load test configuration
|
||||||
|
std::string config_path = std::string(TEST_DATA_DIR) + "/fff_print_tests/test_config.ini";
|
||||||
|
writer.config.load(config_path, ForwardCompatibilitySubstitutionRule::Disable);
|
||||||
|
|
||||||
|
// Test specific G-code generation
|
||||||
|
std::string result = writer.lift();
|
||||||
|
REQUIRE(!result.empty());
|
||||||
|
REQUIRE_THAT(result, Catch::Matchers::ContainsSubstring("G1"));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance Testing Patterns
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Algorithm performance", "[Performance][Algorithm]") {
|
||||||
|
// Large test data
|
||||||
|
std::vector<Point> points = generate_large_point_set(10000);
|
||||||
|
|
||||||
|
// Time the operation (manual timing for Catch2 v2)
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
auto result = convex_hull(points);
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||||
|
|
||||||
|
REQUIRE(result.size() > 0);
|
||||||
|
REQUIRE(duration.count() < 1000); // Should complete in < 1 second
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom String Conversions
|
||||||
|
|
||||||
|
#### For Custom Types
|
||||||
|
```cpp
|
||||||
|
// Method 1: operator<< overload (preferred)
|
||||||
|
std::ostream& operator<<(std::ostream& os, const Point& point) {
|
||||||
|
os << "Point(" << point.x << ", " << point.y << ")";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method 2: StringMaker specialization
|
||||||
|
namespace Catch {
|
||||||
|
template<>
|
||||||
|
struct StringMaker<MyCustomType> {
|
||||||
|
static std::string convert(const MyCustomType& value) {
|
||||||
|
return "MyCustomType{" + std::to_string(value.data) + "}";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method 3: Enum registration (v2.8.0+)
|
||||||
|
enum class Status { Ready, Processing, Complete, Error };
|
||||||
|
|
||||||
|
// Must be at global scope!
|
||||||
|
CATCH_REGISTER_ENUM(Status, Status::Ready, Status::Processing, Status::Complete, Status::Error);
|
||||||
|
|
||||||
|
// Method 4: Exception translation
|
||||||
|
CATCH_TRANSLATE_EXCEPTION(MyCustomException const& ex) {
|
||||||
|
return "MyCustomException: " + std::string(ex.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method 5: Disable range iteration for problematic types
|
||||||
|
namespace Catch {
|
||||||
|
template<>
|
||||||
|
struct is_range<ProblematicType> {
|
||||||
|
static const bool value = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running and Debugging Tests
|
||||||
|
|
||||||
|
### Building Tests
|
||||||
|
```bash
|
||||||
|
# Build all tests
|
||||||
|
cd build && make
|
||||||
|
|
||||||
|
# Build specific test suite
|
||||||
|
cd build && make libslic3r_tests
|
||||||
|
|
||||||
|
# Build and run tests
|
||||||
|
cd build && make && ctest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
#### Essential Test Execution Patterns
|
||||||
|
```bash
|
||||||
|
# REQUIRED: Random order with assertion warnings (best practice)
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --order rand --warn NoAssertions
|
||||||
|
|
||||||
|
# Run all tests with verbose output via CTest
|
||||||
|
cd build && ctest --output-on-failure
|
||||||
|
|
||||||
|
# Run specific test suite with best practices
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --order rand --warn NoAssertions
|
||||||
|
|
||||||
|
# Filter tests with specific tags
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests "[Geometry]" --order rand
|
||||||
|
|
||||||
|
# Filter by test name patterns
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests "*geometry*" --order rand
|
||||||
|
|
||||||
|
# Exclude tests (negation)
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests "~[Performance]" --order rand
|
||||||
|
|
||||||
|
# Combine filters: (Geometry AND Config) OR Algorithm
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests "[Geometry][Config],[Algorithm]" --order rand
|
||||||
|
|
||||||
|
# List available tests, tags, and reporters
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --list-tests
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --list-tags
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --list-reporters
|
||||||
|
|
||||||
|
# Debug failing tests
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --break # Break into debugger on failure
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --success # Show passing tests too
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --durations yes # Show timing info
|
||||||
|
|
||||||
|
# Abort on first failure
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --abort
|
||||||
|
|
||||||
|
# Test sharding for parallel execution (MUST share random seed)
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --order rand --shard-index 0 --shard-count 4 --rng-seed 0xBEEF &
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --order rand --shard-index 1 --shard-count 4 --rng-seed 0xBEEF &
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --order rand --shard-index 2 --shard-count 4 --rng-seed 0xBEEF &
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --order rand --shard-index 3 --shard-count 4 --rng-seed 0xBEEF &
|
||||||
|
wait # Wait for all to complete
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Reporter Options for CI Integration
|
||||||
|
```bash
|
||||||
|
# Different output formats for CI systems
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --reporter console # Default human-readable
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --reporter compact # Minimal output
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --reporter xml # Catch2 XML format
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --reporter junit # JUnit XML (widely supported)
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --reporter tap # Test Anything Protocol
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --reporter teamcity # TeamCity integration
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --reporter sonarqube # SonarQube integration
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --reporter automake # Automake integration
|
||||||
|
|
||||||
|
# Multiple reporters simultaneously (if supported)
|
||||||
|
cd build && ./tests/libslic3r/libslic3r_tests --reporter console --reporter junit::out=results.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Output Control
|
||||||
|
The custom `VerboseConsoleReporter` provides enhanced output:
|
||||||
|
- Test case start/end notifications with timing
|
||||||
|
- Section execution tracking
|
||||||
|
- Color-coded success/failure indicators
|
||||||
|
- Duration reporting for performance analysis
|
||||||
|
|
||||||
|
## Test Data Management
|
||||||
|
|
||||||
|
### Using TEST_DATA_DIR
|
||||||
|
All test data is accessible via the `TEST_DATA_DIR` preprocessor definition:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::string mesh_path = std::string(TEST_DATA_DIR) + "/20mm_cube.obj";
|
||||||
|
std::string config_path = std::string(TEST_DATA_DIR) + "/test_config/printer.ini";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Test Assets
|
||||||
|
|
||||||
|
#### 3D Models
|
||||||
|
- **Basic shapes**: `20mm_cube.obj`, `pyramid.obj`, `sphere.obj`
|
||||||
|
- **Complex geometry**: `extruder_idler.obj`, `ipadstand.obj`, `bridge.obj`
|
||||||
|
- **Edge cases**: `cube_with_hole.obj`, `sloping_hole.obj`, `small_dorito.obj`
|
||||||
|
|
||||||
|
#### File Format Tests
|
||||||
|
- **STL variants**: ASCII/binary, different line endings, Unicode names
|
||||||
|
- **3MF files**: Multi-material, complex assemblies
|
||||||
|
- **Configuration files**: Various printer/material profiles
|
||||||
|
|
||||||
|
#### Test Utilities
|
||||||
|
The `Test` namespace provides helper functions:
|
||||||
|
```cpp
|
||||||
|
using namespace Slic3r::Test;
|
||||||
|
|
||||||
|
// Load standard test meshes
|
||||||
|
TriangleMesh mesh = mesh(TestMesh::cube_20x20x20);
|
||||||
|
|
||||||
|
// Standard test configurations
|
||||||
|
DynamicPrintConfig config = config(TestConfig::PLA_default);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Pitfalls and Solutions
|
||||||
|
|
||||||
|
### Floating-Point Comparisons
|
||||||
|
|
||||||
|
> **CRITICAL**: Never use Approx - it's deprecated due to asymmetry and other issues
|
||||||
|
|
||||||
|
❌ **Incorrect**:
|
||||||
|
```cpp
|
||||||
|
REQUIRE(calculated_volume == expected_volume); // Exact equality
|
||||||
|
REQUIRE(calculated_volume == Catch::Approx(expected)); // Deprecated! Asymmetric!
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Correct**: Always use floating point matchers
|
||||||
|
```cpp
|
||||||
|
// Absolute tolerance - good when values are near zero
|
||||||
|
REQUIRE_THAT(calculated_volume, WithinAbs(expected_volume, 0.001));
|
||||||
|
|
||||||
|
// Relative tolerance - good for values with different magnitudes
|
||||||
|
REQUIRE_THAT(calculated_volume, WithinRel(expected_volume, 0.01)); // 1% tolerance
|
||||||
|
|
||||||
|
// ULP (Units in Last Place) - most precise, requires IEEE-754
|
||||||
|
REQUIRE_THAT(calculated_volume, WithinULP(expected_volume, 4));
|
||||||
|
|
||||||
|
// Combined approach - relative OR absolute
|
||||||
|
REQUIRE_THAT(calculated_volume,
|
||||||
|
WithinRel(expected_volume, 0.001) || WithinAbs(0.0, 0.000001));
|
||||||
|
|
||||||
|
// Precision control for output
|
||||||
|
Catch::StringMaker<double>::precision = 15; // Show more decimal places
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why Approx is Problematic:
|
||||||
|
- **Asymmetric**: `Approx(10).epsilon(0.1) != 11.1` but `Approx(11.1).epsilon(0.1) == 10`
|
||||||
|
- **Double-only**: All computation done in `double`, causes issues with `float` inputs
|
||||||
|
- **Default behavior**: Only uses relative comparison, so `Approx(0) == X` only works for `X == 0`
|
||||||
|
|
||||||
|
### Path Handling
|
||||||
|
❌ **Incorrect**:
|
||||||
|
```cpp
|
||||||
|
std::string path = TEST_DATA_DIR + "/model.obj"; // May have path separator issues
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Correct**:
|
||||||
|
```cpp
|
||||||
|
std::string path = std::string(TEST_DATA_DIR) + "/model.obj";
|
||||||
|
// or use boost::filesystem for complex path operations
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exception Testing
|
||||||
|
❌ **Incorrect**:
|
||||||
|
```cpp
|
||||||
|
bool threw_exception = false;
|
||||||
|
try {
|
||||||
|
risky_function();
|
||||||
|
} catch (...) {
|
||||||
|
threw_exception = true;
|
||||||
|
}
|
||||||
|
REQUIRE(threw_exception);
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Correct**:
|
||||||
|
```cpp
|
||||||
|
REQUIRE_THROWS(risky_function());
|
||||||
|
// or for specific exceptions
|
||||||
|
REQUIRE_THROWS_AS(risky_function(), SpecificException);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Thread Safety
|
||||||
|
|
||||||
|
⚠️ **CRITICAL**: Catch2 assertions are **NOT thread-safe** by default!
|
||||||
|
|
||||||
|
> **Note**: Catch2 v3.9.0+ has opt-in thread-safe assertions via `CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS`, but OrcaSlicer uses v2
|
||||||
|
|
||||||
|
❌ **Incorrect**: Will cause undefined behavior or crashes
|
||||||
|
```cpp
|
||||||
|
std::thread t([&]() {
|
||||||
|
REQUIRE(threaded_operation() == expected); // NOT THREAD-SAFE!
|
||||||
|
CHECK(other_operation()); // NOT THREAD-SAFE!
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Correct**: Collect results, assert on main thread
|
||||||
|
```cpp
|
||||||
|
std::atomic<bool> success{false};
|
||||||
|
std::atomic<int> error_count{0};
|
||||||
|
|
||||||
|
std::thread t([&]() {
|
||||||
|
// Do work in thread, collect results
|
||||||
|
bool result1 = (threaded_operation() == expected);
|
||||||
|
bool result2 = other_operation();
|
||||||
|
|
||||||
|
if (result1 && result2) {
|
||||||
|
success = true;
|
||||||
|
} else {
|
||||||
|
error_count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
t.join();
|
||||||
|
|
||||||
|
// Assert results on main thread
|
||||||
|
REQUIRE(success);
|
||||||
|
REQUIRE(error_count == 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Thread Safety Rules:
|
||||||
|
- **REQUIRE family**: Would terminate process in spawned threads (throws exception with no try-catch)
|
||||||
|
- **CHECK family**: Not thread-safe, can corrupt internal state
|
||||||
|
- **SKIP, FAIL, SUCCEED**: Not thread-safe even with v3 thread-safe assertions
|
||||||
|
- **Message macros**: INFO, CAPTURE, WARN - not thread-safe
|
||||||
|
- **STATIC_REQUIRE/CHECK**: Not thread-safe (relies on runtime registration)
|
||||||
|
|
||||||
|
### Memory Management
|
||||||
|
Use RAII and smart pointers in tests:
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Resource management", "[Memory]") {
|
||||||
|
auto model = std::make_unique<Model>();
|
||||||
|
// Automatic cleanup on test completion/failure
|
||||||
|
|
||||||
|
REQUIRE(model->objects.empty());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Compilation Optimizations
|
||||||
|
```cpp
|
||||||
|
// In CMakeLists.txt or as preprocessor definition
|
||||||
|
#define CATCH_CONFIG_FAST_COMPILE // 20% faster compilation, disables some features
|
||||||
|
|
||||||
|
// For faster test iteration during development
|
||||||
|
#define CATCH_CONFIG_DISABLE_STRINGIFICATION // Workaround for VS2017 raw string bug
|
||||||
|
```
|
||||||
|
|
||||||
|
### Runtime Performance
|
||||||
|
```cpp
|
||||||
|
TEST_CASE("Performance-sensitive test", "[Performance]") {
|
||||||
|
// Manual timing for Catch2 v2 (v3 has built-in benchmarking)
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
auto result = expensive_operation();
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||||
|
|
||||||
|
REQUIRE(result.is_valid());
|
||||||
|
REQUIRE(duration.count() < 1000); // Should complete in < 1 second
|
||||||
|
|
||||||
|
INFO("Operation took " << duration.count() << "ms");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Leak Detection
|
||||||
|
```cpp
|
||||||
|
// For Windows builds - detects memory leaks
|
||||||
|
#define CATCH_CONFIG_WINDOWS_CRTDBG // Must be defined for whole build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with CMake
|
||||||
|
|
||||||
|
### Adding New Test Files
|
||||||
|
1. Create test file: `test_new_feature.cpp`
|
||||||
|
2. Add to appropriate `CMakeLists.txt`:
|
||||||
|
```cmake
|
||||||
|
add_executable(${_TEST_NAME}_tests
|
||||||
|
${_TEST_NAME}_tests.cpp
|
||||||
|
test_existing_feature.cpp
|
||||||
|
test_new_feature.cpp # Add here
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Test Discovery
|
||||||
|
```cmake
|
||||||
|
# Basic test discovery
|
||||||
|
catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||||
|
|
||||||
|
# Advanced test discovery with customization
|
||||||
|
catch_discover_tests(${_TEST_NAME}_tests
|
||||||
|
TEST_PREFIX "${_TEST_NAME}: "
|
||||||
|
TEST_SUFFIX " (auto)"
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
EXTRA_ARGS --order rand --warn NoAssertions
|
||||||
|
PROPERTIES
|
||||||
|
TIMEOUT 300
|
||||||
|
LABELS "unit;core"
|
||||||
|
DISCOVERY_MODE PRE_TEST # or POST_BUILD
|
||||||
|
REPORTER junit
|
||||||
|
OUTPUT_DIR ${CMAKE_BINARY_DIR}/test-results
|
||||||
|
OUTPUT_PREFIX "results_"
|
||||||
|
OUTPUT_SUFFIX ".xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test sharding for parallel execution
|
||||||
|
include(CatchShardTests) # If available
|
||||||
|
catch_shard_tests(${_TEST_NAME}_tests
|
||||||
|
SHARD_COUNT 4
|
||||||
|
TEST_PREFIX "${_TEST_NAME}_shard: "
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conditional Test Compilation
|
||||||
|
```cmake
|
||||||
|
# Feature-dependent tests
|
||||||
|
if (TARGET OpenVDB::openvdb)
|
||||||
|
target_sources(${_TEST_NAME}_tests PRIVATE test_hollowing.cpp)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Platform-specific tests
|
||||||
|
if(WIN32)
|
||||||
|
target_sources(${_TEST_NAME}_tests PRIVATE test_windows_specific.cpp)
|
||||||
|
elseif(UNIX)
|
||||||
|
target_sources(${_TEST_NAME}_tests PRIVATE test_unix_specific.cpp)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Compiler-specific workarounds
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_definitions(${_TEST_NAME}_tests PRIVATE CATCH_CONFIG_DISABLE_STRINGIFICATION)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Fast compile mode for development
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
target_compile_definitions(${_TEST_NAME}_tests PRIVATE CATCH_CONFIG_FAST_COMPILE)
|
||||||
|
endif()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Known Issues and Workarounds
|
||||||
|
|
||||||
|
### Platform-Specific Issues
|
||||||
|
```cpp
|
||||||
|
// MinGW/CygWin slow linking workaround
|
||||||
|
// Use: -fuse-ld=lld flag to speed up linking significantly
|
||||||
|
|
||||||
|
// Visual Studio 2017 raw string literal bug
|
||||||
|
#define CATCH_CONFIG_DISABLE_STRINGIFICATION
|
||||||
|
// This disables expression stringification but works around the compiler bug
|
||||||
|
|
||||||
|
// Visual Studio 2022 spaceship operator issue
|
||||||
|
// REQUIRE((a <=> b) == 0); // May not compile with MSVC
|
||||||
|
// Workaround: use clang-cl or avoid spaceship in assertions
|
||||||
|
|
||||||
|
// QNX/VxWorks C stdlib issues
|
||||||
|
#include <cfoo> // Use C++ headers
|
||||||
|
std::foo_function(); // Always call qualified
|
||||||
|
// NOT: #include <foo.h> and foo_function();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Catch2 Version-Specific Limitations
|
||||||
|
```cpp
|
||||||
|
// OrcaSlicer uses Catch2 v2 - these features are NOT available:
|
||||||
|
// SKIP() macro - Available in v3.3.0+
|
||||||
|
// Thread-safe assertions - Available in v3.9.0+
|
||||||
|
// BENCHMARK improvements - Many in v3.x
|
||||||
|
// testCasePartial events - Available in v3.0.1+
|
||||||
|
// Multiple reporters - Available in v3.0.1+
|
||||||
|
// STATIC_CHECK macro - Available in v3.0.1+
|
||||||
|
|
||||||
|
// v2 Limitations to remember:
|
||||||
|
// - Sections can be re-run if last section fails
|
||||||
|
// - String matcher is "Contains" not "ContainsSubstring"
|
||||||
|
// - Limited benchmarking support compared to v3
|
||||||
|
// - No test sharding built-in
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Organization Best Practices
|
||||||
|
|
||||||
|
#### Project Structure Rules
|
||||||
|
1. **1:1 correspondence**: One test binary per library/module
|
||||||
|
2. **Hidden tests**: Use `[.]` or `[!benchmark]` tags for tests that shouldn't run by default
|
||||||
|
3. **Tag hierarchy**: Use consistent tagging scheme across the project
|
||||||
|
4. **File naming**: Follow `test_<feature>.cpp` pattern
|
||||||
|
|
||||||
|
#### CI/CD Integration
|
||||||
|
```bash
|
||||||
|
# Essential CI test command
|
||||||
|
./tests --order rand --warn NoAssertions --reporter junit::out=results.xml
|
||||||
|
|
||||||
|
# For coverage analysis
|
||||||
|
./tests --order rand --warn NoAssertions --reporter console --success
|
||||||
|
|
||||||
|
# For performance tracking
|
||||||
|
./tests --order rand --warn NoAssertions --durations yes
|
||||||
|
```
|
||||||
|
|
||||||
|
This comprehensive guide ensures robust, maintainable, and efficient testing practices for OrcaSlicer development with Claude Code, incorporating all critical knowledge from the official Catch2 documentation.
|
||||||
@@ -31,6 +31,6 @@ add_subdirectory(libnest2d)
|
|||||||
add_subdirectory(libslic3r)
|
add_subdirectory(libslic3r)
|
||||||
add_subdirectory(slic3rutils)
|
add_subdirectory(slic3rutils)
|
||||||
add_subdirectory(fff_print)
|
add_subdirectory(fff_print)
|
||||||
add_subdirectory(sla_print)
|
# add_subdirectory(sla_print)
|
||||||
add_subdirectory(cpp17 EXCLUDE_FROM_ALL) # does not have to be built all the time
|
add_subdirectory(cpp17 EXCLUDE_FROM_ALL) # does not have to be built all the time
|
||||||
# add_subdirectory(example)
|
# add_subdirectory(example)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.1)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
project(Cpp17Test)
|
project(Cpp17Test)
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,14 @@ target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
|||||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
bambuslicer_copy_dlls(${_TEST_NAME}_tests)
|
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||||
|
orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" output_dlls_Debug)
|
||||||
|
elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
|
||||||
|
orcaslicer_copy_dlls(COPY_DLLS "RelWithDebInfo" "" output_dlls_Release)
|
||||||
|
else()
|
||||||
|
orcaslicer_copy_dlls(COPY_DLLS "Release" "" output_dlls_Release)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})
|
# add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWr
|
|||||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||||
REQUIRE(writer.lift().size() > 0);
|
REQUIRE(writer.lift().size() > 0);
|
||||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
REQUIRE(writer.travel_to_z(trouble_Z + config.z_hop.values[0]).size() == 0);
|
||||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||||
@@ -38,7 +38,7 @@ SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWr
|
|||||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||||
REQUIRE(writer.lift().size() > 0);
|
REQUIRE(writer.lift().size() > 0);
|
||||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
REQUIRE(writer.travel_to_z(trouble_Z + config.z_hop.values[0]).size() == 0);
|
||||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||||
@@ -54,7 +54,7 @@ SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWr
|
|||||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||||
REQUIRE(writer.lift().size() > 0);
|
REQUIRE(writer.lift().size() > 0);
|
||||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
REQUIRE(writer.travel_to_z(trouble_Z + config.z_hop.values[0]).size() == 0);
|
||||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||||
|
|||||||
@@ -99,7 +99,11 @@ SCENARIO("Print: Brim generation", "[Print]") {
|
|||||||
{ "brim_width", 3 }
|
{ "brim_width", 3 }
|
||||||
});
|
});
|
||||||
THEN("Brim Extrusion collection has 3 loops in it") {
|
THEN("Brim Extrusion collection has 3 loops in it") {
|
||||||
REQUIRE(print.brim().items_count() == 3);
|
size_t total_items = 0;
|
||||||
|
for (const auto& pair : print.get_brimMap()) {
|
||||||
|
total_items += pair.second.items_count();
|
||||||
|
}
|
||||||
|
REQUIRE(total_items == 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WHEN("Brim is set to 6mm") {
|
WHEN("Brim is set to 6mm") {
|
||||||
@@ -109,7 +113,11 @@ SCENARIO("Print: Brim generation", "[Print]") {
|
|||||||
{ "brim_width", 6 }
|
{ "brim_width", 6 }
|
||||||
});
|
});
|
||||||
THEN("Brim Extrusion collection has 6 loops in it") {
|
THEN("Brim Extrusion collection has 6 loops in it") {
|
||||||
REQUIRE(print.brim().items_count() == 6);
|
size_t total_items = 0;
|
||||||
|
for (const auto& pair : print.get_brimMap()) {
|
||||||
|
total_items += pair.second.items_count();
|
||||||
|
}
|
||||||
|
REQUIRE(total_items == 6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WHEN("Brim is set to 6mm, extrusion width 0.5mm") {
|
WHEN("Brim is set to 6mm, extrusion width 0.5mm") {
|
||||||
@@ -121,7 +129,11 @@ SCENARIO("Print: Brim generation", "[Print]") {
|
|||||||
});
|
});
|
||||||
print.process();
|
print.process();
|
||||||
THEN("Brim Extrusion collection has 12 loops in it") {
|
THEN("Brim Extrusion collection has 12 loops in it") {
|
||||||
REQUIRE(print.brim().items_count() == 14);
|
size_t total_items = 0;
|
||||||
|
for (const auto& pair : print.get_brimMap()) {
|
||||||
|
total_items += pair.second.items_count();
|
||||||
|
}
|
||||||
|
REQUIRE(total_items == 14);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,11 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
|
|||||||
THEN("2 brim lines") {
|
THEN("2 brim lines") {
|
||||||
Slic3r::Print print;
|
Slic3r::Print print;
|
||||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
|
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
|
||||||
REQUIRE(print.brim().entities.size() == 2);
|
size_t total_entities = 0;
|
||||||
|
for (const auto& pair : print.get_brimMap()) {
|
||||||
|
total_entities += pair.second.entities.size();
|
||||||
|
}
|
||||||
|
REQUIRE(total_entities == 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ SCENARIO("SupportMaterial: support_layers_z and contact_distance", "[SupportMate
|
|||||||
{
|
{
|
||||||
ConstSupportLayerPtrsAdaptor support_layers = print.objects().front()->support_layers();
|
ConstSupportLayerPtrsAdaptor support_layers = print.objects().front()->support_layers();
|
||||||
|
|
||||||
first_support_layer_height_ok = support_layers.front()->print_z == print.config().first_layer_height.value;
|
first_support_layer_height_ok = support_layers.front()->print_z == print.config().initial_layer_print_height.value;
|
||||||
|
|
||||||
layer_height_minimum_ok = true;
|
layer_height_minimum_ok = true;
|
||||||
layer_height_maximum_ok = true;
|
layer_height_maximum_ok = true;
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp printer_parts.cp
|
|||||||
target_link_libraries(${_TEST_NAME}_tests test_common libnest2d )
|
target_link_libraries(${_TEST_NAME}_tests test_common libnest2d )
|
||||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||||
|
|
||||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "${CATCH_EXTRA_ARGS} exclude:[NotWorking]")
|
# add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "${CATCH_EXTRA_ARGS} exclude:[NotWorking]")
|
||||||
|
|||||||
@@ -1161,7 +1161,7 @@ TEST_CASE("Test for bed center distance optimization", "[Nesting], [NestKernels]
|
|||||||
|
|
||||||
NfpPlacer::Config pconfig;
|
NfpPlacer::Config pconfig;
|
||||||
|
|
||||||
pconfig.object_function = [](const Item &item) -> double {
|
pconfig.object_function = [](const Item &item, const std::vector<std::reference_wrapper<Item>>&) -> double {
|
||||||
return pl::magnsq<PointImpl, double>(item.boundingBox().center());
|
return pl::magnsq<PointImpl, double>(item.boundingBox().center());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1205,7 +1205,7 @@ TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]")
|
|||||||
pile_box = sl::boundingBox(pile);
|
pile_box = sl::boundingBox(pile);
|
||||||
};
|
};
|
||||||
|
|
||||||
pconfig.object_function = [&pile_box](const Item &item) -> double {
|
pconfig.object_function = [&pile_box](const Item &item, const std::vector<std::reference_wrapper<Item>>&) -> double {
|
||||||
Box b = sl::boundingBox(item.boundingBox(), pile_box);
|
Box b = sl::boundingBox(item.boundingBox(), pile_box);
|
||||||
double area = b.area<double>() / (double(W) * W);
|
double area = b.area<double>() / (double(W) * W);
|
||||||
return -area;
|
return -area;
|
||||||
|
|||||||
@@ -15,12 +15,11 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_mutable_priority_queue.cpp
|
test_mutable_priority_queue.cpp
|
||||||
test_stl.cpp
|
test_stl.cpp
|
||||||
test_meshboolean.cpp
|
test_meshboolean.cpp
|
||||||
test_marchingsquares.cpp
|
# test_marchingsquares.cpp
|
||||||
test_timeutils.cpp
|
test_timeutils.cpp
|
||||||
test_voronoi.cpp
|
test_voronoi.cpp
|
||||||
test_optimizers.cpp
|
test_optimizers.cpp
|
||||||
test_png_io.cpp
|
# test_png_io.cpp
|
||||||
test_timeutils.cpp
|
|
||||||
test_indexed_triangle_set.cpp
|
test_indexed_triangle_set.cpp
|
||||||
../libnest2d/printer_parts.cpp
|
../libnest2d/printer_parts.cpp
|
||||||
)
|
)
|
||||||
@@ -33,8 +32,14 @@ target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
|||||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
bambuslicer_copy_dlls(${_TEST_NAME}_tests)
|
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||||
|
orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" output_dlls_Debug)
|
||||||
|
elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
|
||||||
|
orcaslicer_copy_dlls(COPY_DLLS "RelWithDebInfo" "" output_dlls_Release)
|
||||||
|
else()
|
||||||
|
orcaslicer_copy_dlls(COPY_DLLS "Release" "" output_dlls_Release)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})
|
# add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})
|
||||||
|
|||||||
@@ -225,8 +225,8 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<e_ordering o = e_ordering::OFF, class P, class Tree>
|
template<e_ordering o = e_ordering::OFF, class P, class Tree, class Alloc>
|
||||||
double polytree_area(const Tree &tree, std::vector<P> *out)
|
double polytree_area(const Tree &tree, std::vector<P, Alloc> *out)
|
||||||
{
|
{
|
||||||
traverse_pt<o>(tree, out);
|
traverse_pt<o>(tree, out);
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ TEST_CASE("Line::perpendicular_to", "[Geometry]") {
|
|||||||
|
|
||||||
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
|
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
|
||||||
// this test was failing on Windows (GH #1950)
|
// this test was failing on Windows (GH #1950)
|
||||||
Slic3r::Polygon polygon(std::vector<Point>({
|
Slic3r::Polygon polygon(Points({
|
||||||
Point(207802834,-57084522),
|
Point(207802834,-57084522),
|
||||||
Point(196528149,-37556190),
|
Point(196528149,-37556190),
|
||||||
Point(173626821,-25420928),
|
Point(173626821,-25420928),
|
||||||
@@ -145,7 +145,7 @@ SCENARIO("polygon_is_convex works") {
|
|||||||
|
|
||||||
TEST_CASE("Creating a polyline generates the obvious lines", "[Geometry]"){
|
TEST_CASE("Creating a polyline generates the obvious lines", "[Geometry]"){
|
||||||
Slic3r::Polyline polyline;
|
Slic3r::Polyline polyline;
|
||||||
polyline.points = std::vector<Point>({Point(0, 0), Point(10, 0), Point(20, 0)});
|
polyline.points = Points({Point(0, 0), Point(10, 0), Point(20, 0)});
|
||||||
REQUIRE(polyline.lines().at(0).a == Point(0,0));
|
REQUIRE(polyline.lines().at(0).a == Point(0,0));
|
||||||
REQUIRE(polyline.lines().at(0).b == Point(10,0));
|
REQUIRE(polyline.lines().at(0).b == Point(10,0));
|
||||||
REQUIRE(polyline.lines().at(1).a == Point(10,0));
|
REQUIRE(polyline.lines().at(1).a == Point(10,0));
|
||||||
@@ -153,7 +153,7 @@ TEST_CASE("Creating a polyline generates the obvious lines", "[Geometry]"){
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){
|
TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){
|
||||||
Slic3r::Polygon polygon(std::vector<Point>({Point(0, 0), Point(10, 0), Point(5, 5)}));
|
Slic3r::Polygon polygon(Points({Point(0, 0), Point(10, 0), Point(5, 5)}));
|
||||||
Slic3r::Polyline split = polygon.split_at_index(1);
|
Slic3r::Polyline split = polygon.split_at_index(1);
|
||||||
REQUIRE(split.points[0]==Point(10,0));
|
REQUIRE(split.points[0]==Point(10,0));
|
||||||
REQUIRE(split.points[1]==Point(5,5));
|
REQUIRE(split.points[1]==Point(5,5));
|
||||||
@@ -163,7 +163,7 @@ TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){
|
|||||||
|
|
||||||
|
|
||||||
TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){
|
TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){
|
||||||
BoundingBox bb(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
|
BoundingBox bb(Points({Point(0, 1), Point(10, 2), Point(20, 2)}));
|
||||||
bb.scale(2);
|
bb.scale(2);
|
||||||
REQUIRE(bb.min == Point(0,2));
|
REQUIRE(bb.min == Point(0,2));
|
||||||
REQUIRE(bb.max == Point(40,4));
|
REQUIRE(bb.max == Point(40,4));
|
||||||
@@ -173,7 +173,7 @@ TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){
|
|||||||
TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
|
TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
|
||||||
Slic3r::Polyline tmp = { Point(10,10), Point(20,10) };
|
Slic3r::Polyline tmp = { Point(10,10), Point(20,10) };
|
||||||
Slic3r::Polygon area = offset(tmp,5).at(0);
|
Slic3r::Polygon area = offset(tmp,5).at(0);
|
||||||
REQUIRE(area.area() == Slic3r::Polygon(std::vector<Point>({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
|
REQUIRE(area.area() == Slic3r::Polygon(Points({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
|
||||||
}
|
}
|
||||||
|
|
||||||
SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
|
SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
|
||||||
@@ -288,10 +288,10 @@ TEST_CASE("smallest_enclosing_circle_welzl", "[Geometry]") {
|
|||||||
|
|
||||||
SCENARIO("Path chaining", "[Geometry]") {
|
SCENARIO("Path chaining", "[Geometry]") {
|
||||||
GIVEN("A path") {
|
GIVEN("A path") {
|
||||||
std::vector<Point> points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
|
Points points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
|
||||||
THEN("Chained with no diagonals (thus 26 units long)") {
|
THEN("Chained with no diagonals (thus 26 units long)") {
|
||||||
std::vector<Points::size_type> indices = chain_points(points);
|
std::vector<size_t> indices = chain_points(points);
|
||||||
for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) {
|
for (size_t i = 0; i + 1 < indices.size(); ++ i) {
|
||||||
double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm();
|
double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm();
|
||||||
REQUIRE(std::abs(dist-26) <= EPSILON);
|
REQUIRE(std::abs(dist-26) <= EPSILON);
|
||||||
}
|
}
|
||||||
@@ -375,7 +375,7 @@ SCENARIO("Line distances", "[Geometry]"){
|
|||||||
|
|
||||||
SCENARIO("Polygon convex/concave detection", "[Geometry]"){
|
SCENARIO("Polygon convex/concave detection", "[Geometry]"){
|
||||||
GIVEN(("A Square with dimension 100")){
|
GIVEN(("A Square with dimension 100")){
|
||||||
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
|
auto square = Slic3r::Polygon /*new_scale*/(Points({
|
||||||
Point(100,100),
|
Point(100,100),
|
||||||
Point(200,100),
|
Point(200,100),
|
||||||
Point(200,200),
|
Point(200,200),
|
||||||
@@ -391,7 +391,7 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
GIVEN("A Square with an extra colinearvertex"){
|
GIVEN("A Square with an extra colinearvertex"){
|
||||||
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
|
auto square = Slic3r::Polygon /*new_scale*/(Points({
|
||||||
Point(150,100),
|
Point(150,100),
|
||||||
Point(200,100),
|
Point(200,100),
|
||||||
Point(200,200),
|
Point(200,200),
|
||||||
@@ -403,7 +403,7 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
GIVEN("A Square with an extra collinear vertex in different order"){
|
GIVEN("A Square with an extra collinear vertex in different order"){
|
||||||
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
|
auto square = Slic3r::Polygon /*new_scale*/(Points({
|
||||||
Point(200,200),
|
Point(200,200),
|
||||||
Point(100,200),
|
Point(100,200),
|
||||||
Point(100,100),
|
Point(100,100),
|
||||||
@@ -416,7 +416,7 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){
|
|||||||
}
|
}
|
||||||
|
|
||||||
GIVEN("A triangle"){
|
GIVEN("A triangle"){
|
||||||
auto triangle = Slic3r::Polygon(std::vector<Point>({
|
auto triangle = Slic3r::Polygon(Points({
|
||||||
Point(16000170,26257364),
|
Point(16000170,26257364),
|
||||||
Point(714223,461012),
|
Point(714223,461012),
|
||||||
Point(31286371,461008)
|
Point(31286371,461008)
|
||||||
@@ -428,7 +428,7 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){
|
|||||||
}
|
}
|
||||||
|
|
||||||
GIVEN("A triangle with an extra collinear point"){
|
GIVEN("A triangle with an extra collinear point"){
|
||||||
auto triangle = Slic3r::Polygon(std::vector<Point>({
|
auto triangle = Slic3r::Polygon(Points({
|
||||||
Point(16000170,26257364),
|
Point(16000170,26257364),
|
||||||
Point(714223,461012),
|
Point(714223,461012),
|
||||||
Point(20000000,461012),
|
Point(20000000,461012),
|
||||||
@@ -442,7 +442,7 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){
|
|||||||
GIVEN("A polygon with concave vertices with angles of specifically 4/3pi"){
|
GIVEN("A polygon with concave vertices with angles of specifically 4/3pi"){
|
||||||
// Two concave vertices of this polygon have angle = PI*4/3, so this test fails
|
// Two concave vertices of this polygon have angle = PI*4/3, so this test fails
|
||||||
// if epsilon is not used.
|
// if epsilon is not used.
|
||||||
auto polygon = Slic3r::Polygon(std::vector<Point>({
|
auto polygon = Slic3r::Polygon(Points({
|
||||||
Point(60246458,14802768),Point(64477191,12360001),
|
Point(60246458,14802768),Point(64477191,12360001),
|
||||||
Point(63727343,11060995),Point(64086449,10853608),
|
Point(63727343,11060995),Point(64086449,10853608),
|
||||||
Point(66393722,14850069),Point(66034704,15057334),
|
Point(66393722,14850069),Point(66034704,15057334),
|
||||||
@@ -460,7 +460,7 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Triangle Simplification does not result in less than 3 points", "[Geometry]"){
|
TEST_CASE("Triangle Simplification does not result in less than 3 points", "[Geometry]"){
|
||||||
auto triangle = Slic3r::Polygon(std::vector<Point>({
|
auto triangle = Slic3r::Polygon(Points({
|
||||||
Point(16000170,26257364), Point(714223,461012), Point(31286371,461008)
|
Point(16000170,26257364), Point(714223,461012), Point(31286371,461008)
|
||||||
}));
|
}));
|
||||||
REQUIRE(triangle.simplify(250000).at(0).points.size() == 3);
|
REQUIRE(triangle.simplify(250000).at(0).points.size() == 3);
|
||||||
|
|||||||
@@ -110,11 +110,11 @@ static float triangle_area(const Vec3f &v0, const Vec3f &v1, const Vec3f &v2)
|
|||||||
return ab.cross(ac).norm() / 2.f;
|
return ab.cross(ac).norm() / 2.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float triangle_area(const Vec3crd &triangle_inices, const std::vector<Vec3f> &vertices)
|
static float triangle_area(const stl_triangle_vertex_indices &triangle_indices, const std::vector<Vec3f> &vertices)
|
||||||
{
|
{
|
||||||
return triangle_area(vertices[triangle_inices[0]],
|
return triangle_area(vertices[triangle_indices[0]],
|
||||||
vertices[triangle_inices[1]],
|
vertices[triangle_indices[1]],
|
||||||
vertices[triangle_inices[2]]);
|
vertices[triangle_indices[2]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
|
|||||||
@@ -19,10 +19,12 @@
|
|||||||
|
|
||||||
using boost::polygon::voronoi_builder;
|
using boost::polygon::voronoi_builder;
|
||||||
using boost::polygon::voronoi_diagram;
|
using boost::polygon::voronoi_diagram;
|
||||||
|
using boost::polygon::construct_voronoi;
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
using VD = Geometry::VoronoiDiagram;
|
using VD = Geometry::VoronoiDiagram;
|
||||||
|
using BoostVD = boost::polygon::voronoi_diagram<double>;
|
||||||
|
|
||||||
// https://svn.boost.org/trac10/ticket/12067
|
// https://svn.boost.org/trac10/ticket/12067
|
||||||
// This bug seems to be confirmed.
|
// This bug seems to be confirmed.
|
||||||
@@ -60,7 +62,7 @@ TEST_CASE("Voronoi missing edges - points 12067", "[Voronoi]")
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construction of the Voronoi Diagram.
|
// Construction of the Voronoi Diagram.
|
||||||
VD vd;
|
BoostVD vd;
|
||||||
construct_voronoi(pts.begin(), pts.end(), &vd);
|
construct_voronoi(pts.begin(), pts.end(), &vd);
|
||||||
|
|
||||||
#ifdef VORONOI_DEBUG_OUT
|
#ifdef VORONOI_DEBUG_OUT
|
||||||
@@ -188,7 +190,7 @@ TEST_CASE("Voronoi missing edges - Alessandro gapfill 12707", "[Voronoi]")
|
|||||||
REQUIRE(intersecting_edges({ poly }).empty());
|
REQUIRE(intersecting_edges({ poly }).empty());
|
||||||
|
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
VD vd;
|
BoostVD vd;
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||||
|
|
||||||
#ifdef VORONOI_DEBUG_OUT
|
#ifdef VORONOI_DEBUG_OUT
|
||||||
@@ -295,7 +297,7 @@ TEST_CASE("Voronoi weirdness", "[Voronoi]")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
VD vd;
|
BoostVD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||||
|
|
||||||
@@ -320,7 +322,7 @@ TEST_CASE("Voronoi division by zero 12903", "[Voronoi]")
|
|||||||
REQUIRE(std::unique(pts2.begin(), pts2.end()) == pts2.end());
|
REQUIRE(std::unique(pts2.begin(), pts2.end()) == pts2.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
VD vd;
|
BoostVD vd;
|
||||||
construct_voronoi(pts.begin(), pts.end(), &vd);
|
construct_voronoi(pts.begin(), pts.end(), &vd);
|
||||||
|
|
||||||
#ifdef VORONOI_DEBUG_OUT
|
#ifdef VORONOI_DEBUG_OUT
|
||||||
@@ -1317,7 +1319,7 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
VD vd;
|
BoostVD vd;
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||||
|
|
||||||
for (const auto& edge : vd.edges())
|
for (const auto& edge : vd.edges())
|
||||||
@@ -1359,7 +1361,7 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]")
|
|||||||
|
|
||||||
VD vd;
|
VD vd;
|
||||||
Lines lines = to_lines(poly_with_hole);
|
Lines lines = to_lines(poly_with_hole);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
vd.construct_voronoi(lines.begin(), lines.end());
|
||||||
|
|
||||||
for (const OffsetTest &ot : {
|
for (const OffsetTest &ot : {
|
||||||
OffsetTest { scale_(0.2), 1, 1 },
|
OffsetTest { scale_(0.2), 1, 1 },
|
||||||
@@ -1425,7 +1427,7 @@ TEST_CASE("Voronoi offset 2", "[VoronoiOffset]")
|
|||||||
|
|
||||||
VD vd;
|
VD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
vd.construct_voronoi(lines.begin(), lines.end());
|
||||||
|
|
||||||
for (const OffsetTest &ot : {
|
for (const OffsetTest &ot : {
|
||||||
OffsetTest { scale_(0.2), 2, 2 },
|
OffsetTest { scale_(0.2), 2, 2 },
|
||||||
@@ -1495,7 +1497,7 @@ TEST_CASE("Voronoi offset 3", "[VoronoiOffset]")
|
|||||||
|
|
||||||
VD vd;
|
VD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
vd.construct_voronoi(lines.begin(), lines.end());
|
||||||
|
|
||||||
for (const OffsetTest &ot : {
|
for (const OffsetTest &ot : {
|
||||||
OffsetTest { scale_(0.2), 2, 2 },
|
OffsetTest { scale_(0.2), 2, 2 },
|
||||||
@@ -1746,7 +1748,7 @@ TEST_CASE("Voronoi offset with edge collapse", "[VoronoiOffset4]")
|
|||||||
|
|
||||||
VD vd;
|
VD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
vd.construct_voronoi(lines.begin(), lines.end());
|
||||||
|
|
||||||
for (const OffsetTest &ot : {
|
for (const OffsetTest &ot : {
|
||||||
OffsetTest { scale_(0.2), 2, 2 },
|
OffsetTest { scale_(0.2), 2, 2 },
|
||||||
@@ -1857,7 +1859,7 @@ TEST_CASE("Voronoi offset 5", "[VoronoiOffset5]")
|
|||||||
|
|
||||||
VD vd;
|
VD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
vd.construct_voronoi(lines.begin(), lines.end());
|
||||||
|
|
||||||
for (const OffsetTest &ot : {
|
for (const OffsetTest &ot : {
|
||||||
OffsetTest { scale_(2.8), 1, 1 },
|
OffsetTest { scale_(2.8), 1, 1 },
|
||||||
@@ -1915,7 +1917,7 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]")
|
|||||||
|
|
||||||
VD vd;
|
VD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
vd.construct_voronoi(lines.begin(), lines.end());
|
||||||
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
|
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
|
||||||
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
|
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
|
||||||
static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
|
static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
|
||||||
@@ -1966,7 +1968,7 @@ TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]")
|
|||||||
|
|
||||||
VD vd;
|
VD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
vd.construct_voronoi(lines.begin(), lines.end());
|
||||||
#ifdef VORONOI_DEBUG_OUT
|
#ifdef VORONOI_DEBUG_OUT
|
||||||
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines);
|
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines);
|
||||||
#endif
|
#endif
|
||||||
@@ -2006,7 +2008,7 @@ TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]")
|
|||||||
|
|
||||||
VD vd;
|
VD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
vd.construct_voronoi(lines.begin(), lines.end());
|
||||||
#ifdef VORONOI_DEBUG_OUT
|
#ifdef VORONOI_DEBUG_OUT
|
||||||
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines);
|
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines);
|
||||||
#endif
|
#endif
|
||||||
@@ -2045,7 +2047,7 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]")
|
|||||||
// polygons_rotate(poly, PI/180);
|
// polygons_rotate(poly, PI/180);
|
||||||
// polygons_rotate(poly, PI/6);
|
// polygons_rotate(poly, PI/6);
|
||||||
|
|
||||||
VD vd;
|
BoostVD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||||
#ifdef VORONOI_DEBUG_OUT
|
#ifdef VORONOI_DEBUG_OUT
|
||||||
@@ -2080,7 +2082,7 @@ TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]")
|
|||||||
|
|
||||||
VD vd;
|
VD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
vd.construct_voronoi(lines.begin(), lines.end());
|
||||||
#ifdef VORONOI_DEBUG_OUT
|
#ifdef VORONOI_DEBUG_OUT
|
||||||
dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines);
|
dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines);
|
||||||
#endif
|
#endif
|
||||||
@@ -2120,7 +2122,7 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]")
|
|||||||
|
|
||||||
VD vd;
|
VD vd;
|
||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
vd.construct_voronoi(lines.begin(), lines.end());
|
||||||
#ifdef VORONOI_DEBUG_OUT
|
#ifdef VORONOI_DEBUG_OUT
|
||||||
dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines);
|
dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp
|
|||||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
||||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||||
|
|
||||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})
|
# add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})
|
||||||
|
|||||||
@@ -10,8 +10,14 @@ endif ()
|
|||||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
bambuslicer_copy_dlls(${_TEST_NAME}_tests)
|
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||||
|
orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" output_dlls_Debug)
|
||||||
|
elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
|
||||||
|
orcaslicer_copy_dlls(COPY_DLLS "RelWithDebInfo" "" output_dlls_Release)
|
||||||
|
else()
|
||||||
|
orcaslicer_copy_dlls(COPY_DLLS "Release" "" output_dlls_Release)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "${CATCH_EXTRA_ARGS} exclude:[NotWorking]")
|
# add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "${CATCH_EXTRA_ARGS} exclude:[NotWorking]")
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ inline Slic3r::TriangleMesh load_model(const std::string &obj_filename)
|
|||||||
{
|
{
|
||||||
Slic3r::TriangleMesh mesh;
|
Slic3r::TriangleMesh mesh;
|
||||||
auto fpath = TEST_DATA_DIR PATH_SEPARATOR + obj_filename;
|
auto fpath = TEST_DATA_DIR PATH_SEPARATOR + obj_filename;
|
||||||
Slic3r::load_obj(fpath.c_str(), &mesh);
|
Slic3r::ObjInfo obj_info;
|
||||||
|
std::string message;
|
||||||
|
Slic3r::load_obj(fpath.c_str(), &mesh, obj_info, message);
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user