diff --git a/pyproject.toml b/pyproject.toml index 5e9a985f..4822ec60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ dev = [ "mypy-gitlab-code-quality", # "pytest-gitlab-code-quality", # pytest version constraint clashes with labthings "pytest", + "pytest-mock==3.14", "matplotlib~=3.10", "hypothesis", ] diff --git a/src/openflexure_microscope_server/scan_directories.py b/src/openflexure_microscope_server/scan_directories.py index d36f11a4..a5001fe5 100644 --- a/src/openflexure_microscope_server/scan_directories.py +++ b/src/openflexure_microscope_server/scan_directories.py @@ -181,7 +181,7 @@ class ScanDirectoryManager: Args: path = path to a location on the disk you want to check min_space [int] = the minimum space required in bytes - default = 500,000,000 (500MiB) + default = 500,000,000 (500MB) Raises: NotEnoughFreeSpaceError if the remaining storage is below min_space diff --git a/tests/test_scan_directories.py b/tests/test_scan_directories.py index 45a22165..1816035e 100644 --- a/tests/test_scan_directories.py +++ b/tests/test_scan_directories.py @@ -4,12 +4,16 @@ import shutil import logging import random import time +from collections import namedtuple + +import pytest from openflexure_microscope_server.scan_directories import ( ScanDirectoryManager, ScanDirectory, ScanInfo, get_files_in_zip, + NotEnoughFreeSpaceError, ) # A global logger to pass in as an Invocation Logger @@ -155,6 +159,27 @@ def test_scan_name_non_sequential(): assert "fake_scan_0012" in all_scans +def test_all_scan_names_taken(): + """ + If the next sequential scan name needs more than 4 digits check error is thrown. + """ + _clear_scan_dir() + os.makedirs(os.path.join(BASE_SCAN_DIR, "fake_scan_9999")) + scan_dir_manager = ScanDirectoryManager(BASE_SCAN_DIR) + with pytest.raises(FileExistsError): + scan_dir_manager.new_scan_dir("fake_scan") + + +def test_no_scan_names_given(): + """ + Check correct default scan name is used if empty string is given + """ + _clear_scan_dir() + scan_dir_manager = ScanDirectoryManager(BASE_SCAN_DIR) + scan_dir = scan_dir_manager.new_scan_dir("") + assert scan_dir.name == "scan_0001" + + def test_scan_info(): """Test the scan info is correct even using fake scan data""" _clear_scan_dir() @@ -203,26 +228,89 @@ def test_empty_scan_info(): def test_zipping_scan_data(): """Test zipping the scan images with fake image data""" + + # Run twice, once calling the ScanDirectory directly, + # Once calling the ScanDirectoryManager + for caller in ["scan_dir", "manager"]: + _clear_scan_dir() + scan_dir_manager = ScanDirectoryManager(BASE_SCAN_DIR) + scan_dir = scan_dir_manager.new_scan_dir("fake_scan") + + # Create 21 fake scan files, a fake stitch, and a fake zip + for i in range(21): + _add_fake_image(scan_dir) + _add_fake_file(scan_dir, "fake_scan_0001_stitched.jpg", in_im_dir=True) + _add_fake_file(scan_dir, "zipfile.zip") + + # zip the directory without setting as the final version. It should only + # zip the 21 scan images + if caller == "scan_dir": + zip_fname = scan_dir.zip_files() + else: + zip_fname = scan_dir_manager.zip_scan("fake_scan_0001") + zip_files = get_files_in_zip(zip_fname) + assert len(zip_files) == 21 + + # Zip again with final version on and there should be 22 images + if caller == "scan_dir": + scan_dir.zip_files(final_version=True) + else: + scan_dir_manager.zip_scan("fake_scan_0001", final_version=True) + zip_files = get_files_in_zip(zip_fname) + assert len(get_files_in_zip(zip_fname)) == 22 + # Check the zips are not in the zip + for file in zip_files: + assert not file.endswith(".zip") + + +def test_creating_scan_dir_for_missing_scan(): + """Check creating ScanDirectory object for a dir that doesn't exist fails.""" _clear_scan_dir() + with pytest.raises(FileNotFoundError): + ScanDirectory(BASE_SCAN_DIR, "not_real_0001") + + +def test_none_returned_for_missing_images_dir(): + """None should be returned for image dir path if images dir never created.""" + _clear_scan_dir() + os.makedirs(os.path.join(BASE_SCAN_DIR, "fake_scan_0001")) + scan_dir = ScanDirectory(BASE_SCAN_DIR, "fake_scan_0001") + assert scan_dir.images_dir is None + # Also check that get scan files returns and empty list + assert scan_dir.get_scan_files() == [] + + +DiskUsage = namedtuple("DiskUsage", ["total", "used", "free"]) + + +@pytest.mark.parametrize("free_space", [500_000_001, 700_000_000, 5_000_000_000]) +def test_disk_not_full(mocker, free_space): + """Check no error thrown if disk has over 500MB of space""" + total_space = 16_000_000_000 + # Mock the disk_usage + mocker.patch( + "shutil.disk_usage", + return_value=DiskUsage( + total=total_space, used=total_space - free_space, free=free_space + ), + ) + scan_dir_manager = ScanDirectoryManager(BASE_SCAN_DIR) - scan_dir = scan_dir_manager.new_scan_dir("fake_scan") + scan_dir_manager.check_free_disk_space() - # Create 21 fake scan files, a fake stitch, and a fake zip - for i in range(21): - _add_fake_image(scan_dir) - _add_fake_file(scan_dir, "fake_scan_0001_stitched.jpg", in_im_dir=True) - _add_fake_file(scan_dir, "zipfile.zip") - # zip the directory without setting as the final version. It should only - # zip the 21 scan images - zip_fname = scan_dir.zip_files() - zip_files = get_files_in_zip(zip_fname) - assert len(zip_files) == 21 +@pytest.mark.parametrize("free_space", [100_000_000, 499_999_999]) +def test_disk_full(mocker, free_space): + """Check error thrown if disk has under 500MB of space""" + total_space = 16_000_000_000 + # Mock the disk_usage + mocker.patch( + "shutil.disk_usage", + return_value=DiskUsage( + total=total_space, used=total_space - free_space, free=free_space + ), + ) - # Zip again with final version on and there should be 22 images - scan_dir.zip_files(final_version=True) - zip_files = get_files_in_zip(zip_fname) - assert len(get_files_in_zip(zip_fname)) == 22 - # Check the zips are not in the zip - for file in zip_files: - assert not file.endswith(".zip") + scan_dir_manager = ScanDirectoryManager(BASE_SCAN_DIR) + with pytest.raises(NotEnoughFreeSpaceError): + scan_dir_manager.check_free_disk_space()