29 Commits

Author SHA1 Message Date
91341b74e2 Adding a some TUM template used to validate the JSON schema 2026-05-24 15:28:54 +02:00
2a5e0bebd4 Adding many items to the JSON parser 2026-05-24 15:26:11 +02:00
8791a2e3f3 Adding some schema for validation and AI generation 2026-05-22 22:51:15 +02:00
6f832cd67b validation: cover nil/None return from lua_func/py_func
Two new steps per language: function returning nothing and function
returning explicit nil/None. Both tagged $(test)_PASS — they would
have failed before the lua nil fix (Lua side reported nil result as
error). Python side already worked but is covered for parity.
2026-05-17 18:13:03 +02:00
ff46886865 lua_func: nil return is not an error
_handle_request was using the 1st pcall return as the error
discriminator, so any Lua function returning nothing (e.g. long_wait
in the example) was reported as failed. Discriminate on the 2nd
return (err) instead, and encode nil result as cjson.null so the
returned_value field stays present in the JSON-RPC response.
2026-05-17 18:04:51 +02:00
50d183d191 removed lua param, useless. 2026-05-17 10:43:25 +02:00
2177715641 examples: long_wait py_func/lua_func to exercise Stop
Two extra steps in example_simple.tum that sleep for 10s, used to
verify that pressing Stop interrupts engaged blocking steps.
2026-05-17 10:42:49 +02:00
a728f561be Make Stop interrupt blocking steps promptly
console.read_until polls a should_stop callback in 0.2s chunks across
all protocols. py_func/lua_func override stop() to tear down the worker
and wake the parent RPC wait. json_rpc adapters honor should_stop too.
Engaged leaf steps now report FAILURE on stop (sleep no-dialog was
silently SUCCESS).
2026-05-17 10:42:40 +02:00
116e528a7d Simplify the Start Stop Pause process (v-and-v/testium#20) 2026-05-16 13:36:18 +02:00
cc744e17a1 Adding ensurepip verification for the build environnement (required by venv) 2026-05-16 13:29:37 +02:00
ab39b49558 now the release note and the manual are copied into dist with build_all 2026-05-13 21:24:35 +02:00
95275c4418 Merge branch 'main' of ssh://git.beafrancois.fr:8328/v-and-v/testium 2026-05-13 14:09:41 +02:00
0d614c2921 release: 0.1.2 2026-05-13 14:05:47 +02:00
9466b091dd docs: rebuild manual PDF 2026-05-13 14:05:47 +02:00
511288bd03 build_all.sh: build wheel + pyinstaller + flatpak + appimage in one go
Collects all four artifacts under <repo>/dist/ (PyInstaller and Flatpak
renamed to testium-<version>(.suff); wheel and AppImage keep PEP 427 /
appimage-builder original names). Re-uses scripts/build_env.sh and
set_env.sh, same venv as run.sh. AppImage build.sh now picks the actual
output file dynamically instead of a hardcoded lowercase name.
2026-05-13 14:03:20 +02:00
51b144f60c Flatpak: bypass XDG portal for .tum open dialog
Native file dialog routes through the XDG document portal, which exposes
only the selected file at /run/user/UID/doc/... — siblings (param.yaml,
.py) are unreachable. Force Qt's non-native dialog in Flatpak so it walks
the real filesystem via --filesystem=home and returns a usable path.
2026-05-13 12:49:46 +02:00
dee8d4a682 generic design elements 2026-05-10 17:41:43 +02:00
e726d47547 generic design elements 2026-05-10 17:40:52 +02:00
5fd50e1c85 release_note: add 0.1.1 entry 2026-05-07 10:09:44 +02:00
51939a566a chore: add src/LICENSE, sync VERSION to 0.1.1 2026-05-07 10:06:17 +02:00
26fccda6bf docs(CLAUDE.md): rewrite Packaging section, document run launcher
Adds Flatpak/AppImage to the channels table, documents the host-only
contract and bins.py mechanics, explains the runtime-aware run launcher.
2026-05-07 10:06:06 +02:00
405fb82fca AppImage packaging: containerized build, host-only py_func/lua_func
build.sh runs appimage-builder in a Debian Bookworm container (Podman or
Docker) so it works on Arch / non-Debian hosts. Uses single src/requirements.txt;
TESTIUM_VERSION exported in runtime.env.
2026-05-07 10:05:58 +02:00
6064d96138 Flatpak packaging: desktop entry, MIME, distributable bundle
org.testium.Testium.yaml uses host Python/Lua only (no bundled interpreter).
build.sh exports a .flatpak bundle. README documents the install procedure.
2026-05-07 10:05:44 +02:00
0658540cc2 run item: runtime-aware launcher, drop testium_path/python_bin params
_testium_launch_cmd() returns the right entry point per mode (AppImage,
Flatpak, PyInstaller, source/wheel). Fixes PermissionError on read-only
__main__.py inside the AppImage squashfs mount.
2026-05-07 10:05:09 +02:00
7bf946dabe py_func/__main__: robust sys.path + diagnostic on import failure
Insert str() parent dir at sys.path[0] (was appending a Path object);
exception handler prints sys.executable and sys.path.
2026-05-07 10:05:00 +02:00
f52d7bbe53 runtime_plot: bump last_values timeout 1s -> 5s, narrow except
Dispatch chain (queue poll + Qt signal/slot + main thread) can exceed 1s on
loaded machines; bare except masked everything other than queue.Empty.
2026-05-07 10:04:44 +02:00
c83ebccb55 version: read TESTIUM_VERSION env in Flatpak/AppImage
Both bundles export it from their launcher; previous fragile __file__-relative
VERSION lookup was reporting 'unknown'.
2026-05-07 10:04:08 +02:00
f17ef8a3a1 dialog_env: pick wayland/xcb from $DISPLAY/$WAYLAND_DISPLAY
Was forcing xcb unconditionally, which hung dialogs on pure-Wayland sessions.
2026-05-07 10:03:54 +02:00
ddb18abc21 bins.py: host-only Python/Lua in sandboxed bundles
_which() probes host dirs only in Flatpak (/run/host/usr/bin) and AppImage
(/usr/bin); apply_host_libs prepares env for host subprocesses (prepend host
libs in Flatpak, strip $APPDIR pollution + PYTHONHOME in AppImage); user
override resolved via _which() for bare names.
2026-05-07 10:03:30 +02:00
63 changed files with 5082 additions and 206 deletions

3
.gitignore vendored
View File

@@ -8,6 +8,8 @@ dist
/.vscode
.venv/
.flatpak-builder/
package/flatpak/repo/
package/flatpak/*.flatpak
crash.tx*
report_test.tx*
*.autosave
@@ -24,6 +26,7 @@ package/appimage/*.AppImage
package/appimage/src
package/appimage/*.py
AppDir
*.squashfs
doc/manual/doxygen
doc/manual/sphinx/build/*
doc/manual/sphinx/source/_build/*

View File

@@ -1,4 +1,4 @@
# Testium — Claude Context
# Testium — Design Context
## What is testium
@@ -183,6 +183,8 @@ Icons are assigned once when the test file is loaded (not updated live on theme
The sub-test's own pass/fail result is intentionally not propagated.
The interpreter and entry point used to spawn the sub-instance are picked automatically by `_testium_launch_cmd()` based on how the parent was started (AppImage → `$APPIMAGE`; Flatpak → `flatpak run`; PyInstaller → the frozen binary; source/wheel → `[sys.executable, abspath(sys.argv[0])]`). The user cannot override either via the YAML — selecting a different testium binary or Python from a sub-test was removed because it was either ill-defined (bundle modes have no separable Python) or could mismatch the parent's environment in surprising ways.
### Report exporters & plugins
`src/testium/interpreter/test_report/test_report.py``_EXPORTER_REGISTRY` dict maps a format name (cmd key in the YAML `report.export`) to a lazy loader. Built-ins: `text`, `json`, `junit` (needs `junit_xml`), `html` (needs `lxml`). `sqlite` is the storage layer, no-op as an export.
@@ -201,17 +203,38 @@ A real-world test plugin lives at `test/validation/fake_exporter/` (CSV exporter
## Packaging
Three distribution channels coexist, sharing the single `src/testium/` package:
Four distribution channels coexist, all sharing the single `src/testium/` package and the single `src/requirements.txt` dependency list:
| Channel | Where | Notes |
|---------|-------|-------|
| Wheel (`pip install`) | `src/pyproject.toml` | Vanilla Python package; entry point `testium = "testium:main"` |
| PyInstaller binary | `package/pyinstaller/` | Single ~130 MB binary. `py_func`, `runtime`, `lua_func` bundled at `_MEIPASS` root so the **host** Python can find them when launched as `python3 py_func`. `api`/`interpreter` are **not** exposed (subprocess isolation). |
| Flatpak | `package/flatpak/` | (Existing recipe, not actively maintained in current refactor wave.) |
| Channel | Where | Build | Notes |
|---------|-------|-------|-------|
| Wheel (`pip install`) | `src/pyproject.toml` | `python -m build` | Vanilla Python package; entry point `testium = "testium:main"`. |
| PyInstaller binary | `package/pyinstaller/` | `build.sh` | Single ~130 MB binary. `py_func`, `runtime`, `lua_func` bundled at `_MEIPASS` root so the **host** Python can find them when launched as `python3 py_func`. `api`/`interpreter` are **not** exposed (subprocess isolation). |
| Flatpak | `package/flatpak/` | `build.sh` (uses `flatpak-builder`) | KDE 6.10 runtime. The bundled Python runs only the main process; `py_func` / `lua_func` MUST run under the **host** interpreter (no Python/Lua bundled). Produces a distributable `.flatpak` bundle. |
| AppImage | `package/appimage/` | `build.sh` (Debian Bookworm container via Podman/Docker) | Bundles Python 3.11 for the main process; `py_func` / `lua_func` MUST run under the **host** interpreter. Build runs in a container so it works on Arch / any non-Debian host. |
The `.deb` work-in-progress lives in `package/deb/`:
- `test_distro.sh debian:bookworm | debian:trixie | ubuntu:24.04` spins up a Docker/Podman container, reports system package availability, falls back to pip for what's missing (`pyside6` on bookworm/ubuntu, `telnetlib3`, `junit_xml`), runs the validation suite. Currently green on the three targets.
### Host-only py_func / lua_func in sandboxed bundles (Flatpak, AppImage)
The bundled Python (Flatpak's runtime python, AppImage's `python3.11`) is reserved for the **main process only**. Subprocesses (`py_func`, `lua_func`, `git`) must use the host's interpreters and tools so user-installed modules (pyserial, junit_xml, …) are visible. This is enforced by `interpreter/utils/bins.py`:
- `_in_flatpak()` (checks `/.flatpak-info`) and `_in_appimage()` (checks `APPIMAGE` env var) detect the sandbox.
- `_which(name)` probes only host bin dirs in those modes:
- Flatpak: `/run/host/usr/{local/,}bin`, `/run/host/bin` (host mounted via `--filesystem=host-os`).
- AppImage: `/usr/local/bin`, `/usr/bin`, `/bin` (we are directly on the host filesystem).
- If the host has no python3/lua, `ensure()` raises `ETUMRuntimeError` at test load with the candidate list — no silent fallback to a bundled interpreter.
- User overrides (`python_bin`/`lua_bin` in globdict): bare names are resolved through `_which()` (host-only), absolute paths are accepted as-is.
- `apply_host_libs(env)` is called by `py_process.py` / `lua_process.py` on the env passed to Popen:
- Flatpak: prepends host lib dirs to `LD_LIBRARY_PATH` so the dynamic linker finds host `.so`'s.
- AppImage: strips `$APPDIR`-prefixed entries from `LD_LIBRARY_PATH` / `PYTHONPATH` / `PATH` and drops `PYTHONHOME`, so the host Python doesn't try to load the bundled stdlib/site-packages.
- `apply_host_lua_paths(env)` (Flatpak only) prepends `/run/host/usr/{lib,share}/lua/X.Y` to `LUA_PATH` / `LUA_CPATH` so `cjson`, `socket`, etc. resolve. Must be called **after** user `lua_env` overrides so host paths win. AppImage relies on host Lua's compiled-in defaults.
- `py_process.py` additionally pops `PYTHONUSERBASE` (set to `/var/data/python` by the Flatpak runtime, which would hide `~/.local/lib/...`).
### Version reporting (`interpreter/utils/version.py`)
Both Flatpak and AppImage export `TESTIUM_VERSION` from a launcher (Flatpak: launcher script in `org.testium.Testium.yaml`; AppImage: `runtime.env` in `AppImageBuilder.yml`). `get_testium_version()` checks `/.flatpak-info` / `APPIMAGE` and reads `TESTIUM_VERSION` rather than relying on package metadata or repo introspection.
## Recent fixes / notable changes
- Restructure: single `src/testium/` Python package (was 4 sibling top-levels: `testium`, `lib`, `py_func`, `lua_func`). `lib/``runtime/`, `libs/``api/`. `pip install` now produces a clean `site-packages/testium/` with no top-level pollution; `.lua` files travel via `package_data`.
- `bins.py`: centralised resolution + cache of external `python3` / `lua` binaries. Replaces the scattered `tm.gd("python_bin")`/`tm.gd("lua_bin")` dance and the duplicated discovery logic in `py_process.py`/`lua_process.py`. Validates at test load via `TestSet._validate_runtime_deps()` so missing interpreters fail fast.

View File

@@ -27,7 +27,19 @@ Pre-built artifacts are published at
runnable directly, no Python installation required on the host. Lua
support still needs a system `lua` interpreter and the `lua-socket` /
`lua-cjson` modules.
* **Flatpak** — *coming soon.*
* **Flatpak bundle** (`testium.flatpak`) — install with:
```sh
# Add Flathub (once, to fetch the KDE/PySide runtimes)
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
# Install the bundle
flatpak install --user testium.flatpak
```
After installation testium appears in the desktop application menu and the
`testium` command is available in the terminal (requires `~/.local/bin` in
`PATH`, which most modern distributions provide by default).
## Quick start

107
build_all.sh Executable file
View File

@@ -0,0 +1,107 @@
#!/bin/bash
# Build every distribution channel of testium, in order:
# 1. Manual PDF -> dist/testium-manual-<v>.pdf
# 2. Wheel -> dist/testium-<v>-py3-none-any.whl (PEP 427 name)
# 3. PyInstaller binary -> dist/testium-<v>
# 4. Flatpak bundle -> dist/testium-<v>.flatpak
# 5. AppImage -> dist/Testium-<v>-x86_64.AppImage (original name)
# release_note.txt is copied to dist/ up front (with a warning if it has no
# entry for the current version).
#
# All artifacts are collected (copied) under <repo>/dist/. Original outputs in
# src/dist/, package/*/dist/, doc/manual/ are left in place. Wheel and AppImage
# keep their original names (which already contain the version); manual,
# pyinstaller and flatpak are renamed to testium(-manual)-<version>(.suff).
#
# Re-uses scripts/build_env.sh and scripts/set_env.sh — the same pair invoked
# by run.sh — so the venv at test/tmp/.venv stays the single source of Python
# dependencies. `build` and `pyinstaller` are installed into that venv on
# demand if not already there. Flatpak and AppImage build in their own
# container/sandbox; their build.sh scripts have their own toolchain checks.
set -e
SCRIPT_DIR=$(realpath "$(dirname "$0")")
VERSION=$(cat "$SCRIPT_DIR/src/VERSION")
DIST_DIR="$SCRIPT_DIR/dist"
mkdir -p "$DIST_DIR"
# Release note: copy it to dist/ and warn (but don't fail) if it has no entry
# for the current version.
RELEASE_NOTE_SRC="$SCRIPT_DIR/release_note.txt"
RELEASE_NOTE="$DIST_DIR/release_note.txt"
cp -f "$RELEASE_NOTE_SRC" "$RELEASE_NOTE"
if ! grep -qE "^version $VERSION([^.0-9]|$)" "$RELEASE_NOTE_SRC"; then
echo "WARNING: release_note.txt has no entry for version $VERSION." >&2
fi
export PY_VENV_NAME=".venv"
export PY_VENV_DIR="$SCRIPT_DIR/test/tmp/$PY_VENV_NAME"
export REQ_PATH="$SCRIPT_DIR/src/requirements.txt"
bash "$SCRIPT_DIR/scripts/build_env.sh"
source "$SCRIPT_DIR/scripts/set_env.sh"
# Ensure wheel/PyInstaller toolchains are present in the venv.
python -m pip install --quiet --upgrade build pyinstaller
step() {
echo
echo "================================================================"
echo " $1"
echo "================================================================"
}
# 1. Manual PDF
step "1/5 Manual PDF (version $VERSION)"
bash "$SCRIPT_DIR/doc/manual/sphinx/build_doc.sh"
MANUAL_SRC="$SCRIPT_DIR/doc/manual/testium_manual.pdf"
MANUAL="$DIST_DIR/testium-manual-${VERSION}.pdf"
cp -f "$MANUAL_SRC" "$MANUAL"
# 2. Wheel — PEP 427 name kept (already contains version)
step "2/5 Wheel (version $VERSION)"
(
cd "$SCRIPT_DIR/src"
rm -rf dist build *.egg-info
python -m build --wheel
)
WHEEL_SRC=$(ls -1t "$SCRIPT_DIR/src/dist"/*.whl | head -1)
WHEEL="$DIST_DIR/$(basename "$WHEEL_SRC")"
cp -f "$WHEEL_SRC" "$WHEEL"
# 3. PyInstaller binary
step "3/5 PyInstaller binary (version $VERSION)"
bash "$SCRIPT_DIR/package/pyinstaller/build.sh"
PYI_SRC="$SCRIPT_DIR/package/pyinstaller/dist/testium"
PYI_BIN="$DIST_DIR/testium-${VERSION}"
cp -f "$PYI_SRC" "$PYI_BIN"
# 4. Flatpak bundle
step "4/5 Flatpak bundle (version $VERSION)"
(
cd "$SCRIPT_DIR/package/flatpak"
bash build.sh
)
FLATPAK_SRC="$SCRIPT_DIR/package/flatpak/testium.flatpak"
FLATPAK_BUNDLE="$DIST_DIR/testium-${VERSION}.flatpak"
cp -f "$FLATPAK_SRC" "$FLATPAK_BUNDLE"
# 5. AppImage
step "5/5 AppImage (version $VERSION)"
(
cd "$SCRIPT_DIR/package/appimage"
bash build.sh
)
APPIMAGE_SRC=$(ls -1t "$SCRIPT_DIR/package/appimage"/*.AppImage 2>/dev/null | head -1)
APPIMAGE="$DIST_DIR/$(basename "$APPIMAGE_SRC")"
cp -f "$APPIMAGE_SRC" "$APPIMAGE"
chmod +x "$APPIMAGE"
step "All packages built"
printf " manual : %s\n" "$MANUAL"
printf " wheel : %s\n" "$WHEEL"
printf " pyinstaller : %s\n" "$PYI_BIN"
printf " flatpak : %s\n" "$FLATPAK_BUNDLE"
printf " appimage : %s\n" "$APPIMAGE"
printf " release_note : %s\n" "$RELEASE_NOTE"

View File

@@ -20,6 +20,22 @@ main:
param:
- 123
- py_func:
name: python long wait
doc: The purpose of this step is to try the tasks "stop" interruption
file: utils.py
func_name: long_wait
param:
- 10
- lua_func:
name: lua long wait
doc: The purpose of this step is to try the tasks "stop" interruption
file: lua_func.lua
func_name: long_wait
param:
- 10
- sleep:
name: sleep item
dialog: true

View File

@@ -1,4 +1,5 @@
tm = require("tm")
socket = require("socket")
local module = {}
@@ -7,4 +8,8 @@ function module.func_to_be_executed(param)
return param
end
function module.long_wait(sec)
socket.sleep(sec)
end
return module

View File

@@ -17,18 +17,3 @@ plot_log_path: /tmp/testium_plot/$(testrun_date)/$(testrun_time)/
python_path_Windows: C:\Users\François\Applications\Python313\python.exe
python_path_Linux: $(home)/tmp/tum_venv/bin/python3
# lua_bin_Windows: C:\Lua\5.1
# lua_bin_Linux: /usr/bin/lua
LUA_PATH_Linux: /usr/share/lua/5.4/?.lua;/usr/local/share/lua/5.4/?.lua;/usr/local/share/lua/5.4/?/init.lua;/usr/share/lua/5.4/?/init.lua;/usr/local/lib/lua/5.4/?.lua;/usr/local/lib/lua/5.4/?/init.lua;/usr/lib/lua/5.4/?.lua;/usr/lib/lua/5.4/?/init.lua;./?.lua;./?/init.lua;/home/francois/.luarocks/share/lua/5.4/?.lua;/home/francois/.luarocks/share/lua/5.4/?/init.lua
LUA_CPATH_Linux: /usr/local/lib/lua/5.4/?.so;/usr/lib/lua/5.4/?.so;/usr/local/lib/lua/5.4/loadall.so;/usr/lib/lua/5.4/loadall.so;./?.so;/home/francois/.luarocks/lib/lua/5.4/?.so
PATH_Linux:
LUA_PATH_Windows: ;.\?.lua;C:\Lua\5.1\lua\?.lua;C:\Lua\5.1\lua\?\init.lua;C:\Lua\5.1\?.lua;C:\Lua\5.1\?\init.lua;C:\Lua\5.1\lua\?.luac
LUA_CPATH_Windows: .\?.dll;C:\Lua\5.1\?.dll;C:\Lua\5.1\loadall.dll;C:\Lua\5.1\clibs\?.dll;C:\Lua\5.1\clibs\loadall.dll;.\?51.dll;C:\Lua\5.1\?51.dll;C:\Lua\5.1\clibs\?51.dll
PATH_Windows: ""
lua_env:
PATH: $(PATH_$(os))
LUA_PATH: $(LUA_PATH_$(os))
LUA_CPATH: $(LUA_CPATH_$(os))

View File

@@ -1,3 +1,5 @@
from time import sleep
def dummy_exit(useless1, useless2):
return True
@@ -10,4 +12,7 @@ def funcToBeExecuted (bla):
def funcToBeExecuted2 (bla):
print(bla)
return blo
return blo
def long_wait (sec):
sleep(sec)

Binary file not shown.

View File

@@ -24,9 +24,8 @@ AppDir:
runtime:
env:
SEQUENCER_REV: '{{APP_VERSION}}'
TESTIUM_VERSION: '{{APP_VERSION}}'
PYTHONPATH: $APPDIR/usr/lib/python3.11/site-packages:$APPDIR/usr/lib/python3.11
QT_QPA_PLATFORM: xcb
path_mappings:
- /usr/share/matplotlib/mpl-data/matplotlibrc:$APPDIR/etc/matplotlibrc
@@ -69,12 +68,13 @@ AppDir:
# Set python 3.11 as default
ln -fs python3.11 $TARGET_APPDIR/usr/bin/python3
# Install pip
if [ ! -f "get-pip.py" ]; then curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py; fi
# Bootstrap pip into the AppDir Python
if [ ! -f "get-pip.py" ]; then curl -sS https://bootstrap.pypa.io/get-pip.py -o get-pip.py; fi
python3.11 get-pip.py --break-system-packages
# Install application dependencies in AppDir
python3.11 -m pip install --break-system-packages --upgrade --isolated --no-input --ignore-installed --prefix=$TARGET_APPDIR/usr -r requirements.txt
python3.11 -m pip install --break-system-packages --upgrade --isolated --no-input --ignore-installed --prefix=$TARGET_APPDIR/usr -r ../../src/requirements.txt
export PIP_CONFIG_FILE=$HOME/.pip/pip.conf
python3.11 -m pip install --break-system-packages --upgrade --isolated --no-input --ignore-installed --prefix=$TARGET_APPDIR/usr ../../src/dist/testium-{{APP_VERSION}}-py3-none-any.whl

View File

@@ -1,12 +1,54 @@
#!/usr/bin/bash
#!/bin/bash
# Build the testium AppImage inside a Debian container (Podman or Docker).
# The resulting .AppImage file is written to this directory.
export APP_VERSION=$(<../../src/VERSION)
set -e
appimage-builder --recipe AppImageBuilder.yml
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
APP_VERSION="$(<"$REPO_ROOT/src/VERSION")"
RESULT=$?
if [ -n "$1" ] && [ "$1" = "install" ]; then
if [ $RESULT -eq 0 ]; then
install -v "testium-${APP_VERSION}-x86_64.AppImage" "${HOME}/.local/bin/testium"
fi
if command -v podman &>/dev/null; then
RUNTIME=podman
elif command -v docker &>/dev/null; then
RUNTIME=docker
else
echo "Error: neither podman nor docker found." >&2
exit 1
fi
echo "Using $RUNTIME — building testium $APP_VERSION AppImage..."
# APPIMAGE_EXTRACT_AND_RUN=1 lets appimagetool run without FUSE in the container.
$RUNTIME run --rm \
--privileged \
-e APPIMAGE_EXTRACT_AND_RUN=1 \
-v "$REPO_ROOT:/work" \
-w /work/package/appimage \
debian:bookworm bash -c "
set -e
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq \
python3 python3-pip python3-venv python3-build \
dpkg-dev fakeroot squashfs-tools wget curl file binutils \
libglib2.0-0 patchelf zsync > /dev/null
# Build the wheel
cd /work/src
python3 -m build --wheel --outdir dist/ > /dev/null
cd /work/package/appimage
# Install appimage-builder
pip3 install appimage-builder --quiet --break-system-packages
# Run the build
export APP_VERSION=$APP_VERSION
appimage-builder --recipe AppImageBuilder.yml --skip-test
"
APPIMAGE_FILE=$(ls -1t Testium-*-x86_64.AppImage 2>/dev/null | head -1)
echo "Done: ${APPIMAGE_FILE}"
if [ "${1}" = "install" ] && [ -n "${APPIMAGE_FILE}" ]; then
install -v "${APPIMAGE_FILE}" "${HOME}/.local/bin/testium"
fi

View File

@@ -1,3 +0,0 @@
tables
pandas
scapy

View File

@@ -5,4 +5,22 @@
# flatpak install flathub org.kde.Sdk//6.10
# flatpak install flathub io.qt.PySide.BaseApp//6.10
flatpak-builder --user --verbose --force-clean --install build org.testium.Testium.yaml
set -e
# Build + install local
flatpak-builder --user --verbose --force-clean --install --repo=repo build org.testium.Testium.yaml
# Génère le bundle distribuable
flatpak build-bundle repo testium.flatpak org.testium.Testium
echo "Bundle généré : $(pwd)/testium.flatpak"
# Crée ~/.local/bin/testium pour pouvoir taper "testium" en console
WRAPPER="$HOME/.local/bin/testium"
mkdir -p "$HOME/.local/bin"
cat > "$WRAPPER" <<'EOF'
#!/bin/sh
exec flatpak run org.testium.Testium "$@"
EOF
chmod +x "$WRAPPER"
echo "Wrapper installé : $WRAPPER"
echo "Assurez-vous que ~/.local/bin est dans votre PATH."

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-testium">
<comment>Testium test script</comment>
<glob pattern="*.tum"/>
</mime-type>
</mime-info>

View File

@@ -0,0 +1,10 @@
[Desktop Entry]
Name=Testium
GenericName=Test Sequencer
Comment=YAML-based test sequencer and runner
Exec=testium %f
Icon=org.testium.Testium
Type=Application
Categories=Development;
MimeType=application/x-testium;
StartupNotify=true

View File

@@ -13,7 +13,9 @@ finish-args:
- --socket=wayland
- --device=dri
- --share=network
- --filesystem=home # Optionnel : si votre testium doit lire des fichiers utilisateurs
- --filesystem=home
- --filesystem=/tmp
- --filesystem=host-os
build-options:
build-args:
@@ -41,18 +43,41 @@ modules:
sources:
- type: dir
path: ../../src
- type: file
path: org.testium.Testium.desktop
- type: file
path: org.testium.Testium-mime.xml
- type: file
path: ../../package/testium.png
build-commands:
# On installe le code source dans /app/lib/testium
# Code source
- mkdir -p /app/lib
- cp -r . /app/lib/
- cp -r testium /app/lib/
- cp VERSION /app/lib/testium/VERSION
# Création du launcher exécutable
# Launcher exécutable
- mkdir -p /app/bin
- |
cat <<EOF > /app/bin/testium
#!/bin/sh
# On ajoute le code source et l'extension PySide6 au PYTHONPATH
export TESTIUM_VERSION="\$(cat /app/lib/testium/VERSION 2>/dev/null || echo unknown)"
export PYTHONPATH="/app/lib/testium:/usr/lib/sdk/pyside6/lib/python3.13/site-packages:\$PYTHONPATH"
exec python3 /app/lib/testium "\$@"
# Expose host binaries (git, python3, lua, …) for subprocess lookups.
# PATH is appended (not prepended) so the main process keeps the sandbox python3.
export PATH="\$PATH:/run/host/usr/local/bin:/run/host/usr/bin:/run/host/bin"
export GIT_PYTHON_GIT_EXECUTABLE="/run/host/usr/bin/git"
exec /usr/bin/python3 /app/lib/testium "\$@"
EOF
- chmod +x /app/bin/testium
# Icône
- mkdir -p /app/share/icons/hicolor/256x256/apps
- cp testium.png /app/share/icons/hicolor/256x256/apps/org.testium.Testium.png
# Entrée menu
- mkdir -p /app/share/applications
- cp org.testium.Testium.desktop /app/share/applications/
# Type MIME pour .tum
- mkdir -p /app/share/mime/packages
- cp org.testium.Testium-mime.xml /app/share/mime/packages/

View File

@@ -1,3 +1,20 @@
version 0.1.2
==============
- Flatpak: opening a test from the GUI now correctly finds its companion
files (param.yaml, .py scripts, ...).
version 0.1.1
==============
- New install channels: Flatpak bundle and AppImage. The AppImage runs
on any distribution (built inside a Debian container).
- About dialog: version is now correct in Flatpak and AppImage builds
(used to display "unknown").
- GUI dialogs no longer hang on pure-Wayland sessions.
- Plot "last values" API: more tolerant timeout on loaded machines.
- run item: `testium_path` and `python_bin` parameters removed —
sub-instances are launched in the same packaging mode as the parent.
- License: EUPL-1.2.
version 0.1
==============
- Start of the project

73
schema/test.tum Normal file
View File

@@ -0,0 +1,73 @@
config_file:
- premier
- saluT
main:
name: Main file
steps:
- group:
name: Test
doc: Une peitite documentation
no_fail: true
steps:
- let:
values:
- my_var: <| ${salut} |>
- check:
values:
- <| ${salut} |>
- dialog_message:
question: c'est quoi?
- lua_func:
file: c'est quoi?
func_name: c'est quoi?
- console:
console_name: cons_1
steps:
- open:
protocol: telnet
terminal_path: ijfeifj
- read_until : {expected: "tutu", timeout: -4.5, mute: true}
- write: something
- writeln: tutu
- close :
- json_rpc:
name: JSONRPC console Query
doc: JSONRPC console Query not waiting (only send)
console:
name : jsonrpc_server
prompt: "@@>"
timeout: 1
version: "2.0"
steps:
- query:
method: echo
params:
- Hello world
- [0, 1, 2, 3]
id: 3095372
no_wait: true
- json_rpc:
name: JSONRPC console Reception
doc: JSONRPC console reception of the previous request
console: {name : jsonrpc_server}
timeout: 1
steps:
- receive:
id: 3095372
timeout: 0.5
report:
enabled: true
log_stored: true
export:
junit:
path: $(validation_report_path)
file_name: $(validation_report_file).junit
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)

View File

@@ -0,0 +1,96 @@
config_file:
- param.yaml
- items/check/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: check test constants
values:
- test: check
- test_path: items/$(test)
- group:
name: check test
steps:
- sequence:
steps:
- py_func:
file: $(test_path)$(psep)check.py
func_name: echo
key: $(test)_PASS
name: Dummy_int
param:
- 2
- py_func:
file: $(test_path)$(psep)check.py
func_name: echo
key: $(test)_PASS
name: Dummy_str
param:
- my taylor is rich
- check:
key: $(test)_PASS
name: Check condition on existing variable (PASS)
values:
- <| $(pfn_Dummy_int) > 1 |>
- check:
key: $(test)_FAIL
name: Check condition on existing variable (FAIL)
values:
- <| "tailor" in "$(pfn_Dummy_str)" |>
filename: /home/renish/workspace/testium/code/test/validation/items/check/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,453 @@
config_file:
- param.yaml
- items/common/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: common test constants
values:
test: common
test_path: items/$(test)
- group:
name: common test
steps:
- sequence:
steps:
- group:
name: Results
steps:
- sequence:
steps:
- group:
name: Expected Result
steps:
- py_func:
expected_result: true
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: Return True expect True
param:
- true
- py_func:
expected_result: false
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_FAIL
name: Return True expect False (must
fail)
param:
- true
- py_func:
expected_result: None
file: $(test_path)$(psep)results$(psep)results.py
func_name: return_none
key: $(test)_PASS
name: Return None expect None
- py_func:
expected_result: PASS
file: $(test_path)$(psep)results$(psep)results.py
func_name: return_none
key: $(test)_PASS
name: Return None expect PASS
- py_func:
expected_result: 14
file: $(test_path)$(psep)results$(psep)results.py
func_name: return_none
key: $(test)_FAIL
name: Return None expect 14 (must fail)
- group:
name: Expected Result Last test result
steps:
- py_func:
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: result is 28
param:
- 28
- py_func:
expected_result: $(last_step_result)
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: check that the last test result
is 28
param:
- 28
- group:
name: Expected result Failure raised issue
steps:
- py_func:
file: $(test_path)$(psep)results$(psep)results.py
func_name: raise_issue
key: $(test)_FAIL
name: Raise an issue (must fail)
param:
- $(str_example)
- py_func:
expected_result: FAIL
file: $(test_path)$(psep)results$(psep)results.py
func_name: raise_issue
key: $(test)_PASS
name: Raise an issue and expected the
test to be FAIL
param:
- $(str_example)
- py_func:
expected_result: FAIL
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_FAIL
name: Return a String expect a FAILURE
(must fail)
param:
- $(str_example)
- group:
name: process result
steps:
- py_func:
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: Process result equal String
param:
- $(str_example)
process_result: '''$(str_example)'' ==
''$(result)'''
- py_func:
expected_result: true
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_FAIL
name: Process result string in the result
(must fail)
param:
- $(str_example)
process_result: '''44'' in ''$(result)'''
- py_func:
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: Save the result in a global variable
param:
- 44
store_result: process_result_value
- py_func:
expected_result: $(process_result_value)
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: Check the saved global variable
param:
- 44
- py_func:
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: store_result with process_result
param:
- $(str_example)
process_result: '''$(result)''.upper()'
store_result: upper_str_example
- py_func:
expected_result: $(upper_str_example)
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: Check store_result with process_result
param:
- $(str_example)
process_result: '''$(result)''.upper()'
- let:
key: $(test)_PASS
name: "store_result on let item (None\
\ value \u2192 stores PASS)"
store_result: let_store_result
values:
- dummy: 0
- py_func:
expected_result: $(let_store_result)
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: Check store_result on let stores
PASS
param:
- PASS
- py_func:
expected_result: FAIL
file: $(test_path)$(psep)results$(psep)results.py
func_name: return_none
key: $(test)_FAIL
name: "store_result on failing test (None\
\ value \u2192 stores FAIL)"
store_result: none_fail_store_result
- py_func:
expected_result: $(none_fail_store_result)
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: Check store_result on failing test
stores FAIL
param:
- FAIL
- py_func:
expected_result: FAIL
file: $(test_path)$(psep)results$(psep)results.py
func_name: return_none
key: $(test)_PASS
name: "store_result with no_fail (None\
\ value \u2192 stores real FAIL, not\
\ forced PASS)"
no_fail: true
store_result: none_nofail_store_result
- py_func:
expected_result: $(none_nofail_store_result)
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: Check store_result with no_fail
stores real FAIL
param:
- FAIL
- py_func:
file: $(test_path)$(psep)results$(psep)results.py
func_name: return_none
key: $(test)_FAIL
name: Process result when result is None
(must fail)
process_result: $(result) is None
- group:
name: no_fail result
steps:
- py_func:
expected_result: false
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: Return True expect False but no_fail=True
no_fail: true
param:
- true
- py_func:
expected_result: false
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_FAIL
name: Return True expect False but no_fail=False
(must fail)
no_fail: false
param:
- true
- py_func:
expected_result: false
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_PASS
name: Return True expect False but no_fail
expansed
no_fail: <| bool(0) == False |>
param:
- true
- py_func:
expected_result: false
file: $(test_path)$(psep)results$(psep)results.py
func_name: echo
key: $(test)_FAIL
name: Return True expect False but no_fail
expansed (must fail)
no_fail: <| bool(1) == False |>
param:
- true
filename: /home/renish/workspace/testium/code/test/validation/items/common/results/test.tum
- group:
name: Conditional
steps:
- sequence:
steps:
- loop:
doc: This loop illustrate the way to exit on
a condition.
exit_condition:
value: <| $(pfn_Echo function) > 3 |>
key: $(test)_PASS
name: Infine loop with conditional exit
steps:
- sleep:
name: small wait
timeout: 0.2
- py_func:
file: $(test_path)$(psep)conditional$(psep)conditional.py
func_name: echo
key: $(test)_PASS
name: Echo function
param:
- $(loop_param)
stop_on_failure: false
- let:
key: $(test)_PASS
name: let
values:
- conditional_exec: <| random.randint(1, 2) |>
- console:
condition: <| $(conditional_exec) == 1 |>
console_name: consname
doc: Opening the console
key: $(test)_PASS
name: Console creation
steps:
- open:
protocol: terminal
terminal_path: $(test_directory)
- writeln: echo "terminal loaded"
- console:
condition: <| $(conditional_exec) == 1 |>
console_name: consname
key: $(test)_PASS
name: Console read_until with timeout
steps:
- read_until:
expected: terminal loaded
timeout: 5
- console:
condition: <| $(conditional_exec) == 1 |>
console_name: consname
key: $(test)_PASS
name: Console write
steps:
- writeln: echo 0
- sleep:
condition: <| $(conditional_exec) == 1 |>
name: sleep item
timeout: 1
- console:
condition: <| $(conditional_exec) == 1 |>
console_name: consname
key: $(test)_PASS
name: Console read_until immediate
steps:
- read_until:
expected: '0'
timeout: 0
- console:
condition: <| $(conditional_exec) == 1 |>
console_name: consname
key: $(test)_PASS
name: Console read_until immediate (2)
steps:
- read_until:
expected: $(terminal_prompt)
timeout: 0
- console:
condition: <| $(conditional_exec) == 1 |>
console_name: consname
key: $(test)_PASS
name: Console closure
steps:
- close: consname
- sleep:
condition: <| $(conditional_exec) == 2 |>
name: sleep item
timeout: 1
filename: /home/renish/workspace/testium/code/test/validation/items/common/conditional/test.tum
- group:
name: Various syntax robustness
steps:
- sequence:
steps:
- sleep:
key: $(test)_PASS
timeout: 0.2
- sleep:
key: $(test)_PASS
name: null
timeout: 0.2
- sleep:
doc: null
key: $(test)_PASS
name: Empty "doc:" declared (must PASS)
timeout: 0.2
filename: /home/renish/workspace/testium/code/test/validation/items/common/syntax_robustness/test.tum
- group:
name: Helper lib functions
steps:
- py_func:
file: $(test_path)$(psep)helper_lib.py
func_name: check_os
key: $(test)_PASS
name: OS
param:
- $(os)
- py_func:
file: $(test_path)$(psep)helper_lib.py
func_name: check_get_main_dir
key: $(test)_PASS
name: get_main_dir
- py_func:
file: $(test_path)$(psep)helper_lib.py
func_name: check_timestamp_as_sec_conversion
key: $(test)_PASS
name: timestamp_as_sec conversion
- py_func:
file: $(test_path)$(psep)helper_lib.py
func_name: check_timestamp
key: $(test)_PASS
name: timestamp and timestamp_as_sec
filename: /home/renish/workspace/testium/code/test/validation/items/common/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,164 @@
config_file:
- param.yaml
- items/console/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: console test constants
values:
- test: console
- test_path: items/$(test)
- group:
name: console test
steps:
- sequence:
steps:
- console:
console_name: term
doc: Opening the console
key: $(test)_PASS
name: Console creation
steps:
- open:
protocol: terminal
terminal_path: $(test_directory)
- writeln: echo "endOfOpen"
- console:
console_name: term
key: $(test)_PASS
name: Console read_until with timeout
steps:
- read_until:
expected: endOfOpen
timeout: 5
- console:
console_name: term
key: $(test)_PASS
name: Console write
steps:
- writeln: echo 0
- sleep:
name: sleep item
timeout: 1
- console:
console_name: term
key: $(test)_PASS
name: Console read_until immediate
steps:
- read_until:
expected: '0'
timeout: 0
- console:
console_name: term
key: $(test)_PASS
name: Console write
steps:
- writeln: echo "HelloConsole"
- console:
console_name: term
key: $(test)_FAIL
name: Console read_until fail
steps:
- read_until:
expected: Something never prints
timeout: 1
- console:
console_name: term
key: $(test)_PASS
name: Console write
steps:
- writeln: echo "HelloConsole"
- console:
console_name: term
key: $(test)_PASS
name: Console read_until no_fail
steps:
- read_until:
expected: Something never prints
no_fail: true
timeout: 1
- console:
console_name: term
key: $(test)_PASS
name: Console read_until muted
steps:
- writeln: echo "HelloConsole"
- read_until:
expected: HelloConsole
mute: true
timeout: 1
- console:
console_name: term
key: $(test)_PASS
name: Console read_until muted
steps:
- writeln: echo "HelloConsole is PASS" && echo "endOfCmd"
- read_until:
expected: endOfCmd
process_result: '''Hello'' in r''''''$(result)''''''
and ''PASS'' in r''''''$(result)'''''' '
timeout: 1
- console:
console_name: term
execute_on_stop: true
key: $(test)_PASS
name: Console closure
steps:
- close: term
filename: /home/renish/workspace/testium/code/test/validation/items/console/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,106 @@
config_file:
- param.yaml
- items/cycle/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
terminal_prompt: $(linux_prompt)
psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: cycle test constants
values:
- test: cycle
- test_path: items/$(test)
- group:
name: cycle test
steps:
- sequence:
steps:
- loop:
iterator: 10
key: $(test)_PASS
name: Cycle number of loops
steps:
- py_func:
file: $(test_path)$(psep)cycle.py
func_name: donothing
name: do nothing
- loop:
iterator:
- 12
- 20
- 30
key: $(test)_PASS
name: Cycle iterating on list
steps:
- py_func:
file: $(test_path)$(psep)cycle.py
func_name: checkloopparam
name: check loop param
param:
- $(loop_param)
- loop:
exit_condition:
file: $(test_path)$(psep)cycle.py
func_name: exitcondition
key: $(test)_PASS
name: Infinite loop with exit condition
steps:
- py_func:
file: $(test_path)$(psep)cycle.py
func_name: donothing
name: do nothing
filename: /home/renish/workspace/testium/code/test/validation/items/cycle/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,135 @@
config_file:
- param.yaml
- items/dialogs/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: dialogs test constants
values:
test: dialogs
test_path: items/$(test)
- group:
name: dialogs test
steps:
- sequence:
steps:
- dialog_image:
auto_result: ok
condition: $(validation_dialogs)
filename: $(test_path)$(psep)IMG_20140213_171455.jpg
key: $(test)_PASS
name: dialog image PASS
question: click ok if you see the image
- dialog_image:
auto_result: cancel
condition: $(validation_dialogs)
filename: $(test_path)$(psep)IMG_20140213_171455.jpg
key: $(test)_FAIL
name: dialog image FAIL
question: click cancel
- dialog_references:
auto_result: ok
condition: $(validation_dialogs)
key: $(test)_PASS
name: dialog_reference PASS
question: click ok
- dialog_references:
auto_result: cancel
condition: $(validation_dialogs)
key: $(test)_FAIL
name: dialog_reference FAIL
question: click cancel
- dialog_value:
auto_result: ok
auto_value: '123'
condition: $(validation_dialogs)
key: $(test)_PASS
name: dialog_value PASS
question: enter 123 and click ok
- dialog_value:
auto_result: ok
condition: $(validation_dialogs)
key: $(test)_FAIL
name: dialog_value empty FAIL
question: enter nothing and click ok
- dialog_value:
auto_result: cancel
condition: $(validation_dialogs)
key: $(test)_FAIL
name: dialog_value canceled FAIL
question: enter nothing and click cancel
- dialog_message:
auto_result: ok
condition: $(validation_dialogs)
key: $(test)_PASS
name: dialog_message PASS
question: click ok
- dialog_question:
auto_result: 'yes'
condition: $(validation_dialogs)
key: $(test)_PASS
name: dialog_question PASS
question: click yes
- dialog_question:
auto_result: 'no'
condition: $(validation_dialogs)
key: $(test)_FAIL
name: dialog_question FAIL
question: click no
filename: /home/renish/workspace/testium/code/test/validation/items/dialogs/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,130 @@
config_file:
- param.yaml
- items/expanse/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: expanse test constants
values:
test: expanse
test_path: items/$(test)
- group:
name: expanse test
steps:
- sequence:
steps:
- check:
key: $(test)_PASS
name: Check variables expansion is correct (PASS)
values:
- <| $(expanse_index) == 1 |>
- <| $(expanse_table)[$(expanse_index)] == 9012 |>
- <| $(expanse_eval) == True |>
- let:
key: $(test)_PASS
name: Dynamic variables expansion
values:
- expanse_select: <|"$(expanse_select)".replace("o", "a")|>
- expanse_index: $(expanse_index_$(expanse_select))
- expanse_table: $(expanse_table_$(expanse_select))
- expanse_eval: <|$(expanse_index) == 1|>
- check:
key: $(test)_PASS
name: Check variables expansion is correct (PASS)
values:
- <| $(expanse_index) == 0 |>
- <| $(expanse_table)[$(expanse_index)] == "abcd" |>
- <| $(expanse_eval) == False |>
- let:
key: $(test)_PASS
name: Complex variables expansion
values:
- var1: expanse
- var2: var
- var3: bla
- var4: blo
- expanse_var_bla: 3
- expanse_blo_var: 5
- expanse_complex: <|<|$(expanse_$(var2)_$(var3))*6|>
+ <|4*$($(var1)_$(var4)_$(var2))|>|>
- check:
key: $(test)_PASS
name: Check complex variables expansion is correct (PASS)
values:
- <| $(expanse_complex) == 38 |>
- let:
key: $(test)_PASS
name: Variables expansion in object
values:
- expanse_key: b
- expanse_var: 3
- expanse_var_2: 6
- expanse_object:
- $(expanse_key): <|2**3|>
a: $(expanse_var_2)
- <|"bla".replace("a", "o")|>:
- <|$(expanse_var)*$(expanse_var_2)|>
- 25
- check:
key: $(test)_PASS
name: Check complex variables expansion is correct (PASS)
values:
- '<| $(expanse_object) == [{"a": 6, "b": 8}, {"blo": [18,
25]}] |>'
filename: /home/renish/workspace/testium/code/test/validation/items/expanse/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,82 @@
config_file:
- param.yaml
- items/git/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: git test constants
values:
test: git
test_path: items/$(test)
- group:
name: git test
steps:
- sequence:
steps:
- git:
key: $(test)_PASS
name: Testium repo
repo: $(test_directory)
- git:
key: $(test)_PASS
name: Testium repo
repo:
- $(test_directory)
- $(test_directory)
filename: /home/renish/workspace/testium/code/test/validation/items/git/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,129 @@
config_file:
- param.yaml
- items/include/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: include test constants
values:
test: include
test_path: items/$(test)
- group:
name: include test
steps:
- sequence:
steps:
- sequence:
steps:
- py_func:
file: $(test_path)$(psep)include.py
func_name: ValidationTest
key: $(test)_PASS
name: My first include test
param:
- $(test parameter)
filename: /home/renish/workspace/testium/code/test/validation/items/include/inc
no template/my first include.tum
- sequence:
steps:
- py_func:
file: $(test_path)$(psep)include.py
func_name: ValidationTest
key: $(test)_PASS
name: My first include test
param:
- $(test parameter)
filename: /home/renish/workspace/testium/code/test/validation/items/include/inc
no template/my first include.tum
- sequence:
steps:
- py_func:
file: $(test_path)$(psep)include.py
func_name: ValidationTest
key: $(test)_PASS
name: My second include test
param:
- My second include test parameter
filename: /home/renish/workspace/testium/code/test/validation/items/include/inc
with template/my second include.tum
- sequence:
steps:
- py_func:
file: $(test_path)$(psep)include.py
func_name: ValidationTest
key: $(test)_PASS
name: My second include test
param:
- My second include test parameter
filename: /home/renish/workspace/testium/code/test/validation/items/include/inc
with template/my second include.tum
- let:
name: Declare param for inclusion
values:
- inc: Dali
- Dali_inc: Dalida
- sequence:
steps:
- let:
name: Test param inclusion 1
values:
- inclusion: $($(inc)_inc)
filename: /home/renish/workspace/testium/code/test/validation/items/include/inc
with template/my_3d_include.tum
filename: /home/renish/workspace/testium/code/test/validation/items/include/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,80 @@
config_file:
- param.yaml
- items/isolation/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: isolation test constants
values:
test: isolation
test_path: items/$(test)
- group:
name: isolation test
steps:
- sequence:
steps:
- py_func:
expected_result: true
file: $(test_path)$(psep)check_isolation.py
func_name: check_isolation
key: $(test)_PASS
name: py_func/lua_func do not depend on testium internals
param:
- $(testium_path)
filename: /home/renish/workspace/testium/code/test/validation/items/isolation/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,490 @@
config_file:
- param.yaml
- items/jsonrpc/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: jsonrpc test constants
values:
test: jsonrpc
test_path: items/$(test)
- group:
name: jsonrpc test
steps:
- sequence:
steps:
- console:
console_name: jrpces
doc: check if jrpc_echo_server.py is available
key: $(test)_PASS
name: json rpc echo server
steps:
- open:
protocol: terminal
- read_until:
expected: $(terminal_prompt)
no_fail: true
timeout: 1
- writeln: test -f /home/renish/workspace/testium/code/test/validation/items/jsonrpc/jrpc_echo_server.py
&& echo JRPC_OK
- read_until:
expected: JRPC_OK
no_fail: true
timeout: 2
- group:
condition: <| 'JRPC_OK' in r'''$(cn_json rpc echo server)''' |>
name: jsonrpc tests
steps:
- console:
console_name: jrpces
key: $(test)_PASS
name: Start the json rpc echo server
steps:
- writeln: python3 /home/renish/workspace/testium/code/test/validation/items/jsonrpc/jrpc_echo_server.py
-c /home/renish/workspace/testium/code/test/validation/items/jsonrpc/jrpces.ini
- read_until:
expected: ready
timeout: 5
- console:
console_name: jsonrpc_server
doc: Opening the RAW TCP console
key: $(test)_PASS
name: Open the raw tcp Console
skipped: $(skip_tcp)
steps:
- open:
protocol: rawtcp
tcp_host: localhost
tcp_port: 4321
- json_rpc:
console:
name: jsonrpc_server
key: $(test)_PASS
name: JSONRPC console Query waiting for reception
skipped: $(skip_tcp)
steps:
- query:
method: echo
params:
- Hello World
- a: 1
b: hello
timeout: 1
- json_rpc:
console:
name: jsonrpc_server
key: $(test)_PASS
name: JSONRPC console Query not waiting (only send)
skipped: $(skip_tcp)
steps:
- query:
id: 3095372
method: echo
no_wait: true
params:
- a: -1
b: olleh
- World Hello
timeout: 1
- sleep:
name: Small delay for the test
skipped: $(skip_tcp)
timeout: 1
- json_rpc:
console:
name: jsonrpc_server
key: $(test)_PASS
name: JSONRPC console Reception
skipped: $(skip_tcp)
steps:
- receive:
id: 3095372
timeout: 1
- console:
console_name: jsonrpc_server
doc: Opening the RAW TCP console
execute_on_stop: true
key: $(test)_PASS
name: Close the raw tcp console
skipped: $(skip_tcp)
steps:
- close: null
- json_rpc:
key: $(test)_PASS
name: JSONRPC UDP query waiting for reception
steps:
- open: null
- query:
method: echo
name: echo
params:
- Hello World
- a: 1
b: hello
timeout: 1
- close: null
timeout: 1
udp:
rcv_port: 8765
server: localhost
snd_port: 4323
- json_rpc:
key: $(test)_FAIL
name: Failing JSONRPC UDP query waiting for reception
(returning an error)
steps:
- open: null
- query:
method: echo2
params:
- Hello World
- a: 1
b: hello
timeout: 1
- close: null
timeout: 1
udp:
rcv_port: 8765
server: localhost
snd_port: 4323
- json_rpc:
key: $(test)_PASS
name: JSONRPC UDP query waiting for reception of
an expected error
steps:
- open: null
- query:
expected_result:
code: -32000
message: function not found
method: echo2
params:
- Hello World
- a: 1
b: hello
- close: null
timeout: 1
udp:
rcv_port: 8765
server: localhost
snd_port: 4323
- json_rpc:
doc: 'Failing JSONRPC UDP query waiting for reception
and checking result
and timeout elapses (wrong udp port)
'
key: $(test)_FAIL
name: Failing UDP JSONRPC query timeout elapses (wrong
udp port)
steps:
- open: null
- query:
method: echo
params:
- Hello World
- a: 1
b: hello
timeout: 0.5
- close: null
timeout: 1
udp:
rcv_port: 48393
server: localhost
snd_port: 4326
- json_rpc:
key: $(test)_PASS
name: JSONRPC UDP query not waiting (only send)
steps:
- open: null
- query:
id: 3095372
method: echo
no_wait: true
params:
- a: -1
b: olleh
- World Hello
timeout: 1
udp:
rcv_port: 8765
server: localhost
snd_port: 4323
- sleep:
name: Small delay for the test
timeout: 1
- json_rpc:
key: $(test)_PASS
name: JSONRPC UDP Reception
steps:
- receive:
id: 3095372
- close: null
timeout: 1
udp:
rcv_port: 8765
server: localhost
snd_port: 4323
- json_rpc:
key: $(test)_PASS
name: JSONRPC UDP query not waiting (only send)
steps:
- open: null
- query:
id: 3095372
method: echo2
no_wait: true
params:
- a: -1
b: olleh
- World Hello
timeout: 1
udp:
rcv_port: 8765
server: localhost
snd_port: 4323
- sleep:
name: Small delay for the test
timeout: 1
- json_rpc:
key: $(test)_FAIL
name: Failing JSONRPC UDP Reception (returning an
error)
steps:
- receive:
id: 3095372
timeout: 1
- close: null
timeout: 1
udp:
rcv_port: 8765
server: localhost
snd_port: 4323
- json_rpc:
doc: JSONRPC UDP query waiting for reception and
checking result
key: $(test)_PASS
name: UDP JSONRPC query waiting and checking
steps:
- open: null
- query:
expected_result:
- - Hello World
- a: 1
b: hello
- {}
method: echo
params:
- Hello World
- a: 1
b: hello
timeout: 1
- close: null
timeout: 1
udp:
rcv_port: 48393
server: localhost
snd_port: 4323
- json_rpc:
doc: JSONRPC UDP query waiting for reception and
checking result
key: $(test)_FAIL
name: Failing UDP JSONRPC query waiting and checking
steps:
- open: null
- query:
expected_result:
- []
- {}
method: echo
params:
- Hello World
- a: 1
b: hello
timeout: 1
- close: null
timeout: 1
udp:
rcv_port: 48393
server: localhost
snd_port: 4323
- json_rpc:
doc: JSONRPC UDP query not waiting, with the purpose
to check the result at reception
key: $(test)_PASS
name: UDP JSONRPC query not waiting (for checking)
steps:
- open: null
- query:
id: 3095372
method: echo
no_wait: true
params:
- a: -1
b: olleh
- World Hello
timeout: 1
udp:
rcv_port: 9876
server: localhost
snd_port: 4323
- sleep:
name: Small delay for the test
timeout: 1
- json_rpc:
doc: JSONRPC UDP Reception and checking result
key: $(test)_PASS
name: UDP JSONRPC reception checking
steps:
- receive:
expected_result:
- - a: -1
b: olleh
- World Hello
- {}
id: 3095372
timeout: 1
- close: null
timeout: 1
udp:
rcv_port: 9876
server: localhost
snd_port: 4323
- json_rpc:
doc: JSONRPC UDP Reception and checking result
key: $(test)_FAIL
name: Failing UDP JSONRPC reception checking
steps:
- receive:
expected_result:
- - a: -1
b: ollhe
- World Hello
- {}
id: 3095372
timeout: 1
- close: null
timeout: 1
udp:
rcv_port: 9876
server: localhost
snd_port: 4323
- json_rpc:
doc: 'JSONRPC UDP query waiting for reception and
checking result with
replacing $(result) and evaluating string.
'
key: $(test)_PASS
name: UDP JSONRPC query waiting and evaluating result
steps:
- open: null
- query:
expected_result: 1
method: echo
params:
- Hello World
- a: 1
b: hello
process_result: $(result)[0][1]['a']
timeout: 1
- close: null
timeout: 1
udp:
rcv_port: 48393
server: localhost
snd_port: 4323
- json_rpc:
doc: 'JSONRPC UDP query waiting for reception and
checking result with
replacing $(result) and evaluating string.
'
key: $(test)_FAIL
name: Failing UDP JSONRPC query waiting and evaluating
result
steps:
- open: null
- query:
expected_result: $(result)[0][1]['a'] == 0
method: echo
params:
- Hello World
- a: 1
b: hello
timeout: 1
- close: null
timeout: 1
udp:
rcv_port: 48393
server: localhost
snd_port: 4323
- console:
console_name: jrpces
doc: check if the jsonrpc echo server is installed
execute_on_stop: true
key: $(test)_PASS
name: Stop json rpc echo server
steps:
- close:
protocol: terminal
filename: /home/renish/workspace/testium/code/test/validation/items/jsonrpc/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

157
schema/test_schema/let.tum Normal file
View File

@@ -0,0 +1,157 @@
config_file:
- param.yaml
- items/let/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: let test constants
values:
- test: let
- test_path: items/$(test)
- group:
name: let test
steps:
- sequence:
steps:
- loop:
iterator: 10
key: $(test)_PASS
name: Cycle number of loops
steps:
- py_func:
file: $(test_path)$(psep)let.py
func_name: donothing
name: do nothing
- let:
name: Let it be
values:
- be: <| $(loop_param) == $(it) |>
- it: $(loop_param)
- loop:
iterator:
- 12
- 20
- 30
key: $(test)_PASS
name: Cycle iterating on list
steps:
- py_func:
file: $(test_path)$(psep)let.py
func_name: checkloopparam
name: check loop param
param:
- $(loop_param)
- let:
name: Let it be
values:
- it: $(loop_param)
- be: <| $(loop_param) == $(it) |>
- let:
key: $(test)_PASS
name: Get time
values:
- loop_t0: $(ts_start_Cycle iterating on list)
- loop_t1: $(ts_end_Cycle iterating on list)
- loop_duration: $(ts_duration_Cycle iterating on list)
- let:
key: $(test)_PASS
name: Get parameter file value
values:
- test_overwrite_me: <| $(overwrite_me) == True |>
- py_func:
file: $(test_path)$(psep)let.py
func_name: checkGlobalDic
name: Check global dic pass
param:
- test_overwrite_me
- true
- let:
key: $(test)_PASS
name: Overwrite parameter file value
values:
- overwrite_me: false
- py_func:
expected_result: $(overwrite_me) == False
file: $(test_path)$(psep)let.py
func_name: checkGlobalDic
key: $(test)_FAIL
name: Check global dic fail
param:
- overwrite_me
- true
- py_func:
expected_result: fail
file: $(test_path)$(psep)let.py
func_name: checkGlobalDic
key: $(test)_PASS
name: Check global dic fail
param:
- overwrite_me
- true
- let:
key: $(test)_PASS
name: Evaluate Overwriting parameter file value
values:
- test_overwrite_me: <| "$(overwrite_me)" == True |>
- check:
key: $(test)_PASS
name: Check Overwriting parameter file value
values:
- <| $(test_overwrite_me) == False |>
filename: /home/renish/workspace/testium/code/test/validation/items/let/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,297 @@
config_file:
- param.yaml
- items/lua_func/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: lua_func test constants
values:
test: lua_func
test_path: items/$(test)
- group:
name: lua_func test
steps:
- sequence:
steps:
- let:
name: lua_func test constants,
values:
lua_func test parameter: test parameter lua_func
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: assertparam
key: $(test)_FAIL
name: fail lua_func
param:
- false
- lua_func:
expected_result: FAIL
file: $(test_path)$(psep)lua_func.lua
func_name: assertparam
key: $(test)_PASS
name: fail lua_func with expected result FAIL
param:
- false
- lua_func:
expected_result: FAIL
file: $(test_path)$(psep)lua_func.lua
func_name: assertparam
key: $(test)_FAIL
name: pass lua_func with expected result FAIL
param:
- true
- lua_func:
expected_result: -1
file: $(test_path)$(psep)lua_func.lua
func_name: echo
key: $(test)_PASS
name: expected -1
param:
- -1
- lua_func:
expected_result: 354848436 - 354848437
file: $(test_path)$(psep)lua_func.lua
func_name: echo
key: $(test)_PASS
name: expected eval
param:
- -1
- lua_func:
expected_result: '[-1, ''a'', {''toto'': ''tata''}]'
file: $(test_path)$(psep)lua_func.lua
func_name: echo
key: $(test)_PASS
name: expected table
param:
- - -1
- a
- toto: tata
- lua_func:
expected_result: $(lua_func test parameter)
file: $(test_path)$(psep)lua_func.lua
func_name: checkglobal
key: $(test)_PASS
name: global param lua_func
param:
- lua_func test parameter
- lua_func:
expected_result: ($(lua_data_to_be_returned))[0]
file: $(test_path)$(psep)lua_func.lua
func_name: checkglobal2
key: $(test)_PASS
name: global param lua_func 1
param:
- 1
- lua_func:
expected_result: ($(lua_data_to_be_returned))[1]
file: $(test_path)$(psep)lua_func.lua
func_name: checkglobal2
key: $(test)_PASS
name: global param lua_func 2
param:
- 2
- lua_func:
expected_result: ($(lua_data_to_be_returned))[2]
file: $(test_path)$(psep)lua_func.lua
func_name: checkglobal2
key: $(test)_PASS
name: global param lua_func 3
param:
- 3
- let:
key: $(test)_PASS
name: python2func
values:
- py: $(test_path)$(psep)lua_func.lua
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: should_not_be_called
name: skipped_checkglobal
param:
- $(test parameter)
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: checkglobal
name: skipped true
param:
- $(test parameter)
skipped: true
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: checkglobal
name: skipped 1
param:
- $(test parameter)
skipped: true
- group:
name: Function results check
steps:
- group:
name: Function result failure
steps:
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: echo
key: $(test)_PASS
name: int failure
param:
- -1
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: echo
key: $(test)_PASS
name: float failure
param:
- -1.3
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: echo
key: $(test)_PASS
name: String failure
param:
- FAIL
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: tuple_return
key: $(test)_PASS
name: Tuple int,str failure
param:
- -1
- Got a failure
- group:
name: Functions result success
steps:
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: echo
key: $(test)_PASS
name: int success
param:
- 0
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: echo
key: $(test)_PASS
name: float success
param:
- 0.3
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: echo
key: $(test)_PASS
name: String success
param:
- Something that is not only strictly FAIL
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: tuple_return
key: $(test)_PASS
name: Tuple int,str success
param:
- 0
- OK
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: test_delgd
key: $(test)_PASS
name: delgd test
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: return_nothing
key: $(test)_PASS
name: function returning nothing should succeed
- lua_func:
file: $(test_path)$(psep)lua_func.lua
func_name: return_explicit_nil
key: $(test)_PASS
name: function returning explicit nil should succeed
- group:
name: context_id tests
steps:
- lua_func:
context_id: lua_ctx_test
expected_result: hello lua
file: $(test_path)$(psep)lua_func.lua
func_name: set_context_value
key: $(test)_PASS
name: set context value
param:
- hello lua
- lua_func:
context_id: lua_ctx_test
expected_result: hello lua
file: $(test_path)$(psep)lua_func.lua
func_name: get_context_value
key: $(test)_PASS
name: get context value (same context_id)
- lua_func:
expected_result: hello lua
file: $(test_path)$(psep)lua_func.lua
func_name: get_context_value
key: $(test)_PASS
name: get context value (no context_id, from main
gd)
- lua_func:
context_id: lua_ctx_other
expected_result: hello lua
file: $(test_path)$(psep)lua_func.lua
func_name: get_context_value
key: $(test)_PASS
name: get context value (different context_id)
filename: /home/renish/workspace/testium/code/test/validation/items/lua_func/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,414 @@
config_file:
- param.yaml
- items/parallel/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: parallel test constants
values:
test: parallel
test_path: items/$(test)
- group:
name: parallel test
steps:
- sequence:
steps:
- parallel:
branches:
- name: Branch A
steps:
- let:
name: Set A done
values:
- branch_a_done: true
- name: Branch B
steps:
- let:
name: Set B done
values:
- branch_b_done: true
key: $(test)_PASS
name: Both branches pass
sync: all
- check:
key: $(test)_PASS
name: Both branches ran
values:
- <| $(branch_a_done) == True |>
- <| $(branch_b_done) == True |>
- parallel:
branches:
- name: Pass branch
steps:
- let:
name: Set pass flag
values:
- pass_branch_ran: true
- name: Fail branch
steps:
- py_func:
expected_result: fail
file: $(test_path)$(psep)parallel.py
func_name: sleep_func
name: Raise exception
param:
- 0
key: $(test)_PASS
name: One branch fails
no_fail: true
sync: all
- check:
key: $(test)_PASS
name: Pass branch still ran
values:
- <| $(pass_branch_ran) == True |>
- let:
name: Reset slow flag
values:
- slow_done: false
- parallel:
branches:
- name: Fast branch
steps:
- let:
name: Fast done
values:
- fast_done: true
- name: Slow branch
steps:
- py_func:
file: $(test_path)$(psep)parallel.py
func_name: sleep_func
name: Sleep 2s
param:
- 2
- let:
name: Slow done
values:
- slow_done: true
key: $(test)_PASS
name: sync any - first wins
sync: any
- check:
key: $(test)_PASS
name: Fast branch ran, slow branch was stopped
values:
- <| $(fast_done) == True |>
- <| $(slow_done) == False |>
- let:
name: Reset sync flag
values:
- sync_flag: ''
- waiter_ran: false
- parallel:
branches:
- name: Setter branch
steps:
- py_func:
file: $(test_path)$(psep)parallel.py
func_name: sleep_func
name: Sleep 0.3s then set flag
param:
- 0.3
- let:
name: Set sync flag
values:
- sync_flag: ready
- name: Waiter branch
steps:
- let:
name: Got flag
values:
- waiter_ran: true
wait_for:
condition: <| "$(sync_flag)" == "ready" |>
timeout: 10
key: $(test)_PASS
name: wait_for synchronization
sync: all
- check:
key: $(test)_PASS
name: Waiter branch ran after flag was set
values:
- <| $(waiter_ran) == True |>
- parallel:
branches:
- name: Sleep A
steps:
- sleep:
name: Sleep 1s A
timeout: 1
- name: Sleep B
steps:
- sleep:
name: Sleep 1s B
timeout: 1
key: $(test)_PASS
name: Timing test
sync: all
- let:
name: Capture parallel duration
values:
- parallel_duration: $(ts_duration_Timing test)
- check:
key: $(test)_PASS
name: Duration < 1.8s (would be 2s if sequential)
values:
- <| float("$(parallel_duration)") < 1.8 |>
- let:
name: Reset N flags
values:
- n_a: false
- n_b: false
- n_c: false
- n_d: false
- parallel:
branches:
- name: NA
steps:
- let:
name: set n_a
values:
- n_a: true
- name: NB
steps:
- let:
name: set n_b
values:
- n_b: true
- name: NC
steps:
- let:
name: set n_c
values:
- n_c: true
- name: ND
steps:
- let:
name: set n_d
values:
- n_d: true
key: $(test)_PASS
name: Four branches
sync: all
- check:
key: $(test)_PASS
name: Four branches all set their flag
values:
- <| $(n_a) == True |>
- <| $(n_b) == True |>
- <| $(n_c) == True |>
- <| $(n_d) == True |>
- let:
name: Reset nested flags
values:
- outer_x: false
- inner_x_1: false
- inner_x_2: false
- parallel:
branches:
- name: Outer X
steps:
- let:
name: set outer_x
values:
- outer_x: true
- parallel:
branches:
- name: Inner X1
steps:
- let:
name: set inner_x_1
values:
- inner_x_1: true
- name: Inner X2
steps:
- let:
name: set inner_x_2
values:
- inner_x_2: true
name: Inner parallel
sync: all
- name: Outer Y
steps:
- sleep:
name: brief sleep
timeout: 0
key: $(test)_PASS
name: Outer parallel
sync: all
- check:
key: $(test)_PASS
name: Nested parallel set all flags
values:
- <| $(outer_x) == True |>
- <| $(inner_x_1) == True |>
- <| $(inner_x_2) == True |>
- let:
name: Reset waiter timeout flag
values:
- waiter_timeout_ran: false
- parallel:
branches:
- name: Quick branch
steps:
- sleep:
name: brief sleep
timeout: 0
- name: Doomed waiter
steps:
- let:
name: should not run
values:
- waiter_timeout_ran: true
wait_for:
condition: <| "never" == "ready" |>
timeout: 1
key: $(test)_PASS
name: wait_for timeout
no_fail: true
sync: all
- check:
key: $(test)_PASS
name: Doomed waiter never ran its steps
values:
- <| $(waiter_timeout_ran) == False |>
- parallel:
branches:
- name: ok branch
steps:
- let:
name: noop
values:
- noop_var: 1
- name: broken branch
steps:
- py_func:
expected_result: fail
file: $(test_path)$(psep)parallel.py
func_name: sleep_func
name: Forced fail
param:
- 0
key: $(test)_FAIL
name: One branch really fails
sync: all
- let:
name: Reset branch condition flag
values:
- cond_branch_ran: false
- other_branch_ran: false
- parallel:
branches:
- condition: <| "always" == "false" |>
name: Skipped branch
steps:
- let:
name: should not run
values:
- cond_branch_ran: true
- name: Other branch
steps:
- let:
name: ran
values:
- other_branch_ran: true
key: $(test)_PASS
name: Condition-skipped branch
sync: all
- check:
key: $(test)_PASS
name: Skipped condition branch did not run
values:
- <| $(cond_branch_ran) == False |>
- <| $(other_branch_ran) == True |>
- let:
name: Reset loop counters
values:
- loop_count_a: 0
- loop_count_b: 0
- loop:
iterator: 3
name: Loop wrapping parallel
steps:
- parallel:
branches:
- name: LA
steps:
- let:
name: bump A
values:
- loop_count_a: <| int("$(loop_count_a)")
+ 1 |>
- name: LB
steps:
- let:
name: bump B
values:
- loop_count_b: <| int("$(loop_count_b)")
+ 1 |>
name: Per-iteration parallel
sync: all
- check:
key: $(test)_PASS
name: Both branches ran 3 times
values:
- <| int("$(loop_count_a)") == 3 |>
- <| int("$(loop_count_b)") == 3 |>
filename: /home/renish/workspace/testium/code/test/validation/items/parallel/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

137
schema/test_schema/plot.tum Normal file
View File

@@ -0,0 +1,137 @@
config_file:
- param.yaml
- items/plot/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: plot test constants
values:
test: plot
test_path: items/$(test)
- group:
name: plot test
steps:
- sequence:
steps:
- group:
condition: <| $(validation_dialogs) and not tm.text_mode()
|>
name: Plot test
steps:
- plot:
key: $(test)_PASS
name: Open the plot
plot_name: Mon Plot
steps:
- open:
log_path: $(validation_report_path)
- plot:
key: $(test)_PASS
name: Add periodic to the plot
plot_name: Mon Plot
steps:
- periodic:
eval: '{"periodic": $(result)}'
file: $(test_path)$(psep)plot.py
func_name: random_value
period: 1
- sleep:
dialog: true
name: sleep
timeout: 3
- loop:
iterator: 10
name: Add of other data in the plot
steps:
- plot:
key: $(test)_PASS
name: Add to the plot
plot_name: Mon Plot
steps:
- add:
value1: $(loop_index)
value2: $(loop_index)+2
- sleep:
name: sleep between values
timeout: 1
- py_func:
file: $(test_path)$(psep)plot.py
func_name: LastValues
key: $(test)_PASS
name: last plot values
param:
- Mon Plot
- plot:
execute_on_stop: true
key: $(test)_PASS
name: Export
plot_name: Mon Plot
steps:
- export: $(validation_report_path)/plot_export.pdf
- export: $(validation_report_path)/plot_export.csv
- plot:
execute_on_stop: true
key: $(test)_PASS
name: Close the plot
plot_name: Mon Plot
steps:
- close:
timeout: 2
wait_dialog_exit: true
filename: /home/renish/workspace/testium/code/test/validation/items/plot/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,326 @@
config_file:
- param.yaml
- items/py_func/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: py_func test constants
values:
test: py_func
test_path: items/$(test)
- group:
name: py_func test
steps:
- sequence:
steps:
- let:
name: py_func test constants,
values:
py_func test parameter: test parameter
- py_func:
file: $(test_path)$(psep)py_func.py
func_name: assertparam
key: $(test)_PASS
name: pass py_func
param:
- true
- py_func:
file: $(test_path)$(psep)py_func.py
func_name: assertparam
key: $(test)_FAIL
name: fail py_func
param:
- false
- py_func:
expected_result: FAIL
file: $(test_path)$(psep)py_func.py
func_name: assertparam
key: $(test)_PASS
name: fail py_func with expected result "FAIL"
param:
- false
- py_func:
expected_result: FAIL
file: $(test_path)$(psep)py_func.py
func_name: assertparam
key: $(test)_FAIL
name: pass py_func with expected result FAIL
param:
- true
- py_func:
expected_result: -1
file: $(test_path)$(psep)py_func.py
func_name: echo
key: $(test)_PASS
name: expected -1
param:
- -1
- py_func:
expected_result: 354848436 - 354848437
file: $(test_path)$(psep)py_func.py
func_name: echo
key: $(test)_PASS
name: expected eval
param:
- -1
- py_func:
expected_result: '[-1, ''a'', {''toto'': ''tata''}]'
file: $(test_path)$(psep)py_func.py
func_name: echo
key: $(test)_PASS
name: expected table
param:
- - -1
- a
- toto: tata
- py_func:
file: $(test_path)$(psep)py_func.py
func_name: checkglobal
key: $(test)_PASS
name: global param py_func
param:
- $(py_func test parameter)
- let:
key: $(test)_PASS
name: python2func
values:
- py: $(test_path)$(psep)py_func.py
- py_func:
expected_result: $(py_func test parameter)
file: $(py)
func_name: checkglobal2
key: $(test)_PASS
name: global param py_func 2
- py_func:
file: $(py)
func_name: checkglobal
key: $(test)_PASS
name: global param py_func
param:
- $(py_func test parameter)
- py_func:
file: $(test_path)$(psep)py_func.py
func_name: should_not_be_called
name: skipped_checkglobal
param:
- $(py_func test parameter)
- py_func:
file: $(test_path)$(psep)py_func.py
func_name: checkglobal
name: skipped true
param:
- $(py_func test parameter)
skipped: true
- py_func:
file: $(test_path)$(psep)py_func.py
func_name: checkglobal
name: skipped 1
param:
- $(py_func test parameter)
skipped: 1
- py_func:
file: $(test_path)$(psep)py_func.py
func_name: ValidationTest
name: FunctionItem test
param:
- $(py_func test parameter)
- group:
name: Function results check
steps:
- group:
name: Function result 1
steps:
- py_func:
expected_result: -1
file: $(test_path)$(psep)py_func.py
func_name: echo
key: $(test)_PASS
name: int failure
param:
- -1
- py_func:
expected_result: -1.3
file: $(test_path)$(psep)py_func.py
func_name: echo
key: $(test)_PASS
name: float failure
param:
- -1.3
- py_func:
expected_result: FAIL
file: $(test_path)$(psep)py_func.py
func_name: echo
key: $(test)_PASS
name: String failure
param:
- FAIL
- py_func:
expected_result:
- -1
- Got a failure
file: $(test_path)$(psep)py_func.py
func_name: tuple_return
key: $(test)_PASS
name: Tuple int,str failure
param:
- -1
- Got a failure
- group:
name: Functions result 2
steps:
- py_func:
expected_result: 0
file: $(test_path)$(psep)py_func.py
func_name: echo
key: $(test)_PASS
name: int success
param:
- 0
- py_func:
expected_result: 0.3
file: $(test_path)$(psep)py_func.py
func_name: echo
key: $(test)_PASS
name: float success
param:
- 0.3
- py_func:
expected_result: Something that is not only
strictly FAIL
file: $(test_path)$(psep)py_func.py
func_name: echo
key: $(test)_PASS
name: String success
param:
- Something that is not only strictly FAIL
- py_func:
expected_result:
- 0
- OK
file: $(test_path)$(psep)py_func.py
func_name: tuple_return
key: $(test)_PASS
name: Tuple int,str success
param:
- 0
- OK
- py_func:
file: $(test_path)$(psep)py_func.py
func_name: test_delgd
key: $(test)_PASS
name: delgd test
- py_func:
file: $(test_path)$(psep)py_func.py
func_name: return_nothing
key: $(test)_PASS
name: function returning nothing should succeed
- py_func:
file: $(test_path)$(psep)py_func.py
func_name: return_explicit_none
key: $(test)_PASS
name: function returning explicit None should succeed
- group:
name: context_id tests
steps:
- py_func:
expected_result: hello context
file: $(test_path)$(psep)py_func.py
func_name: set_context_value
key: $(test)_PASS
name: set serializable value
param:
- hello context
- py_func:
context_id: ctx_test
expected_result: hello context
file: $(test_path)$(psep)py_func.py
func_name: get_context_value
key: $(test)_PASS
name: get serializable value (same context_id)
- py_func:
expected_result: hello context
file: $(test_path)$(psep)py_func.py
func_name: get_context_value
key: $(test)_PASS
name: get serializable value (no context_id, from
main gd)
- py_func:
context_id: ctx_other
expected_result: hello context
file: $(test_path)$(psep)py_func.py
func_name: get_context_value
key: $(test)_PASS
name: get serializable value (different context_id)
- py_func:
context_id: ctx_ns_test
expected_result: hello ns
file: $(test_path)$(psep)py_func.py
func_name: set_ns_value
key: $(test)_PASS
name: set non-serializable value
param:
- hello ns
- py_func:
context_id: ctx_ns_test
expected_result: hello ns
file: $(test_path)$(psep)py_func.py
func_name: get_ns_value
key: $(test)_PASS
name: get non-serializable value (same context_id)
filename: /home/renish/workspace/testium/code/test/validation/items/py_func/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,87 @@
config_file:
- param.yaml
- items/sleep/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: sleep test constants
values:
test: sleep
test_path: items/$(test)
- group:
name: sleep test
steps:
- sequence:
steps:
- sleep:
condition: $(validation_dialogs)
dialog: true
key: $(test)_PASS
name: Sleep timeout with dialogs
timeout: 3
- sleep:
key: $(test)_PASS
name: Sleep timeout without dialog
timeout: 3.0
- sleep:
key: $(test)_PASS
name: Sleep timeout in textual format
skipped: true
timeout: 1h 3m 2s
filename: /home/renish/workspace/testium/code/test/validation/items/sleep/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

View File

@@ -0,0 +1,84 @@
config_file:
- param.yaml
- items/unittest/param.yaml
main:
name: Testium validation suite
steps:
- group:
name: Test preparation
steps:
- let:
condition: <| "$(os)" == "Linux" |>
name: Set test variables for Linux
values:
- terminal_prompt: $(linux_prompt)
- psep: /
- let:
condition: <| "$(os)" == "Windows" |>
name: Set test variables for Windows
values:
- terminal_prompt: $(windows_prompt)
- psep: \
- group:
name: Group of tests
steps:
- let:
name: unittest test constants
values:
test: unittest
test_path: items/$(test)
- group:
name: unittest test
steps:
- sequence:
steps:
- unittest:
key: $(test)_PASS
name: Unittest item
test_file: /home/renish/workspace/testium/code/test/validation/items/unittest/unittest.py
test_method: test_01_pass
- unittest:
key: $(test)_FAIL
name: Unittest item
test_file: /home/renish/workspace/testium/code/test/validation/items/unittest/unittest.py
test_method:
- test_04_disabled
- test_03_fail
filename: /home/renish/workspace/testium/code/test/validation/items/unittest/test.tum
- sequence:
steps:
- report:
export:
- text:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.txt
- html:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.html
- junit:
key: $(test)_PASS
path: $(validation_report_path)$(psep)$(test)_PASS.junit
name: Expected PASS $(test) test
- report:
export:
- text:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.txt
- html:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.html
- junit:
key: $(test)_FAIL
path: $(validation_report_path)$(psep)$(test)_FAIL.junit
name: Expected FAIL $(test) test
filename: /home/renish/workspace/testium/code/test/validation/items/report.tum
report:
enabled: true
export:
junit:
file_name: $(validation_report_file).junit
path: $(validation_report_path)
sqlite:
file_name: $(validation_report_file).sqlite
path: $(validation_report_path)
log_stored: true

532
schema/tum.json Normal file
View File

@@ -0,0 +1,532 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"config_file":{
"desciption": "The list of the configuration files",
"type": "array",
"items": {
"type": "string"
}
},
"items":{
"properties": {
"name": { "type": "string" },
"stop_on_failure":{ "type": "boolean" },
"execute_on_stop":{ "type": "boolean" },
"skipped":{ "type": "boolean" },
"no_fail":{ "type": "boolean" },
"doc":{ "type": "string" },
"key":{ "type": "string" },
"report":{ "$ref": "#/$defs/report_export" },
"condition":{ "type": "string" },
"process_result":{ "type": "string" },
"expected_result":{ },
"store_result":{ "type": "string" }
}
},
"steps" : {
"required": ["steps"],
"properties": {
"version": { "type": "string" },
"steps": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties":{
"let": { "$ref": "#/$defs/let" },
"check": { "$ref": "#/$defs/check" },
"dialog_question": { "$ref": "#/$defs/dialog" },
"dialog_message": { "$ref": "#/$defs/dialog" },
"dialog_note": { "$ref": "#/$defs/dialog" },
"dialog_value": { "$ref": "#/$defs/dialog" },
"dialog_image": { "$ref": "#/$defs/dialog" },
"lua_func": { "$ref": "#/$defs/func" },
"py_func": { "$ref": "#/$defs/func" },
"console": { "$ref": "#/$defs/console" },
"sleep": { "$ref": "#/$defs/sleep" },
"json_rpc": { "$ref": "#/$defs/json_rpc" },
"group": { "$ref": "#/$defs/group" },
"sequence": { "$ref": "#/$defs/sequence" },
"report": { "$ref": "#/$defs/report" },
"loop": { "$ref": "#/$defs/loop" },
"git": { "$ref": "#/$defs/git" },
"unittest": { "$ref": "#/$defs/unittest" }
}
}
}
}
},
"cons_open" : {
"type":"object",
"allOf":
[
{ "$ref": "#/$defs/items" },
{
"properties": {
"protocol" : {
"enum": ["telnet", "ssh", "serial", "rawtcp", "terminal"]
},
"telnet_host" : {"type": "string" },
"telnet_port" : {"type": "number" },
"ssh_host": {"type": "string" },
"ssh_user": {"type": "string" },
"ssh_pwd": {"type": "string" },
"serial_port": {"type": "string" },
"serial_baudrate": {"type": "integer" },
"buffered": {"type": "boolean" },
"tcp_host" : {"type": "string" },
"tcp_port" : {"type": "number" },
"terminal_path": {"type":"string" },
"shell": {"type":"string" }
},
"required": ["protocol"]
}
]
},
"cons_read": {
"type":"object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"properties": {
"protocol" : {
"enum": ["telnet", "ssh", "serial", "rawtcp", "terminal"]
},
"expected" : {"type": "string" },
"timeout": {"type": "number" },
"mute": {"type": "boolean" }
},
"required":["expected"]
}
]
},
"console" : {
"description": "The let items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"required": ["steps", "console_name"],
"properties":{
"console_name": {"type":"string" },
"steps": {
"type": "array",
"items": {
"type": "object",
"properties":{
"open": {
"$ref": "#/$defs/cons_open"
},
"write": { "type": "string" },
"writeln": { "type": "string" },
"read_until": {
"$ref": "#/$defs/cons_read"
},
"close": {
"anyOf": [
{"type": "null"},
{"type": "string"}
]
}
},
"additionalProperties": false
}
}
}
}
]
},
"json_rpc_console": {
"type":"object",
"properties": {
"name" : {"type": "string" },
"prompt" : {"type": "string" }
},
"required":["name"],
"additionalProperties": false
},
"json_rpc_udp": {
"type":"object",
"properties": {
"server" : {"type": "string" },
"udp_snd_port" : {"type": "integer" },
"udp_rcv_port" : {"type": "integer" },
"bufsize" : {"type": "integer" }
},
"required":["name"],
"additionalProperties": false
},
"json_rpc_query": {
"type":"object",
"properties": {
"method" : { "type": "string" },
"params" : { "type": "array" },
"id" : {"type": "integer" },
"no_wait" : {"type": "boolean" }
},
"required":["method"],
"additionalProperties": false
},
"json_rpc_receive": {
"type":"object",
"properties": {
"id" : {"type": "integer" },
"timeout" : {"type": "number" }
},
"required":["id"],
"additionalProperties": false
},
"json_rpc" : {
"description": "The json_rpc items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"required": ["steps"],
"properties":{
"udp" : { "$ref": "#/$defs/json_rpc_udp" },
"console" : { "$ref": "#/$defs/json_rpc_console" },
"timeout": {"type": "number" },
"version": {"enum": ["1.0", "2.0"]},
"steps": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties":{
"open": { "type": "null" },
"query": { "$ref": "#/$defs/json_rpc_query" },
"receive": { "$ref": "#/$defs/json_rpc_receive" },
"writeln": { "type": "string" },
"read_until": { "$ref": "#/$defs/cons_read"
},
"close": { "type": "null" }
}
}
}
}
}
]
},
"group" : {
"description": "The group items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{ "$ref": "#/$defs/steps" }
]
},
"sequence" : {
"description": "The sequence items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{ "$ref": "#/$defs/steps" },
{
"properties": {
"filename": { "type": "string" }
},
"required": ["filename"]
}
]
},
"loop" : {
"description": "The loop items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{ "$ref": "#/$defs/steps" },
{
"properties": {
"iterator": { },
"exit_condition": {
"description": "the posibility to stop the loop",
"type": "object",
"additionalProperties": false,
"properties": {
"time" : {"type":"number"},
"value" : {"type":"number"},
"file" : {"type":"string"},
"func_name" : {"type":"string"},
"eval" : {"type":"string"}
},
"dependentRequired": {
"file" : ["func_name"]
}
}
}
}
]
},
"main" : {
"description": "The Main items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{ "$ref": "#/$defs/steps" },
{
"properties": {
"version": { "type": "string" }
}
}
]
},
"sleep" : {
"description": "Sleep for X time [secondes",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"properties":{
"dialog": { "type": "boolean" },
"timeout": { "type": "number" }
},
"required": ["timeout"]
}
]
},
"let" : {
"description": "The let items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"properties":{
"values": {
"anyOf": [
{
"type": "array",
"items": {"type": "object" }
},
{ "type":"object" }
]
}
},
"required": ["values"]
}
]
},
"check" : {
"description": "The let items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"properties":{
"values": {
"type": "array",
"items": {"type": "string" }
}
},
"required": ["values"]
}
]
},
"git" : {
"description": "The git items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"properties":{
"repo": { "type": "string" }
},
"required": ["repo"]
}
]
},
"unittest" : {
"description": "The unittest items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"properties":{
"test_file": { "type": "string" },
"test_method": {
"anyOf": [
{"type":"string"},
{
"type":"array",
"items": {"type":"string"}
}
]
}
},
"required": ["test_file"]
}
]
},
"dialog" : {
"description": "The let items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"properties":{
"question": { "type": "string" },
"auto_result": { "type": "string" },
"auto_value": { "type": ["number", "string"] },
"filename" : {"type": "string"},
"reference" : {
"type":"array",
"items": { "type":"string"}
}
},
"required": ["question"]
}
]
},
"dialog_choice" : {
"description": "The one choi items",
"type": "object",
"additionalProperties": false,
"properties":{
"name": { "type": "string" },
"description": { "type": "string" },
"icon": { "type": "string"},
"choices": {
"type":"array",
"items": { "$ref": "#/$defs/dialog_choice" }
}
},
"required": ["name", "description"]
},
"dialog_choices" : {
"description": "The dialog choices items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"properties":{
"name": { "type": "string" },
"question": { "type": "string" },
"icon": { "type": "string"},
"choices": {
"type":"array",
"items": { "$ref": "#/$defs/dialog_choice" }
}
},
"required": ["question"]
}
]
},
"func" : {
"description": "The py_fun and lua_func items",
"type": "object",
"unevaluatedProperties": false,
"allOf": [
{ "$ref": "#/$defs/items" },
{
"properties":{
"file": { "type": "string" },
"func_name": { "type": "string" },
"context_id": { "type": "string" },
"param" : {
"anyOf": [
{"type": "array"},
{"type": "object"}
]
}
},
"required": ["file", "func_name"]
}
]
},
"report_export": {
"type": "object",
"properties": {
"path": {"type":"string" },
"file_name": {"type":"string" },
"pattern": {"type":"string" },
"key": {"type":"string" }
}
},
"report":{
"desciption": "The list of the configuration files",
"type": "object",
"additional_properties":false,
"properties": {
"enabled": {"type":"boolean" },
"log_stored": {"type":"boolean" },
"export": {
"anyOf": [
{
"type": "object",
"properties":{
"html": { "$ref": "#/$defs/report_export" },
"sqlite": { "$ref": "#/$defs/report_export" },
"junit": { "$ref": "#/$defs/report_export" }
}
},
{
"type": "array",
"items": {
"type":"object",
"properties":{
"html": { "$ref": "#/$defs/report_export" },
"sqlite": { "$ref": "#/$defs/report_export" },
"junit": { "$ref": "#/$defs/report_export" }
}
}
}
]
}
}
}
},
"type": "object",
"properties": {
"config_file" : { "$ref": "#/$defs/config_file" },
"main": { "$ref": "#/$defs/main" },
"report" : { "$ref": "#/$defs/report" }
},
"required" : ["main"],
"additionalProperties": false
}

View File

@@ -20,6 +20,12 @@ if [ "$?" -ne 0 ]; then
echo "venv must be installed on the host distribution."
exit -1
fi
# Check if venv is installed
python3 -c "import ensurepip"
if [ "$?" -ne 0 ]; then
echo "ensurepip must be installed on the host distribution."
exit -1
fi
# Install the virtual environment if needed
if [ ! -d "$PY_VENV_DIR" ]; then

315
src/LICENSE Normal file
View File

@@ -0,0 +1,315 @@
Copyright (c) 2025-2026 François Dausseur
Licensed under the EUPL
EUROPEAN UNION PUBLIC LICENCE v. 1.2
EUPL © the European Union 2007, 2016
This European Union Public Licence (the 'EUPL') applies to the Work (as
defined below) which is provided under the terms of this Licence. Any use of
the Work, other than as authorised under this Licence is prohibited (to the
extent such use is covered by a right of the copyright holder of the Work).
The Work is provided under the terms of this Licence when the Licensor (as
defined below) has placed the following notice immediately following the
copyright notice for the Work:
Licensed under the EUPL
or has expressed by any other means his willingness to license under the EUPL.
1. Definitions
In this Licence, the following terms have the following meaning:
- 'The Licence': this Licence.
- 'The Original Work': the work or software distributed or communicated by the
Licensor under this Licence, available as Source Code and also as Executable
Code as the case may be.
- 'Derivative Works': the works or software that could be created by the
Licensee, based upon the Original Work or modifications thereof. This Licence
does not define the extent of modification or dependence on the Original
Work required in order to classify a work as a Derivative Work; this extent
is determined by copyright law applicable in the country mentioned in
Article 15.
- 'The Work': the Original Work or its Derivative Works.
- 'The Source Code': the human-readable form of the Work which is the most
convenient for people to study and modify.
- 'The Executable Code': any code which has generally been compiled and which
is meant to be interpreted by a computer as a program.
- 'The Licensor': the natural or legal person that distributes or communicates
the Work under the Licence.
- 'Contributor(s)': any natural or legal person who modifies the Work under
the Licence, or otherwise contributes to the creation of a Derivative Work.
- 'The Licensee' or 'You': any natural or legal person who makes any usage of
the Work under the terms of the Licence.
- 'Distribution' or 'Communication': any act of selling, giving, lending,
renting, distributing, communicating, transmitting, or otherwise making
available, online or offline, copies of the Work or providing access to its
essential functionalities at the disposal of any other natural or legal
person.
2. Scope of the rights granted by the Licence
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
sublicensable licence to do the following, for the duration of copyright
vested in the Original Work:
- use the Work in any circumstance and for all usage,
- reproduce the Work,
- modify the Work, and make Derivative Works based upon the Work,
- communicate to the public, including the right to make available or display
the Work or copies thereof to the public and perform publicly, as the case
may be, the Work,
- distribute the Work or copies thereof,
- lend and rent the Work or copies thereof,
- sublicense rights in the Work or copies thereof.
Those rights can be exercised on any media, supports and formats, whether now
known or later invented, as far as the applicable law permits so.
In the countries where moral rights apply, the Licensor waives his right to
exercise his moral right to the extent allowed by law in order to make
effective the licence of the economic rights here above listed.
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights
to any patents held by the Licensor, to the extent necessary to make use of
the rights granted on the Work under this Licence.
3. Communication of the Source Code
The Licensor may provide the Work either in its Source Code form, or as
Executable Code. If the Work is provided as Executable Code, the Licensor
provides in addition a machine-readable copy of the Source Code of the Work
along with each copy of the Work that the Licensor distributes or indicates,
in a notice following the copyright notice attached to the Work, a repository
where the Source Code is easily and freely accessible for as long as the
Licensor continues to distribute or communicate the Work.
4. Limitations on copyright
Nothing in this Licence is intended to deprive the Licensee of the benefits
from any exception or limitation to the exclusive rights of the rights owners
in the Work, of the exhaustion of those rights or of other applicable
limitations thereto.
5. Obligations of the Licensee
The grant of the rights mentioned above is subject to some restrictions and
obligations imposed on the Licensee. Those obligations are the following:
Attribution right: The Licensee shall keep intact all copyright, patent or
trademarks notices and all notices that refer to the Licence and to the
disclaimer of warranties. The Licensee must include a copy of such notices
and a copy of the Licence with every copy of the Work he/she distributes or
communicates. The Licensee must cause any Derivative Work to carry prominent
notices stating that the Work has been modified and the date of modification.
Copyleft clause: If the Licensee distributes or communicates copies of the
Original Works or Derivative Works, this Distribution or Communication will
be done under the terms of this Licence or of a later version of this Licence
unless the Original Work is expressly distributed only under this version of
the Licence — for example by communicating 'EUPL v. 1.2 only'. The Licensee
(becoming Licensor) cannot offer or impose any additional terms or conditions
on the Work or Derivative Work that alter or restrict the terms of the
Licence.
Compatibility clause: If the Licensee Distributes or Communicates Derivative
Works or copies thereof based upon both the Work and another work licensed
under a Compatible Licence, this Distribution or Communication can be done
under the terms of this Compatible Licence. For the sake of this clause,
'Compatible Licence' refers to the licences listed in the appendix attached
to this Licence. Should the Licensee's obligations under the Compatible
Licence conflict with his/her obligations under this Licence, the obligations
of the Compatible Licence shall prevail.
Provision of Source Code: When distributing or communicating copies of the
Work, the Licensee will provide a machine-readable copy of the Source Code or
indicate a repository where this Source will be easily and freely available
for as long as the Licensee continues to distribute or communicate the Work.
Legal Protection: This Licence does not grant permission to use the trade
names, trademarks, service marks, or names of the Licensor, except as
required for reasonable and customary use in describing the origin of the
Work and reproducing the content of the copyright notice.
6. Chain of Authorship
The original Licensor warrants that the copyright in the Original Work
granted hereunder is owned by him/her or licensed to him/her and that he/she
has the power and authority to grant the Licence.
Each Contributor warrants that the copyright in the modifications he/she
brings to the Work are owned by him/her or licensed to him/her and that
he/she has the power and authority to grant the Licence.
Each time You accept the Licence, the original Licensor and subsequent
Contributors grant You a licence to their contributions to the Work, under
the terms of this Licence.
7. Disclaimer of Warranty
The Work is a work in progress, which is continuously improved by numerous
Contributors. It is not a finished work and may therefore contain defects or
'bugs' inherent to this type of development.
For the above reason, the Work is provided under the Licence on an 'as is'
basis and without warranties of any kind concerning the Work, including
without limitation merchantability, fitness for a particular purpose, absence
of defects or errors, accuracy, non-infringement of intellectual property
rights other than copyright as stated in Article 6 of this Licence.
This disclaimer of warranty is an essential part of the Licence and a
condition for the grant of any rights to the Work.
8. Disclaimer of Liability
Except in the cases of wilful misconduct or damages directly caused to
natural persons, the Licensor will in no event be liable for any direct or
indirect, material or moral, damages of any kind, arising out of the Licence
or of the use of the Work, including without limitation, damages for loss of
goodwill, work stoppage, computer failure or malfunction, loss of data or any
commercial damage, even if the Licensor has been advised of the possibility
of such damage. However, the Licensor will be liable under statutory product
liability laws as far such laws apply to the Work.
9. Additional agreements
While distributing the Work, You may choose to conclude an additional
agreement, defining obligations or services consistent with this Licence.
However, if accepting obligations, You may act only on your own behalf and on
your sole responsibility, not on behalf of the original Licensor or any other
Contributor, and only if You agree to indemnify, defend, and hold each
Contributor harmless for any liability incurred by, or claims asserted
against such Contributor by the fact You have accepted any warranty or
additional liability.
10. Acceptance of the Licence
The provisions of this Licence can be accepted by clicking on an icon 'I
agree' placed under the bottom of a window displaying the text of this
Licence or by affirming consent in any other similar way, in accordance with
the rules of applicable law. Clicking on that icon indicates your clear and
irrevocable acceptance of this Licence and all of its terms and conditions.
Similarly, you irrevocably accept this Licence and all of its terms and
conditions by exercising any rights granted to You by Article 2 of this
Licence, such as the use of the Work, the creation by You of a Derivative
Work or the Distribution or Communication by You of the Work or copies
thereof.
11. Information to the public
In case of any Distribution or Communication of the Work by means of
electronic communication by You (for example, by offering to download the
Work from a remote location) the distribution channel or media (for example,
a website) must at least provide to the public the information requested by
the applicable law regarding the Licensor, the Licence and the way it may be
accessible, concluded, stored and reproduced by the Licensee.
12. Termination of the Licence
The Licence and the rights granted hereunder will terminate automatically
upon any breach by the Licensee of the terms of the Licence.
Such a termination will not terminate the licences of any person who has
received the Work from the Licensee under the Licence, provided such persons
remain in full compliance with the Licence.
13. Miscellaneous
Without prejudice of Article 9 above, the Licence represents the complete
agreement between the Parties as to the Work.
If any provision of the Licence is invalid or unenforceable under applicable
law, this will not affect the validity or enforceability of the Licence as a
whole. Such provision will be construed or reformed so as necessary to make
it valid and enforceable.
The European Commission may publish other linguistic versions or new versions
of this Licence or updated versions of the Appendix, so far this is required
and reasonable, without reducing the scope of the rights granted by the
Licence. New versions of the Licence will be published with a unique version
number.
All linguistic versions of this Licence, approved by the European Commission,
have identical value. Parties can take advantage of the linguistic version of
their choice.
14. Jurisdiction
Without prejudice to specific agreement between parties,
- any litigation resulting from the interpretation of this License, arising
between the European Union institutions, bodies, offices or agencies, as a
Licensor, and any Licensee, will be subject to the jurisdiction of the
Court of Justice of the European Union, as laid down in article 272 of the
Treaty on the Functioning of the European Union,
- any litigation arising between other parties and resulting from the
interpretation of this License, will be subject to the exclusive
jurisdiction of the competent court where the Licensor resides or conducts
its primary business.
15. Applicable Law
Without prejudice to specific agreement between parties,
- this Licence shall be governed by the law of the European Union Member
State where the Licensor has his seat, resides or has his registered
office,
- this licence shall be governed by Belgian law if the Licensor has no seat,
residence or registered office inside a European Union Member State.
Appendix
'Compatible Licences' according to Article 5 EUPL are:
- GNU General Public License (GPL) v. 2, v. 3
- GNU Affero General Public License (AGPL) v. 3
- Open Software License (OSL) v. 2.1, v. 3.0
- Eclipse Public License (EPL) v. 1.0
- CeCILL v. 2.0, v. 2.1
- Mozilla Public Licence (MPL) v. 2
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
works other than software
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
Reciprocity (LiLiQ-R+).
The European Commission may update this Appendix to later versions of the
above licences without producing a new version of the EUPL, as long as they
provide the rights granted in Article 2 of this Licence and protect the
covered Source Code from exclusive appropriation.
All other changes or additions to this Appendix require the production of a
new EUPL version.

View File

@@ -1 +1 @@
0.2
0.1.2

View File

@@ -11,6 +11,7 @@ import threading
from telnetlib3 import Telnet, DO, WILL, WONT, TTYPE, IAC, SB, SE, theNULL
TIMEOUT_NULL = 0.000001
STOP_POLL_INTERVAL = 0.2
class BytesStore(object):
@@ -123,12 +124,14 @@ A {classname}.close() is missing somewhere in your code !'.format(classname=type
# c = ''
return c
def read_until(self, match, timeout=None, return_data=False, mute=False):
def read_until(self, match, timeout=None, return_data=False, mute=False, should_stop=None):
"""
read until the string 'match is found
If timeout is not set (None), this function runs indefinitely
If timeout is set to zero, this function returns immediately
If mute is set to True the characters read from the console will not be displayed
If should_stop is a callable, it is polled between reads (every STOP_POLL_INTERVAL
at most) and the loop exits early — like a timeout — when it returns True.
If function fails (because of a timeout) it will return a 'status' integer set to -1
otherwise it will return 0.
@@ -139,13 +142,6 @@ A {classname}.close() is missing somewhere in your code !'.format(classname=type
status = -1
if not match:
raise ValueError('match parameter can not be empty')
# replace all '\r' by '\n' as any '\r' read will undergo the same replacement
# match = match.replace('\r\n', '\n')
# match = match.replace('\r', '')
# update the console timeout in conformity with what is required.
self.set_read_timeout(timeout)
if timeout is None:
timeout = 1000000
@@ -159,6 +155,7 @@ A {classname}.close() is missing somewhere in your code !'.format(classname=type
# buffer is empty
# Otherwise we are waiting for the timeout to rise
if timeout < TIMEOUT_NULL:
self.set_read_timeout(0)
data = self.readchar(0)
while (status < 0) and ((data is not None) and (data != b'')):
@@ -191,39 +188,45 @@ A {classname}.close() is missing somewhere in your code !'.format(classname=type
# Timeout different than zero
else:
# Poll in short chunks so a stop request is honored within
# STOP_POLL_INTERVAL, regardless of the per-protocol blocking
# behavior of readchar().
self.set_read_timeout(STOP_POLL_INTERVAL)
time_is_out = threading.Event()
timer = threading.Timer(timeout, lambda: time_is_out.set())
timer.start()
# We are waiting for the timeout to rise
try:
while (status < 0) and (not time_is_out.is_set()):
if should_stop is not None and should_stop():
break
while (status < 0) and (not time_is_out.isSet()):
data = self.readchar(timeout)
if data is not None:
data = self._compute_char(data)
if data != '':
if not mute:
self.string_buffer += data
read_data += data
search_deque.append(data)
if search_deque == match_deque:
timer.cancel()
status = 0
if (not mute) and (data != '\n'):
self.string_buffer += '\n'
if data == '\n' or (status >= 0):
# the datas are written line by line for display optimisation in GUI mode
data = self.readchar(STOP_POLL_INTERVAL)
if data is not None:
data = self._compute_char(data)
if data != '':
if not mute:
self.string_buffer = self.string_buffer.replace('\r\n', '\n')
self.string_buffer = self.string_buffer.replace('\r', '')
self.stream.write(self.string_buffer)
self.string_buffer += data
read_data += data
date_str = str(datetime.now()).split('.')[0].split(' ')[1]
self.string_buffer = '[{} {}]'.format(date_str, self.name)
search_deque.append(data)
if search_deque == match_deque:
status = 0
if (not mute) and (data != '\n'):
self.string_buffer += '\n'
if data == '\n' or (status >= 0):
# the datas are written line by line for display optimisation in GUI mode
if not mute:
self.string_buffer = self.string_buffer.replace('\r\n', '\n')
self.string_buffer = self.string_buffer.replace('\r', '')
self.stream.write(self.string_buffer)
date_str = str(datetime.now()).split('.')[0].split(' ')[1]
self.string_buffer = '[{} {}]'.format(date_str, self.name)
finally:
timer.cancel()
if return_data:
return status, read_data

View File

@@ -1,5 +1,6 @@
import sys
import os
import queue
import multiprocessing as mp
from threading import Timer
from time import sleep, monotonic
@@ -367,7 +368,7 @@ class RuntimePlot:
self.msg_queue_in.get()
self.msg_queue_out.put({"command": "last_values"})
try:
res = self.msg_queue_in.get(timeout=1)
except:
res = self.msg_queue_in.get(timeout=5)
except queue.Empty:
raise ETUMRuntimeError(f"Impossible to retrieve the last values of the \"{self.name}\" plot")
return res

View File

@@ -10,6 +10,8 @@ import os
def setup():
"""Configure the Qt environment for dialog subprocess usage."""
if sys.platform.startswith('linux'):
# On Linux/Wayland, force X11 (via XWayland) to avoid crashes
# when Qt is initialized inside a multiprocessing subprocess.
os.environ['QT_QPA_PLATFORM'] = 'xcb'
if os.environ.get('DISPLAY'):
# X11 available: force xcb to avoid crashes in multiprocessing subprocesses.
os.environ['QT_QPA_PLATFORM'] = 'xcb'
elif os.environ.get('WAYLAND_DISPLAY'):
os.environ['QT_QPA_PLATFORM'] = 'wayland'

View File

@@ -20,52 +20,64 @@ class TestItem:
def test_run(f):
@wraps(f)
def wrapper(self):
if not self.skipped:
if self.enabled:
self.run_test_init()
# Conditional execution
raw_condition = self._prms.getParam(
"condition", default=None, processed=False
)
if raw_condition is None:
condition = True
else:
c = self._prms.expanse(raw_condition)
if isinstance(c, bool):
condition = c
else:
condition = False
c = False
if raw_condition == c:
msg = f'"{c}"'
else:
msg = f'"{raw_condition}" --> "{c}"'
# Do we have to skip the test because of a true condition ?
if condition:
if not raw_condition is None:
msg = "condition met: " + msg
self.result.reported = {"input_condition": msg}
print(msg)
# Test preparation
self.run_before_test()
# Test execution
f(self)
else:
msg = "condition not met: " + msg
self.result.set(TestValue.NORUN, msg)
self.result.reported = {"input_condition": msg}
self.run_test_end()
else:
self.result.set(TestValue.NORUN, "test disabled")
print("Test is disabled.")
else:
if self.skipped:
self.result.set(TestValue.NORUN, "test skipped")
print("Test is skipped.")
return self.result
if not self.enabled:
self.result.set(TestValue.NORUN, "test disabled")
print("Test is disabled.")
return self.result
self.run_test_init()
while self._is_paused:
sleep(0.2)
if self.isStopped() :
self.result.set(TestValue.NORUN, "test stopped")
print("Test is Stopped.")
self._is_stopped = False # Restore state for next run
return self.result
# Conditional execution
raw_condition = self._prms.getParam(
"condition", default=None, processed=False
)
if raw_condition is None:
condition = True
else:
c = self._prms.expanse(raw_condition)
if isinstance(c, bool):
condition = c
else:
condition = False
c = False
if raw_condition == c:
msg = f'"{c}"'
else:
msg = f'"{raw_condition}" --> "{c}"'
# Do we have to skip the test because of a true condition ?
if condition:
if not raw_condition is None:
msg = "condition met: " + msg
self.result.reported = {"input_condition": msg}
print(msg)
# Test preparation
self.run_before_test()
# Test execution
f(self)
else:
msg = "condition not met: " + msg
self.result.set(TestValue.NORUN, msg)
self.result.reported = {"input_condition": msg}
self.run_test_end()
return self.result
return wrapper
@@ -255,8 +267,6 @@ class TestItem:
self._sendStatusStarted()
if self._is_breakpoint:
self._is_paused = True
while self._is_paused:
sleep(0.2)
if self.is_container:
self.report.incLevel()
@@ -274,9 +284,6 @@ class TestItem:
if self.is_container:
self.report.decLevel()
while self._is_paused:
sleep(0.2)
# Post evaluation of the test result
self.process_result()
# expected_result treatment
@@ -310,6 +317,7 @@ class TestItem:
self.process_report(self._reported)
self.report.addTest(self, self.result, rk)
self._sendStatusFinished()
def process_result(self):
if self._post_eval is None:

View File

@@ -307,11 +307,17 @@ class TestItemConsoleReadUntil(TestItemConsoleAction):
try:
status, data = cons.read_until(
ru, timeout=read_timeout, return_data=True, mute=mute
ru, timeout=read_timeout, return_data=True, mute=mute,
should_stop=self.isStopped,
)
if status == 0:
self.result.set(TestValue.SUCCESS)
self.result.value = data
elif self.isStopped():
self.result.set(
result=TestValue.FAILURE,
message="Console read aborted on stop request",
)
else:
self.result.set(result=TestValue.FAILURE, message="No matching text")
if mute:

View File

@@ -105,6 +105,7 @@ class TestItemJSRPCActionQuery(TestItemAction):
jrpc_id = randint(1, (2**32) - 1)
send_only = self._prms.expanse(self._send_only)
timeout = self._prms.expanse(self._timeout)
self.token.set_should_stop(self.isStopped)
try:
success, result = self.token.query(
meth, obj, jrpc_id, send_only, timeout=timeout
@@ -146,6 +147,7 @@ class TestItemJSRPCActionReceive(TestItemAction):
def execute(self):
timeout = self._prms.expanse(self._timeout)
jrpc_id = self._prms.expanse(self._jrpc_id)
self.token.set_should_stop(self.isStopped)
try:
success, result = self.token.receive(jrpc_id, timeout)

View File

@@ -2,10 +2,11 @@ import json
import socket
import re
import struct
import time
from runtime.tum_except import ETUMRuntimeError
import api.testium as tm
from api.console import Console
from api.console import Console, STOP_POLL_INTERVAL
def is_ip_address(address):
@@ -45,9 +46,16 @@ class JrpcAdapter:
self._jrpc_version = version
self._mute = mute
self._timeout = timeout
# Optional callable polled by _receive() implementations to abort
# waits early when the test is being stopped. Set by the test item
# action before each query/receive call.
self._should_stop = None
if not (version == "1.0" or version == "2.0"):
raise ETUMRuntimeError("Invalid JSONRPC version passed.")
def set_should_stop(self, cb):
self._should_stop = cb
@property
def timeout(self):
return self._timeout
@@ -249,32 +257,38 @@ class JrpcUdpAdapter(JrpcAdapter):
print(f" | sent to @{self._server}:{self._snd_port}")
def _receive(self, timeout: float) -> str:
# Poll in short chunks so a stop request is honored within
# STOP_POLL_INTERVAL.
self.sock.settimeout(STOP_POLL_INTERVAL)
deadline = time.monotonic() + float(timeout)
data = None
addr = None
while True:
if self._should_stop is not None and self._should_stop():
raise ETUMRuntimeError("JSONRPC udp receive aborted on stop request.")
try:
data, addr = self.sock.recvfrom(self._bufsize)
break
except socket.timeout:
if time.monotonic() >= deadline:
raise ETUMRuntimeError(
"JSONRPC udp answer took too long. Try to increase the timeout."
)
# configures the reception timeout
self.sock.settimeout(timeout)
# Receives the answer from the server
try:
data, addr = self.sock.recvfrom(self._bufsize)
# In case of buffer overload we chose to complain
if len(data) >= self._bufsize:
raise ETUMRuntimeError(
"JSONRPC udp answer size overflow. Try to increase the bufsize"
)
# Converts binary to string
res = data.decode()
# Don't log if mute
if not self._mute:
print(f" | UDP answer: '{res}'")
print(f" | received from @{addr[0]}:{addr[1]}")
except socket.timeout:
# In case of buffer overload we chose to complain
if len(data) >= self._bufsize:
raise ETUMRuntimeError(
"JSONRPC udp answer took too long. Try to increase the timeout."
"JSONRPC udp answer size overflow. Try to increase the bufsize"
)
# Converts binary to string
res = data.decode()
# Don't log if mute
if not self._mute:
print(f" | UDP answer: '{res}'")
print(f" | received from @{addr[0]}:{addr[1]}")
return res
def _build_query(self, method: str, obj, jrpc_id: int):
@@ -339,11 +353,16 @@ class JrpcConsoleAdapter(JrpcAdapter):
def _receive(self, timeout: float) -> str:
status, data = self._cons.read_until(
self._endswith, timeout, return_data=True, mute=self._mute
self._endswith, timeout, return_data=True, mute=self._mute,
should_stop=self._should_stop,
)
# if we did not receive anything, we complain
if not status == 0:
if self._should_stop is not None and self._should_stop():
raise ETUMRuntimeError(
f"JSONRPC console receive aborted on stop request."
)
raise ETUMRuntimeError(
f"The '{self._cons.name}' console did not answer in the requested time."
)

View File

@@ -45,6 +45,18 @@ class TestItemLuaFunc(TestItem):
tm.setgd(_LUA_FUNC_CONTEXTS_KEY, contexts)
return contexts[ctx_id], True
def stop(self):
super().stop()
# Tear down the worker so any in-flight func_call returns promptly.
# join() clears _rpc/_process so a subsequent item reusing the same
# context_id can restart the engine cleanly.
try:
engine, _ = self._get_engine()
engine.stop()
engine.join()
except Exception:
pass
@test_run
def execute(self):
self.result.set(
@@ -96,9 +108,15 @@ Is the lua environnment well defined in the "LUA_PATH" and "LUA_CPATH" variables
return
except ConnectionAbortedError:
self.result.set(TestValue.FAILURE, "lua_func aborted on stop request")
print("lua_func aborted on stop request.")
except:
traceback.print_exception(*sys.exc_info())
self.result.set(
TestValue.FAILURE,
'Unrecoverable "lua_func" item error from {}'.format(self.func_name),
)
if self.isStopped():
self.result.set(TestValue.FAILURE, "lua_func aborted on stop request")
else:
self.result.set(
TestValue.FAILURE,
'Unrecoverable "lua_func" item error from {}'.format(self.func_name),
)

View File

@@ -45,6 +45,18 @@ class TestItemPyFunc(TestItem):
tm.setgd(_PY_FUNC_CONTEXTS_KEY, contexts)
return contexts[ctx_id], True
def stop(self):
super().stop()
# Tear down the worker so any in-flight func_call returns promptly.
# join() clears _rpc/_process so a subsequent item reusing the same
# context_id can restart the engine cleanly.
try:
engine, _ = self._get_engine()
engine.stop()
engine.join()
except Exception:
pass
@test_run
def execute(self):
self.result.set(
@@ -94,9 +106,15 @@ python_bin = {tm.gd("python_bin", "no python path defined")}"""
return
except ConnectionAbortedError:
self.result.set(TestValue.FAILURE, "py_func aborted on stop request")
print("py_func aborted on stop request.")
except:
traceback.print_exception(*sys.exc_info())
self.result.set(
TestValue.FAILURE,
'Unrecoverable "py_func" item error from {}'.format(self.func_name),
)
if self.isStopped():
self.result.set(TestValue.FAILURE, "py_func aborted on stop request")
else:
self.result.set(
TestValue.FAILURE,
'Unrecoverable "py_func" item error from {}'.format(self.func_name),
)

View File

@@ -13,6 +13,32 @@ from interpreter.utils.constants import TestItemType as cst
from runtime.tum_except import ETUMSyntaxError, ETUMRuntimeError, item_load_context
def _testium_launch_cmd():
"""Command prefix to launch a fresh testium instance, runtime-aware.
AppImage / Flatpak / PyInstaller / wheel / source all need a different
entry point than just the path to __main__.py (which may be a .py inside
a read-only bundle, or unreachable from the sub-instance's cwd).
"""
# AppImage: the env var holds the path to the .AppImage file itself.
appimage = os.environ.get("APPIMAGE")
if appimage:
return [appimage]
# Flatpak: re-launch via the Flatpak app id.
if os.path.isfile("/.flatpak-info"):
return ["flatpak", "run", "org.testium.Testium"]
# PyInstaller frozen exe: sys.executable is the binary itself.
if getattr(sys, "frozen", False):
return [sys.executable]
# Source / wheel: re-use the same Python with the same entry point that
# launched this instance, made absolute so cwd changes in the sub-instance
# don't break the lookup. argv[0] is either:
# - the package directory (source: `python3 src/testium ...`)
# - the console_scripts wrapper (wheel: `/usr/bin/testium`)
# Both are runnable as `python <argv0>`.
return [sys.executable, os.path.abspath(sys.argv[0])]
def nowInBetween(start, end):
"""
Check wether current time is within boundaries
@@ -33,8 +59,6 @@ class TestItemRun(TestItem):
with item_load_context(self.cmd(), self.name(), self.seqFilename()):
self.tum_file = self._prms.getParam('tum', required=True)
self.param_file = self._prms.getParam('param_file', default='')
self.python_bin = self._prms.getParam('python_bin', default='')
self.testium_path = self._prms.getParam('testium_path', default='')
self.log_path = self._prms.getParam('log_file', default='')
self.report_path = self._prms.getParam('report_file', default='')
self.start_time = self._prms.getParam('start_time')
@@ -52,18 +76,9 @@ class TestItemRun(TestItem):
'"{}" file could not be found'.format(file_path))
self.tum_file = file_path
pf = self._prms.expanse(self.param_file)
pp = self._prms.expanse(self.python_bin)
sp = self._prms.expanse(self.testium_path)
lp = self._prms.expanse(self.log_path)
rp = self._prms.expanse(self.report_path)
cmd = []
if sp == '':
sp = sys.argv[0]
if pp != '':
cmd.append(pp)
elif not os.path.isfile(sp) or not os.access(sp, os.X_OK):
cmd.append(sys.executable)
cmd.append(sp)
cmd = _testium_launch_cmd()
if tm.text_mode():
cmd.append("-b")
else:

View File

@@ -80,4 +80,7 @@ class TestItemSleep(TestItem):
end_time = _time.time() + float(timeout)
while _time.time() < end_time and not self._is_stopped:
sleep(min(0.05, end_time - _time.time()))
self.result.set(TestValue.SUCCESS, 'Sleep %s sec' % (str(timeout)))
if self._is_stopped:
self.result.set(TestValue.FAILURE, 'Sleep aborted on stop request')
else:
self.result.set(TestValue.SUCCESS, 'Sleep %s sec' % (str(timeout)))

View File

@@ -17,7 +17,7 @@ Public API
``reset()`` : clear the cache (mostly useful for tests)
"""
import shutil
import os
import subprocess
import api.testium as tm
@@ -30,10 +30,126 @@ from runtime.tum_except import ETUMRuntimeError
_PYTHON_CANDIDATES = ["python3", "python"]
_LUA_CANDIDATES = ["lua", "lua5.5", "lua5.4", "lua5.3", "lua5.2", "lua5.1"]
# When running inside a Flatpak, --filesystem=host-os mounts the host at
# /run/host (read-only). Binaries and libraries from the host are not on the
# sandbox PATH/LD_LIBRARY_PATH, so we probe and inject them explicitly.
_FLATPAK_HOST_DIRS = [
"/run/host/usr/local/bin",
"/run/host/usr/bin",
"/run/host/bin",
]
_FLATPAK_HOST_LIB_DIRS = [
"/run/host/usr/lib",
"/run/host/usr/lib64",
"/run/host/usr/local/lib",
]
# Inside an AppImage, AppRun prepends $APPDIR/usr/bin to PATH and exports a
# bundle-local PYTHONHOME / PYTHONPATH / LD_LIBRARY_PATH. We want py_func and
# lua_func to run under the *host* interpreter (not the bundled one), so we
# probe standard host bin dirs directly and scrub APPDIR-prefixed entries from
# the env passed to host subprocesses.
_APPIMAGE_HOST_DIRS = [
"/usr/local/bin",
"/usr/bin",
"/bin",
]
def _in_flatpak():
return os.path.isfile("/.flatpak-info")
def _in_appimage():
return "APPIMAGE" in os.environ
def apply_host_lua_paths(env):
"""Prepend host Lua module dirs to LUA_PATH / LUA_CPATH (Flatpak only).
Must be called after user-defined lua_env overrides are applied, so host
paths are always first regardless of user config. User-defined paths remain
in the variable but after the host ones.
"""
if not _in_flatpak():
return
_LUA_VERSIONS = ["5.5", "5.4", "5.3", "5.2", "5.1"]
_HOST = "/run/host/usr"
cpath_dirs, lpath_dirs = [], []
for v in _LUA_VERSIONS:
for base in [f"{_HOST}/lib/lua/{v}",
f"{_HOST}/lib64/lua/{v}",
f"{_HOST}/lib/x86_64-linux-gnu/lua/{v}"]:
cpath_dirs.append(f"{base}/?.so")
lpath_dirs.append(f"{_HOST}/share/lua/{v}/?.lua")
lpath_dirs.append(f"{_HOST}/share/lua/{v}/?/init.lua")
sep = ";"
host_cpath = sep.join(cpath_dirs)
host_lpath = sep.join(lpath_dirs)
# ;; keeps Lua's compiled-in defaults at the end as last resort
env["LUA_CPATH"] = host_cpath + sep + env.get("LUA_CPATH", ";;")
env["LUA_PATH"] = host_lpath + sep + env.get("LUA_PATH", ";;")
def apply_host_libs(env):
"""Prepare *env* for launching a host binary from inside our bundle.
- Flatpak: prepend host library dirs to LD_LIBRARY_PATH so the dynamic
linker can find host .so files mounted under /run/host.
- AppImage: strip $APPDIR-prefixed entries from LD_LIBRARY_PATH and
PYTHONPATH and drop PYTHONHOME, so the host interpreter doesn't try
to load the bundled (incompatible) Python lib/site-packages.
- Otherwise: no-op.
"""
if _in_flatpak():
dirs = ":".join(d for d in _FLATPAK_HOST_LIB_DIRS if os.path.isdir(d))
if dirs:
existing = env.get("LD_LIBRARY_PATH", "")
env["LD_LIBRARY_PATH"] = dirs + (":" + existing if existing else "")
return
if _in_appimage():
appdir = os.environ.get("APPDIR", "")
if appdir:
for var, sep in (("LD_LIBRARY_PATH", ":"),
("PYTHONPATH", os.pathsep),
("PATH", os.pathsep)):
cur = env.get(var, "")
if not cur:
continue
cleaned = sep.join(
p for p in cur.split(sep)
if p and not p.startswith(appdir)
)
if cleaned:
env[var] = cleaned
else:
env.pop(var, None)
env.pop("PYTHONHOME", None)
def _which(name):
func = sys_app_path_win if tm.OS() == "Windows" else sys_app_path_lin
return func(name)
if tm.OS() == "Windows":
return sys_app_path_win(name)
if _in_flatpak():
for d in _FLATPAK_HOST_DIRS:
p = os.path.join(d, name)
if os.path.isfile(p) and os.access(p, os.X_OK):
return p
return ""
if _in_appimage():
for d in _APPIMAGE_HOST_DIRS:
p = os.path.join(d, name)
if os.path.isfile(p) and os.access(p, os.X_OK):
return p
return ""
return sys_app_path_lin(name)
def _probe_env():
"""Subprocess env for probing host binaries (adds host libs in Flatpak)."""
env = os.environ.copy()
apply_host_libs(env)
return env
def _python_version(path):
@@ -41,7 +157,7 @@ def _python_version(path):
try:
r = subprocess.run(
cmd, capture_output=True, text=True,
encoding=tm.sys_encoding(), timeout=10,
encoding=tm.sys_encoding(), timeout=10, env=_probe_env(),
)
except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired):
return None
@@ -60,6 +176,7 @@ def _lua_version(path):
try:
r = subprocess.run(
[path, "-v"], capture_output=True, text=True, timeout=10,
env=_probe_env(),
)
except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired):
return None
@@ -97,8 +214,16 @@ def _resolve(name):
path = ""
if override:
if shutil.which(override) and validator(override):
path = override
# Absolute path: accept as-is (user knows exactly what they want).
# Bare name: resolve via _which() so the override stays host-only in
# Flatpak/AppImage instead of silently picking the bundled interpreter.
if os.path.isabs(override):
resolved = override if (os.path.isfile(override)
and os.access(override, os.X_OK)) else ""
else:
resolved = _which(override)
if resolved and validator(resolved):
path = resolved
else:
tm.print_warn(
f"Configured {display} interpreter '{override}' is not usable; "

View File

@@ -60,6 +60,7 @@ class LuaProcessBase:
lua_env = tm.gd("lua_env", {})
env = os.environ.copy()
bins.apply_host_libs(env)
if not isinstance(lua_env, dict):
raise ETUMRuntimeError(f"The 'lua_env' global value should be a dictionary. But it is '{lua_env}'.")
@@ -70,6 +71,7 @@ class LuaProcessBase:
env[k] = e
else:
env[k] = e + ";" + env.get(k, "")
bins.apply_host_lua_paths(env)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("localhost", 0))
@@ -144,4 +146,12 @@ class LuaProcessBase:
"""
if self._rpc is not None:
self._rpc.stop()
# Force-kill the worker if it's still running. Needed when user code
# in the worker is stuck and won't notice the parent closing the RPC
# socket on its own.
if self._process is not None and self._process.poll() is None:
try:
self._process.terminate()
except Exception:
pass

View File

@@ -42,6 +42,10 @@ class PyProcessBase:
raise ETUMRuntimeError(f"The 'py_env' global value should be a dictionary. But it is '{py_env}'.")
env = os.environ.copy()
bins.apply_host_libs(env)
# PYTHONUSERBASE is set by the Flatpak runtime to isolate sandbox
# user packages; remove it so the host Python finds ~/.local packages.
env.pop("PYTHONUSERBASE", None)
for k, v in self.CUST_ENV.items():
e = py_env.get(k, "")
if e != "":
@@ -119,3 +123,11 @@ class PyProcessBase:
def stop(self):
if self._rpc is not None:
self._rpc.stop()
# Force-kill the worker if it's still running. Needed when user code
# in the worker is stuck (e.g. sleep, blocking I/O) and won't notice
# the parent closing the RPC socket on its own.
if self._process is not None and self._process.poll() is None:
try:
self._process.terminate()
except Exception:
pass

View File

@@ -31,10 +31,15 @@ def get_version(path :str)-> str:
return "Warning git not supported in your settings, version of {} unknown".format(path)
def get_testium_version():
# Flatpak bundle
if os.path.isfile('/.flatpak-info'):
ver = os.environ.get('TESTIUM_VERSION', '').strip()
return (ver if ver else 'unknown') + " (flatpak release)"
# AppImage
if 'APPIMAGE' in os.environ:
ver = os.getenv('SEQUENCER_REV', 'unknown')
return ver + " (binary release)"
ver = os.environ.get('TESTIUM_VERSION', '').strip()
return (ver if ver else 'unknown') + " (binary release)"
# PyInstaller frozen exe
if getattr(sys, 'frozen', False):

View File

@@ -41,8 +41,7 @@ end
--- INTERNAL: Handle requests from the client
function JSONRPC:_handle_request(req)
local method = self.methods[req.method]
local ok, ret
local res, err
local ok, ret, err
if not method then
if req.id then self:_send_error(req.id, string.format("Method '%s' not registered in lua server")) end
return
@@ -52,15 +51,18 @@ function JSONRPC:_handle_request(req)
-- Only send response if it's not a Notification (notifications have no ID)
if req.id then
if ok then
res = ret
if res == nil then
self:_send_error(req.id, tostring(err))
else
self:_send({ jsonrpc = "2.0", result = { returned_value = res }, id = req.id })
end
else
if not ok then
-- pcall trapped a runtime error in the method itself.
self:_send_error(req.id, tostring(ret))
elseif err ~= nil then
-- Method ran but signaled a logical error via its 2nd return.
self:_send_error(req.id, tostring(err))
else
-- Success. A user function returning nothing yields ret==nil;
-- encode it as JSON null so "returned_value" stays present.
local val = ret
if val == nil then val = json.null end
self:_send({ jsonrpc = "2.0", result = { returned_value = val }, id = req.id })
end
end
end

View File

@@ -212,8 +212,17 @@ class TestFileManager:
d = ""
if w.testFile is not None:
d = os.path.dirname(w.testFile)
# In Flatpak the native dialog goes through the XDG document portal,
# which returns /run/user/UID/doc/.../test.tum and only exposes the
# selected file — sibling files (param.yaml, .py, etc.) are unreachable.
# Force Qt's own dialog, which walks the real filesystem mounted via
# --filesystem=home and returns a regular path with sibling access.
options = QFileDialog.Options()
if os.path.isfile("/.flatpak-info"):
options |= QFileDialog.Option.DontUseNativeDialog
file_name, _ = QFileDialog.getOpenFileName(
w, "Open the test file", d, "testium file (*.tum);;All Files (*)"
w, "Open the test file", d,
"testium file (*.tum);;All Files (*)", options=options
)
if file_name:
self.reload(file_name)

View File

@@ -176,7 +176,7 @@ class TestRunner:
w.actionOpenTest.setDisabled(True)
w.actionExit.setDisabled(True)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(icon_prefix() + "/pause.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon.addPixmap(QtGui.QPixmap(icon_prefix() + "/pause2.png"), QtGui.QIcon.Normal, QtGui.QIcon.On)
w.actionStart_test.setIcon(icon)
w.actionStart_test.setText("Pause test")
w.actionPreferences.setDisabled(True)

View File

@@ -8,16 +8,20 @@ def exception_handler(typ_exc, value, trbk):
print(f"Critical failure : '{value}'.")
tb = traceback.format_exception(typ_exc, value, trbk)
print("".join(tb))
print(f" python : {sys.executable}")
print(f" sys.path : {sys.path}")
sys.excepthook = exception_handler
p = Path(__file__)
p = p.parent / ".."
p = p.resolve()
sys.path.append(p)
# Make the parent directory of py_func/ (= the testium package dir, which also
# contains runtime/, lua_func/, …) the first entry on sys.path so `from py_func
# import main` and `from runtime…` resolve regardless of cwd or how this script
# was invoked. str() because some importers don't play well with PathLike entries.
_pkg_parent = str((Path(__file__).resolve().parent / "..").resolve())
if _pkg_parent not in sys.path:
sys.path.insert(0, _pkg_parent)
from py_func import main
if __name__ == '__main__':
main()
main()

View File

@@ -200,6 +200,7 @@ class JsonRpcConnection:
Raises:
TimeoutError: If no response is received within `timeout`.
ConnectionAbortedError: If stop() was called while waiting.
"""
req_id = next(self.id_gen)
@@ -214,7 +215,12 @@ class JsonRpcConnection:
self.pending.pop(req_id, None)
raise TimeoutError("Timeout JSON-RPC")
return self.pending.pop(req_id)["response"]
entry = self.pending.pop(req_id)
if entry["response"] is None:
# Woken by stop() (or by a malformed dispatch) rather than by a
# real response — abort the call so callers don't block further.
raise ConnectionAbortedError("JSON-RPC client stopped")
return entry["response"]
def print_info(self, msg):
if self.dbg_out is not None:
@@ -223,6 +229,10 @@ class JsonRpcConnection:
def stop(self):
if self.running:
self.running = False
# Wake any in-flight call() so it doesn't sit on its (default 1h)
# timeout. The response stays None and call() raises ConnectionAbortedError.
for entry in list(self.pending.values()):
entry["event"].set()
def join(self):
self.recv_thread.join()

View File

@@ -49,4 +49,12 @@ function module.test_delgd()
return 0
end
function module.return_nothing()
-- Returns no value: ret is nil but no error.
end
function module.return_explicit_nil()
return nil
end
return module

View File

@@ -186,6 +186,18 @@
file: $(test_path)$(psep)lua_func.lua
func_name: test_delgd
- lua_func:
name: function returning nothing should succeed
key: $(test)_PASS
file: $(test_path)$(psep)lua_func.lua
func_name: return_nothing
- lua_func:
name: function returning explicit nil should succeed
key: $(test)_PASS
file: $(test_path)$(psep)lua_func.lua
func_name: return_explicit_nil
- group:
name: context_id tests
steps:

View File

@@ -54,3 +54,10 @@ def test_delgd():
tm.delgd("_py_delgd_test")
assert tm.gd("_py_delgd_test", None) is None
return 0
def return_nothing():
# Falls off the end: implicit None return, no error.
pass
def return_explicit_none():
return None

View File

@@ -196,6 +196,18 @@
file: $(test_path)$(psep)py_func.py
func_name: test_delgd
- py_func:
name: function returning nothing should succeed
key: $(test)_PASS
file: $(test_path)$(psep)py_func.py
func_name: return_nothing
- py_func:
name: function returning explicit None should succeed
key: $(test)_PASS
file: $(test_path)$(psep)py_func.py
func_name: return_explicit_none
- group:
name: context_id tests
steps: