public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
blob dfbe2c8478ca21d5ca60799693be357e3142b814 5357 bytes (raw)

  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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
 
#! /usr/bin/env python3

# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright Red Hat
# Author: David Gibson <david@gibson.dropbear.id.au>

"""
Test A Simple Socket Transport

tasst/snh.py - Simulated network hosts for testing
"""


import contextlib
import subprocess
import sys

import exeter


STDOUT = 1


class SnhProcess(contextlib.AbstractContextManager):
    """
    A background process running on a SimNetHost
    """

    def __init__(self, snh, *cmd, check=True, context_timeout=1.0, **kwargs):
        self.snh = snh
        self.cmd = cmd
        self.check = check
        self.context_timeout = float(context_timeout)

        self.kwargs = kwargs

    def __enter__(self):
        self.popen = subprocess.Popen(self.cmd, **self.kwargs)
        return self

    def run(self, **kwargs):
        stdout, stderr = self.popen.communicate(**kwargs)
        cp = subprocess.CompletedProcess(self.popen.args,
                                         self.popen.returncode,
                                         stdout, stderr)
        if self.check:
            cp.check_returncode()
        return cp

    def terminate(self):
        self.popen.terminate()

    def kill(self):
        self.popen.kill()

    def __exit__(self, *exc_details):
        try:
            self.popen.wait(timeout=self.context_timeout)
        except subprocess.TimeoutExpired as e:
            self.terminate()
            try:
                self.popen.wait(timeout=self.context_timeout)
            except subprocess.TimeoutExpired:
                self.kill()
            raise e


class SimNetHost(contextlib.AbstractContextManager):
    """
    A (usually virtual or simulated) location where we can execute
    commands and configure networks.

    """

    def __init__(self, name):
        self.name = name   # For debugging

    def hostify(self, *cmd, **kwargs):
        raise NotImplementedError

    def __enter__(self):
        return self

    def __exit__(self, *exc_details):
        pass

    def output(self, *cmd, **kwargs):
        proc = self.fg(*cmd, capture=STDOUT, **kwargs)
        return proc.stdout

    def fg(self, *cmd, timeout=None, **kwargs):
        # We don't use subprocess.run() because it kills without
        # attempting to terminate on timeout
        with self.bg(*cmd, **kwargs) as proc:
            res = proc.run(timeout=timeout)
        return res

    def bg(self, *cmd, capture=None, **kwargs):
        if capture == STDOUT:
            kwargs['stdout'] = subprocess.PIPE
        hostcmd, kwargs = self.hostify(*cmd, **kwargs)
        proc = SnhProcess(self, *hostcmd, **kwargs)
        print(f"SimNetHost {self.name}: Started {cmd} => {proc}",
              file=sys.stderr)
        return proc

    # Internal tests
    def test_true(self):
        with self as snh:
            snh.fg('true')

    def test_false(self):
        with self as snh:
            exeter.assert_raises(subprocess.CalledProcessError,
                                 snh.fg, 'false')

    def test_echo(self):
        msg = 'Hello tasst'
        with self as snh:
            out = snh.output('echo', f'{msg}')
            exeter.assert_eq(out, msg.encode('utf-8') + b'\n')

    def test_timeout(self):
        with self as snh:
            exeter.assert_raises(subprocess.TimeoutExpired, snh.fg,
                                 'sleep', 'infinity', timeout=0.1, check=False)

    def test_bg_true(self):
        with self as snh:
            with snh.bg('true'):
                pass

    def test_bg_false(self):
        with self as snh:
            with snh.bg('false') as proc:
                exeter.assert_raises(subprocess.CalledProcessError, proc.run)

    def test_bg_echo(self):
        msg = 'Hello tasst'
        with self as snh:
            with snh.bg('echo', f'{msg}', capture=STDOUT) as proc:
                res = proc.run()
            exeter.assert_eq(res.stdout, msg.encode('utf-8') + b'\n')

    def test_bg_timeout(self):
        with self as snh:
            with snh.bg('sleep', 'infinity') as proc:
                exeter.assert_raises(subprocess.TimeoutExpired,
                                     proc.run, timeout=0.1)
                proc.terminate()

    def test_bg_context_timeout(self):
        with self as snh:
            def run_timeout():
                with snh.bg('sleep', 'infinity', context_timeout=0.1):
                    pass
            exeter.assert_raises(subprocess.TimeoutExpired, run_timeout)

    SELFTESTS = [test_true, test_false, test_echo, test_timeout,
                 test_bg_true, test_bg_false, test_bg_echo, test_bg_timeout,
                 test_bg_context_timeout]

    @classmethod
    def selftest(cls, setup):
        "Register standard snh tests for instance returned by setup"
        for t in cls.SELFTESTS:
            testid = f'{setup.__qualname__}|{t.__qualname__}'
            exeter.register_pipe(testid, setup, t)


class RealHost(SimNetHost):
    """Represents the host on which the tests are running (as opposed
    to some simulated host created by the tests)

    """

    def __init__(self):
        super().__init__('REAL_HOST')

    def hostify(self, *cmd, capable=False, **kwargs):
        assert not capable, \
            "BUG: Shouldn't run commands with capabilities on host"
        return cmd, kwargs


SimNetHost.selftest(RealHost)

debug log:

solving dfbe2c84 ...
found dfbe2c84 in https://archives.passt.top/passt-dev/20240805123701.1720730-10-david@gibson.dropbear.id.au/

applying [1/1] https://archives.passt.top/passt-dev/20240805123701.1720730-10-david@gibson.dropbear.id.au/
diff --git a/test/tasst/snh.py b/test/tasst/snh.py
new file mode 100644
index 00000000..dfbe2c84

Checking patch test/tasst/snh.py...
Applied patch test/tasst/snh.py cleanly.

index at:
100644 dfbe2c8478ca21d5ca60799693be357e3142b814	test/tasst/snh.py

Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).