* test(search,daemon,storage): assert behaviour instead of frozen literals Several suites encoded exact internal values that legitimate tuning and ranking changes are allowed to move: SCIENTIFIC-era profile weights and rrfK values, corpus-statistic figures, and hardcoded timeout/cancellation numbers. They failed on every platform after those changes landed, so the real behaviour regressions they should catch were hidden behind noise. Replace the frozen literals with contract assertions: weights are finite, non-negative and normalize, state selection still holds, capability-gated sources stay zeroed, and hybrid results keep matching canonical hash/path sets. Deadlines are not widened and the daemon paths they cover are unchanged. Also add independent corpus and adapter oracles, deterministic collector cancellation and late-result gates, and synchronize ServiceManager shutdown observation, so the tests fail for the behaviour they name. The portability allowlist keeps its two reviewed raw test-env reads; only their line numbers moved. Signed-off-by: trvon <git@trevon.dev> * ci(tests): summarise Meson failures without flooding the log A failing test run printed the whole Meson suite output, so the actual failing test names and their assertion messages were buried and the uploaded logs were dominated by passing-test chatter. Add a bounded reporter that extracts the failing test names plus their surrounding diagnostics, keeps an overall cap on emitted lines, and is unit-tested against representative Meson output. Wire it into the Tests workflow so failures stay readable. Signed-off-by: trvon <git@trevon.dev> * build(deb): validate candidate identity, mapping and upgrade ordering The Debian packaging scripts took filenames and versions on trust, so a misnamed candidate or a wrong package ordering could pass validation while the real install path was never exercised. Fail closed on candidate identity, version and architecture, verify the package-to-source mapping, seed the legacy-only layout, wait on the actual services, and assert install/upgrade/remove ordering for both Debian and RPM in an offline container. Cases that cannot be proven on a host are reported as skipped rather than silently passing. Signed-off-by: trvon <git@trevon.dev> --------- Signed-off-by: trvon <git@trevon.dev>
169 lines
6.1 KiB
Python
Executable file
169 lines
6.1 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# Copyright (c) 2026 YAMS Contributors
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import io
|
|
import json
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
SCRIPT = Path(__file__).with_name("report_meson_failures.py")
|
|
SPEC = importlib.util.spec_from_file_location("report_meson_failures", SCRIPT)
|
|
assert SPEC and SPEC.loader
|
|
REPORTER = importlib.util.module_from_spec(SPEC)
|
|
sys.modules[SPEC.name] = REPORTER
|
|
SPEC.loader.exec_module(REPORTER)
|
|
|
|
|
|
class MesonFailureReporterTest(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.build_dir = Path(self.temp.name) / "build"
|
|
self.log_dir = self.build_dir / "meson-logs"
|
|
self.log_dir.mkdir(parents=True)
|
|
|
|
def tearDown(self) -> None:
|
|
self.temp.cleanup()
|
|
|
|
def write_jsonl(self, *records: object) -> None:
|
|
(self.log_dir / "testlog.json").write_text(
|
|
"".join(json.dumps(record) + "\n" for record in records),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
def render(self) -> str:
|
|
output = io.StringIO()
|
|
REPORTER.report(self.build_dir, output)
|
|
return output.getvalue()
|
|
|
|
def test_excludes_nonfail_results_and_reports_multiple_failures(self) -> None:
|
|
self.write_jsonl(
|
|
{"name": "passing", "result": "OK", "returncode": 0, "stdout": "pass"},
|
|
{"name": "skipped", "result": "SKIP", "returncode": 0},
|
|
{"name": "expected", "result": "EXPECTEDFAIL", "returncode": 1},
|
|
{
|
|
"name": "surprising-pass",
|
|
"result": "UNEXPECTEDPASS",
|
|
"returncode": 0,
|
|
"stdout": "expected failure did not happen",
|
|
},
|
|
{
|
|
"name": "hung-test",
|
|
"result": "TIMEOUT",
|
|
"returncode": -15,
|
|
"stderr": "timed out waiting for worker",
|
|
},
|
|
)
|
|
|
|
output = self.render()
|
|
|
|
self.assertNotIn("name=passing", output)
|
|
self.assertNotIn("name=skipped", output)
|
|
self.assertNotIn("name=expected ", output)
|
|
self.assertIn(
|
|
"name=surprising-pass status=UNEXPECTEDPASS returncode=0", output
|
|
)
|
|
self.assertIn("name=hung-test status=TIMEOUT returncode=-15", output)
|
|
self.assertIn("expected failure did not happen", output)
|
|
self.assertIn("timed out waiting for worker", output)
|
|
|
|
def test_missing_json_uses_sanitized_text_fallback(self) -> None:
|
|
(self.log_dir / "testlog.txt").write_text(
|
|
"Meson test log\n"
|
|
">>> TOKEN=do-not-print /tmp/private-test --secret argument\n"
|
|
"environment: PASSWORD=also-do-not-print\n"
|
|
"Inherited environment: TOKEN=inherited-secret\n"
|
|
"failing-case: assertion at useful_file.cpp:42\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
output = self.render()
|
|
|
|
self.assertIn("testlog.json is missing", output)
|
|
self.assertIn("bounded fallback tail", output)
|
|
self.assertIn("failing-case: assertion at useful_file.cpp:42", output)
|
|
self.assertIn("command/environment line omitted", output)
|
|
self.assertNotIn("do-not-print", output)
|
|
self.assertNotIn("also-do-not-print", output)
|
|
self.assertNotIn("private-test", output)
|
|
self.assertNotIn("inherited-secret", output)
|
|
|
|
def test_malformed_json_reports_problem_and_uses_text_fallback(self) -> None:
|
|
(self.log_dir / "testlog.json").write_text("{not-json}\n", encoding="utf-8")
|
|
(self.log_dir / "testlog.txt").write_text(
|
|
"timeout-test TIMEOUT after 30 seconds\n", encoding="utf-8"
|
|
)
|
|
|
|
output = self.render()
|
|
|
|
self.assertIn("malformed record at line 1", output)
|
|
self.assertIn("contains no usable records", output)
|
|
self.assertIn("using text fallback", output)
|
|
self.assertIn("timeout-test TIMEOUT after 30 seconds", output)
|
|
|
|
def test_partial_jsonl_keeps_valid_failure_records(self) -> None:
|
|
(self.log_dir / "testlog.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"name": "valid-failure",
|
|
"result": "ERROR",
|
|
"returncode": 2,
|
|
"stdout": "preserve this diagnostic",
|
|
}
|
|
)
|
|
+ "\n{truncated\n",
|
|
encoding="utf-8",
|
|
)
|
|
(self.log_dir / "testlog.txt").write_text(
|
|
"fallback context for truncated record\n", encoding="utf-8"
|
|
)
|
|
|
|
output = self.render()
|
|
|
|
self.assertIn("name=valid-failure status=ERROR returncode=2", output)
|
|
self.assertIn("preserve this diagnostic", output)
|
|
self.assertIn("retained 1 valid record(s)", output)
|
|
self.assertIn("fallback context for truncated record", output)
|
|
|
|
def test_bounds_failure_output_and_ignores_json_env_and_command(self) -> None:
|
|
self.write_jsonl(
|
|
{
|
|
"name": "large-failure",
|
|
"result": "FAIL",
|
|
"returncode": 1,
|
|
"stdout": "A" * (REPORTER.MAX_TEST_OUTPUT_CHARS + 100),
|
|
"env": {"TOKEN": "json-env-secret"},
|
|
"command": ["private-binary", "--token=json-command-secret"],
|
|
}
|
|
)
|
|
|
|
output = self.render()
|
|
|
|
self.assertIn("stdout for large-failure truncated", output)
|
|
self.assertLess(len(output), REPORTER.MAX_TEST_OUTPUT_CHARS + 1_000)
|
|
self.assertNotIn("json-env-secret", output)
|
|
self.assertNotIn("json-command-secret", output)
|
|
self.assertNotIn("private-binary", output)
|
|
|
|
def test_truncated_environment_line_is_not_emitted(self) -> None:
|
|
(self.log_dir / "testlog.txt").write_text(
|
|
"Inherited environment: TOKEN=" + "secret" * REPORTER.MAX_TEXT_READ_BYTES,
|
|
encoding="utf-8",
|
|
)
|
|
output = self.render()
|
|
self.assertNotIn("secret", output)
|
|
|
|
def test_missing_json_and_text_are_reported_honestly(self) -> None:
|
|
output = self.render()
|
|
|
|
self.assertIn("testlog.json is missing", output)
|
|
self.assertIn("testlog.txt is missing", output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|