Files
crossdesk/.github/scripts/test_xmake_version_check.py
T

158 lines
7.2 KiB
Python

"""Exercise CI Xmake selection with isolated executable and Actions-output mocks."""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
SCRIPT = Path(__file__).resolve().parents[1] / "actions/ensure-xmake/check-version.sh"
BASH = shutil.which("bash")
@unittest.skipUnless(BASH, "Bash is required to exercise the CI version check")
class XmakeVersionCheckTest(unittest.TestCase):
def check(self, version_output=None, *, minimum="3.1.1", status=0, arguments=()):
with tempfile.TemporaryDirectory(prefix="crossdesk-xmake-check-") as directory:
root = Path(directory)
output_file = root / "github-output"
output_file.write_text("existing-output=preserved\n", encoding="utf-8")
arguments_file = root / "arguments"
if version_output is not None:
executable = root / "xmake"
executable.write_text(
"#!/bin/sh\n"
'printf "%s\\n" "$@" > "$XMAKE_TEST_ARGUMENTS"\n'
'printf "%s" "$XMAKE_TEST_OUTPUT"\n'
'exit "$XMAKE_TEST_STATUS"\n',
encoding="utf-8",
)
executable.chmod(0o755)
environment = {
**os.environ,
# The checker only needs shell builtins. Isolating PATH also
# guarantees that missing-tool tests cannot find a real xmake.
"PATH": str(root),
"GITHUB_OUTPUT": str(output_file),
"XMAKE_TEST_ARGUMENTS": str(arguments_file),
"XMAKE_TEST_OUTPUT": version_output or "",
"XMAKE_TEST_STATUS": str(status),
}
if minimum is None:
environment.pop("XMAKE_MIN_VERSION", None)
else:
environment["XMAKE_MIN_VERSION"] = minimum
result = subprocess.run(
[BASH, str(SCRIPT), *arguments],
env=environment,
capture_output=True,
text=True,
check=False,
)
outputs = dict(
line.split("=", 1)
for line in output_file.read_text(encoding="utf-8").splitlines()
)
invocation = (
arguments_file.read_text(encoding="utf-8").splitlines()
if arguments_file.exists() else None
)
self.assertEqual(outputs["existing-output"], "preserved")
return result, outputs, invocation
def test_keeps_minimum_and_newer_versions(self):
for version in ("3.1.1", "3.1.2", "3.1.10", "3.2.0", "3.10.0", "4.0.0"):
with self.subTest(version=version):
result, outputs, invocation = self.check(f"xmake v{version}\n")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(outputs["needs-upgrade"], "false")
self.assertEqual(outputs["detected-version"], version)
self.assertEqual(invocation, ["--root", "--version"])
self.assertIn("Keeping installed Xmake", result.stdout)
def test_older_versions_require_upgrade(self):
for version in ("3.1.0", "3.0.99", "2.99.99"):
with self.subTest(version=version):
result, outputs, _ = self.check(f"xmake v{version}\n")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(outputs["needs-upgrade"], "true")
self.assertIn("Install Xmake 3.1.1 or newer", result.stdout)
def test_components_are_compared_numerically(self):
for installed, minimum, expected in (
("3.1.10", "3.1.9", "false"),
("3.1.9", "3.1.10", "true"),
("3.2.0", "3.1.99", "false"),
("3.1.99", "3.2.0", "true"),
("3.01.009", "3.1.9", "false"),
("3.1.999999999999999999999", "3.1.9", "false"),
):
with self.subTest(installed=installed, minimum=minimum):
result, outputs, _ = self.check(f"xmake v{installed}", minimum=minimum)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(outputs["needs-upgrade"], expected)
def test_parses_ansi_colors_build_suffix_and_windows_line_endings(self):
result, outputs, _ = self.check(
"Xmake banner\r\n"
"\x1b[1;38;5;10mxmake\x1b[0m v\x1b[1m3.1.10\x1b[0m+HEAD.abcdef, A build utility\r\n"
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(outputs["needs-upgrade"], "false")
self.assertEqual(outputs["detected-version"], "3.1.10")
def test_missing_broken_and_unrecognizable_tools_require_upgrade(self):
cases = (
(None, 0),
("xmake v9.9.9\n", 17),
("", 0),
("xmake version unknown\n", 0),
("xmake v3.1\n", 0),
("xmake v3.1.1invalid\n", 0),
("xmake v3.1.1-rc.1\n", 0),
("otherxmake v9.9.9\n", 0),
)
for output, status in cases:
with self.subTest(output=output, status=status):
result, outputs, _ = self.check(output, status=status)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(outputs["needs-upgrade"], "true")
self.assertEqual(outputs["detected-version"], "")
self.assertIn("Install Xmake 3.1.1 or newer", result.stdout)
def test_verification_fails_when_install_did_not_provide_required_version(self):
for output, status in ((None, 0), ("xmake v3.1.0", 0), ("unknown", 0), ("xmake v3.1.1", 5)):
with self.subTest(output=output, status=status):
result, outputs, _ = self.check(output, status=status, arguments=("--verify",))
self.assertEqual(result.returncode, 1)
self.assertEqual(outputs["needs-upgrade"], "true")
self.assertIn("Xmake verification failed", result.stderr)
self.assertIn("3.1.1 or newer", result.stderr)
def test_verification_accepts_newer_version(self):
result, outputs, _ = self.check("xmake v4.0.0", arguments=("--verify",))
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(outputs["needs-upgrade"], "false")
def test_invalid_minimum_fails_before_running_xmake(self):
for minimum in (None, "", "3.1", "v3.1.1", "3.1.1+HEAD", "3.1.1\n", "3.a.1"):
with self.subTest(minimum=minimum):
result, outputs, invocation = self.check("xmake v3.1.1", minimum=minimum)
self.assertEqual(result.returncode, 2)
self.assertNotIn("needs-upgrade", outputs)
self.assertIsNone(invocation)
self.assertIn("XMAKE_MIN_VERSION", result.stderr)
def test_unknown_arguments_fail_before_running_xmake(self):
result, outputs, invocation = self.check("xmake v3.1.1", arguments=("--install",))
self.assertEqual(result.returncode, 2)
self.assertNotIn("needs-upgrade", outputs)
self.assertIsNone(invocation)
self.assertIn("Usage:", result.stderr)
if __name__ == "__main__":
unittest.main()