Write tests for checking git hash

This commit is contained in:
Julian Stirling 2025-07-11 16:59:00 +01:00
parent 83a4b72507
commit 5c1f8f2e89
2 changed files with 131 additions and 6 deletions

View file

@ -97,7 +97,7 @@ class VersionData(BaseModel):
:param version: The version string for the server. Or "Undefined" if there is an :param version: The version string for the server. Or "Undefined" if there is an
error. error.
:param version_source: Either a Git commit hash, "TOML", "Dist" or in the case of :param version_source: Either a Git commit hash, "TOML", "Dist" or in the case of
an error - "Undefined". an error - "Undefined".
""" """
version: str version: str
@ -108,7 +108,7 @@ def robust_version_strings() -> tuple[str, str]:
"""Return a version string and information on its source. """Return a version string and information on its source.
Returning a python version string is not without problems. If the package is Returning a python version string is not without problems. If the package is
installed in an editable way, often METDATA is never updated. This provides 4 installed in an editable way, often METADATA is never updated. This provides 4
ways to provide a version string: ways to provide a version string:
1. If both a Git directory and a pyproject.toml are available. Return the version 1. If both a Git directory and a pyproject.toml are available. Return the version

View file

@ -4,8 +4,13 @@ import subprocess
import os import os
import re import re
import tempfile import tempfile
import shutil
import warnings
from hypothesis import strategies as st from hypothesis import strategies as st
from hypothesis.errors import NonInteractiveExampleWarning
import pytest
from openflexure_microscope_server import utilities from openflexure_microscope_server import utilities
@ -21,15 +26,27 @@ SEMVER_REGEX = re.compile(
# Use hypothesis to generate random filenames # Use hypothesis to generate random filenames
FILE_NAME_GEN = st.from_regex(r"[a-zA-Z_-]{5,20}\.[a-z]{1,5}", fullmatch=True) FILE_NAME_GEN = st.from_regex(r"[a-zA-Z_-]{5,20}\.[a-z]{1,5}", fullmatch=True)
# Use hypothesis to generate random data for files # Use hypothesis to generate random data for files
FILE_Data_GEN = st.from_regex(r"[a-zA-Z_-]{5,20}\.[a-z]{1,5}", fullmatch=True) FILE_DATA_GEN = st.from_regex(r"[a-zA-Z_-]{5,20}\.[a-z]{1,5}", fullmatch=True)
def create_fake_file() -> None:
"""Create a file with a random name and random content in the working directory."""
with warnings.catch_warnings():
# Ignore hypothesis warning as we are not using for hypothesis testing but just
# as a convenient way to create random files for Git.
warnings.filterwarnings("ignore", category=NonInteractiveExampleWarning)
fname = FILE_NAME_GEN.example()
with open(fname, "w", encoding="utf-8") as file_obj:
file_obj.write(FILE_DATA_GEN.example())
def _git(command: str) -> str: def _git(command: str) -> str:
"""Run a git command in a subprocess.""" """Run a git command in a subprocess."""
git_command = ["git"] + command.split() git_command = ["git"] + command.split()
# Use check=True so an error is raised if git has a problem. Only read STDOUT # Use check=True so an error is raised if git has a problem. Only read STDOUT
result = subprocess.run(git_command, shell=True, check=True, capture_output=True) result = subprocess.run(git_command, check=True, capture_output=True)
return result.stdout.decode() return result.stdout.decode().strip()
def test_reading_hash_from_git(): def test_reading_hash_from_git():
@ -45,15 +62,123 @@ def test_reading_hash_from_git():
# Git dir not created yet. Returns Undefined # Git dir not created yet. Returns Undefined
assert utilities._get_hash_from_git_dir(git_dir) == "Undefined" assert utilities._get_hash_from_git_dir(git_dir) == "Undefined"
# Intialised. Should still be undefined. # Initialised. Should still be undefined.
_git("init") _git("init")
assert utilities._get_hash_from_git_dir(git_dir) == "Undefined" assert utilities._get_hash_from_git_dir(git_dir) == "Undefined"
# Create some fake files and commit them.
create_fake_file()
create_fake_file()
_git("add -A")
_git("commit -m 'message'")
git_hash1 = _git("rev-parse HEAD")
# Check we read the hash the same as the git executable
assert utilities._get_hash_from_git_dir(git_dir) == git_hash1
# Create more fake files and check again.
create_fake_file()
create_fake_file()
_git("add -A")
_git("commit -m 'message'")
git_hash2 = _git("rev-parse HEAD")
assert utilities._get_hash_from_git_dir(git_dir) == git_hash2
# Save the branch name (robust to the default branch name changing)
branch_name = _git("rev-parse --abbrev-ref HEAD")
# Check out the old commit in detached HEAD and check it is correct
_git(f"checkout {git_hash1}")
assert utilities._get_hash_from_git_dir(git_dir) == git_hash1
# Check out the branch and check commit changed back
_git(f"checkout {branch_name}")
assert utilities._get_hash_from_git_dir(git_dir) == git_hash2
finally:
# Return to original working dir
os.chdir(working_dir)
@pytest.fixture
def git_repo():
"""Return a git repo path (set as working dir) with couple of commits."""
working_dir = os.getcwd()
try:
with tempfile.TemporaryDirectory() as tmpdir:
os.chdir(tmpdir)
# Initialise, create files and commit a couple of times
_git("init")
create_fake_file()
create_fake_file()
_git("add -A")
_git("commit -m 'message'")
create_fake_file()
create_fake_file()
_git("add -A")
_git("commit -m 'message'")
yield tmpdir
finally: finally:
# Return to original working dir # Return to original working dir
os.chdir(working_dir) os.chdir(working_dir)
def test_reading_hash_from_broken_git_dir(git_repo):
"""Break a git dir and check the hash is "Undefined"."""
git_dir = os.path.join(git_repo, ".git")
# Break the git directory and check the hash is undefined
shutil.move(
os.path.join(git_dir, "refs"),
os.path.join(git_dir, "refs1"),
copy_function=shutil.copytree,
)
assert utilities._get_hash_from_git_dir(git_dir) == "Undefined"
# Fix it and check it works again
shutil.move(
os.path.join(git_dir, "refs1"),
os.path.join(git_dir, "refs"),
copy_function=shutil.copytree,
)
commit_hash = utilities._get_hash_from_git_dir(git_dir)
assert utilities.COMMIT_REGEX.match(commit_hash) is not None
# Break the git directory differently and check the hash is undefined
os.remove(os.path.join(git_dir, "HEAD"))
assert utilities._get_hash_from_git_dir(git_dir) == "Undefined"
def test_reading_hash_from_broken_git_ref_file(git_repo):
"""Break the git ref file and check the hash is "Undefined"."""
git_dir = os.path.join(git_repo, ".git")
branch_name = _git("rev-parse --abbrev-ref HEAD")
ref_path = os.path.join(git_dir, "refs", "heads", branch_name)
# Check commit can be read
commit_hash = utilities._get_hash_from_git_dir(git_dir)
assert utilities.COMMIT_REGEX.match(commit_hash) is not None
# Write something into the ref file
with open(ref_path, "w", encoding="utf-8") as head_file:
head_file.write("This is now broken")
# It is now undefined
assert utilities._get_hash_from_git_dir(git_dir) == "Undefined"
def test_reading_hash_from_broken_git_head_file(git_repo):
"""Break the git HEAD file and check the hash is "Undefined"."""
git_dir = os.path.join(git_repo, ".git")
head_path = os.path.join(git_dir, "HEAD")
# Check commit can be read
commit_hash = utilities._get_hash_from_git_dir(git_dir)
assert utilities.COMMIT_REGEX.match(commit_hash) is not None
# Write something into the ref file
with open(head_path, "w", encoding="utf-8") as ref_file:
ref_file.write("This is now broken")
# It is now undefined
assert utilities._get_hash_from_git_dir(git_dir) == "Undefined"
def test_reading_version_from_toml(): def test_reading_version_from_toml():
"""Check version string can be read from this project's TOML file. """Check version string can be read from this project's TOML file.