def test_dependencies(): """Test that dependency lists make sense.""" prop = PropertyTemplate(name="name", bounds=IntegerBounds(0, 1)) cond = ConditionTemplate(name="name", bounds=IntegerBounds(0, 1)) param = ParameterTemplate(name="name", bounds=IntegerBounds(0, 1)) msr_template = MeasurementTemplate("a process template", conditions=[cond], properties=[prop], parameters=[param]) assert prop in msr_template.all_dependencies() assert cond in msr_template.all_dependencies() assert param in msr_template.all_dependencies()
def test_passthrough_bounds(): """Test that unspecified Bounds are accepted and set to None.""" template = ProcessTemplate('foo', conditions=[ (LinkByUID('1', '2'), None), [LinkByUID('3', '4'), None], LinkByUID('5', '6'), ConditionTemplate('foo', bounds=IntegerBounds( 0, 10)), ]) assert len(template.conditions) == 4 for _, bounds in template.conditions: assert bounds is None copied = loads(dumps(template)) assert len(copied.conditions) == 4 for _, bounds in copied.conditions: assert bounds is None from_dict = ProcessTemplate.build({ 'type': 'process_template', 'name': 'foo', 'conditions': [[ { 'scope': 'foo', 'id': 'bar', 'type': 'link_by_uid', }, None, ]], }) assert len(from_dict.conditions) == 1
def test_attributes(): """Exercise permutations of attributes, bounds and values.""" prop_tmpl = PropertyTemplate(name="Property", bounds=RealBounds(0, 10, "m")) cond_tmpl = ConditionTemplate(name="Condition", bounds=CategoricalBounds(["a", "b", "c"])) param_tmpl = ParameterTemplate(name="Parameter", bounds=CompositionBounds( EmpiricalFormula.all_elements())) mol_tmpl = PropertyTemplate(name="Molecule", bounds=MolecularStructureBounds()) int_tmpl = ConditionTemplate(name="Integer", bounds=IntegerBounds(0, 10)) msr = add_measurement(make_node('Material'), name='Measurement', attributes=[ make_attribute(prop_tmpl, 5), make_attribute(cond_tmpl, 'a'), make_attribute(param_tmpl, 'SiC'), make_attribute(mol_tmpl, 'InChI=1S/CSi/c1-2'), make_attribute(mol_tmpl, '[C-]#[Si+]'), make_attribute(int_tmpl, 5) ]) assert msr.properties[0].value.nominal == 5 assert msr.conditions[0].value.category == 'a' assert msr.parameters[0].value.formula == 'SiC' assert msr.properties[1].value.inchi == 'InChI=1S/CSi/c1-2' assert msr.properties[2].value.smiles == '[C-]#[Si+]' assert msr.conditions[1].value.nominal == 5
def test_dependencies(): """Test that dependency lists make sense.""" prop = PropertyTemplate(name="name", bounds=IntegerBounds(0, 1)) cond = ConditionTemplate(name="name", bounds=IntegerBounds(0, 1)) template = MaterialTemplate("measurement template") spec = MaterialSpec("A spec", template=template, properties=[PropertyAndConditions( property=Property("name", template=prop, value=NominalInteger(1)), conditions=[ Condition("name", template=cond, value=NominalInteger(1)) ] )]) assert template in spec.all_dependencies() assert cond in spec.all_dependencies() assert prop in spec.all_dependencies()
def _to_bounds(self) -> IntegerBounds: """ Return the smallest bounds object that is consistent with the Value. Returns ------- IntegerBounds The minimally consistent :class:`bounds <gemd.entity.bounds.integer_bounds.IntegerBounds>`. """ return IntegerBounds(lower_bound=self.lower_bound, upper_bound=self.upper_bound)
def test_dependencies(): """Test that dependency lists make sense.""" prop = PropertyTemplate(name="name", bounds=IntegerBounds(0, 1)) cond = ConditionTemplate(name="name", bounds=IntegerBounds(0, 1)) param = ParameterTemplate(name="name", bounds=IntegerBounds(0, 1)) template = MeasurementTemplate("measurement template", parameters=[param], conditions=[cond], properties=[prop]) spec = MeasurementSpec("A spec", template=template) mat = MaterialRun(name="mr") meas = MeasurementRun("A run", spec=spec, material=mat, properties=[ Property(prop.name, template=prop, value=NominalInteger(1)) ], conditions=[ Condition(cond.name, template=cond, value=NominalInteger(1)) ], parameters=[ Parameter(param.name, template=param, value=NominalInteger(1)) ]) assert template not in meas.all_dependencies() assert spec in meas.all_dependencies() assert mat in meas.all_dependencies() assert prop in meas.all_dependencies() assert cond in meas.all_dependencies() assert param in meas.all_dependencies()
def test_mat_spec_properties(caplog): """Make sure template validations and level controls behave as expected.""" prop_tmpl = PropertyTemplate("Name", bounds=IntegerBounds(0, 2)) cond_tmpl = ConditionTemplate("Name", bounds=IntegerBounds(0, 2)) mat_tmpl = MaterialTemplate("Material Template", properties=[[prop_tmpl, IntegerBounds(0, 1)]]) mat_spec = MaterialSpec("Material Spec", template=mat_tmpl) good_prop = PropertyAndConditions( property=Property("Name", value=NominalInteger(1), template=prop_tmpl), conditions=[Condition("Name", value=NominalInteger(1), template=cond_tmpl)] ) bad_prop = PropertyAndConditions( property=Property("Name", value=NominalInteger(2), template=prop_tmpl), conditions=[Condition("Name", value=NominalInteger(1), template=cond_tmpl)] ) bad_cond = PropertyAndConditions( # This will pass since we don't have a condition constraint property=Property("Name", value=NominalInteger(1), template=prop_tmpl), conditions=[Condition("Name", value=NominalInteger(2), template=cond_tmpl)] ) with validation_level(WarningLevel.IGNORE): mat_spec.properties.append(good_prop) assert len(caplog.records) == 0, "Warning encountered on IGNORE" mat_spec.properties.append(bad_prop) assert len(caplog.records) == 0, "Warning encountered on IGNORE" mat_spec.properties.append(bad_cond) assert len(caplog.records) == 0, "Warning encountered on IGNORE" with validation_level(WarningLevel.WARNING): mat_spec.properties.append(good_prop) assert len(caplog.records) == 0, "Warning encountered on Good value" mat_spec.properties.append(bad_prop) assert len(caplog.records) == 1, "No warning encountered on Bad Value" mat_spec.properties.append(bad_cond) assert len(caplog.records) == 1, "Warning encountered on Bad condition" with validation_level(WarningLevel.FATAL): mat_spec.properties.append(good_prop) # This is fine with pytest.raises(ValueError): mat_spec.properties.append(bad_prop) mat_spec.properties.append(bad_cond) # This should probably not be fine
def test_invalid_template_conversions(): with pytest.raises(NoEquivalentDescriptorError): template_to_descriptor( ConditionTemplate("foo", bounds=IntegerBounds(lower_bound=0, upper_bound=1))) with pytest.raises(NoEquivalentDescriptorError): template_to_descriptor( PropertyTemplate( "mixture", bounds=CompositionBounds(components=["sugar", "spice"]))) class DummyBounds(BaseBounds): """Fake bounds to test unrecognized bounds.""" def contains(self, bounds): return False with pytest.raises(ValueError): template_to_descriptor(ParameterTemplate("dummy", bounds=DummyBounds()))
def test_contains(): """Test that bounds know if a Value is contained within it.""" bounds = IntegerBounds(1, 3) assert bounds.contains(NominalInteger(2)._to_bounds()) assert not bounds.contains(NominalInteger(5)._to_bounds())
def fake_project(): """Fake project that serves templates from template collection's list_all method.""" templates = [ PropertyTemplate("density", bounds=RealBounds(lower_bound=0, upper_bound=100, default_units="g / cm^3"), uids={"my_scope": "density"}), ConditionTemplate("volume", bounds=IntegerBounds(lower_bound=0, upper_bound=11), uids={"my_scope": "volume"}), ParameterTemplate( "speed", bounds=CategoricalBounds(categories=["slow", "fast"]), uids={}) ] class FakePropertyTemplateCollection(PropertyTemplateCollection): def __init__(self): pass def list_all(self, forward: bool = True, per_page: int = 100) -> Iterator[PropertyTemplate]: return iter( [x for x in templates if isinstance(x, PropertyTemplate)]) class FakeConditionTemplateCollection(ConditionTemplateCollection): def __init__(self): pass def list_all(self, forward: bool = True, per_page: int = 100) -> Iterator[ConditionTemplate]: return iter( [x for x in templates if isinstance(x, ConditionTemplate)]) class FakeParameterTemplateCollection(ParameterTemplateCollection): def __init__(self): pass def list_all(self, forward: bool = True, per_page: int = 100) -> Iterator[ParameterTemplate]: return iter( [x for x in templates if isinstance(x, ParameterTemplate)]) class FakeProject(Project): def __init__(self): pass @property def property_templates(self) -> PropertyTemplateCollection: return FakePropertyTemplateCollection() @property def condition_templates(self) -> ConditionTemplateCollection: return FakeConditionTemplateCollection() @property def parameter_templates(self) -> ParameterTemplateCollection: return FakeParameterTemplateCollection() return FakeProject()
def test_links_as_templates(): """Verify that LinkByUIDs don't break anything we could have otherwise done.""" prop_tmpl = PropertyTemplate("Name", uids={"scope": "prop"}, bounds=IntegerBounds(1, 5)) cond_tmpl = ConditionTemplate("Name", uids={"scope": "cond"}, bounds=IntegerBounds(1, 5)) param_tmpl = ParameterTemplate("Name", uids={"scope": "param"}, bounds=IntegerBounds(1, 5)) no_bounds = MeasurementTemplate( "Name", properties=[prop_tmpl], conditions=[cond_tmpl], parameters=[param_tmpl], ) just_right = NominalInteger(2) middling = NominalInteger(4) too_high = NominalInteger(7) scenarios = [ ("property", Property, MeasurementTemplate.validate_property, prop_tmpl), ("condition", Condition, MeasurementTemplate.validate_condition, cond_tmpl), ("parameter", Parameter, MeasurementTemplate.validate_parameter, param_tmpl), ] # Check that Attributes are checked against the template when the attributes links for scenario in scenarios: name, attr, validate, tmpl = scenario assert validate(no_bounds, attr("Other name", template=tmpl.to_link(), value=middling)), \ f"{name} didn't validate with {name}.template as LinkByUID." assert not validate(no_bounds, attr("Other name", template=tmpl.to_link(), value=too_high)), \ f"{name} DID validate with {name}.template as LinkByUID and bad value." with_bounds = MeasurementTemplate( "Name", properties=[(prop_tmpl.to_link(), IntegerBounds(1, 3))], conditions=[(cond_tmpl.to_link(), IntegerBounds(1, 3))], parameters=[(param_tmpl.to_link(), IntegerBounds(1, 3))], ) # Check that Attributes are checked against the bounds when the attributes links for scenario in scenarios: name, attr, validate, tmpl = scenario assert validate(with_bounds, attr("Other name", template=tmpl.to_link(), value=just_right)), \ f"{name} didn't validate with {name}.template as LinkByUID and bounds." assert not validate(with_bounds, attr("Other name", template=tmpl.to_link(), value=middling)), \ f"{name} DID validate with {name}.template as LinkByUID, bad value, and bounds." with_links = MeasurementTemplate( "Name", properties=[(prop_tmpl.to_link())], conditions=[(cond_tmpl.to_link())], parameters=[(param_tmpl.to_link())], ) # Check that tests pass when there's no way to test for scenario in scenarios: name, attr, validate, tmpl = scenario assert validate(with_links, attr("Other name", template=tmpl.to_link(), value=too_high)), \ f"{name} didn't validate with LinkByUID for everything."
def test_mixins(): """Measurement templates have all 3 mixin traits.""" obj = MeasurementTemplate( "Name", properties=[PropertyTemplate("Name", bounds=IntegerBounds(0, 1))], conditions=[ConditionTemplate("Name", bounds=IntegerBounds(0, 1))], parameters=[ParameterTemplate("Name", bounds=IntegerBounds(0, 1))], ) with pytest.raises(TypeError): obj.properties.append( ConditionTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): obj.properties.append( ParameterTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): obj.conditions.append(PropertyTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): obj.conditions.append( ParameterTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): obj.parameters.append( ConditionTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): obj.parameters.append(PropertyTemplate("3", bounds=IntegerBounds(0, 5))) with pytest.raises(TypeError): # You passed a `scalar` to extend obj.properties.extend((PropertyTemplate("3", bounds=IntegerBounds(0, 5)), IntegerBounds(1, 3))) with pytest.raises(ValueError): # You passed a `scalar` to extend obj.properties = (PropertyTemplate("3", bounds=IntegerBounds(1, 3)), IntegerBounds(0, 5)) obj.properties.append( (PropertyTemplate("2", bounds=IntegerBounds(0, 5)), IntegerBounds(1, 3))) obj.properties.extend([ PropertyTemplate("3", bounds=IntegerBounds(0, 5)), (PropertyTemplate("4", bounds=IntegerBounds(0, 5)), IntegerBounds(1, 3)) ]) obj.conditions.insert(1, ConditionTemplate("2", bounds=IntegerBounds(0, 1))) obj.parameters[0] = ParameterTemplate("Name", bounds=IntegerBounds(0, 1)) for x in (obj.properties, obj.conditions, obj.parameters): assert isinstance(x, ValidList) for y in x: assert isinstance(y, list) assert len(y) == 2 assert isinstance(y[0], AttributeTemplate) if y[1] is not None: assert isinstance(y[1], BaseBounds) second = MeasurementTemplate( "Name", properties=[ PropertyTemplate("Name", bounds=IntegerBounds(0, 1)), IntegerBounds(0, 1) ], conditions=[ ConditionTemplate("Name", bounds=IntegerBounds(0, 1)), IntegerBounds(0, 1) ], parameters=[ ParameterTemplate("Name", bounds=IntegerBounds(0, 1)), IntegerBounds(0, 1) ], ) assert len(second.properties) == 1 assert len(second.conditions) == 1 assert len(second.parameters) == 1 good_val = NominalInteger(1) bad_val = NominalInteger(2) assert second.validate_condition(Condition("Other name", value=good_val, template=second.conditions[0][0])), \ "Condition with template and good value didn't validate." assert not second.validate_condition(Condition("Other name", value=bad_val, template=second.conditions[0][0])), \ "Condition with template and bad value DID validate." assert second.validate_parameter(Parameter("Other name", value=good_val, template=second.parameters[0][0])), \ "Parameter with template and good value didn't validate." assert not second.validate_parameter(Parameter("Other name", value=bad_val, template=second.parameters[0][0])), \ "Parameter with template and bad value DID validate." assert second.validate_property(Property("Other name", value=good_val, template=second.properties[0][0])), \ "Property with template and good value didn't validate." assert not second.validate_property(Property("Other name", value=bad_val, template=second.properties[0][0])), \ "Property with template and bad value DID validate." assert second.validate_condition(Condition("Name", value=good_val)), \ "Condition without template and good value didn't validate." assert not second.validate_condition(Condition("Name", value=bad_val)), \ "Condition without template and bad value DID validate." assert second.validate_parameter(Parameter("Name", value=good_val)), \ "Parameter without template and good value didn't validate." assert not second.validate_parameter(Parameter("Name", value=bad_val)), \ "Parameter without template and bad value DID validate." assert second.validate_property(Property("Name", value=good_val)), \ "Property without template and good value didn't validate." assert not second.validate_property(Property("Name", value=bad_val)), \ "Property without template and bad value DID validate." assert second.validate_condition(Condition("Other name", value=bad_val)), \ "Unmatched condition and bad value didn't validate." assert second.validate_parameter(Parameter("Other name", value=bad_val)), \ "Unmatched parameter and bad value didn't validate." assert second.validate_property(Property("Other name", value=bad_val)), \ "Unmatched property and bad value didn't validate." second.conditions[0][1] = None second.parameters[0][1] = None second.properties[0][1] = None assert second.validate_condition(Condition("Name", value=good_val)), \ "Condition and good value with passthrough didn't validate." assert second.validate_parameter(Parameter("Name", value=good_val)), \ "Parameter and good value with passthrough didn't validate." assert second.validate_property(Property("Name", value=good_val)), \ "Property and good value with passthrough didn't validate." assert second.validate_property( PropertyAndConditions(property=Property("Name", value=good_val))), \ "PropertyAndConditions didn't fall back to Property."
def test_contains(): """Test that bounds know if a Value is contained within it.""" bounds = IntegerBounds(1, 3) assert bounds.contains(UniformInteger(1, 2)._to_bounds()) assert not bounds.contains(UniformInteger(3, 5)._to_bounds())
def make_cake_templates(): """Define all templates independently, as in the wild this will be an independent operation.""" tmpl = dict() # Attributes tmpl["Mixer speed setting"] = ParameterTemplate( name="Mixer speed setting", description="What speed setting to use on the mixer", bounds=IntegerBounds(0, 10)) tmpl['Cooking time'] = ConditionTemplate( name="Cooking time", description="The time elapsed during a cooking process", bounds=RealBounds(0, 7 * 24.0, "hr")) tmpl["Oven temperature setting"] = ParameterTemplate( name="Oven temperature setting", description="Where the knob points", bounds=RealBounds(0, 2000.0, "K")) tmpl["Oven temperature"] = ConditionTemplate( name="Oven temperature", description="Actual temperature measured by the thermocouple", bounds=RealBounds(0, 2000.0, "K")) tmpl["Toothpick test"] = PropertyTemplate( name="Toothpick test", description="Results of inserting a toothpick to check doneness", bounds=CategoricalBounds(["wet", "crumbs", "completely clean"])) tmpl["Color"] = PropertyTemplate( name="Baked color", description="Visual observation of the color of a baked good", bounds=CategoricalBounds( ["Pale", "Golden brown", "Deep brown", "Black"])) tmpl["Tastiness"] = PropertyTemplate( name="Tastiness", description="Yumminess on a fairly arbitrary scale", bounds=IntegerBounds(lower_bound=1, upper_bound=10)) tmpl["Nutritional Information"] = PropertyTemplate( name="Nutritional Information", description= "FDA Nutrition Facts, mass basis. Please be attentive to g vs. mg. " "`other-carbohydrate` and `other-fat` are the total values minus the " "broken-out quantities. Other is the difference between the total and the " "serving size.", bounds=CompositionBounds(components=[ 'other', 'saturated-fat', 'trans-fat', 'other-fat', 'cholesterol', 'sodium', 'dietary-fiber', 'sugars', 'other-carbohydrate', 'protein', 'vitamin-d', 'calcium', 'iron', 'potassium' ])) tmpl["Sample Mass"] = ConditionTemplate( name="Sample Mass", description= "Sample size in mass units, to go along with FDA Nutrition Facts", bounds=RealBounds(1.e-3, 1.e4, "g")) tmpl["Expected Sample Mass"] = ParameterTemplate( name="Expected Sample Mass", description= "Specified sample size in mass units, to go along with FDA Nutrition Facts", bounds=RealBounds(1.e-3, 1.e4, "g")) tmpl["Chemical Formula"] = PropertyTemplate( name="Chemical Formula", description="The chemical formula of a material", bounds=CompositionBounds(components=EmpiricalFormula.all_elements())) tmpl["Molecular Structure"] = PropertyTemplate( name="Molecular Structure", description="The molecular structure of the material", bounds=MolecularStructureBounds()) # Objects tmpl["Procuring"] = ProcessTemplate( name="Procuring", description="Buyin' stuff", allowed_names=[] # Takes no ingredients by definition ) tmpl["Baking"] = ProcessTemplate( name="Baking", description='Using heat to promote chemical reactions in a material', allowed_names=['batter'], allowed_labels=['precursor'], conditions=[(tmpl["Oven temperature"], RealBounds(0, 700, "degF"))], parameters=[(tmpl["Oven temperature setting"], RealBounds(100, 550, "degF"))]) tmpl["Icing"] = ProcessTemplate( name="Icing", description='Applying a coating to a substrate', allowed_labels=['coating', 'substrate']) tmpl["Mixing"] = ProcessTemplate( name="Mixing", description='Physically combining ingredients', allowed_labels=[ 'wet', 'dry', 'leavening', 'seasoning', 'sweetener', 'shortening', 'flavoring' ], parameters=[tmpl["Mixer speed setting"]]) tmpl["Generic Material"] = MaterialTemplate(name="Generic") tmpl["Nutritional Material"] = MaterialTemplate( name="Nutritional Material", description="A material with FDA Nutrition Facts attached", properties=[tmpl["Nutritional Information"]]) tmpl["Formulaic Material"] = MaterialTemplate( name="Formulaic Material", description="A material with chemical characterization", properties=[tmpl["Chemical Formula"], tmpl["Molecular Structure"]]) tmpl["Baked Good"] = MaterialTemplate( name="Baked Good", properties=[tmpl["Toothpick test"], tmpl["Color"]]) tmpl["Dessert"] = MaterialTemplate(name="Dessert", properties=[tmpl["Tastiness"]]) tmpl["Doneness"] = MeasurementTemplate( name="Doneness test", description= "An ensemble of tests to determine the doneness of a baked good", properties=[tmpl["Toothpick test"], tmpl["Color"]]) tmpl["Taste test"] = MeasurementTemplate(name="Taste test", properties=[tmpl["Tastiness"]]) tmpl["Nutritional Analysis"] = MeasurementTemplate( name="Nutritional Analysis", properties=[tmpl["Nutritional Information"]], conditions=[tmpl["Sample Mass"]], parameters=[tmpl["Expected Sample Mass"]]) tmpl["Elemental Analysis"] = MeasurementTemplate( name="Elemental Analysis", properties=[tmpl["Chemical Formula"]], conditions=[tmpl["Sample Mass"]], parameters=[tmpl["Expected Sample Mass"]]) for key in tmpl: tmpl[key].add_uid(TEMPLATE_SCOPE, key.lower().replace(' ', '-')) return tmpl
def test_build(): """Test builder routines.""" mix_tmpl = ProcessTemplate(name="Mixing") term_tmpl = MaterialTemplate(name="Terminal") procure_tmpl = ProcessTemplate(name="Procure") raw_tmpl = MaterialTemplate(name="Raw") prop_tmpl = PropertyTemplate(name="Property", bounds=RealBounds(0, 10, "m")) cond_tmpl = ConditionTemplate(name="Condition", bounds=CategoricalBounds(["a", "b", "c"])) param_tmpl = ParameterTemplate(name="Parameter", bounds=CompositionBounds( EmpiricalFormula.all_elements())) mol_tmpl = PropertyTemplate(name="Molecule", bounds=MolecularStructureBounds()) int_tmpl = ConditionTemplate(name="Integer", bounds=IntegerBounds(0, 10)) bad_tmpl = PropertyTemplate(name="Bad", bounds=UnsupportedBounds()) root = make_node(name="Root", material_template=term_tmpl, process_template=mix_tmpl) assert root.template == term_tmpl, "Object didn't link correctly." assert root.process.template == mix_tmpl, "Object didn't link correctly." one = make_node("One", material_template=raw_tmpl, process_template=procure_tmpl) add_edge(output_material=root, input_material=one) add_edge(output_material=root, input_material=make_node("Two")) assert len( root.process.ingredients) == 2, "Ingredient count didn't line up." # Attribute tests with pytest.raises(ValueError): add_attribute(one.spec, cond_tmpl, "b") # No property yet # Create a property-and-condition on the mat spec add_attribute(one.spec, prop_tmpl, 1) assert len( one.spec.properties) == 1, "Material spec didn't get a property." assert one.spec.properties[ 0].property.template == prop_tmpl, "Wrong linking on property." assert one.spec.properties[ 0].property.value.units == "meter", "Wrong units on property." assert one.spec.properties[ 0].property.value.nominal == 1, "Wrong value on property." add_attribute(one.spec, cond_tmpl, "b") assert len( one.spec.properties[0].conditions) == 1, "Wrong location on condition." assert one.spec.properties[0].conditions[0].template == cond_tmpl, \ "Wrong linking on condition." assert one.spec.properties[0].conditions[ 0].value.category == "b", "Wrong value on condition." with pytest.raises(ValueError): add_attribute(one.spec, param_tmpl, "H2O") # Mat Specs don't support parameters # Create a second property-and-condition on the mat spec add_attribute(one.spec, mol_tmpl, "C") assert len(one.spec.properties) == 2, "Second property added." add_attribute(one.spec, int_tmpl, 5) assert len(one.spec.properties[-1].conditions ) == 1, "Second property has a condition." # Attach a measurement msr_tmpl = MeasurementTemplate(name="Measure!", properties=[prop_tmpl]) msr = add_measurement(material=root, template=msr_tmpl) assert len(root.measurements) == 1, "Measurement was added to root." add_attribute(msr, cond_tmpl, "c") # Order shouldn't matter anymore assert len(msr.conditions) == 1, "Condition wasn't added to measurement." add_attribute(msr, prop_tmpl, 5) assert len(msr.properties) == 1, "Property wasn't added to measurement." add_attribute(msr, param_tmpl, "CH4") assert len(msr.parameters) == 1, "Parameter wasn't added to measurement." assert msr.parameters[ 0].template == param_tmpl, "Wrong linking on parameter." assert msr.parameters[ 0].value.formula == "CH4", "Wrong value on parameter." # Test failed builds with pytest.raises(ValueError): add_measurement(material=root) assert len(root.measurements ) == 1, "Failed measurement build still added an object." with pytest.raises(ValueError): add_attribute(msr, bad_tmpl, "Word") assert len( msr.properties) == 1, "Failed attribute build still added an object." with pytest.raises(ValueError): add_attribute(root.process, prop_tmpl, 9) assert len(root.process.conditions ) == 0, "Failed attribute build still added an object." assert len(root.process.parameters ) == 0, "Failed attribute build still added an object."