7 Commits

Author SHA1 Message Date
3fb982b057 Solving refresh bug which disables every tests 2026-06-04 22:13:54 +02:00
53553dc1fa Allow floating number for console timeout 2026-06-04 22:11:23 +02:00
717727bf5a Unified let syntax to be a list of objects 2026-06-04 22:11:23 +02:00
d97d00c593 removed test logs 2026-06-02 00:00:40 +02:00
2b0c4b5ee0 release 2026-06-01 23:48:56 +02:00
59e63e1338 fix(flatpak): console on host + dialog persistence
- term console via flatpak-spawn --host so host venvs resolve (bins.host_console_command)
- QSettings sync() before subprocess kill in choices/tested-refs dialogs
- console regression test: fails on the in-sandbox 0.2.1 console

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 23:42:48 +02:00
de32a524da docs: testium_assist install instructions (Open VSX / VSCode)
Manual (modes.rst) and README: install the extension from Open VSX in
VSCodium/Cursor/etc., and as a .vsix by hand in Microsoft VSCode; note
that testium must be on PATH or set via testium.serverPath.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 23:56:34 +02:00
20 changed files with 141 additions and 33 deletions

View File

@@ -129,6 +129,22 @@ A VSCode / VSCodium client extension (`testium_assist`) wraps `testium lsp`;
the schema is built from testium itself, so new item types and parameters the schema is built from testium itself, so new item types and parameters
appear in the editor on the next testium upgrade with no client change. appear in the editor on the next testium upgrade with no client change.
It is published on [Open VSX](https://open-vsx.org/extension/testium/testium-assist),
so in **VSCodium, Cursor, Windsurf, Theia and code-server** it installs from the
Extensions view (search `testium-assist`) or with
`codium --install-extension testium.testium-assist`.
**Microsoft VSCode** does not list Open VSX extensions, so install the `.vsix`
by hand — download it from the Open VSX page above, then *Extensions → ⋯ →
Install from VSIX…* or:
```sh
code --install-extension testium-assist-0.1.0.vsix
```
The extension runs `testium lsp`, so `testium` must be on the `PATH` (otherwise
point the `testium.serverPath` setting at the binary/AppImage).
## Troubleshooting ## Troubleshooting
### `wl_proxy_marshal_flags` symbol error ### `wl_proxy_marshal_flags` symbol error

View File

@@ -67,3 +67,36 @@ dependencies:
:caption: enable the language server for a wheel / source install :caption: enable the language server for a wheel / source install
pip install 'testium[lsp]' pip install 'testium[lsp]'
Installing the VSCode / VSCodium extension
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The *testium_assist* client extension is published on `Open VSX
<https://open-vsx.org/extension/testium/testium-assist>`_, the registry used by
VSCodium, Cursor, Windsurf, Eclipse Theia and code-server. In those editors,
open the Extensions view and search ``testium-assist``, or install it from the
command line:
.. code-block:: text
:caption: install in VSCodium and other Open VSX editors
codium --install-extension testium.testium-assist
Microsoft *VSCode* uses a different marketplace that does not list Open VSX
extensions, so install the packaged ``.vsix`` by hand. Download it from the
Open VSX page linked above, then either choose *Extensions**⋯*
*Install from VSIX…* in the UI, or run:
.. code-block:: text
:caption: install the .vsix in Microsoft VSCode
code --install-extension testium-assist-0.1.0.vsix
The extension launches ``testium lsp``, so the ``testium`` command must be on
the ``PATH``. If *testium* is installed elsewhere — a specific binary or an
AppImage — point the ``testium.serverPath`` setting at it instead.
Once installed, open a ``.tum`` file: completion of item types, hover
documentation and the outline view become available. If nothing happens, check
that no ``files.associations`` entry forces ``*.tum`` to another language (it
must stay the ``tum`` language the extension provides).

View File

@@ -9,9 +9,9 @@ This element is of the following form:
- let: - let:
name: Let Item name: Let Item
values: values:
key1: value1 - key1: value1
key2: value2 - key2: value2
key3: <| $(variable)[$(loop_index)] |> - key3: <| $(variable)[$(loop_index)] |>
The ``let`` element is used to set values in the global directory. The ``let`` element is used to set values in the global directory.

View File

@@ -51,8 +51,8 @@ The parameter file can be specified in the `.tum` file root:
:caption: configuration files definition in the main `.tum` test file :caption: configuration files definition in the main `.tum` test file
config_file: config_file:
config1.yaml - config1.yaml
config2.yaml - config2.yaml
main: main:
name: Test example name: Test example

Binary file not shown.

View File

@@ -1,3 +1,9 @@
version 0.2.2
==============
- Flatpak sandbox issue fixed for term console. Now a term console is
exactly like a host console.
- Persistence fix of dialogs in case of flatpak.
version 0.2.1 version 0.2.1
============== ==============
- Faster test loading, especially for large tests built from jinja - Faster test loading, especially for large tests built from jinja

View File

@@ -1 +1 @@
0.2.1 0.2.2

View File

@@ -81,9 +81,13 @@ class TermConsole(Console):
bufsize=0) bufsize=0)
else: else:
self.term = pexpect.spawn( shell_cmd, # In Flatpak this returns a `flatpak-spawn --host` wrapper so the
echo=False, # console behaves like a host shell (matching py_func / lua_func /
cwd=self.ppath) # run); elsewhere it's the chosen command unchanged.
from interpreter.utils import bins
argv = bins.host_console_command(shell_cmd, self.ppath)
self.term = pexpect.spawn(argv[0], args=argv[1:],
echo=False, cwd=self.ppath)
self.q = BytesStore() self.q = BytesStore()
self.t = threading.Thread(target=self.enqueue_output) self.t = threading.Thread(target=self.enqueue_output)

View File

@@ -221,6 +221,11 @@ def main(args, conn=None):
if conn: if conn:
settings.setValue(SettingsLastChoices, result) settings.setValue(SettingsLastChoices, result)
# Flush before sending: the parent terminates this subprocess as soon
# as it reads the result, so the QSettings destructor never runs and
# the write would race the kill (lost under Flatpak — see the
# tested-references dialog for the full rationale).
settings.sync()
conn.send([result, success]) conn.send([result, success])
conn.close() conn.close()
else: else:

View File

@@ -344,7 +344,7 @@ class TestItemConsoleReadUntil(TestItemConsoleAction):
def execute(self): def execute(self):
cons = self.get_console() cons = self.get_console()
ru = self._prms.expanse(self._read_until) ru = self._prms.expanse(self._read_until)
read_timeout = int(self._prms.getParam("timeout", default=-1, processed=True)) read_timeout = float(self._prms.getParam("timeout", default=-1, processed=True))
mute = self._prms.getParam("mute", default=False, processed=True) mute = self._prms.getParam("mute", default=False, processed=True)
if read_timeout < 0: if read_timeout < 0:
read_timeout = None read_timeout = None

View File

@@ -76,6 +76,12 @@ def main(args, conn=None):
if conn: if conn:
settings.setValue(SettingsLastReference, result) settings.setValue(SettingsLastReference, result)
# Flush to disk *before* handing the result back: as soon as the parent
# receives it on the pipe it terminates this subprocess (SIGTERM, no
# handler), so the QSettings destructor never runs. Without sync() the
# write races the kill and is lost — reliably so under Flatpak, where
# the .conf is atomically renamed on the slower ~/.var/app overlay.
settings.sync()
conn.send([result, success]) conn.send([result, success])
conn.close() conn.close()
else: else:

View File

@@ -19,6 +19,7 @@ Public API
import atexit import atexit
import os import os
import shlex
import shutil import shutil
import subprocess import subprocess
import tempfile import tempfile
@@ -177,6 +178,27 @@ def flatpak_host_spawn(interp_bin, cmd_args, host_cwd, extra_env=None):
return spawn return spawn
def host_console_command(shell_cmd, cwd):
"""Build the argv to start *shell_cmd* as an ordinary interactive console.
*shell_cmd* is the command the caller chose (a string — shell-split — or
an argv list); the choice is preserved verbatim.
Outside Flatpak the command is returned unchanged. Inside Flatpak a bare
spawn would run in the sandbox under the runtime python3, so a host venv
(``/path/venv/bin/python3 -m mod``) can't see its pip deps. We simply run
it on the host with ``flatpak-spawn --host`` so it behaves like any other
terminal: flatpak-spawn passes the current environment through unchanged
and the shell (sourced venv, profile, …) sets things up as the user wants.
No env forwarding or scrubbing — the launcher's leaked PYTHONPATH points at
/app paths absent on the host, so it's inert there.
"""
argv = shlex.split(shell_cmd) if isinstance(shell_cmd, str) else list(shell_cmd)
if not _in_flatpak():
return argv
return ["flatpak-spawn", "--host", f"--directory={cwd}", *argv]
def _which_host_flatpak(name): def _which_host_flatpak(name):
"""Resolve a binary name (or absolute path) on the host via flatpak-spawn. """Resolve a binary name (or absolute path) on the host via flatpak-spawn.

View File

@@ -51,14 +51,18 @@ class TestFileManager:
w.disconnect_signals() w.disconnect_signals()
# Snapshot user-selected checkboxes and fold state so they survive a # Snapshot user-selected checkboxes and fold state so they survive a
# reload of the same file (same logic as session-restore through prefs). # reload of the same file (same logic as session-restore through prefs).
# checkList works only if show_checkboxes is True
previous_check_list = w.treeTests.getCheckList() previous_check_list = w.treeTests.getCheckList()
previous_fold_list = w.treeTests.getFoldList() previous_fold_list = w.treeTests.getFoldList()
previous_count = w.treeTests.getItemCount() previous_count = w.treeTests.getItemCount()
self.clear_process() self.clear_process()
if self.load(file_name) and w.test_service is not None: if self.load(file_name) and \
if w.treeTests.getItemCount() == previous_count: w.test_service is not None and \
w.treeTests.restoreCheckList(previous_check_list, w.test_service) w.treeTests.getItemCount() == previous_count:
if prefs.settings.show_checkboxes :
w.treeTests.restoreCheckList(previous_check_list, w.test_service)
w.treeTests.restoreFoldList(previous_fold_list) w.treeTests.restoreFoldList(previous_fold_list)
w.reconnect_signals() w.reconnect_signals()
def _make_progress(self, w): def _make_progress(self, w):

View File

@@ -84,7 +84,18 @@
- read_until: {expected: HelloConsole, timeout: 1, mute: true} - read_until: {expected: HelloConsole, timeout: 1, mute: true}
- console: - console:
name: Console read_until muted name: Console read_until float timeout
console_name: term
key: $(test)_PASS
steps:
- writeln: echo "HelloConsole"
{% if os == "Windows" %}
- read_until: {expected: echo "HelloConsole", timeout: 0.2}
{% endif %}
- read_until: {expected: HelloConsole, timeout: 0.2}
- console:
name: Console read_until process result
console_name: term console_name: term
key: $(test)_PASS key: $(test)_PASS
steps: steps:
@@ -94,6 +105,17 @@
{% endif %} {% endif %}
- read_until: {expected: endOfCmd, timeout: 1, process_result: "'Hello' in r'''$(result)''' and 'PASS' in r'''$(result)''' "} - read_until: {expected: endOfCmd, timeout: 1, process_result: "'Hello' in r'''$(result)''' and 'PASS' in r'''$(result)''' "}
{% if os == "Linux" %}
- console:
name: Console runs on host (not the Flatpak sandbox)
doc: Regression guard for the 0.2.1 Flatpak bug (term console spawned inside the sandbox instead of on the host). /.flatpak-info exists only inside the sandbox, so the host-only marker is emitted (and matched by read_until) ONLY when the shell really runs on the host. On a broken Flatpak the marker never appears, read_until times out and the item FAILS. The marker is built at runtime ($M) so it is never present in the command line itself. Passes on every other channel.
console_name: term
key: $(test)_PASS
steps:
- writeln: 'test -e /.flatpak-info && M=SANDBOX || M=HOST; echo "console_host_check_$M"'
- read_until: {expected: console_host_check_HOST, timeout: 5}
{% endif %}
- console: - console:
name: Console closure name: Console closure
execute_on_stop: true execute_on_stop: true

View File

@@ -11,8 +11,8 @@
- let: - let:
name: Let it be name: Let it be
values: values:
it: $(loop_param) - it: $(loop_param)
be: <| $(loop_param) == $(it) |> - be: <| $(loop_param) == $(it) |>
- loop: - loop:
name: Cycle iterating on list name: Cycle iterating on list

View File

@@ -1,7 +1,7 @@
- let: - let:
name: lua_func test constants, name: lua_func test constants,
values: values:
lua_func test parameter: test parameter lua_func - lua_func test parameter: test parameter lua_func
- lua_func: - lua_func:
name: fail lua_func name: fail lua_func

View File

@@ -1,7 +1,7 @@
- let: - let:
name: py_func test constants, name: py_func test constants,
values: values:
py_func test parameter: test parameter - py_func test parameter: test parameter
- py_func: - py_func:
name: pass py_func name: pass py_func

View File

@@ -1,7 +0,0 @@
main:
name: run sub-test (always fail)
steps:
- check:
name: fail
values:
- false

View File

@@ -1,7 +0,0 @@
main:
name: run sub-test (always pass)
steps:
- check:
name: pass
values:
- true

View File

@@ -31,7 +31,11 @@ main:
{% for item in items %} {% for item in items %}
# item test # item test
- let: {name: {{ item }} test constants, values: {test: {{ item }}, test_path: items/$(test)}} - let:
name: {{ item }} test constants
values:
- test: {{ item }}
- test_path: items/$(test)
- group: - group:
name: {{ item }} test name: {{ item }} test
steps: steps: