proj-flow 0.21.0__py3-none-any.whl → 0.22.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.
- proj_flow/__init__.py +1 -1
- proj_flow/api/completers.py +1 -1
- proj_flow/api/env.py +37 -13
- proj_flow/api/release.py +1 -1
- proj_flow/api/step.py +7 -3
- proj_flow/{ext/cplusplus/cmake/presets.py → base/cmake_presets.py} +44 -24
- proj_flow/base/plugins.py +12 -7
- proj_flow/cli/finder.py +4 -3
- proj_flow/dependency.py +6 -2
- proj_flow/ext/cplusplus/cmake/__init__.py +2 -2
- proj_flow/ext/cplusplus/cmake/parser.py +4 -3
- proj_flow/ext/cplusplus/cmake/steps.py +3 -9
- proj_flow/ext/cplusplus/conan/__init__.py +6 -4
- proj_flow/ext/github/publishing.py +1 -0
- proj_flow/ext/python/rtdocs.py +2 -2
- proj_flow/ext/python/steps.py +5 -4
- proj_flow/ext/python/version.py +12 -12
- proj_flow/ext/sign/__init__.py +2 -2
- proj_flow/ext/test_runner/__init__.py +6 -0
- proj_flow/ext/test_runner/cli.py +416 -0
- proj_flow/ext/test_runner/driver/__init__.py +2 -0
- proj_flow/ext/test_runner/driver/commands.py +74 -0
- proj_flow/ext/test_runner/driver/test.py +610 -0
- proj_flow/ext/test_runner/driver/testbed.py +141 -0
- proj_flow/ext/test_runner/utils/__init__.py +2 -0
- proj_flow/ext/test_runner/utils/archives.py +56 -0
- proj_flow/log/rich_text/api.py +3 -4
- proj_flow/minimal/run.py +3 -2
- {proj_flow-0.21.0.dist-info → proj_flow-0.22.0.dist-info}/METADATA +1 -1
- {proj_flow-0.21.0.dist-info → proj_flow-0.22.0.dist-info}/RECORD +33 -25
- {proj_flow-0.21.0.dist-info → proj_flow-0.22.0.dist-info}/WHEEL +0 -0
- {proj_flow-0.21.0.dist-info → proj_flow-0.22.0.dist-info}/entry_points.txt +0 -0
- {proj_flow-0.21.0.dist-info → proj_flow-0.22.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# Copyright (c) 2026 Marcin Zdun
|
|
2
|
+
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
|
+
|
|
4
|
+
import concurrent.futures
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
from os import PathLike
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Annotated, Any, Generator, cast
|
|
13
|
+
|
|
14
|
+
from proj_flow.api import arg, env, release
|
|
15
|
+
from proj_flow.base.cmake_presets import Presets
|
|
16
|
+
from proj_flow.ext.test_runner.driver.commands import HANDLERS
|
|
17
|
+
from proj_flow.ext.test_runner.driver.test import Env, Test
|
|
18
|
+
from proj_flow.ext.test_runner.driver.testbed import Counters, task
|
|
19
|
+
|
|
20
|
+
RUN_LINEAR = os.environ.get("RUN_LINEAR", 0) != 0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@arg.command("tools", "test-runner")
|
|
24
|
+
def test_runner(
|
|
25
|
+
preset_name: Annotated[
|
|
26
|
+
str,
|
|
27
|
+
arg.Argument(
|
|
28
|
+
help="Set name of CMake build preset",
|
|
29
|
+
meta="CONFIG",
|
|
30
|
+
names=["--preset"],
|
|
31
|
+
),
|
|
32
|
+
],
|
|
33
|
+
tests: Annotated[
|
|
34
|
+
str,
|
|
35
|
+
arg.Argument(
|
|
36
|
+
help="Point to directory with the JSON test cases; test cases are enumerated recursively",
|
|
37
|
+
meta="DIR",
|
|
38
|
+
),
|
|
39
|
+
],
|
|
40
|
+
version: Annotated[
|
|
41
|
+
str | None,
|
|
42
|
+
arg.Argument(
|
|
43
|
+
help="Select version to patch output with; defaults to automatic detection",
|
|
44
|
+
meta="SEMVER",
|
|
45
|
+
opt=True,
|
|
46
|
+
),
|
|
47
|
+
],
|
|
48
|
+
run: Annotated[
|
|
49
|
+
list[str],
|
|
50
|
+
arg.Argument(
|
|
51
|
+
help="Filter the tests to run",
|
|
52
|
+
meta="ID",
|
|
53
|
+
action="extend",
|
|
54
|
+
opt=True,
|
|
55
|
+
nargs="*",
|
|
56
|
+
default=[],
|
|
57
|
+
),
|
|
58
|
+
],
|
|
59
|
+
nullify: Annotated[
|
|
60
|
+
bool,
|
|
61
|
+
arg.FlagArgument(
|
|
62
|
+
help='Set the "expected" field of the test cases to null',
|
|
63
|
+
),
|
|
64
|
+
],
|
|
65
|
+
rt: env.Runtime,
|
|
66
|
+
) -> int:
|
|
67
|
+
"""Run specified test steps"""
|
|
68
|
+
|
|
69
|
+
if os.name == "nt":
|
|
70
|
+
sys.stdout.reconfigure(encoding="utf-8") # type: ignore
|
|
71
|
+
|
|
72
|
+
if not version:
|
|
73
|
+
proj = release.get_project(rt)
|
|
74
|
+
version = str(proj.version)
|
|
75
|
+
|
|
76
|
+
presets = Presets().visit_file(Path("CMakePresets.json")) or {}
|
|
77
|
+
preset = presets.get(preset_name)
|
|
78
|
+
|
|
79
|
+
if preset is None:
|
|
80
|
+
print(f"error: preset `{preset_name}` not found", file=sys.stderr)
|
|
81
|
+
return 1
|
|
82
|
+
|
|
83
|
+
binary_dir = preset.expand()
|
|
84
|
+
build_type = preset.build_type
|
|
85
|
+
|
|
86
|
+
if not binary_dir:
|
|
87
|
+
print(
|
|
88
|
+
f"error: preset `{preset_name}` has no binaryDir attached to it",
|
|
89
|
+
file=sys.stderr,
|
|
90
|
+
)
|
|
91
|
+
return 1
|
|
92
|
+
|
|
93
|
+
if not build_type:
|
|
94
|
+
print(
|
|
95
|
+
f"error: preset `{preset_name}` has no CMAKE_BUILD_TYPE attached to it",
|
|
96
|
+
file=sys.stderr,
|
|
97
|
+
)
|
|
98
|
+
return 1
|
|
99
|
+
|
|
100
|
+
config = cast(dict, rt._cfg.get("test-runner", {}))
|
|
101
|
+
target = cast(str | None, config.get("target"))
|
|
102
|
+
|
|
103
|
+
if not isinstance(target, str):
|
|
104
|
+
print(
|
|
105
|
+
"error: cannot find test target; "
|
|
106
|
+
"please add name of the executable as `target' property of "
|
|
107
|
+
"`test-runner' config in flow's configuration file",
|
|
108
|
+
file=sys.stderr,
|
|
109
|
+
)
|
|
110
|
+
return 1
|
|
111
|
+
|
|
112
|
+
ext = ".exe" if sys.platform == "win32" else ""
|
|
113
|
+
target_path = Path(binary_dir) / "bin" / (target + ext)
|
|
114
|
+
if not target_path.is_file():
|
|
115
|
+
print(
|
|
116
|
+
f"error: cannot find {target + ext} in {target_path.parent.as_posix()}",
|
|
117
|
+
file=sys.stderr,
|
|
118
|
+
)
|
|
119
|
+
return 1
|
|
120
|
+
|
|
121
|
+
install_components = cast(list[str], config.get("install", []))
|
|
122
|
+
patches = cast(dict[str, str], config.get("patches"))
|
|
123
|
+
env_prefix = cast(str | None, config.get("report_env"))
|
|
124
|
+
|
|
125
|
+
testsuite_config = cast(dict, config.get("testsuite", {}))
|
|
126
|
+
test_root = cast(str | None, testsuite_config.get("root"))
|
|
127
|
+
test_root_path = Path(test_root).resolve() if test_root else None
|
|
128
|
+
if not test_root_path:
|
|
129
|
+
print(
|
|
130
|
+
"error: cannot find test root directory; "
|
|
131
|
+
"please add name of the directory as `root' property of "
|
|
132
|
+
"`test-runner/testsuite' config in flow's configuration file",
|
|
133
|
+
file=sys.stderr,
|
|
134
|
+
)
|
|
135
|
+
return 1
|
|
136
|
+
|
|
137
|
+
test_data_dir = cast(str | None, testsuite_config.get("data"))
|
|
138
|
+
data_dir = (
|
|
139
|
+
(test_root_path / test_data_dir).resolve()
|
|
140
|
+
if isinstance(test_data_dir, str)
|
|
141
|
+
else None
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
test_default_set = cast(str | None, testsuite_config.get("default-set"))
|
|
145
|
+
if isinstance(test_default_set, str):
|
|
146
|
+
if (
|
|
147
|
+
not (test_root_path / tests).is_dir()
|
|
148
|
+
and (test_root_path / test_default_set / tests).is_dir()
|
|
149
|
+
):
|
|
150
|
+
tests = f"{test_default_set}/{tests}"
|
|
151
|
+
|
|
152
|
+
test_set_dir = test_root_path / tests
|
|
153
|
+
|
|
154
|
+
test_files = _enum_tests(test_set_dir, data_dir)
|
|
155
|
+
tests_to_run = [int(x) for s in (run or []) for x in s.split(",")]
|
|
156
|
+
if not tests_to_run:
|
|
157
|
+
tests_to_run = list(range(1, len(test_files) + 1))
|
|
158
|
+
|
|
159
|
+
independent_tests, linear_tests = _load_tests(test_files, tests_to_run)
|
|
160
|
+
|
|
161
|
+
if nullify:
|
|
162
|
+
for sequence in (independent_tests, linear_tests):
|
|
163
|
+
for test in sequence:
|
|
164
|
+
test[0].nullify(lang=None)
|
|
165
|
+
return 0
|
|
166
|
+
|
|
167
|
+
if not independent_tests and not linear_tests:
|
|
168
|
+
print("No tests to run.", file=sys.stderr)
|
|
169
|
+
return 0
|
|
170
|
+
|
|
171
|
+
env = _make_env(
|
|
172
|
+
target_path,
|
|
173
|
+
data_dir or Path(binary_dir),
|
|
174
|
+
version,
|
|
175
|
+
len(independent_tests) + len(linear_tests),
|
|
176
|
+
patches,
|
|
177
|
+
env_prefix,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
print("target: ", env.target, env.version)
|
|
181
|
+
if env.data_dir_alt is None:
|
|
182
|
+
print("data: ", env.data_dir)
|
|
183
|
+
else:
|
|
184
|
+
print("data: ", env.data_dir, env.data_dir_alt)
|
|
185
|
+
print("tests: ", tests)
|
|
186
|
+
if env.tempdir_alt is None:
|
|
187
|
+
print("$TEMP: ", env.tempdir)
|
|
188
|
+
else:
|
|
189
|
+
print("$TEMP: ", env.tempdir, env.tempdir_alt)
|
|
190
|
+
|
|
191
|
+
os.makedirs(env.tempdir, exist_ok=True)
|
|
192
|
+
|
|
193
|
+
install_dir = Path("build").resolve() / ".test-runner"
|
|
194
|
+
if not _install(
|
|
195
|
+
install_dir,
|
|
196
|
+
binary_dir,
|
|
197
|
+
build_type,
|
|
198
|
+
install_components,
|
|
199
|
+
env,
|
|
200
|
+
):
|
|
201
|
+
return 1
|
|
202
|
+
|
|
203
|
+
return _run_and_report_tests(
|
|
204
|
+
independent_tests=independent_tests,
|
|
205
|
+
linear_tests=linear_tests,
|
|
206
|
+
install_dir=install_dir,
|
|
207
|
+
env=env,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _run_and_report_tests(
|
|
212
|
+
independent_tests: list[tuple[Test, int]],
|
|
213
|
+
linear_tests: list[tuple[Test, int]],
|
|
214
|
+
install_dir: Path,
|
|
215
|
+
env: Env,
|
|
216
|
+
):
|
|
217
|
+
RUN_LINEAR = os.environ.get("RUN_LINEAR", 0) != 0
|
|
218
|
+
if RUN_LINEAR:
|
|
219
|
+
linear_tests[0:0] = independent_tests
|
|
220
|
+
independent_tests = []
|
|
221
|
+
|
|
222
|
+
counters = Counters()
|
|
223
|
+
|
|
224
|
+
if independent_tests:
|
|
225
|
+
try:
|
|
226
|
+
thread_count = int(os.environ.get("POOL_SIZE", "not-a-number"))
|
|
227
|
+
except (ValueError, TypeError):
|
|
228
|
+
thread_count = max(1, (os.cpu_count() or 0)) * 2
|
|
229
|
+
print("threads:", thread_count)
|
|
230
|
+
|
|
231
|
+
_report_tests(counters, _run_async_tests(independent_tests, env, thread_count))
|
|
232
|
+
|
|
233
|
+
_report_tests(counters, _run_sync_tests(linear_tests, env))
|
|
234
|
+
|
|
235
|
+
shutil.rmtree(install_dir, ignore_errors=True)
|
|
236
|
+
shutil.rmtree("build/.testing", ignore_errors=True)
|
|
237
|
+
|
|
238
|
+
if not counters.summary(len(independent_tests) + len(linear_tests)):
|
|
239
|
+
return 1
|
|
240
|
+
|
|
241
|
+
return 0
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _run_async_tests(tests: list[tuple[Test, int]], env: Env, thread_count: int):
|
|
245
|
+
with concurrent.futures.ThreadPoolExecutor(thread_count) as executor:
|
|
246
|
+
futures: list[concurrent.futures.Future[tuple[int, str, str | None, str]]] = []
|
|
247
|
+
for test, counter in tests:
|
|
248
|
+
futures.append(executor.submit(task, env, test, counter))
|
|
249
|
+
|
|
250
|
+
for future in concurrent.futures.as_completed(futures):
|
|
251
|
+
yield future.result()
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _run_sync_tests(tests: list[tuple[Test, int]], env: Env):
|
|
255
|
+
for test, counter in tests:
|
|
256
|
+
yield task(env, test, counter)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _report_tests(
|
|
260
|
+
counters: Counters,
|
|
261
|
+
results: Generator[tuple[int, str, str | None, str], Any, None],
|
|
262
|
+
):
|
|
263
|
+
for outcome, test_id, message, tempdir in results:
|
|
264
|
+
counters.report(outcome, test_id, message)
|
|
265
|
+
shutil.rmtree(tempdir, ignore_errors=True)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _enum_tests(test_root: Path, data_dir: Path | None):
|
|
269
|
+
test_files: list[Path] = []
|
|
270
|
+
for root, _, files in test_root.walk():
|
|
271
|
+
for filename in files:
|
|
272
|
+
if not Path(filename).suffix in [".json", ".yaml", ".yml"]:
|
|
273
|
+
continue
|
|
274
|
+
test_path = root / filename
|
|
275
|
+
if data_dir and test_path.is_relative_to(data_dir):
|
|
276
|
+
continue
|
|
277
|
+
test_files.append(test_path)
|
|
278
|
+
|
|
279
|
+
return test_files
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _load_tests(testsuite: list[Path], run: list[int]):
|
|
283
|
+
counter: int = 0
|
|
284
|
+
independent_tests: list[tuple[Test, int]] = []
|
|
285
|
+
linear_tests: list[tuple[Test, int]] = []
|
|
286
|
+
|
|
287
|
+
for filename in sorted(testsuite):
|
|
288
|
+
counter += 1
|
|
289
|
+
if counter not in run:
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
test = Test.load(filename, counter)
|
|
294
|
+
if not test.ok:
|
|
295
|
+
continue
|
|
296
|
+
except Exception:
|
|
297
|
+
print(">>>>>>", filename.as_posix(), file=sys.stderr)
|
|
298
|
+
raise
|
|
299
|
+
|
|
300
|
+
if test.linear:
|
|
301
|
+
linear_tests.append((test, counter))
|
|
302
|
+
else:
|
|
303
|
+
independent_tests.append((test, counter))
|
|
304
|
+
|
|
305
|
+
return independent_tests, linear_tests
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _cmake_executable(binary_dir: str | PathLike[str]):
|
|
309
|
+
cache = Path(binary_dir) / "CMakeCache.txt"
|
|
310
|
+
if not cache.is_file():
|
|
311
|
+
return "cmake"
|
|
312
|
+
|
|
313
|
+
with cache.open(encoding="UTF-8") as cache_file:
|
|
314
|
+
for line in cache_file:
|
|
315
|
+
line = line.strip()
|
|
316
|
+
if not line.startswith("CMAKE_COMMAND:INTERNAL="):
|
|
317
|
+
continue
|
|
318
|
+
return line.split("=", 1)[1]
|
|
319
|
+
|
|
320
|
+
return "cmake"
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _run_with(test: Test, *args: str):
|
|
324
|
+
cwd = None if test.linear else test.cwd
|
|
325
|
+
subprocess.run(args, shell=False, cwd=cwd)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _target(target: str):
|
|
329
|
+
def impl(test: Test, aditional: list[str]):
|
|
330
|
+
_run_with(test, target, *aditional)
|
|
331
|
+
|
|
332
|
+
return impl
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _make_env(
|
|
336
|
+
target: Path,
|
|
337
|
+
data_dir: Path,
|
|
338
|
+
version: str,
|
|
339
|
+
counter_total: int,
|
|
340
|
+
patches: dict[str, str],
|
|
341
|
+
env_prefix: str | None,
|
|
342
|
+
):
|
|
343
|
+
target_name = target.stem if os.name == "nt" else target.name
|
|
344
|
+
tempdir = (Path(tempfile.gettempdir()) / "test-runner").resolve()
|
|
345
|
+
tempdir_alt = None
|
|
346
|
+
data_dir_alt = None
|
|
347
|
+
|
|
348
|
+
if os.sep != "/":
|
|
349
|
+
tempdir_alt = str(tempdir)
|
|
350
|
+
data_dir_alt = str(data_dir)
|
|
351
|
+
|
|
352
|
+
length = counter_total
|
|
353
|
+
digits = 1
|
|
354
|
+
while length > 9:
|
|
355
|
+
digits += 1
|
|
356
|
+
length = length // 10
|
|
357
|
+
|
|
358
|
+
return Env(
|
|
359
|
+
target=str(target),
|
|
360
|
+
target_name=target_name,
|
|
361
|
+
build_dir=str(target.parent.parent),
|
|
362
|
+
data_dir=data_dir.as_posix(),
|
|
363
|
+
inst_dir=str(data_dir.parent / "copy" / "bin"),
|
|
364
|
+
tempdir=tempdir.as_posix(),
|
|
365
|
+
version=version,
|
|
366
|
+
counter_digits=digits,
|
|
367
|
+
counter_total=counter_total,
|
|
368
|
+
handlers=HANDLERS,
|
|
369
|
+
data_dir_alt=data_dir_alt,
|
|
370
|
+
tempdir_alt=tempdir_alt,
|
|
371
|
+
builtin_patches=patches,
|
|
372
|
+
reportable_env_prefix=env_prefix,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _install(
|
|
377
|
+
dst: str | PathLike[str],
|
|
378
|
+
binary_dir: str | PathLike[str],
|
|
379
|
+
build_type: str,
|
|
380
|
+
components: list[str],
|
|
381
|
+
env: Env,
|
|
382
|
+
):
|
|
383
|
+
dst = Path(dst)
|
|
384
|
+
shutil.rmtree(dst, ignore_errors=True)
|
|
385
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
386
|
+
|
|
387
|
+
args = [
|
|
388
|
+
_cmake_executable(binary_dir),
|
|
389
|
+
"--install",
|
|
390
|
+
str(binary_dir),
|
|
391
|
+
"--config",
|
|
392
|
+
build_type,
|
|
393
|
+
"--prefix",
|
|
394
|
+
str(dst),
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
if not components:
|
|
398
|
+
proc = subprocess.run(args, capture_output=True)
|
|
399
|
+
return proc.returncode == 0
|
|
400
|
+
|
|
401
|
+
args.append("--component")
|
|
402
|
+
args.append("")
|
|
403
|
+
|
|
404
|
+
for component in components:
|
|
405
|
+
args[-1] = component
|
|
406
|
+
proc = subprocess.run(args, capture_output=True)
|
|
407
|
+
if proc.returncode != 0:
|
|
408
|
+
return False
|
|
409
|
+
|
|
410
|
+
env.target = str(dst / "bin" / os.path.basename(env.target))
|
|
411
|
+
target_name = os.path.split(env.target)[1]
|
|
412
|
+
if os.name == "nt":
|
|
413
|
+
target_name = os.path.splitext(target_name)[0]
|
|
414
|
+
env.handlers[target_name] = (0, _target(env.target))
|
|
415
|
+
|
|
416
|
+
return True
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Copyright (c) 2026 Marcin Zdun
|
|
2
|
+
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import stat
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Callable
|
|
9
|
+
|
|
10
|
+
from proj_flow.ext.test_runner.driver import test
|
|
11
|
+
from proj_flow.ext.test_runner.utils.archives import locate_unpack
|
|
12
|
+
|
|
13
|
+
_file_cache = {}
|
|
14
|
+
_rw_mask = stat.S_IWRITE | stat.S_IWGRP | stat.S_IWOTH
|
|
15
|
+
_ro_mask = 0o777 ^ _rw_mask
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _touch(test: test.Test, args: list[str]):
|
|
19
|
+
filename = test.path(args[0])
|
|
20
|
+
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
|
21
|
+
with open(filename, "wb") as f:
|
|
22
|
+
if len(args) > 1:
|
|
23
|
+
f.write(args[1].encode("UTF-8"))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _make_RO(test: test.Test, args: list[str]):
|
|
27
|
+
filename = test.path(args[0])
|
|
28
|
+
mode = os.stat(filename).st_mode
|
|
29
|
+
_file_cache[filename] = mode
|
|
30
|
+
os.chmod(filename, mode & _ro_mask)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _make_RW(test: test.Test, args: list[str]):
|
|
34
|
+
filename = test.path(args[0])
|
|
35
|
+
try:
|
|
36
|
+
mode = _file_cache[filename]
|
|
37
|
+
except KeyError:
|
|
38
|
+
mode = os.stat(filename).st_mode | _rw_mask
|
|
39
|
+
os.chmod(filename, mode)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _unpack(test: test.Test, args: list[str]):
|
|
43
|
+
archive = args[0]
|
|
44
|
+
dst = args[1]
|
|
45
|
+
unpack = locate_unpack(archive)[0]
|
|
46
|
+
unpack(archive, test.path(dst))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _cat(test: test.Test, args: list[str]):
|
|
50
|
+
filename = args[0]
|
|
51
|
+
with open(test.path(filename)) as f:
|
|
52
|
+
sys.stdout.write(f.read())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _shell(test: test.Test, args: list[str]):
|
|
56
|
+
print("shell!!!")
|
|
57
|
+
print("target:", test.current_env.target if test.current_env is not None else "?")
|
|
58
|
+
subprocess.call("pwsh" if os.name == "nt" else "bash", shell=True, cwd=test.cwd)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
HANDLERS: dict[str, tuple[int, Callable[[test.Test, list[str]], None]]] = {
|
|
62
|
+
"mkdirs": (1, lambda test, args: test.makedirs(args[0])),
|
|
63
|
+
"cd": (1, lambda test, args: test.chdir(args[0])),
|
|
64
|
+
"rm": (1, lambda test, args: test.rmtree(args[0])),
|
|
65
|
+
"touch": (1, _touch),
|
|
66
|
+
"unpack": (2, _unpack),
|
|
67
|
+
"store": (2, lambda test, args: test.store_output(args[0], args[1:])),
|
|
68
|
+
"ro": (1, _make_RO),
|
|
69
|
+
"cp": (2, lambda test, args: test.cp(args[0], args[1])),
|
|
70
|
+
"rw": (1, _make_RW),
|
|
71
|
+
"ls": (1, lambda test, args: test.ls(args[0])),
|
|
72
|
+
"cat": (1, _cat),
|
|
73
|
+
"shell": (0, _shell),
|
|
74
|
+
}
|