Files
OrcaSlicer/tests/fff_print/README.md
raistlin7447 29f31b9b38 fff_print: a maintainable testing framework (proposal + coverage) (#14426)
* fix: initialize Print::m_isBBLPrinter

Built outside the GUI/CLI (headless tests, embedded use) the member was read
uninitialized: is_BBL_printer()/wipe_tower_type() feed it into ToolOrdering,
which then non-deterministically dropped per-feature filament assignments.
Default it to false, the value the GUI and CLI already assign for non-Bambu
printers.

* docs(test): add the fff_print testing contract

tests/fff_print/README.md codifies how the suite is organized: one file per
subsystem (each owning both in-memory and emitted-G-code assertions), flat
behavioral test names with a single [Subsystem] tag, a robust-tests guide,
the shared helpers, and an add-a-test checklist. Linked from tests/CLAUDE.md.

* test(fff_print): reorganize the suite to the contract and add coverage

Bring every subsystem into one file per the README: rename the test_data
harness to test_helpers; consolidate skirt/brim; split multi-filament and
cooling into their own files; disperse the test_printgcode grab-bag and the
end-to-end smoke scenario into focused tests; fold test_gcode into
test_gcodewriter. Standardize names and tags, align cube tests on the cube()
helper, and de-qualify the flagship files.

New coverage: multi-filament per-feature and per-object routing; a skirt/brim
behavior matrix (the #14333 rework, including brim ears, with regression
coverage for #14319 and #14366); resolved extrusion-width and config
comments; custom-G-code placeholders; fan control and speed-marker
consumption.

Re-enable three slice tests previously tagged [NotWorking]: the clipper
"Coordinate outside allowed range" error that disabled them was specific to a
past CI runner environment and no longer reproduces.

* test(fff_print): tag arm64-flaky skirt/brim tests NotWorking

Four skirt/brim slice tests intermittently throw ClipperLib's "Coordinate
outside allowed range" on the macOS and Windows arm64 CI toolchains (an FP
divergence, not a slicing bug; see PR #14207). Linux x86_64 and aarch64 are
unaffected. Tag them [NotWorking] so ctest -LE NotWorking skips them.

* test(fff_print): re-enable the arm64 skirt/brim tests

These were tagged [NotWorking] as a stopgap when myfork's daily-driver build
combined them with the cross-platform CI on a base that predated upstream's
m_origin fix (99dea01cc3). With upstream merged in, Print::m_origin is
initialized and the "Coordinate outside allowed range" throw is gone, so the
tests pass on macOS/Windows arm64. Drop the tags.
2026-07-06 22:24:24 +08:00

6.7 KiB

fff_print test suite

Component- and pipeline-level tests for FFF slicing: the path from a Model plus config, through Print / PrintObject, to emitted G-code.

For Catch2 mechanics (assertions, generators, matchers, random ordering, thread-safety), see ../CLAUDE.md. This document is the organizing contract for the suite: where a test goes, and how it is named.

Organizing principle

One file per subsystem. A subsystem is usually a single production class (Flow, PrintObject), but may be a cohesive feature that spans several (skirt/brim lives in Brim.cpp, Print.cpp, and GCode.cpp). That file owns every test for the subsystem: in-memory-state assertions and emitted-G-code assertions alike.

A test's home is decided by what production code it exercises, never by how it observes the result. A skirt test that inspects print.skirt() and one that greps the G-code for ; skirt live in the same file.

If you touched code in a subsystem, its test file is where your test goes. If a subsystem has no file yet, add test_<subsystem>.cpp and list it in CMakeLists.txt.

File ownership

Building blocks (one class, exercised through its API)

File Source (src/libslic3r/) Covers
test_trianglemesh TriangleMesh.{c,h}pp mesh stats, transforms, slicing, split/merge/cut
test_flow Flow.{c,h}pp extrusion width / area math
test_extrusion_entity ExtrusionEntity.{c,h}pp extrusion-collection geometry
test_gcodewriter GCodeWriter.{c,h}pp, GCode.cpp low-level G-code emit primitives, origin
test_model Model.{c,h}pp object / volume / instance construction

Slicing pipeline (build a Print, then assert state or G-code)

File Source (src/libslic3r/) Covers
test_printobject PrintObject.cpp layer heights, perimeter generation
test_fill Fill/ infill patterns and infill G-code
test_skirt_brim Brim.cpp, Print.cpp skirt/brim loop counts, grouping, brim ears, emission order
test_support_material Support/ support & raft layers, contact distance
test_cooling GCode/CoolingBuffer.cpp fan control, speed-marker consumption
test_multifilament GCode/ToolOrdering.cpp per-feature and per-object filament routing
test_print Print.{c,h}pp validate(), solid-shell behavior, sequential printing, custom G-code & config comments, default-slice smoke

Paths are under src/libslic3r/. A trailing / is a directory of related files; otherwise it is a single class. {c,h}pp means the .cpp/.hpp pair.

Naming and tags

  • File: test_<subsystem>.cpp.
  • Test name: a plain behavioral sentence, present tense, stating the contract the test pins down. No Subsystem: prefix (the tag carries that).
    • Good: TEST_CASE("Skirt is emitted once per layer it spans", "[SkirtBrim]")
    • Avoid: TEST_CASE("Print: Skirt generation", "[Print]")
  • Tags:
    • Exactly one subsystem tag, PascalCase, matching the file ([SkirtBrim], [PrintObject], [Fill]). This is the grouping / filter key.
    • Optional cross-cutting tags for a concern that genuinely spans files ([validate], [Regression]).
    • Status tags: [NotWorking] marks a test disabled for a known, documented reason; CI excludes it via ~[NotWorking] (it does not hide itself). Use [.] to hide a test from default runs entirely. Either way, say why in a one-line comment.

Test style

Prefer a flat TEST_CASE per behavior, with GENERATE for parameterized cases and shared setup factored into helpers. The test name carries the behavior, so the BDD scaffolding is usually redundant. Reserve SCENARIO / GIVEN / WHEN / THEN for a test with genuine shared setup that branches into a few closely related variations, and never let a SCENARIO accumulate unrelated WHENs: that grab-bag is what this contract exists to prevent (and it hides failures behind a single coarse test case).

Robust tests

A test should fail only when the behavior it names breaks, not from unrelated changes (the "change-detector" anti-pattern). Test behavior, not incidentals, and aim for one reason to fail. Concretely:

  • Don't depend on or assert defaults: set the config keys the behavior needs, and derive expected values from those inputs (a 20mm cube at 0.2mm = 100 layers), not from a default that may change.
  • Assert the defining property, not an incidental value: prefer "skirt present", "at least 2 brim loops", or "ears vs none" over exact coordinates, extrusion amounts, line counts, or byte sizes.
  • Compare floats with a tolerance (WithinAbs / WithinRel), never ==.
  • Match the meaningful G-code token (; skirt), not whole lines, whitespace, or comment wording.
  • Rely on ordering only when it is the contract (as role_sequence does).
  • Keep tests self-contained: no shared state, green under --order rand.

Helpers

Reuse these instead of building a Print or parsing G-code by hand.

  • Global (tests/test_utils.hpp, available to every suite):
    • load_model("file.obj"): load a TriangleMesh from tests/data/.
    • ScopedTemporaryFile: an RAII temp-file path, removed on scope exit.
  • Suite harness (fff_print/test_helpers.{hpp,cpp}):
    • Build and run: init_print(...), init_and_process_print(...), slice(...) (returns the G-code string), and gcode(print).
    • Two-cube placement: slice_two_cubes_arranged(...) (arranger-positioned), and place_two_cubes_apart(...) / slice_two_cubes_apart(...) (a fixed gap, not arranged).
    • Meshes: cube(size) / make_cube(...) for simple shapes; the TestMesh enum with mesh(...) for named fixtures.
    • G-code analysis: layers_with_role(gcode, role), max_z(gcode), role_passes(gcode, role), role_sequence(gcode, roles). Subsystem-specific checks stay local (for example brim_count in test_skirt_brim).

Promote a helper into the suite harness when it is a general test primitive (not tied to one subsystem's logic), even if only one file uses it today; keep genuinely subsystem-specific helpers local (file-static). Reuse potential, not current usage count, is the test.

Adding a test (checklist)

  1. Find the subsystem's file in the tables; create test_<subsystem>.cpp if missing.
  2. Build the print with a harness helper; set only the config keys the behavior needs.
  3. Assert the behavior, in-memory or via parsed G-code, whichever is clearest.
  4. Name it as a behavioral sentence and tag it [Subsystem].
  5. For a bug fix, add the regression test in the owning file. Name it for the behavior it protects; the test must stand on its own without relying on an external issue or PR for meaning.

Running

ctest --test-dir build/tests/fff_print
build/tests/fff_print/<config>/fff_print_tests --order rand "~[NotWorking]"