diff --git a/src/testium/__init__.py b/src/testium/__init__.py index 30a7c84..daebb43 100755 --- a/src/testium/__init__.py +++ b/src/testium/__init__.py @@ -11,6 +11,16 @@ sys.path.append(os.path.abspath(ourpath.parent)) import interpreter.utils.constants as cst def main(): + # Force UTF-8 on stdout/stderr so the runner's output survives a legacy + # console code page (Windows cp1252 can't encode box-drawing/accented + # chars). Only the stream encoders change; the locale default used for + # config files is untouched. + for _stream in (sys.stdout, sys.stderr): + try: + _stream.reconfigure(encoding="utf-8") + except (AttributeError, ValueError): + pass # no stdout (frozen GUI) or non-reconfigurable stream + # Subcommand dispatch (must run *before* argparse so neither 'schema' nor # 'lsp' has to share the GUI/batch flag surface). The subcommands also # skip the multiprocessing 'spawn' setup which is only meaningful for the diff --git a/src/testium/interpreter/test_report/report_export_html.py b/src/testium/interpreter/test_report/report_export_html.py index 023ec58..bd4f9ce 100644 --- a/src/testium/interpreter/test_report/report_export_html.py +++ b/src/testium/interpreter/test_report/report_export_html.py @@ -14,7 +14,7 @@ class ReportExportHTML(rpe.ReportExport): self.prepareFile() self.create_base() self.process_tests() - with open(self._file_name, 'w') as f: + with open(self._file_name, 'w', encoding="utf-8") as f: f.write(lxml.html.tostring(self.root, pretty_print=True).decode()) def testsIterate(self, row): diff --git a/src/testium/interpreter/test_report/report_export_junit.py b/src/testium/interpreter/test_report/report_export_junit.py index 5f61d91..b9cb007 100644 --- a/src/testium/interpreter/test_report/report_export_junit.py +++ b/src/testium/interpreter/test_report/report_export_junit.py @@ -20,7 +20,7 @@ class ReportExportJUnit(rpe.ReportExport): ts = TestSuite(repname, test_cases=self.test_cases, hostname=tm.gd('host_ip')) - with open(self._file_name, 'w') as f: + with open(self._file_name, 'w', encoding="utf-8") as f: TestSuite.to_file(f, [ts]) def testsIterate(self, row): diff --git a/test/validation/items/jsonrpc/test.tum b/test/validation/items/jsonrpc/test.tum index dfe0d60..0ea39de 100644 --- a/test/validation/items/jsonrpc/test.tum +++ b/test/validation/items/jsonrpc/test.tum @@ -20,7 +20,7 @@ console_name: jrpces key: $(test)_PASS steps: - - writeln: python3 {{include_directory}}/jrpc_echo_server.py -c {{include_directory}}/jrpces.ini + - writeln: '"$(python_bin)" {{include_directory}}/jrpc_echo_server.py -c {{include_directory}}/jrpces.ini' - read_until: {expected: ready, timeout: 5} - console: diff --git a/test/validation/items/run/sub_fail.tum b/test/validation/items/run/sub_fail.tum new file mode 100644 index 0000000..c86b417 --- /dev/null +++ b/test/validation/items/run/sub_fail.tum @@ -0,0 +1,7 @@ +main: + name: run sub-test (always fail) + steps: + - check: + name: fail + values: + - false diff --git a/test/validation/items/run/sub_pass.tum b/test/validation/items/run/sub_pass.tum new file mode 100644 index 0000000..072658f --- /dev/null +++ b/test/validation/items/run/sub_pass.tum @@ -0,0 +1,7 @@ +main: + name: run sub-test (always pass) + steps: + - check: + name: pass + values: + - true diff --git a/test/validation/post_execution.py b/test/validation/post_execution.py index b0ae4df..bb86d4f 100644 --- a/test/validation/post_execution.py +++ b/test/validation/post_execution.py @@ -89,7 +89,7 @@ def exec(): junit_report = report.replace(".sqlite", f"-{test}.xml") print(junit_report) _prepare_file_to_save(junit_report) - with open(junit_report, "w") as f: + with open(junit_report, "w", encoding="utf-8") as f: f.write(TestSuite.to_xml_string([ts])) # cleanup diff --git a/test/validation/run.bat b/test/validation/run.bat index f540504..33a1bf5 100644 --- a/test/validation/run.bat +++ b/test/validation/run.bat @@ -89,6 +89,13 @@ REM Reports are stamped with the mode so successive runs don't clobber each othe SET "TAIL=-b -d "python_bin=%VENV_PYTHON%" -d "validation_report_file=validation-%MODE%" -- "%SCRIPT_DIR%\main.tum"%EXTRA%" +REM The report-exporter plugin (items\report_plugin) is a pip entry-point +REM package. It must live in the *testium* environment, so it is installed into +REM the source/wheel venvs below. A frozen PyInstaller binary cannot see +REM externally-installed plugins, so report_plugin is expected to be skipped +REM there (same as Linux pyinstaller mode). +SET "FAKE_EXPORTER=%SCRIPT_DIR%\fake_exporter" + REM ---------- per-mode launcher ---------------------------------------------- echo -- validation mode: %MODE% @@ -100,8 +107,25 @@ echo ERROR: unknown --mode '%MODE%'. Expected: source ^| wheel ^| pyinstaller. exit /b 1 :MODE_SOURCE -call "%PROJECT_DIR%\run.bat" %TAIL% -exit /b %ERRORLEVEL% +REM Run testium from src\ in a dedicated venv set up here. We do NOT delegate to +REM the project's run.bat: that one launches the GUI and does not forward its +REM arguments, so the suite would never run head-less. +SET "TESTIUM_VENV=%PROJECT_DIR%\test\tmp\testium_venv" +IF NOT EXIST "%TESTIUM_VENV%" ( + echo Creating testium venv at %TESTIUM_VENV% + %PYTHON_EXE% -m venv "%TESTIUM_VENV%" + IF !ERRORLEVEL! NEQ 0 ( + echo ERROR while creating the testium venv. + exit /b 1 + ) + call "%TESTIUM_VENV%\Scripts\pip" install --quiet --upgrade pip + call "%TESTIUM_VENV%\Scripts\pip" install --quiet -r "%PROJECT_DIR%\src\requirements.txt" + REM language-server extra so `testium lsp` works from source (lsp_check.py) + call "%TESTIUM_VENV%\Scripts\pip" install --quiet "pygls>=1.3" +) +call "%TESTIUM_VENV%\Scripts\pip" install --quiet -e "%FAKE_EXPORTER%" +SET CMD="%TESTIUM_VENV%\Scripts\python.exe" "%PROJECT_DIR%\src\testium" +GOTO LAUNCH :MODE_WHEEL SET "WHEEL=%PROJECT_DIR%\dist\testium-%VERSION%-py3-none-any.whl" @@ -115,10 +139,13 @@ IF NOT EXIST "%WHEEL_VENV%" ( echo Creating wheel venv at %WHEEL_VENV% %PYTHON_EXE% -m venv --system-site-packages "%WHEEL_VENV%" call "%WHEEL_VENV%\Scripts\pip" install --quiet --upgrade pip - call "%WHEEL_VENV%\Scripts\pip" install --quiet "%WHEEL%" + REM install with the [lsp] extra so the wheel channel is validated in its + REM language-server-capable form (pulls pygls), matching `pip install testium[lsp]`. + call "%WHEEL_VENV%\Scripts\pip" install --quiet "%WHEEL%[lsp]" ) -"%WHEEL_VENV%\Scripts\python.exe" -m testium %TAIL% -exit /b %ERRORLEVEL% +call "%WHEEL_VENV%\Scripts\pip" install --quiet -e "%FAKE_EXPORTER%" +SET CMD="%WHEEL_VENV%\Scripts\python.exe" -m testium +GOTO LAUNCH :MODE_PYI SET "PYI_BIN=%PROJECT_DIR%\dist\testium-%VERSION%.exe" @@ -127,5 +154,22 @@ IF NOT EXIST "%PYI_BIN%" ( echo ERROR: PyInstaller binary not found in %PROJECT_DIR%\dist -- run build_all.sh first. exit /b 1 ) -"%PYI_BIN%" %TAIL% +SET CMD="%PYI_BIN%" +GOTO LAUNCH + +REM ---------- launch ---------------------------------------------------------- + +:LAUNCH +echo -- launch: %CMD% + +REM LSP check (this exact channel): `schema` must keep its nested actions and +REM `lsp` must answer initialize. Mirrors run.sh; aborts the run on failure. +echo -- LSP check (%MODE%) +"%VENV_PYTHON%" "%SCRIPT_DIR%\lsp_check.py" %CMD% +IF !ERRORLEVEL! NEQ 0 ( + echo ERROR: LSP check failed for mode %MODE%. + exit /b 1 +) + +%CMD% %TAIL% exit /b %ERRORLEVEL%