From 259e58c6252fa454925ae458be6bfe0f34564d1c Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Tue, 13 Jan 2026 21:39:13 -0700 Subject: [PATCH 1/6] ci: Enable code coverage for geos 3.13.1. The code coverage includes the integration tests and we want to run them on a consistent version of geos. Otherwise, they might not pass. --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee83a676f..ee22e60c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,9 +34,7 @@ jobs: boost: '1_66' include: - os: ubuntu - boost: latest - compiler: g++ - geos: latest + geos: '3.13.1' code_coverage: "--enable-code-coverage" - os: ubuntu shell: '/usr/bin/bash -l -e -o pipefail {0}' From 80f465a494ebdc0f5ec34ceaec9896c72cf516c0 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Tue, 13 Jan 2026 21:43:07 -0700 Subject: [PATCH 2/6] ci: Use more CPUs in Windows build and runs. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee22e60c4..899e817e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: - name: Windows specific setup if: matrix.os == 'windows' run: | - echo "export NUM_CPUS='$((`nproc --all`))'" >> ~/.bash_profile + echo "export NUM_CPUS='$((`nproc --all` * 4))'" >> ~/.bash_profile - name: Ubuntu specific setup if: matrix.os == 'ubuntu' run: | From 8f6ec81deb49dbf791dc561fdf953457fdb5423c Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Tue, 13 Jan 2026 21:47:39 -0700 Subject: [PATCH 3/6] test: Better support for Windows in integration_tests.py. --- integration_tests.py | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/integration_tests.py b/integration_tests.py index bde0483fa..9796ef731 100755 --- a/integration_tests.py +++ b/integration_tests.py @@ -15,13 +15,20 @@ import sys import tempfile import xml.etree.ElementTree - -import colour_runner.runner +try: + import colour_runner.runner + colour_runner_available = True +except: + colour_runner_available = False import in_place import termcolor import unittest -from concurrencytest import ConcurrentTestSuite, fork_for_tests +try: + from concurrencytest import ConcurrentTestSuite, fork_for_tests + concurrencytest_available = True +except: + concurrencytest_available = False TestCase = collections.namedtuple("TestCase", ["name", "input_path", "args", "exit_code"]) @@ -213,7 +220,7 @@ def bigger(matchobj): else: svg_file.write(line) - def pcb2gcode_one_directory(self, input_path, cwd, args=None, exit_code=0): + def pcb2gcode_one_directory(self, input_path, pcb2gcode_binary, args=None, exit_code=0): """Run pcb2gcode once in one directory. Current working directory remains unchanged at the end. @@ -221,11 +228,11 @@ def pcb2gcode_one_directory(self, input_path, cwd, args=None, exit_code=0): input_path: Where to run pcb2gcode Returns the path to the output files created. """ - pcb2gcode = os.path.join(cwd, "pcb2gcode") + cwd = os.getcwd() # Save this for later restoring the current working directory actual_output_path = tempfile.mkdtemp() os.chdir(input_path) try: - cmd = [pcb2gcode] + cmd = [pcb2gcode_binary] if not any("output-dir" in x for x in args): cmd += ["--output-dir", actual_output_path] cmd += args or [] @@ -298,7 +305,7 @@ def compare_directories(self, left, right, left_prefix="", right_prefix=""): all_diffs += difflib.unified_diff(data0, data1, '"' + os.path.join(left_prefix, f) + '"', '"' + os.path.join(right_prefix, f) + '"') return ''.join(all_diffs) - def run_one_directory(self, input_path, cwd, expected_output_path, test_prefix, args=[], exit_code=0): + def run_one_directory(self, input_path, pcb2gcode_binary, expected_output_path, test_prefix, args=[], exit_code=0): """Run pcb2gcode on a directory and return the diff as a string. Returns an empty string if there is no mismatch. @@ -307,7 +314,7 @@ def run_one_directory(self, input_path, cwd, expected_output_path, test_prefix, expected_output_path: Path to expected outputs test_prefix: Strin to prepend to all filenamess """ - actual_output_path = self.pcb2gcode_one_directory(input_path, cwd, args, exit_code) + actual_output_path = self.pcb2gcode_one_directory(input_path, pcb2gcode_binary, args, exit_code) if exit_code: return "" diff_text = self.compare_directories(expected_output_path, actual_output_path, @@ -316,12 +323,13 @@ def run_one_directory(self, input_path, cwd, expected_output_path, test_prefix, shutil.rmtree(actual_output_path) return diff_text - def do_test_one(self, test_case, cwd): + def do_test_one(self, test_case, pcb2gcode_binary): test_prefix = os.path.join(test_case.input_path, "expected") + cwd = os.path.dirname(pcb2gcode_binary) input_path = os.path.join(cwd, test_case.input_path) expected_output_path = os.path.join(cwd, test_case.input_path, "expected") print(colored("\nRunning test case:\n" + "\n".join(" %s=%s" % (k,v) for k,v in test_case._asdict().items()), attrs=["bold"]), file=sys.stderr) - diff_text = self.run_one_directory(input_path, cwd, expected_output_path, test_prefix, test_case.args, test_case.exit_code) + diff_text = self.run_one_directory(input_path, pcb2gcode_binary, expected_output_path, test_prefix, test_case.args, test_case.exit_code) self.assertFalse(bool(diff_text), 'Files don\'t match\n' + diff_text) def cmp(x,y): @@ -340,13 +348,15 @@ def cmp(x,y): help='number of threads for running tests concurrently') parser.add_argument('--tests', type=str, default="", help='regex of tests to run') + parser.add_argument('--pcb2gcode-binary', type=str, default="", + help='path to pcb2gcode binary to run. The tests are expected to be in subdirectories of the directory containing the binary.') args = parser.parse_args() if args.tests: TEST_CASES = [t for t in TEST_CASES if re.search(args.tests, t.name)] - cwd = os.getcwd() + pcb2gcode_binary = os.path.join(os.getcwd(), "pcb2gcode") if not args.pcb2gcode_binary else args.pcb2gcode_binary def add_test_case(t): def test_method(self): - self.do_test_one(t, cwd) + self.do_test_one(t, pcb2gcode_binary) setattr(IntegrationTests, 'test_' + t.name, test_method) test_method.__name__ = 'test_' + t.name test_method.__doc__ = str(test_case) @@ -384,8 +394,11 @@ def test_method(self): test_loader.sortTestMethodsUsing = lambda x,y: cmp(all_test_names.index(x), all_test_names.index(y)) suite = test_loader.loadTestsFromTestCase(IntegrationTests) if args.jobs > 1: - suite = ConcurrentTestSuite(suite, fork_for_tests(args.jobs)) - if hasattr(sys.stderr, "isatty") and sys.stderr.isatty(): + if concurrencytest_available: + suite = ConcurrentTestSuite(suite, fork_for_tests(args.jobs)) + else: + print("WARNING: Module 'concurrencytest' not available. Running tests sequentially.", file=sys.stderr) + if colour_runner_available and hasattr(sys.stderr, "isatty") and sys.stderr.isatty(): test_result = colour_runner.runner.ColourTextTestRunner(verbosity=2).run(suite) else: test_result = unittest.TextTestRunner(verbosity=2).run(suite) From ddbb6bc28f831100e6a6c5386b9c1041d2f71445 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Tue, 13 Jan 2026 21:47:48 -0700 Subject: [PATCH 4/6] ci: Add support for running integration tests on more platforms. We still can't run on Windows because it gets the wrong answers. The integration tests are now run on all Ubuntu platforms with geos 3.13.1, which means that it runs for both g++ and g++-10. The code coverage should be combined. --- .github/workflows/ci.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 899e817e3..879a2723c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -348,11 +348,18 @@ jobs: run: make -j ${NUM_CPUS} check-valgrind || (cat mem-test-suite.log && false) - name: Run integration tests - if: matrix.geos != 'none' && matrix.geos != 'latest' && matrix.os == 'ubuntu' + if: matrix.geos != 'none' && matrix.geos != 'latest' && matrix.os != 'windows' run: | sudo apt-get install python3-setuptools pip3 install --user wheel colour_runner unittest2 termcolor concurrencytest in_place - ./integration_tests.py -j ${NUM_CPUS} + /usr/bin/python3 ./integration_tests.py --pcb2gcode-binary "$(pwd)/pcb2gcode" -j ${NUM_CPUS} + # Skip the Windows integration test because there are tiny discrepencies from expected outputs. + #- name: Run integration tests on Windows + # if: matrix.geos != 'none' && matrix.geos != 'latest' && matrix.os == 'windows' + # run: | + # pacman -S --noconfirm mingw-w64-x86_64-python-pip + # pip3 install --user wheel colour_runner unittest2 termcolor concurrencytest in_place + # python3 ./integration_tests.py --pcb2gcode-binary "$(pwd)/pcb2gcode.exe" -j ${NUM_CPUS} - name: Gather coverage if: matrix.code_coverage run: | @@ -480,7 +487,7 @@ jobs: if: matrix.code_coverage uses: actions/upload-artifact@v4 with: - name: version + name: version-${{ steps.sanitize-key.outputs.key }}.txt path: version.txt - name: Save cache uses: actions/cache/save@v5 From 229e39227375485b31804d3130cd54f887b8fdf1 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:06:57 -0700 Subject: [PATCH 5/6] ci: Don't use valgrind with code coverage. Just in case the valgrind confuses the code coverage collection. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 879a2723c..c028ad1c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -335,13 +335,13 @@ jobs: continue-on-error: true run: lcov --directory . -z - name: Run unit tests - if: matrix.os != 'ubuntu' + if: ! (matrix.os == 'ubuntu' && ! matrix.code_coverage) env: VERBOSE: 1 SKIP_GERBERIMPORTER_TESTS_PNG: 1 run: make -j ${NUM_CPUS} check || (cat test-suite.log && false) - name: Run unit tests with valgrind - if: matrix.os == 'ubuntu' + if: matrix.os == 'ubuntu' && ! matrix.code_coverage env: VERBOSE: 1 SKIP_GERBERIMPORTER_TESTS_PNG: 1 From 97cf6053f4c49415144cb45890e944531bd7a27f Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:07:58 -0700 Subject: [PATCH 6/6] ci: Only use code coverage on g++-10. It's slower on the newer g++ for some reason. --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c028ad1c6..a39b85404 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,7 @@ jobs: - os: ubuntu geos: '3.13.1' code_coverage: "--enable-code-coverage" + compiler: 'g++-10' # Only code coverage with g++-10 because latest g++ coverage is slow. - os: ubuntu shell: '/usr/bin/bash -l -e -o pipefail {0}' local_install_path: '$HOME/.local'