Пример #1
0
def test_serialize():
    """Serializing a nested object should be identical to individually serializing each piece."""
    condition = Condition(name="A condition", value=NominalReal(7, ''))
    parameter = Parameter(name="A parameter",
                          value=NormalReal(mean=17, std=1, units=''))
    input_material = MaterialRun(tags="input")
    process = ProcessRun(tags="A tag on a process run")
    ingredient = IngredientRun(material=input_material, process=process)
    material = MaterialRun(tags=["A tag on a material"], process=process)
    measurement = MeasurementRun(tags="A tag on a measurement",
                                 conditions=condition,
                                 parameters=parameter,
                                 material=material)

    # serialize the root of the tree
    native_object = json.loads(dumps(measurement))
    # ingredients don't get serialized on the process
    assert (len(native_object["context"]) == 5)
    assert (native_object["object"]["type"] == LinkByUID.typ)

    # serialize all of the nodes
    native_batch = json.loads(
        dumps([material, process, measurement, ingredient]))
    assert (len(native_batch["context"]) == 5)
    assert (len(native_batch["object"]) == 4)
    assert (all(x["type"] == LinkByUID.typ for x in native_batch["object"]))
def test_material_run():
    """
    Test the ability to create a MaterialRun that is linked to a MaterialSpec.

    Make sure all enumerated values are respected, and check consistency after
    serializing and deserializing.
    """
    # Define a property, and make sure that an inappropriate value for origin throws ValueError
    with pytest.raises(ValueError):
        prop = Property(name="A property", origin="bad origin", value=NominalReal(17, units=''))

    # Create a MaterialSpec with a property
    prop = Property(name="A property", origin="specified", value=NominalReal(17, units=''))
    mat_spec = MaterialSpec(
        name="a specification for a material",
        properties=PropertyAndConditions(prop),
        notes="Funny lookin'"
    )

    # Make sure that when property is serialized, origin (an enumeration) is serialized as a string
    copy_prop = json.loads(dumps(mat_spec))
    copy_origin = copy_prop["context"][0]["properties"][0]['property']['origin']
    assert isinstance(copy_origin, str)

    # Create a MaterialRun, and make sure an inappropriate value for sample_type throws ValueError
    with pytest.raises(ValueError):
        mat = MaterialRun("name", spec=mat_spec, sample_type="imaginary")
    mat = MaterialRun("name", spec=mat_spec, sample_type="virtual")

    # ensure that serialization does not change the MaterialRun
    copy = loads(dumps(mat))
    assert dumps(copy) == dumps(mat), \
        "Material run is modified by serialization or deserialization"
Пример #3
0
def test_complex_substitutions():
    """Make sure accounting works for realistic objects."""
    root = MaterialRun("root",
                       process=ProcessRun("root", spec=ProcessSpec("root")),
                       spec=MaterialSpec("root"))
    root.spec.process = root.process.spec
    input = MaterialRun("input",
                        process=ProcessRun("input", spec=ProcessSpec("input")),
                        spec=MaterialSpec("input"))
    input.spec.process = input.process.spec
    IngredientRun(process=root.process,
                  material=input,
                  spec=IngredientSpec("ingredient",
                                      process=root.process.spec,
                                      material=input.spec))
    param = ParameterTemplate("Param", bounds=RealBounds(-1, 1, "m"))
    root.process.spec.template = ProcessTemplate("Proc", parameters=[param])
    root.process.parameters.append(
        Parameter("Param", value=NormalReal(0, 1, 'm'), template=param))

    links = flatten(root, scope="test-scope")
    index = make_index(links)
    rebuild = substitute_objects(links, index, inplace=True)
    rebuilt_root = next(x for x in rebuild
                        if x.name == root.name and x.typ == root.typ)
    all_objs = recursive_flatmap(rebuilt_root,
                                 func=lambda x: [x],
                                 unidirectional=False)
    unique = [x for i, x in enumerate(all_objs) if i == all_objs.index(x)]
    assert not any(isinstance(x, LinkByUID) for x in unique), "All are objects"
    assert len(links) == len(unique), "Objects are missing"
def test_invalid_assignment():
    """Invalid assignments to `process` or `spec` throw a TypeError."""
    with pytest.raises(TypeError):
        MaterialRun(name=12)
    with pytest.raises(TypeError):
        MaterialRun("name", spec=ProcessRun("a process"))
    with pytest.raises(TypeError):
        MaterialRun("name", process=MaterialSpec("a spec"))
def test_template_access():
    """A material run's template should be equal to its spec's template."""
    template = MaterialTemplate("material template", uids={'id': str(uuid4())})
    spec = MaterialSpec("A spec", uids={'id': str(uuid4())}, template=template)
    mat = MaterialRun("A run", uids=['id', str(uuid4())], spec=spec)
    assert mat.template == template

    mat.spec = LinkByUID.from_entity(spec)
    assert mat.template is None
def test_dependencies():
    """Test that dependency lists make sense."""
    ps = ProcessSpec(name="ps")
    pr = ProcessRun(name="pr", spec=ps)
    ms = MaterialSpec(name="ms", process=ps)
    mr = MaterialRun(name="mr", spec=ms, process=pr)

    assert ps not in mr.all_dependencies()
    assert pr in mr.all_dependencies()
    assert ms in mr.all_dependencies()
def test_build():
    """Test that build recreates the material."""
    spec = MaterialSpec("A spec",
                        properties=PropertyAndConditions(
                            property=Property("a property", value=NominalReal(3, ''))),
                        tags=["a tag"])
    mat = MaterialRun(name="a material", spec=spec)
    mat_dict = mat.as_dict()
    mat_dict['spec'] = mat.spec.as_dict()
    assert MaterialRun.build(mat_dict) == mat
def test_equality():
    """Test that equality check works as expected."""
    spec = MaterialSpec(
        "A spec",
        properties=PropertyAndConditions(
            property=Property("a property", value=NominalReal(3, ''))),
        tags=["a tag"])
    mat1 = MaterialRun("A material", spec=spec)
    mat2 = MaterialRun("A material", spec=spec, tags=["A tag"])
    assert mat1 == deepcopy(mat1)
    assert mat1 != mat2
    assert mat1 != "A material"
def test_process_reassignment():
    """Test that a material can be assigned to a new process."""
    drying = ProcessRun("drying")
    welding = ProcessRun("welding")
    powder = MaterialRun("Powder", process=welding)

    assert powder.process == welding
    assert welding.output_material == powder

    powder.process = drying
    assert powder.process == drying
    assert drying.output_material == powder
    assert welding.output_material is None
def test_process_id_link():
    """Test that a process run can house a LinkByUID object, and that it survives serde."""
    uid = str(uuid4())
    proc_link = LinkByUID(scope='id', id=uid)
    mat_run = MaterialRun("Another cake", process=proc_link)
    copy_material = loads(dumps(mat_run))
    assert dumps(copy_material) == dumps(mat_run)
Пример #11
0
def test_dict_serialization():
    """Test that a dictionary can be serialized and then deserialized as a gemd object."""
    process = ProcessRun("A process")
    mat = MaterialRun("A material", process=process)
    meas = MeasurementRun("A measurement", material=mat)
    copy = loads(dumps(meas.as_dict()))
    assert copy == meas
def test_order_objects():
    """Test that sorting works when given objects."""
    unsorted = [MaterialRun("bar"), ProcessRun("foo")]
    sorted_list = sorted(unsorted, key=lambda x: writable_sort_order(x))

    assert isinstance(sorted_list[0], ProcessRun)
    assert isinstance(sorted_list[1], MaterialRun)
def test_exceptions():
    """Additional tests to get full coverage on exceptions."""
    with pytest.raises(ValueError):
        add_edge(MaterialRun("Input"), make_node("Output"))

    with pytest.raises(ValueError):
        add_edge(make_node("Input"),
                 MaterialRun("Output", spec=LinkByUID("Bad", "ID")))

    with pytest.raises(ValueError):
        add_measurement(make_node('Material'),
                        name='Measurement',
                        attributes=[UnsupportedAttribute("Spider-man")])

    with pytest.raises(ValueError):
        make_attribute(UnsupportedAttributeTemplate, 5)
Пример #14
0
def test_uid_deser():
    """Test that uids continue to be a CaseInsensitiveDict after deserialization."""
    material = MaterialRun("Input material", tags="input", uids={'Sample ID': '500-B'})
    ingredient = IngredientRun(material=material)
    ingredient_copy = loads(dumps(ingredient))
    assert isinstance(ingredient_copy.uids, CaseInsensitiveDict)
    assert ingredient_copy.material == material
    assert ingredient_copy.material.uids['sample id'] == material.uids['Sample ID']
Пример #15
0
def test_material_soft_link():
    """Test that a measurement run can link to a material run, and that it survives serde."""
    dye = MaterialRun("rhodamine",
                      file_links=FileLink(filename='a.csv', url='/a/path'))
    assert dye.measurements == [], "default value of .measurements should be an empty list"

    # The .measurements member should not be settable
    with pytest.raises(AttributeError):
        dye.measurements = [MeasurementRun("Dummy")]

    absorbance = MeasurementRun(name="Absorbance",
                                uids={'id': str(uuid4())},
                                properties=[
                                    Property(name='Abs at 500 nm',
                                             value=NominalReal(0.1, ''))
                                ])
    assert absorbance.material is None, "Measurements should have None as the material by default"
    absorbance.material = dye
    assert absorbance.material == dye, "Material not set correctly for measurement"
    assert dye.measurements == [
        absorbance
    ], "Soft-link from material to measurement not created"

    fluorescence = MeasurementRun(name="Fluorescence",
                                  uids={'id': str(uuid4())},
                                  properties=[
                                      Property(name='PL counts at 550 nm',
                                               value=NominalReal(30000, ''))
                                  ],
                                  material=dye)

    assert fluorescence.material == dye, "Material not set correctly for measurement"
    assert dye.measurements == [absorbance, fluorescence], \
        "Soft-link from material to measurements not created"

    assert loads(dumps(absorbance)) == absorbance, \
        "Measurement should remain unchanged when serialized"
    assert loads(dumps(fluorescence)) == fluorescence, \
        "Measurement should remain unchanged when serialized"

    assert 'measurements' in repr(dye)
    assert 'material' in repr(fluorescence)
    assert 'material' in repr(absorbance)

    subbed = substitute_links(dye)
    assert 'measurements' in repr(subbed)
Пример #16
0
def test_measurement_reassignment():
    """Check that a measurement run can be re-assigned to a new material run."""
    sample1 = MaterialRun("Sample 1")
    sample2 = MaterialRun("Sample 2")
    mass = MeasurementRun("Mass of sample", material=sample1)
    volume = MeasurementRun("Volume of sample", material=sample1)
    assert mass.material == sample1
    assert set(sample1.measurements) == {mass, volume}
    assert sample2.measurements == []

    mass.material = sample2
    assert mass.material == sample2
    assert sample1.measurements == [volume]
    assert sample2.measurements == [mass]

    mass.material = None
    assert mass.material is None
    assert sample2.measurements == []
Пример #17
0
def test_from_entity():
    """Test permutations of LinkByUID.from_entity arguments."""
    run = MaterialRun(name='leaf', process=ProcessRun(name='leaf proc'))
    assert LinkByUID.from_entity(run).scope == 'auto'
    assert LinkByUID.from_entity(run, scope='missing').scope == 'auto'
    assert len(run.uids) == 1

    run.uids['foo'] = 'bar'
    link1 = LinkByUID.from_entity(run, scope='foo')
    assert (link1.scope, link1.id) == ('foo', 'bar')

    with pytest.deprecated_call():
        assert LinkByUID.from_entity(run, 'foo').scope == 'foo'

    with pytest.deprecated_call():
        assert LinkByUID.from_entity(run, name='foo').scope == 'foo'

    with pytest.raises(ValueError):
        LinkByUID.from_entity(run, name='scope1', scope='scope2')
def test_make_index():
    """Test functionality of make_index method."""
    ps1 = ProcessSpec(name="hello", uids={"test_scope": "test_value"})
    pr1 = ProcessRun(
        name="world",
        spec=LinkByUID(scope="test_scope", id="test_value"),
        uids={"test_scope": "another_test_value",
              "other_test": "also_valid"
              },
    )
    ms1 = MaterialSpec(
        name="material",
        process=LinkByUID(scope="test_scope", id="test_value"),
        uids={"second_scope": "this_is_an_id"},
    )
    mr1 = MaterialRun(
        name="material_run",
        spec=LinkByUID(scope="second_scope", id="this_is_an_id"),
        process=LinkByUID(scope="test_scope", id="another_test_value"),
    )

    pr2 = ProcessRun(
        name="goodbye",
        spec=LinkByUID.from_entity(ps1),
        uids={"test_scope": "the_other_value"},
    )
    mr2 = MaterialRun(
        name="cruel",
        spec=LinkByUID.from_entity(ms1),
        process=LinkByUID.from_entity(pr2),
    )

    gems = [ps1, pr1, ms1, mr1, pr2, mr2]
    gem_index = make_index(gems)
    for gem in gems:
        for scope in gem.uids:
            assert (scope, gem.uids[scope]) in gem_index
            assert gem_index[(scope, gem.uids[scope])] == gem

    # Make sure substitute_objects can consume the index
    subbed = substitute_objects(mr1, gem_index)
    assert subbed.spec.uids == ms1.uids
Пример #19
0
def make_node(name: str,
              *,
              process_name: str = None,
              process_template: ProcessTemplate = None,
              material_template: MaterialTemplate = None) -> MaterialRun:
    """
    Generate a material-process spec-run quadruple.

    Parameters
    ----------
    name: str
        Name of the MaterialRun and MaterialSpec.
    process_name: str
        Name of the ProcessRun and ProcessSpec.  Defaults to
        `process_template.name` if `process_template` is defined, else `name`.
    process_template: ProcessTemplate
        ProcessTemplate for the quadruple.
    material_template: MaterialTemplate
        MaterialTemplate for the quadruple.

    Returns
    --------
    MaterialRun
        A MaterialRun with linked processes, specs and templates

    """
    if process_name is None:
        if process_template is None:
            process_name = name
        else:
            process_name = process_template.name

    my_process_spec = ProcessSpec(
        name=process_name,
        template=process_template
    )

    my_process_run = ProcessRun(
        name=process_name,
        spec=my_process_spec
    )

    my_mat_spec = MaterialSpec(
        name=name,
        process=my_process_spec,
        template=material_template
    )

    my_mat_run = MaterialRun(
        name=name,
        process=my_process_run,
        spec=my_mat_spec
    )
    return my_mat_run
Пример #20
0
def test_recursive_foreach():
    """Test that recursive foreach will actually walk through a material history."""
    mat_run = MaterialRun("foo")
    process_run = ProcessRun("bar")
    IngredientRun(process=process_run, material=mat_run)
    output = MaterialRun("material", process=process_run)

    # property templates are trickier than templates because they are referenced in attributes
    template = PropertyTemplate("prop", bounds=RealBounds(0, 1, ""))
    prop = Property("prop", value=NominalReal(1.0, ""), template=template)
    MeasurementRun("check", material=output, properties=prop)

    types = []
    recursive_foreach(output, lambda x: types.append(x.typ))

    expected = [
        "ingredient_run", "material_run", "material_run", "process_run",
        "measurement_run", "property_template"
    ]
    assert sorted(types) == sorted(expected)
Пример #21
0
def test_link_by_uid():
    """Test that linking works."""
    root = MaterialRun(name='root', process=ProcessRun(name='root proc'))
    leaf = MaterialRun(name='leaf', process=ProcessRun(name='leaf proc'))
    IngredientRun(process=root.process, material=leaf)
    IngredientRun(process=root.process,
                  material=LinkByUID.from_entity(leaf, scope='id'))

    # Paranoid assertions about equality's symmetry since it's implemented in 2 places
    assert root.process.ingredients[0].material == root.process.ingredients[
        1].material
    assert root.process.ingredients[0].material.__eq__(
        root.process.ingredients[1].material)
    assert root.process.ingredients[1].material.__eq__(
        root.process.ingredients[0].material)

    # Verify hash collision on equal LinkByUIDs
    assert LinkByUID.from_entity(leaf) in {LinkByUID.from_entity(leaf)}

    copy = loads(dumps(root))
    assert copy.process.ingredients[0].material == copy.process.ingredients[
        1].material
def test_process_run():
    """Test that a process run can house a material, and that it survives serde."""
    process_run = ProcessRun("Bake a cake", uids={'My_ID': str(17)})
    material_run = MaterialRun("A cake", process=process_run)

    # Check that a bi-directional link is established
    assert material_run.process == process_run
    assert process_run.output_material == material_run

    copy_material = loads(dumps(material_run))
    assert dumps(copy_material) == dumps(material_run)

    assert 'output_material' in repr(process_run)
    assert 'process' in repr(material_run)
Пример #23
0
def test_ingredient_run():
    """Tests that a process can house an ingredient, and that pairing survives serialization."""
    # Create a ProcessSpec
    proc_run = ProcessRun(name="a process spec", tags=["tag1", "tag2"])
    ingred_run = IngredientRun(material=MaterialRun(name='Raw'),
                               process=proc_run)

    # Make copies of both specs
    proc_run_copy = loads(dumps(proc_run))

    assert proc_run_copy == proc_run, "Full structure wasn't preserved across serialization"

    assert 'process' in repr(ingred_run)
    assert 'ingredients' in repr(proc_run)
Пример #24
0
def test_many_ingredients():
    """Test that ingredients remain connected to processes when round-robined through json."""
    proc = ProcessRun("foo", spec=ProcessSpec("sfoo"))
    expected = []
    for i in range(10):
        mat = MaterialRun(name=str(i), spec=MaterialSpec("s{}".format(i)))
        i_spec = IngredientSpec(name="i{}".format(i), material=mat.spec, process=proc.spec)
        IngredientRun(process=proc, material=mat, spec=i_spec)
        expected.append("i{}".format(i))

    reloaded = loads(dumps(proc))
    assert len(list(reloaded.ingredients)) == 10
    names = [x.name for x in reloaded.ingredients]
    assert sorted(names) == sorted(expected)
def test_equality():
    """Test that equality check works as expected."""
    spec = MaterialSpec("A spec",
                        properties=PropertyAndConditions(
                            property=Property("a property", value=NominalReal(3, ''))),
                        tags=["a tag"])
    mat1 = MaterialRun("A material", spec=spec)
    mat2 = MaterialRun("A material", spec=spec, tags=["A tag"])
    assert mat1 == deepcopy(mat1)
    assert mat1 != mat2
    assert mat1 != "A material"

    mat3 = deepcopy(mat1)
    assert mat1 == mat3, "Copy somehow failed"
    MeasurementRun("A measurement", material=mat3)
    assert mat1 != mat3

    mat4 = deepcopy(mat3)
    assert mat4 == mat3, "Copy somehow failed"
    mat4.measurements[0].tags.append('A tag')
    assert mat4 != mat3

    mat5 = next(x for x in flatten(mat4, 'test-scope') if isinstance(x, MaterialRun))
    assert mat5 == mat4, "Flattening removes measurement references, but that's okay"
def test_dictionary_substitution():
    """substitute_objects() should substitute LinkByUIDs that occur in dict keys and values."""
    proc = ProcessRun("A process", uids={'id': '123'})
    mat = MaterialRun("A material", uids={'generic id': '38f8jf'})

    proc_link = LinkByUID.from_entity(proc)
    mat_link = LinkByUID.from_entity(mat)
    index = {(mat_link.scope.lower(), mat_link.id): mat,
             (proc_link.scope.lower(), proc_link.id): proc}

    test_dict = {LinkByUID.from_entity(proc): LinkByUID.from_entity(mat)}
    subbed = substitute_objects(test_dict, index)
    k, v = next((k, v) for k, v in subbed.items())
    assert k == proc
    assert v == mat
Пример #27
0
def test_more_iterable_types():
    """Verify recursive_foreach behaves for additional iterable types."""
    obj = MaterialRun("foo", tags=["1", "2", "3"])

    assert "1" in obj.tags
    recursive_foreach({obj}, lambda x: x.tags.remove("1"))
    assert "1" not in obj.tags

    dct = {obj: obj}
    assert "2" in obj.tags
    recursive_foreach(dct.keys(), lambda x: x.tags.remove("2"))
    assert "2" not in obj.tags

    assert "3" in obj.tags
    recursive_foreach(dct.values(), lambda x: x.tags.remove("3"))
    assert "3" not in obj.tags
Пример #28
0
def test_scope_substitution():
    """Test that the native id gets serialized, when specified."""
    native_id = 'id1'
    # Create measurement and material with two ids
    mat = MaterialRun("A material", uids={
        native_id: str(uuid4()), "an_id": str(uuid4()), "another_id": str(uuid4())})
    meas = MeasurementRun("A measurement", material=mat, uids={
        "some_id": str(uuid4()), native_id: str(uuid4()), "an_id": str(uuid4())})

    # Turn the material pointer into a LinkByUID using native_id
    subbed = substitute_links(meas, scope=native_id)
    assert subbed.material == LinkByUID.from_entity(mat, scope=native_id)

    # Put the measurement into a list and convert that into a LinkByUID using native_id
    measurements_list = [meas]
    subbed = substitute_links(measurements_list, scope=native_id)
    assert subbed == [LinkByUID.from_entity(meas, scope=native_id)]
def test_table():
    """Convert a dictionary to a pandas dataframe and then to a table."""
    material = MaterialRun("name")
    df = pd.DataFrame.from_records(data)
    result = ingest_table(material, df)
    assert isinstance(result, MaterialRun)
    assert len(result.measurements) == len(data)

    filename = "/tmp/table_example.json"

    # write to a file (for human inspection)
    with open(filename, "w") as f:
        dump(result, f, indent=2)

    # read it back
    with open(filename, "r") as f:
        copy = load(f)

    assert isinstance(copy, MaterialRun)
Пример #30
0
def test_thin_dumps():
    """Test that thin_dumps turns pointers into links."""
    mat = MaterialRun("The actual material")
    meas_spec = MeasurementSpec("measurement", uids={'my_scope': '324324'})
    meas = MeasurementRun("The measurement", spec=meas_spec, material=mat)

    thin_copy = MeasurementRun.build(json.loads(GEMDJson().thin_dumps(meas)))
    assert isinstance(thin_copy, MeasurementRun)
    assert isinstance(thin_copy.material, LinkByUID)
    assert isinstance(thin_copy.spec, LinkByUID)
    assert thin_copy.spec.id == meas_spec.uids['my_scope']

    # Check that LinkByUID objects are correctly converted their JSON equivalent
    expected_json = '{"id": "my_id", "scope": "scope", "type": "link_by_uid"}'
    assert GEMDJson().thin_dumps(LinkByUID('scope', 'my_id')) == expected_json

    # Check that objects lacking .uid attributes will raise an exception when dumped
    with pytest.raises(TypeError):
        GEMDJson().thin_dumps({{'key': 'value'}})