1 """Simple assertions which print the expected and received values on failure."""
5 from pathlib import Path
10 Num = T.TypeVar("Num", int, float)
13 def blank(value: T.AnyStr) -> None:
14 """Check that value is an empty or blank string."""
15 if not isinstance(value, str) or value.strip():
16 raise ValueError(f'expected "{value!r}" to be a blank string')
19 def false(value: T.Any) -> None:
20 """Check that value is falsy."""
22 raise ValueError(f'expected "{value}" to be falsy')
25 def success(value: int) -> None:
26 """Check that value represents a successful exit code."""
27 if not isinstance(value, int) or value != 0:
28 raise ValueError(f'expected "{value}" to be 0', value)
31 def failure(value: int) -> None:
32 """Check that value represents an unsuccessful exit code."""
33 if not isinstance(value, int) or value == 0:
34 raise ValueError(f'expected "{value}" to NOT be 0', value)
37 def true(value: T.Any) -> None:
38 """Check that value is truthy."""
40 raise ValueError(f'expected "{value}" to be truthy', value)
43 def port(value: int) -> None:
44 """Check that value resembles a port."""
45 if not isinstance(value, int) or value < 1 or value > 65535:
46 raise ValueError(f'expected "{value}" to be be a port')
49 def equals(expected: Val, actual: Val) -> None:
50 """Check that the two values are equal."""
51 if expected != actual:
52 raise ValueError(f'expected "{expected}", got "{actual}"')
55 def has_prefix(text: T.AnyStr, prefix: T.AnyStr) -> None:
56 """Check that text has prefix."""
57 if not text.startswith(prefix):
58 raise ValueError(f"expected {text!r} to start with {prefix!r}")
61 def greater(value: Num, than: Num) -> None:
62 """Check that value is greater than the other value."""
64 raise ValueError(f"value {value} must be greater than {than}")
67 def in_range(value: Num, gte: Num, lte: Num) -> None:
68 """Check that value lies in the range [min, max]."""
69 if not gte >= value >= lte:
70 raise ValueError(f"value {value} must be between {gte} and {lte}")
73 def lines(text: T.AnyStr, num: int) -> None:
74 """Check that text splits into `num` lines."""
75 rows = text.splitlines()
77 raise ValueError(f"expected {num} lines, got {len(rows)}: {rows}")
80 def is_in(needle: Val, *haystacks: T.Container[Val]) -> None:
81 """Check that at least one haystack includes needle."""
82 for haystack in haystacks:
83 if needle in haystack:
85 raise ValueError(f'expected any of "{haystacks}" to include "{needle}"')
88 def not_in(needle: Val, *haystacks: T.Container[Val]) -> None:
89 """Check that all haystacks do not include needle."""
90 for haystack in haystacks:
91 if needle in haystack:
92 raise ValueError(f'expected all "{haystacks}" NOT to include "{needle}"')
95 def _read_content(path: T.Union[str, os.PathLike], search: T.AnyStr) -> T.AnyStr:
96 """Read text or binary content, depending on the type of search argument."""
97 if isinstance(search, str):
98 mode, enc = "r", "utf-8"
100 mode, enc = "rb", None
101 with open(path, mode=mode, encoding=enc) as f:
105 def in_file(path: T.Union[str, os.PathLike], text: T.AnyStr) -> None:
106 """Check that file contains a string."""
107 is_in(text, _read_content(path, text))
110 def not_in_file(path: T.Union[str, os.PathLike], text: T.AnyStr) -> None:
111 """Check that file does not contain a string."""
112 not_in(text, _read_content(path, text))
115 def nodes(node, want_nodes: int) -> None:
116 """Check that node can reach exactly N nodes (including itself)."""
117 log.debug("want %d reachable nodes from tinc %s", want_nodes, node)
118 stdout, _ = node.cmd("dump", "reachable", "nodes")
119 lines(stdout, want_nodes)
122 def files_eq(path0: str, path1: str) -> None:
123 """Compare file contents, ignoring whitespace at both ends."""
124 log.debug("comparing files %s and %s", path0, path1)
126 def read(path: str) -> str:
127 log.debug("reading file %s", path)
128 with open(path, "r", encoding="utf-8") as f:
129 return f.read().strip()
131 content0 = read(path0)
132 content1 = read(path1)
134 if content0 != content1:
135 raise ValueError(f"expected files {path0} and {path1} to match")
138 def file_exists(path: T.Union[str, Path]) -> None:
139 """Check that file exists."""
140 if not os.path.isfile(path):
141 raise ValueError(f"expected file '{path}' to exist")
144 def dir_exists(path: T.Union[str, Path]) -> None:
145 """Check that directory exists."""
146 if not os.path.isdir(path):
147 raise ValueError(f"expected directory '{path}' to exist")