Compare commits
10 Commits
v0.1.1
...
116e528a7d
| Author | SHA1 | Date | |
|---|---|---|---|
| 116e528a7d | |||
| cc744e17a1 | |||
| ab39b49558 | |||
| 95275c4418 | |||
| 0d614c2921 | |||
| 9466b091dd | |||
| 511288bd03 | |||
| 51b144f60c | |||
| dee8d4a682 | |||
| e726d47547 |
@@ -1,4 +1,4 @@
|
||||
# Testium — Claude Context
|
||||
# Testium — Design Context
|
||||
|
||||
## What is testium
|
||||
|
||||
107
build_all.sh
Executable file
107
build_all.sh
Executable 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.
@@ -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
|
||||
|
||||
@@ -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
|
||||
==============
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.1.1
|
||||
0.1.2
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user