tests: Speed up tests by a lot by bulk-caching kicad footprint renders
This commit is contained in:
parent
f447b12571
commit
ec85d6c169
2 changed files with 61 additions and 10 deletions
|
|
@ -1,11 +1,14 @@
|
|||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tqdm
|
||||
import multiprocessing.pool
|
||||
import subprocess
|
||||
from itertools import chain
|
||||
|
||||
import pytest
|
||||
|
||||
from .image_support import ImageDifference, run_cargo_cmd
|
||||
from .image_support import ImageDifference, run_cargo_cmd, bulk_populate_kicad_fp_export_cache
|
||||
|
||||
def pytest_assertrepr_compare(op, left, right):
|
||||
if isinstance(left, ImageDifference) or isinstance(right, ImageDifference):
|
||||
|
|
@ -24,41 +27,53 @@ def pytest_runtest_makereport(item, call):
|
|||
|
||||
fail_dir = Path('gerbonara_test_failures')
|
||||
def pytest_sessionstart(session):
|
||||
if not hasattr(session.config, 'workerinput'): # on worker
|
||||
if 'PYTEST_XDIST_WORKER' in os.environ: # only run this on the controller
|
||||
return
|
||||
|
||||
# on coordinator
|
||||
for f in chain(fail_dir.glob('*.gbr'), fail_dir.glob('*.png')):
|
||||
f.unlink()
|
||||
|
||||
try:
|
||||
run_cargo_cmd('resvg', '--help')
|
||||
run_cargo_cmd('resvg', '--help', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except FileNotFoundError:
|
||||
pytest.exit('resvg binary not found, aborting test.', 2)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
if 'PYTEST_XDIST_WORKER' in os.environ: # only run this on the controller
|
||||
return
|
||||
|
||||
if (lib_dir := os.environ.get('KICAD_FOOTPRINTS')):
|
||||
lib_dir = Path(lib_dir).expanduser()
|
||||
if not lib_dir.is_dir():
|
||||
raise ValueError(f'Path "{lib_dir}" given by KICAD_FOOTPRINTS environment variable does not exist or is not a directory.')
|
||||
|
||||
print('Checking and bulk re-building KiCad footprint library cache')
|
||||
with multiprocessing.pool.ThreadPool() as pool: # use thread pool here since we're only monitoring podman processes
|
||||
lib_dirs = list(lib_dir.glob('*.pretty'))
|
||||
res = list(tqdm.tqdm(pool.imap(lambda path: bulk_populate_kicad_fp_export_cache(path), lib_dirs), total=len(lib_dirs)))
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption('--kicad-symbol-library', nargs='*', help='Run symbol library tests on given symbol libraries. May be given multiple times.')
|
||||
parser.addoption('--kicad-footprint-files', nargs='*', help='Run footprint library tests on given footprint files. May be given multiple times.')
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if 'kicad_library_file' in metafunc.fixturenames:
|
||||
if not (library_files := metafunc.config.getoption('symbol_library', None)):
|
||||
if (lib_dir := os.environ.get('KICAD_SYMBOLS')):
|
||||
lib_dir = Path(lib_dir).expanduser()
|
||||
if not lib_dir.is_dir():
|
||||
raise ValueError(f'Path "{lib_dir}" given by KICAD_SYMBOLS environment variable does not exist or is not a directory.')
|
||||
library_files = list(lib_dir.glob('*.kicad_sym'))
|
||||
else:
|
||||
raise ValueError('Either --kicad-symbol-library command line parameter or KICAD_SYMBOLS environment variable must be given.')
|
||||
raise ValueError('Either --kicad-symbol-library command line parameter or KICAD_SYMBOLS environment variable must be given to run kicad symbol tests.')
|
||||
metafunc.parametrize('kicad_library_file', library_files, ids=list(map(str, library_files)))
|
||||
|
||||
if 'kicad_mod_file' in metafunc.fixturenames:
|
||||
if not (mod_files := metafunc.config.getoption('footprint_files', None)):
|
||||
if (lib_dir := os.environ.get('KICAD_FOOTPRINTS')):
|
||||
lib_dir = Path(lib_dir).expanduser()
|
||||
if not lib_dir.is_dir():
|
||||
raise ValueError(f'Path "{lib_dir}" given by KICAD_FOOTPRINTS environment variable does not exist or is not a directory.')
|
||||
mod_files = list(lib_dir.glob('*.pretty/*.kicad_mod'))
|
||||
else:
|
||||
raise ValueError('Either --kicad-footprint-files command line parameter or KICAD_FOOTPRINTS environment variable must be given.')
|
||||
raise ValueError('Either --kicad-footprint-files command line parameter or KICAD_FOOTPRINTS environment variable must be given to run kicad footprint tests.')
|
||||
metafunc.parametrize('kicad_mod_file', mod_files, ids=list(map(str, mod_files)))
|
||||
|
|
|
|||
|
|
@ -23,13 +23,17 @@ from pathlib import Path
|
|||
import tempfile
|
||||
import textwrap
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
import random
|
||||
import statistics
|
||||
from functools import total_ordering
|
||||
import shutil
|
||||
import bs4
|
||||
from contextlib import contextmanager
|
||||
import hashlib
|
||||
|
||||
import tqdm
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
|
@ -174,6 +178,38 @@ def kicad_fp_export(mod_file, out_svg):
|
|||
print(f'Re-using cache for {mod_file.name}')
|
||||
shutil.copy(cachefile, out_svg)
|
||||
|
||||
|
||||
def bulk_populate_kicad_fp_export_cache(pretty_dir):
|
||||
def cachefile(mod_file):
|
||||
params = f'(noparams)'.encode()
|
||||
digest = hashlib.blake2b(mod_file.read_bytes() + params).hexdigest()
|
||||
return cachedir / f'{digest}.svg'
|
||||
|
||||
mod_files = list(pretty_dir.glob('*.kicad_mod'))
|
||||
hit_rate = statistics.mean([int(cachefile(fn).is_file())
|
||||
for fn in random.sample(mod_files, min(len(mod_files), 50))])
|
||||
|
||||
if hit_rate < 0.9:
|
||||
#tqdm.tqdm.write(f'Modfile cache is out of date (hit rate {hit_rate*100:.0f}%), re-building entire cache in bulk')
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
os.chmod(tmpdir, 0o1777)
|
||||
cmd = ['podman', 'run',
|
||||
'--rm', # Clean up volumes after exit
|
||||
'--userns=keep-id', # To allow container to read from bind mount
|
||||
'--mount', f'type=bind,src={pretty_dir},dst=/{pretty_dir.name}',
|
||||
'--mount', f'type=bind,src={tmpdir},dst=/out',
|
||||
'registry.hub.docker.com/kicad/kicad:nightly',
|
||||
'kicad-cli', 'fp', 'export', 'svg', '--output', '/out', f'/{pretty_dir.name}']
|
||||
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL)
|
||||
|
||||
for fn in mod_files:
|
||||
out_file = Path(tmpdir) / fn.with_suffix('.svg').name
|
||||
if not out_file.is_file():
|
||||
tqdm.tqdm.write(f'Output file {out_file} is missing while bulk re-building cache for {pretty_dir}.')
|
||||
else:
|
||||
shutil.copy(out_file, cachefile(fn))
|
||||
|
||||
@contextmanager
|
||||
def svg_soup(filename):
|
||||
with open(filename, 'r') as f:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue