| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| import os |
| from io import StringIO |
| |
| import pytest |
| |
| from buildstream import _yaml, Node, ProvenanceInformation, SequenceNode |
| from buildstream.exceptions import LoadErrorReason |
| from buildstream._exceptions import LoadError |
| |
| |
| DATA_DIR = os.path.join( |
| os.path.dirname(os.path.realpath(__file__)), |
| "yaml", |
| ) |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_load_yaml(datafiles): |
| |
| filename = os.path.join(datafiles, "basics.yaml") |
| |
| loaded = _yaml.load(filename, shortname=None) |
| assert loaded.get_str("kind") == "pony" |
| |
| |
| def assert_provenance(filename, line, col, node): |
| provenance = node.get_provenance() |
| |
| assert isinstance(provenance, ProvenanceInformation) |
| |
| assert provenance._shortname == filename |
| assert provenance._line == line |
| assert provenance._col == col |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_basic_provenance(datafiles): |
| |
| filename = os.path.join(datafiles, "basics.yaml") |
| |
| loaded = _yaml.load(filename, shortname=None) |
| assert loaded.get_str("kind") == "pony" |
| |
| assert_provenance(filename, 1, 0, loaded) |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_member_provenance(datafiles): |
| |
| filename = os.path.join(datafiles, "basics.yaml") |
| |
| loaded = _yaml.load(filename, shortname=None) |
| assert loaded.get_str("kind") == "pony" |
| assert_provenance(filename, 2, 13, loaded.get_scalar("description")) |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_element_provenance(datafiles): |
| |
| filename = os.path.join(datafiles, "basics.yaml") |
| |
| loaded = _yaml.load(filename, shortname=None) |
| assert loaded.get_str("kind") == "pony" |
| assert_provenance(filename, 5, 2, loaded.get_sequence("moods").scalar_at(1)) |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_mapping_validate_keys(datafiles): |
| |
| valid = os.path.join(datafiles, "basics.yaml") |
| invalid = os.path.join(datafiles, "invalid.yaml") |
| |
| base = _yaml.load(valid, shortname=None) |
| |
| base.validate_keys(["kind", "description", "moods", "children", "extra"]) |
| |
| base = _yaml.load(invalid, shortname=None) |
| |
| with pytest.raises(LoadError) as exc: |
| base.validate_keys(["kind", "description", "moods", "children", "extra"]) |
| |
| assert exc.value.reason == LoadErrorReason.INVALID_DATA |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_node_get(datafiles): |
| |
| filename = os.path.join(datafiles, "basics.yaml") |
| |
| base = _yaml.load(filename, shortname=None) |
| assert base.get_str("kind") == "pony" |
| |
| children = base.get_sequence("children") |
| assert isinstance(children, SequenceNode) |
| assert len(children) == 7 |
| |
| child = base.get_sequence("children").mapping_at(6) |
| assert_provenance(filename, 20, 8, child.get_scalar("mood")) |
| |
| extra = base.get_mapping("extra") |
| with pytest.raises(LoadError) as exc: |
| extra.get_mapping("old") |
| |
| assert exc.value.reason == LoadErrorReason.INVALID_DATA |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_node_set(datafiles): |
| |
| filename = os.path.join(datafiles, "basics.yaml") |
| |
| base = _yaml.load(filename, shortname=None) |
| |
| assert "mother" not in base |
| base["mother"] = "snow white" |
| assert base.get_str("mother") == "snow white" |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_node_set_overwrite(datafiles): |
| |
| filename = os.path.join(datafiles, "basics.yaml") |
| |
| base = _yaml.load(filename, shortname=None) |
| |
| # Overwrite a string |
| assert base.get_str("kind") == "pony" |
| base["kind"] = "cow" |
| assert base.get_str("kind") == "cow" |
| |
| # Overwrite a list as a string |
| assert base.get_str_list("moods") == ["happy", "sad"] |
| base["moods"] = "unemotional" |
| assert base.get_str("moods") == "unemotional" |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_node_set_list_element(datafiles): |
| |
| filename = os.path.join(datafiles, "basics.yaml") |
| |
| base = _yaml.load(filename, shortname=None) |
| |
| assert base.get_str_list("moods") == ["happy", "sad"] |
| base.get_sequence("moods")[0] = "confused" |
| |
| assert base.get_str_list("moods") == ["confused", "sad"] |
| |
| |
| # Really this is testing _yaml.node_copy(), we want to |
| # be sure that compositing values still preserves the original |
| # values in the copied dict. |
| # |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_composite_preserve_originals(datafiles): |
| |
| filename = os.path.join(datafiles, "basics.yaml") |
| overlayfile = os.path.join(datafiles, "composite.yaml") |
| |
| base = _yaml.load(filename, shortname=None) |
| overlay = _yaml.load(overlayfile, shortname=None) |
| base_copy = base.clone() |
| overlay._composite(base_copy) |
| |
| copy_extra = base_copy.get_mapping("extra") |
| orig_extra = base.get_mapping("extra") |
| |
| # Test that the node copy has the overridden value... |
| assert copy_extra.get_str("old") == "override" |
| |
| # But the original node is not effected by the override. |
| assert orig_extra.get_str("old") == "new" |
| |
| |
| # Tests for list composition |
| # |
| # Each test composits a filename on top of basics.yaml, and tests |
| # the toplevel children list at the specified index |
| # |
| # Parameters: |
| # filename: The file to composite on top of basics.yaml |
| # index: The index in the children list |
| # length: The expected length of the children list |
| # mood: The expected value of the mood attribute of the dictionary found at index in children |
| # prov_file: The expected provenance filename of "mood" |
| # prov_line: The expected provenance line of "mood" |
| # prov_col: The expected provenance column of "mood" |
| # |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| @pytest.mark.parametrize( |
| "filename,index,length,mood,prov_file,prov_line,prov_col", |
| [ |
| # Test results of compositing with the (<) prepend directive |
| ("listprepend.yaml", 0, 9, "prepended1", "listprepend.yaml", 5, 10), |
| ("listprepend.yaml", 1, 9, "prepended2", "listprepend.yaml", 7, 10), |
| ("listprepend.yaml", 2, 9, "silly", "basics.yaml", 8, 8), |
| ("listprepend.yaml", 8, 9, "sleepy", "basics.yaml", 20, 8), |
| # Test results of compositing with the (>) append directive |
| ("listappend.yaml", 7, 9, "appended1", "listappend.yaml", 5, 10), |
| ("listappend.yaml", 8, 9, "appended2", "listappend.yaml", 7, 10), |
| ("listappend.yaml", 0, 9, "silly", "basics.yaml", 8, 8), |
| ("listappend.yaml", 6, 9, "sleepy", "basics.yaml", 20, 8), |
| # Test results of compositing with both (<) and (>) directives |
| ("listappendprepend.yaml", 0, 11, "prepended1", "listappendprepend.yaml", 5, 10), |
| ("listappendprepend.yaml", 1, 11, "prepended2", "listappendprepend.yaml", 7, 10), |
| ("listappendprepend.yaml", 2, 11, "silly", "basics.yaml", 8, 8), |
| ("listappendprepend.yaml", 8, 11, "sleepy", "basics.yaml", 20, 8), |
| ("listappendprepend.yaml", 9, 11, "appended1", "listappendprepend.yaml", 10, 10), |
| ("listappendprepend.yaml", 10, 11, "appended2", "listappendprepend.yaml", 12, 10), |
| # Test results of compositing with the (=) overwrite directive |
| ("listoverwrite.yaml", 0, 2, "overwrite1", "listoverwrite.yaml", 5, 10), |
| ("listoverwrite.yaml", 1, 2, "overwrite2", "listoverwrite.yaml", 7, 10), |
| # Test results of compositing without any directive, implicitly overwriting |
| ("implicitoverwrite.yaml", 0, 2, "overwrite1", "implicitoverwrite.yaml", 4, 8), |
| ("implicitoverwrite.yaml", 1, 2, "overwrite2", "implicitoverwrite.yaml", 6, 8), |
| ], |
| ) |
| def test_list_composition(datafiles, filename, tmpdir, index, length, mood, prov_file, prov_line, prov_col): |
| base_file = os.path.join(datafiles, "basics.yaml") |
| overlay_file = os.path.join(datafiles, filename) |
| |
| base = _yaml.load(base_file, shortname="basics.yaml") |
| overlay = _yaml.load(overlay_file, shortname=filename) |
| |
| overlay._composite(base) |
| |
| children = base.get_sequence("children") |
| assert len(children) == length |
| child = children.mapping_at(index) |
| |
| assert child.get_str("mood") == mood |
| assert_provenance(prov_file, prov_line, prov_col, child.get_node("mood")) |
| |
| |
| # Test that overwriting a list with an empty list works as expected. |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_list_deletion(datafiles): |
| base = os.path.join(datafiles, "basics.yaml") |
| overlay = os.path.join(datafiles, "listoverwriteempty.yaml") |
| |
| base = _yaml.load(base, shortname="basics.yaml") |
| overlay = _yaml.load(overlay, shortname="listoverwriteempty.yaml") |
| overlay._composite(base) |
| |
| children = base.get_sequence("children") |
| assert not children |
| |
| |
| # Tests for deep list composition |
| # |
| # Same as test_list_composition(), but adds an additional file |
| # in between so that lists are composited twice. |
| # |
| # This test will to two iterations for each parameter |
| # specification, expecting the same results |
| # |
| # First iteration: |
| # composited = basics.yaml & filename1 |
| # composited = composited & filename2 |
| # |
| # Second iteration: |
| # composited = filename1 & filename2 |
| # composited = basics.yaml & composited |
| # |
| # Parameters: |
| # filename1: The file to composite on top of basics.yaml |
| # filename2: The file to composite on top of filename1 |
| # index: The index in the children list |
| # length: The expected length of the children list |
| # mood: The expected value of the mood attribute of the dictionary found at index in children |
| # prov_file: The expected provenance filename of "mood" |
| # prov_line: The expected provenance line of "mood" |
| # prov_col: The expected provenance column of "mood" |
| # |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| @pytest.mark.parametrize( |
| "filename1,filename2,index,length,mood,prov_file,prov_line,prov_col", |
| [ |
| # Test results of compositing literal list with (>) and then (<) |
| ("listprepend.yaml", "listappend.yaml", 0, 11, "prepended1", "listprepend.yaml", 5, 10), |
| ("listprepend.yaml", "listappend.yaml", 1, 11, "prepended2", "listprepend.yaml", 7, 10), |
| ("listprepend.yaml", "listappend.yaml", 2, 11, "silly", "basics.yaml", 8, 8), |
| ("listprepend.yaml", "listappend.yaml", 8, 11, "sleepy", "basics.yaml", 20, 8), |
| ("listprepend.yaml", "listappend.yaml", 9, 11, "appended1", "listappend.yaml", 5, 10), |
| ("listprepend.yaml", "listappend.yaml", 10, 11, "appended2", "listappend.yaml", 7, 10), |
| # Test results of compositing literal list with (<) and then (>) |
| ("listappend.yaml", "listprepend.yaml", 0, 11, "prepended1", "listprepend.yaml", 5, 10), |
| ("listappend.yaml", "listprepend.yaml", 1, 11, "prepended2", "listprepend.yaml", 7, 10), |
| ("listappend.yaml", "listprepend.yaml", 2, 11, "silly", "basics.yaml", 8, 8), |
| ("listappend.yaml", "listprepend.yaml", 8, 11, "sleepy", "basics.yaml", 20, 8), |
| ("listappend.yaml", "listprepend.yaml", 9, 11, "appended1", "listappend.yaml", 5, 10), |
| ("listappend.yaml", "listprepend.yaml", 10, 11, "appended2", "listappend.yaml", 7, 10), |
| # Test results of compositing literal list with (>) and then (>) |
| ("listappend.yaml", "secondappend.yaml", 0, 11, "silly", "basics.yaml", 8, 8), |
| ("listappend.yaml", "secondappend.yaml", 6, 11, "sleepy", "basics.yaml", 20, 8), |
| ("listappend.yaml", "secondappend.yaml", 7, 11, "appended1", "listappend.yaml", 5, 10), |
| ("listappend.yaml", "secondappend.yaml", 8, 11, "appended2", "listappend.yaml", 7, 10), |
| ("listappend.yaml", "secondappend.yaml", 9, 11, "secondappend1", "secondappend.yaml", 5, 10), |
| ("listappend.yaml", "secondappend.yaml", 10, 11, "secondappend2", "secondappend.yaml", 7, 10), |
| # Test results of compositing literal list with (>) and then (>) |
| ("listprepend.yaml", "secondprepend.yaml", 0, 11, "secondprepend1", "secondprepend.yaml", 5, 10), |
| ("listprepend.yaml", "secondprepend.yaml", 1, 11, "secondprepend2", "secondprepend.yaml", 7, 10), |
| ("listprepend.yaml", "secondprepend.yaml", 2, 11, "prepended1", "listprepend.yaml", 5, 10), |
| ("listprepend.yaml", "secondprepend.yaml", 3, 11, "prepended2", "listprepend.yaml", 7, 10), |
| ("listprepend.yaml", "secondprepend.yaml", 4, 11, "silly", "basics.yaml", 8, 8), |
| ("listprepend.yaml", "secondprepend.yaml", 10, 11, "sleepy", "basics.yaml", 20, 8), |
| # Test results of compositing literal list with (>) or (<) and then another literal list |
| ("listappend.yaml", "implicitoverwrite.yaml", 0, 2, "overwrite1", "implicitoverwrite.yaml", 4, 8), |
| ("listappend.yaml", "implicitoverwrite.yaml", 1, 2, "overwrite2", "implicitoverwrite.yaml", 6, 8), |
| ("listprepend.yaml", "implicitoverwrite.yaml", 0, 2, "overwrite1", "implicitoverwrite.yaml", 4, 8), |
| ("listprepend.yaml", "implicitoverwrite.yaml", 1, 2, "overwrite2", "implicitoverwrite.yaml", 6, 8), |
| # Test results of compositing literal list with (>) or (<) and then an explicit (=) overwrite |
| ("listappend.yaml", "listoverwrite.yaml", 0, 2, "overwrite1", "listoverwrite.yaml", 5, 10), |
| ("listappend.yaml", "listoverwrite.yaml", 1, 2, "overwrite2", "listoverwrite.yaml", 7, 10), |
| ("listprepend.yaml", "listoverwrite.yaml", 0, 2, "overwrite1", "listoverwrite.yaml", 5, 10), |
| ("listprepend.yaml", "listoverwrite.yaml", 1, 2, "overwrite2", "listoverwrite.yaml", 7, 10), |
| # Test results of compositing literal list an explicit overwrite (=) and then with (>) or (<) |
| ("listoverwrite.yaml", "listappend.yaml", 0, 4, "overwrite1", "listoverwrite.yaml", 5, 10), |
| ("listoverwrite.yaml", "listappend.yaml", 1, 4, "overwrite2", "listoverwrite.yaml", 7, 10), |
| ("listoverwrite.yaml", "listappend.yaml", 2, 4, "appended1", "listappend.yaml", 5, 10), |
| ("listoverwrite.yaml", "listappend.yaml", 3, 4, "appended2", "listappend.yaml", 7, 10), |
| ("listoverwrite.yaml", "listprepend.yaml", 0, 4, "prepended1", "listprepend.yaml", 5, 10), |
| ("listoverwrite.yaml", "listprepend.yaml", 1, 4, "prepended2", "listprepend.yaml", 7, 10), |
| ("listoverwrite.yaml", "listprepend.yaml", 2, 4, "overwrite1", "listoverwrite.yaml", 5, 10), |
| ("listoverwrite.yaml", "listprepend.yaml", 3, 4, "overwrite2", "listoverwrite.yaml", 7, 10), |
| ], |
| ) |
| def test_list_composition_twice( |
| datafiles, tmpdir, filename1, filename2, index, length, mood, prov_file, prov_line, prov_col |
| ): |
| file_base = os.path.join(datafiles, "basics.yaml") |
| file1 = os.path.join(datafiles, filename1) |
| file2 = os.path.join(datafiles, filename2) |
| |
| ##################### |
| # Round 1 - Fight ! |
| ##################### |
| base = _yaml.load(file_base, shortname="basics.yaml") |
| overlay1 = _yaml.load(file1, shortname=filename1) |
| overlay2 = _yaml.load(file2, shortname=filename2) |
| |
| overlay1._composite(base) |
| overlay2._composite(base) |
| |
| children = base.get_sequence("children") |
| assert len(children) == length |
| child = children.mapping_at(index) |
| |
| assert child.get_str("mood") == mood |
| assert_provenance(prov_file, prov_line, prov_col, child.get_node("mood")) |
| |
| ##################### |
| # Round 2 - Fight ! |
| ##################### |
| base = _yaml.load(file_base, shortname="basics.yaml") |
| overlay1 = _yaml.load(file1, shortname=filename1) |
| overlay2 = _yaml.load(file2, shortname=filename2) |
| |
| overlay2._composite(overlay1) |
| overlay1._composite(base) |
| |
| children = base.get_sequence("children") |
| assert len(children) == length |
| child = children.mapping_at(index) |
| |
| assert child.get_str("mood") == mood |
| assert_provenance(prov_file, prov_line, prov_col, child.get_node("mood")) |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_convert_value_to_string(datafiles): |
| conf_file = os.path.join(datafiles, "convert_value_to_str.yaml") |
| |
| # Run file through yaml to convert it |
| test_dict = _yaml.load(conf_file, shortname=None) |
| |
| user_config = test_dict.get_str("Test1") |
| assert isinstance(user_config, str) |
| assert user_config == "1_23_4" |
| |
| user_config = test_dict.get_str("Test2") |
| assert isinstance(user_config, str) |
| assert user_config == "1.23.4" |
| |
| user_config = test_dict.get_str("Test3") |
| assert isinstance(user_config, str) |
| assert user_config == "1.20" |
| |
| user_config = test_dict.get_str("Test4") |
| assert isinstance(user_config, str) |
| assert user_config == "OneTwoThree" |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_value_doesnt_match_expected(datafiles): |
| conf_file = os.path.join(datafiles, "convert_value_to_str.yaml") |
| |
| # Run file through yaml to convert it |
| test_dict = _yaml.load(conf_file, shortname=None) |
| |
| with pytest.raises(LoadError) as exc: |
| test_dict.get_int("Test4") |
| assert exc.value.reason == LoadErrorReason.INVALID_DATA |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_roundtrip_dump(datafiles): |
| filename = os.path.join(datafiles, "roundtrip-test.yaml") |
| with open(filename, "r", encoding="utf-8") as fh: |
| rt_raw = fh.read() |
| |
| rt_loaded = _yaml.roundtrip_load(filename) |
| |
| # Now walk the loaded data structure, checking for ints etc. |
| def walk_node(node): |
| for v in node.values(): |
| if isinstance(v, list): |
| walk_list(v) |
| elif isinstance(v, dict): |
| walk_node(v) |
| else: |
| assert isinstance(v, str) |
| |
| def walk_list(l): |
| for v in l: |
| if isinstance(v, list): |
| walk_list(v) |
| elif isinstance(v, dict): |
| walk_node(v) |
| else: |
| assert isinstance(v, str) |
| |
| walk_node(rt_loaded) |
| |
| outfile = StringIO() |
| _yaml.roundtrip_dump(rt_loaded, file=outfile) |
| rt_back = outfile.getvalue() |
| |
| assert rt_raw == rt_back |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| @pytest.mark.parametrize( |
| "case", |
| [ |
| ["a", "b", "c"], |
| ["foo", 1], |
| ["stuff", 0, "colour"], |
| ["bird", 0, 1], |
| ], |
| ) |
| def test_node_find_target(datafiles, case): |
| filename = os.path.join(datafiles, "traversal.yaml") |
| # We set copy_tree in order to ensure that the nodes in `loaded` |
| # are not the same nodes as in `prov.toplevel` |
| loaded = _yaml.load(filename, shortname=None, copy_tree=True) |
| |
| prov = loaded.get_provenance() |
| |
| toplevel = prov._toplevel |
| |
| assert toplevel is not loaded |
| |
| # Walk down the node tree, with insider knowledge of how nodes are |
| # laid out. Client code should never do this. |
| def _walk(node, entry, rest): |
| if rest: |
| if isinstance(entry, int): |
| new_node = node.node_at(entry) |
| else: |
| new_node = node.get_node(entry) |
| |
| return _walk(new_node, rest[0], rest[1:]) |
| else: |
| if isinstance(entry, int): |
| return node.node_at(entry) |
| return node.get_node(entry) |
| |
| want = _walk(loaded, case[0], case[1:]) |
| found_path = toplevel._find(want) |
| |
| assert case == found_path |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_node_find_target_fails(datafiles): |
| filename = os.path.join(datafiles, "traversal.yaml") |
| loaded = _yaml.load(filename, shortname=None, copy_tree=True) |
| |
| brand_new = Node.from_dict({}) |
| |
| assert loaded._find(brand_new) is None |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| @pytest.mark.parametrize( |
| "filename, provenance", |
| [ |
| ("list-of-dict.yaml", "list-of-dict.yaml [line 2 column 2]"), |
| ("list-of-list.yaml", "list-of-list.yaml [line 2 column 2]"), |
| ], |
| ids=["list-of-dict", "list-of-list"], |
| ) |
| def test_get_str_list_invalid(datafiles, filename, provenance): |
| conf_file = os.path.join(datafiles, filename) |
| |
| base = _yaml.load(conf_file, shortname=None) |
| |
| with pytest.raises(LoadError) as exc: |
| base.get_str_list("list") |
| assert exc.value.reason == LoadErrorReason.INVALID_DATA |
| assert provenance in str(exc.value) |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_get_str_list_default_none(datafiles): |
| conf_file = os.path.join(datafiles, "list-of-dict.yaml") |
| |
| base = _yaml.load(conf_file, shortname=None) |
| |
| # There is no "pony" key here, assert that the default return is smooth |
| strings = base.get_str_list("pony", None) |
| assert strings is None |
| |
| |
| @pytest.mark.datafiles(os.path.join(DATA_DIR)) |
| def test_mapping_node_assign_none(datafiles): |
| conf_file = os.path.join(datafiles, "dictionary.yaml") |
| dump_file = os.path.join(datafiles, "dictionary-dump.yaml") |
| |
| base = _yaml.load(conf_file, shortname=None) |
| nested = base.get_mapping("nested") |
| nested["ref"] = None |
| |
| # Check that we have successfully set the ref to None |
| value = nested.get_scalar("ref") |
| assert value.is_none() |
| |
| # Without saving and loading, our None value is retained in memory |
| stripped = base.strip_node_info() |
| assert stripped["nested"]["ref"] is None |
| |
| # Save and load |
| _yaml.roundtrip_dump(base, dump_file) |
| loaded = _yaml.load(dump_file, shortname=None) |
| loaded_nested = loaded.get_mapping("nested") |
| value = loaded_nested.get_scalar("ref") |
| |
| # The loaded value will be an empty string, because we don't recognize None |
| # value representations in YAML |
| assert value.as_str() == "" |