aboutsummaryrefslogtreecommitdiff
path: root/tests/net-scenarios/test_run_net_scenarios.py
blob: 1d921853aa36b83a3b501214defda6a8e723cf65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
"""Test run-net-scenarios.sh scenario accounting.

Bug: the scenario_diagnose_expect else-branch printed a FAIL line but never
forced a non-zero subshell exit, so a scenario whose only failure was the
diagnose check counted as passed and the run printed "all scenarios passed"
with exit 0. A net-doctor diagnosis regression would ship behind a green run.

Method: run the real script against a temp scenario dir (NET_SCENARIO_DIR)
with stub ssh/rsync/jq on PATH, driving one fake scenario whose check results
are controlled per test. Assert on the script's exit code and summary line.

Run from repo root:
    python3 -m unittest tests.net-scenarios.test_run_net_scenarios
"""

import os
import shutil
import subprocess
import tempfile
import textwrap
import unittest

REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
SCRIPT = os.path.join(REPO_ROOT, "scripts", "testing", "run-net-scenarios.sh")

STUB = "#!/bin/sh\ncat >/dev/null 2>&1 || true\nexit 0\n"


class RunNetScenarios(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.mkdtemp(prefix="net-scen-test-")
        self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
        self.bindir = os.path.join(self.tmp, "bin")
        os.mkdir(self.bindir)
        for tool in ("ssh", "rsync", "jq"):
            p = os.path.join(self.bindir, tool)
            with open(p, "w") as f:
                f.write(STUB)
            os.chmod(p, 0o755)
        self.scen_dir = os.path.join(self.tmp, "scenarios")
        os.mkdir(self.scen_dir)

    def write_scenario(self, *, brk=0, diagnose=0, fix=0, assert_rc=0,
                       with_diagnose=True):
        diag_fn = (
            f"scenario_diagnose_expect() {{ return {diagnose}; }}\n"
            if with_diagnose else ""
        )
        body = textwrap.dedent(f"""\
            SCENARIO_DESC="fake scenario for harness tests"
            scenario_break() {{ return {brk}; }}
            {diag_fn}scenario_fix() {{ return {fix}; }}
            scenario_assert() {{ return {assert_rc}; }}
        """)
        with open(os.path.join(self.scen_dir, "fake-scenario.sh"), "w") as f:
            f.write(body)

    def run_script(self):
        env = dict(os.environ)
        env["PATH"] = self.bindir + os.pathsep + env["PATH"]
        env["NET_SCENARIO_DIR"] = self.scen_dir
        env["DOTFILES"] = self.tmp  # rsync is stubbed; path just has to exist
        return subprocess.run(
            ["bash", SCRIPT, "--target", "root@fake-vm"],
            capture_output=True, text=True, timeout=20, env=env,
        )

    def test_all_checks_pass_exits_zero(self):
        self.write_scenario()
        r = self.run_script()
        self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
        self.assertIn("all scenarios passed", r.stdout)

    def test_diagnose_failure_alone_fails_the_run(self):
        # The bug: break/fix/assert all pass, only the diagnose check fails.
        self.write_scenario(diagnose=1)
        r = self.run_script()
        self.assertIn("diagnose did NOT name it", r.stdout)
        self.assertNotEqual(r.returncode, 0,
                            "a diagnose regression must not exit green")
        self.assertIn("1 scenario(s) failed", r.stdout)

    def test_assert_failure_fails_the_run(self):
        self.write_scenario(assert_rc=1)
        r = self.run_script()
        self.assertNotEqual(r.returncode, 0)
        self.assertIn("NOT repaired", r.stdout)

    def test_break_failure_fails_the_run(self):
        self.write_scenario(brk=1)
        r = self.run_script()
        self.assertNotEqual(r.returncode, 0)
        self.assertIn("break failed", r.stdout)

    def test_scenario_without_diagnose_hook_still_passes(self):
        self.write_scenario(with_diagnose=False)
        r = self.run_script()
        self.assertEqual(r.returncode, 0, r.stdout + r.stderr)


if __name__ == "__main__":
    unittest.main()