aboutsummaryrefslogtreecommitdiff
blob: 556b34ccf67250cfcd7c2300543b46ed73878b8b (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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import os
import signal

import pytest
from snakeoil import process
from snakeoil.contexts import chdir
from snakeoil.process import spawn

BASH_BINARY = process.find_binary("bash", fallback="")


@pytest.mark.skipif(not BASH_BINARY, reason="missing bash binary")
class TestSpawn:
    @pytest.fixture(autouse=True)
    def _setup(self, tmp_path):
        orig_path = os.environ["PATH"]
        os.environ["PATH"] = ":".join([str(tmp_path)] + orig_path.split(":"))

        yield

        os.environ["PATH"] = orig_path

    @pytest.fixture
    def dev_null(self):
        """Return an open write file descriptor to `/dev/null`."""
        with open("/dev/null", "w") as null_file:
            yield null_file.fileno()

    def generate_script(self, tmp_path, filename, text):
        assert not os.path.isabs(filename)
        fp = tmp_path / filename
        fp.write_text("#!/usr/bin/env bash\n" + text)
        fp.chmod(0o750)
        assert fp.stat().st_mode & 0o750 == 0o750
        return fp

    def test_get_output(self, tmp_path, dev_null):
        filename = "spawn-getoutput.sh"
        for r, s, text, args in (
            [0, ["dar\n"], "echo dar\n", {}],
            [0, ["dar"], "echo -n dar", {}],
            [1, ["blah\n", "dar\n"], "echo blah\necho dar\nexit 1", {}],
            [0, [], "echo dar 1>&2", {"fd_pipes": {1: 1, 2: dev_null}}],
        ):
            fp = self.generate_script(tmp_path, filename, text)
            assert (r, s) == spawn.spawn_get_output(
                str(fp), spawn_type=spawn.spawn_bash, **args
            )
        os.unlink(fp)

    @pytest.mark.skipif(not spawn.is_sandbox_capable(), reason="missing sandbox binary")
    def test_sandbox(self, tmp_path):
        fp = self.generate_script(tmp_path, "spawn-sandbox.sh", "echo $LD_PRELOAD")
        ret = spawn.spawn_get_output(str(fp), spawn_type=spawn.spawn_sandbox)
        assert ret[1], "no output; exit code was %s; script location %s" % (ret[0], fp)
        assert "libsandbox.so" in [
            os.path.basename(x.strip()) for x in ret[1][0].split()
        ]
        os.unlink(fp)

    @pytest.mark.skipif(not spawn.is_sandbox_capable(), reason="missing sandbox binary")
    def test_sandbox_empty_dir(self, tmp_path):
        """sandbox gets pissy if it's ran from a nonexistent dir

        this verifies our fix works.
        """
        fp = self.generate_script(tmp_path, "spawn-sandbox.sh", "echo $LD_PRELOAD")
        dpath = tmp_path / "dar"
        dpath.mkdir()
        with chdir(dpath):
            dpath.rmdir()
            assert "libsandbox.so" in [
                os.path.basename(x.strip())
                for x in spawn.spawn_get_output(
                    str(fp), spawn_type=spawn.spawn_sandbox, cwd="/"
                )[1][0].split()
            ]
            fp.unlink()

    def test_process_exit_code(self):
        assert spawn.process_exit_code(0) == 0
        assert spawn.process_exit_code(16 << 8) == 16

    def generate_background_pid(self):
        try:
            return spawn.spawn(["sleep", "5s"], returnpid=True)[0]
        except process.CommandNotFound:
            pytest.skip("can't complete the test, sleep binary doesn't exist")

    def test_spawn_returnpid(self):
        pid = self.generate_background_pid()
        try:
            assert os.kill(pid, 0) is None, "returned pid was invalid, or sleep died"
            assert pid in spawn.spawned_pids, "pid wasn't recorded in global pids"
        finally:
            os.kill(pid, signal.SIGKILL)

    def test_cleanup_pids(self):
        pid = self.generate_background_pid()
        spawn.cleanup_pids([pid])
        with pytest.raises(OSError):
            os.kill(pid, 0)
        assert pid not in spawn.spawned_pids, "pid wasn't removed from global pids"

    def test_spawn_bash(self, capfd):
        # bash builtin for true without exec'ing true (eg, no path lookup)
        assert 0 == spawn.spawn_bash("echo bash")
        out, _err = capfd.readouterr()
        assert out.strip() == "bash"

    def test_umask(self, tmp_path):
        fp = self.generate_script(tmp_path, "spawn-umask.sh", f"#!{BASH_BINARY}\numask")
        try:
            old_umask = os.umask(0)
            if old_umask == 0:
                # crap.
                desired = 0o22
                os.umask(desired)
            else:
                desired = 0
            assert str(desired).lstrip("0") == spawn.spawn_get_output(str(fp))[1][
                0
            ].strip().lstrip("0")
        finally:
            os.umask(old_umask)