Recoded all the tests. I used the class I wrote for kicad-automation-scripts.

Now the tests are simpler, I also suitched to test the application from "outside"
because I was having some ridiculous fails.
Also added coverage meassurement, which is currently pathetic.
This commit is contained in:
Salvador E. Tropea 2020-05-14 13:11:31 -03:00
parent 450b0ab671
commit d500178c24
13 changed files with 365 additions and 344 deletions

10
.coveragerc Normal file
View File

@ -0,0 +1,10 @@
[run]
source =
kiplot
src
[report]
exclude_lines =
pragma: no cover
# raise RuntimeError

4
.gitignore vendored
View File

@ -30,3 +30,7 @@ MANIFEST
*.kicad_pcb-bak
*.epr
htmlcov/
pp/
output/
.coverage

View File

@ -21,7 +21,7 @@ test: lint
test_local: lint
rm -rf output
$(PY_COV) erase
pytest-3 --plot_dir output
pytest-3 --test_dir output
$(PY_COV) report
$(PY_COV) html
x-www-browser htmlcov/index.html

View File

@ -4,6 +4,6 @@ Test configuration
def pytest_addoption(parser):
parser.addoption("--plot_dir", action="store", default=None,
help="the plot dir to use (omit to use a temp dir). "
"If given, plots will _not_ be cleared after testing.")
parser.addoption("--test_dir", action="store", default=None,
help="the test output dir to use (omit to use a temp dir). "
"If given, outputs will _not_ be cleared after testing.")

View File

@ -1,121 +0,0 @@
import os
import shutil
import tempfile
import logging
import pytest
import sys
here = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, os.path.dirname(os.path.dirname(here)))
from kiplot import kiplot
from kiplot import config_reader
KICAD_PCB_EXT = '.kicad_pcb'
class KiPlotTestContext(object):
def __init__(self, test_name):
self.cfg = None
# The name used for the test output dirs and other logging
self.test_name = test_name
# The name of the PCB board file (will be interpolated into the plot
# files by pcbnewm so we need to know
self.board_name = None
# The actual board file that will be loaded
self.board_file = None
# The directory under which to place plots (None: use a temp dir)
self.plot_dir = pytest.config.getoption('plot_dir')
# The actual output dir for this plot run
self._output_dir = None
# Clean the output dir afterwards (true for temp dirs)
self._del_dir_after = self.plot_dir is None
def _get_text_cfg_dir(self):
this_dir = os.path.dirname(os.path.realpath(__file__))
return os.path.join(this_dir, '../yaml_samples')
def _get_board_cfg_dir(self):
this_dir = os.path.dirname(os.path.realpath(__file__))
return os.path.join(this_dir, '../board_samples')
def load_yaml_config_file(self, filename):
"""
Reads a config from a YAML file
"""
cfg_file = os.path.join(self._get_text_cfg_dir(), filename)
pcb_file = os.path.join(self._get_board_cfg_dir(),
self.board_name + KICAD_PCB_EXT)
cr = config_reader.CfgYamlReader(pcb_file)
with open(cfg_file) as cf_file:
cfg = cr.read(cf_file)
self.cfg = cfg
def _load_board_file(self, filename=None):
"""
Load the named board.
@param filename: a filename to load, or None to load the relevant
board name from the board sample dir
"""
if filename is None:
self.board_file = os.path.join(self._get_board_cfg_dir(),
self.board_name + KICAD_PCB_EXT)
else:
self.board_file = filename
assert os.path.isfile(self.board_file)
def _set_up_output_dir(self):
if not self.plot_dir:
# create a tmp dir
self.output_dir = tempfile.mkdtemp(
prefix='tmp_kiplot_{}'.format(self.test_name))
else:
self.output_dir = os.path.join(self.plot_dir, self.test_name)
# just create the dir
if os.path.isdir(self.output_dir):
# exists, that's OK
pass
else:
os.makedirs(self.output_dir)
self.cfg.outdir = self.output_dir
logging.info('Output dir: '+self.output_dir)
def clean_up(self):
logging.debug('Clean-up')
if self._del_dir_after:
logging.debug('Removing dir')
shutil.rmtree(self.output_dir)
def do_plot(self):
self.cfg.validate()
self._load_board_file(self.board_file)
self._set_up_output_dir()
plotter = kiplot.Plotter(self.cfg)
plotter.plot(self.board_file, '', False, ['all'])

View File

@ -13,165 +13,78 @@ pytest-3 --log-cli-level debug
"""
from . import plotting_test_utils
import os
import mmap
import re
import logging
import sys
# Look for the 'utils' module from where the script is running
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(script_dir))
# Utils import
from utils import context
POS_DIR = 'positiondir'
positions = {'R1': (105, 35, 'top'), 'R2': (110, 35, 'bottom'), 'R3': (110, 45, 'top')}
def expect_file_at(filename):
assert(os.path.isfile(filename))
def get_pos_top_filename(board_name):
return board_name + '-top.pos'
def get_pos_bot_filename(board_name):
return board_name + '-bottom.pos'
def get_pos_both_filename(board_name):
return board_name + '-both.pos'
def expect_position(pos_data, side, ref, x, y, expected, inches=False):
def expect_position(ctx, file, comp, no_comp=[], inches=False):
"""
Check if a component is or isn't in the file
Check if a list of components are or aren't in the file
"""
# expr = rb'^'+ref.encode()+rb'\s+\S+\s+\S+\s+([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)\s+(\S+)$'
expr = rb'^'+ref.encode()+rb'\s+\S+\s+\S+\s+([-\d\.]+)\s+([-\d\.]+)\s+([-\d\.]+)\s+(\S+)\s*$'
m = re.search(expr, pos_data, re.MULTILINE)
if m:
logging.debug("Position found for " + ref)
else:
logging.debug("Position not found for " + ref)
if expected:
assert(m)
# Components that must be found
texts = []
for k in comp:
texts.append('^'+k+r'\s+\S+\s+\S+\s+([-\d\.]+)\s+([-\d\.]+)\s+([-\d\.]+)\s+(\S+)\s*$')
res = ctx.search_in_file(file, texts)
for k in comp:
x, y, side = positions[k]
if inches:
x = x/25.4
y = y/25.4
assert(abs(float(x) - float(m.group(1))) < 0.001)
assert(abs(float(y) + float(m.group(2))) < 0.001)
assert(side == m.group(4).decode())
# logging.debug(ref+' '+str(x)+' '+str(y)+' -> '+m.group(1).decode()+' '+m.group(2).decode()+' '+m.group(3).decode()+
# ' '+m.group(4).decode())
else:
assert(m is None)
matches = res.pop(0)
assert(abs(float(x) - float(matches[0])) < 0.001)
assert(abs(float(y) + float(matches[1])) < 0.001)
assert(side == matches[3])
def get_mmapped_data(filename):
with open(filename) as fo:
return mmap.mmap(fo.fileno(), 0, access=mmap.ACCESS_READ)
# Components that must not be found
texts = []
for k in no_comp:
expr = '^'+k+r'\s+\S+\s+\S+\s+([-\d\.]+)\s+([-\d\.]+)\s+([-\d\.]+)\s+(\S+)\s*$'
texts.append(expr)
ctx.search_not_in_file(file, texts)
def test_3Rs_position():
ctx = plotting_test_utils.KiPlotTestContext('3Rs_position')
ctx.board_name = '3Rs'
ctx.load_yaml_config_file('simple_position.yaml')
ctx.do_plot()
pos_dir = ctx.cfg.resolve_output_dir_for_name('position')
pos_top = os.path.join(pos_dir, get_pos_top_filename(ctx.board_name))
pos_bot = os.path.join(pos_dir, get_pos_bot_filename(ctx.board_name))
expect_file_at(pos_top)
expect_file_at(pos_bot)
top = get_mmapped_data(pos_top)
bot = get_mmapped_data(pos_bot)
expect_position(top, 'top', 'R1', 105, 35, True)
expect_position(bot, 'bottom', 'R1', 105, 35, False)
expect_position(top, 'top', 'R2', 110, 35, False)
expect_position(bot, 'bottom', 'R2', 110, 35, True)
expect_position(top, 'top', 'R3', 110, 45, False)
expect_position(bot, 'bottom', 'R3', 110, 45, False)
ctx = context.TestContext('3Rs_position', '3Rs', 'simple_position')
ctx.run()
pos_top = ctx.get_pos_top_filename(POS_DIR)
pos_bot = ctx.get_pos_bot_filename(POS_DIR)
ctx.expect_out_file(pos_top)
ctx.expect_out_file(pos_bot)
expect_position(ctx, pos_top, ['R1'], ['R2', 'R3'])
expect_position(ctx, pos_bot, ['R2'], ['R1', 'R3'])
ctx.clean_up()
def test_3Rs_position_unified():
ctx = plotting_test_utils.KiPlotTestContext('3Rs_position_unified')
ctx.board_name = '3Rs'
ctx.load_yaml_config_file('simple_position_unified.yaml')
ctx.do_plot()
pos_dir = ctx.cfg.resolve_output_dir_for_name('position')
pos_both = os.path.join(pos_dir, get_pos_both_filename(ctx.board_name))
expect_file_at(pos_both)
both = get_mmapped_data(pos_both)
expect_position(both, 'top', 'R1', 105, 35, True)
expect_position(both, 'bottom', 'R2', 110, 35, True)
expect_position(both, '', 'R3', 110, 45, False)
ctx = context.TestContext('3Rs_position_unified', '3Rs', 'simple_position_unified')
ctx.run()
expect_position(ctx, ctx.get_pos_both_filename(POS_DIR), ['R1', 'R2'], ['R3'])
ctx.clean_up()
def test_3Rs_position_unified_th():
ctx = plotting_test_utils.KiPlotTestContext('3Rs_position_unified_th')
ctx.board_name = '3Rs'
ctx.load_yaml_config_file('simple_position_unified_th.yaml')
ctx.do_plot()
pos_dir = ctx.cfg.resolve_output_dir_for_name('position')
pos_both = os.path.join(pos_dir, get_pos_both_filename(ctx.board_name))
expect_file_at(pos_both)
both = get_mmapped_data(pos_both)
expect_position(both, 'top', 'R1', 105, 35, True)
expect_position(both, 'bottom', 'R2', 110, 35, True)
expect_position(both, 'top', 'R3', 110, 45, True)
ctx = context.TestContext('3Rs_position_unified_th', '3Rs', 'simple_position_unified_th')
ctx.run()
expect_position(ctx, ctx.get_pos_both_filename(POS_DIR), ['R1', 'R2', 'R3'])
ctx.clean_up()
def test_3Rs_position_inches():
ctx = plotting_test_utils.KiPlotTestContext('3Rs_position_inches')
ctx.board_name = '3Rs'
ctx.load_yaml_config_file('simple_position_inches.yaml')
ctx.do_plot()
pos_dir = ctx.cfg.resolve_output_dir_for_name('position')
pos_top = os.path.join(pos_dir, get_pos_top_filename(ctx.board_name))
pos_bot = os.path.join(pos_dir, get_pos_bot_filename(ctx.board_name))
expect_file_at(pos_top)
expect_file_at(pos_bot)
top = get_mmapped_data(pos_top)
bot = get_mmapped_data(pos_bot)
expect_position(top, 'top', 'R1', 105, 35, True, True)
expect_position(bot, 'bottom', 'R1', 105, 35, False, True)
expect_position(top, 'top', 'R2', 110, 35, False, True)
expect_position(bot, 'bottom', 'R2', 110, 35, True, True)
expect_position(top, 'top', 'R3', 110, 45, False, True)
expect_position(bot, 'bottom', 'R3', 110, 45, False, True)
ctx = context.TestContext('3Rs_position_inches', '3Rs', 'simple_position_inches')
ctx.run()
pos_top = ctx.get_pos_top_filename(POS_DIR)
pos_bot = ctx.get_pos_bot_filename(POS_DIR)
ctx.expect_out_file(pos_top)
ctx.expect_out_file(pos_bot)
expect_position(ctx, pos_top, ['R1'], ['R2', 'R3'], True)
expect_position(ctx, pos_bot, ['R2'], ['R1', 'R3'], True)
ctx.clean_up()

View File

@ -1,112 +1,64 @@
"""
Tests of simple 2-layer PCBs
"""
Tests of simple 2-layer PCBs.
We generate the gerbers.
from . import plotting_test_utils
For debug information use:
pytest-3 --log-cli-level debug
"""
import os
import mmap
import re
import sys
import logging
# Look for the 'utils' module from where the script is running
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(script_dir))
# Utils import
from utils import context
def expect_file_at(filename):
assert(os.path.isfile(filename))
def get_gerber_filename(board_name, layer_slug, ext='.gbr'):
return board_name + '-' + layer_slug + ext
def get_gerber_job_filename(board_name):
return board_name + '-job.gbrjob'
def find_gerber_aperture(s, ap_desc):
m = re.search(rb'%AD(.*)' + ap_desc + rb'\*%', s)
if not m:
return None
return m.group(1)
def expect_gerber_has_apertures(gbr_data, ap_list):
aps = []
def expect_gerber_has_apertures(ctx, file, ap_list):
ap_matches = []
for ap in ap_list:
# find the circular aperture for the outline
ap_no = find_gerber_aperture(gbr_data, ap)
ap_matches.append(r'%AD(.*)'+ap+r'\*%')
grps = ctx.search_in_file(file, ap_matches)
aps = []
for grp in grps:
ap_no = grp[0]
assert ap_no is not None
# apertures from D10 to D999
assert len(ap_no) in [2, 3]
aps.append(ap_no)
logging.debug("Found apertures {}".format(aps))
return aps
def expect_gerber_flash_at(gbr_data, pos):
def expect_gerber_flash_at(ctx, file, pos):
"""
Check for a gerber flash at a given point
(it's hard to check that aperture is right without a real gerber parser
"""
repat = r'^X{x}Y{y}D03\*$'.format(
x=int(pos[0] * 100000),
y=int(pos[1] * 100000)
)
m = re.search(repat.encode(), gbr_data, re.MULTILINE)
assert(m)
logging.debug("Gerber flash found: " + repat)
repat = r'^X{x}Y{y}D03\*$'.format(x=int(pos[0]*100000), y=int(pos[1]*100000))
ctx.search_in_file(file, [repat])
logging.debug("Gerber flash found: "+repat)
def get_mmapped_data(filename):
with open(filename) as fo:
return mmap.mmap(fo.fileno(), 0, access=mmap.ACCESS_READ)
# content of test_sample.py
def test_2layer():
prj = 'simple_2layer'
ctx = context.TestContext('Simple_2_layer', prj, prj)
ctx.run()
ctx = plotting_test_utils.KiPlotTestContext('simple_2layer')
g_dir = 'gerberdir'
f_cu = ctx.get_gerber_filename(g_dir, 'F_Cu')
ctx.expect_out_file(f_cu)
ctx.expect_out_file(ctx.get_gerber_job_filename(g_dir))
ctx.board_name = 'simple_2layer'
ctx.load_yaml_config_file('simple_2layer.kiplot.yaml')
ctx.do_plot()
gbr_dir = ctx.cfg.resolve_output_dir_for_name('gerbers')
f_cu_gbr = os.path.join(gbr_dir,
get_gerber_filename(ctx.board_name, "F_Cu"))
expect_file_at(f_cu_gbr)
# The gerber job file
job_file = os.path.join(gbr_dir,
get_gerber_job_filename(ctx.board_name))
expect_file_at(job_file)
f_cu_data = get_mmapped_data(f_cu_gbr)
expect_gerber_has_apertures(f_cu_data, [
rb"C,0.200000",
rb"R,2.000000X2.000000",
rb"C,1.000000"])
expect_gerber_has_apertures(ctx, f_cu, [
r"C,0.200000",
r"R,2.000000X2.000000",
r"C,1.000000"])
# expect a flash for the square pad
expect_gerber_flash_at(f_cu_data, (140, -100))
expect_gerber_flash_at(ctx, f_cu, (140, -100))
ctx.clean_up()

263
tests/utils/context.py Normal file
View File

@ -0,0 +1,263 @@
import os
import shutil
import tempfile
import logging
import subprocess
import re
import pytest
from glob import glob
from pty import openpty
COVERAGE_SCRIPT = 'python3-coverage'
KICAD_PCB_EXT = '.kicad_pcb'
KICAD_SCH_EXT = '.sch'
REF_DIR = 'N/A'
MODE_SCH = 1
MODE_PCB = 0
class TestContext(object):
def __init__(self, test_name, board_name, yaml_name):
# We are using PCBs
self.mode = MODE_PCB
# The name used for the test output dirs and other logging
self.test_name = test_name
# The name of the PCB board file
self.board_name = board_name
# The actual board file that will be loaded
self._get_board_file()
# The YAML file we'll use
self._get_yaml_name(yaml_name)
# The actual output dir for this run
self._set_up_output_dir(pytest.config.getoption('test_dir'))
# stdout and stderr from the run
self.out = None
self.err = None
self.proc = None
def _get_board_dir(self):
this_dir = os.path.dirname(os.path.realpath(__file__))
return os.path.join(this_dir, '../board_samples')
def _get_board_file(self):
self.board_file = os.path.abspath(os.path.join(self._get_board_dir(),
self.board_name +
(KICAD_PCB_EXT if self.mode == MODE_PCB else KICAD_SCH_EXT)))
logging.info('KiCad file: '+self.board_file)
assert os.path.isfile(self.board_file)
def _get_yaml_dir(self):
this_dir = os.path.dirname(os.path.realpath(__file__))
return os.path.join(this_dir, '../yaml_samples')
def _get_yaml_name(self, name):
self.yaml_file = os.path.abspath(os.path.join(self._get_yaml_dir(), name+'.kiplot.yaml'))
logging.info('YAML file: '+self.yaml_file)
assert os.path.isfile(self.yaml_file)
def _set_up_output_dir(self, test_dir):
if test_dir:
self.output_dir = os.path.join(test_dir, self.test_name)
os.makedirs(self.output_dir, exist_ok=True)
self._del_dir_after = False
else:
# create a tmp dir
self.output_dir = tempfile.mkdtemp(prefix='tmp-kiplot-'+self.test_name+'-')
self._del_dir_after = True
logging.info('Output dir: '+self.output_dir)
def clean_up(self):
logging.debug('Clean-up')
if self._del_dir_after:
logging.debug('Removing dir')
shutil.rmtree(self.output_dir)
def get_out_path(self, filename):
return os.path.join(self.output_dir, filename)
def get_gerber_job_filename(self, dir):
return os.path.join(dir, self.board_name+'-job.gbrjob')
def get_gerber_filename(self, dir, layer_slug, ext='.gbr'):
return os.path.join(dir, self.board_name+'-'+layer_slug+ext)
def get_pos_top_filename(self, dir):
return os.path.join(dir, self.board_name+'-top.pos')
def get_pos_bot_filename(self, dir):
return os.path.join(dir, self.board_name+'-bottom.pos')
def get_pos_both_filename(self, dir):
return os.path.join(dir, self.board_name+'-both.pos')
def expect_out_file(self, filename):
file = self.get_out_path(filename)
assert os.path.isfile(file)
assert os.path.getsize(file) > 0
return file
def dont_expect_out_file(self, filename):
file = self.get_out_path(filename)
assert not os.path.isfile(file)
def create_dummy_out_file(self, filename):
file = self.get_out_path(filename)
with open(file, 'w') as f:
f.write('Dummy file\n')
def run(self, ret_val=None, extra=None, use_a_tty=False, filename=None):
logging.debug('Running '+self.test_name)
# Change the command to be local and add the board and output arguments
cmd = [COVERAGE_SCRIPT, 'run', '-a']
cmd.append(os.path.abspath(os.path.dirname(os.path.abspath(__file__))+'/../../src/kiplot'))
cmd = cmd+['-b', filename if filename else self.board_file]
cmd = cmd+['-c', self.yaml_file]
cmd = cmd+['-d', self.output_dir]
if extra is not None:
cmd = cmd+extra
logging.debug(cmd)
out_filename = self.get_out_path('output.txt')
err_filename = self.get_out_path('error.txt')
if use_a_tty:
# This is used to test the coloured logs, we need stderr to be a TTY
master, slave = openpty()
f_err = slave
f_out = slave
else:
# Redirect stdout and stderr to files
f_out = os.open(out_filename, os.O_RDWR | os.O_CREAT)
f_err = os.open(err_filename, os.O_RDWR | os.O_CREAT)
# Run the process
process = subprocess.Popen(cmd, stdout=f_out, stderr=f_err)
ret_code = process.wait()
logging.debug('ret_code '+str(ret_code))
if use_a_tty:
self.err = os.read(master, 10000)
self.err = self.err.decode()
self.out = self.err
exp_ret = 0 if ret_val is None else ret_val
assert ret_code == exp_ret
if use_a_tty:
os.close(master)
os.close(slave)
with open(out_filename, 'w') as f:
f.write(self.out)
with open(err_filename, 'w') as f:
f.write(self.out)
else:
# Read stdout
os.lseek(f_out, 0, os.SEEK_SET)
self.out = os.read(f_out, 10000)
os.close(f_out)
self.out = self.out.decode()
# Read stderr
os.lseek(f_err, 0, os.SEEK_SET)
self.err = os.read(f_err, 10000)
os.close(f_err)
self.err = self.err.decode()
def search_out(self, text):
m = re.search(text, self.out, re.MULTILINE)
return m
def search_err(self, text):
m = re.search(text, self.err, re.MULTILINE)
return m
def search_in_file(self, file, texts):
logging.debug('Searching in "'+file+'" output')
with open(self.get_out_path(file)) as f:
txt = f.read()
res = []
for t in texts:
logging.debug('- r"'+t+'"')
m = re.search(t, txt, re.MULTILINE)
assert m
# logging.debug(' '+m.group(0))
res.append(m.groups())
return res
def search_not_in_file(self, file, texts):
logging.debug('Searching not in "'+file+'" output')
with open(self.get_out_path(file)) as f:
txt = f.read()
for t in texts:
logging.debug('- r"'+t+'"')
m = re.search(t, txt, re.MULTILINE)
assert m is None
def compare_image(self, image, reference=None, diff='diff.png'):
""" For images and single page PDFs """
if reference is None:
reference = image
cmd = ['compare', '-metric', 'MSE',
self.get_out_path(image),
os.path.join(REF_DIR, reference),
self.get_out_path(diff)]
logging.debug('Comparing images with: '+str(cmd))
res = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
m = re.match(r'([\d\.]+) \(([\d\.]+)\)', res.decode())
assert m
logging.debug('MSE={} ({})'.format(m.group(1), m.group(2)))
assert float(m.group(2)) == 0.0
def compare_pdf(self, gen, reference=None, diff='diff-{}.png'):
""" For multi-page PDFs """
if reference is None:
reference = gen
logging.debug('Comparing PDFs: '+gen+' vs '+reference)
# Split the reference
logging.debug('Splitting '+reference)
cmd = ['convert', '-density', '150',
os.path.join(REF_DIR, reference),
self.get_out_path('ref-%d.png')]
subprocess.check_call(cmd)
# Split the generated
logging.debug('Splitting '+gen)
cmd = ['convert', '-density', '150',
self.get_out_path(gen),
self.get_out_path('gen-%d.png')]
subprocess.check_call(cmd)
# Chek number of pages
ref_pages = glob(self.get_out_path('ref-*.png'))
gen_pages = glob(self.get_out_path('gen-*.png'))
logging.debug('Pages {} vs {}'.format(len(gen_pages), len(ref_pages)))
assert len(ref_pages) == len(gen_pages)
# Compare each page
for page in range(len(ref_pages)):
cmd = ['compare', '-metric', 'MSE',
self.get_out_path('ref-'+str(page)+'.png'),
self.get_out_path('gen-'+str(page)+'.png'),
self.get_out_path(diff.format(page))]
logging.debug('Comparing images with: '+str(cmd))
res = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
m = re.match(r'([\d\.]+) \(([\d\.]+)\)', res.decode())
assert m
logging.debug('MSE={} ({})'.format(m.group(1), m.group(2)))
assert float(m.group(2)) == 0.0
def compare_txt(self, text, reference=None, diff='diff.txt'):
if reference is None:
reference = text
cmd = ['/bin/sh', '-c', 'diff -ub '+os.path.join(REF_DIR, reference)+' ' +
self.get_out_path(text)+' > '+self.get_out_path(diff)]
logging.debug('Comparing texts with: '+str(cmd))
res = subprocess.call(cmd)
assert res == 0
def filter_txt(self, file, pattern, repl):
fname = self.get_out_path(file)
with open(fname) as f:
txt = f.read()
with open(fname, 'w') as f:
f.write(re.sub(pattern, repl, txt))
class TestContextSCH(TestContext):
def __init__(self, test_name, board_name, yaml_name):
super().__init__(test_name, board_name, yaml_name)
self.mode = MODE_SCH
self._get_board_file()