1# Copyright (c) 2020 Ultimaker B.V. 2# Cura is released under the terms of the LGPLv3 or higher. 3 4import pytest #This module contains unit tests. 5import unittest.mock #To monkeypatch some mocks in place of dependencies. 6 7import cura.Settings.CuraContainerStack #To get the list of container types. 8from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationError #To test raising these errors. 9from UM.Settings.DefinitionContainer import DefinitionContainer #To test against the class DefinitionContainer. 10from UM.Settings.InstanceContainer import InstanceContainer #To test against the class InstanceContainer. 11from UM.Settings.SettingInstance import InstanceState 12import UM.Settings.ContainerRegistry 13import UM.Settings.ContainerStack 14import UM.Settings.SettingDefinition #To add settings to the definition. 15 16from cura.Settings.cura_empty_instance_containers import empty_container 17 18 19def getInstanceContainer(container_type) -> InstanceContainer: 20 """Gets an instance container with a specified container type. 21 22 :param container_type: The type metadata for the instance container. 23 :return: An instance container instance. 24 """ 25 26 container = InstanceContainer(container_id = "InstanceContainer") 27 container.setMetaDataEntry("type", container_type) 28 return container 29 30 31class DefinitionContainerSubClass(DefinitionContainer): 32 def __init__(self): 33 super().__init__(container_id = "SubDefinitionContainer") 34 35 36class InstanceContainerSubClass(InstanceContainer): 37 def __init__(self, container_type): 38 super().__init__(container_id = "SubInstanceContainer") 39 self.setMetaDataEntry("type", container_type) 40 41 42############################START OF TEST CASES################################ 43 44 45def test_addContainer(global_stack): 46 """Tests whether adding a container is properly forbidden.""" 47 48 with pytest.raises(InvalidOperationError): 49 global_stack.addContainer(unittest.mock.MagicMock()) 50 51 52def test_addExtruder(global_stack): 53 """Tests adding extruders to the global stack.""" 54 55 mock_definition = unittest.mock.MagicMock() 56 mock_definition.getProperty = lambda key, property, context = None: 2 if key == "machine_extruder_count" and property == "value" else None 57 58 with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): 59 global_stack.definition = mock_definition 60 61 assert len(global_stack.extruderList) == 0 62 first_extruder = unittest.mock.MagicMock() 63 first_extruder.getMetaDataEntry = lambda key: 0 if key == "position" else None 64 with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): 65 global_stack.addExtruder(first_extruder) 66 assert len(global_stack.extruderList) == 1 67 assert global_stack.extruderList[0] == first_extruder 68 second_extruder = unittest.mock.MagicMock() 69 second_extruder.getMetaDataEntry = lambda key: 1 if key == "position" else None 70 with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): 71 global_stack.addExtruder(second_extruder) 72 assert len(global_stack.extruderList) == 2 73 assert global_stack.extruderList[1] == second_extruder 74 # Disabled for now for Custom FDM Printer 75 # with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): 76 # with pytest.raises(TooManyExtrudersError): #Should be limited to 2 extruders because of machine_extruder_count. 77 # global_stack.addExtruder(unittest.mock.MagicMock()) 78 assert len(global_stack.extruderList) == 2 # Didn't add the faulty extruder. 79 80 81#Tests setting user changes profiles to invalid containers. 82@pytest.mark.parametrize("container", [ 83 getInstanceContainer(container_type = "wrong container type"), 84 getInstanceContainer(container_type = "material"), #Existing, but still wrong type. 85 DefinitionContainer(container_id = "wrong class") 86]) 87def test_constrainUserChangesInvalid(container, global_stack): 88 with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. 89 global_stack.userChanges = container 90 91 92#Tests setting user changes profiles. 93@pytest.mark.parametrize("container", [ 94 getInstanceContainer(container_type = "user"), 95 InstanceContainerSubClass(container_type = "user") 96]) 97def test_constrainUserChangesValid(container, global_stack): 98 global_stack.userChanges = container #Should not give an error. 99 100 101#Tests setting quality changes profiles to invalid containers. 102@pytest.mark.parametrize("container", [ 103 getInstanceContainer(container_type = "wrong container type"), 104 getInstanceContainer(container_type = "material"), #Existing, but still wrong type. 105 DefinitionContainer(container_id = "wrong class") 106]) 107def test_constrainQualityChangesInvalid(container, global_stack): 108 with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. 109 global_stack.qualityChanges = container 110 111 112#Test setting quality changes profiles. 113@pytest.mark.parametrize("container", [ 114 getInstanceContainer(container_type = "quality_changes"), 115 InstanceContainerSubClass(container_type = "quality_changes") 116]) 117def test_constrainQualityChangesValid(container, global_stack): 118 global_stack.qualityChanges = container #Should not give an error. 119 120 121#Tests setting quality profiles to invalid containers. 122@pytest.mark.parametrize("container", [ 123 getInstanceContainer(container_type = "wrong container type"), 124 getInstanceContainer(container_type = "material"), #Existing, but still wrong type. 125 DefinitionContainer(container_id = "wrong class") 126]) 127def test_constrainQualityInvalid(container, global_stack): 128 with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. 129 global_stack.quality = container 130 131 132#Test setting quality profiles. 133@pytest.mark.parametrize("container", [ 134 getInstanceContainer(container_type = "quality"), 135 InstanceContainerSubClass(container_type = "quality") 136]) 137def test_constrainQualityValid(container, global_stack): 138 global_stack.quality = container #Should not give an error. 139 140 141#Tests setting materials to invalid containers. 142@pytest.mark.parametrize("container", [ 143 getInstanceContainer(container_type = "wrong container type"), 144 getInstanceContainer(container_type = "quality"), #Existing, but still wrong type. 145 DefinitionContainer(container_id = "wrong class") 146]) 147def test_constrainMaterialInvalid(container, global_stack): 148 with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. 149 global_stack.material = container 150 151 152#Test setting materials. 153@pytest.mark.parametrize("container", [ 154 getInstanceContainer(container_type = "material"), 155 InstanceContainerSubClass(container_type = "material") 156]) 157def test_constrainMaterialValid(container, global_stack): 158 global_stack.material = container #Should not give an error. 159 160 161#Tests setting variants to invalid containers. 162@pytest.mark.parametrize("container", [ 163 getInstanceContainer(container_type = "wrong container type"), 164 getInstanceContainer(container_type = "material"), #Existing, but still wrong type. 165 DefinitionContainer(container_id = "wrong class") 166]) 167def test_constrainVariantInvalid(container, global_stack): 168 with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. 169 global_stack.variant = container 170 171 172#Test setting variants. 173@pytest.mark.parametrize("container", [ 174 getInstanceContainer(container_type = "variant"), 175 InstanceContainerSubClass(container_type = "variant") 176]) 177def test_constrainVariantValid(container, global_stack): 178 global_stack.variant = container #Should not give an error. 179 180 181#Tests setting definition changes profiles to invalid containers. 182@pytest.mark.parametrize("container", [ 183 getInstanceContainer(container_type = "wrong container type"), 184 getInstanceContainer(container_type = "material"), #Existing, but still wrong type. 185 DefinitionContainer(container_id = "wrong class") 186]) 187def test_constrainDefinitionChangesInvalid(container, global_stack): 188 with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. 189 global_stack.definitionChanges = container 190 191 192#Test setting definition changes profiles. 193@pytest.mark.parametrize("container", [ 194 getInstanceContainer(container_type = "definition_changes"), 195 InstanceContainerSubClass(container_type = "definition_changes") 196]) 197def test_constrainDefinitionChangesValid(container, global_stack): 198 global_stack.definitionChanges = container #Should not give an error. 199 200 201#Tests setting definitions to invalid containers. 202@pytest.mark.parametrize("container", [ 203 getInstanceContainer(container_type = "wrong class"), 204 getInstanceContainer(container_type = "material"), #Existing, but still wrong class. 205]) 206def test_constrainDefinitionInvalid(container, global_stack): 207 with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. 208 global_stack.definition = container 209 210 211#Test setting definitions. 212@pytest.mark.parametrize("container", [ 213 DefinitionContainer(container_id = "DefinitionContainer"), 214 DefinitionContainerSubClass() 215]) 216def test_constrainDefinitionValid(container, global_stack): 217 global_stack.definition = container #Should not give an error. 218 219 220def test_deserializeCompletesEmptyContainers(global_stack): 221 """Tests whether deserialising completes the missing containers with empty ones. The initial containers are just the 222 223 definition and the definition_changes (that cannot be empty after CURA-5281) 224 """ 225 226 global_stack._containers = [DefinitionContainer(container_id = "definition"), global_stack.definitionChanges] #Set the internal state of this stack manually. 227 228 with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. 229 global_stack.deserialize("") 230 231 assert len(global_stack.getContainers()) == len(cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap) #Needs a slot for every type. 232 for container_type_index in cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap: 233 if container_type_index in \ 234 (cura.Settings.CuraContainerStack._ContainerIndexes.Definition, cura.Settings.CuraContainerStack._ContainerIndexes.DefinitionChanges): #We're not checking the definition or definition_changes 235 continue 236 assert global_stack.getContainer(container_type_index) == empty_container #All others need to be empty. 237 238 239def test_deserializeRemovesWrongInstanceContainer(global_stack): 240 """Tests whether an instance container with the wrong type gets removed when deserialising.""" 241 242 global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "wrong type") 243 global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") 244 245 with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. 246 global_stack.deserialize("") 247 248 assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty. 249 250 251def test_deserializeRemovesWrongContainerClass(global_stack): 252 """Tests whether a container with the wrong class gets removed when deserialising.""" 253 254 global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = DefinitionContainer(container_id = "wrong class") 255 global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") 256 257 with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. 258 global_stack.deserialize("") 259 260 assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty. 261 262 263def test_deserializeWrongDefinitionClass(global_stack): 264 """Tests whether an instance container in the definition spot results in an error.""" 265 266 global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = getInstanceContainer(container_type = "definition") #Correct type but wrong class. 267 268 with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. 269 with pytest.raises(UM.Settings.ContainerStack.InvalidContainerStackError): #Must raise an error that there is no definition container. 270 global_stack.deserialize("") 271 272 273def test_deserializeMoveInstanceContainer(global_stack): 274 """Tests whether an instance container with the wrong type is moved into the correct slot by deserialising.""" 275 276 global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "material") #Not in the correct spot. 277 global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") 278 279 with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. 280 global_stack.deserialize("") 281 282 assert global_stack.quality == empty_container 283 assert global_stack.material != empty_container 284 285 286def test_deserializeMoveDefinitionContainer(global_stack): 287 """Tests whether a definition container in the wrong spot is moved into the correct spot by deserialising.""" 288 289 global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot. 290 291 with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. 292 global_stack.deserialize("") 293 294 assert global_stack.material == empty_container 295 assert global_stack.definition != empty_container 296 297 298def test_getPropertyFallThrough(global_stack): 299 """Tests whether getProperty properly applies the stack-like behaviour on its containers.""" 300 301 #A few instance container mocks to put in the stack. 302 mock_layer_heights = {} #For each container type, a mock container that defines layer height to something unique. 303 mock_no_settings = {} #For each container type, a mock container that has no settings at all. 304 container_indexes = cura.Settings.CuraContainerStack._ContainerIndexes #Cache. 305 for type_id, type_name in container_indexes.IndexTypeMap.items(): 306 container = unittest.mock.MagicMock() 307 container.getProperty = lambda key, property, context = None, type_id = type_id: type_id if (key == "layer_height" and property == "value") else None #Returns the container type ID as layer height, in order to identify it. 308 container.hasProperty = lambda key, property: key == "layer_height" 309 container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name) 310 mock_layer_heights[type_id] = container 311 312 container = unittest.mock.MagicMock() 313 container.getProperty = unittest.mock.MagicMock(return_value = None) #Has no settings at all. 314 container.hasProperty = unittest.mock.MagicMock(return_value = False) 315 container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name) 316 mock_no_settings[type_id] = container 317 318 global_stack.userChanges = mock_no_settings[container_indexes.UserChanges] 319 global_stack.qualityChanges = mock_no_settings[container_indexes.QualityChanges] 320 global_stack.quality = mock_no_settings[container_indexes.Quality] 321 global_stack.material = mock_no_settings[container_indexes.Material] 322 global_stack.variant = mock_no_settings[container_indexes.Variant] 323 global_stack.definitionChanges = mock_no_settings[container_indexes.DefinitionChanges] 324 with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. 325 global_stack.definition = mock_layer_heights[container_indexes.Definition] #There's a layer height in here! 326 327 assert global_stack.getProperty("layer_height", "value") == container_indexes.Definition 328 global_stack.definitionChanges = mock_layer_heights[container_indexes.DefinitionChanges] 329 assert global_stack.getProperty("layer_height", "value") == container_indexes.DefinitionChanges 330 global_stack.variant = mock_layer_heights[container_indexes.Variant] 331 assert global_stack.getProperty("layer_height", "value") == container_indexes.Variant 332 global_stack.material = mock_layer_heights[container_indexes.Material] 333 assert global_stack.getProperty("layer_height", "value") == container_indexes.Material 334 global_stack.quality = mock_layer_heights[container_indexes.Quality] 335 assert global_stack.getProperty("layer_height", "value") == container_indexes.Quality 336 global_stack.qualityChanges = mock_layer_heights[container_indexes.QualityChanges] 337 assert global_stack.getProperty("layer_height", "value") == container_indexes.QualityChanges 338 global_stack.userChanges = mock_layer_heights[container_indexes.UserChanges] 339 assert global_stack.getProperty("layer_height", "value") == container_indexes.UserChanges 340 341 342def test_getPropertyNoResolveInDefinition(global_stack): 343 """In definitions, test whether having no resolve allows us to find the value.""" 344 345 value = unittest.mock.MagicMock() #Just sets the value for bed temperature. 346 value.getProperty = lambda key, property, context = None: 10 if (key == "material_bed_temperature" and property == "value") else None 347 348 with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. 349 global_stack.definition = value 350 assert global_stack.getProperty("material_bed_temperature", "value") == 10 #No resolve, so fall through to value. 351 352 353def test_getPropertyResolveInDefinition(global_stack): 354 """In definitions, when the value is asked and there is a resolve function, it must get the resolve first.""" 355 356 resolve_and_value = unittest.mock.MagicMock() #Sets the resolve and value for bed temperature. 357 resolve_and_value.getProperty = lambda key, property, context = None: (7.5 if property == "resolve" else 5) if (key == "material_bed_temperature" and property in ("resolve", "value")) else None #7.5 resolve, 5 value. 358 359 with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. 360 global_stack.definition = resolve_and_value 361 assert global_stack.getProperty("material_bed_temperature", "value") == 7.5 #Resolve wins in the definition. 362 363 364def test_getPropertyResolveInInstance(global_stack): 365 """In instance containers, when the value is asked and there is a resolve function, it must get the value first.""" 366 367 container_indices = cura.Settings.CuraContainerStack._ContainerIndexes 368 instance_containers = {} 369 for container_type in container_indices.IndexTypeMap: 370 instance_containers[container_type] = unittest.mock.MagicMock() #Sets the resolve and value for bed temperature. 371 instance_containers[container_type].getProperty = lambda key, property, context = None: (7.5 if property == "resolve" else (InstanceState.User if property == "state" else (5 if property != "limit_to_extruder" else "-1"))) if (key == "material_bed_temperature") else None #7.5 resolve, 5 value. 372 instance_containers[container_type].getMetaDataEntry = unittest.mock.MagicMock(return_value = container_indices.IndexTypeMap[container_type]) #Make queries for the type return the desired type. 373 instance_containers[container_indices.Definition].getProperty = lambda key, property, context = None: 10 if (key == "material_bed_temperature" and property == "value") else None #Definition only has value. 374 with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. 375 global_stack.definition = instance_containers[container_indices.Definition] #Stack must have a definition. 376 377 #For all instance container slots, the value reigns over resolve. 378 global_stack.definitionChanges = instance_containers[container_indices.DefinitionChanges] 379 assert global_stack.getProperty("material_bed_temperature", "value") == 5 380 global_stack.variant = instance_containers[container_indices.Variant] 381 assert global_stack.getProperty("material_bed_temperature", "value") == 5 382 global_stack.material = instance_containers[container_indices.Material] 383 assert global_stack.getProperty("material_bed_temperature", "value") == 5 384 global_stack.quality = instance_containers[container_indices.Quality] 385 assert global_stack.getProperty("material_bed_temperature", "value") == 5 386 global_stack.qualityChanges = instance_containers[container_indices.QualityChanges] 387 assert global_stack.getProperty("material_bed_temperature", "value") == 5 388 global_stack.userChanges = instance_containers[container_indices.UserChanges] 389 assert global_stack.getProperty("material_bed_temperature", "value") == 5 390 391 392def test_getPropertyInstancesBeforeResolve(global_stack): 393 """Tests whether the value in instances gets evaluated before the resolve in definitions.""" 394 395 def getValueProperty(key, property, context = None): 396 if key != "material_bed_temperature": 397 return None 398 if property == "value": 399 return 10 400 if property == "limit_to_extruder": 401 return -1 402 return InstanceState.User 403 404 def getResolveProperty(key, property, context = None): 405 if key != "material_bed_temperature": 406 return None 407 if property == "resolve": 408 return 7.5 409 return None 410 411 value = unittest.mock.MagicMock() #Sets just the value. 412 value.getProperty = unittest.mock.MagicMock(side_effect = getValueProperty) 413 value.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality_changes") 414 resolve = unittest.mock.MagicMock() #Sets just the resolve. 415 resolve.getProperty = unittest.mock.MagicMock(side_effect = getResolveProperty) 416 417 with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. 418 global_stack.definition = resolve 419 global_stack.qualityChanges = value 420 421 assert global_stack.getProperty("material_bed_temperature", "value") == 10 422 423 424def test_hasUserValueUserChanges(global_stack): 425 """Tests whether the hasUserValue returns true for settings that are changed in the user-changes container.""" 426 427 container = unittest.mock.MagicMock() 428 container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user") 429 container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set. 430 global_stack.userChanges = container 431 432 assert global_stack.hasUserValue("layer_height") 433 assert not global_stack.hasUserValue("infill_sparse_density") 434 assert not global_stack.hasUserValue("") 435 436 437def test_hasUserValueQualityChanges(global_stack): 438 """Tests whether the hasUserValue returns true for settings that are changed in the quality-changes container.""" 439 440 container = unittest.mock.MagicMock() 441 container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality_changes") 442 container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set. 443 global_stack.qualityChanges = container 444 445 assert global_stack.hasUserValue("layer_height") 446 assert not global_stack.hasUserValue("infill_sparse_density") 447 assert not global_stack.hasUserValue("") 448 449 450def test_hasNoUserValue(global_stack): 451 """Tests whether a container in some other place on the stack is correctly not recognised as user value.""" 452 453 container = unittest.mock.MagicMock() 454 container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality") 455 container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set. 456 global_stack.quality = container 457 458 assert not global_stack.hasUserValue("layer_height") #However this container is quality, so it's not a user value. 459 460 461def test_insertContainer(global_stack): 462 """Tests whether inserting a container is properly forbidden.""" 463 464 with pytest.raises(InvalidOperationError): 465 global_stack.insertContainer(0, unittest.mock.MagicMock()) 466 467 468def test_removeContainer(global_stack): 469 """Tests whether removing a container is properly forbidden.""" 470 471 with pytest.raises(InvalidOperationError): 472 global_stack.removeContainer(unittest.mock.MagicMock()) 473 474 475def test_setNextStack(global_stack): 476 """Tests whether changing the next stack is properly forbidden.""" 477 478 with pytest.raises(InvalidOperationError): 479 global_stack.setNextStack(unittest.mock.MagicMock()) 480 481 482## Tests setting properties directly on the global stack. 483@pytest.mark.parametrize("key, property, value", [ 484 ("layer_height", "value", 0.1337), 485 ("foo", "value", 100), 486 ("support_enabled", "value", True), 487 ("layer_height", "default_value", 0.1337), 488 ("layer_height", "is_bright_pink", "of course") 489]) 490def test_setPropertyUser(key, property, value, global_stack): 491 user_changes = unittest.mock.MagicMock() 492 user_changes.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user") 493 global_stack.userChanges = user_changes 494 495 global_stack.setProperty(key, property, value) # The actual test. 496 497 # Make sure that the user container gets a setProperty call. 498 global_stack.userChanges.setProperty.assert_called_once_with(key, property, value, None, False)