10 Commits

Author SHA1 Message Date
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
10 changed files with 198 additions and 71 deletions

View File

@@ -1,4 +1,4 @@
# Testium — Claude Context
# Testium — Design Context
## What is testium

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"

Binary file not shown.

View File

@@ -46,8 +46,9 @@ $RUNTIME run --rm \
appimage-builder --recipe AppImageBuilder.yml --skip-test
"
echo "Done: testium-${APP_VERSION}-x86_64.AppImage"
APPIMAGE_FILE=$(ls -1t Testium-*-x86_64.AppImage 2>/dev/null | head -1)
echo "Done: ${APPIMAGE_FILE}"
if [ "${1}" = "install" ]; then
install -v "testium-${APP_VERSION}-x86_64.AppImage" "${HOME}/.local/bin/testium"
if [ "${1}" = "install" ] && [ -n "${APPIMAGE_FILE}" ]; then
install -v "${APPIMAGE_FILE}" "${HOME}/.local/bin/testium"
fi

View File

@@ -1,23 +1,19 @@
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
==============
- Packaging: Flatpak bundle (desktop entry, MIME, distributable .flatpak)
and AppImage (containerized build, runs on Arch / non-Debian hosts).
- bins.py: host-only Python/Lua resolution from sandboxed bundles
(Flatpak / AppImage); fail fast at test load if the host interpreter
is missing.
- run item: runtime-aware launcher (AppImage / Flatpak / PyInstaller /
source / wheel); drop testium_path / python_bin parameters.
- dialog_env: auto-detect Wayland vs xcb from $DISPLAY / $WAYLAND_DISPLAY
instead of forcing xcb (was hanging dialogs on pure-Wayland sessions).
- version: read TESTIUM_VERSION env in Flatpak/AppImage so the About
dialog stops reporting "unknown".
- runtime_plot last_values: bump timeout 1s -> 5s and narrow the bare
except to queue.Empty.
- py_func/__main__: robust sys.path init, diagnostic on import failure.
- Subprocess stdio (py_func / lua_func) routed into the parent log.
- README refocused on users (quick_start, tutorial); CONTRIBUTING filled.
- Docs: CLAUDE.md Packaging section rewritten.
- LICENSE file (EUPL-1.2) added.
- 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
==============

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

View File

@@ -1 +1 @@
0.1.1
0.1.2

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

@@ -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)