From 559fc82de65c87b94062aed245d19c022adc08f2 Mon Sep 17 00:00:00 2001 From: Wouter De Borger Date: Tue, 16 Oct 2018 11:22:49 +0200 Subject: [PATCH] Issue/747 entity origin (#752) * Track all locations where an instance has been created. (fixes #747) --- .gitignore | 2 +- src/inmanta/ast/entity.py | 2 +- src/inmanta/ast/statements/__init__.py | 2 +- src/inmanta/ast/statements/generator.py | 3 +- src/inmanta/execute/runtime.py | 13 +++++- tests/test_compilation.py | 24 ++++++++++ tests/test_compiler_errors.py | 60 +++++++++++++++++++++++++ 7 files changed, 99 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 23fa43f0b3..d4163725b2 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,4 @@ docs/moduleguides .mypy .mypy_cache mypy -types \ No newline at end of file +types diff --git a/src/inmanta/ast/entity.py b/src/inmanta/ast/entity.py index 9b047c7028..a5e6f17215 100644 --- a/src/inmanta/ast/entity.py +++ b/src/inmanta/ast/entity.py @@ -319,7 +319,7 @@ def get_instance(self, Return an instance of the class defined in this entity """ out = Instance(self, resolver, queue) - out.location = location + out.set_location(location) for k, v in attributes.items(): out.set_attribute(k, v, location) diff --git a/src/inmanta/ast/statements/__init__.py b/src/inmanta/ast/statements/__init__.py index 208d798ed8..fa445818b1 100644 --- a/src/inmanta/ast/statements/__init__.py +++ b/src/inmanta/ast/statements/__init__.py @@ -44,7 +44,7 @@ def copy_location(self, statement: Locatable) -> None: """ Copy the location of this statement in the given statement """ - statement.location = self.location + statement.set_location(self.location) def get_namespace(self) -> "Namespace": return self.namespace diff --git a/src/inmanta/ast/statements/generator.py b/src/inmanta/ast/statements/generator.py index d009933c9a..048f866059 100644 --- a/src/inmanta/ast/statements/generator.py +++ b/src/inmanta/ast/statements/generator.py @@ -290,13 +290,12 @@ def execute(self, requires: Dict[object, ResultVariable], resolver: Resolver, qu raise Exception("Inconsistent indexes detected!") object_instance = first + self.copy_location(object_instance) for k, v in attributes.items(): object_instance.set_attribute(k, v, self.location) - else: # create the instance object_instance = type_class.get_instance(attributes, resolver, queue, self.location) - self.copy_location(object_instance) # deferred execution for indirect attributes for attributename, valueexpression in self._indirect_attributes.items(): diff --git a/src/inmanta/execute/runtime.py b/src/inmanta/execute/runtime.py index 9e12010d3b..c13d0ce323 100644 --- a/src/inmanta/execute/runtime.py +++ b/src/inmanta/execute/runtime.py @@ -21,7 +21,7 @@ from inmanta.ast import RuntimeException, NotFoundException, DoubleSetException, OptionalValueException, AttributeException, \ Locatable, Location from inmanta.ast.type import Type -from typing import Dict, Any +from typing import List, Dict, Any class ResultCollector(object): @@ -636,6 +636,8 @@ def __init__(self, mytype, resolver, queue): # see inmanta.ast.execute.scheduler.QueueScheduler self.trackers = [] + self.locations = [] + def get_type(self): return self.type @@ -657,7 +659,7 @@ def __repr__(self): return "%s %02x" % (self.type, self.sid) def __str__(self): - return "%s (instantiated at %s)" % (self.type, self.location) + return "%s (instantiated at %s)" % (self.type, ",".join([str(l) for l in self.get_locations()])) def add_implementation(self, impl): if impl in self.implemenations: @@ -709,5 +711,12 @@ def verify_done(self): return False return True + def set_location(self, location: Location): + Locatable.set_location(self, location) + self.locations.append(location) + def get_location(self) -> Location: return self.location + + def get_locations(self) -> List[Location]: + return self.locations diff --git a/tests/test_compilation.py b/tests/test_compilation.py index 1e0ceb4797..af4a325f62 100644 --- a/tests/test_compilation.py +++ b/tests/test_compilation.py @@ -2682,6 +2682,30 @@ def test_lazy_attibutes3(snippetcompiler): assert "5" == root.lookup("a").get_value().lookup("value").get_value().lookup("value").get_value() +def test_747_entity_multi_location(snippetcompiler): + snippetcompiler.setup_for_snippet(""" +entity Alpha: + string name +end + +implementation none for Alpha: +end +implement Alpha using none + +index Alpha(name) + +a= Alpha(name="A") +b= Alpha(name="A") +c= Alpha(name="A") +""", autostd=False) + (_, scopes) = compiler.do_compile() + + root = scopes.get_child("__config__") + a = root.lookup("a").get_value() + assert len(a.get_locations()) == 3 + assert sorted([l.lnr for l in a.get_locations()]) == [12, 13, 14] + + def test_749_is_unknown(snippetcompiler): snippetcompiler.setup_for_snippet(""" import tests diff --git a/tests/test_compiler_errors.py b/tests/test_compiler_errors.py index 6f6df46095..afe978af96 100644 --- a/tests/test_compiler_errors.py +++ b/tests/test_compiler_errors.py @@ -440,3 +440,63 @@ def test_672_missing_type(snippetcompiler): """, "could not find type Testt in namespace __config__" " (reported in Implementation(test) ({dir}/main.cf:5))") + + +def test_747_index_collisions(snippetcompiler): + snippetcompiler.setup_for_error( + """ + entity Test: + string name + string value + end + + implementation none for Test: + end + + implement Test using none + + index Test(name) + Test(name="A", value="a") + Test(name="A", value="b") + + """, + """Could not set attribute `value` on instance `__config__::Test (instantiated at {dir}/main.cf:13,{dir}/main.cf:14)` (reported in Construct(Test) ({dir}/main.cf:14)) +caused by: + value set twice: +\told value: a +\t\tset at {dir}/main.cf:13 +\tnew value: b +\t\tset at {dir}/main.cf:14 + (reported in Construct(Test) ({dir}/main.cf:14))""" # nopep8 + ) + + +def test_747_index_collisions_invisible(snippetcompiler): + snippetcompiler.setup_for_error( + """ + entity Test: + string name + string value + end + + implementation none for Test: + end + + implement Test using none + + index Test(name) + + for v in ["a","b"]: + Test(name="A", value=v) + end + + """, + """Could not set attribute `value` on instance `__config__::Test (instantiated at {dir}/main.cf:15,{dir}/main.cf:15)` (reported in Construct(Test) ({dir}/main.cf:15)) +caused by: + value set twice: +\told value: a +\t\tset at {dir}/main.cf:15:34 +\tnew value: b +\t\tset at {dir}/main.cf:15:34 + (reported in Construct(Test) ({dir}/main.cf:15))""" # nopep8 + )