diff --git a/tested/dsl/ast_translator.py b/tested/dsl/ast_translator.py index 0cdc0614..60e8dbbb 100644 --- a/tested/dsl/ast_translator.py +++ b/tested/dsl/ast_translator.py @@ -51,9 +51,11 @@ NumberType, ObjectKeyValuePair, ObjectType, + PropertyAssignment, SequenceType, Statement, Value, + VariableAssignment, VariableType, serialize_from_python, ) @@ -95,7 +97,15 @@ def _is_type_cast(node: ast.expr) -> bool: def _convert_ann_assignment(node: ast.AnnAssign) -> Assignment: if not isinstance(node.target, ast.Name): - raise InvalidDslError("You can only assign to simple variables") + actual = ast.dump(node) + raise InvalidDslError( + f""" + You can only assign to simple variables when using type hints. + You are trying to assign to a target of type {type(node.target)}. + The full assignment is: + {actual} + """ + ) assert node.value value = _convert_expression(node.value, False) if isinstance(node.annotation, ast.Name): @@ -110,7 +120,7 @@ def _convert_ann_assignment(node: ast.AnnAssign) -> Assignment: if not is_our_type: type_ = VariableType(data=type_) - return Assignment( + return VariableAssignment( variable=node.target.id, expression=value, type=cast(VariableType | AllTypes, type_), @@ -118,32 +128,38 @@ def _convert_ann_assignment(node: ast.AnnAssign) -> Assignment: def _convert_assignment(node: ast.Assign) -> Assignment: - # raise InvalidDslError("You need to annotate the variable with a type.") if n := len(node.targets) != 1: raise InvalidDslError( f"You must assign to exactly one variable, but got {n} variables." ) variable = node.targets[0] - if not isinstance(variable, ast.Name): - actual = ast.dump(node) - raise InvalidDslError(f"You can only assign to simple variables, got: {actual}") value = _convert_expression(node.value, False) - # Support a few obvious ones, such as constructor calls or literal values. - type_ = None - if isinstance(value, Value): - type_ = value.type - elif isinstance(value, FunctionCall) and value.type == FunctionType.CONSTRUCTOR: - type_ = VariableType(data=value.name) + if isinstance(variable, ast.Name): + # Support a few obvious ones, such as constructor calls or literal values. + type_ = None + if isinstance(value, Value): + type_ = value.type + elif isinstance(value, FunctionCall) and value.type == FunctionType.CONSTRUCTOR: + type_ = VariableType(data=value.name) + + if not type_: + raise InvalidDslError( + f"Could not deduce the type of variable {variable.id}: add a type annotation." + ) - if not type_: + assert isinstance(type_, AllTypes | VariableType) + return VariableAssignment(variable=variable.id, expression=value, type=type_) + elif isinstance(variable, ast.Attribute): + property_access = _convert_expression(variable, False) + assert isinstance(property_access, FunctionCall) + return PropertyAssignment(property=property_access, expression=value) + else: + actual = ast.dump(node) raise InvalidDslError( - f"Could not deduce the type of variable {variable.id}: add a type annotation." + f"You can only assign to simple variables or attributes, got: {actual}." ) - assert isinstance(type_, AllTypes | VariableType) - return Assignment(variable=variable.id, expression=value, type=type_) - def _convert_call(node: ast.Call) -> FunctionCall: # We consider function calls that start with a capital to be constructors. diff --git a/tested/languages/bash/generators.py b/tested/languages/bash/generators.py index b39314e0..4ff5d7fa 100644 --- a/tested/languages/bash/generators.py +++ b/tested/languages/bash/generators.py @@ -11,13 +11,13 @@ ) from tested.languages.utils import convert_unknown_type from tested.serialisation import ( - Assignment, FunctionCall, FunctionType, Identifier, Statement, StringType, Value, + VariableAssignment, ) from tested.testsuite import MainInput @@ -153,7 +153,7 @@ def convert_statement(statement: Statement) -> str: return convert_function_call(statement, index_fun, []) elif isinstance(statement, Value): return convert_value(statement) - elif isinstance(statement, Assignment): + elif isinstance(statement, VariableAssignment): result = f"local {statement.variable}=" if isinstance(statement.expression, FunctionCall): result += f"$({convert_statement(statement.expression)})" diff --git a/tested/languages/c/generators.py b/tested/languages/c/generators.py index 0aae333d..f2d23cf3 100644 --- a/tested/languages/c/generators.py +++ b/tested/languages/c/generators.py @@ -19,7 +19,6 @@ ) from tested.languages.utils import convert_unknown_type, is_special_void_call from tested.serialisation import ( - Assignment, Expression, FunctionCall, FunctionType, @@ -29,6 +28,7 @@ Statement, StringType, Value, + VariableAssignment, VariableType, as_basic_type, ) @@ -149,7 +149,7 @@ def convert_statement(statement: Statement, full=False) -> str: return convert_function_call(statement) elif isinstance(statement, Value): return convert_value(statement) - elif isinstance(statement, Assignment): + elif isinstance(statement, VariableAssignment): if full: prefix = convert_declaration(statement.type) + " " else: diff --git a/tested/languages/csharp/generators.py b/tested/languages/csharp/generators.py index c48b127b..7cd6b118 100644 --- a/tested/languages/csharp/generators.py +++ b/tested/languages/csharp/generators.py @@ -22,18 +22,19 @@ ) from tested.languages.utils import convert_unknown_type, is_special_void_call from tested.serialisation import ( - Assignment, Expression, FunctionCall, FunctionType, Identifier, NamedArgument, ObjectType, + PropertyAssignment, SequenceType, SpecialNumbers, Statement, StringType, Value, + VariableAssignment, VariableType, as_basic_type, ) @@ -245,13 +246,18 @@ def convert_statement(statement: Statement, full=False) -> str: return convert_function_call(statement) elif isinstance(statement, Value): return convert_value(statement) - elif isinstance(statement, Assignment): + elif isinstance(statement, PropertyAssignment): + return ( + f"{convert_statement(statement.property)} = " + f"{convert_statement(statement.expression)};" + ) + elif isinstance(statement, VariableAssignment): if full: prefix = convert_declaration(statement.type, statement.expression) else: prefix = "" return ( - f"{prefix}{statement.variable} = " + f"{prefix} {statement.variable} = " f"{convert_statement(statement.expression)}" ) raise AssertionError(f"Unknown statement: {statement!r}") @@ -269,7 +275,7 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) if ( not tc.testcase.is_main_testcase() and isinstance(tc.input, PreparedTestcaseStatement) - and isinstance(tc.input.statement, Assignment) + and isinstance(tc.input.statement, VariableAssignment) ): result += ( convert_declaration( diff --git a/tested/languages/generation.py b/tested/languages/generation.py index 442b3c1f..1f1c074d 100644 --- a/tested/languages/generation.py +++ b/tested/languages/generation.py @@ -30,7 +30,7 @@ prepare_expression, ) from tested.parsing import get_converter -from tested.serialisation import Assignment, Expression, Statement, VariableType +from tested.serialisation import Expression, Statement, VariableType from tested.testsuite import ( Context, FileUrl, @@ -39,6 +39,7 @@ Testcase, TextData, ) +from tested.utils import is_statement_strict if TYPE_CHECKING: from tested.judge.planning import PlannedExecutionUnit @@ -232,7 +233,7 @@ def generate_statement(bundle: Bundle, statement: Statement) -> str: if isinstance(statement, Expression): statement = prepare_expression(bundle, statement) else: - assert isinstance(statement, Assignment) + assert is_statement_strict(statement) statement = prepare_assignment(bundle, statement) return bundle.language.generate_statement(statement) diff --git a/tested/languages/haskell/generators.py b/tested/languages/haskell/generators.py index 56349323..ffcc68f5 100644 --- a/tested/languages/haskell/generators.py +++ b/tested/languages/haskell/generators.py @@ -20,7 +20,6 @@ ) from tested.languages.utils import convert_unknown_type from tested.serialisation import ( - Assignment, Expression, FunctionCall, Identifier, @@ -30,6 +29,7 @@ Statement, StringType, Value, + VariableAssignment, VariableType, as_basic_type, ) @@ -161,7 +161,7 @@ def convert_statement(statement: Statement, lifting=False) -> str: result += ")" return result else: - assert isinstance(statement, Assignment) + assert isinstance(statement, VariableAssignment) return f"let {statement.variable} = {convert_statement(statement.expression)}" diff --git a/tested/languages/java/generators.py b/tested/languages/java/generators.py index acab03a2..fcf8a24b 100644 --- a/tested/languages/java/generators.py +++ b/tested/languages/java/generators.py @@ -23,17 +23,18 @@ ) from tested.languages.utils import convert_unknown_type, is_special_void_call from tested.serialisation import ( - Assignment, Expression, FunctionCall, FunctionType, Identifier, ObjectType, + PropertyAssignment, SequenceType, SpecialNumbers, Statement, StringType, Value, + VariableAssignment, VariableType, as_basic_type, ) @@ -239,7 +240,12 @@ def convert_statement(statement: Statement, full=False) -> str: return convert_function_call(statement) elif isinstance(statement, Value): return convert_value(statement) - elif isinstance(statement, Assignment): + elif isinstance(statement, PropertyAssignment): + return ( + f"{convert_statement(statement.property)} = " + f"{convert_statement(statement.expression)}" + ) + elif isinstance(statement, VariableAssignment): if full: prefix = convert_declaration(statement.type, statement.expression) + " " else: @@ -259,12 +265,12 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) for tc in ctx.testcases: result += "this.writeSeparator();\n" - # In Java, we need special code to make variables available outside of + # In Java, we need special code to make variables available outside # the try-catch block. if ( not tc.testcase.is_main_testcase() and isinstance(tc.input, PreparedTestcaseStatement) - and isinstance(tc.input.statement, Assignment) + and isinstance(tc.input.statement, VariableAssignment) ): result += ( convert_declaration( diff --git a/tested/languages/javascript/generators.py b/tested/languages/javascript/generators.py index b1d53cf6..8693ac1d 100644 --- a/tested/languages/javascript/generators.py +++ b/tested/languages/javascript/generators.py @@ -21,17 +21,18 @@ ) from tested.languages.utils import convert_unknown_type from tested.serialisation import ( - Assignment, Expression, FunctionCall, FunctionType, Identifier, ObjectType, + PropertyAssignment, SequenceType, SpecialNumbers, Statement, StringType, Value, + VariableAssignment, as_basic_type, ) from tested.testsuite import MainInput @@ -129,7 +130,12 @@ def convert_statement(statement: Statement, internal=False, full=False) -> str: return convert_function_call(statement, internal) elif isinstance(statement, Value): return convert_value(statement) - elif isinstance(statement, Assignment): + elif isinstance(statement, PropertyAssignment): + return ( + f"{convert_statement(statement.property, True)} = " + f"{convert_statement(statement.expression, True)}" + ) + elif isinstance(statement, VariableAssignment): if full: prefix = "let " else: @@ -172,7 +178,7 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) if ( not tc.testcase.is_main_testcase() and isinstance(tc.input, PreparedTestcaseStatement) - and isinstance(tc.input.statement, Assignment) + and isinstance(tc.input.statement, VariableAssignment) ): result += f"let {tc.input.statement.variable}\n" diff --git a/tested/languages/kotlin/generators.py b/tested/languages/kotlin/generators.py index fcfdb5c4..7604d297 100644 --- a/tested/languages/kotlin/generators.py +++ b/tested/languages/kotlin/generators.py @@ -30,11 +30,13 @@ Identifier, NamedArgument, ObjectType, + PropertyAssignment, SequenceType, SpecialNumbers, Statement, StringType, Value, + VariableAssignment, VariableType, as_basic_type, ) @@ -244,7 +246,12 @@ def convert_statement(statement: Statement, full=False) -> str: return convert_function_call(statement) elif isinstance(statement, Value): return convert_value(statement) - elif isinstance(statement, Assignment): + elif isinstance(statement, PropertyAssignment): + return ( + f"{convert_statement(statement.property)} = " + f"{convert_statement(statement.expression)};" + ) + elif isinstance(statement, VariableAssignment): prefix = "var " if full else "" return ( f"{prefix}{statement.variable} = " @@ -320,7 +327,7 @@ class {pu.unit.name}: AutoCloseable {{ if ( not tc.testcase.is_main_testcase() and isinstance(tc.input, PreparedTestcaseStatement) - and isinstance(tc.input.statement, Assignment) + and isinstance(tc.input.statement, VariableAssignment) ): decl = convert_declaration( tc.input.statement.type, tc.input.statement.expression diff --git a/tested/languages/preparation.py b/tested/languages/preparation.py index d21f00bd..a29530c2 100644 --- a/tested/languages/preparation.py +++ b/tested/languages/preparation.py @@ -30,8 +30,10 @@ NothingType, ObjectKeyValuePair, ObjectType, + PropertyAssignment, SequenceType, Statement, + VariableAssignment, VariableType, ) from tested.testsuite import ( @@ -200,13 +202,22 @@ def prepare_argument( def prepare_assignment(bundle: Bundle, assignment: Assignment) -> Assignment: - if isinstance(assignment.type, VariableType): - class_type = conventionalize_class(bundle.language, assignment.type.data) - assignment = assignment.replace_type(VariableType(data=class_type)) + if isinstance(assignment, VariableAssignment): + if isinstance(assignment.type, VariableType): + class_type = conventionalize_class(bundle.language, assignment.type.data) + assignment = assignment.replace_type(VariableType(data=class_type)) - assignment = assignment.replace_variable( - conventionalize_identifier(bundle.language, assignment.variable) - ) + assignment = assignment.replace_variable( + conventionalize_identifier(bundle.language, assignment.variable) + ) + elif isinstance(assignment, PropertyAssignment): + prepared = prepare_expression(bundle, assignment.property) + assert isinstance(prepared, FunctionCall) + assignment = assignment.replace_property(prepared) + else: + raise AssertionError( + f"Unknown assignment class {type(assignment)} for {assignment}" + ) prepared = prepare_expression(bundle, assignment.expression) return assignment.replace_expression(prepared) diff --git a/tested/languages/python/generators.py b/tested/languages/python/generators.py index 277c8057..73afabb8 100644 --- a/tested/languages/python/generators.py +++ b/tested/languages/python/generators.py @@ -26,11 +26,13 @@ Identifier, NamedArgument, ObjectType, + PropertyAssignment, SequenceType, SpecialNumbers, Statement, StringType, Value, + VariableAssignment, as_basic_type, ) from tested.testsuite import MainInput @@ -127,7 +129,12 @@ def convert_statement(statement: Statement, with_namespace=False) -> str: return convert_function_call(statement, with_namespace) elif isinstance(statement, Value): return convert_value(statement) - elif isinstance(statement, Assignment): + elif isinstance(statement, PropertyAssignment): + return ( + f"{convert_statement(statement.property)} = " + f"{convert_statement(statement.expression, with_namespace)}" + ) + elif isinstance(statement, VariableAssignment): return ( f"{statement.variable} = " f"{convert_statement(statement.expression, with_namespace)}" diff --git a/tested/serialisation.py b/tested/serialisation.py index 30ca56ea..e3478e73 100644 --- a/tested/serialisation.py +++ b/tested/serialisation.py @@ -26,7 +26,7 @@ from enum import StrEnum, auto, unique from functools import reduce, total_ordering from types import NoneType -from typing import Any, Literal, Optional, Union, cast +from typing import Any, Literal, Optional, Self, Union, cast, override from attrs import define, field, resolve_types, validators @@ -451,38 +451,95 @@ class VariableType: @define -class Assignment(WithFeatures, WithFunctions): +class AbstractAssignment(WithFeatures, WithFunctions): """ - Assigns the return value of a function to a variable. Because the expression - part is pretty simple, the type of the value is determined by looking at the - expression. It is also possible to define the type. If the type cannot be - determined and it is not specified, this is an error. + Assign the result of an expression to a variable or property. + + When assigning to a variable, TESTed will attempt to deduce the type of the + variable by looking at the expression. For example, if the expression is a + constructor call, the type of the variable is trivially determinable. + + However, the type deduction is not smart: anything more complex needs an explicit + type on the variable, or an error will be thrown. """ - variable: str expression: Expression - type: AllTypes | VariableType - def replace_expression(self, expression: Expression) -> "Assignment": - return Assignment(variable=self.variable, expression=expression, type=self.type) + def get_used_features(self) -> FeatureSet: + base = FeatureSet({Construct.ASSIGNMENTS}, set(), set()) + other = self.expression.get_used_features() + + return combine_features([base, other]) + + def get_functions(self) -> Iterable["FunctionCall"]: + return self.expression.get_functions() + + def replace_expression(self, expression: Expression) -> Self: + raise NotImplementedError() + + +@define +class VariableAssignment(AbstractAssignment): + """ + Assign the result of an expression to a variable. + + When assigning to a variable in the DSL, TESTed will attempt to deduce the type + of the variable by looking at the expression. For example, if the expression is a + constructor call, the type of the variable is trivially determinable. + + However, the type deduction is not smart: anything more complex needs an explicit + type on the variable, or an error will be thrown. + """ + + variable: str + type: AllTypes | VariableType - def replace_variable(self, variable: str) -> "Assignment": - return Assignment(variable=variable, expression=self.expression, type=self.type) + def replace_variable(self, variable: str) -> "VariableAssignment": + return VariableAssignment( + variable=variable, expression=self.expression, type=self.type + ) - def replace_type(self, type_name: AllTypes | VariableType) -> "Assignment": - return Assignment( + def replace_type(self, type_name: AllTypes | VariableType) -> "VariableAssignment": + return VariableAssignment( variable=self.variable, expression=self.expression, type=type_name ) + def replace_expression(self, expression: Expression) -> "VariableAssignment": + return VariableAssignment( + variable=self.variable, expression=expression, type=self.type + ) + + +@define +class PropertyAssignment(AbstractAssignment): + """ + Assign the result of an expression to a property. + """ + + property: FunctionCall = field() + + @property.validator # type: ignore + def check(self, attribute, value: FunctionCall): + if not value.type == FunctionType.PROPERTY: + raise ValueError( + f"Assigning to a property requires a property to assign to, got {value} instead." + ) + + @override def get_used_features(self) -> FeatureSet: - base = FeatureSet({Construct.ASSIGNMENTS}, set(), set()) + base = FeatureSet({Construct.ASSIGNMENTS, Construct.OBJECTS}, set(), set()) other = self.expression.get_used_features() return combine_features([base, other]) - def get_functions(self) -> Iterable["FunctionCall"]: - return self.expression.get_functions() + def replace_expression(self, expression: Expression) -> "PropertyAssignment": + return PropertyAssignment(property=self.property, expression=expression) + + def replace_property(self, prop: FunctionCall) -> "PropertyAssignment": + return PropertyAssignment(property=prop, expression=self.expression) + +Assignment = VariableAssignment | PropertyAssignment # If changing this, also update is_statement_strict in the utils. Statement = Assignment | Expression diff --git a/tests/exercises/objects/evaluation/property_assignment.yaml b/tests/exercises/objects/evaluation/property_assignment.yaml new file mode 100644 index 00000000..303d09ab --- /dev/null +++ b/tests/exercises/objects/evaluation/property_assignment.yaml @@ -0,0 +1,7 @@ +- tab: "Feedback" + contexts: + - testcases: + - statement: 'instance = Equal_checker(10)' + - statement: 'instance.prop = 5' + - expression: 'instance.prop' + return: 5 diff --git a/tests/exercises/objects/solution/correct.cs b/tests/exercises/objects/solution/correct.cs index 1e18b5e8..ddf7f1e6 100644 --- a/tests/exercises/objects/solution/correct.cs +++ b/tests/exercises/objects/solution/correct.cs @@ -1,6 +1,7 @@ public class EqualChecker { private readonly int number; + public int Prop = 0; public EqualChecker(int number) { this.number = number; diff --git a/tests/exercises/objects/solution/correct.java b/tests/exercises/objects/solution/correct.java index 1975dc99..f7de6c78 100644 --- a/tests/exercises/objects/solution/correct.java +++ b/tests/exercises/objects/solution/correct.java @@ -3,6 +3,7 @@ class EqualChecker { private final int number; + public int prop = 0; EqualChecker(int number) { this.number = number; diff --git a/tests/exercises/objects/solution/correct.kt b/tests/exercises/objects/solution/correct.kt index 11b1dd87..2ef9d95a 100644 --- a/tests/exercises/objects/solution/correct.kt +++ b/tests/exercises/objects/solution/correct.kt @@ -1,5 +1,8 @@ class EqualChecker(private val value: Any?) { + + var prop: Int = 0; + fun check(other: Any?): Boolean { return other == value } -} \ No newline at end of file +} diff --git a/tests/test_dsl_expression.py b/tests/test_dsl_expression.py index 1d8d9c9b..2f8e89a3 100644 --- a/tests/test_dsl_expression.py +++ b/tests/test_dsl_expression.py @@ -14,11 +14,9 @@ BasicObjectTypes, BasicSequenceTypes, BasicStringTypes, - ObjectTypes, ) from tested.dsl.ast_translator import InvalidDslError, parse_string from tested.serialisation import ( - Assignment, BooleanType, FunctionCall, FunctionType, @@ -26,8 +24,10 @@ NumberType, ObjectKeyValuePair, ObjectType, + PropertyAssignment, SequenceType, StringType, + VariableAssignment, VariableType, ) @@ -324,7 +324,7 @@ def test_parse_error_fun_assign(): def test_parse_fun_assign(): assign = parse_string("data: integer = first([object.gen_int()])") - assert isinstance(assign, Assignment) + assert isinstance(assign, VariableAssignment) assert assign.type == BasicNumericTypes.INTEGER assert assign.variable == "data" expr = assign.expression @@ -346,7 +346,7 @@ def test_parse_fun_assign(): def test_parse_constructor_assign(): assign = parse_string("cont: Container = Container({object.version})") - assert isinstance(assign, Assignment) + assert isinstance(assign, VariableAssignment) assert isinstance(assign.type, VariableType) assert assign.type.data == "Container" assert assign.variable == "cont" @@ -369,7 +369,7 @@ def test_parse_constructor_assign(): def test_parse_constructor_assign2(): assign = parse_string("cont = Container({object.version})") - assert isinstance(assign, Assignment) + assert isinstance(assign, VariableAssignment) assert isinstance(assign.type, VariableType) assert assign.type.data == "Container" assert assign.variable == "cont" @@ -392,7 +392,7 @@ def test_parse_constructor_assign2(): def test_parse_value_assign(): assign = parse_string("lijst: list = list([Container(5, true)])") - assert isinstance(assign, Assignment) + assert isinstance(assign, VariableAssignment) assert assign.type == AdvancedSequenceTypes.LIST assert assign.variable == "lijst" expr = assign.expression @@ -411,6 +411,34 @@ def test_parse_value_assign(): assert elem.arguments[1] == "true" +def test_parse_attribute_assign(): + assign = parse_string("the_object.data = first([object.gen_int()])") + assert isinstance(assign, PropertyAssignment) + assert assign.property.type == FunctionType.PROPERTY + assert assign.property.name == "data" + assert assign.property.namespace == "the_object" + expr = assign.expression + assert isinstance(expr, FunctionCall) + assert expr.namespace is None + assert expr.name == "first" + assert expr.type == FunctionType.FUNCTION + assert len(expr.arguments) == 1 + arg = expr.arguments[0] + assert arg.type == BasicSequenceTypes.SEQUENCE + assert len(arg.data) == 1 + data = arg.data[0] + assert isinstance(data, FunctionCall) + assert data.type == FunctionType.FUNCTION + assert data.namespace == "object" + assert data.name == "gen_int" + assert len(data.arguments) == 0 + + +def test_parse_attribute_assign_hinted(): + with pytest.raises(InvalidDslError): + parse_string("the_object.data: Test = first([object.gen_int()])") + + def test_parse_function(): function = parse_string('generate({"size": get_size()})') assert isinstance(function, FunctionCall) diff --git a/tests/test_dsl_yaml.py b/tests/test_dsl_yaml.py index b6f2e1a3..7e3137bb 100644 --- a/tests/test_dsl_yaml.py +++ b/tests/test_dsl_yaml.py @@ -19,12 +19,12 @@ ) from tested.dsl import parse_dsl, translate_to_test_suite from tested.serialisation import ( - Assignment, FunctionCall, NumberType, ObjectType, SequenceType, StringType, + VariableAssignment, ) from tested.testsuite import ( CustomCheckOracle, @@ -281,14 +281,14 @@ def test_statements(): tests0, tests1 = ctx0.testcases, ctx1.testcases assert len(tests0) == 2 - assert isinstance(tests0[0].input, Assignment) + assert isinstance(tests0[0].input, VariableAssignment) assert tests0[0].output.stdout.data == "New safe\n" assert tests0[0].output.stdout.oracle.options["ignoreWhitespace"] assert isinstance(tests0[1].input, FunctionCall) assert tests0[1].output.result.value.data == "Ignore whitespace" assert len(tests1) == 2 - assert isinstance(tests1[0].input, Assignment) + assert isinstance(tests1[0].input, VariableAssignment) assert tests1[0].output.stdout.data == "New safe\n" assert not tests1[0].output.stdout.oracle.options["ignoreWhitespace"] assert isinstance(tests1[1].input, FunctionCall) diff --git a/tests/test_functionality.py b/tests/test_functionality.py index 29a59b04..b7b03461 100644 --- a/tests/test_functionality.py +++ b/tests/test_functionality.py @@ -695,6 +695,24 @@ def test_objects_chained(language: str, tmp_path: Path, pytestconfig): assert len(updates.find_all("start-testcase")) == 3 +@pytest.mark.parametrize( + "language", ["python", "java", "kotlin", "javascript", "csharp"] +) +def test_property_assignment(language: str, tmp_path: Path, pytestconfig): + conf = configuration( + pytestconfig, + "objects", + language, + tmp_path, + "property_assignment.yaml", + "correct", + ) + result = execute_config(conf) + updates = assert_valid_output(result, pytestconfig) + assert updates.find_status_enum() == ["correct"] * 1 + assert len(updates.find_all("start-testcase")) == 3 + + @pytest.mark.parametrize( "language", ["python", "java", "kotlin", "javascript", "csharp"] )