forked from TrueCloudLab/frostfs-testlib
Implement basic version of local shell
Also added two simple reporters that can be used by the shell to report command execution details. Signed-off-by: Vladimir Domnich <v.domnich@yadro.com>
This commit is contained in:
parent
c0bbfd1705
commit
f6ee129354
11 changed files with 453 additions and 0 deletions
9
tests/helpers.py
Normal file
9
tests/helpers.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
import traceback
|
||||
|
||||
|
||||
def format_error_details(error: Exception) -> str:
|
||||
return "".join(traceback.format_exception(
|
||||
etype=type(error),
|
||||
value=error,
|
||||
tb=error.__traceback__)
|
||||
)
|
78
tests/test_local_shell_interactive.py
Normal file
78
tests/test_local_shell_interactive.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from shell.interfaces import CommandOptions, InteractiveInput
|
||||
from shell.local_shell import LocalShell
|
||||
from tests.helpers import format_error_details
|
||||
|
||||
|
||||
class TestLocalShellInteractive(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.shell = LocalShell()
|
||||
|
||||
def test_command_with_one_prompt(self):
|
||||
script = "password = input('Password: '); print(password)"
|
||||
|
||||
inputs = [InteractiveInput(prompt_pattern="Password", input="test")]
|
||||
result = self.shell.exec(
|
||||
f"python -c \"{script}\"",
|
||||
CommandOptions(interactive_inputs=inputs)
|
||||
)
|
||||
|
||||
self.assertEqual(0, result.return_code)
|
||||
self.assertOutputLines(["Password: test", "test"], result.stdout)
|
||||
self.assertEqual("", result.stderr)
|
||||
|
||||
def test_command_with_several_prompts(self):
|
||||
script = (
|
||||
"input1 = input('Input1: '); print(input1); "
|
||||
"input2 = input('Input2: '); print(input2)"
|
||||
)
|
||||
inputs = [
|
||||
InteractiveInput(prompt_pattern="Input1", input="test1"),
|
||||
InteractiveInput(prompt_pattern="Input2", input="test2"),
|
||||
]
|
||||
|
||||
result = self.shell.exec(
|
||||
f"python -c \"{script}\"",
|
||||
CommandOptions(interactive_inputs=inputs)
|
||||
)
|
||||
|
||||
self.assertEqual(0, result.return_code)
|
||||
self.assertOutputLines(["Input1: test1", "test1", "Input2: test2", "test2"], result.stdout)
|
||||
self.assertEqual("", result.stderr)
|
||||
|
||||
def test_failed_command_with_check(self):
|
||||
script = "invalid script"
|
||||
inputs = [InteractiveInput(prompt_pattern=".*", input="test")]
|
||||
|
||||
with self.assertRaises(RuntimeError) as exc:
|
||||
self.shell.exec(f"python -c \"{script}\"", CommandOptions(interactive_inputs=inputs))
|
||||
|
||||
error = format_error_details(exc.exception)
|
||||
self.assertIn("Error", error)
|
||||
# TODO: it would be nice to have return code as well
|
||||
# self.assertIn("return code: 1", error)
|
||||
|
||||
def test_failed_command_without_check(self):
|
||||
script = "invalid script"
|
||||
inputs = [InteractiveInput(prompt_pattern=".*", input="test")]
|
||||
|
||||
result = self.shell.exec(
|
||||
f"python -c \"{script}\"",
|
||||
CommandOptions(interactive_inputs=inputs, check=False),
|
||||
)
|
||||
self.assertEqual(1, result.return_code)
|
||||
|
||||
def test_non_existing_binary(self):
|
||||
inputs = [InteractiveInput(prompt_pattern=".*", input="test")]
|
||||
|
||||
with self.assertRaises(RuntimeError) as exc:
|
||||
self.shell.exec("not-a-command", CommandOptions(interactive_inputs=inputs))
|
||||
|
||||
error = format_error_details(exc.exception)
|
||||
self.assertIn("command was not found or was not executable", error)
|
||||
|
||||
def assertOutputLines(self, expected_lines: list[str], output: str) -> None:
|
||||
output_lines = [line.strip() for line in output.split("\n") if line.strip()]
|
||||
self.assertEqual(expected_lines, output_lines)
|
46
tests/test_local_shell_non_interactive.py
Normal file
46
tests/test_local_shell_non_interactive.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from shell.interfaces import CommandOptions
|
||||
from shell.local_shell import LocalShell
|
||||
from tests.helpers import format_error_details
|
||||
|
||||
|
||||
class TestLocalShellNonInteractive(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.shell = LocalShell()
|
||||
|
||||
def test_successful_command(self):
|
||||
script = "print('test')"
|
||||
|
||||
result = self.shell.exec(f"python -c \"{script}\"")
|
||||
|
||||
self.assertEqual(0, result.return_code)
|
||||
self.assertEqual("test", result.stdout.strip())
|
||||
self.assertEqual("", result.stderr)
|
||||
|
||||
def test_failed_command_with_check(self):
|
||||
script = "invalid script"
|
||||
|
||||
with self.assertRaises(RuntimeError) as exc:
|
||||
self.shell.exec(f"python -c \"{script}\"")
|
||||
|
||||
error = format_error_details(exc.exception)
|
||||
self.assertIn("Error", error)
|
||||
self.assertIn("return code: 1", error)
|
||||
|
||||
def test_failed_command_without_check(self):
|
||||
script = "invalid script"
|
||||
|
||||
result = self.shell.exec(f"python -c \"{script}\"", CommandOptions(check=False))
|
||||
|
||||
self.assertEqual(1, result.return_code)
|
||||
self.assertIn("Error", result.stdout)
|
||||
|
||||
def test_non_existing_binary(self):
|
||||
with self.assertRaises(RuntimeError) as exc:
|
||||
self.shell.exec(f"not-a-command")
|
||||
|
||||
error = format_error_details(exc.exception)
|
||||
self.assertIn("Error", error)
|
||||
self.assertIn("return code: 127", error)
|
Loading…
Add table
Add a link
Reference in a new issue