| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- #!/usr/bin/env python3
- """Exercise real download functions with deterministic HTTP responses; no network required."""
- import os
- import re
- import subprocess
- import tempfile
- import unittest
- from pathlib import Path
- ROOT = Path(__file__).resolve().parents[1]
- def functions(path):
- """Load Bash definitions without executing a launcher or module."""
- return "\n".join(
- re.findall(r"(?ms)^[\w.]+\(\) \{\n.*?^\}", path.read_text(encoding="utf-8"))
- )
- BOOTSTRAP = functions(ROOT / "linuxgsm.sh")
- DOWNLOAD = functions(ROOT / "lgsm/modules/core_dl.sh")
- UPDATE = (
- (ROOT / "lgsm/modules/command_update_linuxgsm.sh")
- .read_text()
- .split("# Check linuxsm.sh", 1)[0]
- )
- MOCKS = r"""
- githubuser="GameServerManagers"
- githubrepo="LinuxGSM"
- githubbranch="${TEST_BRANCH:-feature/example}"
- version="v26.2.0"
- rootdir="$TEST_DIR"
- local_filedir="$TEST_DIR"
- modulesdir="$TEST_DIR"
- lgsmlog="$TEST_DIR/log"
- fn_firstcommand_set() { :; }
- fn_print_dots() { :; }
- fn_script_log_info() { :; }
- fn_script_log_fail() { printf '%s\n' "$*" >> "$TEST_DIR/log"; }
- fn_print_fail_nl() { printf '%s\n' "$*"; }
- fn_print_ok_nl() { :; }
- check.sh() { :; }
- info_distro.sh() { :; }
- info_game.sh() { :; }
- curl() {
- printf '%s\n' "$*" >> "$TEST_DIR/calls"
- local output="" probe="" url="${!#}"
- while [ "$#" -gt 0 ]; do
- case "$1" in
- --write-out) probe=1; shift ;;
- -o) output="$2"; shift ;;
- esac
- shift
- done
- if [ "$probe" ]; then
- printf '%s' "${HTTP_CODE:-200}"
- return "${PROBE_EXIT:-0}"
- fi
- if [[ "$url" == https://raw.githubusercontent.com/* ]] && [ "${PRIMARY_FAIL:-0}" == 1 ]; then
- return 22
- fi
- if [ "$output" ]; then
- printf '#!/bin/bash\n' > "$output"
- fi
- }
- """
- class BranchValidation(unittest.TestCase):
- """Run production Bash with controlled HTTP responses and isolated files."""
- def run_bash(self, code, *, core=False, legacy=False, **env):
- """Return process results, HTTP calls, log output and download existence."""
- with tempfile.TemporaryDirectory() as directory:
- source = BOOTSTRAP + ("\n" + DOWNLOAD if core else "")
- if legacy:
- source += "\nunset -f fn_check_github_branch\n"
- result = subprocess.run(
- ["bash", "-c", source + "\n" + MOCKS + "\n" + code],
- env={**os.environ, "TEST_DIR": directory, **env},
- check=False,
- capture_output=True,
- text=True,
- timeout=10,
- )
- calls = Path(directory, "calls")
- log = Path(directory, "log")
- return (
- result,
- (
- calls.read_text(encoding="utf-8").splitlines()
- if calls.exists()
- else []
- ),
- log.read_text(encoding="utf-8") if log.exists() else "",
- Path(directory, "sample.sh").exists(),
- )
- def fetch(self, core=False):
- """Build a call to the real bootstrap or core download entry point."""
- name = "fn_fetch_file_github" if core else "fn_bootstrap_fetch_file_github"
- return f'{name} lgsm/modules sample.sh "$TEST_DIR" nochmodx norun noforcedl nohash\n'
- def test_missing_reference_stops_before_bootstrap_download(self):
- """Missing reference stops before bootstrap download."""
- result, calls, log, exists = self.run_bash(self.fetch(), HTTP_CODE="404")
- self.assertEqual(result.returncode, 1, result.stderr)
- self.assertIn(
- 'Branch "feature/example" not found on GameServerManagers/LinuxGSM',
- result.stdout,
- )
- self.assertIn('Branch "feature/example"', log)
- self.assertEqual(len(calls), 1)
- self.assertFalse(exists)
- def test_missing_reference_with_loaded_core_exits_nonzero(self):
- """Missing reference with loaded core exits nonzero."""
- code = "core_exit.sh() { echo unexpected-exit-module; exit 0; }\n" + self.fetch(
- True
- )
- result, calls, _, exists = self.run_bash(code, core=True, HTTP_CODE="404")
- self.assertEqual(result.returncode, 1, result.stderr)
- self.assertEqual(len(calls), 1)
- self.assertFalse(exists)
- def test_valid_reference_is_checked_once_across_bootstrap_and_update(self):
- """Valid reference is checked once across bootstrap and update."""
- result, calls, _, exists = self.run_bash(self.fetch() + UPDATE)
- self.assertEqual(result.returncode, 0, result.stderr)
- probes = [call for call in calls if "api.github.com" in call]
- self.assertEqual(len(probes), 1)
- self.assertIn("--connect-timeout 3 --max-time 10", probes[0])
- self.assertIn("--data-urlencode ref=feature/example", probes[0])
- self.assertTrue(exists)
- def test_non404_failures_keep_bitbucket_fallback(self):
- """Non404 failures keep bitbucket fallback."""
- for core in (False, True):
- for status, exit_code in (
- ("403", "0"),
- ("429", "0"),
- ("500", "0"),
- ("503", "0"),
- ("000", "28"),
- ("000", "6"),
- ):
- with self.subTest(core=core, status=status, exit_code=exit_code):
- result, calls, _, exists = self.run_bash(
- self.fetch(core),
- core=core,
- HTTP_CODE=status,
- PROBE_EXIT=exit_code,
- PRIMARY_FAIL="1",
- )
- self.assertEqual(result.returncode, 0, result.stderr)
- self.assertTrue(any("bitbucket.org" in call for call in calls))
- self.assertTrue(exists)
- self.assertNotIn("not found", result.stdout)
- def test_cached_file_needs_no_network(self):
- """Cached file needs no network."""
- for core in (False, True):
- with self.subTest(core=core):
- result, calls, _, _ = self.run_bash(
- 'touch "$TEST_DIR/sample.sh"\n' + self.fetch(core),
- core=core,
- HTTP_CODE="404",
- )
- self.assertEqual(result.returncode, 0, result.stderr)
- self.assertEqual(calls, [])
- def test_update_missing_branch_stops_before_repo_selection(self):
- """Update missing branch stops before repo selection."""
- result, calls, _, _ = self.run_bash(UPDATE, HTTP_CODE="404")
- self.assertEqual(result.returncode, 1, result.stderr)
- self.assertEqual(len(calls), 1)
- self.assertIn("api.github.com", calls[0])
- def test_update_non404_keeps_repo_fallback(self):
- """Update non404 keeps repo fallback."""
- for status in ("429", "500", "000"):
- with self.subTest(status=status):
- result, calls, _, _ = self.run_bash(
- UPDATE,
- HTTP_CODE=status,
- PRIMARY_FAIL="1",
- )
- self.assertEqual(result.returncode, 0, result.stderr)
- self.assertTrue(any("bitbucket.org" in call for call in calls))
- def test_upstream_master_still_downloads_version_tag(self):
- """Upstream master still downloads version tag."""
- result, calls, _, _ = self.run_bash(self.fetch(), TEST_BRANCH="master")
- self.assertEqual(result.returncode, 0, result.stderr)
- self.assertIn("/v26.2.0/lgsm/modules/sample.sh", calls[-1])
- def test_fork_master_is_not_version_pinned(self):
- """Fork master is not version pinned."""
- result, calls, _, _ = self.run_bash(
- 'githubuser="Example"\n' + self.fetch(), TEST_BRANCH="master"
- )
- self.assertEqual(result.returncode, 0, result.stderr)
- self.assertIn("/Example/LinuxGSM/master/lgsm/modules/sample.sh", calls[-1])
- def test_ref_change_gets_new_validation(self):
- """Ref change gets new validation."""
- result, calls, _, _ = self.run_bash(
- "fn_check_github_branch\ngithubbranch=other\nfn_check_github_branch"
- )
- self.assertEqual(result.returncode, 0, result.stderr)
- self.assertEqual(len(calls), 2)
- def test_tags_and_commit_refs_are_passed_unchanged(self):
- """Tags and commit refs are passed unchanged."""
- for ref in ("v26.2.0", "2a494011fb636642b9a5d1332a79e359586c54f8"):
- with self.subTest(ref=ref):
- result, calls, _, _ = self.run_bash(self.fetch(), TEST_BRANCH=ref)
- self.assertEqual(result.returncode, 0, result.stderr)
- self.assertIn("ref=" + ref, calls[0])
- self.assertIn("/" + ref + "/", calls[-1])
- def test_new_modules_support_older_launchers(self):
- """New modules support older launchers."""
- for code in (self.fetch(True), UPDATE):
- with self.subTest(code=code[:30]):
- result, calls, _, _ = self.run_bash(code, core=True, legacy=True)
- self.assertEqual(result.returncode, 0, result.stderr)
- self.assertFalse(any("api.github.com" in call for call in calls))
- def test_forced_cached_download_validates_ref(self):
- """A forced download must validate even when the destination exists."""
- for core in (False, True):
- with self.subTest(core=core):
- code = 'touch "$TEST_DIR/sample.sh"\n' + self.fetch(core).replace(
- "noforcedl", "forcedl"
- )
- result, calls, _, _ = self.run_bash(code, core=core, HTTP_CODE="404")
- self.assertEqual(result.returncode, 1, result.stderr)
- self.assertEqual(len(calls), 1)
- def test_unrelated_download_does_not_validate_linuxgsm_ref(self):
- """Other GitHub repositories should not depend on LinuxGSM validation."""
- code = (
- "fn_fetch_file https://raw.githubusercontent.com/Other/Repo/main/file "
- '"" GitHub "" "$TEST_DIR" sample.sh nochmodx norun noforce nohash'
- )
- result, calls, _, exists = self.run_bash(code, core=True, HTTP_CODE="404")
- self.assertEqual(result.returncode, 0, result.stderr)
- self.assertTrue(exists)
- self.assertEqual(len(calls), 1)
- self.assertNotIn("api.github.com", calls[0])
- if __name__ == "__main__":
- unittest.main(verbosity=2)
|