tenzir-test 0.9.4__py3-none-any.whl → 0.10.0__py3-none-any.whl
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/run.py +47 -20
- tenzir_test/runners/shell_runner.py +28 -26
- {tenzir_test-0.9.4.dist-info → tenzir_test-0.10.0.dist-info}/METADATA +1 -31
- {tenzir_test-0.9.4.dist-info → tenzir_test-0.10.0.dist-info}/RECORD +7 -7
- {tenzir_test-0.9.4.dist-info → tenzir_test-0.10.0.dist-info}/WHEEL +0 -0
- {tenzir_test-0.9.4.dist-info → tenzir_test-0.10.0.dist-info}/entry_points.txt +0 -0
- {tenzir_test-0.9.4.dist-info → tenzir_test-0.10.0.dist-info}/licenses/LICENSE +0 -0
tenzir_test/run.py
CHANGED
|
@@ -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
|
|
|
@@ -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)
|
|
@@ -5,7 +5,7 @@ tenzir_test/cli.py,sha256=O1FaxYFKWal--aqs-ZFT2URqL26_HjUtFNDACIBspXA,5452
|
|
|
5
5
|
tenzir_test/config.py,sha256=q1_VEXuxL-xsGlnooeGvXxx9cMw652UEB9a1mPzZIQs,1680
|
|
6
6
|
tenzir_test/packages.py,sha256=cTCQdGjCS1XmuKyiwh0ew-z9tHn6J-xZ6nvBP-hU8bc,948
|
|
7
7
|
tenzir_test/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
tenzir_test/run.py,sha256=
|
|
8
|
+
tenzir_test/run.py,sha256=W0NMpftm4MG6oiVFO0rjUxgS-h-JjFKyUTDLM3U7bS8,116871
|
|
9
9
|
tenzir_test/engine/__init__.py,sha256=5APwy90YDm7rmL_qCZfToAcfbQthcZ8yV2_ExXKqaqE,110
|
|
10
10
|
tenzir_test/engine/operations.py,sha256=OCYjuMHyMAaay4s08u2Sl7oE-PmgeXumylp7R8GYIH4,950
|
|
11
11
|
tenzir_test/engine/registry.py,sha256=LXCr6TGlv1sR1m1eboTk7SrbS2IVErc3PqUuHxGA2xk,594
|
|
@@ -19,11 +19,11 @@ tenzir_test/runners/custom_python_fixture_runner.py,sha256=bbG0ZvCL6Cs6BCKJQQPAB
|
|
|
19
19
|
tenzir_test/runners/diff_runner.py,sha256=ah1hr1vvD6BON2PZz61mxwioRFIzHFuaAbJ0DjDSqG4,5151
|
|
20
20
|
tenzir_test/runners/ext_runner.py,sha256=sKL9Mw_ksVVBWnrdIJR2WS5ueVnLKuNYYWZ22FTZIPo,730
|
|
21
21
|
tenzir_test/runners/runner.py,sha256=LtlD8huQOSmD7RyYDnKeCuI4Y6vhxGXMKsHA2qgfWN0,989
|
|
22
|
-
tenzir_test/runners/shell_runner.py,sha256=
|
|
22
|
+
tenzir_test/runners/shell_runner.py,sha256=EREqIaHxG5_nl8CmeQYWsiM6rZS9frCkhdaUHoRUGRw,6024
|
|
23
23
|
tenzir_test/runners/tenzir_runner.py,sha256=464FFYS_mh6l-ehccc-S8cIUO1MxdapwQL5X3PmMkMI,1006
|
|
24
24
|
tenzir_test/runners/tql_runner.py,sha256=2ZLMf3TIKwcOvaOFrVvvhzK-EcWmGOUZxKkbSoByyQA,248
|
|
25
|
-
tenzir_test-0.
|
|
26
|
-
tenzir_test-0.
|
|
27
|
-
tenzir_test-0.
|
|
28
|
-
tenzir_test-0.
|
|
29
|
-
tenzir_test-0.
|
|
25
|
+
tenzir_test-0.10.0.dist-info/METADATA,sha256=12vESrRyq_hSo-r2ljuX6PptSeWWPPMXtx_l04JfnRE,3008
|
|
26
|
+
tenzir_test-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
27
|
+
tenzir_test-0.10.0.dist-info/entry_points.txt,sha256=q0eD9RQ_9eMPYvFNpBElo55HQYeaPgLfe9YhLsNwl10,93
|
|
28
|
+
tenzir_test-0.10.0.dist-info/licenses/LICENSE,sha256=ajMbpcBiSTXI8Rr4t17pvowV-On8DktghfZKxY_A22Q,10750
|
|
29
|
+
tenzir_test-0.10.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|