Skip to content

Commit

Permalink
add support to run_shell_cmd for providing list of answers in qa_patt…
Browse files Browse the repository at this point in the history
…erns + fix output for hooks triggered for interactive shell commands run with run_shell_cmd
  • Loading branch information
boegel committed Mar 6, 2024
1 parent 23fa25c commit 5783910
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 2 deletions.
20 changes: 18 additions & 2 deletions easybuild/tools/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,11 @@ def to_cmd_str(cmd):

if with_hooks:
hooks = load_hooks(build_option('hooks'))
hook_res = run_hook(RUN_SHELL_CMD, hooks, pre_step_hook=True, args=[cmd], kwargs={'work_dir': work_dir})
kwargs = {
'interactive': bool(qa_patterns),
'work_dir': work_dir,
}
hook_res = run_hook(RUN_SHELL_CMD, hooks, pre_step_hook=True, args=[cmd], kwargs=kwargs)
if hook_res:
cmd, old_cmd = hook_res, cmd
cmd_str = to_cmd_str(cmd)
Expand Down Expand Up @@ -375,10 +379,21 @@ def to_cmd_str(cmd):

# only consider answering questions if there's new output beyond additional whitespace
if qa_patterns:
for question, answer in qa_patterns:
for question, answers in qa_patterns:

question += r'[\s\n]*$'
regex = re.compile(question.encode())
if regex.search(stdout):
# if answer is specified as a list, we take the first item as current answer,
# and add it to the back of the list (so we cycle through answers)
if isinstance(answers, list):
answer = answers.pop(0)
answers.append(answer)
elif isinstance(answers, str):
answer = answers
else:
raise EasyBuildError(f"Unknown type of answers encountered: {answers}")

answer += '\n'
os.write(proc.stdin.fileno(), answer.encode())
time_no_match = 0
Expand Down Expand Up @@ -437,6 +452,7 @@ def to_cmd_str(cmd):
if with_hooks:
run_hook_kwargs = {
'exit_code': res.exit_code,
'interactive': bool(qa_patterns),
'output': res.output,
'stderr': res.stderr,
'work_dir': res.work_dir,
Expand Down
40 changes: 40 additions & 0 deletions test/framework/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,27 @@ def test_run_cmd_qa_answers(self):
self.assertEqual(out, "question\nanswer1\nquestion\nanswer2\n" * 2)
self.assertEqual(ec, 0)

def test_run_shell_cmd_qa_answers(self):
"""Test providing list of answers for a question in run_shell_cmd."""

cmd = "echo question; read x; echo $x; " * 2
qa = [("question", ["answer1", "answer2"])]

with self.mocked_stdout_stderr():
res = run_shell_cmd(cmd, qa_patterns=qa)
self.assertEqual(res.output, "question\nanswer1\nquestion\nanswer2\n")
self.assertEqual(res.exit_code, 0)

with self.mocked_stdout_stderr():
self.assertErrorRegex(EasyBuildError, "Unknown type of answers encountered", run_shell_cmd, cmd, qa_patterns=[('question', 1)])

# test cycling of answers
cmd = cmd * 2
with self.mocked_stdout_stderr():
res = run_shell_cmd(cmd, qa_patterns=qa)
self.assertEqual(res.output, "question\nanswer1\nquestion\nanswer2\n" * 2)
self.assertEqual(res.exit_code, 0)

def test_run_cmd_simple(self):
"""Test return value for run_cmd in 'simple' mode."""
with self.mocked_stdout_stderr():
Expand Down Expand Up @@ -1124,6 +1145,14 @@ def test_run_cmd_dry_run(self):
self.assertEqual(read_file(outfile), "This is always echoed\n")

# Q&A commands
self.mock_stdout(True)
run_shell_cmd("some_qa_cmd", qa_patterns=[('question1', 'answer1')])
stdout = self.get_stdout()
self.mock_stdout(False)

expected = """ running interactive shell command "some_qa_cmd"\n"""
self.assertIn(expected, stdout)

self.mock_stdout(True)
run_cmd_qa("some_qa_cmd", {'question1': 'answer1'})
stdout = self.get_stdout()
Expand Down Expand Up @@ -1565,6 +1594,17 @@ def post_run_shell_cmd_hook(cmd, *args, **kwargs):
])
self.assertEqual(stdout, expected_stdout)

with self.mocked_stdout_stderr():
run_shell_cmd("sleep 2; make", qa_patterns=[('q', 'a')])
stdout = self.get_stdout()

expected_stdout = '\n'.join([
"pre-run hook interactive 'sleep 2; make' in %s" % cwd,
"post-run hook interactive 'sleep 2; echo make' (exit code: 0, output: 'make\n')",
'',
])
self.assertEqual(stdout, expected_stdout)

with self.mocked_stdout_stderr():
run_cmd_qa("sleep 2; make", qa={})
stdout = self.get_stdout()
Expand Down

0 comments on commit 5783910

Please sign in to comment.