Add dozo tests

This commit is contained in:
Cédric Leporcq 2021-08-08 15:33:47 +02:00
parent b9870524db
commit d7ca7f8321
54 changed files with 364 additions and 7 deletions

View File

@ -20,6 +20,12 @@ EXIFTOOL_STAYOPEN_EOF_LEN = len(EXIFTOOL_STAYOPEN_EOF)
EXIFTOOL_PROCESSES = [] EXIFTOOL_PROCESSES = []
def exiftool_is_running():
ps = subprocess.run(["ps"], capture_output=True)
stdout = ps.stdout.decode("utf-8")
return "exiftool" in stdout
@atexit.register @atexit.register
def terminate_exiftool(): def terminate_exiftool():
"""Terminate any running ExifTool subprocesses; call this to cleanup when done using ExifTool """ """Terminate any running ExifTool subprocesses; call this to cleanup when done using ExifTool """

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
samples/images/IMG_1693.tif Normal file

Binary file not shown.

BIN
samples/images/IMG_1994.JPG Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

BIN
samples/images/IMG_1994.cr2 Executable file

Binary file not shown.

BIN
samples/images/IMG_1997.JPG Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
samples/images/IMG_1997.cr2 Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
samples/images/Pumkins1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

BIN
samples/images/Pumkins2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
samples/images/Tulips.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

BIN
samples/images/wedding.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 KiB

0
tests/__init__.py Normal file
View File

13
tests/conftest.py Normal file
View File

@ -0,0 +1,13 @@
""" pytest test configuration """
import pytest
from dozo.exiftool import _ExifToolProc
@pytest.fixture(autouse=True)
def reset_singletons():
""" Need to clean up any ExifTool singletons between tests """
_ExifToolProc.instance = None

75
tests/test_config.py Normal file
View File

@ -0,0 +1,75 @@
from configparser import RawConfigParser
from pathlib import Path
import pytest
import shutil
import tempfile
from unittest import mock
from dozo import config
# Helpers
import random
import string
def random_char(y):
return ''.join(random.choice(string.printable) for x in range(y))
def write_random_file(file_path):
with open(file_path, 'w') as conf_file:
conf_file.write(random_char(20))
class TestConfig:
@pytest.fixture(scope="module")
def conf_path(self):
tmp_path = tempfile.mkdtemp(prefix='dozo-')
yield Path(tmp_path, "dozo.conf")
shutil.rmtree(tmp_path)
@pytest.fixture(scope="module")
def conf(self, conf_path):
return config.load_config(conf_path)
def test_write(self, conf_path):
conf = RawConfigParser()
conf['Path'] = {
'day_begins': '4',
'dirs_path':'%u{%Y-%m}/{city}|{city}-{%Y}/{folders[:1]}/{folder}',
'name':'{%Y-%m-%b-%H-%M-%S}-{basename}.%l{ext}'
}
conf['Geolocation'] = {
'geocoder': 'Nominatium'
}
config.write(conf_path, conf)
assert conf_path.is_file()
def test_load_config(self, conf):
"""
Read files from config and return variables
"""
# test valid config file
assert conf['Path']['dirs_path'] == '%u{%Y-%m}/{city}|{city}-{%Y}/{folders[:1]}/{folder}'
assert conf['Path']['name'] == '{%Y-%m-%b-%H-%M-%S}-{basename}.%l{ext}'
assert conf['Path']['day_begins'] == '4'
assert conf['Geolocation']['geocoder'] == 'Nominatium'
def test_load_config_no_exist(self):
# test file not exist
conf = config.load_config('filename')
assert conf == {}
def test_load_config_invalid(self, conf_path):
# test invalid config
write_random_file(conf_path)
with pytest.raises(Exception) as e:
config.load_config(conf_path)
assert e.typename == 'MissingSectionHeaderError'
def test_get_path_definition(self, conf):
"""
Get path definition from config
"""
path = config.get_path_definition(conf)
assert path == '%u{%Y-%m}/{city}|{city}-{%Y}/{folders[:1]}/{folder}/{%Y-%m-%b-%H-%M-%S}-{basename}.%l{ext}'

19
tests/test_dozo.py Normal file
View File

@ -0,0 +1,19 @@
import pytest
CONTENT = "content"
class TestDozo:
@pytest.mark.skip()
def test__sort(self):
assert 0
def test_needsfiles(tmpdir):
assert tmpdir
def test_create_file(tmp_path):
d = tmp_path / "sub"
d.mkdir()
p = d / "hello.txt"
p.write_text(CONTENT)
assert p.read_text() == CONTENT
assert len(list(tmp_path.iterdir())) == 1

View File

@ -1,6 +1,5 @@
import json import json
import pytest import pytest
import subprocess
import dozo.exiftool import dozo.exiftool
from dozo.exiftool import get_exiftool_path from dozo.exiftool import get_exiftool_path
@ -178,15 +177,11 @@ def test_exiftool_terminate():
""" Test that exiftool process is terminated when exiftool.terminate() is called """ """ Test that exiftool process is terminated when exiftool.terminate() is called """
exif1 = dozo.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD) exif1 = dozo.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
ps = subprocess.run(["ps"], capture_output=True) assert dozo.exiftool.exiftool_is_running()
stdout = ps.stdout.decode("utf-8")
assert "exiftool" in stdout
dozo.exiftool.terminate_exiftool() dozo.exiftool.terminate_exiftool()
ps = subprocess.run(["ps"], capture_output=True) assert not dozo.exiftool.exiftool_is_running()
stdout = ps.stdout.decode("utf-8")
assert "exiftool" not in stdout
# verify we can create a new instance after termination # verify we can create a new instance after termination
exif2 = dozo.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD) exif2 = dozo.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)

205
tests/test_filesystem.py Normal file
View File

@ -0,0 +1,205 @@
# TODO to be removed later
from datetime import datetime
import os
import pytest
from pathlib import Path
import re
import shutil
from sys import platform
import tempfile
from time import sleep
from dozo import constants
from dozo.database import Db
from dozo.filesystem import FileSystem
from dozo.media.media import Media
from dozo.exiftool import ExifToolCaching, exiftool_is_running, terminate_exiftool
DOZO_PATH = Path(__file__).parent.parent
@pytest.mark.skip()
class TestDb:
pass
class TestFilesystem:
def setup_class(cls):
cls.SRCPATH = tempfile.mkdtemp(prefix='dozo-src')
filenames = ['photo.png', 'plain.jpg', 'text.txt', 'withoutextension',
'no-exif.jpg']
cls.FILE_PATHS = set()
for filename in filenames:
source_path = Path(cls.SRCPATH, filename)
file_path = Path(DOZO_PATH, 'samples', filename)
shutil.copyfile(file_path, source_path)
cls.FILE_PATHS.add(source_path)
cls.path_format = constants.default_path + '/' + constants.default_name
def teardown_class(self):
terminate_exiftool()
assert not exiftool_is_running()
def test_get_part(self):
"""
Test all parts
"""
# Item to search for:
filesystem = FileSystem()
items = filesystem.get_items()
masks = [
'{album}',
'{basename}',
'{camera_make}',
'{camera_model}',
'{city}',
'{"custom"}',
'{country}',
'{ext}',
'{folder}',
'{folders[1:3]}',
'{location}',
'{name}',
'{original_name}',
'{state}',
'{title}',
'{%Y-%m-%d}',
'{%Y-%m-%d_%H-%M-%S}',
'{%Y-%m-%b}'
]
media = Media()
exif_tags = {
'album': media.album_keys,
'camera_make': media.camera_make_keys,
'camera_model': media.camera_model_keys,
# 'date_original': media.date_original,
# 'date_created': media.date_created,
# 'date_modified': media.date_modified,
'latitude': media.latitude_keys,
'longitude': media.longitude_keys,
'original_name': [media.original_name_key],
'title': media.title_keys
}
subdirs = Path('a', 'b', 'c', 'd')
for file_path in self.FILE_PATHS:
media = Media(str(file_path))
exif_data = ExifToolCaching(str(file_path)).asdict()
metadata = media.get_metadata()
for item, regex in items.items():
for mask in masks:
matched = re.search(regex, mask)
if matched:
part = filesystem.get_part(item, mask[1:-1], metadata,
{}, subdirs)
# check if part is correct
assert isinstance(part, str)
expected_part = ''
if item == 'basename':
expected_part = file_path.stem
elif item == 'date':
assert datetime.strptime(part, mask[1:-1])
expected_part = part
elif item == 'folder':
expected_part = subdirs.name
elif item == 'folders':
if platform == "win32":
assert '\\' in part
else:
assert '/' in part
expected_part = part
elif item == 'ext':
expected_part = file_path.suffix[1:]
if item == 'name':
expected_part = file_path.stem
for i, rx in filesystem.match_date_from_string(expected_part):
expected_part = re.sub(rx, '', expected_part)
elif item == 'custom':
expected_part = mask[2:-2]
elif item in ('city', 'country', 'location', 'state'):
expected_part = part
elif item in exif_tags.keys():
f = False
for key in exif_tags[item]:
if key in exif_data:
f = True
expected_part = exif_data[key]
break
if f == False:
expected_part = ''
assert part == expected_part
def test_get_date_taken(self):
filesystem = FileSystem()
for file_path in self.FILE_PATHS:
exif_data = ExifToolCaching(str(file_path)).asdict()
media = Media(str(file_path))
metadata = media.get_metadata()
date_taken = filesystem.get_date_taken(metadata)
dates = {}
for key, date in ('original', media.date_original), ('created',
media.date_created), ('modified', media.date_modified):
dates[key] = media.get_date_attribute(date)
if media.original_name_key in exif_data:
date_filename = filesystem.get_date_from_string(
exif_data[media.original_name_key])
else:
date_filename = filesystem.get_date_from_string(file_path.name)
if dates['original']:
assert date_taken == dates['original']
elif date_filename:
assert date_taken == date_filename
elif dates['created']:
assert date_taken == dates['created']
elif dates['modified']:
assert date_taken == dates['modified']
def test_sort_files(self, tmp_path):
db = Db(tmp_path)
filesystem = FileSystem(path_format=self.path_format)
summary, has_errors = filesystem.sort_files([self.SRCPATH], tmp_path, db)
# Summary is created and there is no errors
assert summary, summary
assert not has_errors, has_errors
# TODO check if path follow path_format
# TODO make another class?
def test_sort_file(self, tmp_path):
for mode in 'copy', 'move':
filesystem = FileSystem(path_format=self.path_format, mode=mode)
# copy mode
src_path = Path(self.SRCPATH, 'photo.png')
dest_path = Path(tmp_path,'photo_copy.png')
src_checksum = filesystem.checksum(src_path)
result_copy = filesystem.sort_file(src_path, dest_path)
assert result_copy
# Ensure files remain the same
assert filesystem.checkcomp(dest_path, src_checksum)
if mode == 'copy':
assert src_path.exists()
else:
assert not src_path.exists()
# TODO check for conflicts
# TODO check date
# filesystem.sort_files
#- Sort similar images into a directory
# filesystem.sort_similar

44
tests/test_media.py Normal file
View File

@ -0,0 +1,44 @@
import pytest
from pathlib import Path
import shutil
import tempfile
from dozo import constants
from dozo.media.media import Media
from dozo.media.audio import Audio
from dozo.media.photo import Photo
from dozo.media.video import Video
from dozo.exiftool import ExifToolCaching
DOZO_PATH = Path(__file__).parent.parent
class TestMetadata:
def setup_class(cls):
cls.SRCPATH = tempfile.mkdtemp(prefix='dozo-src')
filenames = ['invalid.jpg', 'photo.png', 'plain.jpg', 'text.txt', 'withoutextension']
cls.file_paths = set()
for filename in filenames:
source_path = Path(cls.SRCPATH, filename)
file_path = Path(DOZO_PATH, 'samples', filename)
shutil.copyfile(file_path, source_path)
cls.file_paths.add(source_path)
cls.path_format = constants.default_path + '/' + constants.default_name
def test_get_exiftool_attribute(self, tmp_path):
for file_path in self.file_paths:
exif_data = ExifToolCaching(str(file_path)).asdict()
ignore_tags = ('File:FileModifyDate', 'File:FileAccessDate')
exif_data_filtered = {}
for key in exif_data:
if key not in ignore_tags:
exif_data_filtered[key] = exif_data[key]
media = Media(str(file_path), ignore_tags)
exif = media.get_exiftool_attributes()
# Ensure returned value is a dictionary
assert isinstance(exif, dict)
for tag in ignore_tags:
assert tag not in exif
assert exif == exif_data_filtered