summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--zephyr/zmake/tests/test_build_config.py184
2 files changed, 185 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 167dec2ca1..8e6fefd841 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ cscope.*
*.pyc
__pycache__
*.egg-info
+.hypothesis/
diff --git a/zephyr/zmake/tests/test_build_config.py b/zephyr/zmake/tests/test_build_config.py
new file mode 100644
index 0000000000..6b835fdca4
--- /dev/null
+++ b/zephyr/zmake/tests/test_build_config.py
@@ -0,0 +1,184 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import hypothesis
+import hypothesis.strategies as st
+import os
+import pathlib
+import pytest
+import string
+import tempfile
+
+import zmake.jobserver
+import zmake.util as util
+from zmake.build_config import BuildConfig
+
+# Strategies for use with hypothesis
+filenames = st.text(
+ alphabet=set(string.printable) - {'/', ';'},
+ min_size=1,
+ max_size=254)
+paths = st.builds(lambda parts: pathlib.Path('/', *parts),
+ st.iterables(filenames, min_size=1))
+config_keys = st.text(alphabet=set(string.ascii_uppercase) | {'_'}, min_size=1)
+config_values = st.builds(str, st.just('y') | st.just('n') | st.integers())
+config_dicts = st.dictionaries(keys=config_keys, values=config_values)
+config_dicts_at_least_one_entry = st.dictionaries(keys=config_keys,
+ values=config_values,
+ min_size=1)
+
+build_configs = st.builds(BuildConfig,
+ environ_defs=config_dicts,
+ cmake_defs=config_dicts,
+ kconfig_defs=config_dicts,
+ kconfig_files=st.lists(paths))
+build_configs_no_kconfig = st.builds(BuildConfig,
+ environ_defs=config_dicts,
+ cmake_defs=config_dicts)
+build_configs_with_at_least_one_kconfig = st.builds(
+ BuildConfig,
+ environ_defs=config_dicts,
+ cmake_defs=config_dicts,
+ kconfig_defs=config_dicts_at_least_one_entry)
+
+
+@hypothesis.given(st.data(), build_configs)
+def test_merge(coins, combined):
+ """Test that when splitting a config in half and merging the two
+ halves, we get the original config back.
+ """
+ def split(iterable):
+ left = []
+ right = []
+ bools = st.booleans()
+ for item in iterable:
+ if coins.draw(bools):
+ left.append(item)
+ else:
+ right.append(item)
+ return left, right
+
+ # Split the original config into two
+ env1, env2 = split(combined.environ_defs.items())
+ cmake1, cmake2 = split(combined.cmake_defs.items())
+ kconf1, kconf2 = split(combined.kconfig_defs.items())
+ files1, files2 = split(combined.kconfig_files)
+
+ c1 = BuildConfig(environ_defs=dict(env1), cmake_defs=dict(cmake1),
+ kconfig_defs=dict(kconf1), kconfig_files=files1)
+ c2 = BuildConfig(environ_defs=dict(env2), cmake_defs=dict(cmake2),
+ kconfig_defs=dict(kconf2), kconfig_files=files2)
+
+ # Merge the split configs
+ merged = c1 | c2
+
+ # Assert that the merged split configs is the original config
+ assert merged.environ_defs == combined.environ_defs
+ assert merged.cmake_defs == combined.cmake_defs
+ assert merged.kconfig_defs == combined.kconfig_defs
+ assert set(merged.kconfig_files) == set(combined.kconfig_files)
+
+
+class FakeJobClient(zmake.jobserver.JobClient):
+ """Simple job client to capture argv/environ."""
+ def __init__(self):
+ self.captured_argv = []
+ self.captured_env = {}
+
+ def get_job(self):
+ return zmake.jobserver.JobHandle(lambda: None)
+
+ def popen(self, argv, env={}, **kwargs):
+ self.captured_argv = [str(arg) for arg in argv]
+ self.captured_env = {str(k): str(v) for k, v in env.items()}
+
+
+def parse_cmake_args(argv):
+ """Parse command line arguments like cmake does.
+
+ This is an intenionally minimal implementation, which only
+ understands the subset of arguments actually used by zmake.
+
+ Args:
+ argv: The argument list.
+
+ Returns:
+ A 2-tuple of a namespace from argparse and the corresponding
+ parsed Cmake definitions.
+ """
+ assert argv[0] == '/usr/bin/cmake'
+
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument('-S', dest='source_dir', type=pathlib.Path)
+ parser.add_argument('-B', dest='build_dir', type=pathlib.Path)
+ parser.add_argument('-G', dest='generator')
+ parser.add_argument('-D', dest='defs', action='append', default=[])
+ args = parser.parse_args(argv[1:])
+
+ # Build the definition dictionary
+ cmake_defs = {}
+ for defn in args.defs:
+ key, sep, val = defn.partition('=')
+ if not sep:
+ val = '1'
+ assert key not in cmake_defs
+ cmake_defs[key] = val
+
+ return args, cmake_defs
+
+
+@hypothesis.given(build_configs_no_kconfig, paths, paths)
+def test_popen_cmake_no_kconfig(conf, project_dir, build_dir):
+ """Test popen_cmake for a config with no kconfig definitions."""
+ job_client = FakeJobClient()
+ conf.popen_cmake(job_client, project_dir, build_dir)
+
+ args, cmake_defs = parse_cmake_args(job_client.captured_argv)
+
+ assert cmake_defs == conf.cmake_defs
+ assert job_client.captured_env == conf.environ_defs
+
+
+@hypothesis.given(build_configs_with_at_least_one_kconfig, paths, paths)
+def test_popen_cmake_kconfig_but_no_file(conf, project_dir, build_dir):
+ """Test that running popen_cmake with Kconfig definitions to write
+ out, but no path to do so, should raise an error.
+ """
+ job_client = FakeJobClient()
+
+ with pytest.raises(ValueError):
+ conf.popen_cmake(job_client, project_dir, build_dir)
+
+
+@hypothesis.given(build_configs, paths, paths)
+def test_popen_cmake_kconfig(conf, project_dir, build_dir):
+ job_client = FakeJobClient()
+
+ with tempfile.NamedTemporaryFile('w', delete=False) as f:
+ temp_path = f.name
+
+ try:
+ conf.popen_cmake(job_client, project_dir, build_dir,
+ kconfig_path=pathlib.Path(temp_path))
+
+ args, cmake_defs = parse_cmake_args(job_client.captured_argv)
+
+ expected_kconfig_files = set(str(f) for f in conf.kconfig_files)
+ expected_kconfig_files.add(temp_path)
+
+ if expected_kconfig_files:
+ kconfig_files = set(cmake_defs.pop('CONF_FILE').split(';'))
+ else:
+ assert 'CONF_FILE' not in cmake_defs
+ kconfig_files = set()
+
+ assert cmake_defs == conf.cmake_defs
+ assert job_client.captured_env == conf.environ_defs
+ assert kconfig_files == expected_kconfig_files
+
+ kconfig_defs = util.read_kconfig_file(temp_path)
+ assert kconfig_defs == conf.kconfig_defs
+ finally:
+ os.unlink(temp_path)