2019-08-12 21:46:30 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2023-02-06 12:35:28 +01:00
|
|
|
# Copyright 2019-2023 Mike Fährmann
|
2019-08-12 21:46:30 +02:00
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License version 2 as
|
|
|
|
# published by the Free Software Foundation.
|
|
|
|
|
2020-01-02 20:58:10 +01:00
|
|
|
import os
|
2020-05-02 01:15:50 +02:00
|
|
|
import sys
|
|
|
|
import unittest
|
|
|
|
from unittest.mock import Mock, mock_open, patch
|
|
|
|
|
2020-05-18 01:35:53 +02:00
|
|
|
import logging
|
2019-08-12 21:46:30 +02:00
|
|
|
import zipfile
|
|
|
|
import tempfile
|
2020-11-18 17:11:55 +01:00
|
|
|
import collections
|
2022-03-23 23:05:14 +01:00
|
|
|
from datetime import datetime
|
2019-08-12 21:46:30 +02:00
|
|
|
|
2020-05-02 01:15:50 +02:00
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
2021-09-27 21:17:44 +02:00
|
|
|
from gallery_dl import extractor, output, path # noqa E402
|
2020-11-18 17:11:55 +01:00
|
|
|
from gallery_dl import postprocessor, config # noqa E402
|
2020-05-02 01:15:50 +02:00
|
|
|
from gallery_dl.postprocessor.common import PostProcessor # noqa E402
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
class MockPostprocessorModule(Mock):
|
|
|
|
__postprocessor__ = "mock"
|
|
|
|
|
|
|
|
|
2020-05-18 01:35:53 +02:00
|
|
|
class FakeJob():
|
|
|
|
|
2024-02-27 01:37:57 +01:00
|
|
|
def __init__(self, extr=extractor.find("generic:https://example.org/")):
|
|
|
|
extr.directory_fmt = ("{category}",)
|
2021-06-04 20:58:11 +02:00
|
|
|
self.extractor = extr
|
2021-09-27 21:17:44 +02:00
|
|
|
self.pathfmt = path.PathFormat(extr)
|
2020-05-18 01:35:53 +02:00
|
|
|
self.out = output.NullOutput()
|
|
|
|
self.get_logger = logging.getLogger
|
2020-11-18 17:11:55 +01:00
|
|
|
self.hooks = collections.defaultdict(list)
|
2020-05-18 01:35:53 +02:00
|
|
|
|
2021-06-04 18:08:08 +02:00
|
|
|
def register_hooks(self, hooks, options):
|
|
|
|
for hook, callback in hooks.items():
|
|
|
|
self.hooks[hook].append(callback)
|
|
|
|
|
2020-05-18 01:35:53 +02:00
|
|
|
|
2019-08-12 21:46:30 +02:00
|
|
|
class TestPostprocessorModule(unittest.TestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
postprocessor._cache.clear()
|
|
|
|
|
|
|
|
def test_find(self):
|
|
|
|
for name in (postprocessor.modules):
|
|
|
|
cls = postprocessor.find(name)
|
|
|
|
self.assertEqual(cls.__name__, name.capitalize() + "PP")
|
|
|
|
self.assertIs(cls.__base__, PostProcessor)
|
|
|
|
|
|
|
|
self.assertEqual(postprocessor.find("foo"), None)
|
|
|
|
self.assertEqual(postprocessor.find(1234) , None)
|
|
|
|
self.assertEqual(postprocessor.find(None) , None)
|
|
|
|
|
2021-03-01 01:25:46 +01:00
|
|
|
@patch("builtins.__import__")
|
2019-08-12 21:46:30 +02:00
|
|
|
def test_cache(self, import_module):
|
|
|
|
import_module.return_value = MockPostprocessorModule()
|
|
|
|
|
|
|
|
for name in (postprocessor.modules):
|
|
|
|
postprocessor.find(name)
|
|
|
|
self.assertEqual(import_module.call_count, len(postprocessor.modules))
|
|
|
|
|
|
|
|
# no new calls to import_module
|
|
|
|
for name in (postprocessor.modules):
|
|
|
|
postprocessor.find(name)
|
|
|
|
self.assertEqual(import_module.call_count, len(postprocessor.modules))
|
|
|
|
|
|
|
|
|
|
|
|
class BasePostprocessorTest(unittest.TestCase):
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def setUpClass(cls):
|
|
|
|
cls.dir = tempfile.TemporaryDirectory()
|
2019-11-23 23:50:16 +01:00
|
|
|
config.set((), "base-directory", cls.dir.name)
|
2020-05-18 01:35:53 +02:00
|
|
|
cls.job = FakeJob()
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def tearDownClass(cls):
|
|
|
|
cls.dir.cleanup()
|
|
|
|
config.clear()
|
|
|
|
|
2020-11-18 17:11:55 +01:00
|
|
|
def tearDown(self):
|
|
|
|
self.job.hooks.clear()
|
|
|
|
|
2019-08-12 21:46:30 +02:00
|
|
|
def _create(self, options=None, data=None):
|
|
|
|
kwdict = {"category": "test", "filename": "file", "extension": "ext"}
|
|
|
|
if options is None:
|
|
|
|
options = {}
|
|
|
|
if data is not None:
|
|
|
|
kwdict.update(data)
|
|
|
|
|
2020-05-18 01:35:53 +02:00
|
|
|
self.pathfmt = self.job.pathfmt
|
2019-08-12 21:46:30 +02:00
|
|
|
self.pathfmt.set_directory(kwdict)
|
|
|
|
self.pathfmt.set_filename(kwdict)
|
2022-11-08 17:01:10 +01:00
|
|
|
self.pathfmt.build_path()
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
pp = postprocessor.find(self.__class__.__name__[:-4].lower())
|
2020-05-18 01:35:53 +02:00
|
|
|
return pp(self.job, options)
|
2019-08-12 21:46:30 +02:00
|
|
|
|
2023-08-10 19:46:37 +02:00
|
|
|
def _trigger(self, events=None):
|
2020-11-18 17:11:55 +01:00
|
|
|
for event in (events or ("prepare", "file")):
|
|
|
|
for callback in self.job.hooks[event]:
|
2023-08-10 19:46:37 +02:00
|
|
|
callback(self.pathfmt)
|
2020-11-18 17:11:55 +01:00
|
|
|
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
class ClassifyTest(BasePostprocessorTest):
|
|
|
|
|
|
|
|
def test_classify_default(self):
|
|
|
|
pp = self._create()
|
|
|
|
|
|
|
|
self.assertEqual(pp.mapping, {
|
|
|
|
ext: directory
|
|
|
|
for directory, exts in pp.DEFAULT_MAPPING.items()
|
|
|
|
for ext in exts
|
|
|
|
})
|
|
|
|
self.pathfmt.set_extension("jpg")
|
2022-11-08 17:01:10 +01:00
|
|
|
self.pathfmt.build_path()
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
pp.prepare(self.pathfmt)
|
|
|
|
path = os.path.join(self.dir.name, "test", "Pictures")
|
2019-08-21 23:54:08 +02:00
|
|
|
self.assertEqual(self.pathfmt.path, path + "/file.jpg")
|
2019-08-12 21:46:30 +02:00
|
|
|
self.assertEqual(self.pathfmt.realpath, path + "/file.jpg")
|
|
|
|
|
|
|
|
with patch("os.makedirs") as mkdirs:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-12 21:46:30 +02:00
|
|
|
mkdirs.assert_called_once_with(path, exist_ok=True)
|
|
|
|
|
|
|
|
def test_classify_noop(self):
|
|
|
|
pp = self._create()
|
|
|
|
rp = self.pathfmt.realpath
|
|
|
|
|
|
|
|
pp.prepare(self.pathfmt)
|
2019-08-21 23:54:08 +02:00
|
|
|
self.assertEqual(self.pathfmt.path, rp)
|
2019-08-12 21:46:30 +02:00
|
|
|
self.assertEqual(self.pathfmt.realpath, rp)
|
|
|
|
|
|
|
|
with patch("os.makedirs") as mkdirs:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-12 21:46:30 +02:00
|
|
|
self.assertEqual(mkdirs.call_count, 0)
|
|
|
|
|
|
|
|
def test_classify_custom(self):
|
|
|
|
pp = self._create({"mapping": {
|
|
|
|
"foo/bar": ["foo", "bar"],
|
|
|
|
}})
|
|
|
|
|
|
|
|
self.assertEqual(pp.mapping, {
|
|
|
|
"foo": "foo/bar",
|
|
|
|
"bar": "foo/bar",
|
|
|
|
})
|
|
|
|
self.pathfmt.set_extension("foo")
|
2022-11-08 17:01:10 +01:00
|
|
|
self.pathfmt.build_path()
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
pp.prepare(self.pathfmt)
|
|
|
|
path = os.path.join(self.dir.name, "test", "foo", "bar")
|
2019-08-21 23:54:08 +02:00
|
|
|
self.assertEqual(self.pathfmt.path, path + "/file.foo")
|
2019-08-12 21:46:30 +02:00
|
|
|
self.assertEqual(self.pathfmt.realpath, path + "/file.foo")
|
|
|
|
|
|
|
|
with patch("os.makedirs") as mkdirs:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-12 21:46:30 +02:00
|
|
|
mkdirs.assert_called_once_with(path, exist_ok=True)
|
|
|
|
|
|
|
|
|
2023-06-21 23:03:05 +02:00
|
|
|
class ExecTest(BasePostprocessorTest):
|
|
|
|
|
|
|
|
def test_command_string(self):
|
|
|
|
self._create({
|
2023-10-09 12:50:10 +02:00
|
|
|
"command": "echo {} {_path} {_directory} {_filename} && rm {};",
|
2023-06-21 23:03:05 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
with patch("subprocess.Popen") as p:
|
|
|
|
i = Mock()
|
|
|
|
i.wait.return_value = 0
|
|
|
|
p.return_value = i
|
|
|
|
self._trigger(("after",))
|
|
|
|
|
|
|
|
p.assert_called_once_with(
|
2023-10-09 12:50:10 +02:00
|
|
|
"echo {0} {0} {1} {2} && rm {0};".format(
|
|
|
|
self.pathfmt.realpath,
|
|
|
|
self.pathfmt.realdirectory,
|
|
|
|
self.pathfmt.filename),
|
|
|
|
shell=True)
|
2023-06-21 23:03:05 +02:00
|
|
|
i.wait.assert_called_once_with()
|
|
|
|
|
|
|
|
def test_command_list(self):
|
|
|
|
self._create({
|
|
|
|
"command": ["~/script.sh", "{category}",
|
2023-06-25 14:01:26 +02:00
|
|
|
"\fE _directory.upper()"],
|
2023-06-21 23:03:05 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
with patch("subprocess.Popen") as p:
|
|
|
|
i = Mock()
|
|
|
|
i.wait.return_value = 0
|
|
|
|
p.return_value = i
|
|
|
|
self._trigger(("after",))
|
|
|
|
|
|
|
|
p.assert_called_once_with(
|
|
|
|
[
|
|
|
|
os.path.expanduser("~/script.sh"),
|
|
|
|
self.pathfmt.kwdict["category"],
|
|
|
|
self.pathfmt.realdirectory.upper(),
|
|
|
|
],
|
|
|
|
shell=False,
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_command_returncode(self):
|
|
|
|
self._create({
|
|
|
|
"command": "echo {}",
|
|
|
|
})
|
|
|
|
|
|
|
|
with patch("subprocess.Popen") as p:
|
|
|
|
i = Mock()
|
|
|
|
i.wait.return_value = 123
|
|
|
|
p.return_value = i
|
|
|
|
|
|
|
|
with self.assertLogs() as log:
|
|
|
|
self._trigger(("after",))
|
|
|
|
|
|
|
|
msg = ("WARNING:postprocessor.exec:'echo {}' returned with "
|
|
|
|
"non-zero exit status (123)".format(self.pathfmt.realpath))
|
|
|
|
self.assertEqual(log.output[0], msg)
|
|
|
|
|
|
|
|
def test_async(self):
|
|
|
|
self._create({
|
|
|
|
"async" : True,
|
|
|
|
"command": "echo {}",
|
|
|
|
})
|
|
|
|
|
|
|
|
with patch("subprocess.Popen") as p:
|
|
|
|
i = Mock()
|
|
|
|
p.return_value = i
|
|
|
|
self._trigger(("after",))
|
|
|
|
|
|
|
|
self.assertTrue(p.called)
|
|
|
|
self.assertFalse(i.wait.called)
|
|
|
|
|
|
|
|
|
2019-08-12 21:46:30 +02:00
|
|
|
class MetadataTest(BasePostprocessorTest):
|
|
|
|
|
|
|
|
def test_metadata_default(self):
|
|
|
|
pp = self._create()
|
|
|
|
|
|
|
|
# default arguments
|
|
|
|
self.assertEqual(pp.write , pp._write_json)
|
|
|
|
self.assertEqual(pp.extension, "json")
|
2023-02-06 12:35:28 +01:00
|
|
|
self.assertTrue(callable(pp._json_encode))
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
def test_metadata_json(self):
|
|
|
|
pp = self._create({
|
2023-02-07 18:28:14 +01:00
|
|
|
"mode" : "json",
|
|
|
|
"extension" : "JSON",
|
2019-11-21 16:57:39 +01:00
|
|
|
}, {
|
2023-02-07 18:28:14 +01:00
|
|
|
"public" : "hello ワールド",
|
|
|
|
"_private" : "foo バー",
|
2019-08-12 21:46:30 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
self.assertEqual(pp.write , pp._write_json)
|
|
|
|
self.assertEqual(pp.extension, "JSON")
|
2023-02-06 12:35:28 +01:00
|
|
|
self.assertTrue(callable(pp._json_encode))
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
path = self.pathfmt.realpath + ".JSON"
|
|
|
|
m.assert_called_once_with(path, "w", encoding="utf-8")
|
2023-02-07 18:28:14 +01:00
|
|
|
|
|
|
|
if sys.hexversion >= 0x3060000:
|
|
|
|
# python 3.4 & 3.5 have random order without 'sort: True'
|
|
|
|
self.assertEqual(self._output(m), """{
|
|
|
|
"category": "test",
|
|
|
|
"filename": "file",
|
|
|
|
"extension": "ext",
|
|
|
|
"public": "hello ワールド"
|
2019-08-12 21:46:30 +02:00
|
|
|
}
|
2023-02-06 12:35:28 +01:00
|
|
|
""")
|
|
|
|
|
|
|
|
def test_metadata_json_options(self):
|
|
|
|
pp = self._create({
|
2023-02-07 18:28:14 +01:00
|
|
|
"mode" : "json",
|
|
|
|
"ascii" : True,
|
|
|
|
"sort" : True,
|
|
|
|
"separators": [",", " : "],
|
|
|
|
"private" : True,
|
|
|
|
"indent" : None,
|
|
|
|
"open" : "a",
|
|
|
|
"encoding" : "UTF-8",
|
|
|
|
"extension" : "JSON",
|
2023-02-06 12:35:28 +01:00
|
|
|
}, {
|
2023-02-07 18:28:14 +01:00
|
|
|
"public" : "hello ワールド",
|
|
|
|
"_private" : "foo バー",
|
2023-02-06 12:35:28 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
self.assertEqual(pp.write , pp._write_json)
|
|
|
|
self.assertEqual(pp.extension, "JSON")
|
|
|
|
self.assertTrue(callable(pp._json_encode))
|
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
|
|
|
self._trigger()
|
|
|
|
|
|
|
|
path = self.pathfmt.realpath + ".JSON"
|
|
|
|
m.assert_called_once_with(path, "a", encoding="UTF-8")
|
|
|
|
self.assertEqual(self._output(m), """{\
|
2023-02-07 18:28:14 +01:00
|
|
|
"_private" : "foo \\u30d0\\u30fc",\
|
|
|
|
"category" : "test",\
|
|
|
|
"extension" : "ext",\
|
|
|
|
"filename" : "file",\
|
|
|
|
"public" : "hello \\u30ef\\u30fc\\u30eb\\u30c9"}
|
2019-08-12 21:46:30 +02:00
|
|
|
""")
|
|
|
|
|
|
|
|
def test_metadata_tags(self):
|
2019-08-26 23:10:12 +02:00
|
|
|
pp = self._create(
|
|
|
|
{"mode": "tags"},
|
|
|
|
{"tags": ["foo", "bar", "baz"]},
|
|
|
|
)
|
2019-08-12 21:46:30 +02:00
|
|
|
self.assertEqual(pp.write, pp._write_tags)
|
|
|
|
self.assertEqual(pp.extension, "txt")
|
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
path = self.pathfmt.realpath + ".txt"
|
|
|
|
m.assert_called_once_with(path, "w", encoding="utf-8")
|
|
|
|
self.assertEqual(self._output(m), "foo\nbar\nbaz\n")
|
|
|
|
|
|
|
|
def test_metadata_tags_split_1(self):
|
2020-11-18 17:11:55 +01:00
|
|
|
self._create(
|
2019-08-26 23:10:12 +02:00
|
|
|
{"mode": "tags"},
|
|
|
|
{"tags": "foo, bar, baz"},
|
|
|
|
)
|
2019-08-12 21:46:30 +02:00
|
|
|
with patch("builtins.open", mock_open()) as m:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-12 21:46:30 +02:00
|
|
|
self.assertEqual(self._output(m), "foo\nbar\nbaz\n")
|
|
|
|
|
|
|
|
def test_metadata_tags_split_2(self):
|
2020-11-18 17:11:55 +01:00
|
|
|
self._create(
|
2019-08-12 21:46:30 +02:00
|
|
|
{"mode": "tags"},
|
|
|
|
{"tags": "foobar1 foobar2 foobarbaz"},
|
|
|
|
)
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-12 21:46:30 +02:00
|
|
|
self.assertEqual(self._output(m), "foobar1\nfoobar2\nfoobarbaz\n")
|
|
|
|
|
|
|
|
def test_metadata_tags_tagstring(self):
|
2020-11-18 17:11:55 +01:00
|
|
|
self._create(
|
2019-08-26 23:10:12 +02:00
|
|
|
{"mode": "tags"},
|
|
|
|
{"tag_string": "foo, bar, baz"},
|
|
|
|
)
|
2019-08-12 21:46:30 +02:00
|
|
|
with patch("builtins.open", mock_open()) as m:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-12 21:46:30 +02:00
|
|
|
self.assertEqual(self._output(m), "foo\nbar\nbaz\n")
|
|
|
|
|
2021-06-04 20:58:11 +02:00
|
|
|
def test_metadata_tags_dict(self):
|
|
|
|
self._create(
|
|
|
|
{"mode": "tags"},
|
|
|
|
{"tags": {"g": ["foobar1", "foobar2"], "m": ["foobarbaz"]}},
|
|
|
|
)
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
|
|
|
self._trigger()
|
|
|
|
self.assertEqual(self._output(m), "foobar1\nfoobar2\nfoobarbaz\n")
|
|
|
|
|
2023-02-19 13:14:34 +01:00
|
|
|
def test_metadata_tags_list_of_dict(self):
|
2023-02-19 13:13:21 +01:00
|
|
|
self._create(
|
|
|
|
{"mode": "tags"},
|
2023-02-20 07:33:30 +01:00
|
|
|
{"tags": [
|
2023-11-04 17:33:14 +01:00
|
|
|
{"g": "foobar1", "m": "foobar2", "u": True},
|
|
|
|
{"g": None, "m": "foobarbaz", "u": [3, 4]},
|
2023-02-20 07:33:30 +01:00
|
|
|
]},
|
2023-02-19 13:13:21 +01:00
|
|
|
)
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
|
|
|
self._trigger()
|
|
|
|
self.assertEqual(self._output(m), "foobar1\nfoobar2\nfoobarbaz\n")
|
|
|
|
|
2019-08-12 21:46:30 +02:00
|
|
|
def test_metadata_custom(self):
|
2020-10-27 20:09:58 +01:00
|
|
|
def test(pp_info):
|
|
|
|
pp = self._create(pp_info, {"foo": "bar"})
|
|
|
|
self.assertEqual(pp.write, pp._write_custom)
|
|
|
|
self.assertEqual(pp.extension, "txt")
|
2020-11-20 22:28:01 +01:00
|
|
|
self.assertTrue(pp._content_fmt)
|
2020-10-27 20:09:58 +01:00
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2020-10-27 20:09:58 +01:00
|
|
|
self.assertEqual(self._output(m), "bar\nNone\n")
|
2020-11-18 17:11:55 +01:00
|
|
|
self.job.hooks.clear()
|
2020-10-27 20:09:58 +01:00
|
|
|
|
|
|
|
test({"mode": "custom", "content-format": "{foo}\n{missing}\n"})
|
|
|
|
test({"mode": "custom", "content-format": ["{foo}", "{missing}"]})
|
|
|
|
test({"mode": "custom", "format": "{foo}\n{missing}\n"})
|
2022-10-04 17:53:04 +02:00
|
|
|
test({"format": "{foo}\n{missing}\n"})
|
2019-08-12 21:46:30 +02:00
|
|
|
|
2019-11-29 23:12:22 +01:00
|
|
|
def test_metadata_extfmt(self):
|
|
|
|
pp = self._create({
|
|
|
|
"extension" : "ignored",
|
|
|
|
"extension-format": "json",
|
|
|
|
})
|
|
|
|
|
2020-11-20 22:28:01 +01:00
|
|
|
self.assertEqual(pp._filename, pp._filename_extfmt)
|
2019-11-29 23:12:22 +01:00
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-11-29 23:12:22 +01:00
|
|
|
|
|
|
|
path = self.pathfmt.realdirectory + "file.json"
|
|
|
|
m.assert_called_once_with(path, "w", encoding="utf-8")
|
|
|
|
|
|
|
|
def test_metadata_extfmt_2(self):
|
2020-11-18 17:11:55 +01:00
|
|
|
self._create({
|
2019-11-29 23:12:22 +01:00
|
|
|
"extension-format": "{extension!u}-data:{category:Res/ES/}",
|
|
|
|
})
|
|
|
|
|
|
|
|
self.pathfmt.prefix = "2."
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-11-29 23:12:22 +01:00
|
|
|
|
|
|
|
path = self.pathfmt.realdirectory + "file.2.EXT-data:tESt"
|
|
|
|
m.assert_called_once_with(path, "w", encoding="utf-8")
|
|
|
|
|
2020-01-02 20:58:10 +01:00
|
|
|
def test_metadata_directory(self):
|
2020-11-18 17:11:55 +01:00
|
|
|
self._create({
|
2020-01-02 20:58:10 +01:00
|
|
|
"directory": "metadata",
|
|
|
|
})
|
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2020-01-02 20:58:10 +01:00
|
|
|
|
|
|
|
path = self.pathfmt.realdirectory + "metadata/file.ext.json"
|
|
|
|
m.assert_called_once_with(path, "w", encoding="utf-8")
|
|
|
|
|
|
|
|
def test_metadata_directory_2(self):
|
2020-11-18 17:11:55 +01:00
|
|
|
self._create({
|
2020-01-02 20:58:10 +01:00
|
|
|
"directory" : "metadata////",
|
|
|
|
"extension-format": "json",
|
|
|
|
})
|
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2020-01-02 20:58:10 +01:00
|
|
|
|
|
|
|
path = self.pathfmt.realdirectory + "metadata/file.json"
|
|
|
|
m.assert_called_once_with(path, "w", encoding="utf-8")
|
|
|
|
|
2020-11-20 22:28:01 +01:00
|
|
|
def test_metadata_filename(self):
|
|
|
|
self._create({
|
2021-01-10 00:10:47 +01:00
|
|
|
"filename" : "{category}_{filename}_/meta/\n\r.data",
|
2020-11-20 22:28:01 +01:00
|
|
|
"extension-format": "json",
|
|
|
|
})
|
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m:
|
|
|
|
self._trigger()
|
|
|
|
|
2021-01-10 00:10:47 +01:00
|
|
|
path = self.pathfmt.realdirectory + "test_file__meta_.data"
|
2020-11-20 22:28:01 +01:00
|
|
|
m.assert_called_once_with(path, "w", encoding="utf-8")
|
|
|
|
|
2022-05-30 21:15:16 +02:00
|
|
|
def test_metadata_stdout(self):
|
2023-02-07 18:28:14 +01:00
|
|
|
self._create({"filename": "-", "indent": None, "sort": True})
|
2022-05-30 21:15:16 +02:00
|
|
|
|
|
|
|
with patch("sys.stdout", Mock()) as m:
|
|
|
|
self._trigger()
|
|
|
|
|
|
|
|
self.assertEqual(self._output(m), """\
|
|
|
|
{"category": "test", "extension": "ext", "filename": "file"}
|
|
|
|
""")
|
|
|
|
|
2022-07-19 12:24:26 +02:00
|
|
|
def test_metadata_modify(self):
|
2023-04-26 19:31:27 +02:00
|
|
|
kwdict = {"foo": 0, "bar": {"bax": 1, "bay": 2, "baz": 3, "ba2": {}}}
|
2022-07-19 12:24:26 +02:00
|
|
|
self._create({
|
|
|
|
"mode": "modify",
|
|
|
|
"fields": {
|
2023-04-25 14:30:18 +02:00
|
|
|
"foo" : "{filename}-{foo!s}",
|
|
|
|
"foo2" : "\fE bar['bax'] + 122",
|
2023-04-26 19:31:27 +02:00
|
|
|
"bar[\"baz\"]" : "{_now}",
|
2023-04-25 14:30:18 +02:00
|
|
|
"bar['ba2'][a]": "test",
|
2022-07-19 12:24:26 +02:00
|
|
|
},
|
|
|
|
}, kwdict)
|
|
|
|
|
2023-04-25 14:17:25 +02:00
|
|
|
pdict = self.pathfmt.kwdict
|
2022-07-19 12:24:26 +02:00
|
|
|
self.assertIsNot(kwdict, pdict)
|
|
|
|
self.assertEqual(pdict["foo"], kwdict["foo"])
|
|
|
|
self.assertEqual(pdict["bar"], kwdict["bar"])
|
|
|
|
|
|
|
|
self._trigger()
|
|
|
|
|
2023-04-25 14:17:25 +02:00
|
|
|
self.assertEqual(pdict["foo"] , "file-0")
|
|
|
|
self.assertEqual(pdict["foo2"], 123)
|
|
|
|
self.assertEqual(pdict["bar"]["ba2"]["a"], "test")
|
2022-07-19 12:24:26 +02:00
|
|
|
self.assertIsInstance(pdict["bar"]["baz"], datetime)
|
|
|
|
|
2022-07-19 00:57:29 +02:00
|
|
|
def test_metadata_delete(self):
|
2023-04-25 14:17:25 +02:00
|
|
|
kwdict = {
|
|
|
|
"foo": 0,
|
|
|
|
"bar": {
|
|
|
|
"bax": 1,
|
|
|
|
"bay": 2,
|
|
|
|
"baz": {"a": 3, "b": 4},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
self._create({
|
|
|
|
"mode": "delete",
|
2023-04-25 14:30:18 +02:00
|
|
|
"fields": ["foo", "bar['bax']", "bar[\"baz\"][a]"],
|
2023-04-25 14:17:25 +02:00
|
|
|
}, kwdict)
|
2022-07-19 00:57:29 +02:00
|
|
|
|
2023-04-25 14:17:25 +02:00
|
|
|
pdict = self.pathfmt.kwdict
|
2022-07-19 12:24:26 +02:00
|
|
|
self.assertIsNot(kwdict, pdict)
|
2023-04-25 14:17:25 +02:00
|
|
|
|
2022-07-19 00:57:29 +02:00
|
|
|
self.assertEqual(pdict["foo"], kwdict["foo"])
|
|
|
|
self.assertEqual(pdict["bar"], kwdict["bar"])
|
|
|
|
|
|
|
|
self._trigger()
|
2023-04-25 14:17:25 +02:00
|
|
|
|
2022-07-19 00:57:29 +02:00
|
|
|
self.assertNotIn("foo", pdict)
|
2023-04-25 14:17:25 +02:00
|
|
|
self.assertNotIn("bax", pdict["bar"])
|
|
|
|
self.assertNotIn("a", pdict["bar"]["baz"])
|
2022-07-19 00:57:29 +02:00
|
|
|
|
2023-03-17 23:16:52 +01:00
|
|
|
# no errors for deleted/undefined fields
|
2022-07-19 00:57:29 +02:00
|
|
|
self._trigger()
|
|
|
|
self.assertNotIn("foo", pdict)
|
2023-04-25 14:17:25 +02:00
|
|
|
self.assertNotIn("bax", pdict["bar"])
|
|
|
|
self.assertNotIn("a", pdict["bar"]["baz"])
|
2022-07-19 00:57:29 +02:00
|
|
|
|
2023-03-17 23:16:52 +01:00
|
|
|
def test_metadata_option_skip(self):
|
|
|
|
self._create({"skip": True})
|
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m, \
|
|
|
|
patch("os.path.exists") as e:
|
|
|
|
e.return_value = True
|
|
|
|
self._trigger()
|
|
|
|
|
|
|
|
self.assertTrue(e.called)
|
|
|
|
self.assertTrue(not m.called)
|
|
|
|
self.assertTrue(not len(self._output(m)))
|
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m, \
|
|
|
|
patch("os.path.exists") as e:
|
|
|
|
e.return_value = False
|
|
|
|
self._trigger()
|
|
|
|
|
|
|
|
self.assertTrue(e.called)
|
|
|
|
self.assertTrue(m.called)
|
|
|
|
self.assertGreater(len(self._output(m)), 0)
|
|
|
|
|
|
|
|
path = self.pathfmt.realdirectory + "file.ext.json"
|
|
|
|
m.assert_called_once_with(path, "w", encoding="utf-8")
|
|
|
|
|
|
|
|
def test_metadata_option_skip_false(self):
|
|
|
|
self._create({"skip": False})
|
|
|
|
|
|
|
|
with patch("builtins.open", mock_open()) as m, \
|
|
|
|
patch("os.path.exists") as e:
|
|
|
|
self._trigger()
|
|
|
|
|
|
|
|
self.assertTrue(not e.called)
|
|
|
|
self.assertTrue(m.called)
|
|
|
|
|
2019-08-12 21:46:30 +02:00
|
|
|
@staticmethod
|
|
|
|
def _output(mock):
|
|
|
|
return "".join(
|
|
|
|
call[1][0]
|
|
|
|
for call in mock.mock_calls
|
2022-05-30 21:15:16 +02:00
|
|
|
if call[0].endswith("write")
|
2019-08-12 21:46:30 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class MtimeTest(BasePostprocessorTest):
|
|
|
|
|
|
|
|
def test_mtime_datetime(self):
|
2022-03-23 23:05:14 +01:00
|
|
|
self._create(None, {"date": datetime(1980, 1, 1)})
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-14 21:57:42 +02:00
|
|
|
self.assertEqual(self.pathfmt.kwdict["_mtime"], 315532800)
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
def test_mtime_timestamp(self):
|
2020-11-18 17:11:55 +01:00
|
|
|
self._create(None, {"date": 315532800})
|
|
|
|
self._trigger()
|
2019-08-14 21:57:42 +02:00
|
|
|
self.assertEqual(self.pathfmt.kwdict["_mtime"], 315532800)
|
2019-08-12 21:46:30 +02:00
|
|
|
|
2022-07-08 20:56:01 +02:00
|
|
|
def test_mtime_key(self):
|
2020-11-18 17:11:55 +01:00
|
|
|
self._create({"key": "foo"}, {"foo": 315532800})
|
|
|
|
self._trigger()
|
2019-08-14 21:57:42 +02:00
|
|
|
self.assertEqual(self.pathfmt.kwdict["_mtime"], 315532800)
|
2019-08-12 21:46:30 +02:00
|
|
|
|
2022-07-08 20:56:01 +02:00
|
|
|
def test_mtime_value(self):
|
|
|
|
self._create({"value": "{foo}"}, {"foo": 315532800})
|
|
|
|
self._trigger()
|
|
|
|
self.assertEqual(self.pathfmt.kwdict["_mtime"], 315532800)
|
|
|
|
|
2019-08-12 21:46:30 +02:00
|
|
|
|
2023-07-24 15:22:57 +02:00
|
|
|
class PythonTest(BasePostprocessorTest):
|
|
|
|
|
|
|
|
def test_module(self):
|
|
|
|
path = os.path.join(self.dir.name, "module.py")
|
|
|
|
self._write_module(path)
|
|
|
|
|
|
|
|
sys.path.insert(0, self.dir.name)
|
|
|
|
try:
|
|
|
|
self._create({"function": "module:calc"}, {"_value": 123})
|
|
|
|
finally:
|
|
|
|
del sys.path[0]
|
|
|
|
|
|
|
|
self.assertNotIn("_result", self.pathfmt.kwdict)
|
|
|
|
self._trigger()
|
|
|
|
self.assertEqual(self.pathfmt.kwdict["_result"], 246)
|
|
|
|
|
|
|
|
def test_path(self):
|
|
|
|
path = os.path.join(self.dir.name, "module.py")
|
|
|
|
self._write_module(path)
|
|
|
|
|
|
|
|
self._create({"function": path + ":calc"}, {"_value": 12})
|
|
|
|
|
|
|
|
self.assertNotIn("_result", self.pathfmt.kwdict)
|
|
|
|
self._trigger()
|
|
|
|
self.assertEqual(self.pathfmt.kwdict["_result"], 24)
|
|
|
|
|
|
|
|
def _write_module(self, path):
|
|
|
|
with open(path, "w") as fp:
|
|
|
|
fp.write("""
|
|
|
|
def calc(kwdict):
|
|
|
|
kwdict["_result"] = kwdict["_value"] * 2
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
2019-08-12 21:46:30 +02:00
|
|
|
class ZipTest(BasePostprocessorTest):
|
|
|
|
|
|
|
|
def test_zip_default(self):
|
|
|
|
pp = self._create()
|
2022-07-14 11:55:39 +02:00
|
|
|
self.assertEqual(self.job.hooks["file"][0], pp.write_fast)
|
|
|
|
self.assertEqual(pp.path, self.pathfmt.realdirectory[:-1])
|
2019-08-12 21:46:30 +02:00
|
|
|
self.assertEqual(pp.delete, True)
|
2020-08-31 21:53:18 +02:00
|
|
|
self.assertEqual(pp.args, (
|
2022-07-14 11:55:39 +02:00
|
|
|
pp.path + ".zip", "a", zipfile.ZIP_STORED, True,
|
2020-08-31 21:53:18 +02:00
|
|
|
))
|
|
|
|
self.assertTrue(pp.args[0].endswith("/test.zip"))
|
|
|
|
|
|
|
|
def test_zip_safe(self):
|
|
|
|
pp = self._create({"mode": "safe"})
|
2020-11-18 17:11:55 +01:00
|
|
|
self.assertEqual(self.job.hooks["file"][0], pp.write_safe)
|
2022-07-14 11:55:39 +02:00
|
|
|
self.assertEqual(pp.path, self.pathfmt.realdirectory[:-1])
|
2020-08-31 21:53:18 +02:00
|
|
|
self.assertEqual(pp.delete, True)
|
|
|
|
self.assertEqual(pp.args, (
|
2022-07-14 11:55:39 +02:00
|
|
|
pp.path + ".zip", "a", zipfile.ZIP_STORED, True,
|
2020-08-31 21:53:18 +02:00
|
|
|
))
|
|
|
|
self.assertTrue(pp.args[0].endswith("/test.zip"))
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
def test_zip_options(self):
|
|
|
|
pp = self._create({
|
|
|
|
"keep-files": True,
|
|
|
|
"compression": "zip",
|
|
|
|
"extension": "cbz",
|
|
|
|
})
|
|
|
|
self.assertEqual(pp.delete, False)
|
|
|
|
self.assertEqual(pp.args, (
|
2022-07-14 11:55:39 +02:00
|
|
|
pp.path + ".cbz", "a", zipfile.ZIP_DEFLATED, True,
|
2019-08-12 21:46:30 +02:00
|
|
|
))
|
2020-08-31 21:53:18 +02:00
|
|
|
self.assertTrue(pp.args[0].endswith("/test.cbz"))
|
2019-08-26 23:10:12 +02:00
|
|
|
|
|
|
|
def test_zip_write(self):
|
|
|
|
with tempfile.NamedTemporaryFile("w", dir=self.dir.name) as file:
|
2022-09-09 11:41:27 +02:00
|
|
|
pp = self._create({"files": [file.name, "_info_.json"],
|
|
|
|
"keep-files": True})
|
|
|
|
|
|
|
|
filename = os.path.basename(file.name)
|
2019-08-26 23:10:12 +02:00
|
|
|
file.write("foobar\n")
|
|
|
|
|
|
|
|
# write dummy file with 3 different names
|
|
|
|
for i in range(3):
|
|
|
|
name = "file{}.ext".format(i)
|
|
|
|
self.pathfmt.temppath = file.name
|
|
|
|
self.pathfmt.filename = name
|
|
|
|
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-26 23:10:12 +02:00
|
|
|
|
2020-08-31 21:53:18 +02:00
|
|
|
nti = pp.zfile.NameToInfo
|
2022-09-09 11:41:27 +02:00
|
|
|
self.assertEqual(len(nti), i+2)
|
2019-08-26 23:10:12 +02:00
|
|
|
self.assertIn(name, nti)
|
|
|
|
|
|
|
|
# check file contents
|
2022-09-09 11:41:27 +02:00
|
|
|
self.assertEqual(len(nti), 4)
|
2019-08-26 23:10:12 +02:00
|
|
|
self.assertIn("file0.ext", nti)
|
|
|
|
self.assertIn("file1.ext", nti)
|
|
|
|
self.assertIn("file2.ext", nti)
|
2022-09-09 11:41:27 +02:00
|
|
|
self.assertIn(filename, nti)
|
2019-08-26 23:10:12 +02:00
|
|
|
|
|
|
|
# write the last file a second time (will be skipped)
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2022-09-09 11:41:27 +02:00
|
|
|
self.assertEqual(len(pp.zfile.NameToInfo), 4)
|
2019-08-26 23:10:12 +02:00
|
|
|
|
|
|
|
# close file
|
2023-08-10 19:46:37 +02:00
|
|
|
self._trigger(("finalize",))
|
2019-08-26 23:10:12 +02:00
|
|
|
|
|
|
|
# reopen to check persistence
|
|
|
|
with zipfile.ZipFile(pp.zfile.filename) as file:
|
|
|
|
nti = file.NameToInfo
|
2022-09-09 11:41:27 +02:00
|
|
|
self.assertEqual(len(pp.zfile.NameToInfo), 4)
|
2022-07-14 11:55:39 +02:00
|
|
|
self.assertIn("file0.ext", nti)
|
|
|
|
self.assertIn("file1.ext", nti)
|
|
|
|
self.assertIn("file2.ext", nti)
|
2022-09-09 11:41:27 +02:00
|
|
|
self.assertIn(filename, nti)
|
2019-08-26 23:10:12 +02:00
|
|
|
|
|
|
|
os.unlink(pp.zfile.filename)
|
|
|
|
|
|
|
|
def test_zip_write_mock(self):
|
|
|
|
|
|
|
|
def side_effect(_, name):
|
|
|
|
pp.zfile.NameToInfo.add(name)
|
|
|
|
|
|
|
|
pp = self._create()
|
|
|
|
pp.zfile = Mock()
|
|
|
|
pp.zfile.NameToInfo = set()
|
|
|
|
pp.zfile.write.side_effect = side_effect
|
|
|
|
|
|
|
|
# write 3 files
|
|
|
|
for i in range(3):
|
|
|
|
self.pathfmt.temppath = self.pathfmt.realdirectory + "file.ext"
|
|
|
|
self.pathfmt.filename = "file{}.ext".format(i)
|
2020-11-18 17:11:55 +01:00
|
|
|
self._trigger()
|
2019-08-26 23:10:12 +02:00
|
|
|
|
2020-11-18 17:11:55 +01:00
|
|
|
# write the last file a second time (should be skipped)
|
|
|
|
self._trigger()
|
2019-08-26 23:10:12 +02:00
|
|
|
|
2020-11-18 17:11:55 +01:00
|
|
|
# close file
|
2023-08-10 19:46:37 +02:00
|
|
|
self._trigger(("finalize",))
|
2019-08-26 23:10:12 +02:00
|
|
|
|
|
|
|
self.assertEqual(pp.zfile.write.call_count, 3)
|
|
|
|
for call in pp.zfile.write.call_args_list:
|
|
|
|
args, kwargs = call
|
|
|
|
self.assertEqual(len(args), 2)
|
|
|
|
self.assertEqual(len(kwargs), 0)
|
|
|
|
self.assertEqual(args[0], self.pathfmt.temppath)
|
|
|
|
self.assertRegex(args[1], r"file\d\.ext")
|
|
|
|
self.assertEqual(pp.zfile.close.call_count, 1)
|
2019-08-12 21:46:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
unittest.main()
|