test: Format with black

Signed-off-by: Jonathan Woollett-Light <jcawl@amazon.co.uk>
This commit is contained in:
Jonathan Woollett-Light
2023-06-05 09:55:04 +01:00
committed by Jonathan Woollett-Light
parent 285971e8c7
commit 56bfdc97bb
8 changed files with 198 additions and 194 deletions

View File

@@ -64,11 +64,11 @@ CONTAINER_VERSION = "v24"
# This represents the version of the Buildkite Docker plugin.
DOCKER_PLUGIN_VERSION = "v5.3.0"
X86_AGENT_TAGS = os.getenv('X86_LINUX_AGENT_TAGS')
AARCH64_AGENT_TAGS = os.getenv('AARCH64_LINUX_AGENT_TAGS')
DOCKER_PLUGIN_CONFIG = os.getenv('DOCKER_PLUGIN_CONFIG')
TESTS_TO_SKIP = os.getenv('TESTS_TO_SKIP')
TIMEOUTS_MIN = os.getenv('TIMEOUTS_MIN')
X86_AGENT_TAGS = os.getenv("X86_LINUX_AGENT_TAGS")
AARCH64_AGENT_TAGS = os.getenv("AARCH64_LINUX_AGENT_TAGS")
DOCKER_PLUGIN_CONFIG = os.getenv("DOCKER_PLUGIN_CONFIG")
TESTS_TO_SKIP = os.getenv("TESTS_TO_SKIP")
TIMEOUTS_MIN = os.getenv("TIMEOUTS_MIN")
# This env allows setting the hypervisor on which the tests are running at the
# pipeline level. This will not override the hypervisor tag in case one is
# already specified in the test definition.
@@ -76,7 +76,7 @@ TIMEOUTS_MIN = os.getenv('TIMEOUTS_MIN')
# experiencing some timeouts mostly with the mshv hosts right now, and we are
# fixing the default to kvm to work around that problem.
# More details here: https://github.com/rust-vmm/community/issues/137
DEFAULT_AGENT_TAG_HYPERVISOR = os.getenv('DEFAULT_AGENT_TAG_HYPERVISOR', 'kvm')
DEFAULT_AGENT_TAG_HYPERVISOR = os.getenv("DEFAULT_AGENT_TAG_HYPERVISOR", "kvm")
PARENT_DIR = pathlib.Path(__file__).parent.resolve()
@@ -99,59 +99,59 @@ class BuildkiteStep:
# dictionaries are ordered. For readability reasons, this order should
# not be changed.
self.step_config = {
'label': None,
'command': None,
'retry': {'automatic': False},
'agents': {'os': 'linux'},
'plugins': [
"label": None,
"command": None,
"retry": {"automatic": False},
"agents": {"os": "linux"},
"plugins": [
{
f"docker#{DOCKER_PLUGIN_VERSION}": {
'image': f"rustvmm/dev:{CONTAINER_VERSION}",
'always-pull': True
"image": f"rustvmm/dev:{CONTAINER_VERSION}",
"always-pull": True,
}
}
],
'timeout_in_minutes': 15
"timeout_in_minutes": 15,
}
def _set_platform(self, platform):
""" Set platform if given in the json input. """
"""Set platform if given in the json input."""
if platform:
# We need to change `aarch64` to `arm` because of the way we are
# setting the tags on the host.
if platform == 'aarch64':
platform = 'arm'
self.step_config['agents']['platform'] = f"{platform}.metal"
if platform == "aarch64":
platform = "arm"
self.step_config["agents"]["platform"] = f"{platform}.metal"
def _set_hypervisor(self, hypervisor):
""" Set hypervisor if given in the json input. """
supported_hypervisors = ['kvm', 'mshv']
"""Set hypervisor if given in the json input."""
supported_hypervisors = ["kvm", "mshv"]
if hypervisor:
if hypervisor in supported_hypervisors:
self.step_config['agents']['hypervisor'] = hypervisor
self.step_config["agents"]["hypervisor"] = hypervisor
def _set_conditional(self, conditional):
""" Set conditional if given in the json input. """
"""Set conditional if given in the json input."""
if conditional:
self.step_config['if'] = conditional
self.step_config["if"] = conditional
def _set_timeout_in_minutes(self, timeout):
""" Set the timeout if given in the json input. """
"""Set the timeout if given in the json input."""
if timeout:
self.step_config['timeout_in_minutes'] = timeout
self.step_config["timeout_in_minutes"] = timeout
def _set_agent_queue(self, queue):
"""Set the agent queue if provided in the json input."""
if queue:
self.step_config['agents']['queue'] = queue
self.step_config["agents"]["queue"] = queue
def _add_docker_config(self, cfg):
""" Add configuration for docker if given in the json input. """
"""Add configuration for docker if given in the json input."""
if cfg:
target = self.step_config['plugins'][0][f"docker#{DOCKER_PLUGIN_VERSION}"]
target = self.step_config["plugins"][0][f"docker#{DOCKER_PLUGIN_VERSION}"]
for key, val in cfg.items():
target[key] = val
@@ -164,13 +164,11 @@ class BuildkiteStep:
if env_var:
env_cfg = json.loads(env_var)
tests = env_cfg.get('tests')
assert tests, \
f"Environment variable {env_var} is missing the `tests` key."
tests = env_cfg.get("tests")
assert tests, f"Environment variable {env_var} is missing the `tests` key."
cfg = env_cfg.get('cfg')
assert cfg, \
f"Environment variable {env_var} is missing the `cfg` key."
cfg = env_cfg.get("cfg")
assert cfg, f"Environment variable {env_var} is missing the `cfg` key."
if test_name in tests:
if override:
@@ -186,17 +184,17 @@ class BuildkiteStep:
"""
env_var = None
platform = self.step_config['agents'].get('platform')
platform = self.step_config["agents"].get("platform")
# Since the platform is optional, only override the config if the
# platform was provided.
if platform:
if platform == 'x86_64.metal' and X86_AGENT_TAGS:
if platform == "x86_64.metal" and X86_AGENT_TAGS:
env_var = X86_AGENT_TAGS
if platform == 'arm.metal' and AARCH64_AGENT_TAGS:
if platform == "arm.metal" and AARCH64_AGENT_TAGS:
env_var = AARCH64_AGENT_TAGS
target = self.step_config['agents']
target = self.step_config["agents"]
self._env_change_config(test_name, env_var, target, override=True)
def _env_add_docker_config(self, test_name):
@@ -205,7 +203,7 @@ class BuildkiteStep:
`DOCKER_PLUGIN_CONFIG` environment variable.
"""
target = self.step_config['plugins'][0][f"docker#{DOCKER_PLUGIN_VERSION}"]
target = self.step_config["plugins"][0][f"docker#{DOCKER_PLUGIN_VERSION}"]
self._env_change_config(test_name, DOCKER_PLUGIN_CONFIG, target)
def _env_override_timeout(self, test_name):
@@ -221,28 +219,25 @@ class BuildkiteStep:
Further configuration from environment variables may be added.
"""
test_name = input.get('test_name')
command = input.get('command')
platform = input.get('platform')
hypervisor = input.get('hypervisor')
docker = input.get('docker_plugin')
conditional = input.get('conditional')
timeout = input.get('timeout_in_minutes')
queue = input.get('queue')
test_name = input.get("test_name")
command = input.get("command")
platform = input.get("platform")
hypervisor = input.get("hypervisor")
docker = input.get("docker_plugin")
conditional = input.get("conditional")
timeout = input.get("timeout_in_minutes")
queue = input.get("queue")
# Mandatory keys.
assert test_name, "Step is missing test name."
platform_string = f"-{platform}" if platform else ""
self.step_config['label'] = f"{test_name}{platform_string}"
self.step_config["label"] = f"{test_name}{platform_string}"
assert command, "Step is missing command."
if "{target_platform}" in command:
assert platform, \
"Command requires platform, but platform is missing."
command = command.replace(
"{target_platform}", platform
)
self.step_config['command'] = command
assert platform, "Command requires platform, but platform is missing."
command = command.replace("{target_platform}", platform)
self.step_config["command"] = command
# Optional keys.
self._set_platform(platform)
@@ -262,9 +257,19 @@ class BuildkiteStep:
# forwarding the key, values without any change.
# We need to filter for keys that have special meaning and which we
# don't want to re-add.
special_keys = ['conditional', 'docker_plugin', 'platform', 'test_name', 'queue', 'hypervisor']
additional_keys = {k: v for k, v in input.items() if not (k in self.step_config) and
not(k in special_keys)}
special_keys = [
"conditional",
"docker_plugin",
"platform",
"test_name",
"queue",
"hypervisor",
]
additional_keys = {
k: v
for k, v in input.items()
if not (k in self.step_config) and not (k in special_keys)
}
if additional_keys:
self.step_config.update(additional_keys)
@@ -283,15 +288,15 @@ class BuildkiteConfig:
self.bk_config = None
def build(self, input):
""" Build the final Buildkite configuration fron the json input. """
"""Build the final Buildkite configuration fron the json input."""
self.bk_config = {'steps': []}
tests = input.get('tests')
self.bk_config = {"steps": []}
tests = input.get("tests")
assert tests, "Input is missing list of tests."
for test in tests:
platforms = test.get('platform')
test_name = test.get('test_name')
platforms = test.get("platform")
test_name = test.get("test_name")
if TESTS_TO_SKIP:
tests_to_skip = json.loads(TESTS_TO_SKIP)
@@ -305,20 +310,20 @@ class BuildkiteConfig:
for platform in platforms:
step_input = copy.deepcopy(test)
step_input['platform'] = platform
if not step_input.get('hypervisor'):
step_input['hypervisor'] = DEFAULT_AGENT_TAG_HYPERVISOR
step_input["platform"] = platform
if not step_input.get("hypervisor"):
step_input["hypervisor"] = DEFAULT_AGENT_TAG_HYPERVISOR
step = BuildkiteStep()
step_output = step.build(step_input)
self.bk_config['steps'].append(step_output)
self.bk_config["steps"].append(step_output)
# Return the object's attributes and their values as a dictionary.
return self.bk_config
def generate_pipeline(config_file):
""" Generate the pipeline yaml file from a json configuration file. """
"""Generate the pipeline yaml file from a json configuration file."""
with open(config_file) as json_file:
json_cfg = json.load(json_file)
@@ -329,7 +334,7 @@ def generate_pipeline(config_file):
yaml.dump(output, sys.stdout, sort_keys=False)
if __name__ == '__main__':
if __name__ == "__main__":
help_text = dedent(
"""
This script supports overriding the following configurations through
@@ -345,17 +350,18 @@ if __name__ == '__main__':
- TIMEOUTS_MIN: overrides the timeout value for specific tests.
"""
)
parser = ArgumentParser(description=help_text,
formatter_class=RawTextHelpFormatter)
parser = ArgumentParser(description=help_text, formatter_class=RawTextHelpFormatter)
# By default we're generating the rust-vmm-ci pipeline with the test
# configuration committed to this repository.
# This parameter is useful for generating the pipeline for repositories
# that have custom pipelines, and it helps with keeping the container
# version the same across pipelines.
parser.add_argument('-t', '--test-description',
metavar="JSON_FILE",
help='The path to the JSON file containing the test'
' description for the CI.',
default=f'{PARENT_DIR}/test_description.json')
parser.add_argument(
"-t",
"--test-description",
metavar="JSON_FILE",
help="The path to the JSON file containing the test" " description for the CI.",
default=f"{PARENT_DIR}/test_description.json",
)
args = parser.parse_args()
generate_pipeline(args.test_description)

14
.github/workflows/black.yaml vendored Normal file
View File

@@ -0,0 +1,14 @@
on:
push:
jobs:
black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install black
run: pip install black
- name: Run black
run: black . --check

View File

@@ -15,28 +15,22 @@ def pytest_addoption(parser):
"--profile",
default=PROFILE_CI,
choices=[PROFILE_CI, PROFILE_DEVEL],
help="Profile for running the test: {} or {}".format(
PROFILE_CI,
PROFILE_DEVEL
)
help="Profile for running the test: {} or {}".format(PROFILE_CI, PROFILE_DEVEL),
)
parser.addoption(
"--no-cleanup",
action="store_true",
default=False,
help="Keep the coverage report in `kcov_output` directory. If this "
"flag is not provided, both coverage related directories are "
"removed."
"flag is not provided, both coverage related directories are "
"removed.",
)
parser.addoption(
"--test-scope",
default=WORKSPACE,
choices=[WORKSPACE, CRATE],
help="Defines the scope of running tests: {} or {}".format(
WORKSPACE,
CRATE
)
help="Defines the scope of running tests: {} or {}".format(WORKSPACE, CRATE),
)

View File

@@ -14,14 +14,12 @@ import subprocess
from utils import get_repo_root_path
REMOTE = \
os.environ.get('BUILDKITE_REPO') or \
os.environ.get('REMOTE') or \
"origin"
BASE_BRANCH = \
os.environ.get('BUILDKITE_PULL_REQUEST_BASE_BRANCH') or \
os.environ.get('BASE_BRANCH') or \
"main"
REMOTE = os.environ.get("BUILDKITE_REPO") or os.environ.get("REMOTE") or "origin"
BASE_BRANCH = (
os.environ.get("BUILDKITE_PULL_REQUEST_BASE_BRANCH")
or os.environ.get("BASE_BRANCH")
or "main"
)
# File used for saving the results of cargo bench
# when running on the PR branch.
PR_BENCH_RESULTS_FILE = "pr_bench_results"
@@ -67,12 +65,9 @@ def test_bench():
# This is a bit of a &*%^ way of checking if the benchmark does not
# exist. Hopefully it will be possible to check it in another way
# ...soon
print(
"There are no benchmarks in main. No comparison can happen."
)
print("There are no benchmarks in main. No comparison can happen.")
else:
assert return_code == 0, "stdout: {}\n stderr: {}".format(
stdout, stderr)
assert return_code == 0, "stdout: {}\n stderr: {}".format(stdout, stderr)
def _run_cargo_bench(baseline):
@@ -80,43 +75,44 @@ def _run_cargo_bench(baseline):
process = subprocess.run(
"cargo bench --bench main --all-features -- --noplot "
"--save-baseline {}".format(baseline),
shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
)
return process.returncode, process.stdout.decode('utf-8'),\
process.stderr.decode('utf-8')
return (
process.returncode,
process.stdout.decode("utf-8"),
process.stderr.decode("utf-8"),
)
def _run_critcmp():
p = subprocess.run(
"critcmp {} {}".format(
UPSTREAM_BENCH_RESULTS_FILE, PR_BENCH_RESULTS_FILE
),
shell=True, check=True,
"critcmp {} {}".format(UPSTREAM_BENCH_RESULTS_FILE, PR_BENCH_RESULTS_FILE),
shell=True,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
stderr=subprocess.PIPE,
)
print(p.stdout.decode('utf-8'))
print('ERRORS')
print(p.stderr.decode('utf-8'))
print(p.stdout.decode("utf-8"))
print("ERRORS")
print(p.stderr.decode("utf-8"))
def _git_checkout_upstream_branch():
subprocess.run(
"git fetch {} {}".format(REMOTE, BASE_BRANCH),
shell=True, check=True
)
subprocess.run(
"git checkout FETCH_HEAD",
shell=True, check=True
"git fetch {} {}".format(REMOTE, BASE_BRANCH), shell=True, check=True
)
subprocess.run("git checkout FETCH_HEAD", shell=True, check=True)
def _git_checkout_pr_branch():
subprocess.run(
"git checkout -",
shell=True, check=True,
shell=True,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
stderr=subprocess.PIPE,
)

View File

@@ -16,14 +16,12 @@ from utils import get_cmd_output
COMMIT_TITLE_MAX_LEN = 60
COMMIT_BODY_LINE_MAX_LEN = 75
REMOTE = \
os.environ.get('BUILDKITE_REPO') or \
os.environ.get('REMOTE') or \
"origin"
BASE_BRANCH = \
os.environ.get('BUILDKITE_PULL_REQUEST_BASE_BRANCH') or \
os.environ.get('BASE_BRANCH') or \
"main"
REMOTE = os.environ.get("BUILDKITE_REPO") or os.environ.get("REMOTE") or "origin"
BASE_BRANCH = (
os.environ.get("BUILDKITE_PULL_REQUEST_BASE_BRANCH")
or os.environ.get("BASE_BRANCH")
or "main"
)
def test_commit_format():
@@ -50,8 +48,7 @@ def test_commit_format():
) from None
# Get hashes of PR's commits in their abbreviated form for
# a prettier printing.
shas_cmd = "git log --no-merges --pretty=%h --no-decorate " \
"FETCH_HEAD..HEAD"
shas_cmd = "git log --no-merges --pretty=%h --no-decorate " "FETCH_HEAD..HEAD"
shas = get_cmd_output(shas_cmd)
for sha in shas.split():
@@ -63,18 +60,20 @@ def test_commit_format():
message_cmd = "git show --pretty=format:%B -s " + sha
message = get_cmd_output(message_cmd)
message_lines = message.split("\n")
assert len(message_lines) >= 3,\
"The commit '{}' should contain at least 3 lines: title, " \
"blank line and a sign-off one." \
.format(sha)
assert len(message_lines) >= 3, (
"The commit '{}' should contain at least 3 lines: title, "
"blank line and a sign-off one.".format(sha)
)
title = message_lines[0]
assert message_lines[1] == "",\
"For commit '{}', title is divided into multiple lines. " \
"Please keep it one line long and make sure you add a blank " \
assert message_lines[1] == "", (
"For commit '{}', title is divided into multiple lines. "
"Please keep it one line long and make sure you add a blank "
"line between title and description.".format(sha)
assert len(title) <= COMMIT_TITLE_MAX_LEN,\
"For commit '{}', title exceeds {} chars. " \
)
assert len(title) <= COMMIT_TITLE_MAX_LEN, (
"For commit '{}', title exceeds {} chars. "
"Please keep it shorter.".format(sha, COMMIT_TITLE_MAX_LEN)
)
found_signed_off = False
@@ -85,11 +84,13 @@ def test_commit_format():
# the commit message ended and we don't want to check
# line lengths anymore for the current commit.
break
assert len(line) <= COMMIT_BODY_LINE_MAX_LEN,\
"For commit '{}', message line '{}' exceeds {} chars. " \
"Please keep it shorter or split it in " \
"multiple lines.".format(sha, line,
COMMIT_BODY_LINE_MAX_LEN)
assert found_signed_off, "Commit '{}' is not signed. " \
"Please run 'git commit -s --amend' " \
"on it.".format(sha)
assert len(line) <= COMMIT_BODY_LINE_MAX_LEN, (
"For commit '{}', message line '{}' exceeds {} chars. "
"Please keep it shorter or split it in "
"multiple lines.".format(sha, line, COMMIT_BODY_LINE_MAX_LEN)
)
assert found_signed_off, (
"Commit '{}' is not signed. "
"Please run 'git commit -s --amend' "
"on it.".format(sha)
)

View File

@@ -51,15 +51,16 @@ def _read_test_config():
assert "exclude_path" in coverage_config
assert "crate_features" in coverage_config
assert ' ' not in coverage_config["crate_features"], \
"spaces are not allowed in crate_features value"
assert (
" " not in coverage_config["crate_features"]
), "spaces are not allowed in crate_features value"
return coverage_config
def _write_coverage_config(coverage_config):
"""Updates the coverage config file as per `coverage_config`"""
with open(COVERAGE_CONFIG_PATH, 'w') as outfile:
with open(COVERAGE_CONFIG_PATH, "w") as outfile:
json.dump(coverage_config, outfile)
@@ -78,48 +79,43 @@ def _get_current_coverage(coverage_config, no_cleanup, test_scope):
shutil.rmtree(kcov_output_dir, ignore_errors=True)
shutil.rmtree(kcov_build_dir, ignore_errors=True)
exclude_pattern = (
'${CARGO_HOME:-$HOME/.cargo/},'
'usr/lib/,'
'lib/'
)
exclude_pattern = "${CARGO_HOME:-$HOME/.cargo/}," "usr/lib/," "lib/"
exclude_region = "'mod tests {'"
additional_exclude_path = coverage_config["exclude_path"]
if additional_exclude_path:
exclude_pattern += ',' + additional_exclude_path
exclude_pattern += "," + additional_exclude_path
additional_kcov_param = ''
additional_kcov_param = ""
if test_scope == pytest.workspace:
additional_kcov_param += '--all '
additional_kcov_param += "--all "
crate_features = coverage_config["crate_features"]
if crate_features:
additional_kcov_param += '--features=' + crate_features
additional_kcov_param += "--features=" + crate_features
kcov_cmd = "CARGO_TARGET_DIR={} cargo kcov {} " \
"--output {} -- " \
"--exclude-region={} " \
"--exclude-pattern={} " \
"--verify".format(
kcov_build_dir,
additional_kcov_param,
kcov_output_dir,
exclude_region,
exclude_pattern
)
kcov_cmd = (
"CARGO_TARGET_DIR={} cargo kcov {} "
"--output {} -- "
"--exclude-region={} "
"--exclude-pattern={} "
"--verify".format(
kcov_build_dir,
additional_kcov_param,
kcov_output_dir,
exclude_region,
exclude_pattern,
)
)
# Pytest closes stdin by default, but some tests might need it to be open.
# In the future, should the need arise, we can feed custom data to stdin.
subprocess.run(kcov_cmd, shell=True, check=True, input=b'')
subprocess.run(kcov_cmd, shell=True, check=True, input=b"")
# Read the coverage reported by kcov.
coverage_file = os.path.join(kcov_output_dir, 'index.js')
coverage_file = os.path.join(kcov_output_dir, "index.js")
with open(coverage_file) as cov_output:
coverage = float(re.findall(
r'"covered":"(\d+\.\d)"',
cov_output.read()
)[0])
coverage = float(re.findall(r'"covered":"(\d+\.\d)"', cov_output.read())[0])
# Remove coverage related directories.
# If user provided `--no-cleanup` flag, `kcov_output_dir`
@@ -140,14 +136,12 @@ def test_coverage(profile, no_cleanup, test_scope):
if previous_coverage < current_coverage:
if profile == pytest.profile_ci:
# In the CI Profile we expect the coverage to be manually updated.
assert False, \
"Coverage is increased from {} to {}. " \
"Please update the coverage in coverage_config_{}.json."\
.format(
previous_coverage,
current_coverage,
platform.machine()
assert False, (
"Coverage is increased from {} to {}. "
"Please update the coverage in coverage_config_{}.json.".format(
previous_coverage, current_coverage, platform.machine()
)
)
elif profile == pytest.profile_devel:
coverage_config["coverage_score"] = current_coverage
_write_coverage_config(coverage_config)
@@ -157,5 +151,7 @@ def test_coverage(profile, no_cleanup, test_scope):
# `pytest_addoption`.
assert False, "Invalid test profile."
elif previous_coverage > current_coverage:
assert False, "Coverage drops by {:.2f}%. Please add unit tests for " \
"the uncovered lines.".format(diff)
assert False, (
"Coverage drops by {:.2f}%. Please add unit tests for "
"the uncovered lines.".format(diff)
)

View File

@@ -15,7 +15,6 @@ def get_repo_root_path():
def get_cmd_output(cmd):
"""Returns stdout content of `cmd` command."""
cmd_out = subprocess.run(cmd, shell=True, check=True,
stdout=subprocess.PIPE)
stdout = cmd_out.stdout.decode('utf-8')
cmd_out = subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE)
stdout = cmd_out.stdout.decode("utf-8")
return stdout

View File

@@ -21,19 +21,18 @@ class TestsContainer(unittest.TestCase):
def make_test_function(command):
def test(self):
subprocess.run(command, shell=True, check=True)
return test
def retrieve_test_list(
config_file=f"{PARENT_DIR}/.buildkite/test_description.json"
):
def retrieve_test_list(config_file=f"{PARENT_DIR}/.buildkite/test_description.json"):
with open(config_file) as jsonFile:
test_list = json.load(jsonFile)
jsonFile.close()
return test_list
if __name__ == '__main__':
if __name__ == "__main__":
help_text = dedent(
"""
This script allows running all the tests at once on the local machine.
@@ -44,15 +43,14 @@ if __name__ == '__main__':
base branch, and these tests may not work as expected.
"""
)
parser = ArgumentParser(description=help_text,
formatter_class=RawTextHelpFormatter)
parser = ArgumentParser(description=help_text, formatter_class=RawTextHelpFormatter)
parser.parse_args()
test_config = retrieve_test_list()
for test in test_config['tests']:
command = test['command']
for test in test_config["tests"]:
command = test["command"]
command = command.replace("{target_platform}", platform.machine())
test_func = make_test_function(command)
setattr(TestsContainer, 'test_{}'.format(test['test_name']), test_func)
setattr(TestsContainer, "test_{}".format(test["test_name"]), test_func)
unittest.main(verbosity=2)