ci: bound backend test step summaries (#3410)
Co-authored-by: ayattara <alassane.yattara@savoirfairelinux.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Behavior tests for the bounded GitHub Actions step-summary writer."""
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
SCRIPT = REPO_ROOT / ".github" / "scripts" / "bounded-step-summary.py"
|
||||
|
||||
|
||||
class BoundedStepSummaryTest(unittest.TestCase):
|
||||
def run_writer(self, summary: str, *, max_bytes: int = 983_040) -> bytes:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
source = tmp_path / "generated-summary.md"
|
||||
output = tmp_path / "github-step-summary.md"
|
||||
source.write_text(summary, encoding="utf-8")
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"python3",
|
||||
str(SCRIPT),
|
||||
"--input",
|
||||
str(source),
|
||||
"--output",
|
||||
str(output),
|
||||
"--diagnostics-label",
|
||||
"backend-test-results-1",
|
||||
"--diagnostics-url",
|
||||
"https://github.example.test/kdlbs/kandev/actions/runs/123#artifacts",
|
||||
"--max-bytes",
|
||||
str(max_bytes),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
return output.read_bytes()
|
||||
|
||||
def test_normal_summary_is_unchanged(self) -> None:
|
||||
summary = "## 📝 Test results\n\n🔴 `TestWorkspaceFailure` failed\n"
|
||||
|
||||
self.assertEqual(self.run_writer(summary), summary.encode("utf-8"))
|
||||
|
||||
def test_oversized_summary_is_bounded_explicit_and_utf8_safe(self) -> None:
|
||||
summary = (
|
||||
"## 📝 Test results\n\n"
|
||||
"🔴 `TestWorkspaceFailure` failed\n\n"
|
||||
"boundary-padding-12"
|
||||
+ "diagnostic=失敗🔥\n" * 60_000
|
||||
)
|
||||
source_bytes = summary.encode("utf-8")
|
||||
self.assertGreater(len(source_bytes), 1_048_576)
|
||||
|
||||
first = self.run_writer(summary)
|
||||
second = self.run_writer(summary)
|
||||
rendered = first.decode("utf-8")
|
||||
|
||||
self.assertLessEqual(len(first), 983_040)
|
||||
self.assertEqual(first, second)
|
||||
self.assertIn("TestWorkspaceFailure", rendered)
|
||||
self.assertIn("truncated", rendered.lower())
|
||||
self.assertIn("backend-test-results-1", rendered)
|
||||
self.assertIn(
|
||||
"https://github.example.test/kdlbs/kandev/actions/runs/123#artifacts",
|
||||
rendered,
|
||||
)
|
||||
|
||||
def test_oversized_summary_retains_later_failure_identity(self) -> None:
|
||||
summary = (
|
||||
"<h2>📝 Test results</h2>\n"
|
||||
"<table><tr><td><ul>"
|
||||
"<li>🔴<code>TestEarlyFailure</code></li>"
|
||||
"</ul></td></tr></table>\n"
|
||||
+ "diagnostic=失敗🔥\n" * 200_000
|
||||
+ "<table><tr><td><ul>"
|
||||
"<li>🔴<code>TestLaterFailure</code></li>"
|
||||
"</ul></td></tr></table>\n"
|
||||
)
|
||||
|
||||
rendered = self.run_writer(summary).decode("utf-8")
|
||||
|
||||
self.assertIn("TestEarlyFailure", rendered)
|
||||
self.assertIn("TestLaterFailure", rendered)
|
||||
|
||||
def test_invalid_utf8_input_is_rejected(self) -> None:
|
||||
# Contract coverage for the writer's existing UTF-8 validation.
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
source = tmp_path / "generated-summary.md"
|
||||
output = tmp_path / "github-step-summary.md"
|
||||
source.write_bytes(b"valid prefix\xff")
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"python3",
|
||||
str(SCRIPT),
|
||||
"--input",
|
||||
str(source),
|
||||
"--output",
|
||||
str(output),
|
||||
"--diagnostics-label",
|
||||
"backend-test-results-1",
|
||||
"--diagnostics-url",
|
||||
"https://github.example.test/kdlbs/kandev/actions/runs/123#artifacts",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("UnicodeDecodeError", result.stderr)
|
||||
|
||||
def test_impossibly_small_max_bytes_is_rejected(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
source = tmp_path / "generated-summary.md"
|
||||
output = tmp_path / "github-step-summary.md"
|
||||
source.write_text("oversized summary", encoding="utf-8")
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"python3",
|
||||
str(SCRIPT),
|
||||
"--input",
|
||||
str(source),
|
||||
"--output",
|
||||
str(output),
|
||||
"--diagnostics-label",
|
||||
"backend-test-results-1",
|
||||
"--diagnostics-url",
|
||||
"https://github.example.test/kdlbs/kandev/actions/runs/123#artifacts",
|
||||
"--max-bytes",
|
||||
"1",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn(
|
||||
"max bytes is too small for the truncation notice",
|
||||
result.stderr,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user