From 86b9c3e4e48ba47ef28781d06b97846aca74bc8e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 21 Oct 2021 21:08:46 -0600 Subject: env: Allow U-Boot scripts to be placed in a .env file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At present U-Boot environment variables, and thus scripts, are defined by CONFIG_EXTRA_ENV_SETTINGS. It is painful to add large amounts of text to this file and dealing with quoting and newlines is harder than it should be. It would be better if we could just type the script into a text file and have it included by U-Boot. Add a feature that brings in a .env file associated with the board config, if present. To use it, create a file in a board/ directory, typically called .env and controlled by the CONFIG_ENV_SOURCE_FILE option. The environment variables should be of the form "var=value". Values can extend to multiple lines. See the README under 'Environment Variables:' for more information and an example. In many cases environment variables need access to the U-Boot CONFIG variables to select different options. Enable this so that the environment scripts can be as useful as the ones currently in the board config files. This uses the C preprocessor, means that comments can be included in the environment using /* ... */ Also support += to allow variables to be appended to. This is needed when using the preprocessor. Signed-off-by: Simon Glass Reviewed-by: Marek BehĂșn Tested-by: Marek BehĂșn --- test/py/tests/test_env.py | 107 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) (limited to 'test/py') diff --git a/test/py/tests/test_env.py b/test/py/tests/test_env.py index 9bed2f48d77..f85cb031382 100644 --- a/test/py/tests/test_env.py +++ b/test/py/tests/test_env.py @@ -7,6 +7,7 @@ import os import os.path from subprocess import call, check_call, CalledProcessError +import tempfile import pytest import u_boot_utils @@ -515,3 +516,109 @@ def test_env_ext4(state_test_env): finally: if fs_img: call('rm -f %s' % fs_img, shell=True) + +def test_env_text(u_boot_console): + """Test the script that converts the environment to a text file""" + + def check_script(intext, expect_val): + """Check a test case + + Args: + intext: Text to pass to the script + expect_val: Expected value of the CONFIG_EXTRA_ENV_TEXT string, or + None if we expect it not to be defined + """ + with tempfile.TemporaryDirectory() as path: + fname = os.path.join(path, 'infile') + with open(fname, 'w') as inf: + print(intext, file=inf) + result = u_boot_utils.run_and_log(cons, ['awk', '-f', script, fname]) + if expect_val is not None: + expect = '#define CONFIG_EXTRA_ENV_TEXT "%s"\n' % expect_val + assert result == expect + else: + assert result == '' + + cons = u_boot_console + script = os.path.join(cons.config.source_dir, 'scripts', 'env2string.awk') + + # simple script with a single var + check_script('fred=123', 'fred=123\\0') + + # no vars + check_script('', None) + + # two vars + check_script('''fred=123 +ernie=456''', 'fred=123\\0ernie=456\\0') + + # blank lines + check_script('''fred=123 + + +ernie=456 + +''', 'fred=123\\0ernie=456\\0') + + # append + check_script('''fred=123 +ernie=456 +fred+= 456''', 'fred=123 456\\0ernie=456\\0') + + # append from empty + check_script('''fred= +ernie=456 +fred+= 456''', 'fred= 456\\0ernie=456\\0') + + # variable with + in it + check_script('fred+ernie=123', 'fred+ernie=123\\0') + + # ignores variables that are empty + check_script('''fred= +fred+= +ernie=456''', 'ernie=456\\0') + + # single-character env name + check_script('''f=123 +e=456 +f+= 456''', 'e=456\\0f=123 456\\0') + + # contains quotes + check_script('''fred="my var" +ernie=another"''', 'fred=\\"my var\\"\\0ernie=another\\"\\0') + + # variable name ending in + + check_script('''fred\\+=my var +fred++= again''', 'fred+=my var again\\0') + + # variable name containing + + check_script('''fred+jane=both +fred+jane+=again +ernie=456''', 'fred+jane=bothagain\\0ernie=456\\0') + + # multi-line vars - new vars always start at column 1 + check_script('''fred=first + second +\tthird with tab + + after blank + confusing=oops +ernie=another"''', 'fred=first second third with tab after blank confusing=oops\\0ernie=another\\"\\0') + + # real-world example + check_script('''ubifs_boot= + env exists bootubipart || + env set bootubipart UBI; + env exists bootubivol || + env set bootubivol boot; + if ubi part ${bootubipart} && + ubifsmount ubi${devnum}:${bootubivol}; + then + devtype=ubi; + run scan_dev_for_boot; + fi +''', + 'ubifs_boot=env exists bootubipart || env set bootubipart UBI; ' + 'env exists bootubivol || env set bootubivol boot; ' + 'if ubi part ${bootubipart} && ubifsmount ubi${devnum}:${bootubivol}; ' + 'then devtype=ubi; run scan_dev_for_boot; fi\\0') -- cgit v1.2.3 From 15156c95e9710447cc66f4b18009220bc4098d4e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sat, 23 Oct 2021 17:25:57 -0600 Subject: test/py: Allow passing input to a program When running a program on the host, allow input to be passed in as stdin. This is needed for running sfdisk, for example. Signed-off-by: Simon Glass --- test/py/multiplexed_log.py | 8 +++++--- test/py/u_boot_utils.py | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'test/py') diff --git a/test/py/multiplexed_log.py b/test/py/multiplexed_log.py index 545a7743022..9325fae46d2 100644 --- a/test/py/multiplexed_log.py +++ b/test/py/multiplexed_log.py @@ -109,7 +109,7 @@ class RunAndLog(object): """Clean up any resources managed by this object.""" pass - def run(self, cmd, cwd=None, ignore_errors=False): + def run(self, cmd, cwd=None, ignore_errors=False, stdin=None): """Run a command as a sub-process, and log the results. The output is available at self.output which can be useful if there is @@ -123,6 +123,7 @@ class RunAndLog(object): function will simply return if the command cannot be executed or exits with an error code, otherwise an exception will be raised if such problems occur. + stdin: Input string to pass to the command as stdin (or None) Returns: The output as a string. @@ -135,8 +136,9 @@ class RunAndLog(object): try: p = subprocess.Popen(cmd, cwd=cwd, - stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - (stdout, stderr) = p.communicate() + stdin=subprocess.PIPE if stdin else None, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + (stdout, stderr) = p.communicate(input=stdin) if stdout is not None: stdout = stdout.decode('utf-8') if stderr is not None: diff --git a/test/py/u_boot_utils.py b/test/py/u_boot_utils.py index e816c7fbb6a..f44442e0c77 100644 --- a/test/py/u_boot_utils.py +++ b/test/py/u_boot_utils.py @@ -154,7 +154,7 @@ def wait_until_file_open_fails(fn, ignore_errors): return raise Exception('File can still be opened') -def run_and_log(u_boot_console, cmd, ignore_errors=False): +def run_and_log(u_boot_console, cmd, ignore_errors=False, stdin=None): """Run a command and log its output. Args: @@ -166,6 +166,7 @@ def run_and_log(u_boot_console, cmd, ignore_errors=False): will simply return if the command cannot be executed or exits with an error code, otherwise an exception will be raised if such problems occur. + stdin: Input string to pass to the command as stdin (or None) Returns: The output as a string. @@ -173,7 +174,7 @@ def run_and_log(u_boot_console, cmd, ignore_errors=False): if isinstance(cmd, str): cmd = cmd.split() runner = u_boot_console.log.get_runner(cmd[0], sys.stdout) - output = runner.run(cmd, ignore_errors=ignore_errors) + output = runner.run(cmd, ignore_errors=ignore_errors, stdin=stdin) runner.close() return output -- cgit v1.2.3 From 689d0a1cb04cfc4bf9588660d03084c324bc10be Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sat, 23 Oct 2021 17:26:11 -0600 Subject: test/py: Relax the naming rules for unit tests At present the collection function used by pytest is quite strict on the naming of the functions it detects. In particular it requires the name of the test to be repeated in the function name. This is not enforced anywhere else, but instead the tests are silently omitted from the pytest run. This affects a few dozen tests. The rule does not seem to have any particular purpose. Relax it, so that all tests that use the UNIT_TEST() macro will run, regardless of the name of the test function. Signed-off-by: Simon Glass --- test/py/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/py') diff --git a/test/py/conftest.py b/test/py/conftest.py index 11a3f307ea8..16e445cd8ee 100644 --- a/test/py/conftest.py +++ b/test/py/conftest.py @@ -226,7 +226,7 @@ def pytest_configure(config): import u_boot_console_exec_attach console = u_boot_console_exec_attach.ConsoleExecAttach(log, ubconfig) -re_ut_test_list = re.compile(r'[^a-zA-Z0-9_]_u_boot_list_2_ut_(.*)_test_2_\1_test_(.*)\s*$') +re_ut_test_list = re.compile(r'[^a-zA-Z0-9_]_u_boot_list_2_ut_(.*)_test_2_(.*)\s*$') def generate_ut_subtest(metafunc, fixture_name, sym_path): """Provide parametrization for a ut_subtest fixture. -- cgit v1.2.3 From 452e8c9086a9f95739582da5ccc2130e4bf1ae8b Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sat, 23 Oct 2021 17:26:12 -0600 Subject: test/py: Raise a ValueError if a command fails At present an Exception is raised if a command fails. This is a very broad class and makes it difficult for callers to catch the error without also catching other things, like programming bugs. Change it to ValueError to make this easier. Signed-off-by: Simon Glass --- test/py/multiplexed_log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/py') diff --git a/test/py/multiplexed_log.py b/test/py/multiplexed_log.py index 9325fae46d2..6688207fb31 100644 --- a/test/py/multiplexed_log.py +++ b/test/py/multiplexed_log.py @@ -165,7 +165,7 @@ class RunAndLog(object): if output and not output.endswith('\n'): output += '\n' if exit_status and not exception and not ignore_errors: - exception = Exception('Exit code: ' + str(exit_status)) + exception = ValueError('Exit code: ' + str(exit_status)) if exception: output += str(exception) + '\n' self.logfile.write(self, output) -- cgit v1.2.3