Add tests for some device & address variables
[tinc] / test / integration / testlib / check.py
index 1d1dfb0..7f70b99 100755 (executable)
@@ -1,6 +1,8 @@
 """Simple assertions which print the expected and received values on failure."""
 
+import os.path
 import typing as T
+from pathlib import Path
 
 from .log import log
 
@@ -8,12 +10,30 @@ Val = T.TypeVar("Val")
 Num = T.TypeVar("Num", int, float)
 
 
+def blank(value: T.AnyStr) -> None:
+    """Check that value is an empty or blank string."""
+    if not isinstance(value, str) or value.strip():
+        raise ValueError(f'expected "{value!r}" to be a blank string')
+
+
 def false(value: T.Any) -> None:
     """Check that value is falsy."""
     if value:
         raise ValueError(f'expected "{value}" to be falsy')
 
 
+def success(value: int) -> None:
+    """Check that value represents a successful exit code."""
+    if not isinstance(value, int) or value != 0:
+        raise ValueError(f'expected "{value}" to be 0', value)
+
+
+def failure(value: int) -> None:
+    """Check that value represents an unsuccessful exit code."""
+    if not isinstance(value, int) or value == 0:
+        raise ValueError(f'expected "{value}" to NOT be 0', value)
+
+
 def true(value: T.Any) -> None:
     """Check that value is truthy."""
     if not value:
@@ -50,6 +70,13 @@ def in_range(value: Num, gte: Num, lte: Num) -> None:
         raise ValueError(f"value {value} must be between {gte} and {lte}")
 
 
+def lines(text: T.AnyStr, num: int) -> None:
+    """Check that text splits into `num` lines."""
+    rows = text.splitlines()
+    if len(rows) != num:
+        raise ValueError(f"expected {num} lines, got {len(rows)}: {rows}")
+
+
 def is_in(needle: Val, *haystacks: T.Container[Val]) -> None:
     """Check that at least one haystack includes needle."""
     for haystack in haystacks:
@@ -65,11 +92,31 @@ def not_in(needle: Val, *haystacks: T.Container[Val]) -> None:
             raise ValueError(f'expected all "{haystacks}" NOT to include "{needle}"')
 
 
+def _read_content(path: T.Union[str, os.PathLike], search: T.AnyStr) -> T.AnyStr:
+    """Read text or binary content, depending on the type of search argument."""
+    if isinstance(search, str):
+        mode, enc = "r", "utf-8"
+    else:
+        mode, enc = "rb", None
+    with open(path, mode=mode, encoding=enc) as f:
+        return f.read()
+
+
+def in_file(path: T.Union[str, os.PathLike], text: T.AnyStr) -> None:
+    """Check that file contains a string."""
+    is_in(text, _read_content(path, text))
+
+
+def not_in_file(path: T.Union[str, os.PathLike], text: T.AnyStr) -> None:
+    """Check that file does not contain a string."""
+    not_in(text, _read_content(path, text))
+
+
 def nodes(node, want_nodes: int) -> None:
     """Check that node can reach exactly N nodes (including itself)."""
     log.debug("want %d reachable nodes from tinc %s", want_nodes, node)
     stdout, _ = node.cmd("dump", "reachable", "nodes")
-    equals(want_nodes, len(stdout.splitlines()))
+    lines(stdout, want_nodes)
 
 
 def files_eq(path0: str, path1: str) -> None:
@@ -86,3 +133,15 @@ def files_eq(path0: str, path1: str) -> None:
 
     if content0 != content1:
         raise ValueError(f"expected files {path0} and {path1} to match")
+
+
+def file_exists(path: T.Union[str, Path]) -> None:
+    """Check that file exists."""
+    if not os.path.isfile(path):
+        raise ValueError(f"expected file '{path}' to exist")
+
+
+def dir_exists(path: T.Union[str, Path]) -> None:
+    """Check that directory exists."""
+    if not os.path.isdir(path):
+        raise ValueError(f"expected directory '{path}' to exist")