diff --git a/cato/variable_processing/variable_predefinition.py b/cato/variable_processing/variable_predefinition.py index e4615a86..384404e7 100644 --- a/cato/variable_processing/variable_predefinition.py +++ b/cato/variable_processing/variable_predefinition.py @@ -12,35 +12,35 @@ class VariablePredefinition: name="Autodesk Maya", variables={ "maya_version": "2020", - "maya_location": r"C:\Program Files\Autodesk\Maya{@maya_version}", + "maya_location": r"C:\Program Files\Autodesk\Maya{{maya_version}}", }, ) VRAY_FOR_MAYA_PREDEFINITION = VariablePredefinition( name="V-Ray for Maya", variables={ - "vray_scene_file": "{@test_resources}/scene.vrscene", - "vray_render_command": r'"{@maya_location}\vray\bin\vray.exe" -sceneFile={@vray_scene_file} -imgFile={@image_output_exr} -progressIncrement=1 -display=0', - "vray_gpu_render_command": r'"{@maya_location}\vray\bin\vray.exe" -sceneFile={@vray_scene_file} -imgFile={@image_output_exr} -progressIncrement=1 -display=0 -rtengine=5', + "vray_scene_file": "{{test_resources}}/scene.vrscene", + "vray_render_command": r'"{{maya_location}}\vray\bin\vray.exe" -sceneFile={{vray_scene_file}} -imgFile={{image_output_exr}} -progressIncrement=1 -display=0', + "vray_gpu_render_command": r'"{{maya_location}}\vray\bin\vray.exe" -sceneFile={{vray_scene_file}} -imgFile={{image_output_exr}} -progressIncrement=1 -display=0 -rtengine=5', }, ) MTOA_PREDEFINITION = VariablePredefinition( name="Arnold for Maya", variables={ - "arnold_scene_file": "{@test_resources}/{@test_name}.ass", - "arnold_location": r"C:\Program Files\Autodesk\Arnold\maya{@maya_version}", - "arnold_render_command": r'"{@arnold_location}\bin\kick" -i {@test_resources}/{@test_name}.ass -o {@image_output_png} -of exr -dw -v 2', + "arnold_scene_file": "{{test_resources}}/{{test_name}}.ass", + "arnold_location": r"C:\Program Files\Autodesk\Arnold\maya{{maya_version}}", + "arnold_render_command": r'"{{arnold_location}}\bin\kick" -i {{test_resources}}/{{test_name}}.ass -o {{image_output_png}} -of exr -dw -v 2', }, ) BLENDER_PREDEFINITION = VariablePredefinition( name="Blender", variables={ - "blender_scene_file": "{@test_resources}/{@test_name}.blend", + "blender_scene_file": "{{test_resources}}/{{test_name}}.blend", "blender_version": "2.90", - "blender_location": r"C:\Program Files\Blender Foundation\Blender {@blender_version}", - "blender_render_command": r'"{@blender_location}\blender.exe" -b {@blender_scene_file} -o {@image_output_folder}/{@test_name} -F PNG -f {@frame}', + "blender_location": r"C:\Program Files\Blender Foundation\Blender {{blender_version}}", + "blender_render_command": r'"{{blender_location}}\blender.exe" -b {{blender_scene_file}} -o {{image_output_folder}}/{{test_name}} -F PNG -f {{frame}}', }, ) diff --git a/cato/variable_processing/variable_processor.py b/cato/variable_processing/variable_processor.py index 4e92cd8b..c6b9fc42 100644 --- a/cato/variable_processing/variable_processor.py +++ b/cato/variable_processing/variable_processor.py @@ -1,10 +1,22 @@ from typing import Dict, Optional, List +import jinja2 + from cato_common.domain.config import RunConfig from cato_common.domain.test import Test from cato_common.domain.test_suite import TestSuite from cato.variable_processing.variable_predefinition import VariablePredefinition -from cato.vendor import lucidity + +import logging + +logger = logging.getLogger(__name__) + + +class VariableSubstitutionRecursionDepthExceeded(Exception): + def __init__(self, max_recursion_depth: int, template: str) -> None: + super(VariableSubstitutionRecursionDepthExceeded, self).__init__( + f"Max recursions ({max_recursion_depth}) exceeded for template '{template}'." + ) class VariableProcessor: @@ -21,19 +33,19 @@ def evaluate_variables( "suite_name": current_suite.name, "config_path": config.resource_path, "output_folder": config.output_folder, - "suite_resources": "{@config_path}/{@suite_name}", - "test_resources": "{@config_path}/{@suite_name}/{@test_name}", - "reference_image_no_extension": "{@test_resources}/reference", - "reference_image_png": "{@reference_image_no_extension}.png", - "reference_image_exr": "{@reference_image_no_extension}.exr", - "reference_image_jpg": "{@reference_image_no_extension}.jpg", - "reference_image_tif": "{@reference_image_no_extension}.tif", - "image_output_folder": "{@output_folder}/result/{@suite_name}/{@test_name}", - "image_output_no_extension": "{@image_output_folder}/{@test_name}", - "image_output_png": "{@image_output_no_extension}.png", - "image_output_exr": "{@image_output_no_extension}.exr", - "image_output_jpg": "{@image_output_no_extension}.jpg", - "image_output_tif": "{@image_output_no_extension}.tif", + "suite_resources": "{{config_path}}/{{suite_name}}", + "test_resources": "{{config_path}}/{{suite_name}}/{{test_name}}", + "reference_image_no_extension": "{{test_resources}}/reference", + "reference_image_png": "{{reference_image_no_extension}}.png", + "reference_image_exr": "{{reference_image_no_extension}}.exr", + "reference_image_jpg": "{{reference_image_no_extension}}.jpg", + "reference_image_tif": "{{reference_image_no_extension}}.tif", + "image_output_folder": "{{output_folder}}/result/{{suite_name}}/{{test_name}}", + "image_output_no_extension": "{{image_output_folder}}/{{test_name}}", + "image_output_png": "{{image_output_no_extension}}.png", + "image_output_exr": "{{image_output_no_extension}}.exr", + "image_output_jpg": "{{image_output_no_extension}}.jpg", + "image_output_tif": "{{image_output_no_extension}}.tif", } if predefinitions: for predefinition in predefinitions: @@ -42,25 +54,24 @@ def evaluate_variables( default_variables.update(current_suite.variables) default_variables.update(test.variables) - templates: Dict[str, lucidity.Template] = {} - for name, content in default_variables.items(): - template = lucidity.Template(name, content) - template.template_resolver = templates - templates[name] = template - formatted = {} - for name, template in templates.items(): - formatted[name] = template.format({}) + for name, template in default_variables.items(): + formatted[name] = self._render_recursive(template, default_variables) return formatted def format_command(self, command: str, variables: Dict[str, str]) -> str: - templates: Dict[str, lucidity.Template] = {} - for name, template_content in variables.items(): - template = lucidity.Template(name, template_content) - template.template_resolver = templates - templates[name] = template - command_template = lucidity.Template(command, command) - command_template.template_resolver = templates - return command_template.format(variables) + return self._render_recursive(command, variables) + + def _render_recursive(self, template: str, variables: Dict[str, str]) -> str: + previous = template + max_recursion_depth = 50 + counter = 0 + while counter < max_recursion_depth: + counter += 1 + curr = jinja2.Template(previous).render(**variables) + if curr == previous: + return curr + previous = curr + raise VariableSubstitutionRecursionDepthExceeded(max_recursion_depth, template) diff --git a/cato_common/config/config_template_generator.py b/cato_common/config/config_template_generator.py index 27e099e6..f3553fc8 100644 --- a/cato_common/config/config_template_generator.py +++ b/cato_common/config/config_template_generator.py @@ -9,24 +9,24 @@ "tests": [ {"name": "My_first_test", "command": "python --version"}, { - "name": "use {@test_resources} to get test resource folder", - "command": "python --version {@test_resources}", + "name": "use {{test_resources}} to get test resource folder", + "command": "python --version {{test_resources}}", }, { - "name": "use {@image_output_png} to get path where to put image", - "command": "python --version {@image_output_png}", + "name": "use {{image_output_png}} to get path where to put image", + "command": "python --version {{image_output_png}}", }, { "name": "use own variables in command", "variables": {"frame": "7"}, - "command": "python --version {@frame}", + "command": "python --version {{frame}}", }, { "name": "define own image output if renderer does not support settings full path", "variables": { - "image_output": "{@image_output_folder}/my_render.png" + "image_output": "{{image_output_folder}}/my_render.png" }, - "command": "python --version {@image_output}", + "command": "python --version {{image_output}}", }, ], } diff --git a/example_suite/cato.json b/example_suite/cato.json index 34f24f2d..c659e56f 100644 --- a/example_suite/cato.json +++ b/example_suite/cato.json @@ -1,7 +1,7 @@ { "projectName": "TestSpheres", "variables": { - "copy_image_command": "python -c \"import shutil;shutil.copy('{@reference_image_no_extension}{@extension}','{@image_output_no_extension}{@extension}')\"" + "copy_image_command": "python -c \"import shutil;shutil.copy('{{reference_image_no_extension}}{{extension}}','{{image_output_no_extension}}{{extension}}')\"" }, "suites": [ { @@ -9,19 +9,19 @@ "tests": [ { "name": "exr_singlechannel_16_bit", - "command": "{@copy_image_command}" + "command": "{{copy_image_command}}" }, { "name": "exr_singlechannel_32_bit", - "command": "{@copy_image_command}" + "command": "{{copy_image_command}}" }, { "name": "exr_multichannel_16_bit_1080p", - "command": "{@copy_image_command}" + "command": "{{copy_image_command}}" }, { "name": "exr_multichannel_16_bit", - "command": "{@copy_image_command}" + "command": "{{copy_image_command}}" } ], "variables": { @@ -33,7 +33,7 @@ "tests": [ { "name": "jpeg", - "command": "{@copy_image_command}" + "command": "{{copy_image_command}}" } ], "variables": { @@ -45,11 +45,11 @@ "tests": [ { "name": "png_8_bit", - "command": "{@copy_image_command}" + "command": "{{copy_image_command}}" }, { "name": "png_16_bit", - "command": "{@copy_image_command}" + "command": "{{copy_image_command}}" } ], "variables": { @@ -61,11 +61,11 @@ "tests": [ { "name": "tiff_8_bit", - "command": "{@copy_image_command}" + "command": "{{copy_image_command}}" }, { "name": "tiff_16_bit", - "command": "{@copy_image_command}" + "command": "{{copy_image_command}}" } ], "variables": { diff --git a/tests/integrationtests/cato_cmd/test_cato_cli.py b/tests/integrationtests/cato_cmd/test_cato_cli.py index a9c64188..98ac509c 100644 --- a/tests/integrationtests/cato_cmd/test_cato_cli.py +++ b/tests/integrationtests/cato_cmd/test_cato_cli.py @@ -15,7 +15,7 @@ def run_config(tmp_path, test_resource_provider, object_mapper): test1 = Test( name="PythonOutputVersion", # copy image script - command=f"python {os.path.join(os.path.dirname(__file__), 'copy_image.py')} {test_resource_provider.resource_by_name('test_image_black.png')} {{@image_output_png}}", + command=f"python {os.path.join(os.path.dirname(__file__), 'copy_image.py')} {test_resource_provider.resource_by_name('test_image_black.png')} {{{{image_output_png}}}}", variables={ "reference_image_png": test_resource_provider.resource_by_name( "test_image_black.png" diff --git a/tests/resources/cato_cmd_integ_tests/cato.json b/tests/resources/cato_cmd_integ_tests/cato.json index a09c72f2..7372f6ab 100644 --- a/tests/resources/cato_cmd_integ_tests/cato.json +++ b/tests/resources/cato_cmd_integ_tests/cato.json @@ -6,11 +6,11 @@ "tests": [ { "name": "write_white_image", - "command": "{@python_exe} {@suite_resources}/write.py \"{@image_output_png}\" white" + "command": "{{python_exe}} {{suite_resources}}/write.py \"{{image_output_png}}\" white" }, { "name": "write_black_image", - "command": "{@python_exe} {@suite_resources}/write.py \"{@image_output_png}\" black" + "command": "{{python_exe}} {{suite_resources}}/write.py \"{{image_output_png}}\" black" } ] } diff --git a/tests/unittests/cato/test_test_runner.py b/tests/unittests/cato/test_test_runner.py index 94038078..3343f617 100644 --- a/tests/unittests/cato/test_test_runner.py +++ b/tests/unittests/cato/test_test_runner.py @@ -109,7 +109,7 @@ def test_should_report_test_start( def test_should_replace_placeholder(self, test_context): test = Test( name="my_first_test", - command="crayg -s {test_resources}/test.json -o {image_output_png}", + command="crayg -s {{test_resources}}/test.json -o {{image_output_png}}", variables={}, comparison_settings=ComparisonSettings.default(), ) diff --git a/tests/unittests/cato/test_variable_processor.py b/tests/unittests/cato/test_variable_processor.py index 63ec01aa..6d1008c4 100644 --- a/tests/unittests/cato/test_variable_processor.py +++ b/tests/unittests/cato/test_variable_processor.py @@ -1,9 +1,14 @@ +import pytest + from cato_common.domain.comparison_settings import ComparisonSettings from cato_common.domain.config import RunConfig from cato_common.domain.test import Test from cato_common.domain.test_suite import TestSuite from cato.variable_processing.variable_predefinition import PREDEFINITIONS -from cato.variable_processing.variable_processor import VariableProcessor +from cato.variable_processing.variable_processor import ( + VariableProcessor, + VariableSubstitutionRecursionDepthExceeded, +) REFERENCE_IMAGE_PNG = "config_path/my_test_suite/test_name/reference.png" REFERENCE_IMAGE_NO_EXTENSION = "config_path/my_test_suite/test_name/reference" @@ -74,7 +79,7 @@ def test_evaluate_variables_custom_image_output(): "test_command", variables={ "frame": "7", - "image_output_png": "{@image_output_folder}/test_name{@frame}.png", + "image_output_png": "{{image_output_folder}}/test_name{{frame}}.png", }, comparison_settings=ComparisonSettings.default(), ) @@ -112,7 +117,7 @@ def test_format_command(): variable_processor = VariableProcessor() command = variable_processor.format_command( - "{@image_output_png}", {"image_output_png": "test"} + "{{image_output_png}}", {"image_output_png": "test"} ) assert command == "test" @@ -355,7 +360,21 @@ def test_evaluate_variables_maya_predefinition(): } -# maya -# vray -# arnold -# blender +def test_raise_max_recursion_exceeded_for_cyclic_variables(): + config = RunConfig( + project_name=EXAMPLE_PROJECT, + resource_path="config_path", + suites=[], + output_folder="test", + ) + suite = TestSuite(name="my_test_suite", tests=[]) + test = Test( + "test_name", + "test_command", + variables={"variable1": "{{variable2}}", "variable2": "{{variable1}}"}, + comparison_settings=ComparisonSettings.default(), + ) + variable_processor = VariableProcessor() + + with pytest.raises(VariableSubstitutionRecursionDepthExceeded): + variable_processor.evaluate_variables(config, suite, test)