TestDefinitionContainer.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import json # To check files for unnecessarily overridden properties.
  4. import os
  5. import os.path
  6. import pytest #This module contains automated tests.
  7. from typing import Any, Dict
  8. import uuid
  9. import UM.Settings.ContainerRegistry #To create empty instance containers.
  10. import UM.Settings.ContainerStack #To set the container registry the container stacks use.
  11. from UM.Settings.DefinitionContainer import DefinitionContainer #To check against the class of DefinitionContainer.
  12. from UM.Resources import Resources
  13. Resources.addSearchPath(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "resources")))
  14. machine_filepaths = sorted(os.listdir(os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions")))
  15. all_meshes = os.listdir(os.path.join(os.path.dirname(__file__), "..", "..", "resources", "meshes"))
  16. all_images = os.listdir(os.path.join(os.path.dirname(__file__), "..", "..", "resources", "images"))
  17. @pytest.fixture
  18. def definition_container():
  19. uid = str(uuid.uuid4())
  20. result = UM.Settings.DefinitionContainer.DefinitionContainer(uid)
  21. assert result.getId() == uid
  22. return result
  23. ## Tests all definition containers
  24. @pytest.mark.parametrize("file_name", machine_filepaths)
  25. def test_validateMachineDefinitionContainer(file_name, definition_container):
  26. if file_name == "fdmprinter.def.json" or file_name == "fdmextruder.def.json":
  27. return # Stop checking, these are root files.
  28. definition_path = os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions")
  29. assertIsDefinitionValid(definition_container, definition_path, file_name)
  30. def assertIsDefinitionValid(definition_container, path, file_name):
  31. with open(os.path.join(path, file_name), encoding = "utf-8") as data:
  32. json = data.read()
  33. parser, is_valid = definition_container.readAndValidateSerialized(json)
  34. assert is_valid #The definition has invalid JSON structure.
  35. metadata = DefinitionContainer.deserializeMetadata(json, "whatever")
  36. # If the definition defines a platform file, it should be in /resources/meshes/
  37. if "platform" in metadata[0]:
  38. assert metadata[0]["platform"] in all_meshes
  39. if "platform_texture" in metadata[0]:
  40. assert metadata[0]["platform_texture"] in all_images
  41. ## Tests whether setting values are not being hidden by parent containers.
  42. #
  43. # When a definition container defines a "default_value" but inherits from a
  44. # definition that defines a "value", the "default_value" is ineffective. This
  45. # test fails on those things.
  46. @pytest.mark.parametrize("file_name", machine_filepaths)
  47. def test_validateOverridingDefaultValue(file_name: str):
  48. definition_path = os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions", file_name)
  49. with open(definition_path, encoding = "utf-8") as f:
  50. doc = json.load(f)
  51. if "inherits" not in doc:
  52. return # We only want to check for documents where the inheritance overrides the children. If there's no inheritance, this can't happen so it's fine.
  53. if "overrides" not in doc:
  54. return # No settings are being overridden. No need to check anything.
  55. parent_settings = getInheritedSettings(doc["inherits"])
  56. for key, val in doc["overrides"].items():
  57. if "value" in parent_settings[key]:
  58. assert "default_value" not in val, "Unnecessary default_value for {key} in {file_name}".format(key = key, file_name = file_name) # If there is a value in the parent settings, then the default_value is not effective.
  59. ## Get all settings and their properties from a definition we're inheriting
  60. # from.
  61. # \param definition_id The definition we're inheriting from.
  62. # \return A dictionary of settings by key. Each setting is a dictionary of
  63. # properties.
  64. def getInheritedSettings(definition_id: str) -> Dict[str, Any]:
  65. definition_path = os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions", definition_id + ".def.json")
  66. with open(definition_path, encoding = "utf-8") as f:
  67. doc = json.load(f)
  68. result = {}
  69. if "inherits" in doc: # Recursive inheritance.
  70. result.update(getInheritedSettings(doc["inherits"]))
  71. if "settings" in doc:
  72. result.update(flattenSettings(doc["settings"]))
  73. if "overrides" in doc:
  74. result = merge_dicts(result, doc["overrides"])
  75. return result
  76. ## Put all settings in the main dictionary rather than in children dicts.
  77. # \param settings Nested settings. The keys are the setting IDs. The values
  78. # are dictionaries of properties per setting, including the "children"
  79. # property.
  80. # \return A dictionary of settings by key. Each setting is a dictionary of
  81. # properties.
  82. def flattenSettings(settings: Dict[str, Any]) -> Dict[str, Any]:
  83. result = {}
  84. for entry, contents in settings.items():
  85. if "children" in contents:
  86. result.update(flattenSettings(contents["children"]))
  87. del contents["children"]
  88. result[entry] = contents
  89. return result
  90. ## Make one dictionary override the other. Nested dictionaries override each
  91. # other in the same way.
  92. # \param base A dictionary of settings that will get overridden by the other.
  93. # \param overrides A dictionary of settings that will override the other.
  94. # \return Combined setting data.
  95. def merge_dicts(base: Dict[str, Any], overrides: Dict[str, Any]) -> Dict[str, Any]:
  96. result = {}
  97. result.update(base)
  98. for key, val in overrides.items():
  99. if key not in result:
  100. result[key] = val
  101. continue
  102. if isinstance(result[key], dict) and isinstance(val, dict):
  103. result[key] = merge_dicts(result[key], val)
  104. else:
  105. result[key] = val
  106. return result
  107. ## Verifies that definition contains don't have an ID field.
  108. #
  109. # ID fields are legacy. They should not be used any more. This is legacy that
  110. # people don't seem to be able to get used to.
  111. @pytest.mark.parametrize("file_name", machine_filepaths)
  112. def test_noId(file_name):
  113. definition_path = os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions", file_name)
  114. with open(definition_path, encoding = "utf-8") as f:
  115. doc = json.load(f)
  116. assert "id" not in doc, "Definitions should not have an ID field."