Add a new TestCase that asserts that examples exit successfully.

Examples changes that were necessary:
- loft.py: failed on macos (only) because of (seemingly) over-precise
  floating-point accuracy assertion. Loosened the tolerance, and
  expressed it as a multiple of the expected value.
  > AssertionError: delta=0.002982314711971412 is greater than tolerance=0.001; got=1306.3375467197516, want=1306.3405290344635
- packed_boxes.py: only emit output files when GEN_DOCS is
  True (mimicking lego.py).
This commit is contained in:
Ami Fischman 2025-02-21 23:41:52 -08:00
parent aeb6b32b65
commit 80097a9227
3 changed files with 95 additions and 2 deletions

View file

@ -41,7 +41,11 @@ with BuildPart() as art:
top_bottom = art.faces().filter_by(GeomType.PLANE)
offset(openings=top_bottom, amount=0.5)
assert abs(art.part.volume - 1306.3405290344635) < 1e-3
want = 1306.3405290344635
got = art.part.volume
delta = abs(got - want)
tolerance = want * 1e-5
assert delta < tolerance, f"{delta=} is greater than {tolerance=}; {got=}, {want=}"
show(art, names=["art"])
# [End]

View file

@ -12,6 +12,8 @@ import operator
import random
import build123d as bd
GEN_DOCS = False
random.seed(123456)
test_boxes = [bd.Box(random.randint(1, 20), random.randint(1, 20), random.randint(1, 5))
for _ in range(50)]
@ -28,7 +30,8 @@ def export_svg(parts, name):
exporter.add_layer("Hidden", line_color=(99, 99, 99), line_type=bd.LineType.ISO_DOT)
exporter.add_shape(visible, layer="Visible")
exporter.add_shape(hidden, layer="Hidden")
exporter.write(f"../docs/assets/{name}.svg")
if GEN_DOCS:
exporter.write(f"../docs/assets/{name}.svg")
export_svg(test_boxes, "packed_boxes_input")
export_svg(packed, "packed_boxes_output")

86
tests/test_examples.py Normal file
View file

@ -0,0 +1,86 @@
"""
build123d Example tests
name: test_examples.py
by: fischman
date: February 21 2025
desc: Unit tests for the build123d examples, ensuring they don't raise.
"""
from pathlib import Path
import os
import subprocess
import sys
import tempfile
import unittest
_examples_dir = Path(os.path.abspath(os.path.dirname(__file__))).parent / "examples"
_MOCK_OCP_VSCODE_CONTENTS = """
from pathlib import Path
import re
import sys
from unittest.mock import Mock
mock_module = Mock()
mock_module.show = Mock()
mock_module.show_object = Mock()
mock_module.show_all = Mock()
sys.modules["ocp_vscode"] = mock_module
"""
def generate_example_test(path: Path):
"""Generate and return a function to test the example at `path`."""
name = path.name
def assert_example_does_not_raise(self):
with tempfile.TemporaryDirectory(
prefix=f"build123d_test_examples_{name}"
) as tmpdir:
# More examples emit output files than read input files,
# so default to running with a temporary directory to
# avoid cluttering the git working directory. For
# examples that want to read assets from the examples
# directory, use that. If an example is added in the
# future that wants to both read assets from the examples
# directory and write output files, deal with it then.
cwd = tmpdir if 'benchy' not in path.name else _examples_dir
mock_ocp_vscode = Path(tmpdir) / "_mock_ocp_vscode.py"
with open(mock_ocp_vscode, "w", encoding="utf-8") as f:
f.write(_MOCK_OCP_VSCODE_CONTENTS)
got = subprocess.run(
[
sys.executable,
"-c",
f"exec(open(r'{mock_ocp_vscode}').read()); exec(open(r'{path}').read())",
],
capture_output=True,
cwd=cwd,
check=False,
)
self.assertEqual(
0, got.returncode, f"stdout/stderr: {got.stdout} / {got.stderr}"
)
return assert_example_does_not_raise
class TestExamples(unittest.TestCase):
"""Tests build123d examples."""
for example in sorted(_examples_dir.iterdir()):
if example.name.startswith("_") or not example.name.endswith(".py"):
continue
setattr(
TestExamples,
f"test_{example.name.replace('.', '_')}",
generate_example_test(example),
)
if __name__ == "__main__":
unittest.main()