Skip to content
This repository was archived by the owner on Dec 27, 2023. It is now read-only.

Commit f051172

Browse files
committed
Add altp package for parallel execution
This patch adds altp package that is an alternative implementation of ltp package, using asyncio for parallel tests execution. In order to use it, please set ASYNC_RUN before run. The altp package also introduces a new UI for parallel execution and it replaces paramiko library with asyncssh, since paramiko doesn't support coroutines. Beware this is an EXPERIMENTAL support and it's not the ending version.
1 parent d192522 commit f051172

36 files changed

+7869
-9
lines changed

.github/workflows/linting.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
fail-fast: false
1212
matrix:
13-
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]
13+
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0"]
1414

1515
steps:
1616
- name: Show OS
@@ -31,4 +31,4 @@ jobs:
3131
run: python3 runltp-ng --help
3232

3333
- name: Lint with pylint
34-
run: pylint --rcfile=pylint.ini ltp
34+
run: pylint --rcfile=pylint.ini ltp altp

.github/workflows/tests.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
fail-fast: false
1212
matrix:
13-
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]
13+
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0"]
1414

1515
steps:
1616
- name: Show OS
@@ -25,7 +25,7 @@ jobs:
2525
python-version: ${{ matrix.python-version }}
2626

2727
- name: Install dependencies
28-
run: python3 -m pip install pytest pytest-xdist
28+
run: python3 -m pip install asyncssh pytest pytest-asyncio
2929

3030
- name: Test with pytest
31-
run: python3 -m pytest -n 8 -m "not qemu and not ssh"
31+
run: python3 -m pytest -m "not qemu and not ssh and not ltx"

Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ INSTALL_DIR := $(BASE_DIR)/runltp-ng.d
1212

1313
install:
1414
mkdir -p $(INSTALL_DIR)/ltp
15+
mkdir -p $(INSTALL_DIR)/altp
1516

1617
install -m 00644 $(top_srcdir)/tools/runltp-ng/ltp/*.py $(INSTALL_DIR)/ltp
18+
install -m 00644 $(top_srcdir)/tools/runltp-ng/ltp/*.py $(INSTALL_DIR)/altp
1719
install -m 00775 $(top_srcdir)/tools/runltp-ng/runltp-ng $(BASE_DIR)/runltp-ng
1820

1921
include $(top_srcdir)/include/mk/generic_leaf_target.mk

README.md

+21
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,27 @@ Once a new SUT class is implemented and placed inside the `ltp` package folder,
116116
`runltp-ng -s help` command can be used to see if application correctly
117117
recognise it.
118118

119+
Parallel execution
120+
==================
121+
122+
The tool now supports a new experimental feature that is implemented inside the
123+
`altp` folder. This particular feature permits to execute multiple tests in
124+
parallel when using host execution or SSH protocol.
125+
126+
To enable the new parallel execution feature, please set the `ASYNC_RUN` flag
127+
as following:
128+
129+
# run syscalls testing suite in parallel on host using 16 workers
130+
ASYNC_RUN=1 ./runltp-ng --run-suite syscalls --workers 16
131+
132+
# run syscalls testing suite in parallel via SSH using 16 workers
133+
# NOTE: asyncssh package must be installed in the system
134+
./runltp-ng --sut=ssh:host myhost.com:user=root:key_file=myhost_id_rsa \
135+
--run-suite syscalls --workers 16
136+
137+
Unfortunately, `paramiko` doesn't support parallel commands execution, so we
138+
switched into `asyncssh` library that is also way more easy to use.
139+
119140
Development
120141
===========
121142

altp/__init__.py

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""
2+
.. module:: __init__
3+
:platform: Linux
4+
:synopsis: ltp package definition
5+
6+
.. moduleauthor:: Andrea Cervesato <[email protected]>
7+
"""
8+
import sys
9+
import signal
10+
import typing
11+
import asyncio
12+
from altp.events import EventsHandler
13+
14+
15+
class LTPException(Exception):
16+
"""
17+
The most generic exception that is raised by any ltp package when
18+
something bad happens.
19+
"""
20+
pass
21+
22+
23+
events = EventsHandler()
24+
25+
26+
def get_event_loop() -> asyncio.BaseEventLoop:
27+
"""
28+
Return the current asyncio event loop.
29+
"""
30+
loop = None
31+
32+
try:
33+
loop = asyncio.get_running_loop()
34+
except (AttributeError, RuntimeError):
35+
pass
36+
37+
if not loop:
38+
try:
39+
loop = asyncio.get_event_loop()
40+
except RuntimeError:
41+
pass
42+
43+
if not loop:
44+
loop = asyncio.new_event_loop()
45+
asyncio.set_event_loop(loop)
46+
47+
return loop
48+
49+
50+
def create_task(coro: typing.Coroutine) -> asyncio.Task:
51+
"""
52+
Create a new task.
53+
"""
54+
loop = get_event_loop()
55+
task = loop.create_task(coro)
56+
57+
return task
58+
59+
60+
def cancel_tasks(loop: asyncio.AbstractEventLoop) -> None:
61+
"""
62+
Cancel all asyncio running tasks.
63+
"""
64+
to_cancel = None
65+
66+
# pylint: disable=no-member
67+
if sys.version_info >= (3, 7):
68+
to_cancel = asyncio.all_tasks(loop=loop)
69+
else:
70+
to_cancel = asyncio.Task.all_tasks(loop=loop)
71+
72+
if not to_cancel:
73+
return
74+
75+
for task in to_cancel:
76+
if task.cancelled():
77+
continue
78+
79+
task.cancel()
80+
81+
# pylint: disable=deprecated-argument
82+
if sys.version_info >= (3, 10):
83+
loop.run_until_complete(
84+
asyncio.gather(*to_cancel, return_exceptions=True))
85+
else:
86+
loop.run_until_complete(
87+
asyncio.gather(*to_cancel, loop=loop, return_exceptions=True))
88+
89+
for task in to_cancel:
90+
if task.cancelled():
91+
continue
92+
93+
if task.exception() is not None:
94+
loop.call_exception_handler({
95+
'message': 'unhandled exception during asyncio.run() shutdown',
96+
'exception': task.exception(),
97+
'task': task,
98+
})
99+
100+
101+
def to_thread(coro: callable, *args: typing.Any) -> typing.Any:
102+
"""
103+
Run coroutine inside a thread. This is useful for blocking I/O operations.
104+
"""
105+
loop = get_event_loop()
106+
return loop.run_in_executor(None, coro, *args)
107+
108+
109+
def run(coro: typing.Coroutine) -> typing.Any:
110+
"""
111+
Run coroutine inside running event loop and it cancel all loop
112+
tasks at the end. Useful when we want to run the main() function.
113+
"""
114+
loop = get_event_loop()
115+
116+
def handler() -> None:
117+
cancel_tasks(loop)
118+
119+
# we don't have to handle signal again
120+
loop.remove_signal_handler(signal.SIGTERM)
121+
loop.add_signal_handler(signal.SIGINT, lambda: None)
122+
123+
for sig in (signal.SIGTERM, signal.SIGINT):
124+
loop.add_signal_handler(sig, handler)
125+
126+
try:
127+
return loop.run_until_complete(coro)
128+
finally:
129+
cancel_tasks(loop)
130+
131+
132+
__all__ = [
133+
"LTPException",
134+
"events",
135+
"get_event_loop"
136+
]

0 commit comments

Comments
 (0)