From dcf9668e6e9d9d622a34dc9ec3bd5d6c6376b0ac Mon Sep 17 00:00:00 2001 From: Niko Strijbol Date: Fri, 12 Apr 2024 17:50:42 +0200 Subject: [PATCH] Improve JavaScript handling of Map/Object Switch to Map by default and introduce two new advanced types to make the distinction between Map/Object when needed. The new types are "dictionary" (since "map" is already in use) for the datastructure and "object" for the JavaScript-specific "Object" type, which no other language natively supports at the moment. All existing languages have been updated to support the "dictionary" advanced type if they supported the basic "map" type, while the advanced type for "object" is reduced, except in JavaScript, which supports both. As part of this, the way to indicate type limits on certain elements of collections has been reworked to be more flexible. It is now possible to specify limits for all types separately. At the moment, we still only recognize limits on map keys and set elements, so nothing changes there. --- tested/datatypes/__init__.py | 3 +- tested/datatypes/advanced.py | 13 +++++ tested/features.py | 35 ++++++++++---- tested/languages/config.py | 29 +++++------ tested/languages/csharp/config.py | 2 + tested/languages/csharp/templates/Values.cs | 2 +- tested/languages/java/config.py | 22 ++++++--- tested/languages/java/templates/Values.java | 8 ++-- tested/languages/javascript/config.py | 26 ++++------ tested/languages/javascript/generators.py | 20 ++++++-- .../languages/javascript/templates/values.js | 4 +- tested/languages/kotlin/config.py | 20 +++++--- tested/languages/kotlin/templates/Values.kt | 2 +- tested/languages/python/config.py | 20 +++++--- tested/languages/python/templates/values.py | 2 +- .../missing_key_types_js_dictionary.yaml | 4 ++ .../missing_key_types_js_object.yaml | 4 ++ tests/test_dsl_expression.py | 48 ++++++++++++++++++- tests/test_functionality.py | 35 ++++++++++++-- tests/test_serialisation.py | 20 +++++++- 20 files changed, 239 insertions(+), 80 deletions(-) create mode 100644 tests/exercises/objects/evaluation/missing_key_types_js_dictionary.yaml create mode 100644 tests/exercises/objects/evaluation/missing_key_types_js_object.yaml diff --git a/tested/datatypes/__init__.py b/tested/datatypes/__init__.py index 746b267a..8627f0a4 100644 --- a/tested/datatypes/__init__.py +++ b/tested/datatypes/__init__.py @@ -15,6 +15,7 @@ from tested.datatypes.advanced import ( AdvancedNothingTypes, AdvancedNumericTypes, + AdvancedObjectTypes, AdvancedSequenceTypes, AdvancedStringTypes, AdvancedTypes, @@ -36,7 +37,7 @@ BooleanTypes = BasicBooleanTypes NothingTypes = BasicNothingTypes | AdvancedNothingTypes SequenceTypes = BasicSequenceTypes | AdvancedSequenceTypes -ObjectTypes = BasicObjectTypes +ObjectTypes = BasicObjectTypes | AdvancedObjectTypes SimpleTypes = NumericTypes | StringTypes | BooleanTypes | NothingTypes ComplexTypes = SequenceTypes | ObjectTypes diff --git a/tested/datatypes/advanced.py b/tested/datatypes/advanced.py index 8bcdf954..02ad8fed 100644 --- a/tested/datatypes/advanced.py +++ b/tested/datatypes/advanced.py @@ -10,6 +10,7 @@ from tested.datatypes.basic import ( BasicNothingTypes, BasicNumericTypes, + BasicObjectTypes, BasicSequenceTypes, BasicStringTypes, BasicTypes, @@ -110,9 +111,21 @@ class AdvancedNothingTypes(_AdvancedDataType): """ +class AdvancedObjectTypes(_AdvancedDataType): + DICTIONARY = "dictionary", BasicObjectTypes.MAP + """ + A proper map/dictionary/associative array data structure. + """ + OBJECT = "object", BasicObjectTypes.MAP + """ + An object like in JavaScript. + """ + + AdvancedTypes = Union[ AdvancedNumericTypes, AdvancedSequenceTypes, AdvancedStringTypes, AdvancedNothingTypes, + AdvancedObjectTypes, ] diff --git a/tested/features.py b/tested/features.py index fe5ad4af..c71f3a43 100644 --- a/tested/features.py +++ b/tested/features.py @@ -11,7 +11,14 @@ from attrs import define -from tested.datatypes import AllTypes, BasicObjectTypes, BasicSequenceTypes, NestedTypes +from tested.datatypes import ( + AllTypes, + BasicObjectTypes, + BasicSequenceTypes, + ComplexExpressionTypes, + NestedTypes, + resolve_to_basic, +) if TYPE_CHECKING: from tested.languages.config import Language @@ -166,20 +173,28 @@ def is_supported(language: "Language") -> bool: return False nested_types = [] for key, value_types in required.nested_types: - if key in (BasicSequenceTypes.SET, BasicObjectTypes.MAP): + # Skip these + if isinstance(key, ComplexExpressionTypes): + continue + basic_key = resolve_to_basic(key) + if basic_key in (BasicSequenceTypes.SET, BasicObjectTypes.MAP): nested_types.append((key, value_types)) - restricted = { - BasicSequenceTypes.SET: language.set_type_restrictions(), - BasicObjectTypes.MAP: language.map_type_restrictions(), - } + collection_restrictions = language.collection_restrictions() for key, value_types in nested_types: - if not (value_types <= restricted[key]): + basic_key = resolve_to_basic(key) + restrictions = collection_restrictions.get( + key, collection_restrictions.get(basic_key) + ) + # If None, skip as there are no restrictions. + if restrictions is None: + continue + if not (value_types <= restrictions): _logger.warning("This test suite is not compatible!") - _logger.warning(f"Required {key} types are {value_types}.") - _logger.warning(f"The language supports {restricted[key]}.") - missing = (value_types ^ restricted[key]) & value_types + _logger.warning(f"For {key}, used types are {value_types}.") + _logger.warning(f"Language restrictions are {restrictions}.") + missing = (value_types ^ restrictions) & value_types _logger.warning(f"Missing types are: {missing}.") return False diff --git a/tested/languages/config.py b/tested/languages/config.py index 1e8ba2cb..10b171c2 100644 --- a/tested/languages/config.py +++ b/tested/languages/config.py @@ -243,29 +243,24 @@ def supported_constructs(self) -> set[Construct]: """ return set() - def map_type_restrictions(self) -> set[ExpressionTypes] | None: + def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: """ - Get type restrictions that apply to map types in this language. + Get type restrictions for other types. - If you return None, all data types are assumed to be usable as the key in - a map data type, such as a dictionary or hashmap. Otherwise, you must return - a whitelist of the allowed types. + The exact interpretation varies by the type. Only some restrictions are + currently recognized: - :return: The whitelist of allowed types, or everything is allowed. - """ - return None - - def set_type_restrictions(self) -> set[ExpressionTypes] | None: - """ - Get type restrictions that apply to the set types in this language. + Restrictions on `BasicObjectTypes.Map` (or related advanced types) are + interpreted as restrictions on the type of the keys. The provided types + are a whitelist: the only allowed types. - If you return None, all data types are assumed to be usable as the key in - a set data type, such as a HashSet. Otherwise, you must return a whitelist - of the allowed types. + Restrictions on `BasicSetTypes.SET` (or related advanced types) are + interpreted as restrictions on the type of the elements. The provided + types are a whitelist: the only allowed types. - :return: The whitelist of allowed types, or everything is allowed. + :return: Mapping of types to their restrictions. Unmapped types are unrestricted. """ - return None + return dict() def datatype_support(self) -> dict[AllTypes, TypeSupport]: """ diff --git a/tested/languages/csharp/config.py b/tested/languages/csharp/config.py index 651d2ed0..8fe5951f 100644 --- a/tested/languages/csharp/config.py +++ b/tested/languages/csharp/config.py @@ -76,6 +76,8 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "sequence": "supported", "set": "supported", "map": "supported", + "dictionary": "supported", + "object": "reduced", "nothing": "supported", "undefined": "reduced", "null": "reduced", diff --git a/tested/languages/csharp/templates/Values.cs b/tested/languages/csharp/templates/Values.cs index 29afbedb..1a54a9be 100644 --- a/tested/languages/csharp/templates/Values.cs +++ b/tested/languages/csharp/templates/Values.cs @@ -88,7 +88,7 @@ class Values type = "set"; data = encodeSequence((IEnumerable) value); } else if (value is IDictionary) { - type = "map"; + type = "dictionary"; List entries = new List(); foreach (DictionaryEntry entry in (IDictionary) value) { diff --git a/tested/languages/java/config.py b/tested/languages/java/config.py index 2d67f641..a61c3fc6 100644 --- a/tested/languages/java/config.py +++ b/tested/languages/java/config.py @@ -3,7 +3,12 @@ from pathlib import Path from typing import TYPE_CHECKING -from tested.datatypes import AllTypes, ExpressionTypes +from tested.datatypes import ( + AllTypes, + BasicObjectTypes, + BasicSequenceTypes, + ExpressionTypes, +) from tested.dodona import AnnotateCode, Message from tested.features import Construct, TypeSupport from tested.languages.config import ( @@ -71,6 +76,8 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "sequence": "supported", "set": "supported", "map": "supported", + "dictionary": "supported", + "object": "reduced", "nothing": "supported", "undefined": "reduced", "null": "reduced", @@ -91,8 +98,8 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "list": "supported", } - def map_type_restrictions(self) -> set[ExpressionTypes] | None: - return { # type: ignore + def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: + restrictions = { "integer", "real", "char", @@ -113,9 +120,10 @@ def map_type_restrictions(self) -> set[ExpressionTypes] | None: "function_calls", "identifiers", } - - def set_type_restrictions(self) -> set[ExpressionTypes] | None: - return self.map_type_restrictions() + return { + BasicObjectTypes.MAP: restrictions, # type: ignore + BasicSequenceTypes.SET: restrictions, + } def compilation(self, files: list[str]) -> CallbackResult: def file_filter(file: Path) -> bool: @@ -171,6 +179,8 @@ def get_declaration_metadata(self) -> TypeDeclarationMetadata: "sequence": "List", "set": "Set", "map": "Map", + "dictionary": "Map", + "object": "Map", "nothing": "Void", "undefined": "Void", "int8": "byte", diff --git a/tested/languages/java/templates/Values.java b/tested/languages/java/templates/Values.java index bfcb54b0..e00e548e 100644 --- a/tested/languages/java/templates/Values.java +++ b/tested/languages/java/templates/Values.java @@ -112,7 +112,7 @@ private static List internalEncode(Object value) { type = "set"; data = encodeSequence((Iterable) value); } else if (value instanceof Map) { - type = "map"; + type = "dictionary"; var elements = new ArrayList(); for (Map.Entry entry : ((Map) value).entrySet()) { elements.add("{ \"key\":" + encode(entry.getKey()) + ",\"value\": " + encode(entry.getValue()) + "}"); @@ -163,7 +163,7 @@ private static String convertMessage(EvaluationResult.Message message) { var description = asJson(message.description); var format = asJson(message.format); var permission = asJson(message.permission); - + return """ { "description": %s, @@ -172,7 +172,7 @@ private static String convertMessage(EvaluationResult.Message message) { } """.formatted(description, format, permission); } - + private static String asJson(String value) { if (value == null) { return "null"; @@ -188,7 +188,7 @@ public static void sendEvaluated(PrintWriter writer, EvaluationResult r) { var dslExpected = asJson(r.dslExpected); var dslActual = asJson(r.dslActual); var messages = String.join(", ", converted); - + String result = """ { "result": %b, diff --git a/tested/languages/javascript/config.py b/tested/languages/javascript/config.py index 624374d5..f1cd8ffc 100644 --- a/tested/languages/javascript/config.py +++ b/tested/languages/javascript/config.py @@ -3,7 +3,12 @@ from pathlib import Path from typing import TYPE_CHECKING -from tested.datatypes import AllTypes, BasicStringTypes, ExpressionTypes +from tested.datatypes import ( + AdvancedObjectTypes, + AllTypes, + BasicStringTypes, + ExpressionTypes, +) from tested.dodona import AnnotateCode, Message from tested.features import Construct, TypeSupport from tested.languages.config import ( @@ -72,6 +77,8 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "sequence": "supported", "set": "supported", "map": "supported", + "dictionary": "supported", + "object": "supported", "nothing": "supported", "undefined": "supported", "null": "supported", @@ -92,21 +99,8 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "tuple": "reduced", } - def map_type_restrictions(self) -> set[ExpressionTypes] | None: - return {BasicStringTypes.TEXT} - - def set_type_restrictions(self) -> set[ExpressionTypes] | None: - return { # type: ignore - "integer", - "real", - "text", - "boolean", - "sequence", - "set", - "map", - "function_calls", - "identifiers", - } + def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: + return {AdvancedObjectTypes.OBJECT: {BasicStringTypes.TEXT}} def compilation(self, files: list[str]) -> CallbackResult: submission = submission_file(self) diff --git a/tested/languages/javascript/generators.py b/tested/languages/javascript/generators.py index 2993cf85..b1d53cf6 100644 --- a/tested/languages/javascript/generators.py +++ b/tested/languages/javascript/generators.py @@ -11,6 +11,7 @@ BasicSequenceTypes, BasicStringTypes, ) +from tested.datatypes.advanced import AdvancedObjectTypes from tested.languages.conventionalize import submission_file from tested.languages.preparation import ( PreparedContext, @@ -48,6 +49,17 @@ def convert_value(value: Value) -> str: raise AssertionError("Double extended values are not supported in js.") elif value.type == AdvancedNumericTypes.FIXED_PRECISION: raise AssertionError("Fixed precision values are not supported in js.") + elif value.type == AdvancedObjectTypes.OBJECT: + assert isinstance(value, ObjectType) + result = "{" + for i, pair in enumerate(value.data): + result += convert_statement(pair.key, True) + result += ": " + result += convert_statement(pair.value, True) + if i != len(value.data) - 1: + result += ", " + result += "}" + return result elif value.type in ( AdvancedNumericTypes.INT_64, AdvancedNumericTypes.U_INT_64, @@ -79,14 +91,16 @@ def convert_value(value: Value) -> str: return f"new Set([{convert_arguments(value.data)}])" elif value.type == BasicObjectTypes.MAP: assert isinstance(value, ObjectType) - result = "{" + result = "new Map([" for i, pair in enumerate(value.data): + result += "[" result += convert_statement(pair.key, True) - result += ": " + result += ", " result += convert_statement(pair.value, True) + result += "]" if i != len(value.data) - 1: result += ", " - result += "}" + result += "])" return result elif value.type == BasicStringTypes.UNKNOWN: assert isinstance(value, StringType) diff --git a/tested/languages/javascript/templates/values.js b/tested/languages/javascript/templates/values.js index 987fba72..82cacfaa 100644 --- a/tested/languages/javascript/templates/values.js +++ b/tested/languages/javascript/templates/values.js @@ -53,7 +53,7 @@ function encode(value) { type = "set"; value = Array.from(value).map(encode); } else if (value instanceof Map) { - type = "map"; + type = "dictionary"; value = Array .from(value) .map(([key, value]) => { @@ -65,7 +65,7 @@ function encode(value) { ); } else if (value?.constructor === Object) { // Plain objects - type = "map"; + type = "object"; // Process the elements of the object. value = Object.entries(value).map(([key, value]) => { return { diff --git a/tested/languages/kotlin/config.py b/tested/languages/kotlin/config.py index c3467605..99a32f47 100644 --- a/tested/languages/kotlin/config.py +++ b/tested/languages/kotlin/config.py @@ -4,7 +4,12 @@ from pathlib import Path from typing import TYPE_CHECKING -from tested.datatypes import AllTypes, ExpressionTypes +from tested.datatypes import ( + AllTypes, + BasicObjectTypes, + BasicSequenceTypes, + ExpressionTypes, +) from tested.dodona import AnnotateCode, Message, Status from tested.features import Construct, TypeSupport from tested.languages.config import ( @@ -80,6 +85,8 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "sequence": "supported", "set": "supported", "map": "supported", + "dictionary": "supported", + "object": "reduced", "nothing": "supported", "undefined": "reduced", "null": "reduced", @@ -101,8 +108,8 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "tuple": "reduced", } - def map_type_restrictions(self) -> set[ExpressionTypes] | None: - return { # type: ignore + def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: + restrictions = { "integer", "real", "char", @@ -124,9 +131,10 @@ def map_type_restrictions(self) -> set[ExpressionTypes] | None: "function_calls", "identifiers", } - - def set_type_restrictions(self) -> set[ExpressionTypes] | None: - return self.map_type_restrictions() + return { + BasicObjectTypes.MAP: restrictions, # type: ignore + BasicSequenceTypes.SET: restrictions, # type: ignore + } def compilation(self, files: list[str]) -> CallbackResult: def file_filter(file: Path) -> bool: diff --git a/tested/languages/kotlin/templates/Values.kt b/tested/languages/kotlin/templates/Values.kt index aef14142..c1399c65 100644 --- a/tested/languages/kotlin/templates/Values.kt +++ b/tested/languages/kotlin/templates/Values.kt @@ -124,7 +124,7 @@ private fun internalEncode(value: Any?): Array { type = "set" data = encodeSequence(value.asIterable()) } else if (value is Map<*, *>) { - type = "map" + type = "dictionary" data = value.asSequence() .map { e -> String.format("{\"key\": %s, \"value\": %s }", encode(e.key), diff --git a/tested/languages/python/config.py b/tested/languages/python/config.py index 91138f0a..e28e99a1 100644 --- a/tested/languages/python/config.py +++ b/tested/languages/python/config.py @@ -4,7 +4,12 @@ from pathlib import Path from typing import TYPE_CHECKING -from tested.datatypes import AllTypes, ExpressionTypes +from tested.datatypes import ( + AllTypes, + BasicObjectTypes, + BasicSequenceTypes, + ExpressionTypes, +) from tested.dodona import AnnotateCode, Message, Severity from tested.features import Construct, TypeSupport from tested.languages.config import ( @@ -77,6 +82,8 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "sequence": "supported", "set": "supported", "map": "supported", + "dictionary": "supported", + "object": "reduced", "nothing": "supported", "undefined": "reduced", "null": "reduced", @@ -98,8 +105,8 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "tuple": "supported", } - def map_type_restrictions(self) -> set[ExpressionTypes] | None: - return { # type: ignore + def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: + restrictions = { "integer", "real", "text", @@ -110,9 +117,10 @@ def map_type_restrictions(self) -> set[ExpressionTypes] | None: "function_calls", "identifiers", } - - def set_type_restrictions(self) -> set[ExpressionTypes] | None: - return self.map_type_restrictions() + return { + BasicObjectTypes.MAP: restrictions, # type: ignore + BasicSequenceTypes.SET: restrictions, # type: ignore + } def compilation(self, files: list[str]) -> CallbackResult: result = [x.replace(".py", ".pyc") for x in files] diff --git a/tested/languages/python/templates/values.py b/tested/languages/python/templates/values.py index ea38a0ac..4d81270c 100644 --- a/tested/languages/python/templates/values.py +++ b/tested/languages/python/templates/values.py @@ -51,7 +51,7 @@ def encode(value): type_ = "set" data_ = [encode(x) for x in value] elif isinstance(value, dict): - type_ = "map" + type_ = "dictionary" data_ = [{"key": encode(k), "value": encode(v)} for k, v in value.items()] else: type_ = "unknown" diff --git a/tests/exercises/objects/evaluation/missing_key_types_js_dictionary.yaml b/tests/exercises/objects/evaluation/missing_key_types_js_dictionary.yaml new file mode 100644 index 00000000..d299c4ee --- /dev/null +++ b/tests/exercises/objects/evaluation/missing_key_types_js_dictionary.yaml @@ -0,0 +1,4 @@ +- tab: "Feedback" + testcases: + - expression: 'dictionary({5: "a"})' + return: {5: "a"} diff --git a/tests/exercises/objects/evaluation/missing_key_types_js_object.yaml b/tests/exercises/objects/evaluation/missing_key_types_js_object.yaml new file mode 100644 index 00000000..a8689719 --- /dev/null +++ b/tests/exercises/objects/evaluation/missing_key_types_js_object.yaml @@ -0,0 +1,4 @@ +- tab: "Feedback" + testcases: + - expression: 'object({5: "a"})' + return: "invalid" diff --git a/tests/test_dsl_expression.py b/tests/test_dsl_expression.py index 2a8b4b05..1d8d9c9b 100644 --- a/tests/test_dsl_expression.py +++ b/tests/test_dsl_expression.py @@ -5,11 +5,13 @@ from tested.datatypes import ( AdvancedNothingTypes, AdvancedNumericTypes, + AdvancedObjectTypes, AdvancedSequenceTypes, AdvancedStringTypes, BasicBooleanTypes, BasicNothingTypes, BasicNumericTypes, + BasicObjectTypes, BasicSequenceTypes, BasicStringTypes, ObjectTypes, @@ -229,7 +231,49 @@ def test_parse_value_adv_sequence(): def test_parse_value_dict(): parsed = parse_string('{"ignore": True, 5: 0}') - assert parsed.type == ObjectTypes.MAP + assert parsed.type == BasicObjectTypes.MAP + parsed: ObjectType + assert len(parsed.data) == 2 + key, value = parsed.data[0].key, parsed.data[0].value + assert isinstance(key, StringType) + assert key.type == BasicStringTypes.TEXT + assert key.data == "ignore" + assert isinstance(value, BooleanType) + assert value.type == BasicBooleanTypes.BOOLEAN + assert value.data is True + key, value = parsed.data[1].key, parsed.data[1].value + assert isinstance(key, NumberType) + assert key.type == BasicNumericTypes.INTEGER + assert key.data == 5 + assert isinstance(value, NumberType) + assert value.type == BasicNumericTypes.INTEGER + assert value.data == 0 + + +def test_parse_value_dictionary(): + parsed = parse_string('dictionary({"ignore": True, 5: 0})') + assert parsed.type == AdvancedObjectTypes.DICTIONARY + parsed: ObjectType + assert len(parsed.data) == 2 + key, value = parsed.data[0].key, parsed.data[0].value + assert isinstance(key, StringType) + assert key.type == BasicStringTypes.TEXT + assert key.data == "ignore" + assert isinstance(value, BooleanType) + assert value.type == BasicBooleanTypes.BOOLEAN + assert value.data is True + key, value = parsed.data[1].key, parsed.data[1].value + assert isinstance(key, NumberType) + assert key.type == BasicNumericTypes.INTEGER + assert key.data == 5 + assert isinstance(value, NumberType) + assert value.type == BasicNumericTypes.INTEGER + assert value.data == 0 + + +def test_parse_value_object(): + parsed = parse_string('object({"ignore": True, 5: 0})') + assert parsed.type == AdvancedObjectTypes.OBJECT parsed: ObjectType assert len(parsed.data) == 2 key, value = parsed.data[0].key, parsed.data[0].value @@ -375,7 +419,7 @@ def test_parse_function(): assert function.name == "generate" assert len(function.arguments) == 1 arg = function.arguments[0] - assert arg.type == ObjectTypes.MAP + assert arg.type == BasicObjectTypes.MAP arg: ObjectType assert len(arg.data) == 1 pair: ObjectKeyValuePair = arg.data[0] diff --git a/tests/test_functionality.py b/tests/test_functionality.py index 361fc67f..37bf1122 100644 --- a/tests/test_functionality.py +++ b/tests/test_functionality.py @@ -511,10 +511,9 @@ def test_heterogeneous_arguments_are_detected(lang: str, tmp_path: Path, pytestc assert updates.find_status_enum() == ["internal error"] -@pytest.mark.parametrize("lang", ["python", "javascript"]) -def test_missing_key_types_detected(lang: str, tmp_path: Path, pytestconfig): +def test_missing_key_types_detected(tmp_path: Path, pytestconfig): conf = configuration( - pytestconfig, "objects", lang, tmp_path, "missing_key_types.yaml", "solution" + pytestconfig, "objects", "python", tmp_path, "missing_key_types.yaml", "correct" ) result = execute_config(conf) updates = assert_valid_output(result, pytestconfig) @@ -522,6 +521,36 @@ def test_missing_key_types_detected(lang: str, tmp_path: Path, pytestconfig): assert updates.find_status_enum() == ["internal error"] +def test_missing_key_types_detected_js_object(tmp_path: Path, pytestconfig): + conf = configuration( + pytestconfig, + "objects", + "javascript", + tmp_path, + "missing_key_types_js_object.yaml", + "correct", + ) + result = execute_config(conf) + updates = assert_valid_output(result, pytestconfig) + assert len(updates.find_all("start-testcase")) == 0 + assert updates.find_status_enum() == ["internal error"] + + +@pytest.mark.parametrize( + "suite", ["missing_key_types_js_dictionary", "missing_key_types"] +) +def test_missing_key_types_detected_js_dictionary( + suite: str, tmp_path: Path, pytestconfig +): + conf = configuration( + pytestconfig, "objects", "javascript", tmp_path, f"{suite}.yaml", "correct" + ) + result = execute_config(conf) + updates = assert_valid_output(result, pytestconfig) + assert len(updates.find_all("start-testcase")) == 1 + assert updates.find_status_enum() == ["correct"] + + @pytest.mark.parametrize("lang", ["python", "java", "kotlin", "javascript", "csharp"]) def test_programmed_evaluator_lotto(lang: str, tmp_path: Path, pytestconfig): conf = configuration( diff --git a/tests/test_serialisation.py b/tests/test_serialisation.py index 8eee9b1c..e23b575a 100644 --- a/tests/test_serialisation.py +++ b/tests/test_serialisation.py @@ -32,6 +32,7 @@ BasicTypes, resolve_to_basic, ) +from tested.datatypes.advanced import AdvancedObjectTypes from tested.features import TypeSupport, fallback_type_support_map from tested.judge.compilation import run_compilation from tested.judge.execution import execute_file, filter_files @@ -160,9 +161,26 @@ StringType(type=BasicStringTypes.TEXT, data="data"), ], ), - # Char StringType(type=AdvancedStringTypes.CHAR, data="h"), NothingType(type=AdvancedNothingTypes.UNDEFINED), + ObjectType( + type=AdvancedObjectTypes.DICTIONARY, + data=[ + ObjectKeyValuePair( + key=StringType(type=BasicStringTypes.TEXT, data="data"), + value=NumberType(type=BasicNumericTypes.INTEGER, data=5), + ) + ], + ), + ObjectType( + type=AdvancedObjectTypes.OBJECT, + data=[ + ObjectKeyValuePair( + key=StringType(type=BasicStringTypes.TEXT, data="data"), + value=NumberType(type=BasicNumericTypes.INTEGER, data=5), + ) + ], + ), ]