tenzir-test 0.9.4__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.
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/PKG-INFO +1 -31
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/README.md +0 -30
- tenzir_test-0.10.0/example-project/tests/shell/exit-code-test.sh +8 -0
- tenzir_test-0.10.0/example-project/tests/shell/exit-code-test.txt +2 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/pyproject.toml +1 -1
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/run.py +47 -20
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/runners/shell_runner.py +28 -26
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/tests/test_run_config.py +44 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/tests/test_shell_runner.py +45 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/.gitignore +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/LICENSE +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/README.md +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/fixtures/README.md +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/fixtures/__init__.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/fixtures/http.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/fixtures/server.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/inputs/events.ndjson +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/runners/__init__.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/runners/xxd.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/01-context-create.tql +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/01-context-create.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/02-context-update.tql +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/02-context-update.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/03-context-inspect.tql +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/03-context-inspect.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/test.yaml +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/fail.tql +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/fail.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/hex/hello.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/hex/hello.xxd +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/http-fixture.tql +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/http-fixture.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/lazy.tql +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/lazy.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/node-fixture.tql +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/node-fixture.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/executor-only/sum.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/executor-only/sum.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/executor-with-http-fixture/request.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/executor-with-http-fixture/request.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/executor-with-http-fixture/test.yaml +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/executor-with-node-fixture/context-manager.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/executor-with-node-fixture/context-manager.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/fixture-driving/manual_control.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/fixture-driving/manual_control.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/flaky_coin.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/flaky_coin.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/hello_world.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/hello_world.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/read-inputs.tql +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/read-inputs.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/shell/http-fixture-check.sh +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/shell/http-fixture-check.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/shell/tmp-dir.sh +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/shell/tmp-dir.txt +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/__init__.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/_python_runner.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/checks.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/cli.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/config.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/engine/__init__.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/engine/operations.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/engine/registry.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/engine/state.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/engine/worker.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/fixtures/__init__.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/fixtures/node.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/packages.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/py.typed +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/runners/__init__.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/runners/_utils.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/runners/custom_python_fixture_runner.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/runners/diff_runner.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/runners/ext_runner.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/runners/runner.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/runners/tenzir_runner.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/runners/tql_runner.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/tests/test_cli.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/tests/test_config.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/tests/test_engine_operations.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/tests/test_python_runner.py +0 -0
- {tenzir_test-0.9.4 → tenzir_test-0.10.0}/tests/test_run.py +0 -0
- {tenzir_test-0.9.4 → 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.
|
|
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
|
|
@@ -55,36 +55,6 @@ uvx tenzir-test --help
|
|
|
55
55
|
`uvx` downloads the newest compatible release, runs it in an isolated
|
|
56
56
|
environment, and caches subsequent invocations for fast reuse.
|
|
57
57
|
|
|
58
|
-
## 🚀 Quick Start
|
|
59
|
-
|
|
60
|
-
Create a project skeleton that mirrors the layout the harness expects:
|
|
61
|
-
|
|
62
|
-
```text
|
|
63
|
-
project-root/
|
|
64
|
-
├── fixtures/
|
|
65
|
-
│ └── http.py
|
|
66
|
-
├── inputs/
|
|
67
|
-
│ └── sample.ndjson
|
|
68
|
-
├── runners/
|
|
69
|
-
│ └── __init__.py
|
|
70
|
-
└── tests/
|
|
71
|
-
├── alerts/
|
|
72
|
-
│ ├── sample.py
|
|
73
|
-
│ └── sample.txt
|
|
74
|
-
└── regression/
|
|
75
|
-
├── dummy.tql
|
|
76
|
-
└── dummy.txt
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
1. Author fixtures in `fixtures/` and register them at import time.
|
|
80
|
-
2. Store reusable datasets in `inputs/`—the harness exposes the path via
|
|
81
|
-
`TENZIR_INPUTS` and provides a per-test scratch directory through
|
|
82
|
-
`TENZIR_TMP_DIR` when tests execute.
|
|
83
|
-
Use `--keep` (or `-k`) to preserve those temporary directories for debugging.
|
|
84
|
-
3. Create tests in `tests/` and pair them with reference artifacts (for example
|
|
85
|
-
`.txt`) that the harness compares against.
|
|
86
|
-
4. Run `uvx tenzir-test` from the project root to execute the full suite.
|
|
87
|
-
|
|
88
58
|
## 📚 Documentation
|
|
89
59
|
|
|
90
60
|
Consult our [user guide](https://docs.tenzir.com/guides/testing/write-tests)
|
|
@@ -26,36 +26,6 @@ uvx tenzir-test --help
|
|
|
26
26
|
`uvx` downloads the newest compatible release, runs it in an isolated
|
|
27
27
|
environment, and caches subsequent invocations for fast reuse.
|
|
28
28
|
|
|
29
|
-
## 🚀 Quick Start
|
|
30
|
-
|
|
31
|
-
Create a project skeleton that mirrors the layout the harness expects:
|
|
32
|
-
|
|
33
|
-
```text
|
|
34
|
-
project-root/
|
|
35
|
-
├── fixtures/
|
|
36
|
-
│ └── http.py
|
|
37
|
-
├── inputs/
|
|
38
|
-
│ └── sample.ndjson
|
|
39
|
-
├── runners/
|
|
40
|
-
│ └── __init__.py
|
|
41
|
-
└── tests/
|
|
42
|
-
├── alerts/
|
|
43
|
-
│ ├── sample.py
|
|
44
|
-
│ └── sample.txt
|
|
45
|
-
└── regression/
|
|
46
|
-
├── dummy.tql
|
|
47
|
-
└── dummy.txt
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
1. Author fixtures in `fixtures/` and register them at import time.
|
|
51
|
-
2. Store reusable datasets in `inputs/`—the harness exposes the path via
|
|
52
|
-
`TENZIR_INPUTS` and provides a per-test scratch directory through
|
|
53
|
-
`TENZIR_TMP_DIR` when tests execute.
|
|
54
|
-
Use `--keep` (or `-k`) to preserve those temporary directories for debugging.
|
|
55
|
-
3. Create tests in `tests/` and pair them with reference artifacts (for example
|
|
56
|
-
`.txt`) that the harness compares against.
|
|
57
|
-
4. Run `uvx tenzir-test` from the project root to execute the full suite.
|
|
58
|
-
|
|
59
29
|
## 📚 Documentation
|
|
60
30
|
|
|
61
31
|
Consult our [user guide](https://docs.tenzir.com/guides/testing/write-tests)
|
|
@@ -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
|
|
@@ -349,6 +350,7 @@ _TMP_ROOT_NAME = ".tenzir-test"
|
|
|
349
350
|
_TMP_SUBDIR_NAME = "tmp"
|
|
350
351
|
_TMP_BASE_DIRS: set[Path] = set()
|
|
351
352
|
_ACTIVE_TMP_DIRS: set[Path] = set()
|
|
353
|
+
_TMP_DIR_LOCK = threading.Lock()
|
|
352
354
|
KEEP_TMP_DIRS = bool(os.environ.get(_TMP_KEEP_ENV_VAR))
|
|
353
355
|
|
|
354
356
|
SHOW_DIFF_OUTPUT = True
|
|
@@ -506,10 +508,13 @@ def _tmp_prefix_for(test: Path) -> str:
|
|
|
506
508
|
|
|
507
509
|
|
|
508
510
|
def _create_test_tmp_dir(test: Path) -> Path:
|
|
509
|
-
base = _resolve_tmp_base()
|
|
510
511
|
prefix = f"{_tmp_prefix_for(test)}-"
|
|
511
|
-
|
|
512
|
-
|
|
512
|
+
with _TMP_DIR_LOCK:
|
|
513
|
+
base = _resolve_tmp_base()
|
|
514
|
+
if not base.exists():
|
|
515
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
516
|
+
path = Path(tempfile.mkdtemp(prefix=prefix, dir=str(base)))
|
|
517
|
+
_ACTIVE_TMP_DIRS.add(path)
|
|
513
518
|
return path
|
|
514
519
|
|
|
515
520
|
|
|
@@ -522,22 +527,27 @@ def cleanup_test_tmp_dir(path: str | os.PathLike[str] | None) -> None:
|
|
|
522
527
|
if not path:
|
|
523
528
|
return
|
|
524
529
|
tmp_path = Path(path)
|
|
525
|
-
|
|
530
|
+
with _TMP_DIR_LOCK:
|
|
531
|
+
_ACTIVE_TMP_DIRS.discard(tmp_path)
|
|
526
532
|
try:
|
|
527
533
|
fixtures_impl.invoke_tmp_dir_cleanup(tmp_path)
|
|
528
534
|
except Exception: # pragma: no cover - defensive logging
|
|
529
535
|
pass
|
|
530
536
|
if KEEP_TMP_DIRS:
|
|
531
537
|
return
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
538
|
+
with _TMP_DIR_LOCK:
|
|
539
|
+
if tmp_path.exists():
|
|
540
|
+
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
541
|
+
_cleanup_tmp_base_dirs()
|
|
535
542
|
|
|
536
543
|
|
|
537
544
|
def _cleanup_remaining_tmp_dirs() -> None:
|
|
538
|
-
|
|
545
|
+
with _TMP_DIR_LOCK:
|
|
546
|
+
remaining = list(_ACTIVE_TMP_DIRS)
|
|
547
|
+
for tmp_path in remaining:
|
|
539
548
|
cleanup_test_tmp_dir(tmp_path)
|
|
540
|
-
|
|
549
|
+
with _TMP_DIR_LOCK:
|
|
550
|
+
_cleanup_tmp_base_dirs()
|
|
541
551
|
|
|
542
552
|
|
|
543
553
|
def _cleanup_all_tmp_dirs() -> None:
|
|
@@ -545,9 +555,12 @@ def _cleanup_all_tmp_dirs() -> None:
|
|
|
545
555
|
|
|
546
556
|
if KEEP_TMP_DIRS:
|
|
547
557
|
return
|
|
548
|
-
|
|
558
|
+
with _TMP_DIR_LOCK:
|
|
559
|
+
remaining = list(_ACTIVE_TMP_DIRS)
|
|
560
|
+
for tmp_path in remaining:
|
|
549
561
|
cleanup_test_tmp_dir(tmp_path)
|
|
550
|
-
|
|
562
|
+
with _TMP_DIR_LOCK:
|
|
563
|
+
_cleanup_tmp_base_dirs()
|
|
551
564
|
|
|
552
565
|
|
|
553
566
|
def _cleanup_tmp_base_dirs() -> None:
|
|
@@ -2620,9 +2633,13 @@ def run_simple_test(
|
|
|
2620
2633
|
)
|
|
2621
2634
|
good = completed.returncode == 0
|
|
2622
2635
|
output = b""
|
|
2636
|
+
stderr_output = b""
|
|
2623
2637
|
if not passthrough_mode:
|
|
2624
|
-
|
|
2625
|
-
|
|
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"")
|
|
2626
2643
|
except subprocess.TimeoutExpired:
|
|
2627
2644
|
report_failure(test, f"└─▶ \033[31msubprocess hit {timeout}s timeout\033[0m")
|
|
2628
2645
|
return False
|
|
@@ -2642,20 +2659,30 @@ def run_simple_test(
|
|
|
2642
2659
|
return False
|
|
2643
2660
|
if interrupted:
|
|
2644
2661
|
_request_interrupt()
|
|
2645
|
-
|
|
2662
|
+
summary_line = (
|
|
2646
2663
|
_INTERRUPTED_NOTICE
|
|
2647
2664
|
if interrupted
|
|
2648
|
-
else f"
|
|
2665
|
+
else f"└─▶ \033[31mgot unexpected exit code {completed.returncode}\033[0m"
|
|
2649
2666
|
)
|
|
2650
2667
|
if passthrough_mode:
|
|
2651
|
-
report_failure(test,
|
|
2668
|
+
report_failure(test, summary_line)
|
|
2652
2669
|
else:
|
|
2653
2670
|
with stdout_lock:
|
|
2654
|
-
|
|
2671
|
+
fail(test)
|
|
2655
2672
|
if not interrupted:
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
sys.stdout.buffer.write(
|
|
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")
|
|
2659
2686
|
return False
|
|
2660
2687
|
if passthrough_mode:
|
|
2661
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.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/01-context-create.tql
RENAMED
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/01-context-create.txt
RENAMED
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/02-context-update.tql
RENAMED
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/02-context-update.txt
RENAMED
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/03-context-inspect.tql
RENAMED
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/context/03-context-inspect.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/flaky_coin.py
RENAMED
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/flaky_coin.txt
RENAMED
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/hello_world.py
RENAMED
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/example-project/tests/python/pure-python/hello_world.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenzir_test-0.9.4 → tenzir_test-0.10.0}/src/tenzir_test/runners/custom_python_fixture_runner.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|