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:
Ondřej Samohel 2023-04-13 15:27:21 +02:00 committed by GitHub
commit 14944b943b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 261 additions and 55 deletions

View file

@ -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,)

View file

@ -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,)

View 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"

View file

@ -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()