From beb7938dc60474dad96052bf785eace533d003dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Fri, 27 Dec 2024 14:34:16 +0100 Subject: [PATCH] [tests] Add unit tests for the `rootFolder` variables --- tests/nodes/test/RelativePaths.py | 24 +++ tests/test_nodeRootFolders.py | 281 ++++++++++++++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 tests/nodes/test/RelativePaths.py create mode 100644 tests/test_nodeRootFolders.py diff --git a/tests/nodes/test/RelativePaths.py b/tests/nodes/test/RelativePaths.py new file mode 100644 index 0000000000..e5dd701120 --- /dev/null +++ b/tests/nodes/test/RelativePaths.py @@ -0,0 +1,24 @@ +from meshroom.core import desc + +class RelativePaths(desc.Node): + documentation = "Test node with filepaths that are set relatively to some variables." + inputs = [ + desc.File( + name="relativePathInput", + label="Relative Input File", + description="Relative path to the input file.", + value="${NODE_SOURCECODE_FOLDER}" + "/input.txt", + ), + ] + + outputs = [ + desc.File( + name="output", + label="Output", + description="Path to the output file.", + value="${NODE_CACHE_FOLDER}" + "file.out", + ), + ] + + def processChunk(self, chunk): + pass diff --git a/tests/test_nodeRootFolders.py b/tests/test_nodeRootFolders.py new file mode 100644 index 0000000000..1a244c30dd --- /dev/null +++ b/tests/test_nodeRootFolders.py @@ -0,0 +1,281 @@ +# coding:utf-8 + +from inspect import getfile +import os +from pathlib import Path +import pytest +from tempfile import mktemp, gettempdir + +from meshroom.core.exception import GraphCompatibilityError +from meshroom.core.graph import Graph, loadGraph +from meshroom.core import desc, loadAllNodes, registerNodeType, unregisterNodeType +from meshroom.core.node import Node +import meshroom.core + + +class RelativePaths(desc.Node): + """ + Local "RelativePaths" node, with a different source code folder as the one in nodes/test. + """ + documentation = "Test node with filepaths that are set relatively to some variables." + inputs = [ + desc.File( + name="relativePathInput", + label="Relative Input File", + description="Relative path to the input file.", + value="${NODE_SOURCECODE_FOLDER}" + "/input.txt", + ), + ] + + outputs = [ + desc.File( + name="output", + label="Output", + description="Path to the output file.", + value="${NODE_CACHE_FOLDER}" + "file.out", + ), + ] + + def processChunk(self, chunk): + pass + + +class RelativePathsV2(desc.Node): + """ + Local "RelativePaths" node, with a different source code folder as the one in nodes/test. + """ + documentation = "Test node with filepaths that are set relatively to some variables." + inputs = [ + desc.File( + name="relativePathInput2", + label="Relative Input File", + description="Relative path to the input file.", + value="${NODE_SOURCECODE_FOLDER}" + "/input.txt", + ), + ] + + outputs = [ + desc.File( + name="output", + label="Output", + description="Path to the output file.", + value="${NODE_CACHE_FOLDER}" + "file.out", + ), + ] + + def processChunk(self, chunk): + pass + + +class FakeRelativePaths(desc.Node): + documentation = """ + Test node with filepaths that are set relatively to some variables that do not exist. + """ + inputs = [ + desc.File( + name="relativePathInput", + label="Relative Input File", + description="Relative path to the input file.", + value="${NODE_UNEXISTING_FOLDER}" + "/input.txt", + ), + ] + + outputs = [ + desc.File( + name="output", + label="Output", + description="Path to the output file.", + value="${NODE_RANDOM_FOLDER}" + "file.out", + ) + ] + + def processChunk(self, chunk): + pass + + +def test_registerSameNodeWithDifferentLocations(): + """ + Check that the nodes with the same description but registered at different locations + have different evaluations for the NODE_SOURCECODE_FOLDER value. + """ + loadAllNodes(os.path.join(os.path.dirname(__file__), "nodes")) + assert "RelativePaths" in meshroom.core.nodesDesc + + # Node loaded from "nodes/test" + n1 = Node("RelativePaths") + sourceFolderNode1 = n1.attribute("relativePathInput").getEvalValue() + assert sourceFolderNode1 == \ + Path(getfile(meshroom.core.nodesDesc["RelativePaths"])).parent.resolve().as_posix() + \ + "/input.txt" + assert sourceFolderNode1 == n1.sourceCodeFolder + "/input.txt" + assert Path(sourceFolderNode1).parent.resolve().as_posix() == \ + n1.rootFolder["NODE_SOURCECODE_FOLDER"] + + # Unregister that node and replace it with the one from this file + unregisterNodeType(RelativePaths) + assert "RelativePaths" not in meshroom.core.nodesDesc + + registerNodeType(RelativePaths) + assert "RelativePaths" in meshroom.core.nodesDesc + + n2 = Node("RelativePaths") + sourceFolderNode2 = n2.attribute("relativePathInput").getEvalValue() + assert sourceFolderNode2 == \ + Path(getfile(meshroom.core.nodesDesc["RelativePaths"])).parent.resolve().as_posix() + \ + "/input.txt" + assert sourceFolderNode2 == n2.sourceCodeFolder + "/input.txt" + assert Path(sourceFolderNode2).parent.resolve().as_posix() == \ + n2.rootFolder["NODE_SOURCECODE_FOLDER"] + + assert sourceFolderNode1 is not sourceFolderNode2 + unregisterNodeType(RelativePaths) + + +def test_reloadGraphWithDifferentNodeLocations(): + """ + Save a Graph with a node description registered at a specific location, unregister that node + type, and register the same description from a different location. + """ + loadAllNodes(os.path.join(os.path.dirname(__file__), "nodes")) + assert "RelativePaths" in meshroom.core.nodesDesc + + graph = Graph('') + node = graph.addNewNode("RelativePaths") + name = node.name + + # Save graph in a file + filename = mktemp() + graph.save(filename) + + sourceCodeFolderNode = node.attribute("relativePathInput").getEvalValue() + assert sourceCodeFolderNode == node.rootFolder["NODE_SOURCECODE_FOLDER"] + "/input.txt" + + # Output attribute, already evaluated upon the node's creation + cacheFolderNode = node.attribute("output").value + + node._buildCmdVars() + assert desc.Node.internalFolder == node.rootFolder["NODE_CACHE_FOLDER"] + assert cacheFolderNode == desc.Node.internalFolder.format(**node._cmdVars) + "file.out" + + # Delete the current graph + del graph + + # Unregister that node and replace it with the one from this file + unregisterNodeType(meshroom.core.nodesDesc["RelativePaths"]) + assert "RelativePaths" not in meshroom.core.nodesDesc + + registerNodeType(RelativePaths) + assert "RelativePaths" in meshroom.core.nodesDesc + + # Reload the graph + graph = loadGraph(filename) + assert graph + node = graph.node(name) + assert node.nodeType == "RelativePaths" + + # Check that the relative path is different for the input + assert node.attribute("relativePathInput").getEvalValue() != sourceCodeFolderNode + + # Check that it is the same for the cache + assert node.attribute("output").value == cacheFolderNode + + os.remove(filename) + unregisterNodeType(RelativePaths) + + +def test_reloadGraphWithCompatibilityIssue(graphSavedOnDisk): + registerNodeType(RelativePaths) + + graph: Graph = graphSavedOnDisk + node = graph.addNewNode(RelativePaths.__name__) + graph.save() + + # Replace saved node description by V2 + meshroom.core.nodesDesc[RelativePaths.__name__] = RelativePathsV2 + + # Check that the compatibility issue is raised + with pytest.raises(GraphCompatibilityError): + loadGraph(graph.filepath, strictCompatibility=True) + + compatibilityGraph = loadGraph(graph.filepath) + compatibilityNode = compatibilityGraph.node(node.name) + assert compatibilityNode.isCompatibilityNode + + # Check that the rootFolder content for CompatibilityNodes checks out + # rootFolder for CompatibilityNodes only contains "NODE_CACHE_FOLDER" + assert len(compatibilityNode.rootFolder) != len(node.rootFolder) + assert "NODE_CACHE_FOLDER" in compatibilityNode.rootFolder + + # Input values should be the same, but the evaluation should differ as rootFolder for + # CompatibilityNodes do not include "NODE_SOURCECODE_FOLDER" + assert compatibilityNode.attribute("relativePathInput").value == \ + node.attribute("relativePathInput").value + assert compatibilityNode.attribute("relativePathInput").getEvalValue() != \ + node.attribute("relativePathInput").getEvalValue() + + # Output values are evaluated straight away: they should be the same since "NODE_CACHE_FOLDER" + # is always available + assert compatibilityNode.attribute("output").value == \ + compatibilityNode.attribute("output").getEvalValue() == \ + node.attribute("output").value + + unregisterNodeType(RelativePaths) + + +class TestRootFolderContent(): + """ Class to test changes on the root folders' values. """ + @classmethod + def setup_class(cls): + """ Register nodes upon class' creation. """ + registerNodeType(RelativePaths) + registerNodeType(FakeRelativePaths) + + @classmethod + def teardown_class(cls): + """ Unregister nodes upon class' destruction. """ + unregisterNodeType(RelativePaths) + unregisterNodeType(FakeRelativePaths) + + def test_updateRootFolders(self): + """ + Check that root folder variables can be added and removed. + """ + assert "RelativePaths" in meshroom.core.nodesDesc + + node = Node("RelativePaths") + assert len(node.rootFolder) == 2 + + # Add a new element in the list of root folders + node.rootFolder.update({"NODE_TEST_FOLDER": gettempdir()}) + assert len(node.rootFolder) == 3 + + attr = node.attribute("relativePathInput") + + sourceCodeFolder = attr.getEvalValue() + attr.value = "${NODE_TEST_FOLDER}" + "/input.txt" + assert attr.getEvalValue() == gettempdir() + "/input.txt" + assert attr.getEvalValue() != sourceCodeFolder + + # Remove the extra element in the list of root folders + node.rootFolder.pop("NODE_TEST_FOLDER", None) + assert len(node.rootFolder) == 2 + + assert attr.getEvalValue() != gettempdir() + "/input.txt" + # Unevaluated value as the variable does not exist + assert attr.getEvalValue() == attr.value + + attr.value = attr.defaultValue() + assert attr.getEvalValue() == sourceCodeFolder + + + def test_nonExistingRootFolder(self): + """ + Check that a variable that does not correspond to any root folder is not evaluated. + """ + node = Node("FakeRelativePaths") + + # The root folder variable cannot be evaluated as it does not exist: it is not substituted + assert node.attribute("relativePathInput").getEvalValue() == \ + node.attribute("relativePathInput").value + assert node.attribute("output").getEvalValue() == \ + node.attribute("output").value == "${NODE_RANDOM_FOLDER}file.out"