tenzir-test 0.9.5__tar.gz → 0.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/PKG-INFO +1 -1
  2. tenzir_test-0.10.0/example-project/tests/shell/exit-code-test.sh +8 -0
  3. tenzir_test-0.10.0/example-project/tests/shell/exit-code-test.txt +2 -0
  4. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/pyproject.toml +1 -1
  5. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/run.py +24 -9
  6. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/runners/shell_runner.py +28 -26
  7. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/tests/test_run_config.py +44 -0
  8. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/tests/test_shell_runner.py +45 -0
  9. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/.gitignore +0 -0
  10. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/LICENSE +0 -0
  11. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/README.md +0 -0
  12. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/README.md +0 -0
  13. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/fixtures/README.md +0 -0
  14. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/fixtures/__init__.py +0 -0
  15. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/fixtures/http.py +0 -0
  16. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/fixtures/server.py +0 -0
  17. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/inputs/events.ndjson +0 -0
  18. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/runners/__init__.py +0 -0
  19. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/runners/xxd.py +0 -0
  20. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/context/01-context-create.tql +0 -0
  21. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/context/01-context-create.txt +0 -0
  22. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/context/02-context-update.tql +0 -0
  23. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/context/02-context-update.txt +0 -0
  24. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/context/03-context-inspect.tql +0 -0
  25. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/context/03-context-inspect.txt +0 -0
  26. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/context/test.yaml +0 -0
  27. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/fail.tql +0 -0
  28. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/fail.txt +0 -0
  29. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/hex/hello.txt +0 -0
  30. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/hex/hello.xxd +0 -0
  31. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/http-fixture.tql +0 -0
  32. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/http-fixture.txt +0 -0
  33. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/lazy.tql +0 -0
  34. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/lazy.txt +0 -0
  35. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/node-fixture.tql +0 -0
  36. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/node-fixture.txt +0 -0
  37. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/executor-only/sum.py +0 -0
  38. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/executor-only/sum.txt +0 -0
  39. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/executor-with-http-fixture/request.py +0 -0
  40. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/executor-with-http-fixture/request.txt +0 -0
  41. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/executor-with-http-fixture/test.yaml +0 -0
  42. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/executor-with-node-fixture/context-manager.py +0 -0
  43. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/executor-with-node-fixture/context-manager.txt +0 -0
  44. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/fixture-driving/manual_control.py +0 -0
  45. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/fixture-driving/manual_control.txt +0 -0
  46. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/flaky_coin.py +0 -0
  47. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/flaky_coin.txt +0 -0
  48. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/hello_world.py +0 -0
  49. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/hello_world.txt +0 -0
  50. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/read-inputs.tql +0 -0
  51. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/read-inputs.txt +0 -0
  52. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/shell/http-fixture-check.sh +0 -0
  53. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/shell/http-fixture-check.txt +0 -0
  54. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/shell/tmp-dir.sh +0 -0
  55. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/example-project/tests/shell/tmp-dir.txt +0 -0
  56. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/__init__.py +0 -0
  57. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/_python_runner.py +0 -0
  58. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/checks.py +0 -0
  59. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/cli.py +0 -0
  60. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/config.py +0 -0
  61. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/engine/__init__.py +0 -0
  62. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/engine/operations.py +0 -0
  63. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/engine/registry.py +0 -0
  64. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/engine/state.py +0 -0
  65. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/engine/worker.py +0 -0
  66. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/fixtures/__init__.py +0 -0
  67. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/fixtures/node.py +0 -0
  68. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/packages.py +0 -0
  69. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/py.typed +0 -0
  70. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/runners/__init__.py +0 -0
  71. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/runners/_utils.py +0 -0
  72. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/runners/custom_python_fixture_runner.py +0 -0
  73. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/runners/diff_runner.py +0 -0
  74. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/runners/ext_runner.py +0 -0
  75. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/runners/runner.py +0 -0
  76. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/runners/tenzir_runner.py +0 -0
  77. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/src/tenzir_test/runners/tql_runner.py +0 -0
  78. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/tests/test_cli.py +0 -0
  79. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/tests/test_config.py +0 -0
  80. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/tests/test_engine_operations.py +0 -0
  81. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/tests/test_python_runner.py +0 -0
  82. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/tests/test_run.py +0 -0
  83. {tenzir_test-0.9.5 → tenzir_test-0.10.0}/tests/test_runner_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tenzir-test
3
- Version: 0.9.5
3
+ Version: 0.10.0
4
4
  Summary: Reusable test execution framework extracted from the Tenzir repository.
5
5
  Project-URL: Homepage, https://github.com/tenzir/test
6
6
  Project-URL: Repository, https://github.com/tenzir/test
@@ -0,0 +1,8 @@
1
+ #!/bin/sh
2
+
3
+ # This test intentionally fails to demonstrate the arrow output
4
+
5
+ echo "Running test..."
6
+ echo "About to fail with exit code 42"
7
+ echo "Simulated error message on stderr" >&2
8
+ exit 42
@@ -0,0 +1,2 @@
1
+ Running test...
2
+ About to fail with exit code 42
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tenzir-test"
3
- version = "0.9.5"
3
+ version = "0.10.0"
4
4
  description = "Reusable test execution framework extracted from the Tenzir repository."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -169,6 +169,7 @@ PASS_MAX_COLOR = "\033[92m"
169
169
  FAIL_COLOR = "\033[31m"
170
170
  SKIP_COLOR = "\033[90;1m"
171
171
  RESET_COLOR = "\033[0m"
172
+ DETAIL_COLOR = "\033[2;37m"
172
173
  PASS_SPECTRUM = [
173
174
  "\033[38;5;52m", # 0-9% deep red
174
175
  "\033[38;5;88m", # 10-19% red
@@ -2632,9 +2633,13 @@ def run_simple_test(
2632
2633
  )
2633
2634
  good = completed.returncode == 0
2634
2635
  output = b""
2636
+ stderr_output = b""
2635
2637
  if not passthrough_mode:
2636
- captured = completed.stdout or b""
2637
- output = captured.replace(str(ROOT).encode() + b"/", b"")
2638
+ root_bytes = str(ROOT).encode() + b"/"
2639
+ captured_stdout = completed.stdout or b""
2640
+ output = captured_stdout.replace(root_bytes, b"")
2641
+ captured_stderr = completed.stderr or b""
2642
+ stderr_output = captured_stderr.replace(root_bytes, b"")
2638
2643
  except subprocess.TimeoutExpired:
2639
2644
  report_failure(test, f"└─▶ \033[31msubprocess hit {timeout}s timeout\033[0m")
2640
2645
  return False
@@ -2654,20 +2659,30 @@ def run_simple_test(
2654
2659
  return False
2655
2660
  if interrupted:
2656
2661
  _request_interrupt()
2657
- message = (
2662
+ summary_line = (
2658
2663
  _INTERRUPTED_NOTICE
2659
2664
  if interrupted
2660
- else f"┌─▶ \033[31mgot unexpected exit code {completed.returncode}\033[0m"
2665
+ else f"└─▶ \033[31mgot unexpected exit code {completed.returncode}\033[0m"
2661
2666
  )
2662
2667
  if passthrough_mode:
2663
- report_failure(test, message)
2668
+ report_failure(test, summary_line)
2664
2669
  else:
2665
2670
  with stdout_lock:
2666
- report_failure(test, message)
2671
+ fail(test)
2667
2672
  if not interrupted:
2668
- for last, line in last_and(output.split(b"\n")):
2669
- prefix = "│ " if not last else "└─"
2670
- sys.stdout.buffer.write(prefix.encode() + line + b"\n")
2673
+ line_prefix = "│ ".encode()
2674
+ for line in output.splitlines():
2675
+ sys.stdout.buffer.write(line_prefix + line + b"\n")
2676
+ if completed.returncode != 0 and stderr_output:
2677
+ sys.stdout.write("├─▶ stderr\n")
2678
+ detail_prefix = DETAIL_COLOR.encode()
2679
+ reset_bytes = RESET_COLOR.encode()
2680
+ for line in stderr_output.splitlines():
2681
+ sys.stdout.buffer.write(
2682
+ line_prefix + detail_prefix + line + reset_bytes + b"\n"
2683
+ )
2684
+ if summary_line:
2685
+ sys.stdout.write(summary_line + "\n")
2671
2686
  return False
2672
2687
  if passthrough_mode:
2673
2688
  success(test)
@@ -64,47 +64,49 @@ class ShellRunner(ExtRunner):
64
64
  fixture_api.pop_context(context_token)
65
65
  run_mod.cleanup_test_tmp_dir(env.get(run_mod.TEST_TMP_ENV_VAR))
66
66
 
67
+ stdout_data = completed.stdout
68
+ if isinstance(stdout_data, str):
69
+ stdout_bytes: bytes = stdout_data.encode()
70
+ else:
71
+ stdout_bytes = stdout_data or b""
72
+
73
+ stderr_data = completed.stderr
74
+ if isinstance(stderr_data, str):
75
+ stderr_bytes: bytes = stderr_data.encode()
76
+ else:
77
+ stderr_bytes = stderr_data or b""
78
+
67
79
  good = completed.returncode == 0
68
80
  if expect_error == good:
69
81
  suppressed = run_mod.should_suppress_failure_output()
82
+ summary_line = f"└─▶ \033[31mgot unexpected exit code {completed.returncode}\033[0m"
70
83
  if passthrough:
71
84
  if not suppressed:
72
- run_mod.report_failure(
73
- test,
74
- f"┌─▶ \033[31mgot unexpected exit code {completed.returncode}\033[0m",
75
- )
85
+ run_mod.report_failure(test, summary_line)
76
86
  return False
77
87
  if suppressed:
78
88
  return False
89
+
79
90
  with run_mod.stdout_lock:
80
- run_mod.report_failure(
81
- test,
82
- f"┌─▶ \033[31mgot unexpected exit code {completed.returncode}\033[0m",
83
- )
84
- stdout = completed.stdout or b""
85
- for last, line in run_mod.last_and(stdout.split(b"\n")):
86
- prefix = "│ " if not last else "└─"
87
- sys.stdout.buffer.write(prefix.encode() + line + b"\n")
88
- if completed.stderr:
89
- sys.stdout.buffer.write(completed.stderr)
91
+ run_mod.fail(test)
92
+ line_prefix = "│ ".encode()
93
+ for line in stdout_bytes.splitlines():
94
+ sys.stdout.buffer.write(line_prefix + line + b"\n")
95
+ if stderr_bytes:
96
+ sys.stdout.write("├─▶ stderr\n")
97
+ detail_prefix = run_mod.DETAIL_COLOR.encode()
98
+ reset_bytes = run_mod.RESET_COLOR.encode()
99
+ for line in stderr_bytes.splitlines():
100
+ sys.stdout.buffer.write(
101
+ line_prefix + detail_prefix + line + reset_bytes + b"\n"
102
+ )
103
+ sys.stdout.write(summary_line + "\n")
90
104
  return False
91
105
 
92
106
  if passthrough:
93
107
  run_mod.success(test)
94
108
  return True
95
109
 
96
- stdout_data = completed.stdout
97
- if isinstance(stdout_data, str):
98
- stdout_bytes: bytes = stdout_data.encode()
99
- else:
100
- stdout_bytes = stdout_data or b""
101
-
102
- stderr_data = completed.stderr
103
- if isinstance(stderr_data, str):
104
- stderr_bytes: bytes = stderr_data.encode()
105
- else:
106
- stderr_bytes = stderr_data or b""
107
-
108
110
  root_prefix: bytes = (str(run_mod.ROOT) + "/").encode()
109
111
  stdout_bytes = stdout_bytes.replace(root_prefix, b"")
110
112
 
@@ -502,6 +502,7 @@ def test_run_simple_test_injects_package_dirs(
502
502
  def __init__(self) -> None:
503
503
  self.returncode = 0
504
504
  self.stdout = b"ok"
505
+ self.stderr = b""
505
506
 
506
507
  def fake_run(cmd, timeout, stdout=None, stderr=None, env=None, **kwargs): # type: ignore[no-untyped-def]
507
508
  captured["cmd"] = list(cmd)
@@ -563,6 +564,7 @@ def test_run_simple_test_respects_inputs_override(
563
564
  def __init__(self) -> None:
564
565
  self.returncode = 0
565
566
  self.stdout = b"ok"
567
+ self.stderr = b""
566
568
 
567
569
  def fake_run(cmd, timeout, stdout=None, stderr=None, env=None, **kwargs): # type: ignore[no-untyped-def]
568
570
  captured["cmd"] = list(cmd)
@@ -634,6 +636,48 @@ def test_run_simple_test_passthrough_streams_output(
634
636
  assert captured.get("stdout") is None
635
637
 
636
638
 
639
+ def test_run_simple_test_reports_stderr_on_failure(
640
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
641
+ ) -> None:
642
+ test_file = tmp_path / "case.tql"
643
+ test_file.write_text("version\nwrite_json\n", encoding="utf-8")
644
+
645
+ original_settings = config.Settings(
646
+ root=run.ROOT,
647
+ tenzir_binary=run.TENZIR_BINARY,
648
+ tenzir_node_binary=run.TENZIR_NODE_BINARY,
649
+ )
650
+ run.apply_settings(
651
+ config.Settings(
652
+ root=tmp_path,
653
+ tenzir_binary=sys.executable,
654
+ tenzir_node_binary=None,
655
+ )
656
+ )
657
+
658
+ class FakeCompletedProcess:
659
+ def __init__(self) -> None:
660
+ self.returncode = 13
661
+ self.stdout = b"captured stdout\n"
662
+ self.stderr = b"captured stderr\n"
663
+
664
+ monkeypatch.setattr(run, "run_subprocess", lambda *args, **kwargs: FakeCompletedProcess())
665
+
666
+ try:
667
+ result = run.run_simple_test(test_file, update=False, output_ext="txt")
668
+ finally:
669
+ run.apply_settings(original_settings)
670
+
671
+ assert result is False
672
+ lines = capsys.readouterr().out.splitlines()
673
+ assert "✘" in lines[0] and lines[0].endswith("case.tql")
674
+ assert run.ANSI_ESCAPE.sub("", lines[1]) == "│ captured stdout"
675
+ assert run.ANSI_ESCAPE.sub("", lines[2]) == "├─▶ stderr"
676
+ assert run.ANSI_ESCAPE.sub("", lines[3]) == "│ captured stderr"
677
+ assert lines[4].startswith("└─▶ ")
678
+ assert "got unexpected exit code 13" in lines[4]
679
+
680
+
637
681
  def test_run_simple_test_suppresses_diff_on_interrupt(
638
682
  monkeypatch: pytest.MonkeyPatch, tmp_path: Path
639
683
  ) -> None:
@@ -145,3 +145,48 @@ def test_shell_runner_passthrough_streams_output(
145
145
  assert captured["check"] is True
146
146
  assert captured["capture_output"] is False
147
147
  assert captured["cwd"] == str(tmp_path)
148
+
149
+
150
+ def test_shell_runner_reports_stderr_on_failure(
151
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
152
+ ) -> None:
153
+ script = tmp_path / "tests" / "shell" / "fail.sh"
154
+ script.parent.mkdir(parents=True, exist_ok=True)
155
+ script.write_text("echo stdout\n >&2 echo stderr\n exit 42\n", encoding="utf-8")
156
+ script.chmod(0o755)
157
+
158
+ original_settings = config.Settings(
159
+ root=run.ROOT,
160
+ tenzir_binary=run.TENZIR_BINARY,
161
+ tenzir_node_binary=run.TENZIR_NODE_BINARY,
162
+ )
163
+
164
+ class FakeCompletedProcess:
165
+ def __init__(self) -> None:
166
+ self.returncode = 42
167
+ self.stdout = b"stdout\n"
168
+ self.stderr = b"stderr\n"
169
+
170
+ monkeypatch.setattr(run, "run_subprocess", lambda *args, **kwargs: FakeCompletedProcess())
171
+
172
+ try:
173
+ run.apply_settings(
174
+ config.Settings(
175
+ root=tmp_path,
176
+ tenzir_binary=run.TENZIR_BINARY,
177
+ tenzir_node_binary=run.TENZIR_NODE_BINARY,
178
+ )
179
+ )
180
+ runner = run.ShellRunner()
181
+ result = runner.run(script, update=False, coverage=False)
182
+ finally:
183
+ run.apply_settings(original_settings)
184
+
185
+ assert result is False
186
+ lines = capsys.readouterr().out.splitlines()
187
+ assert "✘" in lines[0] and lines[0].endswith("tests/shell/fail.sh")
188
+ assert run.ANSI_ESCAPE.sub("", lines[1]) == "│ stdout"
189
+ assert run.ANSI_ESCAPE.sub("", lines[2]) == "├─▶ stderr"
190
+ assert run.ANSI_ESCAPE.sub("", lines[3]) == "│ stderr"
191
+ assert lines[4].startswith("└─▶ ")
192
+ assert "got unexpected exit code 42" in lines[4]
File without changes
File without changes
File without changes