mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #4698 from ynput/bugfix/OP-3951_Deadline-checking-existing-frames-fails-when-there-is-number-in-file-name
This commit is contained in:
commit
14944b943b
4 changed files with 261 additions and 55 deletions
|
|
@ -1,41 +0,0 @@
|
|||
import clique
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class ValidateSequenceFrames(pyblish.api.InstancePlugin):
|
||||
"""Ensure the sequence of frames is complete
|
||||
|
||||
The files found in the folder are checked against the frameStart and
|
||||
frameEnd of the instance. If the first or last file is not
|
||||
corresponding with the first or last frame it is flagged as invalid.
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Sequence Frames"
|
||||
families = ["render"]
|
||||
hosts = ["unreal"]
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
representations = instance.data.get("representations")
|
||||
for repr in representations:
|
||||
patterns = [clique.PATTERNS["frames"]]
|
||||
collections, remainder = clique.assemble(
|
||||
repr["files"], minimum_items=1, patterns=patterns)
|
||||
|
||||
assert not remainder, "Must not have remainder"
|
||||
assert len(collections) == 1, "Must detect single collection"
|
||||
collection = collections[0]
|
||||
frames = list(collection.indexes)
|
||||
|
||||
current_range = (frames[0], frames[-1])
|
||||
required_range = (instance.data["frameStart"],
|
||||
instance.data["frameEnd"])
|
||||
|
||||
if current_range != required_range:
|
||||
raise ValueError(f"Invalid frame range: {current_range} - "
|
||||
f"expected: {required_range}")
|
||||
|
||||
missing = collection.holes().indexes
|
||||
assert not missing, "Missing frames: %s" % (missing,)
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
import clique
|
||||
import pyblish.api
|
||||
|
||||
|
||||
|
|
@ -7,28 +11,51 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin):
|
|||
The files found in the folder are checked against the startFrame and
|
||||
endFrame of the instance. If the first or last file is not
|
||||
corresponding with the first or last frame it is flagged as invalid.
|
||||
|
||||
Used regular expression pattern handles numbers in the file names
|
||||
(eg "Main_beauty.v001.1001.exr", "Main_beauty_v001.1001.exr",
|
||||
"Main_beauty.1001.1001.exr") but not numbers behind frames (eg.
|
||||
"Main_beauty.1001.v001.exr")
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Sequence Frames"
|
||||
families = ["imagesequence"]
|
||||
hosts = ["shell"]
|
||||
families = ["imagesequence", "render"]
|
||||
hosts = ["shell", "unreal"]
|
||||
|
||||
def process(self, instance):
|
||||
representations = instance.data.get("representations")
|
||||
if not representations:
|
||||
return
|
||||
for repr in representations:
|
||||
repr_files = repr["files"]
|
||||
if isinstance(repr_files, str):
|
||||
continue
|
||||
|
||||
collection = instance[0]
|
||||
self.log.info(collection)
|
||||
ext = repr.get("ext")
|
||||
if not ext:
|
||||
_, ext = os.path.splitext(repr_files[0])
|
||||
elif not ext.startswith("."):
|
||||
ext = ".{}".format(ext)
|
||||
pattern = r"\D?(?P<index>(?P<padding>0*)\d+){}$".format(
|
||||
re.escape(ext))
|
||||
patterns = [pattern]
|
||||
|
||||
frames = list(collection.indexes)
|
||||
collections, remainder = clique.assemble(
|
||||
repr_files, minimum_items=1, patterns=patterns)
|
||||
|
||||
current_range = (frames[0], frames[-1])
|
||||
required_range = (instance.data["frameStart"],
|
||||
instance.data["frameEnd"])
|
||||
assert not remainder, "Must not have remainder"
|
||||
assert len(collections) == 1, "Must detect single collection"
|
||||
collection = collections[0]
|
||||
frames = list(collection.indexes)
|
||||
|
||||
if current_range != required_range:
|
||||
raise ValueError("Invalid frame range: {0} - "
|
||||
"expected: {1}".format(current_range,
|
||||
required_range))
|
||||
current_range = (frames[0], frames[-1])
|
||||
required_range = (instance.data["frameStart"],
|
||||
instance.data["frameEnd"])
|
||||
|
||||
missing = collection.holes().indexes
|
||||
assert not missing, "Missing frames: %s" % (missing,)
|
||||
if current_range != required_range:
|
||||
raise ValueError(f"Invalid frame range: {current_range} - "
|
||||
f"expected: {required_range}")
|
||||
|
||||
missing = collection.holes().indexes
|
||||
assert not missing, "Missing frames: %s" % (missing,)
|
||||
|
|
|
|||
36
tests/unit/openpype/conftest.py
Normal file
36
tests/unit/openpype/conftest.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""Dummy environment that allows importing Openpype modules and run
|
||||
tests in parent folder and all subfolders manually from IDE.
|
||||
|
||||
This should not get triggered if the tests are running from `runtests` as it
|
||||
is expected there that environment is handled by OP itself.
|
||||
|
||||
This environment should be enough to run simple `BaseTest` where no
|
||||
external preparation is necessary (eg. no prepared DB, no source files).
|
||||
These tests might be enough to import and run simple pyblish plugins to
|
||||
validate logic.
|
||||
|
||||
Please be aware that these tests might use values in real databases, so use
|
||||
`BaseTest` only for logic without side effects or special configuration. For
|
||||
these there is `tests.lib.testing_classes.ModuleUnitTest` which would setup
|
||||
proper test DB (but it requires `mongorestore` on the sys.path)
|
||||
|
||||
If pyblish plugins require any host dependent communication, it would need
|
||||
to be mocked.
|
||||
|
||||
This setting of env vars is necessary to run before any imports of OP code!
|
||||
(This is why it is in `conftest.py` file.)
|
||||
If your test requires any additional env var, copy this file to folder of your
|
||||
test, it should only that folder.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
|
||||
if not os.environ.get("IS_TEST"): # running tests from cmd or CI
|
||||
os.environ["OPENPYPE_MONGO"] = "mongodb://localhost:27017"
|
||||
os.environ["AVALON_DB"] = "avalon"
|
||||
os.environ["OPENPYPE_DATABASE_NAME"] = "openpype"
|
||||
os.environ["AVALON_TIMEOUT"] = '3000'
|
||||
os.environ["OPENPYPE_DEBUG"] = "1"
|
||||
os.environ["AVALON_ASSET"] = "test_asset"
|
||||
os.environ["AVALON_PROJECT"] = "test_project"
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
|
||||
|
||||
"""Test Publish_plugins pipeline publish modul, tests API methods
|
||||
|
||||
File:
|
||||
creates temporary directory and downloads .zip file from GDrive
|
||||
unzips .zip file
|
||||
uses content of .zip file (MongoDB's dumps) to import to new databases
|
||||
with use of 'monkeypatch_session' modifies required env vars
|
||||
temporarily
|
||||
runs battery of tests checking that site operation for Sync Server
|
||||
module are working
|
||||
removes temporary folder
|
||||
removes temporary databases (?)
|
||||
"""
|
||||
import pytest
|
||||
import logging
|
||||
|
||||
from pyblish.api import Instance as PyblishInstance
|
||||
|
||||
from tests.lib.testing_classes import BaseTest
|
||||
from openpype.plugins.publish.validate_sequence_frames import (
|
||||
ValidateSequenceFrames
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestValidateSequenceFrames(BaseTest):
|
||||
""" Testing ValidateSequenceFrames plugin
|
||||
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def instance(self):
|
||||
|
||||
class Instance(PyblishInstance):
|
||||
data = {
|
||||
"frameStart": 1001,
|
||||
"frameEnd": 1002,
|
||||
"representations": []
|
||||
}
|
||||
yield Instance
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def plugin(self):
|
||||
plugin = ValidateSequenceFrames()
|
||||
plugin.log = log
|
||||
|
||||
yield plugin
|
||||
|
||||
def test_validate_sequence_frames_single_frame(self, instance, plugin):
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": "Main_beauty.1001.exr",
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
instance.data["frameEnd"] = 1001
|
||||
|
||||
plugin.process(instance)
|
||||
|
||||
@pytest.mark.parametrize("files",
|
||||
[
|
||||
["Main_beauty.v001.1001.exr",
|
||||
"Main_beauty.v001.1002.exr"],
|
||||
["Main_beauty_v001.1001.exr",
|
||||
"Main_beauty_v001.1002.exr"],
|
||||
["Main_beauty.1001.1001.exr",
|
||||
"Main_beauty.1001.1002.exr"],
|
||||
["Main_beauty_v001_1001.exr",
|
||||
"Main_beauty_v001_1002.exr"]])
|
||||
def test_validate_sequence_frames_name(self, instance,
|
||||
plugin, files):
|
||||
# tests for names with number inside, caused clique failure before
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": files,
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
|
||||
plugin.process(instance)
|
||||
|
||||
@pytest.mark.parametrize("files",
|
||||
[["Main_beauty.1001.v001.exr",
|
||||
"Main_beauty.1002.v001.exr"]])
|
||||
def test_validate_sequence_frames_wrong_name(self, instance,
|
||||
plugin, files):
|
||||
# tests for names with number inside, caused clique failure before
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": files,
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
plugin.process(instance)
|
||||
assert ("Must detect single collection" in
|
||||
str(excinfo.value))
|
||||
|
||||
@pytest.mark.parametrize("files",
|
||||
[["Main_beauty.v001.1001.ass.gz",
|
||||
"Main_beauty.v001.1002.ass.gz"]])
|
||||
def test_validate_sequence_frames_possible_wrong_name(
|
||||
self, instance, plugin, files):
|
||||
# currently pattern fails on extensions with dots
|
||||
representations = [
|
||||
{
|
||||
"files": files,
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
plugin.process(instance)
|
||||
assert ("Must not have remainder" in
|
||||
str(excinfo.value))
|
||||
|
||||
@pytest.mark.parametrize("files",
|
||||
[["Main_beauty.v001.1001.ass.gz",
|
||||
"Main_beauty.v001.1002.ass.gz"]])
|
||||
def test_validate_sequence_frames__correct_ext(
|
||||
self, instance, plugin, files):
|
||||
# currently pattern fails on extensions with dots
|
||||
representations = [
|
||||
{
|
||||
"ext": "ass.gz",
|
||||
"files": files,
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
|
||||
plugin.process(instance)
|
||||
|
||||
def test_validate_sequence_frames_multi_frame(self, instance, plugin):
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": ["Main_beauty.1001.exr", "Main_beauty.1002.exr",
|
||||
"Main_beauty.1003.exr"]
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
instance.data["frameEnd"] = 1003
|
||||
|
||||
plugin.process(instance)
|
||||
|
||||
def test_validate_sequence_frames_multi_frame_missing(self, instance,
|
||||
plugin):
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": ["Main_beauty.1001.exr", "Main_beauty.1002.exr"]
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
instance.data["frameEnd"] = 1003
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
plugin.process(instance)
|
||||
assert ("Invalid frame range: (1001, 1002) - expected: (1001, 1003)" in
|
||||
str(excinfo.value))
|
||||
|
||||
def test_validate_sequence_frames_multi_frame_hole(self, instance, plugin):
|
||||
representations = [
|
||||
{
|
||||
"ext": "exr",
|
||||
"files": ["Main_beauty.1001.exr", "Main_beauty.1003.exr"]
|
||||
}
|
||||
]
|
||||
instance.data["representations"] = representations
|
||||
instance.data["frameEnd"] = 1003
|
||||
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
plugin.process(instance)
|
||||
assert ("Missing frames: [1002]" in str(excinfo.value))
|
||||
|
||||
|
||||
test_case = TestValidateSequenceFrames()
|
||||
Loading…
Add table
Add a link
Reference in a new issue