diff --git a/easybuild/tools/run.py b/easybuild/tools/run.py index ce4298a4cc..c972277b00 100644 --- a/easybuild/tools/run.py +++ b/easybuild/tools/run.py @@ -74,7 +74,7 @@ ] -RunResult = namedtuple('RunResult', ('output', 'exit_code', 'stderr')) +RunResult = namedtuple('RunResult', ('cmd', 'exit_code', 'output', 'stderr', 'work_dir')) def run_cmd_cache(func): @@ -142,9 +142,9 @@ def run(cmd, fail_on_error=True, split_stderr=False, stdin=None, env=None, raise NotImplementedError if isinstance(cmd, str): - cmd_msg = cmd.strip() + cmd_str = cmd.strip() elif isinstance(cmd, list): - cmd_msg = ' '.join(cmd) + cmd_str = ' '.join(cmd) else: raise EasyBuildError(f"Unknown command type ('{type(cmd)}'): {cmd}") @@ -164,15 +164,15 @@ def run(cmd, fail_on_error=True, split_stderr=False, stdin=None, env=None, if not in_dry_run and build_option('extended_dry_run'): if not hidden: silent = build_option('silent') - msg = f" running command \"{cmd_msg}\"\n" + msg = f" running command \"{cmd_str}\"\n" msg += f" (in {work_dir})" dry_run_msg(msg, silent=silent) - return RunResult(output='', exit_code=0, stderr=None) + return RunResult(cmd=cmd_str, exit_code=0, output='', stderr=None, work_dir=work_dir) start_time = datetime.now() if not hidden: - cmd_trace_msg(cmd_msg, start_time, work_dir, stdin, cmd_out_fp) + cmd_trace_msg(cmd_str, start_time, work_dir, stdin, cmd_out_fp) if stdin: # 'input' value fed to subprocess.run must be a byte sequence @@ -185,7 +185,7 @@ def run(cmd, fail_on_error=True, split_stderr=False, stdin=None, env=None, stderr = subprocess.PIPE if split_stderr else subprocess.STDOUT - _log.info(f"Running command '{cmd_msg}' in {work_dir}") + _log.info(f"Running command '{cmd_str}' in {work_dir}") proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=stderr, check=fail_on_error, env=env, input=stdin, shell=shell, executable=executable) @@ -193,13 +193,13 @@ def run(cmd, fail_on_error=True, split_stderr=False, stdin=None, env=None, output = proc.stdout.decode('utf-8', 'ignore') stderr_output = proc.stderr.decode('utf-8', 'ignore') if split_stderr else None - res = RunResult(output=output, exit_code=proc.returncode, stderr=stderr_output) + res = RunResult(cmd=cmd_str, exit_code=proc.returncode, output=output, stderr=stderr_output, work_dir=work_dir) if split_stderr: - log_msg = f"Command '{cmd_msg}' exited with exit code {res.exit_code}, " + log_msg = f"Command '{cmd_str}' exited with exit code {res.exit_code}, " log_msg += f"with stdout:\n{res.output}\nstderr:\n{res.stderr}" else: - log_msg = f"Command '{cmd_msg}' exited with exit code {res.exit_code} and output:\n{res.output}" + log_msg = f"Command '{cmd_str}' exited with exit code {res.exit_code} and output:\n{res.output}" _log.info(log_msg) if not hidden: diff --git a/test/framework/run.py b/test/framework/run.py index 00b3ca778a..4cd6821569 100644 --- a/test/framework/run.py +++ b/test/framework/run.py @@ -166,8 +166,11 @@ def test_run_basic(self): res = run("echo hello") self.assertEqual(res.output, "hello\n") # no reason echo hello could fail + self.assertEqual(res.cmd, "echo hello") self.assertEqual(res.exit_code, 0) - self.assertEqual(type(res.output), str) + self.assertTrue(isinstance(res.output, str)) + self.assertEqual(res.stderr, None) + self.assertTrue(res.work_dir and isinstance(res.work_dir, str)) # test running command that emits non-UTF-8 characters # this is constructed to reproduce errors like: @@ -181,9 +184,11 @@ def test_run_basic(self): with self.mocked_stdout_stderr(): res = run(cmd) + self.assertEqual(res.cmd, cmd) self.assertEqual(res.exit_code, 0) self.assertTrue(res.output.startswith('foo ') and res.output.endswith(' bar')) - self.assertEqual(type(res.output), str) + self.assertTrue(isinstance(res.output, str)) + self.assertTrue(res.work_dir and isinstance(res.work_dir, str)) def test_run_cmd_log(self): """Test logging of executed commands.""" @@ -761,37 +766,47 @@ def test_run_cmd_cache(self): def test_run_cache(self): """Test caching for run""" + + cmd = "ulimit -u" with self.mocked_stdout_stderr(): - res = run("ulimit -u") + res = run(cmd) first_out = res.output self.assertEqual(res.exit_code, 0) + with self.mocked_stdout_stderr(): - res = run("ulimit -u") + res = run(cmd) cached_out = res.output self.assertEqual(res.exit_code, 0) self.assertEqual(first_out, cached_out) # inject value into cache to check whether executing command again really returns cached value with self.mocked_stdout_stderr(): - cached_res = RunResult(output="123456", exit_code=123, stderr=None) - run.update_cache({("ulimit -u", None): cached_res}) - res = run("ulimit -u") + cached_res = RunResult(cmd=cmd, output="123456", exit_code=123, stderr=None, work_dir='/test_ulimit') + run.update_cache({(cmd, None): cached_res}) + res = run(cmd) + self.assertEqual(res.cmd, cmd) self.assertEqual(res.exit_code, 123) self.assertEqual(res.output, "123456") + self.assertEqual(res.stderr, None) + self.assertEqual(res.work_dir, '/test_ulimit') # also test with command that uses stdin + cmd = "cat" with self.mocked_stdout_stderr(): - res = run("cat", stdin='foo') + res = run(cmd, stdin='foo') self.assertEqual(res.exit_code, 0) self.assertEqual(res.output, 'foo') # inject different output for cat with 'foo' as stdin to check whether cached value is used with self.mocked_stdout_stderr(): - cached_res = RunResult(output="bar", exit_code=123, stderr=None) - run.update_cache({('cat', 'foo'): cached_res}) - res = run("cat", stdin='foo') + cached_res = RunResult(cmd=cmd, output="bar", exit_code=123, stderr=None, work_dir='/test_cat') + run.update_cache({(cmd, 'foo'): cached_res}) + res = run(cmd, stdin='foo') + self.assertEqual(res.cmd, cmd) self.assertEqual(res.exit_code, 123) self.assertEqual(res.output, 'bar') + self.assertEqual(res.stderr, None) + self.assertEqual(res.work_dir, '/test_cat') run.clear_cache() diff --git a/test/framework/systemtools.py b/test/framework/systemtools.py index cb8c5a740d..82f1315dc5 100644 --- a/test/framework/systemtools.py +++ b/test/framework/systemtools.py @@ -340,7 +340,7 @@ def mocked_run(cmd, **kwargs): "ulimit -u": '40', } if cmd in known_cmds: - return RunResult(output=known_cmds[cmd], exit_code=0, stderr=None) + return RunResult(cmd=cmd, exit_code=0, output=known_cmds[cmd], stderr=None, work_dir=os.getcwd()) else: return run(cmd, **kwargs) @@ -774,7 +774,8 @@ def test_gcc_version_darwin(self): """Test getting gcc version (mocked for Darwin).""" st.get_os_type = lambda: st.DARWIN out = "Apple LLVM version 7.0.0 (clang-700.1.76)" - st.run = lambda *args, **kwargs: RunResult(output=out, exit_code=0, stderr=None) + mocked_run_res = RunResult(cmd="gcc --version", exit_code=0, output=out, stderr=None, work_dir=os.getcwd()) + st.run = lambda *args, **kwargs: mocked_run_res self.assertEqual(get_gcc_version(), None) def test_glibc_version_native(self):