diff --git a/README.md b/README.md index 33f4ee6b..11cd3bac 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ $ cat exercise/simple-example/config.json "source": "exercise/simple-example/correct.py", "judge": ".", "workdir": "workdir/", - "plan_name": "suite.yaml", + "test_suite": "suite.yaml", "memory_limit": 536870912, "time_limit": 60 } @@ -120,7 +120,7 @@ These attributes are used by TESTed: - `source`: path of the submission that must be evaluated - `judge`: path of the root directory of TESTEd - `workdir`: path of a temporary directory (see below) -- `plan_name`: path of the test suite, relative to the resources directory (as defined above) +- `test_suite`: path of the test suite, relative to the resources directory (as defined above) Before evaluating a submission, TESTed generates test code in the workdir. Create that directory: diff --git a/tested/datatypes/__init__.py b/tested/datatypes/__init__.py index 9bd9969e..746b267a 100644 --- a/tested/datatypes/__init__.py +++ b/tested/datatypes/__init__.py @@ -64,6 +64,8 @@ def resolve_to_basic(type_: AllTypes) -> BasicTypes: def string_to_type(type_string: str) -> AllTypes: enums = get_args(AllTypes) for enum in enums: - if type_string in enum.__members__: - return enum[type_string] + try: + return enum(type_string) + except ValueError: + pass raise ValueError(f"Unknown type string {type_string}") diff --git a/tested/datatypes/advanced.py b/tested/datatypes/advanced.py index 56bf42cb..8bcdf954 100644 --- a/tested/datatypes/advanced.py +++ b/tested/datatypes/advanced.py @@ -91,6 +91,10 @@ class AdvancedStringTypes(_AdvancedDataType): """ A single character """ + STRING = "string", BasicStringTypes.TEXT + """ + A string (sequence of characters). + """ class AdvancedNothingTypes(_AdvancedDataType): diff --git a/tested/descriptions/converters.py b/tested/descriptions/converters.py index 689ac7e4..fd0a91d5 100644 --- a/tested/descriptions/converters.py +++ b/tested/descriptions/converters.py @@ -1,12 +1,14 @@ from functools import partial from typing import cast +from attr import dataclass from jinja2 import Template from marko import Markdown from tested.configs import Bundle -from tested.datatypes import AllTypes +from tested.datatypes import AdvancedTypes, AllTypes, string_to_type from tested.descriptions.renderer import TestedRenderer, render_one_statement +from tested.features import TypeSupport from tested.internationalization import get_i18n_string, set_locale from tested.languages import Language from tested.languages.conventionalize import ( @@ -18,22 +20,74 @@ conventionalize_property, ) from tested.languages.generation import NestedTypeDeclaration, generate_type_declaration +from tested.utils import get_args -def type_declaration( - language: Language, type_: AllTypes, *others: NestedTypeDeclaration -) -> str: - if len(others): - result = generate_type_declaration(language, (type_, others)) - else: - result = generate_type_declaration(language, type_) - assert isinstance(result, str) - return result +@dataclass +class Datatype: + locale: str + language: Language + type_: AllTypes + others: tuple[NestedTypeDeclaration] + def _types(self) -> list[str]: + if len(self.others): + result = [ + generate_type_declaration(self.language, (self.type_, self.others)) + ] + elif isinstance(self.type_, AdvancedTypes): + result = [generate_type_declaration(self.language, self.type_)] + else: + possible_types = [self.type_] + supported_types = self.language.datatype_support() + for advanced_type_enum in get_args(AdvancedTypes): + for advanced_type in advanced_type_enum: + if ( + advanced_type.base_type == self.type_ + and supported_types.get(advanced_type) == TypeSupport.SUPPORTED + ): + possible_types.append(advanced_type) -def common_type_name(type_: AllTypes, plural: bool = False): - key = "plural" if plural else "singular" - return get_i18n_string(f"types.{key}.{type_}") + all_types = { + generate_type_declaration(self.language, x) for x in possible_types + } + result = sorted(all_types) + + assert ( + len(result) > 0 + ), f"Could not find concrete type for {self.type_} in {self.language.__class__.__name__}" + return result + + def __str__(self) -> str: + types = self._types() + types = [f"`{x}`" for x in types] + last_sep = f" {get_i18n_string('types.joiner')} " + return last_sep.join( + [", ".join(types[:-1]), types[-1]] if len(types) > 2 else types + ) + + @property + def simple(self) -> str: + if len(self.others): + return generate_type_declaration(self.language, (self.type_, self.others)) + else: + return generate_type_declaration(self.language, self.type_) + + @property + def singular(self) -> str: + return get_i18n_string(f"types.singular.{self.type_}") + + @property + def plural(self) -> str: + return get_i18n_string(f"types.singular.{self.type_}") + + +def construct_datatype( + locale: str, language: Language, type_: str, *others: NestedTypeDeclaration +) -> Datatype: + enum_type = string_to_type(type_) + # noinspection PyTypeChecker + return Datatype(locale=locale, language=language, type_=enum_type, others=others) def convert_templated_problem(bundle: Bundle, raw_description: str) -> str: @@ -61,8 +115,7 @@ def convert_templated_problem(bundle: Bundle, raw_description: str) -> str: # Access to the current programming language programming_language=bundle.config.programming_language, # Data type conversion - datatype=partial(type_declaration, language), - datatype_common=common_type_name, + datatype=partial(construct_datatype, bundle.config.natural_language, language), t=partial(render_one_statement, bundle), ) diff --git a/tested/descriptions/renderer.py b/tested/descriptions/renderer.py index 820489fa..2e8ee30c 100644 --- a/tested/descriptions/renderer.py +++ b/tested/descriptions/renderer.py @@ -7,8 +7,10 @@ from marko.md_renderer import MarkdownRenderer from tested.configs import Bundle -from tested.dsl import parse_string -from tested.languages.generation import generate_statement +from tested.dsl import parse_dsl, parse_string +from tested.judge.evaluation import Channel, guess_expected_value, should_show +from tested.languages.generation import generate_statement, get_readable_input +from tested.testsuite import OutputChannel, Testcase TESTED_EXAMPLE_FORMAT = "console?lang=tested" @@ -23,6 +25,27 @@ def render_one_statement(bundle: Bundle, statement: str) -> str: return bundle.language.cleanup_description(generated_statement) +# Similar to _add_channel +def _add_output( + bundle: Bundle, output: OutputChannel, channel: Channel, results: list[str] +): + if should_show(output, channel): + expected = guess_expected_value(bundle, output) + results.append(expected) + + +def get_expected_output(bundle: Bundle, tc: Testcase) -> list[str]: + results = [] + _add_output(bundle, tc.output.stdout, Channel.STDOUT, results) + _add_output(bundle, tc.output.stderr, Channel.STDERR, results) + _add_output(bundle, tc.output.file, Channel.FILE, results) + _add_output(bundle, tc.output.exception, Channel.EXCEPTION, results) + _add_output(bundle, tc.output.result, Channel.RETURN, results) + _add_output(bundle, tc.output.exit_code, Channel.EXIT, results) + + return results + + class TestedRenderer(MarkdownRenderer): bundle: Bundle _doctest_parser: DocTestParser @@ -72,9 +95,44 @@ def _render_normal_statements(self, element: block.FencedCode) -> str: body = "\n".join(resulting_lines) return f"```{language}\n{body}\n```\n" + def _render_dsl_statements(self, element: block.FencedCode) -> str: + """ + Render a single statement (or multiple lines of single statements). + """ + assert element.lang == "dsl" + + rendered_dsl = self.render_children(element) + + # Parse the DSL + parsed_dsl = parse_dsl(rendered_dsl) + + # Get all actual tests + tests = [] + for tab in parsed_dsl.tabs: + for context in tab.contexts: + for testcase in context.testcases: + tests.append(testcase) + + resulting_lines = [] + prompt = self.bundle.language.get_declaration_metadata().get("prompt", ">") + for testcase in tests: + stmt_message, _ = get_readable_input(self.bundle, testcase) + resulting_lines.append(f"{prompt} {stmt_message.description}") + output_lines = get_expected_output(self.bundle, testcase) + resulting_lines.extend(output_lines) + + language = ( + f"console?lang={self.bundle.config.programming_language}&prompt={prompt}" + ) + body = "\n".join(resulting_lines) + + return f"```{language}\n{body}```\n" + def render_fenced_code(self, element: block.FencedCode) -> str: if element.lang == "tested": return self._render_normal_statements(element) + elif element.lang == "dsl": + return self._render_dsl_statements(element) elif element.lang == TESTED_EXAMPLE_FORMAT: return self._render_doctest(element) else: diff --git a/tested/internationalization/en.yaml b/tested/internationalization/en.yaml index 4de30317..a6ff51dc 100644 --- a/tested/internationalization/en.yaml +++ b/tested/internationalization/en.yaml @@ -170,6 +170,7 @@ en: return: "Parse YAML return values" return-raw: "Parse return-raw values" types: + joiner: "or" singular: integer: "integer" real: "real number" @@ -189,7 +190,7 @@ en: bigint: "big integer" single_precision: "single precision floating point number" double_precision: "double precision floating point number" - fixed_precision: "fixed precision number" + fixed_precision: "fixed precision number" any: "any" list: "list" tuple: "tuple" diff --git a/tested/internationalization/nl.yaml b/tested/internationalization/nl.yaml index 1601aa2b..6d6fcebf 100644 --- a/tested/internationalization/nl.yaml +++ b/tested/internationalization/nl.yaml @@ -170,6 +170,7 @@ nl: return: "Parsen van YAML returnwaarden" return-raw: "Parsen return-raw waarden" types: + joiner: "of" singular: integer: "geheel getal" real: "reƫel getal" diff --git a/tested/languages/bash/config.py b/tested/languages/bash/config.py index 3cb40588..5e6854cb 100644 --- a/tested/languages/bash/config.py +++ b/tested/languages/bash/config.py @@ -31,6 +31,7 @@ def naming_conventions(self) -> dict[Conventionable, NamingConventions]: def datatype_support(self) -> dict[AllTypes, TypeSupport]: return { AdvancedStringTypes.CHAR: TypeSupport.REDUCED, + AdvancedStringTypes.STRING: TypeSupport.SUPPORTED, BasicStringTypes.TEXT: TypeSupport.SUPPORTED, } @@ -107,9 +108,6 @@ def generate_encoder(self, values: list[Value]) -> str: def get_declaration_metadata(self) -> TypeDeclarationMetadata: return { - "names": { # type: ignore - "text": "str", - "char": "str", - }, + "names": {"text": "str", "char": "str", "string": "str"}, # type: ignore "prompt": "$", } diff --git a/tested/languages/c/config.py b/tested/languages/c/config.py index 5519b86a..fb71063e 100644 --- a/tested/languages/c/config.py +++ b/tested/languages/c/config.py @@ -54,6 +54,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "real": "supported", "char": "supported", "text": "supported", + "string": "supported", "boolean": "supported", "nothing": "supported", "undefined": "reduced", @@ -166,6 +167,7 @@ def get_declaration_metadata(self) -> TypeDeclarationMetadata: "real": "double", "char": "char", "text": "char*", + "string": "char*", "boolean": "bool", "nothing": "void", "undefined": "void", diff --git a/tested/languages/csharp/config.py b/tested/languages/csharp/config.py index 835e22a0..f0667b0f 100644 --- a/tested/languages/csharp/config.py +++ b/tested/languages/csharp/config.py @@ -70,6 +70,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "integer": "supported", "real": "supported", "char": "reduced", + "string": "supported", "text": "supported", "boolean": "supported", "sequence": "supported", @@ -245,6 +246,7 @@ def get_declaration_metadata(self) -> TypeDeclarationMetadata: "real": "Double", "char": "char", "text": "string", + "string": "string", "boolean": "Boolean", "sequence": "List", "set": "Set", diff --git a/tested/languages/generation.py b/tested/languages/generation.py index c5b61b10..e14185aa 100644 --- a/tested/languages/generation.py +++ b/tested/languages/generation.py @@ -91,7 +91,6 @@ 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 @@ -305,9 +304,13 @@ def _convert_single_type( def generate_type_declaration( language: Language, declaration: NestedTypeDeclaration, inner: bool = False -) -> str | bool: +) -> str: if not isinstance(declaration, tuple): - return _convert_single_type(language, declaration, inner) + simple_result = _convert_single_type(language, declaration, inner) + assert isinstance( + simple_result, str + ), f"{declaration} is a simple type and should generate a string" + return simple_result meta = language.get_declaration_metadata() base_type, nested = declaration @@ -326,7 +329,6 @@ def generate_type_declaration( converted_nested = [] for x in nested: converted_x = generate_type_declaration(language, x, True) - assert isinstance(converted_x, str) converted_nested.append(converted_x) if base is True: diff --git a/tested/languages/haskell/config.py b/tested/languages/haskell/config.py index da8c523b..d4a81f83 100644 --- a/tested/languages/haskell/config.py +++ b/tested/languages/haskell/config.py @@ -50,6 +50,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "integer": "supported", "real": "supported", "char": "supported", + "string": "supported", "text": "supported", "boolean": "supported", "sequence": "supported", @@ -186,6 +187,7 @@ def get_declaration_metadata(self) -> TypeDeclarationMetadata: "integer": "Int", "real": "Double", "char": "Char", + "string": "String", "text": "String", "boolean": "Bool", "nothing": "Nothing", diff --git a/tested/languages/java/config.py b/tested/languages/java/config.py index a6ea4b70..14532acc 100644 --- a/tested/languages/java/config.py +++ b/tested/languages/java/config.py @@ -65,6 +65,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "integer": "supported", "real": "supported", "char": "supported", + "string": "supported", "text": "supported", "boolean": "supported", "sequence": "supported", @@ -165,6 +166,7 @@ def get_declaration_metadata(self) -> TypeDeclarationMetadata: "real": "double", "char": "char", "text": "String", + "string": "String", "boolean": "boolean", "sequence": "List", "set": "Set", diff --git a/tested/languages/javascript/config.py b/tested/languages/javascript/config.py index e09f330d..94afd381 100644 --- a/tested/languages/javascript/config.py +++ b/tested/languages/javascript/config.py @@ -67,6 +67,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "real": "supported", "char": "reduced", "text": "supported", + "string": "supported", "boolean": "supported", "sequence": "supported", "set": "supported", @@ -209,6 +210,7 @@ def get_declaration_metadata(self) -> TypeDeclarationMetadata: "real": "number", "char": "string", "text": "string", + "string": "string", "boolean": "boolean", "sequence": "array", "set": "set", diff --git a/tested/languages/kotlin/config.py b/tested/languages/kotlin/config.py index 80f5466a..c1a294b4 100644 --- a/tested/languages/kotlin/config.py +++ b/tested/languages/kotlin/config.py @@ -75,6 +75,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "real": "supported", "char": "supported", "text": "supported", + "string": "supported", "boolean": "supported", "sequence": "supported", "set": "supported", @@ -106,6 +107,7 @@ def map_type_restrictions(self) -> set[ExpressionTypes] | None: "real", "char", "text", + "string", "boolean", "sequence", "set", @@ -235,6 +237,7 @@ def get_declaration_metadata(self) -> TypeDeclarationMetadata: "real": "Double", "char": "Char", "text": "String", + "string": "String", "boolean": "Boolean", "sequence": "List", "set": "Set", diff --git a/tested/languages/python/config.py b/tested/languages/python/config.py index 6427607d..487a59f5 100644 --- a/tested/languages/python/config.py +++ b/tested/languages/python/config.py @@ -72,6 +72,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "real": "supported", "char": "reduced", "text": "supported", + "string": "supported", "boolean": "supported", "sequence": "supported", "set": "supported", @@ -235,10 +236,11 @@ def get_declaration_metadata(self) -> TypeDeclarationMetadata: "real": "float", "char": "str", "text": "str", + "string": "str", "boolean": "bool", - "sequence": "List", - "set": "Set", - "map": "Dict", + "sequence": "list", + "set": "set", + "map": "dict", "nothing": "None", "undefined": "None", "int8": "int", @@ -254,9 +256,9 @@ def get_declaration_metadata(self) -> TypeDeclarationMetadata: "double_precision": "float", "double_extended": "Decimal", "fixed_precision": "Decimal", - "array": "List", - "list": "List", - "tuple": "Tuple", + "array": "list", + "list": "list", + "tuple": "tuple", "any": "Any", }, "prompt": ">>>", diff --git a/tests/descriptions/example.md.jinja2 b/tests/descriptions/example.md.jinja2 index 8fcef24b..4a2c11d7 100644 --- a/tests/descriptions/example.md.jinja2 +++ b/tests/descriptions/example.md.jinja2 @@ -1,4 +1,4 @@ -Aan de functie `{{ function("splits_in_woorden") }}` moet een string (`{{ datatype("text") }}`) doorgegeven worden. +Aan de functie `{{ function("splits_in_woorden") }}` moet een string (`{{ datatype("string") }}`) doorgegeven worden. De functie geeft een lijst van letters (`{{ datatype("sequence", "char") }}`) terug. ```tested diff --git a/tests/descriptions/example.python.md b/tests/descriptions/example.python.md index 0eb85d4b..e46587a9 100644 --- a/tests/descriptions/example.python.md +++ b/tests/descriptions/example.python.md @@ -1,5 +1,5 @@ Aan de functie `splits_in_woorden` moet een string (`str`) doorgegeven worden. -De functie geeft een lijst van letters (`List[str]`) terug. +De functie geeft een lijst van letters (`list[str]`) terug. ```python a_function_call('yes') diff --git a/tests/descriptions/recoupling.md.j2 b/tests/descriptions/recoupling.md.j2 new file mode 100644 index 00000000..a582acdc --- /dev/null +++ b/tests/descriptions/recoupling.md.j2 @@ -0,0 +1,45 @@ +Write a function `{{ function('divide') }}` that takes two arguments: +_i_) a word (`{{ datatype("text")}}`) and +_ii_) the number of (non-overlapping) groups $$n \\in \\mathbb{N}\_0$$ (`{{datatype("integer")}}`) into which the word must be divided. +If the word passed to the function `{{function('divide')}}` cannot be divided into $$n$$ groups that have the same length, an `AssertionError` must be raised with the message `invalid division`. +Otherwise, the function must return a {{datatype("list").singular}} (`{{datatype("list")}}`) containing the $$n$$ groups (`{{datatype("text")}}`) into which the given word can be divided. +All groups need to have the same length (same number of letters). + +Write another function `{{function('recouple')}}` that takes two arguments: +_i_) a {{datatype("sequence").singular}} (`{{datatype("sequence")}}`) of $$m \\in \\mathbb{N}\_0$$ words (`{{datatype("text")}}`) and +_ii_) the number of (non-overlapping) groups $$n \\in \\mathbb{N}\_0$$ (`{{datatype("integer")}}`) into which the words must be divided. +If at least one of the words passed to the function `{{function('recouple')}}` cannot be divided into $$n$$ groups that have the same length, an `AssertionError` must be raised with the message `invalid division`. +Otherwise, the function must return a {{datatype("sequence").singular}} containing the $$n$$ new words (`{{datatype("text")}}`) obtained when each of the $$m$$ given words is divided into $$n$$ groups that have the same length, and if each of the $$m$$ corresponding groups is merged into a new word. +The type of the returned {{datatype("sequence").singular}} (`{{datatype("sequence")}}`) must correspond to the type of the {{datatype("sequence").singular}} passed as a first argument to the function. + +### Example + +```dsl +units: +- unit: "Divide" + scripts: + - expression: "divide('accost', 3)" + return: ["ac", "co", "st"] + - expression: "divide('COMMUNED', 4)" + return: ["CO", "MM", "UN", "ED"] + - expression: "divide('programming', 5)" + exception: + message: "invalid division" + types: + python: AssertionError + javascript: AssertionError + java: IllegalArgumentException +- unit: "Recouple" + scripts: + - expression: "recouple(['ACcoST', 'COmmIT', 'LAunCH', 'DEedED'], 3)" + return: !list ["ACCOLADE", "communed", "STITCHED"] + - expression: "recouple(('ACCOLADE', 'communed', 'STITCHED'), 4)" + return: !tuple ["ACcoST", "COmmIT", "LAunCH", "DEedED"] + - expression: "recouple(['programming', 'computer', 'games'], 5)" + exception: + message: "invalid division" + types: + python: AssertionError + javascript: AssertionError + java: IllegalArgumentException +``` diff --git a/tests/descriptions/recoupling.python.md b/tests/descriptions/recoupling.python.md new file mode 100644 index 00000000..e8da3717 --- /dev/null +++ b/tests/descriptions/recoupling.python.md @@ -0,0 +1,31 @@ +Write a function `divide` that takes two arguments: +*i*) a word (`str`) and +*ii*) the number of (non-overlapping) groups $$n \\in \\mathbb{N}\_0$$ (`int`) into which the word must be divided. +If the word passed to the function `divide` cannot be divided into $$n$$ groups that have the same length, an `AssertionError` must be raised with the message `invalid division`. +Otherwise, the function must return a list (`list`) containing the $$n$$ groups (`str`) into which the given word can be divided. +All groups need to have the same length (same number of letters). + +Write another function `recouple` that takes two arguments: +*i*) a sequence (`list` or `tuple`) of $$m \\in \\mathbb{N}\_0$$ words (`str`) and +*ii*) the number of (non-overlapping) groups $$n \\in \\mathbb{N}\_0$$ (`int`) into which the words must be divided. +If at least one of the words passed to the function `recouple` cannot be divided into $$n$$ groups that have the same length, an `AssertionError` must be raised with the message `invalid division`. +Otherwise, the function must return a sequence containing the $$n$$ new words (`str`) obtained when each of the $$m$$ given words is divided into $$n$$ groups that have the same length, and if each of the $$m$$ corresponding groups is merged into a new word. +The type of the returned sequence (`list` or `tuple`) must correspond to the type of the sequence passed as a first argument to the function. + +### Example + +```console?lang=python&prompt=>>> +>>> divide('accost', 3) +['ac', 'co', 'st'] +>>> divide('COMMUNED', 4) +['CO', 'MM', 'UN', 'ED'] +>>> divide('programming', 5) +AssertionError: invalid division + +>>> recouple(['ACcoST', 'COmmIT', 'LAunCH', 'DEedED'], 3) +['ACCOLADE', 'communed', 'STITCHED'] +>>> recouple(('ACCOLADE', 'communed', 'STITCHED'), 4) +('ACcoST', 'COmmIT', 'LAunCH', 'DEedED') +>>> recouple(['programming', 'computer', 'games'], 5) +AssertionError: invalid division +``` diff --git a/tests/test_problem_statements.py b/tests/test_problem_statements.py index 0eaa3c25..89af0e08 100644 --- a/tests/test_problem_statements.py +++ b/tests/test_problem_statements.py @@ -43,40 +43,34 @@ def test_template_function_name(lang: str, expected: str): @pytest.mark.parametrize( ("lang", "tested_type", "expected"), [ - # Python ("python", "'integer'", "int"), ("python", "'real'", "float"), ("python", "'text'", "str"), - ("python", '"sequence", "integer"', "List[int]"), - ("python", '"array", ("set", ("integer", ))', "List[Set[int]]"), + ("python", '"sequence", "integer"', "list[int]"), + ("python", '"array", ("set", ("integer", ))', "list[set[int]]"), ( "python", '"tuple", ("sequence", ("real", )), "text"', - "Tuple[List[float], str]", + "tuple[list[float], str]", ), - # Java ("java", "'integer'", "int"), ("java", "'real'", "double"), ("java", "'text'", "String"), ("java", '"sequence", "integer"', "List"), ("java", '"array", ("set", ("integer", ))', "Set[]"), - # c ("c", "'integer'", "int"), ("c", "'real'", "double"), ("c", "'text'", "char*"), - # Kotlin ("kotlin", "'integer'", "Int"), ("kotlin", "'real'", "Double"), ("kotlin", "'text'", "String"), ("kotlin", '"sequence", "integer"', "List"), ("kotlin", '"array", ("set", ("integer", ))', "Array>"), - # JavaScript ("javascript", "'integer'", "number"), ("javascript", "'real'", "number"), ("javascript", "'text'", "string"), ("javascript", '"sequence", "integer"', "array"), ("javascript", '"array", ("set", ("integer", ))', "array>"), - # Haskell ("haskell", "'integer'", "Int"), ("haskell", "'real'", "Double"), ("haskell", "'text'", "String"), @@ -89,7 +83,7 @@ def test_template_function_name(lang: str, expected: str): ], ) def test_template_type_name(lang: str, tested_type: Any, expected: str): - template = f"""{{{{ datatype({tested_type}) }}}}""" + template = f"""{{{{ datatype({tested_type}).simple }}}}""" instance = process_problem_statement(template, lang) assert instance == f"{expected}" @@ -97,25 +91,20 @@ def test_template_type_name(lang: str, tested_type: Any, expected: str): @pytest.mark.parametrize( ("lang", "tested_type", "expected"), [ - # Python ("python", "'sequence'", "sequence"), ("python", "'map'", "map"), - # Java ("java", "'sequence'", "sequence"), ("java", "'map'", "map"), - # Kotlin ("kotlin", "'sequence'", "sequence"), ("kotlin", "'map'", "map"), - # JavaScript ("javascript", "'sequence'", "sequence"), ("javascript", "'map'", "map"), - # Haskell ("haskell", "'sequence'", "sequence"), ("haskell", "'list'", "list"), ], ) def test_template_natural_type_name(lang: str, tested_type: Any, expected: str): - template = f"""{{{{ datatype_common({tested_type}) }}}}""" + template = f"""{{{{ datatype({tested_type}).singular }}}}""" instance = process_problem_statement(template, lang) assert instance == f"{expected}" @@ -123,25 +112,20 @@ def test_template_natural_type_name(lang: str, tested_type: Any, expected: str): @pytest.mark.parametrize( ("lang", "tested_type", "expected"), [ - # Python ("python", "'sequence'", "sequentie"), ("python", "'map'", "afbeelding"), - # Java ("java", "'sequence'", "sequentie"), ("java", "'map'", "afbeelding"), - # Kotlin ("kotlin", "'sequence'", "sequentie"), ("kotlin", "'map'", "afbeelding"), - # JavaScript ("javascript", "'sequence'", "sequentie"), ("javascript", "'map'", "afbeelding"), - # Haskell ("haskell", "'sequence'", "sequentie"), ("haskell", "'list'", "lijst"), ], ) def test_template_natural_type_name_nl(lang: str, tested_type: Any, expected: str): - template = f"""{{{{ datatype_common({tested_type}) }}}}""" + template = f"""{{{{ datatype({tested_type}).singular }}}}""" instance = process_problem_statement(template, lang, "nl") assert instance == f"{expected}" @@ -297,3 +281,16 @@ def test_multiline_statement(): ``` """ assert actual == expected + + +def test_long_description(): + test_dir = Path(__file__).parent + description_template = test_dir / "descriptions" / "recoupling.md.j2" + description_python = test_dir / "descriptions" / f"recoupling.python.md" + with open(description_template, "r") as dp: + template = dp.read() + with open(description_python, "r") as dp: + expected = dp.read() + actual = process_problem_statement(template, "python") + + assert actual == expected