""" pytest test configuration """ import os import pathlib import shutil import tempfile import time import pytest from osxphotos.utils import is_macos if is_macos: import photoscript from applescript import AppleScript from photoscript.utils import ditto from .test_catalina_10_15_7 import UUID_DICT_LOCAL from osxphotos.exiftool import _ExifToolProc # run timewarp tests (configured with --timewarp) TEST_TIMEWARP = False # run import tests (configured with --test-import) TEST_IMPORT = False # run sync tests (configured with --test-sync) TEST_SYNC = False # run add-locations tests (configured with --test-add-locations) TEST_ADD_LOCATIONS = False # run batch-edit tests (configured with --test-batch-edit) TEST_BATCH_EDIT = False # don't clean up crash logs (configured with --no-cleanup) NO_CLEANUP = False def get_os_version(): if not is_macos: return (None, None, None) import platform # returns tuple containing OS version # e.g. 10.13.6 = (10, 13, 6) version = platform.mac_ver()[0].split(".") if len(version) == 2: (ver, major) = version minor = "0" elif len(version) == 3: (ver, major, minor) = version else: raise ( ValueError( f"Could not parse version string: {platform.mac_ver()} {version}" ) ) return (ver, major, minor) OS_VER = get_os_version() if is_macos else [None, None] if OS_VER[0] == "10" and OS_VER[1] == "15": # Catalina TEST_LIBRARY = "tests/Test-10.15.7.photoslibrary" TEST_LIBRARY_IMPORT = TEST_LIBRARY TEST_LIBRARY_SYNC = TEST_LIBRARY from tests.config_timewarp_catalina import TEST_LIBRARY_TIMEWARP TEST_LIBRARY_ADD_LOCATIONS = None elif OS_VER[0] == "13": # Ventura TEST_LIBRARY = "tests/Test-13.0.0.photoslibrary" TEST_LIBRARY_IMPORT = TEST_LIBRARY TEST_LIBRARY_SYNC = TEST_LIBRARY from tests.config_timewarp_ventura import TEST_LIBRARY_TIMEWARP TEST_LIBRARY_ADD_LOCATIONS = "tests/Test-13.0.0.photoslibrary" else: TEST_LIBRARY = None TEST_LIBRARY_TIMEWARP = None TEST_LIBRARY_SYNC = None TEST_LIBRARY_ADD_LOCATIONS = None @pytest.fixture(scope="session", autouse=is_macos) def setup_photos_timewarp(): if not TEST_TIMEWARP: return copy_photos_library(TEST_LIBRARY_TIMEWARP, delay=5) @pytest.fixture(scope="session", autouse=is_macos) def setup_photos_import(): if not TEST_IMPORT: return copy_photos_library(TEST_LIBRARY_IMPORT, delay=10) @pytest.fixture(scope="session", autouse=is_macos) def setup_photos_sync(): if not TEST_SYNC: return copy_photos_library(TEST_LIBRARY_SYNC, delay=10) @pytest.fixture(scope="session", autouse=is_macos) def setup_photos_add_locations(): if not TEST_ADD_LOCATIONS: return copy_photos_library(TEST_LIBRARY_ADD_LOCATIONS, delay=10) @pytest.fixture(autouse=True) def reset_singletons(): """Need to clean up any ExifTool singletons between tests""" _ExifToolProc.instance = None def pytest_addoption(parser): parser.addoption( "--addalbum", action="store_true", default=False, help="run --add-exported-to-album tests", ) parser.addoption( "--timewarp", action="store_true", default=False, help="run --timewarp tests" ) parser.addoption( "--test-import", action="store_true", default=False, help="run `osxphotos import` tests", ) parser.addoption("--test-batch-edit", action="store_true", default=False) parser.addoption( "--test-sync", action="store_true", default=False, help="run `osxphotos sync` tests", ) parser.addoption( "--test-add-locations", action="store_true", default=False, help="run `osxphotos add-locations` tests", ) parser.addoption( "--no-cleanup", action="store_true", default=False, help="don't clean up crash logs after tests", ) def pytest_configure(config): if ( sum( bool(x) for x in [ config.getoption("--addalbum"), config.getoption("--timewarp"), config.getoption("--test-import"), config.getoption("--test-sync"), config.getoption("--test-batch-edit"), 0, ] ) > 1 ): pytest.exit( "--addalbum, --timewarp, --test-import, --test-sync, --test-batch-edit are mutually exclusive" ) config.addinivalue_line( "markers", "addalbum: mark test as requiring --addalbum to run" ) config.addinivalue_line( "markers", "timewarp: mark test as requiring --timewarp to run" ) config.addinivalue_line( "markers", "test_import: mark test as requiring --test-import to run" ) config.addinivalue_line( "markers", "test_sync: mark test as requiring --test-sync to run" ) config.addinivalue_line( "markers", "test_add_locations: mark test as requiring --test-add-locations to run", ) config.addinivalue_line( "markers", "test_batch_edit: mark test as requiring --test-batch-edit to run" ) # this is hacky but I can't figure out how to check config options in other fixtures if config.getoption("--timewarp"): global TEST_TIMEWARP TEST_TIMEWARP = True if config.getoption("--test-import"): global TEST_IMPORT TEST_IMPORT = True if config.getoption("--test-sync"): global TEST_SYNC TEST_SYNC = True if config.getoption("--test-add-locations"): global TEST_ADD_LOCATIONS TEST_ADD_LOCATIONS = True if config.getoption("--no-cleanup"): global NO_CLEANUP NO_CLEANUP = True if config.getoption("--test-batch-edit"): global TEST_BATCH_EDIT TEST_BATCH_EDIT = True def pytest_collection_modifyitems(config, items): if not (config.getoption("--addalbum") and TEST_LIBRARY is not None): skip_addalbum = pytest.mark.skip(reason="need --addalbum option to run") for item in items: if "addalbum" in item.keywords: item.add_marker(skip_addalbum) if not (config.getoption("--timewarp") and TEST_LIBRARY_TIMEWARP is not None): skip_timewarp = pytest.mark.skip(reason="need --timewarp option to run") for item in items: if "timewarp" in item.keywords: item.add_marker(skip_timewarp) if not (config.getoption("--test-import") and TEST_LIBRARY_IMPORT is not None): skip_test_import = pytest.mark.skip(reason="need --test-import option to run") for item in items: if "test_import" in item.keywords: item.add_marker(skip_test_import) if not (config.getoption("--test-sync") and TEST_LIBRARY_SYNC is not None): skip_test_sync = pytest.mark.skip(reason="need --test-sync option to run") for item in items: if "test_sync" in item.keywords: item.add_marker(skip_test_sync) if not (config.getoption("--test-batch-edit")): skip_test_batch_edit = pytest.mark.skip( reason="need --test-batch-edit option to run" ) for item in items: if "test_batch_edit" in item.keywords: item.add_marker(skip_test_batch_edit) if not ( config.getoption("--test-add-locations") and TEST_LIBRARY_ADD_LOCATIONS is not None ): skip_test_sync = pytest.mark.skip( reason="need --test-add-locations option and MacOS Ventura to run" ) for item in items: if "test_add_locations" in item.keywords: item.add_marker(skip_test_sync) def copy_photos_library(photos_library, delay=0, open=True): """copy the test library and open Photos, returns path to copied library""" # quit Photos if it's running photoslib = photoscript.PhotosLibrary() photoslib.quit() src = pathlib.Path(os.getcwd()) / photos_library picture_folder = ( pathlib.Path(os.environ["PHOTOSCRIPT_PICTURES_FOLDER"]) if "PHOTOSCRIPT_PICTURES_FOLDER" in os.environ else pathlib.Path("~/Pictures") ) picture_folder = picture_folder.expanduser() if not picture_folder.is_dir(): pytest.exit(f"Invalid picture folder: '{picture_folder}'") dest = picture_folder / pathlib.Path(photos_library).name # copy src directory to dest directory, removing it if it already exists shutil.rmtree(str(dest), ignore_errors=True) print(f"copying {src} to {picture_folder} ...") copyFolder = AppleScript( """ on copyFolder(sourceFolder, destinationFolder) -- sourceFolder and destinationFolder are strings of POSIX paths set sourceFolder to POSIX file sourceFolder set destinationFolder to POSIX file destinationFolder tell application "Finder" duplicate sourceFolder to destinationFolder end tell end copyFolder """ ) copyFolder.call("copyFolder", str(src), str(picture_folder)) # open Photos if open: # sometimes doesn't open the first time time.sleep(delay) photoslib.open(str(dest)) time.sleep(delay) photoslib.open(str(dest)) return dest @pytest.fixture def addalbum_library(): copy_photos_library(TEST_LIBRARY, delay=10) def copy_photos_library_to_path(photos_library_path: str, dest_path: str) -> str: """Copy a photos library to a folder""" if is_macos: ditto(photos_library_path, dest_path) else: shutil.copytree(photos_library_path, dest_path) return dest_path @pytest.fixture(scope="session", autouse=True) def delete_crash_logs(): """Delete left over crash logs from tests that were supposed to crash""" yield path = pathlib.Path(os.getcwd()) / "osxphotos_crash.log" if path.is_file() and not NO_CLEANUP: path.unlink() @pytest.fixture def photoslib(): return photoscript.PhotosLibrary() @pytest.fixture def suspend_capture(pytestconfig): class suspend_guard: def __init__(self): self.capmanager = pytestconfig.pluginmanager.getplugin("capturemanager") def __enter__(self): self.capmanager.suspend_global_capture(in_=True) def __exit__(self, _1, _2, _3): self.capmanager.resume_global_capture() yield suspend_guard() @pytest.fixture def output_file(): """Create a temporary filename for writing output""" tempdir = tempfile.gettempdir() fd, filename = tempfile.mkstemp(dir=tempdir) os.close(fd) yield filename os.remove(filename)