Move unit tests to their own sub-dir in tests
This commit is contained in:
parent
62b414af3c
commit
d08d4cd325
35 changed files with 6 additions and 7 deletions
275
tests/unit_tests/test_version_strings.py
Normal file
275
tests/unit_tests/test_version_strings.py
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
"""Tests that version strings can be produced reliably."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from hypothesis import strategies as st
|
||||
from hypothesis.errors import NonInteractiveExampleWarning
|
||||
|
||||
from openflexure_microscope_server import utilities
|
||||
|
||||
# Useful for really finding the repo dir when we mock where it is.
|
||||
THIS_DIR = os.path.dirname(__file__)
|
||||
TRUE_REPO_DIR = os.path.dirname(os.path.dirname(THIS_DIR))
|
||||
|
||||
# For explicit version checking.
|
||||
VER_STRING = "3.0.0-alpha4"
|
||||
|
||||
# This is the regex provided by https://semver.org/
|
||||
SEMVER_REGEX = re.compile(
|
||||
r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)"
|
||||
r"(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
|
||||
r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
|
||||
r"(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
||||
)
|
||||
|
||||
# Use hypothesis to generate random filenames
|
||||
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
|
||||
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:
|
||||
"""Run a git command in a subprocess."""
|
||||
git_command = ["git"] + command.split()
|
||||
# Use check=True so an error is raised if git has a problem. Only read STDOUT
|
||||
result = subprocess.run(git_command, check=True, capture_output=True)
|
||||
return result.stdout.decode().strip()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_dir():
|
||||
"""Return the path of a temporary directory (set as working dir)."""
|
||||
working_dir = os.getcwd()
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
os.chdir(tmpdir)
|
||||
yield tmpdir
|
||||
finally:
|
||||
# Return to original working dir
|
||||
os.chdir(working_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def git_repo(temp_dir):
|
||||
"""Return a git repo path (set as working dir) with couple of commits."""
|
||||
# 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 'message2'")
|
||||
|
||||
# Yielding here as tempdir is yielding and we want to stay in the tempdir
|
||||
# context manager. Silencing ruff saying it should be return.
|
||||
yield temp_dir # noqa: PT022
|
||||
|
||||
|
||||
def test_version_data_git_and_toml(mocker, git_repo):
|
||||
"""Check expected version reported if git repo and pyproject.toml are available."""
|
||||
# Patch the variable for this repo to the repo provided by the fixture.
|
||||
mocker.patch.object(utilities, "REPO_DIR", new=git_repo)
|
||||
git_hash = _git("rev-parse HEAD")
|
||||
shutil.copy(os.path.join(TRUE_REPO_DIR, "pyproject.toml"), git_repo)
|
||||
version_data = utilities.robust_version_strings()
|
||||
# Check versions are as expected. Prepending the v to the semantic version.
|
||||
assert version_data.version == "v" + VER_STRING
|
||||
assert version_data.version_source == git_hash
|
||||
|
||||
|
||||
def test_version_data_toml_only(mocker, temp_dir):
|
||||
"""In the case of a pyproject.toml but not git dir, check version is reported."""
|
||||
# Patch the variable for this repo to the temp dir provided by the fixture.
|
||||
mocker.patch.object(utilities, "REPO_DIR", new=temp_dir)
|
||||
shutil.copy(os.path.join(TRUE_REPO_DIR, "pyproject.toml"), temp_dir)
|
||||
version_data = utilities.robust_version_strings()
|
||||
# Check versions are as expected. Prepending the v to the semantic version.
|
||||
assert version_data.version == "v" + VER_STRING
|
||||
assert version_data.version_source == "TOML"
|
||||
|
||||
|
||||
def test_version_data_git_only(mocker, git_repo, caplog):
|
||||
"""In the odd case of a git repo and no pyproject.toml. Check the hash is reported.
|
||||
|
||||
The version string should just be reported as "Undefined". This is weird, so we
|
||||
expect there to be errors logged.
|
||||
"""
|
||||
mocker.patch.object(utilities, "REPO_DIR", new=git_repo)
|
||||
with caplog.at_level(logging.ERROR):
|
||||
git_hash = _git("rev-parse HEAD")
|
||||
version_data = utilities.robust_version_strings()
|
||||
# Check versions are as expected. Prepending the v to the semantic version.
|
||||
assert version_data.version == "Undefined"
|
||||
assert version_data.version_source == git_hash
|
||||
# There should be 1 error level log
|
||||
assert len(caplog.records) == 1
|
||||
|
||||
|
||||
def test_version_distribution(mocker, temp_dir):
|
||||
"""Simulate a distribution package with no pyproject.toml or git directory.
|
||||
|
||||
Check the returned result is from importlib.
|
||||
"""
|
||||
mocker.patch.object(utilities, "REPO_DIR", new=temp_dir)
|
||||
# As `openflexure_microscope_server.utilities` imported `version` from
|
||||
# `importlib.metadata` to mock the already imported:
|
||||
# `openflexure_microscope_server.utilities.version`, this is `version` from
|
||||
# `importlib.metadata`
|
||||
mocker.patch(
|
||||
"openflexure_microscope_server.utilities.version", return_value="oobar"
|
||||
)
|
||||
version_data = utilities.robust_version_strings()
|
||||
# Expect the version is the output from importlib.metadata.version, prepended by a
|
||||
# "v". So, voobar, as we mocked the version to be oobar.!
|
||||
assert version_data.version == "voobar"
|
||||
assert version_data.version_source == "Dist"
|
||||
|
||||
|
||||
def test_reading_hash_from_git():
|
||||
"""Use create a Git repo and check that hash can be read.
|
||||
|
||||
The performs a series of directory and git operations, commented as they go.
|
||||
"""
|
||||
working_dir = os.getcwd()
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
os.chdir(tmpdir)
|
||||
git_dir = os.path.join(tmpdir, ".git")
|
||||
# Git dir not created yet. Returns Undefined
|
||||
assert utilities._get_hash_from_git_dir(git_dir) == "Undefined"
|
||||
|
||||
# Initialised. Should still be undefined.
|
||||
_git("init")
|
||||
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 'message2'")
|
||||
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)
|
||||
|
||||
|
||||
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():
|
||||
"""Check version string can be read from this project's TOML file.
|
||||
|
||||
As we only expect to ever read our own TOML file there is not a need to do
|
||||
excessive testing on different generated TOML files.
|
||||
"""
|
||||
project_toml_path = os.path.join(utilities.REPO_DIR, "pyproject.toml")
|
||||
ver_string = utilities._get_version_from_toml(project_toml_path)
|
||||
# Check version does follow semantic versioning.
|
||||
assert SEMVER_REGEX.match(ver_string)
|
||||
# Explicit check
|
||||
assert ver_string == VER_STRING
|
||||
|
||||
|
||||
def test_error_reading_version_from_toml():
|
||||
"""Check Undefined is returned if error reading TOML file."""
|
||||
# Just give it the wrong path.
|
||||
project_toml_path = os.path.join(utilities.REPO_DIR, "pyppproject.toml")
|
||||
ver_string = utilities._get_version_from_toml(project_toml_path)
|
||||
# Explicit check
|
||||
assert ver_string == "Undefined"
|
||||
Loading…
Add table
Add a link
Reference in a new issue