tenzir-test 0.15.0__py3-none-any.whl → 1.0.1__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.
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import json
4
4
  import os
5
5
  import runpy
6
+ import shlex
6
7
  import sys
7
8
  from contextvars import Token
8
9
  from pathlib import Path
@@ -49,14 +50,18 @@ def _push_context_from_env() -> Token[Any] | None:
49
50
  config_args_raw = payload.get("config_args", ())
50
51
  config_args = tuple(str(arg) for arg in config_args_raw)
51
52
  env = dict(os.environ)
53
+ tenzir_binary_str = env.get("TENZIR_PYTHON_FIXTURE_BINARY") or env.get("TENZIR_BINARY")
54
+ tenzir_node_binary_str = env.get("TENZIR_NODE_BINARY")
52
55
  context = _fixtures.FixtureContext(
53
56
  test=test_path,
54
57
  config=config,
55
58
  coverage=coverage,
56
59
  env=env,
57
60
  config_args=config_args,
58
- tenzir_binary=env.get("TENZIR_PYTHON_FIXTURE_BINARY") or env.get("TENZIR_BINARY"),
59
- tenzir_node_binary=env.get("TENZIR_NODE_BINARY"),
61
+ tenzir_binary=tuple(shlex.split(tenzir_binary_str)) if tenzir_binary_str else None,
62
+ tenzir_node_binary=tuple(shlex.split(tenzir_node_binary_str))
63
+ if tenzir_node_binary_str
64
+ else None,
60
65
  )
61
66
  token = _fixtures.push_context(context)
62
67
  return cast(Token[Any], token)
tenzir_test/cli.py CHANGED
@@ -39,18 +39,6 @@ def _normalize_exit_code(value: object) -> int:
39
39
  ),
40
40
  help="Project root to scan for tests.",
41
41
  )
42
- @click.option(
43
- "tenzir_binary",
44
- "--tenzir-binary",
45
- type=click.Path(path_type=Path, dir_okay=False, writable=False, resolve_path=False),
46
- help="Path to the tenzir executable.",
47
- )
48
- @click.option(
49
- "tenzir_node_binary",
50
- "--tenzir-node-binary",
51
- type=click.Path(path_type=Path, dir_okay=False, writable=False, resolve_path=False),
52
- help="Path to the tenzir-node executable.",
53
- )
54
42
  @click.option(
55
43
  "package_dirs",
56
44
  "--package-dirs",
@@ -150,8 +138,6 @@ def cli(
150
138
  ctx: click.Context,
151
139
  *,
152
140
  root: Path | None,
153
- tenzir_binary: Path | None,
154
- tenzir_node_binary: Path | None,
155
141
  package_dirs: tuple[str, ...],
156
142
  tests: tuple[Path, ...],
157
143
  update: bool,
@@ -186,8 +172,6 @@ def cli(
186
172
  try:
187
173
  result = runtime.run_cli(
188
174
  root=root,
189
- tenzir_binary=tenzir_binary,
190
- tenzir_node_binary=tenzir_node_binary,
191
175
  package_dirs=package_paths,
192
176
  tests=list(tests),
193
177
  update=update,
tenzir_test/config.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
+ import shlex
4
5
  import shutil
5
6
  from dataclasses import dataclass
6
7
  from pathlib import Path
@@ -12,8 +13,8 @@ class Settings:
12
13
  """Configuration values steering how the harness discovers binaries and data."""
13
14
 
14
15
  root: Path
15
- tenzir_binary: str | None
16
- tenzir_node_binary: str | None
16
+ tenzir_binary: tuple[str, ...] | None
17
+ tenzir_node_binary: tuple[str, ...] | None
17
18
 
18
19
  @property
19
20
  def inputs_dir(self) -> Path:
@@ -26,17 +27,34 @@ class Settings:
26
27
  return direct
27
28
 
28
29
 
29
- def _coerce_binary(value: str | os.PathLike[str] | None) -> str | None:
30
- if value is None:
31
- return None
32
- return str(value)
30
+ def _resolve_binary(
31
+ env_var: str | None,
32
+ binary_name: str,
33
+ ) -> tuple[str, ...] | None:
34
+ """Resolve a binary with fallback to uvx."""
35
+ if env_var:
36
+ try:
37
+ parts = tuple(shlex.split(env_var))
38
+ except ValueError as e:
39
+ raise ValueError(
40
+ f"Invalid shell syntax in environment variable for {binary_name}: {e}"
41
+ ) from e
42
+ if not parts:
43
+ raise ValueError(f"Empty command in environment variable for {binary_name}")
44
+ return parts
45
+ which_result = shutil.which(binary_name)
46
+ if which_result:
47
+ return (which_result,)
48
+ if shutil.which("uvx"):
49
+ if binary_name == "tenzir-node":
50
+ return ("uvx", "--from", "tenzir", "tenzir-node")
51
+ return ("uvx", binary_name)
52
+ return None
33
53
 
34
54
 
35
55
  def discover_settings(
36
56
  *,
37
57
  root: Path | None = None,
38
- tenzir_binary: str | os.PathLike[str] | None = None,
39
- tenzir_node_binary: str | os.PathLike[str] | None = None,
40
58
  env: Mapping[str, str] | None = None,
41
59
  ) -> Settings:
42
60
  """Produce harness settings by combining CLI overrides with environment defaults."""
@@ -46,12 +64,7 @@ def discover_settings(
46
64
  chosen_root = root or environment.get("TENZIR_TEST_ROOT") or Path.cwd()
47
65
  root_path = Path(chosen_root).resolve()
48
66
 
49
- binary_cli = _coerce_binary(tenzir_binary)
50
- binary_env = environment.get("TENZIR_BINARY")
51
- tenzir_path = binary_cli or binary_env or shutil.which("tenzir")
52
-
53
- node_cli = _coerce_binary(tenzir_node_binary)
54
- node_env = environment.get("TENZIR_NODE_BINARY")
55
- node_path = node_cli or node_env or shutil.which("tenzir-node")
67
+ tenzir_path = _resolve_binary(environment.get("TENZIR_BINARY"), "tenzir")
68
+ node_path = _resolve_binary(environment.get("TENZIR_NODE_BINARY"), "tenzir-node")
56
69
 
57
70
  return Settings(root=root_path, tenzir_binary=tenzir_path, tenzir_node_binary=node_path)
@@ -1,13 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
- from typing import Optional
5
4
 
6
5
  from ..config import Settings
7
6
  from tenzir_test import run
8
7
 
9
- TENZIR_BINARY: Optional[str] = run.TENZIR_BINARY
10
- TENZIR_NODE_BINARY: Optional[str] = run.TENZIR_NODE_BINARY
8
+ TENZIR_BINARY: tuple[str, ...] | None = run.TENZIR_BINARY
9
+ TENZIR_NODE_BINARY: tuple[str, ...] | None = run.TENZIR_NODE_BINARY
11
10
  ROOT: Path = run.ROOT
12
11
  INPUTS_DIR: Path = run.INPUTS_DIR
13
12
 
@@ -42,8 +42,8 @@ class FixtureContext:
42
42
  coverage: bool
43
43
  env: dict[str, str]
44
44
  config_args: Sequence[str]
45
- tenzir_binary: str | None
46
- tenzir_node_binary: str | None
45
+ tenzir_binary: tuple[str, ...] | None
46
+ tenzir_node_binary: tuple[str, ...] | None
47
47
 
48
48
 
49
49
  _CONTEXT: ContextVar[FixtureContext | None] = ContextVar(
@@ -57,7 +57,7 @@ class Executor:
57
57
  def __init__(self, env: Mapping[str, str] | None = None) -> None:
58
58
  source = env or os.environ
59
59
  try:
60
- self.binary: str = source["TENZIR_NODE_CLIENT_BINARY"]
60
+ self.binary: tuple[str, ...] = tuple(shlex.split(source["TENZIR_NODE_CLIENT_BINARY"]))
61
61
  except KeyError as exc: # pragma: no cover - defensive guard
62
62
  raise RuntimeError("TENZIR_NODE_CLIENT_BINARY is not configured") from exc
63
63
  self.endpoint: str | None = source.get("TENZIR_NODE_CLIENT_ENDPOINT")
@@ -72,7 +72,7 @@ class Executor:
72
72
  self, source: str, desired_timeout: float | None = None, mirror: bool = False
73
73
  ) -> subprocess.CompletedProcess[bytes]:
74
74
  cmd = [
75
- self.binary,
75
+ *self.binary,
76
76
  "--bare-mode",
77
77
  "--console-verbosity=warning",
78
78
  "--multi",
@@ -165,9 +165,13 @@ def node() -> Iterator[dict[str, str]]:
165
165
  if context is None:
166
166
  raise RuntimeError("node fixture requires an active test context")
167
167
 
168
- node_binary = context.tenzir_node_binary or context.env.get("TENZIR_NODE_BINARY")
168
+ node_binary: tuple[str, ...] | None = context.tenzir_node_binary
169
169
  if not node_binary:
170
- raise RuntimeError("TENZIR_NODE_BINARY must be configured for the node fixture")
170
+ raise RuntimeError(
171
+ "tenzir-node binary not available. The harness checks: "
172
+ "TENZIR_NODE_BINARY env var, PATH lookup, uvx fallback. "
173
+ "Ensure tenzir-node is installed, uv is available, or set TENZIR_NODE_BINARY."
174
+ )
171
175
 
172
176
  env = context.env.copy()
173
177
  # Extract and filter config arguments: we handle --config and --package-dirs separately.
@@ -240,7 +244,7 @@ def node() -> Iterator[dict[str, str]]:
240
244
  test_root = context.test.parent
241
245
 
242
246
  node_cmd = [
243
- node_binary,
247
+ *node_binary,
244
248
  "--bare-mode",
245
249
  "--console-verbosity=warning",
246
250
  f"--state-directory={state_dir}",
@@ -292,9 +296,14 @@ def node() -> Iterator[dict[str, str]]:
292
296
  )
293
297
  raise RuntimeError(f"failed to obtain endpoint from tenzir-node ({detail})")
294
298
 
299
+ client_binary: str | None = None
300
+ if context.tenzir_binary:
301
+ client_binary = shlex.join(context.tenzir_binary)
302
+ else:
303
+ client_binary = env.get("TENZIR_BINARY")
295
304
  fixture_env = {
296
305
  "TENZIR_NODE_CLIENT_ENDPOINT": endpoint,
297
- "TENZIR_NODE_CLIENT_BINARY": context.tenzir_binary or env.get("TENZIR_BINARY"),
306
+ "TENZIR_NODE_CLIENT_BINARY": client_binary,
298
307
  "TENZIR_NODE_CLIENT_TIMEOUT": str(context.config["timeout"]),
299
308
  "TENZIR_NODE_STATE_DIRECTORY": str(state_dir),
300
309
  "TENZIR_NODE_CACHE_DIRECTORY": str(cache_dir),
tenzir_test/run.py CHANGED
@@ -176,8 +176,8 @@ def detect_execution_mode(root: Path) -> tuple[ExecutionMode, Path | None]:
176
176
 
177
177
 
178
178
  _settings: Settings | None = None
179
- TENZIR_BINARY: str | None = None
180
- TENZIR_NODE_BINARY: str | None = None
179
+ TENZIR_BINARY: tuple[str, ...] | None = None
180
+ TENZIR_NODE_BINARY: tuple[str, ...] | None = None
181
181
  ROOT: Path = Path.cwd()
182
182
  INPUTS_DIR: Path = ROOT / "inputs"
183
183
  EXECUTION_MODE: ExecutionMode = ExecutionMode.PROJECT
@@ -1866,9 +1866,9 @@ def get_test_env_and_config_args(
1866
1866
  if node_config_file.exists():
1867
1867
  env["TENZIR_NODE_CONFIG"] = str(node_config_file)
1868
1868
  if TENZIR_BINARY:
1869
- env["TENZIR_BINARY"] = TENZIR_BINARY
1869
+ env["TENZIR_BINARY"] = shlex.join(TENZIR_BINARY)
1870
1870
  if TENZIR_NODE_BINARY:
1871
- env["TENZIR_NODE_BINARY"] = TENZIR_NODE_BINARY
1871
+ env["TENZIR_NODE_BINARY"] = shlex.join(TENZIR_NODE_BINARY)
1872
1872
  env["TENZIR_TEST_ROOT"] = str(ROOT)
1873
1873
  tmp_dir = _create_test_tmp_dir(test)
1874
1874
  env[TEST_TMP_ENV_VAR] = str(tmp_dir)
@@ -2711,7 +2711,7 @@ def get_version() -> str:
2711
2711
  return (
2712
2712
  subprocess.check_output(
2713
2713
  [
2714
- TENZIR_BINARY,
2714
+ *TENZIR_BINARY,
2715
2715
  "--bare-mode",
2716
2716
  "--console-verbosity=warning",
2717
2717
  "version | select version | write_lines",
@@ -2949,7 +2949,7 @@ def run_simple_test(
2949
2949
  if not TENZIR_BINARY:
2950
2950
  raise RuntimeError("TENZIR_BINARY must be configured before running tests")
2951
2951
  cmd: list[str] = [
2952
- TENZIR_BINARY,
2952
+ *TENZIR_BINARY,
2953
2953
  "--bare-mode",
2954
2954
  "--console-verbosity=warning",
2955
2955
  "--multi",
@@ -3359,8 +3359,6 @@ def collect_all_tests(directory: Path) -> Iterator[Path]:
3359
3359
  def run_cli(
3360
3360
  *,
3361
3361
  root: Path | None,
3362
- tenzir_binary: Path | None,
3363
- tenzir_node_binary: Path | None,
3364
3362
  package_dirs: Sequence[Path] | None = None,
3365
3363
  tests: Sequence[Path],
3366
3364
  update: bool,
@@ -3448,11 +3446,7 @@ def run_cli(
3448
3446
  print(f"{INFO} ignoring --update in passthrough mode")
3449
3447
  update = False
3450
3448
 
3451
- settings = discover_settings(
3452
- root=root,
3453
- tenzir_binary=tenzir_binary,
3454
- tenzir_node_binary=tenzir_node_binary,
3455
- )
3449
+ settings = discover_settings(root=root)
3456
3450
  apply_settings(settings)
3457
3451
  _set_cli_packages(list(package_dirs or []))
3458
3452
  selected_tests = list(tests)
@@ -3796,8 +3790,6 @@ def run_cli(
3796
3790
  def execute(
3797
3791
  *,
3798
3792
  root: Path | None = None,
3799
- tenzir_binary: Path | None = None,
3800
- tenzir_node_binary: Path | None = None,
3801
3793
  tests: Sequence[Path] = (),
3802
3794
  update: bool = False,
3803
3795
  debug: bool = False,
@@ -3825,8 +3817,6 @@ def execute(
3825
3817
  resolved_jobs = jobs if jobs is not None else get_default_jobs()
3826
3818
  return run_cli(
3827
3819
  root=root,
3828
- tenzir_binary=tenzir_binary,
3829
- tenzir_node_binary=tenzir_node_binary,
3830
3820
  tests=list(tests),
3831
3821
  update=update,
3832
3822
  debug=debug,
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import os
5
+ import shlex
5
6
  import subprocess
6
7
  import sys
7
8
  import typing
@@ -99,9 +100,9 @@ class CustomPythonFixture(ExtRunner):
99
100
  endpoint = None
100
101
  new_pythonpath = os.pathsep.join(pythonpath_entries)
101
102
  env["PYTHONPATH"] = new_pythonpath
102
- env["TENZIR_NODE_CLIENT_BINARY"] = binary
103
+ env["TENZIR_NODE_CLIENT_BINARY"] = shlex.join(binary)
103
104
  env["TENZIR_NODE_CLIENT_TIMEOUT"] = str(timeout)
104
- env.setdefault("TENZIR_PYTHON_FIXTURE_BINARY", binary)
105
+ env.setdefault("TENZIR_PYTHON_FIXTURE_BINARY", shlex.join(binary))
105
106
  env["TENZIR_PYTHON_FIXTURE_TIMEOUT"] = str(timeout)
106
107
  if node_requested and endpoint:
107
108
  env["TENZIR_PYTHON_FIXTURE_ENDPOINT"] = endpoint
@@ -66,7 +66,7 @@ class DiffRunner(TqlRunner):
66
66
  binary = run_mod.TENZIR_BINARY
67
67
  if not binary:
68
68
  raise RuntimeError("TENZIR_BINARY must be configured for diff runners")
69
- base_cmd: list[str] = [binary, *config_args]
69
+ base_cmd: list[str] = [*binary, *config_args]
70
70
 
71
71
  if coverage:
72
72
  coverage_dir = env.get(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tenzir-test
3
- Version: 0.15.0
3
+ Version: 1.0.1
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
@@ -1,28 +1,28 @@
1
1
  tenzir_test/__init__.py,sha256=k7V6Pbjaa8SAy6t4KnaauHTyfnyVEwc1VGtH823MANU,1181
2
- tenzir_test/_python_runner.py,sha256=LmghMIolsNEC2wUyJdv1h_cefOxTxET1IACrw-_hHuY,2900
3
- tenzir_test/cli.py,sha256=e93O5dyp6oeiuW3Im11wWyuOc6duKTwUJmcmFuoVnoE,6803
4
- tenzir_test/config.py,sha256=bVuMJlEvevZxCmvzTJ4bs4SkYLNGZtxcCDYhtC-0sp8,1697
2
+ tenzir_test/_python_runner.py,sha256=ZODuZ6ll21gxBRj34iq-7lOmZOiqzFyu7wnPnS0K648,3130
3
+ tenzir_test/cli.py,sha256=WwddXpkjg3OIT5E54LOobaibDkGH9UvTLzZ9iMKw-qQ,6241
4
+ tenzir_test/config.py,sha256=z4ayS62SfOLNwrEgNktVeulyQ2QW4KUlN1KX0Za1NDM,2110
5
5
  tenzir_test/packages.py,sha256=cTCQdGjCS1XmuKyiwh0ew-z9tHn6J-xZ6nvBP-hU8bc,948
6
6
  tenzir_test/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- tenzir_test/run.py,sha256=OmW-qaWK6Y9vCAWZmnMczz-ycaleMa9jmiPcATpCoL8,133280
7
+ tenzir_test/run.py,sha256=cWCvK4N4fKnFnJe8QBZ9OPDgc9mFIzaNhuduG1L1Mzs,132979
8
8
  tenzir_test/engine/__init__.py,sha256=5APwy90YDm7rmL_qCZfToAcfbQthcZ8yV2_ExXKqaqE,110
9
9
  tenzir_test/engine/operations.py,sha256=OCYjuMHyMAaay4s08u2Sl7oE-PmgeXumylp7R8GYIH4,950
10
10
  tenzir_test/engine/registry.py,sha256=LXCr6TGlv1sR1m1eboTk7SrbS2IVErc3PqUuHxGA2xk,594
11
- tenzir_test/engine/state.py,sha256=Ez-Q27dL5oNqiJE0A4P2OP0p6i7aYKzH4ZyVWNgEezs,889
11
+ tenzir_test/engine/state.py,sha256=zolKmUWpTEajoq4gY8KYv_puCeS_DOpOTExBwLulNjo,879
12
12
  tenzir_test/engine/worker.py,sha256=WwIkx1m_ANNveQjNisy5-qpbUZl_DfDXxVIfdxABKjc,140
13
- tenzir_test/fixtures/__init__.py,sha256=PdkN334btlqagY-4wAwsCyfhHE8kd_RLNoVM_ULii_I,18384
14
- tenzir_test/fixtures/node.py,sha256=Y3YqWeqYz-mg3B5V0wIthk55zVOd4JS37zlTdHzlA6c,11193
13
+ tenzir_test/fixtures/__init__.py,sha256=H8deAoYg7HMPsZZ2UTO2KE1N82CbCQqN0bXk8q7B9ww,18441
14
+ tenzir_test/fixtures/node.py,sha256=iNT3H0y_9g-CzJO7-EPrAYBOX69O-g3lnuQiAvGqThc,11524
15
15
  tenzir_test/runners/__init__.py,sha256=M3p-TsDp231Dy58miDb467bA1kLYzgpa0pqVr_KP1ro,4616
16
16
  tenzir_test/runners/_utils.py,sha256=BWv7UEPGa01l4tGTCg5i_22NblIyRw8vjk_5NIf1x_c,467
17
- tenzir_test/runners/custom_python_fixture_runner.py,sha256=uXbSf18xTttf51k2EbjWf8AqN2R7TfpfSCkLyU5HJx4,7284
18
- tenzir_test/runners/diff_runner.py,sha256=WIEry_-4R4VIlA9HON5kl2ipyy1TKzS4jRc2wPl6r5c,5476
17
+ tenzir_test/runners/custom_python_fixture_runner.py,sha256=kJ4NYfLnB2ntZguv1pzIge_ith7D49YHKM35CJXU8MA,7321
18
+ tenzir_test/runners/diff_runner.py,sha256=zRLeoqwqLdIpP5weq20K0VQdIXPy7QFmZ462Hlg0L0k,5477
19
19
  tenzir_test/runners/ext_runner.py,sha256=sKL9Mw_ksVVBWnrdIJR2WS5ueVnLKuNYYWZ22FTZIPo,730
20
20
  tenzir_test/runners/runner.py,sha256=LtlD8huQOSmD7RyYDnKeCuI4Y6vhxGXMKsHA2qgfWN0,989
21
21
  tenzir_test/runners/shell_runner.py,sha256=OuofgHeZN2FaO6xRI3uyqstLBymc6rmWC4HAnSn91AE,6068
22
22
  tenzir_test/runners/tenzir_runner.py,sha256=464FFYS_mh6l-ehccc-S8cIUO1MxdapwQL5X3PmMkMI,1006
23
23
  tenzir_test/runners/tql_runner.py,sha256=2ZLMf3TIKwcOvaOFrVvvhzK-EcWmGOUZxKkbSoByyQA,248
24
- tenzir_test-0.15.0.dist-info/METADATA,sha256=MWDDBuFmf5BU8h1kBsj591GmYUPgv58-IZ357_iu0n8,3066
25
- tenzir_test-0.15.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
26
- tenzir_test-0.15.0.dist-info/entry_points.txt,sha256=l8DJgiEVrjScdTTo613cZ3PKodOmqrUVIbz-3awfV8w,53
27
- tenzir_test-0.15.0.dist-info/licenses/LICENSE,sha256=ajMbpcBiSTXI8Rr4t17pvowV-On8DktghfZKxY_A22Q,10750
28
- tenzir_test-0.15.0.dist-info/RECORD,,
24
+ tenzir_test-1.0.1.dist-info/METADATA,sha256=VxkVLX0mwLhiKxMPIAagcRdb6EssgTeCLQ_EGK5MpiA,3065
25
+ tenzir_test-1.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
26
+ tenzir_test-1.0.1.dist-info/entry_points.txt,sha256=l8DJgiEVrjScdTTo613cZ3PKodOmqrUVIbz-3awfV8w,53
27
+ tenzir_test-1.0.1.dist-info/licenses/LICENSE,sha256=ajMbpcBiSTXI8Rr4t17pvowV-On8DktghfZKxY_A22Q,10750
28
+ tenzir_test-1.0.1.dist-info/RECORD,,