diff options
Diffstat (limited to 'gerbonara/gerber/tests')
-rw-r--r-- | gerbonara/gerber/tests/conftest.py | 3 | ||||
-rw-r--r-- | gerbonara/gerber/tests/image_support.py | 33 | ||||
-rw-r--r-- | gerbonara/gerber/tests/test_rs274x.py | 103 |
3 files changed, 80 insertions, 59 deletions
diff --git a/gerbonara/gerber/tests/conftest.py b/gerbonara/gerber/tests/conftest.py index 0ad2555..c8fd475 100644 --- a/gerbonara/gerber/tests/conftest.py +++ b/gerbonara/gerber/tests/conftest.py @@ -3,14 +3,13 @@ import pytest from .image_support import ImageDifference -@pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_assertrepr_compare(op, left, right): if isinstance(left, ImageDifference) or isinstance(right, ImageDifference): diff = left if isinstance(left, ImageDifference) else right return [ f'Image difference assertion failed.', f' Reference: {diff.ref_path}', - f' Actual: {diff.out_path}', + f' Actual: {diff.act_path}', f' Calculated difference: {diff}', ] # store report in node object so tmp_gbr can determine if the test failed. diff --git a/gerbonara/gerber/tests/image_support.py b/gerbonara/gerber/tests/image_support.py index ee8e6b9..e1b1c00 100644 --- a/gerbonara/gerber/tests/image_support.py +++ b/gerbonara/gerber/tests/image_support.py @@ -1,13 +1,28 @@ import subprocess from pathlib import Path import tempfile +import os +from functools import total_ordering import numpy as np +from PIL import Image + +class ImageDifference: + def __init__(self, value, ref_path, act_path): + self.value, self.ref_path, self.act_path = value, ref_path, act_path + + def __float__(self): + return float(self.value) + + def __eq__(self, other): + return float(self) == float(other) + + def __lt__(self, other): + return float(self) < float(other) + + def __str__(self): + return str(float(self)) -class ImageDifference(float): - def __init__(self, value, ref_path, out_path): - super().__init__(value) - self.ref_path, self.out_path = ref_path, out_path def run_cargo_cmd(cmd, args, **kwargs): if cmd.upper() in os.environ: @@ -22,16 +37,18 @@ def run_cargo_cmd(cmd, args, **kwargs): def svg_to_png(in_svg, out_png): run_cargo_cmd('resvg', [in_svg, out_png], check=True, stdout=subprocess.DEVNULL) -def gbr_to_svg(in_gbr, out_svg): +def gbr_to_svg(in_gbr, out_svg, origin=(0, 0), size=(10, 10)): + x, y = origin + w, h = size cmd = ['gerbv', '-x', 'svg', '--border=0', - #f'--origin={origin_x:.6f}x{origin_y:.6f}', f'--window_inch={width:.6f}x{height:.6f}', + f'--origin={x:.6f}x{y:.6f}', f'--window_inch={w:.6f}x{h:.6f}', '--foreground=#ffffff', '-o', str(out_svg), str(in_gbr)] subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def gerber_difference(reference, actual): - with tempfile.NamedTemporaryFile(suffix='.svg') as out_svg,\ + with tempfile.NamedTemporaryFile(suffix='.svg') as act_svg,\ tempfile.NamedTemporaryFile(suffix='.svg') as ref_svg: gbr_to_svg(reference, ref_svg.name) @@ -58,6 +75,6 @@ def image_difference(reference, actual): ref, out = ref.mean(axis=2), out.mean(axis=2) # convert to grayscale delta = np.abs(out - ref).astype(float) / 255 - return ImageDifference(delta.mean(), ref, out) + return ImageDifference(delta.mean(), reference, actual) diff --git a/gerbonara/gerber/tests/test_rs274x.py b/gerbonara/gerber/tests/test_rs274x.py index beaea11..f359ca9 100644 --- a/gerbonara/gerber/tests/test_rs274x.py +++ b/gerbonara/gerber/tests/test_rs274x.py @@ -3,6 +3,7 @@ # Author: Hamilton Kibbe <ham@hamiltonkib.be> import os +import re import pytest import functools import tempfile @@ -18,8 +19,10 @@ from .image_support import gerber_difference fail_dir = Path('gerbonara_test_failures') @pytest.fixture(scope='session', autouse=True) def clear_failure_dir(request): - if fail_dir.is_dir(): - shutil.rmtree(fail_dir) + for f in fail_dir.glob('*.gbr'): + f.unlink() + +reference_path = lambda reference: Path(__file__).parent / 'resources' / reference @pytest.fixture def tmp_gbr(request): @@ -30,61 +33,63 @@ def tmp_gbr(request): if request.node.rep_call.failed: module, _, test_name = request.node.nodeid.rpartition('::') _test, _, test_name = test_name.partition('_') - test_name = test_name.replace('[', '_').replace(']', '_') + test_name, _, _ext = test_name.partition('.') + test_name = re.sub(r'[^\w\d]', '_', test_name) fail_dir.mkdir(exist_ok=True) perm_path = fail_dir / f'failure_{test_name}.gbr' shutil.copy(tmp_out_gbr.name, perm_path) - print('Failing output saved to {perm_path}') + print(f'Failing output saved to {perm_path}') + print(f'Reference file is {reference_path(request.node.funcargs["reference"])}') +@pytest.mark.filterwarnings('ignore:Deprecated.*statement found.*:DeprecationWarning') +@pytest.mark.filterwarnings('ignore::SyntaxWarning') @pytest.mark.parametrize('reference', [ l.strip() for l in ''' board_outline.GKO example_outline_with_arcs.gbr -''' -#example_two_square_boxes.gbr -#example_coincident_hole.gbr -#example_cutin.gbr -#example_cutin_multiple.gbr -#example_flash_circle.gbr -#example_flash_obround.gbr -#example_flash_polygon.gbr -#example_flash_rectangle.gbr -#example_fully_coincident.gbr -#example_guess_by_content.g0 -#example_holes_dont_clear.gbr -#example_level_holes.gbr -#example_not_overlapping_contour.gbr -#example_not_overlapping_touching.gbr -#example_overlapping_contour.gbr -#example_overlapping_touching.gbr -#example_simple_contour.gbr -#example_single_contour_1.gbr -#example_single_contour_2.gbr -#example_single_contour_3.gbr -#example_am_exposure_modifier.gbr -#bottom_copper.GBL -#bottom_mask.GBS -#bottom_silk.GBO -#eagle_files/copper_bottom_l4.gbr -#eagle_files/copper_inner_l2.gbr -#eagle_files/copper_inner_l3.gbr -#eagle_files/copper_top_l1.gbr -#eagle_files/profile.gbr -#eagle_files/silkscreen_bottom.gbr -#eagle_files/silkscreen_top.gbr -#eagle_files/soldermask_bottom.gbr -#eagle_files/soldermask_top.gbr -#eagle_files/solderpaste_bottom.gbr -#eagle_files/solderpaste_top.gbr -#multiline_read.ger -#test_fine_lines_x.gbr -#test_fine_lines_y.gbr -#top_copper.GTL -#top_mask.GTS -#top_silk.GTO -''' +example_two_square_boxes.gbr +example_coincident_hole.gbr +example_cutin.gbr +example_cutin_multiple.gbr +example_flash_circle.gbr +example_flash_obround.gbr +example_flash_polygon.gbr +example_flash_rectangle.gbr +example_fully_coincident.gbr +example_guess_by_content.g0 +example_holes_dont_clear.gbr +example_level_holes.gbr +example_not_overlapping_contour.gbr +example_not_overlapping_touching.gbr +example_overlapping_contour.gbr +example_overlapping_touching.gbr +example_simple_contour.gbr +example_single_contour_1.gbr +example_single_contour_2.gbr +example_single_contour_3.gbr +example_am_exposure_modifier.gbr +bottom_copper.GBL +bottom_mask.GBS +bottom_silk.GBO +eagle_files/copper_bottom_l4.gbr +eagle_files/copper_inner_l2.gbr +eagle_files/copper_inner_l3.gbr +eagle_files/copper_top_l1.gbr +eagle_files/profile.gbr +eagle_files/silkscreen_bottom.gbr +eagle_files/silkscreen_top.gbr +eagle_files/soldermask_bottom.gbr +eagle_files/soldermask_top.gbr +eagle_files/solderpaste_bottom.gbr +eagle_files/solderpaste_top.gbr +multiline_read.ger +test_fine_lines_x.gbr +test_fine_lines_y.gbr +top_copper.GTL +top_mask.GTS +top_silk.GTO '''.splitlines() if l ]) def test_round_trip(tmp_gbr, reference): - ref = Path(__file__).parent / 'resources' / reference + ref = reference_path(reference) GerberFile.open(ref).save(tmp_gbr) - assert gerber_difference(ref, tmp_gbr) < 0.02 + assert gerber_difference(ref, tmp_gbr) < 1e-5 |