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
|
#!/usr/bin/env python
import os
import platform
import re
import shlex
import shutil
import subprocess
import sys
import tempfile
import difflib
import stat
REQUIRED_BINARIES = ["git", "gittuf", "ssh-keygen"]
SNIPPET_PATTERN = r"```bash\n([\s\S]*?)\n```"
if platform.system() == "Windows":
EXPECTED_OUTPUT_FILENAME = "tester-expected-win.txt"
SHELL = "powershell.exe"
else:
EXPECTED_OUTPUT_FILENAME = "tester-expected-unix.txt"
SHELL = "/bin/bash"
GET_STARTED_FILENAME = "get-started.md"
# Validate that we have all the binaries required to run the test commands
def check_binaries():
for p in REQUIRED_BINARIES:
if not shutil.which(p):
raise Exception(f"required command {p} not found")
# This is required for deleting the directory on Windows
def remove_readonly(func, path, _):
"""Clear the readonly attribute and retry the operation."""
os.chmod(path, stat.S_IWRITE)
func(path)
# Replacements for some bash commands to work in PowerShell
def powershellify(cmds):
for i in range(len(cmds)):
# chaining commands in PowerShell works with ; instead of &&.
cmds[i] = cmds[i].replace("&&", ";")
# mkdir generates output in PowerShell, redirecting that to null
if "mkdir" in cmds[i]:
cmds[i] = re.sub(r'(mkdir\s+[a-zA-Z0-9-]+)', r'\1 > $null', cmds[i])
# quotes work differently in PowerShell, and the -N "" in the
# documentation causes ssh-keygen to break down in PowerShell.
# This can be solved by encapsulating the "" in single quotes like such:
if "ssh-keygen" in cmds[i]:
cmds[i] = cmds[i].replace('-N ""', "-N '\"\"'")
return cmds
def test_commands():
curr_path = os.getcwd()
docs_path = os.path.join(curr_path, "docs")
testing_path = os.path.join(docs_path, "testing")
get_started_file = os.path.realpath(os.path.join(docs_path, GET_STARTED_FILENAME))
os.chdir(testing_path)
# Check for supported platform
match platform.system():
case "Linux" | "Darwin" | "Windows":
expected_output_file = os.path.realpath(os.path.join(testing_path, EXPECTED_OUTPUT_FILENAME))
case _:
raise SystemExit("Unknown platform.")
# Prepare temporary directory
tmp_dir = os.path.realpath(tempfile.mkdtemp())
os.chdir(tmp_dir)
try:
with open(expected_output_file) as fp1, open(get_started_file) as fp2:
# Read in the get_started.md and expected output files
expected_output = fp1.read()
expected_output = re.sub(r'[\r\n]', '', expected_output)
get_started = fp2.read()
snippets = re.findall(SNIPPET_PATTERN, get_started)
# Prepend the set command to echo commands and exit in case of
# failure
if platform.system() == "Windows":
snippets = powershellify(snippets)
script = "\nSet-PSDebug -Trace 1\n {}".format("\n".join(snippets))
else:
script = "\nset -xe\n{}".format("\n".join(snippets))
script += "\ngittuf verify-ref main" # Workaround for non-deterministic hashes
# Set some environment variables to control commit creation
cmd_env = os.environ.copy()
cmd_env["GIT_AUTHOR_NAME"] = "Jane Doe"
cmd_env["GIT_AUTHOR_EMAIL"] = "jane.doe@example.com"
cmd_env["GIT_AUTHOR_DATE"] = "2024-06-03T14:00:00.000Z"
cmd_env["GIT_COMMITTER_NAME"] = "Jane Doe"
cmd_env["GIT_COMMITTER_EMAIL"] = "jane.doe@example.com"
cmd_env["GIT_COMMITTER_DATE"] = "2024-06-03T14:00:00.000Z"
# Execute generated script
proc = subprocess.Popen(
[SHELL, "-c", script],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
universal_newlines=True, env=cmd_env)
stdout, _ = proc.communicate()
stdout = re.sub(r'[\r\n]', '', stdout)
# Compare and notify user of result
if stdout != expected_output:
difflist = list(difflib.Differ().compare(
expected_output.splitlines(),
stdout.splitlines()))
raise SystemExit("Testing failed due to unexpected output:\n {}".format("\n".join(difflist)))
else:
print("Testing completed successfully.")
finally:
# Cleanup
os.chdir(curr_path)
shutil.rmtree(tmp_dir, onerror=remove_readonly)
if __name__ == "__main__":
check_binaries()
test_commands()
|