atex 0.7__py3-none-any.whl → 0.9__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.
- atex/cli/fmf.py +143 -0
- atex/cli/libvirt.py +127 -0
- atex/cli/testingfarm.py +35 -13
- atex/connection/__init__.py +13 -19
- atex/connection/podman.py +63 -0
- atex/connection/ssh.py +34 -52
- atex/executor/__init__.py +2 -0
- atex/executor/duration.py +60 -0
- atex/executor/executor.py +402 -0
- atex/executor/reporter.py +101 -0
- atex/{minitmt → executor}/scripts.py +37 -25
- atex/{minitmt → executor}/testcontrol.py +54 -42
- atex/fmf.py +237 -0
- atex/orchestrator/__init__.py +3 -59
- atex/orchestrator/aggregator.py +82 -134
- atex/orchestrator/orchestrator.py +385 -0
- atex/provision/__init__.py +74 -105
- atex/provision/libvirt/__init__.py +2 -24
- atex/provision/libvirt/libvirt.py +465 -0
- atex/provision/libvirt/locking.py +168 -0
- atex/provision/libvirt/setup-libvirt.sh +21 -1
- atex/provision/podman/__init__.py +1 -0
- atex/provision/podman/podman.py +274 -0
- atex/provision/testingfarm/__init__.py +2 -29
- atex/provision/testingfarm/api.py +123 -65
- atex/provision/testingfarm/testingfarm.py +234 -0
- atex/util/__init__.py +1 -6
- atex/util/libvirt.py +18 -0
- atex/util/log.py +31 -8
- atex/util/named_mapping.py +158 -0
- atex/util/path.py +16 -0
- atex/util/ssh_keygen.py +14 -0
- atex/util/threads.py +99 -0
- atex-0.9.dist-info/METADATA +178 -0
- atex-0.9.dist-info/RECORD +43 -0
- atex/cli/minitmt.py +0 -175
- atex/minitmt/__init__.py +0 -23
- atex/minitmt/executor.py +0 -348
- atex/minitmt/fmf.py +0 -202
- atex/provision/nspawn/README +0 -74
- atex/provision/podman/README +0 -59
- atex/provision/podman/host_container.sh +0 -74
- atex/provision/testingfarm/foo.py +0 -1
- atex-0.7.dist-info/METADATA +0 -102
- atex-0.7.dist-info/RECORD +0 -32
- {atex-0.7.dist-info → atex-0.9.dist-info}/WHEEL +0 -0
- {atex-0.7.dist-info → atex-0.9.dist-info}/entry_points.txt +0 -0
- {atex-0.7.dist-info → atex-0.9.dist-info}/licenses/COPYING.txt +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atex
|
|
3
|
+
Version: 0.9
|
|
4
|
+
Summary: Ad-hoc Test EXecutor
|
|
5
|
+
Project-URL: Homepage, https://github.com/RHSecurityCompliance/atex
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
License-File: COPYING.txt
|
|
8
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Topic :: Software Development :: Testing
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Requires-Dist: fmf>=1.6
|
|
13
|
+
Requires-Dist: pyyaml
|
|
14
|
+
Requires-Dist: urllib3<3,>=2
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# ATEX = Ad-hoc Test EXecutor
|
|
18
|
+
|
|
19
|
+
A collections of Python APIs to provision operating systems, collect
|
|
20
|
+
and execute [FMF](https://github.com/teemtee/fmf/)-style tests, gather
|
|
21
|
+
and organize their results and generate reports from those results.
|
|
22
|
+
|
|
23
|
+
The name comes from a (fairly unique to FMF/TMT ecosystem) approach that
|
|
24
|
+
allows provisioning a pool of systems and scheduling tests on them as one would
|
|
25
|
+
on an ad-hoc pool of thread/process workers - once a worker becomes free,
|
|
26
|
+
it receives a test to run.
|
|
27
|
+
This is in contrast to splitting a large list of N tests onto M workers
|
|
28
|
+
like N/M, which yields significant time penalties due to tests having
|
|
29
|
+
very varies runtimes.
|
|
30
|
+
|
|
31
|
+
Above all, this project is meant to be a toolbox, not a silver-plate solution.
|
|
32
|
+
Use its Python APIs to build a CLI tool for your specific use case.
|
|
33
|
+
The CLI tool provided here is just for demonstration / testing, not for serious
|
|
34
|
+
use - we want to avoid huge modular CLIs for Every Possible Scenario. That's
|
|
35
|
+
the job of the Python API. Any CLI should be simple by nature.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
THIS PROJECT IS HEAVILY WIP, THINGS WILL MOVE AROUND, CHANGE AND OTHERWISE
|
|
40
|
+
BREAK. DO NOT USE IT (for now).
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
Unless specified otherwise, any content within this repository is distributed
|
|
47
|
+
under the GNU GPLv3 license, see the [COPYING.txt](COPYING.txt) file for more.
|
|
48
|
+
|
|
49
|
+
## Testing this project
|
|
50
|
+
|
|
51
|
+
There are some limited sanity tests provided via `pytest`, although:
|
|
52
|
+
|
|
53
|
+
* Some require additional variables (ie. Testing Farm) and will ERROR
|
|
54
|
+
without them.
|
|
55
|
+
* Some take a long time (ie. Testing Farm) due to system provisioning
|
|
56
|
+
taking a long time, so install `pytest-xdist` and run with a large `-n`.
|
|
57
|
+
|
|
58
|
+
Currently, the recommended approach is to split the execution:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
# synchronously, because podman CLI has concurrency issues
|
|
62
|
+
pytest tests/provision/test_podman.py
|
|
63
|
+
|
|
64
|
+
# in parallel, because provisioning takes a long time
|
|
65
|
+
export TESTING_FARM_API_TOKEN=...
|
|
66
|
+
export TESTING_FARM_COMPOSE=...
|
|
67
|
+
pytest -n 20 tests/provision/test_podman.py
|
|
68
|
+
|
|
69
|
+
# fast enough for synchronous execution
|
|
70
|
+
pytest tests/fmf
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Parallelism and cleanup
|
|
74
|
+
|
|
75
|
+
There are effectively 3 methods of running things in parallel in Python:
|
|
76
|
+
|
|
77
|
+
- `threading.Thread` (and related `concurrent.futures` classes)
|
|
78
|
+
- `multiprocessing.Process` (and related `concurrent.futures` classes)
|
|
79
|
+
- `asyncio`
|
|
80
|
+
|
|
81
|
+
and there is no clear winner (in terms of cleanup on `SIGTERM` or Ctrl-C):
|
|
82
|
+
|
|
83
|
+
- `Thread` has signal handlers only in the main thread and is unable to
|
|
84
|
+
interrupt any running threads without super ugly workarounds like `sleep(1)`
|
|
85
|
+
in every thread, checking some "pls exit" variable
|
|
86
|
+
- `Process` is too heavyweight and makes sharing native Python objects hard,
|
|
87
|
+
but it does handle signals in each process individually
|
|
88
|
+
- `asyncio` handles interrupting perfectly (every `try`/`except`/`finally`
|
|
89
|
+
completes just fine, `KeyboardInterrupt` is raised in every async context),
|
|
90
|
+
but async python is still (3.14) too weird and unsupported
|
|
91
|
+
- `asyncio` effectively re-implements `subprocess` with a slightly different
|
|
92
|
+
API, same with `asyncio.Transport` and derivatives reimplementing `socket`
|
|
93
|
+
- 3rd party libraries like `requests` or `urllib3` don't support it, one needs
|
|
94
|
+
to resort to spawning these in separate threads anyway
|
|
95
|
+
- same with `os.*` functions and syscalls
|
|
96
|
+
- every thing exposed via API needs to have 2 copies - async and non-async,
|
|
97
|
+
making it unbearable
|
|
98
|
+
- other stdlib bugs, ie. "large" reads returning BlockingIOError sometimes
|
|
99
|
+
|
|
100
|
+
The approach chosen by this project was to use `threading.Thread`, and
|
|
101
|
+
implement thread safety for classes and their functions that need it.
|
|
102
|
+
For example:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
class MachineReserver:
|
|
106
|
+
def __init__(self):
|
|
107
|
+
self.lock = threading.RLock()
|
|
108
|
+
self.job = None
|
|
109
|
+
self.proc = None
|
|
110
|
+
|
|
111
|
+
def reserve(self, ...):
|
|
112
|
+
try:
|
|
113
|
+
...
|
|
114
|
+
job = schedule_new_job_on_external_service()
|
|
115
|
+
with self.lock:
|
|
116
|
+
self.job = job
|
|
117
|
+
...
|
|
118
|
+
while not reserved(self.job):
|
|
119
|
+
time.sleep(60)
|
|
120
|
+
...
|
|
121
|
+
with self.lock:
|
|
122
|
+
self.proc = subprocess.Popen(["ssh", f"{user}@{host}", ...)
|
|
123
|
+
...
|
|
124
|
+
return machine
|
|
125
|
+
except Exception:
|
|
126
|
+
self.abort()
|
|
127
|
+
raise
|
|
128
|
+
|
|
129
|
+
def abort(self):
|
|
130
|
+
with self.lock:
|
|
131
|
+
if self.job:
|
|
132
|
+
cancel_external_service(self.job)
|
|
133
|
+
self.job = None
|
|
134
|
+
if self.proc:
|
|
135
|
+
self.proc.kill()
|
|
136
|
+
self.proc = None
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Here, it is expected for `.reserve()` to be called in a long-running thread that
|
|
140
|
+
provisions a new machine on some external service, waits for it to be installed
|
|
141
|
+
and reserved, connects an ssh session to it and returns it back.
|
|
142
|
+
|
|
143
|
+
But equally, `.abort()` can be called from an external thread and clean up any
|
|
144
|
+
non-pythonic resources (external jobs, processes, temporary files, etc.) at
|
|
145
|
+
which point **we don't care what happens to .reserve()**, it will probably fail
|
|
146
|
+
with some exception, but doesn't do any harm.
|
|
147
|
+
|
|
148
|
+
Here is where `daemon=True` threads come in handy - we can simply call `.abort()`
|
|
149
|
+
from a `KeyboardInterrupt` (or `SIGTERM`) handle in the main thread, and just
|
|
150
|
+
exit, automatically killing any leftover threads that are uselessly sleeping.
|
|
151
|
+
(Realistically, we might want to spawn new threads to run many `.abort()`s in
|
|
152
|
+
parallel, but the main thread can wait for those just fine.)
|
|
153
|
+
|
|
154
|
+
It is not perfect, but it's probably the best Python can do.
|
|
155
|
+
|
|
156
|
+
Note that races can still occur between a resource being reserved and written
|
|
157
|
+
to `self.*` for `.abort()` to free, so resource de-allocation is not 100%
|
|
158
|
+
guaranteed, but single-threaded interrupting has the same issue.
|
|
159
|
+
Do have fallbacks (ie. max reserve times on the external service).
|
|
160
|
+
|
|
161
|
+
Also note that `.reserve()` and `.abort()` could be also called by a context
|
|
162
|
+
manager as `__enter__` and `__exit__`, ie. by a non-threaded caller (running
|
|
163
|
+
everything in the main thread).
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
## Unsorted notes
|
|
167
|
+
|
|
168
|
+
TODO: codestyle from contest
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
- this is not tmt, the goal is to make a python toolbox *for* making runcontest
|
|
172
|
+
style tools easily, not to replace those tools with tmt-style CLI syntax
|
|
173
|
+
|
|
174
|
+
- the whole point is to make usecase-targeted easy-to-use tools that don't
|
|
175
|
+
intimidate users with 1 KB long command line, and runcontest is a nice example
|
|
176
|
+
|
|
177
|
+
- TL;DR - use a modular pythonic approach, not a gluetool-style long CLI
|
|
178
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
atex/__init__.py,sha256=LdX67gprtHYeAkjLhFPKzpc7ECv2rHxUbHKDGbGXO1c,517
|
|
2
|
+
atex/fmf.py,sha256=gkJXIaRO7_KvwJR-V6Tc1NVn4a9Hq7hoBLQLhxYIdbg,8834
|
|
3
|
+
atex/cli/__init__.py,sha256=erHv68SsybRbdgJ60013y9jVqY1ec-cb9T9ThPCJ_HY,2408
|
|
4
|
+
atex/cli/fmf.py,sha256=HfbTgFbCwK4Nuyq6vtGutcq_4-4kj-tmoqzXUn3AYtY,3573
|
|
5
|
+
atex/cli/libvirt.py,sha256=vGcan9u0Nor_DaEjgOj3Yzjtlq4WNVLr5oYMb8zFb6M,3743
|
|
6
|
+
atex/cli/testingfarm.py,sha256=DHMupwU3AjbkwkzvzdhlxtsuBnxVaba5i8T8g9sKRug,7569
|
|
7
|
+
atex/connection/__init__.py,sha256=dj8ZBcEspom7Z_UjecfLGBRNvLZ3dyGR9q19i_B4xpY,3880
|
|
8
|
+
atex/connection/podman.py,sha256=JUsi4jOkNFGsEHCqUQF003R2yRfJPsMnUSBEZM3H3Kk,1953
|
|
9
|
+
atex/connection/ssh.py,sha256=c8v01mj0pf6VU8z2cLLCvPGPmGUZp8wQsTG441k3Qy4,13674
|
|
10
|
+
atex/executor/__init__.py,sha256=XCfhi7QDELjey7N1uzhMjc46Kp1Jsd5bOCf52I27SCE,85
|
|
11
|
+
atex/executor/duration.py,sha256=x06sItKOZi6XA8KszQwZGpIb1Z_L-HWqIwZKo2SDo0s,1759
|
|
12
|
+
atex/executor/executor.py,sha256=OcKBXQ77OktmzPWAbdCb81Yj44Jr4SEkwW_CxdEhFP4,15674
|
|
13
|
+
atex/executor/reporter.py,sha256=MceFmHFt0bTEClBZbRI1WnFbfMhR0e1noOzcu7gjKuQ,3403
|
|
14
|
+
atex/executor/scripts.py,sha256=6xqP-m_9UCVb3z0tCWIMczimz_cTeCIJucSp0AGYsRw,5567
|
|
15
|
+
atex/executor/testcontrol.py,sha256=O4V4jRx7lZv-El1i9jkPha-aWzjq_8m6loK7sNGVD14,12695
|
|
16
|
+
atex/orchestrator/__init__.py,sha256=GSHtWoZjJrLiatjUHsMAkYB8VebSQD9ApQw0ymCrGTQ,212
|
|
17
|
+
atex/orchestrator/aggregator.py,sha256=naypN8T42iPau_4eLeXak-INR1_vsYhP_QSp3xLM_FA,3837
|
|
18
|
+
atex/orchestrator/orchestrator.py,sha256=qAwka7MqpAWA6mLgsMl1m1HpQP2mERcaAASNvJwgfTM,14570
|
|
19
|
+
atex/provision/__init__.py,sha256=Pj_JnDaJA8DzKRGmLcc2JiwPsuQc2TYlE-hDR56SwTY,4083
|
|
20
|
+
atex/provision/libvirt/VM_PROVISION,sha256=7pkZ-ozgTyK4qNGC-E-HUznr4IhbosWSASbB72Gknl8,2664
|
|
21
|
+
atex/provision/libvirt/__init__.py,sha256=pKG5IpZSC2IHs5wL2ecQx_fd9AzAXEbZmDzA7RyZsfM,119
|
|
22
|
+
atex/provision/libvirt/libvirt.py,sha256=7wux-zhQoNODJoPB4iMqPlGEJe7rEEZeLSOtMU3TB0M,18160
|
|
23
|
+
atex/provision/libvirt/locking.py,sha256=e9zbtR0uzJZBAzucfhs9VQaDCx-jp0pWJi1pb7JPK4w,5630
|
|
24
|
+
atex/provision/libvirt/setup-libvirt.sh,sha256=oCMy9SCnbC_QuAzO2sFwvB5ui1kMQ6uviHsgdXyoFXc,2428
|
|
25
|
+
atex/provision/podman/__init__.py,sha256=V9miIDQV-CyDVcbf2-1qtDbXDhhZzJza5oXas-JxI8o,66
|
|
26
|
+
atex/provision/podman/podman.py,sha256=FJK4uLDuHBj8_3Wlg4a-4JgGIRGyLX4v7UVcxEorHbA,9578
|
|
27
|
+
atex/provision/testingfarm/__init__.py,sha256=kZncgLGdRCR4FMaRQr2GTwJ8vjlA-24ri8JO2ueZJuw,113
|
|
28
|
+
atex/provision/testingfarm/api.py,sha256=9xeoIN28KumJyMUEUoyEUZcIxVH9G34Wecyy3bp2Cmg,21612
|
|
29
|
+
atex/provision/testingfarm/testingfarm.py,sha256=-Q33FAretSGPWdmNJUywJZVfXYGtsAfLP5uqRZTGfQQ,8631
|
|
30
|
+
atex/util/__init__.py,sha256=cWHFbtQ4mDlKe6lXyPDWRmWJOTcHDGfVuW_-GYa8hB0,1473
|
|
31
|
+
atex/util/dedent.py,sha256=SEuJMtLzqz3dQ7g7qyZzEJ9VYynVlk52tQCJY-FveXo,603
|
|
32
|
+
atex/util/libvirt.py,sha256=kDZmT6xLYEZkQNLZY98gJ2M48DDWXxHF8rQY9PnjB3U,660
|
|
33
|
+
atex/util/log.py,sha256=70f6YiF2RwNUfE5BXZMSQonzfI_uxQoD7_S4bRD_Btw,2466
|
|
34
|
+
atex/util/named_mapping.py,sha256=UBMe9TetjV-DGPhjYjJ42YtC40FVPKAAEROXl9MA5fo,4700
|
|
35
|
+
atex/util/path.py,sha256=x-kXqiWCVodfZWbEwtC5A8LFvutpDIPYv2m0boZSlXU,504
|
|
36
|
+
atex/util/ssh_keygen.py,sha256=9yuSl2yBV7pG3Qfsf9tossVC00nbIUrAeLdbwTykpjk,384
|
|
37
|
+
atex/util/subprocess.py,sha256=IQT9QHe2kMaaO_XPSry-DwObYstGsq6_QdwdbhYDjko,1826
|
|
38
|
+
atex/util/threads.py,sha256=46-5nV-qJqi1YZ4qEshmZXGUxr8j9_9xT9eEpkjRr5I,3355
|
|
39
|
+
atex-0.9.dist-info/METADATA,sha256=iLGFHVxeK9KWrPaRdL4hkbNkb-OgJkeohHhxYz90tAI,6857
|
|
40
|
+
atex-0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
41
|
+
atex-0.9.dist-info/entry_points.txt,sha256=pLqJdcfeyQTgup2h6dWb6SvkHhtOl-W5Eg9zV8moK0o,39
|
|
42
|
+
atex-0.9.dist-info/licenses/COPYING.txt,sha256=oEuj51jdmbXcCUy7pZ-KE0BNcJTR1okudRp5zQ0yWnU,670
|
|
43
|
+
atex-0.9.dist-info/RECORD,,
|
atex/cli/minitmt.py
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
#import re
|
|
3
|
-
import pprint
|
|
4
|
-
#import subprocess
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from .. import connection, provision, minitmt
|
|
8
|
-
from ..orchestrator import aggregator
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _fatal(msg):
|
|
12
|
-
print(msg, file=sys.stderr)
|
|
13
|
-
sys.exit(1)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _get_context(args):
|
|
17
|
-
context = {}
|
|
18
|
-
if args.context:
|
|
19
|
-
for c in args.context:
|
|
20
|
-
key, value = c.split("=", 1)
|
|
21
|
-
context[key] = value
|
|
22
|
-
return context or None
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def discover(args):
|
|
26
|
-
result = minitmt.fmf.FMFTests(args.root, args.plan, context=_get_context(args))
|
|
27
|
-
for name in result.tests:
|
|
28
|
-
print(name)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def show(args):
|
|
32
|
-
result = minitmt.fmf.FMFTests(args.root, args.plan, context=_get_context(args))
|
|
33
|
-
if tests := list(result.match(args.test)):
|
|
34
|
-
for test in tests:
|
|
35
|
-
print(f"\n--- {test.name} ---")
|
|
36
|
-
pprint.pprint(test.data)
|
|
37
|
-
else:
|
|
38
|
-
_fatal(f"Not reachable via {args.plan} discovery: {args.test}")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def execute(args):
|
|
42
|
-
# remote system connection
|
|
43
|
-
ssh_keypath = Path(args.ssh_identity)
|
|
44
|
-
if not ssh_keypath.exists():
|
|
45
|
-
_fatal(f"SSH Identity {args.ssh_identity} does not exist")
|
|
46
|
-
ssh_options = {
|
|
47
|
-
"User": args.user,
|
|
48
|
-
"Hostname": args.host,
|
|
49
|
-
"IdentityFile": ssh_keypath,
|
|
50
|
-
}
|
|
51
|
-
env = dict(x.split("=",1) for x in args.env)
|
|
52
|
-
|
|
53
|
-
# dummy Remote that just wraps the connection
|
|
54
|
-
class DummyRemote(provision.Remote, connection.ssh.ManagedSSHConn):
|
|
55
|
-
@staticmethod
|
|
56
|
-
def release():
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
@staticmethod
|
|
60
|
-
def alive():
|
|
61
|
-
return True
|
|
62
|
-
|
|
63
|
-
# result aggregation
|
|
64
|
-
with aggregator.CSVAggregator(args.results_csv, args.results_dir) as csv_aggregator:
|
|
65
|
-
platform_aggregator = csv_aggregator.for_platform(args.platform)
|
|
66
|
-
|
|
67
|
-
# tests discovery and selection
|
|
68
|
-
result = minitmt.fmf.FMFTests(args.root, args.plan, context=_get_context(args))
|
|
69
|
-
if args.test:
|
|
70
|
-
tests = list(result.match(args.test))
|
|
71
|
-
if not tests:
|
|
72
|
-
_fatal(f"Not reachable via plan {args.plan} discovery: {args.test}")
|
|
73
|
-
else:
|
|
74
|
-
tests = list(result.as_fmftests())
|
|
75
|
-
if not tests:
|
|
76
|
-
_fatal(f"No tests found for plan {args.plan}")
|
|
77
|
-
|
|
78
|
-
# test run
|
|
79
|
-
with DummyRemote(ssh_options) as remote:
|
|
80
|
-
executor = minitmt.executor.Executor(remote, platform_aggregator, env=env)
|
|
81
|
-
executor.upload_tests(args.root)
|
|
82
|
-
executor.setup_plan(result)
|
|
83
|
-
for test in tests:
|
|
84
|
-
executor.run_test(test)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def setup_script(args):
|
|
88
|
-
result = minitmt.fmf.FMFTests(args.root, args.plan, context=_get_context(args))
|
|
89
|
-
try:
|
|
90
|
-
test = result.as_fmftest(args.test)
|
|
91
|
-
except KeyError:
|
|
92
|
-
print(f"Not reachable via {args.plan} discovery: {args.test}")
|
|
93
|
-
raise SystemExit(1) from None
|
|
94
|
-
output = minitmt.scripts.test_setup(
|
|
95
|
-
test=test,
|
|
96
|
-
tests_dir=args.remote_root,
|
|
97
|
-
debug=args.script_debug,
|
|
98
|
-
)
|
|
99
|
-
print(output, end="")
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def parse_args(parser):
|
|
103
|
-
parser.add_argument("--root", help="path to directory with fmf tests", default=".")
|
|
104
|
-
parser.add_argument("--context", "-c", help="tmt style key=value context", action="append")
|
|
105
|
-
cmds = parser.add_subparsers(
|
|
106
|
-
dest="_cmd", help="minitmt feature", metavar="<cmd>", required=True,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
cmd = cmds.add_parser(
|
|
110
|
-
"discover", aliases=("di",),
|
|
111
|
-
help="list tests, post-processed by tmt plans",
|
|
112
|
-
)
|
|
113
|
-
cmd.add_argument("plan", help="tmt plan to use for discovery")
|
|
114
|
-
|
|
115
|
-
cmd = cmds.add_parser(
|
|
116
|
-
"show",
|
|
117
|
-
help="show fmf data of a test",
|
|
118
|
-
)
|
|
119
|
-
cmd.add_argument("plan", help="tmt plan to use for discovery")
|
|
120
|
-
cmd.add_argument("test", help="fmf style test regex")
|
|
121
|
-
|
|
122
|
-
cmd = cmds.add_parser(
|
|
123
|
-
"execute", aliases=("ex",),
|
|
124
|
-
help="run a plan (or test) on a remote system",
|
|
125
|
-
)
|
|
126
|
-
#grp = cmd.add_mutually_exclusive_group()
|
|
127
|
-
#grp.add_argument("--test", "-t", help="fmf style test regex")
|
|
128
|
-
#grp.add_argument("--plan", "-p", help="tmt plan name (path) inside metadata root")
|
|
129
|
-
cmd.add_argument("--env", "-e", help="environment to pass to prepare/test", action="append")
|
|
130
|
-
cmd.add_argument("--test", "-t", help="fmf style test regex")
|
|
131
|
-
cmd.add_argument(
|
|
132
|
-
"--plan", "-p", help="tmt plan name (path) inside metadata root", required=True,
|
|
133
|
-
)
|
|
134
|
-
cmd.add_argument("--platform", help="platform name, ie. rhel9@x86_64", required=True)
|
|
135
|
-
cmd.add_argument("--user", help="ssh user to connect via", required=True)
|
|
136
|
-
cmd.add_argument("--host", help="ssh host to connect to", required=True)
|
|
137
|
-
cmd.add_argument(
|
|
138
|
-
"--ssh-identity", help="path to a ssh keyfile for login", required=True,
|
|
139
|
-
)
|
|
140
|
-
cmd.add_argument(
|
|
141
|
-
"--results-csv", help="path to would-be-created .csv.gz results", required=True,
|
|
142
|
-
)
|
|
143
|
-
cmd.add_argument(
|
|
144
|
-
"--results-dir", help="path to would-be-created dir for uploaded files", required=True,
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
cmd = cmds.add_parser(
|
|
148
|
-
"setup-script",
|
|
149
|
-
help="generate a script prepping tests for run",
|
|
150
|
-
)
|
|
151
|
-
cmd.add_argument("--remote-root", help="path to tests repo on the remote", required=True)
|
|
152
|
-
cmd.add_argument("--script-debug", help="do 'set -x' in the script", action="store_true")
|
|
153
|
-
cmd.add_argument("plan", help="tmt plan to use for discovery")
|
|
154
|
-
cmd.add_argument("test", help="full fmf test name (not regex)")
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def main(args):
|
|
158
|
-
if args._cmd in ("discover", "di"):
|
|
159
|
-
discover(args)
|
|
160
|
-
elif args._cmd == "show":
|
|
161
|
-
show(args)
|
|
162
|
-
elif args._cmd in ("execute", "ex"):
|
|
163
|
-
execute(args)
|
|
164
|
-
elif args._cmd == "setup-script":
|
|
165
|
-
setup_script(args)
|
|
166
|
-
else:
|
|
167
|
-
raise RuntimeError(f"unknown args: {args}")
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
CLI_SPEC = {
|
|
171
|
-
"aliases": ("tmt",),
|
|
172
|
-
"help": "simple test executor using atex.minitmt",
|
|
173
|
-
"args": parse_args,
|
|
174
|
-
"main": main,
|
|
175
|
-
}
|
atex/minitmt/__init__.py
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
TODO Minitmt documentation - reference README, etc.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import importlib as _importlib
|
|
6
|
-
import pkgutil as _pkgutil
|
|
7
|
-
|
|
8
|
-
__all__ = [
|
|
9
|
-
info.name for info in _pkgutil.iter_modules(__spec__.submodule_search_locations)
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def __dir__():
|
|
14
|
-
return __all__
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# lazily import submodules
|
|
18
|
-
def __getattr__(attr):
|
|
19
|
-
# importing a module known to exist
|
|
20
|
-
if attr in __all__:
|
|
21
|
-
return _importlib.import_module(f".{attr}", __name__)
|
|
22
|
-
else:
|
|
23
|
-
raise AttributeError(f"module '{__name__}' has no attribute '{attr}'")
|