Skip to content

Commit

Permalink
Merge pull request #485 from dodona-edu/enhance/heredoc-stdin
Browse files Browse the repository at this point in the history
Use here-doc for stdin if both stdin and arguments are used
  • Loading branch information
niknetniko authored Dec 20, 2023
2 parents 91930a4 + eeb408d commit 6a7a1a7
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 9 deletions.
9 changes: 8 additions & 1 deletion tested/dsl/translate_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ class ReturnOracle:
)


def _ensure_trailing_newline(text: str) -> str:
if text[-1] != "\n":
return text + "\n"
else:
return text


def _parse_yaml_value(loader: yaml.Loader, node: yaml.Node) -> Any:
if isinstance(node, yaml.MappingNode):
result = loader.construct_mapping(node)
Expand Down Expand Up @@ -442,7 +449,7 @@ def _convert_testcase(testcase: YamlDict, context: DslContext) -> Testcase:
else:
if "stdin" in testcase:
assert isinstance(testcase["stdin"], str)
stdin = TextData(data=testcase["stdin"])
stdin = TextData(data=_ensure_trailing_newline(testcase["stdin"]))
else:
stdin = EmptyChannel.NONE
arguments = testcase.get("arguments", [])
Expand Down
26 changes: 20 additions & 6 deletions tested/languages/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ def _handle_link_files(link_files: Iterable[FileUrl], language: str) -> tuple[st
)


def _get_heredoc_token(stdin: str) -> str:
delimiter = "STDIN"
stdin_lines = stdin.splitlines()
while delimiter in stdin:
delimiter = delimiter + "N"
return delimiter


def get_readable_input(
bundle: Bundle, case: Testcase
) -> tuple[ExtendedMessage, set[FileUrl]]:
Expand All @@ -113,20 +121,26 @@ def get_readable_input(
assert isinstance(case.input, MainInput)
# See https://rouge-ruby.github.io/docs/Rouge/Lexers/ConsoleLexer.html
format_ = "console"
# Determine the command (with arguments)
submission = submission_name(bundle.language)
command = shlex.join([submission] + case.input.arguments)
args = f"$ {command}"
# Determine the stdin
if isinstance(case.input.stdin, TextData):
stdin = case.input.stdin.get_data_as_string(bundle.config.resources)
else:
stdin = ""
if not stdin:
text = args

# If we have both stdin and arguments, we use a here-document.
if case.input.arguments and stdin:
assert stdin[-1] == "\n", "stdin must end with a newline"
delimiter = _get_heredoc_token(stdin)
text = f"{args} << '{delimiter}'\n{stdin}{delimiter}"
elif stdin:
assert not case.input.arguments
text = stdin
else:
if case.input.arguments:
text = f"{args}\n{stdin}"
else:
text = stdin
text = args
elif isinstance(case.input, Statement):
format_ = bundle.config.programming_language
text = generate_statement(bundle, case.input)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_dsl_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_parse_one_tab_ctx():
assert len(context.testcases) == 1
tc = context.testcases[0]
assert tc.is_main_testcase()
assert tc.input.stdin.data == "Input string"
assert tc.input.stdin.data == "Input string\n"
assert tc.input.arguments == ["--arg", "argument"]
assert tc.output.stderr.data == "Error string"
assert tc.output.stdout.data == "Output string"
Expand Down
47 changes: 46 additions & 1 deletion tests/test_functionality.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
NumberType,
StringType,
)
from tested.testsuite import Context, MainInput, Suite, Tab, Testcase
from tested.testsuite import Context, MainInput, Suite, Tab, Testcase, TextData
from tests.manual_utils import assert_valid_output, configuration, execute_config

COMPILE_LANGUAGES = [
Expand Down Expand Up @@ -1036,3 +1036,48 @@ def test_main_call_quotes(tmp_path: Path, pytestconfig):
assert (
actual.description == "$ submission hello 'it'\"'\"'s' '$yes' --hello=no -hello"
)


def test_stdin_and_arguments_use_heredoc(tmp_path: Path, pytestconfig):
conf = configuration(
pytestconfig,
"echo-function",
"bash",
tmp_path,
"two.yaml",
"top-level-output",
)
the_input = Testcase(
input=MainInput(
arguments=["hello"], stdin=TextData(data="One line\nSecond line\n")
)
)
suite = Suite(tabs=[Tab(contexts=[Context(testcases=[the_input])], name="hallo")])
bundle = create_bundle(conf, sys.stdout, suite)
actual, _ = get_readable_input(bundle, the_input)

assert (
actual.description
== "$ submission hello << 'STDIN'\nOne line\nSecond line\nSTDIN"
)


def test_stdin_token_is_unique(tmp_path: Path, pytestconfig):
conf = configuration(
pytestconfig,
"echo-function",
"bash",
tmp_path,
"two.yaml",
"top-level-output",
)
the_input = Testcase(
input=MainInput(arguments=["hello"], stdin=TextData(data="One line\nSTDIN\n"))
)
suite = Suite(tabs=[Tab(contexts=[Context(testcases=[the_input])], name="hallo")])
bundle = create_bundle(conf, sys.stdout, suite)
actual, _ = get_readable_input(bundle, the_input)

assert (
actual.description == "$ submission hello << 'STDINN'\nOne line\nSTDIN\nSTDINN"
)

0 comments on commit 6a7a1a7

Please sign in to comment.