diff --git a/CHANGELOG.md b/CHANGELOG.md index 98eba066cf..a59c1732cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ v 2019.2 Changes in this release: -- various bugfixes (#1046, #968) +- various bugfixes (#1046, #968, #1045) - Migration from mongodb to postgres - added metering using pyformance - added influxdb reporter for protocol endpoint metrics diff --git a/src/inmanta/app.py b/src/inmanta/app.py index be7f63efd3..b352005467 100755 --- a/src/inmanta/app.py +++ b/src/inmanta/app.py @@ -358,11 +358,7 @@ def export(options): version, _ = export.run(types, scopes, metadata=metadata, model_export=options.model_export) if exp is not None: - if not options.errors: - print(exp, file=sys.stderr) - sys.exit(1) - else: - raise exp + raise exp if options.model: modelexporter = ModelExporter(types) diff --git a/src/inmanta/ast/type.py b/src/inmanta/ast/type.py index 6f8ccf7e5d..eddafae3b8 100644 --- a/src/inmanta/ast/type.py +++ b/src/inmanta/ast/type.py @@ -182,7 +182,7 @@ def validate(cls, value): return True if not isinstance(value, numbers.Number): - raise RuntimeException(None, "Invalid value '%s'expected Number" % value) + raise RuntimeException(None, "Invalid value '%s', expected Number" % value) return True # allow this function to be called from a lambda function diff --git a/tests/conftest.py b/tests/conftest.py index 836ef34979..c608bafcb2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -497,6 +497,11 @@ def keep(self): return {"env": self.env, "libs": self.libs, "project": self.project_dir} def setup_for_snippet(self, snippet, autostd=True): + self.setup_for_snippet_external(snippet) + + Project.set(Project(self.project_dir, autostd=autostd)) + + def setup_for_snippet_external(self, snippet): with open(os.path.join(self.project_dir, "project.yml"), "w") as cfg: cfg.write( """ @@ -506,15 +511,12 @@ def setup_for_snippet(self, snippet, autostd=True): version: 1.0 repo: ['https://github.com/inmanta/']""" % (self.libs, - os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "modules"), - self.libs)) - + os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "modules"), + self.libs)) self.main = os.path.join(self.project_dir, "main.cf") with open(self.main, "w") as x: x.write(snippet) - Project.set(Project(self.project_dir, autostd=autostd)) - def do_export(self, include_status=False, do_raise=True): return self._do_export(deploy=False, include_status=include_status, do_raise=do_raise) diff --git a/tests/test_app.py b/tests/test_app.py index b1a57a81e9..819051058b 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -28,7 +28,9 @@ from subprocess import TimeoutExpired -def get_command(tmp_dir, stdout_log_level=None, log_file=None, log_level_log_file=None, timed=False): +def get_command( + tmp_dir, stdout_log_level=None, log_file=None, log_level_log_file=None, timed=False +): root_dir = tmp_dir.mkdir("root").strpath log_dir = os.path.join(root_dir, "log") state_dir = os.path.join(root_dir, "data") @@ -38,7 +40,7 @@ def get_command(tmp_dir, stdout_log_level=None, log_file=None, log_level_log_fil port = conftest.get_free_tcp_port() - with open(config_file, 'w+') as f: + with open(config_file, "w+") as f: f.write("[config]\n") f.write("log-dir=" + log_dir + "\n") f.write("state-dir=" + state_dir + "\n") @@ -59,10 +61,12 @@ def get_command(tmp_dir, stdout_log_level=None, log_file=None, log_level_log_fil return (args, log_dir) -def do_run(args, env={}): +def do_run(args, env={}, cwd=None): baseenv = os.environ.copy() baseenv.update(env) - process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=baseenv) + process = subprocess.Popen( + args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=baseenv + ) return process @@ -71,8 +75,15 @@ def convert_to_ascii(text): def do_kill(process, killtime=3, termtime=2): - t1 = Timer(killtime, process.kill) - t2 = Timer(termtime, process.terminate) + def do_and_log(func, msg): + def w(): + print(msg) + func() + + return w + + t1 = Timer(killtime, do_and_log(process.kill, "killed process")) + t2 = Timer(termtime, do_and_log(process.terminate, "terminated process")) t1.start() t2.start() @@ -100,14 +111,14 @@ def run_with_tty(args): def get_timestamp_regex(): - return r'[\d]{4}\-[\d]{2}\-[\d]{2} [\d]{2}\:[\d]{2}\:[\d]{2}\,[\d]{3}' + return r"[\d]{4}\-[\d]{2}\-[\d]{2} [\d]{2}\:[\d]{2}\:[\d]{2}\,[\d]{3}" def get_compiled_regexes(regexes, timed): result = [] for regex in regexes: if timed: - regex = get_timestamp_regex() + ' ' + regex + regex = get_timestamp_regex() + " " + regex compiled_regex = re.compile(regex) result.append(compiled_regex) return result @@ -129,26 +140,91 @@ def test_verify_that_colorama_package_is_not_present(): assert not is_colorama_package_available() -@pytest.mark.parametrize("log_level, timed, with_tty, regexes_required_lines, regexes_forbidden_lines", [ - (3, False, False, [r'[a-z.]*[ ]*INFO[\s]+Starting server endpoint', - r'[a-z.]*[ ]*DEBUG[\s]+Starting Server Rest Endpoint'], []), - (2, False, False, [r'[a-z.]*[ ]*INFO[\s]+Starting server endpoint'], - [r'[a-z.]*[ ]*DEBUG[\s]+Starting Server Rest Endpoint']), - (3, False, True, [r'\x1b\[32m[a-z.]*[ ]*INFO[\s]*\x1b\[0m \x1b\[34mStarting server endpoint', - r'\x1b\[36m[a-z.]*[ ]*DEBUG[\s]*\x1b\[0m \x1b\[34mStarting Server Rest Endpoint'], []), - (2, False, True, [r'\x1b\[32m[a-z.]*[ ]*INFO[\s]*\x1b\[0m \x1b\[34mStarting server endpoint'], - [r'\x1b\[36m[a-z.]*[ ]*DEBUG[\s]*\x1b\[0m \x1b\[34mStarting Server Rest Endpoint']), - (3, True, False, [r'[a-z.]*[ ]*INFO[\s]+Starting server endpoint', - r'[a-z.]*[ ]*DEBUG[\s]+Starting Server Rest Endpoint'], []), - (2, True, False, [r'[a-z.]*[ ]*INFO[\s]+Starting server endpoint'], - [r'[a-z.]*[ ]*DEBUG[\s]+Starting Server Rest Endpoint']), - (3, True, True, [r'\x1b\[32m[a-z.]*[ ]*INFO[\s]*\x1b\[0m \x1b\[34mStarting server endpoint', - r'\x1b\[36m[a-z.]*[ ]*DEBUG[\s]*\x1b\[0m \x1b\[34mStarting Server Rest Endpoint'], []), - (2, True, True, [r'\x1b\[32m[a-z.]*[ ]*INFO[\s]*\x1b\[0m \x1b\[34mStarting server endpoint'], - [r'\x1b\[36m[a-z.]*[ ]*DEBUG[\s]*\x1b\[0m \x1b\[34mStarting Server Rest Endpoint']) -]) +@pytest.mark.parametrize( + "log_level, timed, with_tty, regexes_required_lines, regexes_forbidden_lines", + [ + ( + 3, + False, + False, + [ + r"[a-z.]*[ ]*INFO[\s]+Starting server endpoint", + r"[a-z.]*[ ]*DEBUG[\s]+Starting Server Rest Endpoint", + ], + [], + ), + ( + 2, + False, + False, + [r"[a-z.]*[ ]*INFO[\s]+Starting server endpoint"], + [r"[a-z.]*[ ]*DEBUG[\s]+Starting Server Rest Endpoint"], + ), + ( + 3, + False, + True, + [ + r"\x1b\[32m[a-z.]*[ ]*INFO[\s]*\x1b\[0m \x1b\[34mStarting server endpoint", + r"\x1b\[36m[a-z.]*[ ]*DEBUG[\s]*\x1b\[0m \x1b\[34mStarting Server Rest Endpoint", + ], + [], + ), + ( + 2, + False, + True, + [ + r"\x1b\[32m[a-z.]*[ ]*INFO[\s]*\x1b\[0m \x1b\[34mStarting server endpoint" + ], + [ + r"\x1b\[36m[a-z.]*[ ]*DEBUG[\s]*\x1b\[0m \x1b\[34mStarting Server Rest Endpoint" + ], + ), + ( + 3, + True, + False, + [ + r"[a-z.]*[ ]*INFO[\s]+Starting server endpoint", + r"[a-z.]*[ ]*DEBUG[\s]+Starting Server Rest Endpoint", + ], + [], + ), + ( + 2, + True, + False, + [r"[a-z.]*[ ]*INFO[\s]+Starting server endpoint"], + [r"[a-z.]*[ ]*DEBUG[\s]+Starting Server Rest Endpoint"], + ), + ( + 3, + True, + True, + [ + r"\x1b\[32m[a-z.]*[ ]*INFO[\s]*\x1b\[0m \x1b\[34mStarting server endpoint", + r"\x1b\[36m[a-z.]*[ ]*DEBUG[\s]*\x1b\[0m \x1b\[34mStarting Server Rest Endpoint", + ], + [], + ), + ( + 2, + True, + True, + [ + r"\x1b\[32m[a-z.]*[ ]*INFO[\s]*\x1b\[0m \x1b\[34mStarting server endpoint" + ], + [ + r"\x1b\[36m[a-z.]*[ ]*DEBUG[\s]*\x1b\[0m \x1b\[34mStarting Server Rest Endpoint" + ], + ), + ], +) @pytest.mark.timeout(20) -def test_no_log_file_set(tmpdir, log_level, timed, with_tty, regexes_required_lines, regexes_forbidden_lines): +def test_no_log_file_set( + tmpdir, log_level, timed, with_tty, regexes_required_lines, regexes_forbidden_lines +): if is_colorama_package_available() and with_tty: pytest.skip("Colorama is present") @@ -163,30 +239,62 @@ def test_no_log_file_set(tmpdir, log_level, timed, with_tty, regexes_required_li check_logs(stdout, regexes_required_lines, regexes_forbidden_lines, timed) -@pytest.mark.parametrize("log_level, with_tty, regexes_required_lines, regexes_forbidden_lines", [ - (3, False, [r'[a-z.]*[ ]*INFO[\s]+[a-x\.A-Z]*[\s]Starting server endpoint', - r'[a-z.]*[ ]*DEBUG[\s]+[a-x\.A-Z]*[\s]Starting Server Rest Endpoint'], []), - (2, False, [r'[a-z.]*[ ]*INFO[\s]+[a-x\.A-Z]*[\s]Starting server endpoint'], - [r'[a-z.]*[ ]*DEBUG[\s]+[a-x\.A-Z]*[\s]Starting Server Rest Endpoint']), - (3, True, [r'[a-z.]*[ ]*INFO[\s]+[a-x\.A-Z]*[\s]Starting server endpoint', - r'[a-z.]*[ ]*DEBUG[\s]+[a-x\.A-Z]*[\s]Starting Server Rest Endpoint'], []), - (2, True, [r'[a-z.]*[ ]*INFO[\s]+[a-x\.A-Z]*[\s]Starting server endpoint'], - [r'[a-z.]*[ ]*DEBUG[\s]+[a-x\.A-Z]*[\s]Starting Server Rest Endpoint']) -]) +@pytest.mark.parametrize( + "log_level, with_tty, regexes_required_lines, regexes_forbidden_lines", + [ + ( + 3, + False, + [ + r"[a-z.]*[ ]*INFO[\s]+[a-x\.A-Z]*[\s]Starting server endpoint", + r"[a-z.]*[ ]*DEBUG[\s]+[a-x\.A-Z]*[\s]Starting Server Rest Endpoint", + ], + [], + ), + ( + 2, + False, + [r"[a-z.]*[ ]*INFO[\s]+[a-x\.A-Z]*[\s]Starting server endpoint"], + [r"[a-z.]*[ ]*DEBUG[\s]+[a-x\.A-Z]*[\s]Starting Server Rest Endpoint"], + ), + ( + 3, + True, + [ + r"[a-z.]*[ ]*INFO[\s]+[a-x\.A-Z]*[\s]Starting server endpoint", + r"[a-z.]*[ ]*DEBUG[\s]+[a-x\.A-Z]*[\s]Starting Server Rest Endpoint", + ], + [], + ), + ( + 2, + True, + [r"[a-z.]*[ ]*INFO[\s]+[a-x\.A-Z]*[\s]Starting server endpoint"], + [r"[a-z.]*[ ]*DEBUG[\s]+[a-x\.A-Z]*[\s]Starting Server Rest Endpoint"], + ), + ], +) @pytest.mark.timeout(60) -def test_log_file_set(tmpdir, log_level, with_tty, regexes_required_lines, regexes_forbidden_lines): +def test_log_file_set( + tmpdir, log_level, with_tty, regexes_required_lines, regexes_forbidden_lines +): if is_colorama_package_available() and with_tty: pytest.skip("Colorama is present") log_file = "server.log" - (args, log_dir) = get_command(tmpdir, stdout_log_level=log_level, log_file=log_file, log_level_log_file=log_level) + (args, log_dir) = get_command( + tmpdir, + stdout_log_level=log_level, + log_file=log_file, + log_level_log_file=log_level, + ) if with_tty: (stdout, _) = run_with_tty(args) else: (stdout, _) = run_without_tty(args) assert log_file in os.listdir(log_dir) log_file = os.path.join(log_dir, log_file) - with open(log_file, 'r') as f: + with open(log_file, "r") as f: log_lines = f.readlines() check_logs(log_lines, regexes_required_lines, regexes_forbidden_lines, timed=True) check_logs(stdout, [], regexes_required_lines, timed=True) @@ -194,20 +302,28 @@ def test_log_file_set(tmpdir, log_level, with_tty, regexes_required_lines, regex def check_logs(log_lines, regexes_required_lines, regexes_forbidden_lines, timed): - compiled_regexes_requires_lines = get_compiled_regexes(regexes_required_lines, timed) - compiled_regexes_forbidden_lines = get_compiled_regexes(regexes_forbidden_lines, timed) + compiled_regexes_requires_lines = get_compiled_regexes( + regexes_required_lines, timed + ) + compiled_regexes_forbidden_lines = get_compiled_regexes( + regexes_forbidden_lines, timed + ) for line in log_lines: print(line) for regex in compiled_regexes_requires_lines: if not any(regex.match(line) for line in log_lines): - pytest.fail("Required pattern was not found in log lines: %s" % (regex.pattern,)) + pytest.fail( + "Required pattern was not found in log lines: %s" % (regex.pattern,) + ) for regex in compiled_regexes_forbidden_lines: if any(regex.match(line) for line in log_lines): pytest.fail("Forbidden pattern found in log lines: %s" % (regex.pattern,)) def test_check_shutdown(): - process = do_run([sys.executable, os.path.join(os.path.dirname(__file__), "miniapp.py")]) + process = do_run( + [sys.executable, os.path.join(os.path.dirname(__file__), "miniapp.py")] + ) # wait for handler to be in place try: process.communicate(timeout=2) @@ -222,10 +338,46 @@ def test_check_shutdown(): def test_check_bad_shutdown(): - print([sys.executable, os.path.join(os.path.dirname(__file__), "miniapp.py"), "bad"]) - process = do_run([sys.executable, os.path.join(os.path.dirname(__file__), "miniapp.py"), "bad"]) + print( + [sys.executable, os.path.join(os.path.dirname(__file__), "miniapp.py"), "bad"] + ) + process = do_run( + [sys.executable, os.path.join(os.path.dirname(__file__), "miniapp.py"), "bad"] + ) out, err = do_kill(process, killtime=5, termtime=2) print(out, err) assert "----- Thread Dump ----" in out assert "STOP" not in out assert "SHUTDOWN COMPLETE" not in out + + +def test_compiler_exception_output(snippetcompiler): + snippetcompiler.setup_for_snippet_external( + """ +entity Test: + number attr +end + +implement Test using std::none + +o = Test(attr="1234") +""" + ) + + output = """Could not set attribute `attr` on instance `__config__::Test (instantiated at ./main.cf:8)` """ \ + """(reported in Construct(Test) (./main.cf:8)) +caused by: + Invalid value '1234', expected Number (reported in Construct(Test) (./main.cf:8)) +""" + + def exec(*cmd): + process = do_run( + [sys.executable, "-m", "inmanta.app"] + list(cmd), + cwd=snippetcompiler.project_dir, + ) + out, err = process.communicate(timeout=5) + assert out.decode() == "" + assert err.decode() == output + + exec("compile") + exec("export", "-J", "out.json")