From 306d4b73efd3a09bcadd141fccdff503c11d7060 Mon Sep 17 00:00:00 2001 From: Kris Austin Date: Sun, 26 Jul 2026 10:53:56 -0500 Subject: [PATCH] test: stop littering the working directory with debug files (#14785) --- tests/fff_print/test_helpers.cpp | 11 --- tests/libslic3r/test_3mf.cpp | 9 +- tests/libslic3r/test_hollowing.cpp | 4 +- tests/libslic3r/test_indexed_triangle_set.cpp | 11 ++- tests/libslic3r/test_marchingsquares.cpp | 50 ++++------ tests/sla_print/sla_print_tests.cpp | 2 +- tests/sla_print/sla_supptgen_tests.cpp | 10 +- tests/sla_print/sla_test_utils.cpp | 31 +++--- tests/test_utils.hpp | 99 +++++++++++++++++++ 9 files changed, 156 insertions(+), 71 deletions(-) diff --git a/tests/fff_print/test_helpers.cpp b/tests/fff_print/test_helpers.cpp index 47d21e5aad..64493d5f74 100644 --- a/tests/fff_print/test_helpers.cpp +++ b/tests/fff_print/test_helpers.cpp @@ -491,17 +491,6 @@ SCENARIO("init_print functionality", "[test_helpers]") { THEN("Export gcode functions outputs text.") { REQUIRE(! Slic3r::Test::gcode(print).empty()); } -#if 0 - THEN("Embedded meshes exported") { - std::string path = "C:\\data\\temp\\embedded_meshes\\"; - for (auto kvp : Slic3r::Test::mesh_names) { - Slic3r::TriangleMesh m = mesh(kvp.first); - std::string name = kvp.second; - REQUIRE(Slic3r::store_stl((path + name + ".stl").c_str(), &m, true) == true); - REQUIRE(Slic3r::store_obj((path + name + ".obj").c_str(), &m) == true); - } - } -#endif } } } diff --git a/tests/libslic3r/test_3mf.cpp b/tests/libslic3r/test_3mf.cpp index 7a692f949f..1a082cd8e0 100644 --- a/tests/libslic3r/test_3mf.cpp +++ b/tests/libslic3r/test_3mf.cpp @@ -9,6 +9,8 @@ #include "libslic3r/MultiNozzleUtils.hpp" #include "libslic3r/ProjectTask.hpp" +#include "test_utils.hpp" + #include #include @@ -109,8 +111,8 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") { src_object->instances.front()->set_transformation(src_instance_transform); WHEN("model is saved+loaded to/from 3mf file") { - // save the model to 3mf file - std::string test_file = std::string(TEST_DATA_DIR) + "/test_3mf/prusa.3mf"; + ScopedTemporaryFile temp(".3mf"); + const std::string test_file = temp.string(); store_3mf(test_file.c_str(), &src_model, nullptr, false); // load back the model from the 3mf file @@ -120,7 +122,6 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") { ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; load_3mf(test_file.c_str(), dst_config, ctxt, &dst_model, false); } - boost::filesystem::remove(test_file); // compare meshes TriangleMesh src_mesh = src_model.mesh(); @@ -522,7 +523,7 @@ SCENARIO("2D convex hull of sinking object", "[3mf][.]") { object->center_around_origin(false); // This outputs the same exact data as the Prusaslicer test - object->volumes[0]->mesh().write_ascii("/tmp/orca.ascii"); + write_debug_stl("3mf/orca.ascii", object->volumes[0]->mesh()); // set instance's attitude so that it is rotated, scaled (and sinking? how is it sinking? the rotation? does it matter if it's sinking?) ModelInstance* instance = object->instances[0]; diff --git a/tests/libslic3r/test_hollowing.cpp b/tests/libslic3r/test_hollowing.cpp index bea399a05e..dc39b47373 100644 --- a/tests/libslic3r/test_hollowing.cpp +++ b/tests/libslic3r/test_hollowing.cpp @@ -4,6 +4,8 @@ #include "libslic3r/SLA/Hollowing.hpp" +#include "test_utils.hpp" + TEST_CASE("Hollow two overlapping spheres") { using namespace Slic3r; @@ -16,6 +18,6 @@ TEST_CASE("Hollow two overlapping spheres") { sla::hollow_mesh(sphere1, sla::HollowingConfig{}, sla::HollowingFlags::hfRemoveInsideTriangles); - sphere1.WriteOBJFile("twospheres.obj"); + write_debug_obj("hollowing/twospheres.obj", sphere1); } diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index f8ef508f6e..43116936d3 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -5,6 +5,8 @@ #include "libslic3r/TriangleMesh.hpp" +#include "test_utils.hpp" + using namespace Slic3r; TEST_CASE("Split empty mesh", "[its_split][its]") { @@ -29,13 +31,15 @@ TEST_CASE("Split simple mesh consisting of one part", "[its_split][its]") { REQUIRE(res.front().vertices.size() == cube.vertices.size()); } +// Dump each split part as its own OBJ for eyeballing; no-op in release. void debug_write_obj(const std::vector &res, const std::string &name) { #ifndef NDEBUG size_t part_idx = 0; - for (auto &part_its : res) { - its_write_obj(part_its, (name + std::to_string(part_idx++) + ".obj").c_str()); - } + for (const auto &part_its : res) + write_debug_obj("indexed_triangle_set/" + name + std::to_string(part_idx++) + ".obj", part_its); +#else + (void) res; (void) name; #endif } @@ -260,7 +264,6 @@ TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]") CHECK(is_similar(its_, its, cfg)); } -#include "test_utils.hpp" TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]") { TriangleMesh mesh = load_model("frog_legs.obj"); diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index 4768db5f7b..6844ecb6ac 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -191,22 +191,21 @@ static void test_expolys(Rst&& rst, const ExPolygons& ref, Vec2i32 window, const for (const ExPolygon& expoly : ref) rst.draw(expoly); - std::fstream out(name + ".png", std::ios::out); - out << rst.encode(sla::PNGRasterEncoder{}); - out.close(); + write_debug_stream("marchingsquares/" + name + ".png", + [&] { return rst.encode(sla::PNGRasterEncoder{}); }); const ExPolygons bmp = rstGetPolys(rst); const ExPolygons ext = sla::raster_to_polygons(rst, window); - SVG svg(name + ".svg", raster_bb); - svg.draw(bmp, "green"); - if (pixel_size.x() >= scale_(0.5)) - svg.draw_grid(raster_bb, "grey", scale_(0.05), pixel_size.x()); - if (window_size.x() >= scale_(1.0)) - svg.draw_grid(raster_bb, "grey", scale_(0.10), window_size.x()); - svg.draw_outline(ref, "red", "red", scale_(0.3)); - svg.draw_outline(ext, "blue", "blue"); - svg.Close(); + write_debug_svg("marchingsquares/" + name + ".svg", raster_bb, [&](SVG &svg) { + svg.draw(bmp, "green"); + if (pixel_size.x() >= scale_(0.5)) + svg.draw_grid(raster_bb, "grey", scale_(0.05), pixel_size.x()); + if (window_size.x() >= scale_(1.0)) + svg.draw_grid(raster_bb, "grey", scale_(0.10), window_size.x()); + svg.draw_outline(ref, "red", "red", scale_(0.3)); + svg.draw_outline(ext, "blue", "blue"); + }); // Note all these areas are unscaled back to mm^2. double raster_area = unscaled(unscaled(area(bmp))); @@ -432,9 +431,7 @@ static void recreate_object_from_rasters(const std::string& objname, float lh) double disp_w = 120.96; double disp_h = 68.04; -#ifndef NDEBUG size_t cntr = 0; -#endif for (ExPolygons& layer : layers) { auto rst = create_raster(res, disp_w, disp_h); @@ -442,11 +439,8 @@ static void recreate_object_from_rasters(const std::string& objname, float lh) rst.draw(island); } -#ifndef NDEBUG - std::fstream out(objname + std::to_string(cntr) + ".png", std::ios::out); - out << rst.encode(sla::PNGRasterEncoder{}); - out.close(); -#endif + write_debug_stream("marchingsquares/" + objname + std::to_string(cntr) + ".png", + [&] { return rst.encode(sla::PNGRasterEncoder{}); }); ExPolygons layer_ = sla::raster_to_polygons(rst); // float delta = scaled(std::min(rst.pixel_dimensions().h_mm, @@ -454,21 +448,19 @@ static void recreate_object_from_rasters(const std::string& objname, float lh) // layer_ = expolygons_simplify(layer_, delta); -#ifndef NDEBUG - SVG svg(objname + std::to_string(cntr) + ".svg", rstBBox(rst)); - svg.draw(layer_); - svg.draw(layer, "green"); - svg.Close(); -#endif + write_debug_svg("marchingsquares/" + objname + std::to_string(cntr) + ".svg", rstBBox(rst), + [&](SVG &svg) { + svg.draw(layer_); + svg.draw(layer, "green"); + }); double layera = 0., layera_ = 0.; for (auto& p : layer) layera += p.area(); for (auto& p : layer_) layera_ += p.area(); -#ifndef NDEBUG - std::cout << cntr++ << std::endl; -#endif + ++cntr; + double diff = std::abs(layera_ - layera); REQUIRE((diff <= 0.1 * layera || diff < scaled(1.) * scaled(1.))); @@ -477,7 +469,7 @@ static void recreate_object_from_rasters(const std::string& objname, float lh) indexed_triangle_set out = slices_to_mesh(layers, bb.min.z(), double(lh), double(lh)); - its_write_obj(out, "out_from_rasters.obj"); + write_debug_obj("marchingsquares/out_from_rasters.obj", out); } TEST_CASE("Recreate object from rasters", "[SL1Import]") { recreate_object_from_rasters("frog_legs.obj", 0.05f); } diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 269f5ed7b3..308925a163 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -229,7 +229,7 @@ TEST_CASE("halfcone test", "[halfcone]") { indexed_triangle_set m = sla::get_mesh(br, 45); its_merge_vertices(m); - its_write_obj(m, "Halfcone.obj"); + write_debug_obj("sla_print/Halfcone.obj", m); } TEST_CASE("Test concurrency") diff --git a/tests/sla_print/sla_supptgen_tests.cpp b/tests/sla_print/sla_supptgen_tests.cpp index 458b2a7686..32f049d78e 100644 --- a/tests/sla_print/sla_supptgen_tests.cpp +++ b/tests/sla_print/sla_supptgen_tests.cpp @@ -13,7 +13,7 @@ TEST_CASE("Overhanging point should be supported", "[SupGen]") { // Pyramid with 45 deg slope TriangleMesh mesh = make_pyramid(10.f, 10.f); mesh.rotate_y(float(PI)); - mesh.WriteOBJFile("Pyramid.obj"); + write_debug_obj("sla_supptgen/Pyramid.obj", mesh); sla::SupportPoints pts = calc_support_pts(mesh); @@ -55,7 +55,7 @@ TEST_CASE("Overhanging horizontal surface should be supported", "[SupGen]") { TriangleMesh mesh = make_cube(width, depth, height); mesh.translate(0., 0., 5.); // lift up - mesh.WriteOBJFile("Cuboid.obj"); + write_debug_obj("sla_supptgen/Cuboid.obj", mesh); sla::SupportPointGenerator::Config cfg; sla::SupportPoints pts = calc_support_pts(mesh, cfg); @@ -81,7 +81,7 @@ TEST_CASE("Overhanging edge should be supported", "[SupGen]") { TriangleMesh mesh = make_prism(width, depth, height); mesh.rotate_y(float(PI)); // rotate on its back mesh.translate(0., 0., height); - mesh.WriteOBJFile("Prism.obj"); + write_debug_obj("sla_supptgen/Prism.obj", mesh); sla::SupportPointGenerator::Config cfg; sla::SupportPoints pts = calc_support_pts(mesh, cfg); @@ -106,7 +106,7 @@ TEST_CASE("Hollowed cube should be supported from the inside", "[SupGen][Hollowe hollow_mesh(mesh, HollowingConfig{}); - mesh.WriteOBJFile("cube_hollowed.obj"); + write_debug_obj("sla_supptgen/cube_hollowed.obj", mesh); auto bb = mesh.bounding_box(); auto h = float(bb.max.z() - bb.min.z()); @@ -129,7 +129,7 @@ TEST_CASE("Two parallel plates should be supported", "[SupGen][Hollowed]") mesh_high.translate(0., 0., 10.); // lift up mesh.merge(mesh_high); - mesh.WriteOBJFile("parallel_plates.obj"); + write_debug_obj("sla_supptgen/parallel_plates.obj", mesh); sla::SupportPointGenerator::Config cfg; sla::SupportPoints pts = calc_support_pts(mesh, cfg); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 1bd4238408..a74bad09e4 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -47,8 +47,9 @@ void test_support_model_collision(const std::string &obj_filename, notouch = notouch && area(intersections) < PI * pinhead_r * pinhead_r; } - /*if (!notouch) */export_failed_case(support_slices, byproducts); - + if (!notouch) + export_failed_case(support_slices, byproducts); + REQUIRE(notouch); } @@ -62,11 +63,11 @@ void export_failed_case(const std::vector &support_slices, const Sup std::stringstream ss; if (!intersections.empty()) { ss << byproducts.obj_fname << std::setprecision(4) << n << ".svg"; - SVG svg(ss.str()); - svg.draw(sup_slice, "green"); - svg.draw(mod_slice, "blue"); - svg.draw(intersections, "red"); - svg.Close(); + write_debug_svg("sla/" + ss.str(), [&](SVG &svg) { + svg.draw(sup_slice, "green"); + svg.draw(mod_slice, "blue"); + svg.draw(intersections, "red"); + }); } } @@ -74,8 +75,8 @@ void export_failed_case(const std::vector &support_slices, const Sup byproducts.supporttree.retrieve_full_mesh(its); TriangleMesh m{its}; m.merge(byproducts.input_mesh); - m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" + - byproducts.obj_fname).c_str()); + write_debug_obj("sla/" + Catch::getResultCapture().getCurrentTestName() + + "_" + byproducts.obj_fname, m); } void test_supports(const std::string &obj_filename, @@ -350,13 +351,11 @@ void check_raster_transformations(sla::RasterBase::Orientation o, sla::RasterBas REQUIRE((w < res.width_px && h < res.height_px)); auto px = raster.read_pixel(w, h); - - if (px != FullWhite) { - std::fstream outf("out.png", std::ios::out); - - outf << raster.encode(sla::PNGRasterEncoder()); - } - + + if (px != FullWhite) + write_debug_stream("sla/raster_transform_mismatch.png", + [&] { return raster.encode(sla::PNGRasterEncoder()); }); + REQUIRE(px == FullWhite); } diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index 7fe55c5333..d928f2f41e 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -3,9 +3,14 @@ #include #include +#include #include +#include +#include +#include + #if defined(WIN32) || defined(_WIN32) #define PATH_SEPARATOR R"(\)" #else @@ -44,4 +49,98 @@ private: boost::filesystem::path m_path; }; +// --------------------------------------------------------------------------- +// Debug-only test artifacts +// +// Files a test dumps for inspection: a mesh, an SVG, or any streamable blob such +// as a PNG. In debug builds each run writes to a fresh temp folder (path printed +// once); the name may include a subfolder (e.g. "marchingsquares/foo.svg"). +// --------------------------------------------------------------------------- + +// Maps name to a path under the run's temp folder, creating any parent dirs +// (forward slashes work on Windows). Not gated, so only call it from a +// write_debug_* helper or inside an #ifndef NDEBUG block. +inline std::string debug_artifact_path(const std::string &name) +{ + static const boost::filesystem::path root = [] { + boost::filesystem::path dir = boost::filesystem::temp_directory_path() + / boost::filesystem::unique_path("orca-test-artifacts-%%%%-%%%%"); + boost::filesystem::create_directories(dir); + std::printf("Debug test artifacts will be written to %s\n", dir.string().c_str()); + return dir; + }(); + boost::filesystem::path full = root / name; + boost::filesystem::create_directories(full.parent_path()); + return full.string(); +} + +// Dump a mesh as OBJ. +inline void write_debug_obj(const std::string &name, const Slic3r::TriangleMesh &mesh) +{ +#ifndef NDEBUG + mesh.WriteOBJFile(debug_artifact_path(name).c_str()); +#else + (void) name; (void) mesh; +#endif +} + +inline void write_debug_obj(const std::string &name, const indexed_triangle_set &its) +{ +#ifndef NDEBUG + its_write_obj(its, debug_artifact_path(name).c_str()); +#else + (void) name; (void) its; +#endif +} + +// Dump a mesh as ASCII STL. +inline void write_debug_stl(const std::string &name, const Slic3r::TriangleMesh &mesh) +{ +#ifndef NDEBUG + mesh.write_ascii(debug_artifact_path(name).c_str()); +#else + (void) name; (void) mesh; +#endif +} + +// Draw an SVG artifact through a callback that receives the open SVG. Second +// overload takes a BoundingBox when the drawing needs one. +template +inline void write_debug_svg(const std::string &name, Draw &&draw) +{ +#ifndef NDEBUG + Slic3r::SVG svg(debug_artifact_path(name)); + draw(svg); + svg.Close(); +#else + (void) name; (void) draw; +#endif +} + +template +inline void write_debug_svg(const std::string &name, const Slic3r::BoundingBox &bbox, Draw &&draw) +{ +#ifndef NDEBUG + Slic3r::SVG svg(debug_artifact_path(name), bbox); + draw(svg); + svg.Close(); +#else + (void) name; (void) bbox; (void) draw; +#endif +} + +// Write a callback's result (e.g. raster.encode(sla::PNGRasterEncoder{})) to an +// artifact. operator<< is resolved by ADL at the call site, so this header needn't +// include the producer's headers. +template +inline void write_debug_stream(const std::string &name, Produce &&produce) +{ +#ifndef NDEBUG + std::ofstream out(debug_artifact_path(name), std::ios::out | std::ios::binary); + out << produce(); +#else + (void) name; (void) produce; +#endif +} + #endif // SLIC3R_TEST_UTILS