File: common.py

package info (click to toggle)
debusine 0.14.4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,344 kB
  • sloc: python: 198,722; sh: 850; javascript: 335; makefile: 117
file content (113 lines) | stat: -rw-r--r-- 3,364 bytes parent folder | download | duplicates (2)
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
# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Common utility methods."""

import logging
import socket
import subprocess
from functools import cached_property
from typing import Any

import yaml

logger = logging.getLogger(__name__)


class Configuration:
    """Configuration setup for the integration tests."""

    DEBUSINE_SERVER_USER = 'debusine-server'

    @classmethod
    def get_base_url(cls) -> str:
        """Return the base URL for the test server."""
        return f"https://{socket.getfqdn()}"


class RunResult:
    """Encapsulate the result of executing a command."""

    def __init__(
        self, stdout: str, stderr: str, returncode: int, cmd: list[str]
    ) -> None:
        """Initialize RunResult."""
        self.stdout = stdout
        self.stderr = stderr
        self.returncode = returncode
        self.cmd = cmd

    @cached_property
    def parsed_stdout(self) -> Any:
        """Return the command stdout parsed as YAML."""
        # Parse it only once and only if trying to use __getitem__.
        # Some cmd run by RunResult might not return valid Yaml
        # (so the yaml should only be parsed if it's used)
        try:
            return yaml.safe_load(self.stdout)
        except yaml.YAMLError as exc:
            raise ValueError(
                f'Error parsing\n'
                f"{self.cmd=}\n"
                f"{self.stdout=}\n"
                f"{self.stderr=}\n"
                f"{self.returncode=}\n"
                f"{exc=}"
            )

    def __getitem__(self, key: str) -> Any:
        """Return item from parsed stdout from the command."""
        if self.parsed_stdout is None:
            raise KeyError(
                f'KeyError: "{key}" cannot be found: stdout is None\n'
                f"{self.cmd=}\n"
                f"{self.stdout=}\n"
                f"{self.stderr=}\n"
                f"{self.returncode=}"
            )
        elif key not in self.parsed_stdout:
            raise KeyError(
                f'KeyError: "{key}" does not exist.\n'
                f"{self.cmd=}\n"
                f"{self.stdout=}\n"
                f"{self.parsed_stdout=}\n"
                f"{self.stderr=}\n"
                f"{self.returncode=}"
            )

        return self.parsed_stdout[key]


def run_as(user: str, cmd: list[str], *, stdin: str | None = None) -> RunResult:
    """Run cmd as user using sudo."""
    cmd = ['sudo', '-u', user] + cmd

    return run(cmd, stdin=stdin)


def run(
    cmd: list[str], *, stdin: str | None = None, timeout: float | None = None
) -> RunResult:
    """Run cmd with stdin, return subprocess.CompletedProcess."""
    logger.debug('Exec: %s (stdin: %s)', cmd, stdin)

    completed_process = subprocess.run(
        cmd,
        text=True,
        capture_output=True,
        input=stdin,
        timeout=timeout,
    )

    return RunResult(
        completed_process.stdout,
        completed_process.stderr,
        completed_process.returncode,
        cmd,
    )