2014-02-12 14:30:20 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2014-11-21 19:05:13 +00:00
|
|
|
# Import python libs
|
2017-12-15 18:14:18 +00:00
|
|
|
from __future__ import absolute_import, print_function, unicode_literals
|
2017-02-27 15:59:04 +00:00
|
|
|
from textwrap import dedent
|
2014-11-21 19:05:13 +00:00
|
|
|
|
2014-02-12 14:30:20 +00:00
|
|
|
# Import Salt Testing libs
|
2017-02-27 13:58:07 +00:00
|
|
|
from tests.support.unit import skipIf, TestCase
|
2014-02-17 12:22:32 +00:00
|
|
|
|
2014-02-12 14:30:20 +00:00
|
|
|
# Import 3rd party libs
|
|
|
|
import jinja2
|
Use explicit unicode strings + break up salt.utils
This PR is part of what will be an ongoing effort to use explicit
unicode strings in Salt. Because Python 3 does not suport Python 2's raw
unicode string syntax (i.e. `ur'\d+'`), we must use
`salt.utils.locales.sdecode()` to ensure that the raw string is unicode.
However, because of how `salt/utils/__init__.py` has evolved into the
hulking monstrosity it is today, this means importing a large module in
places where it is not needed, which could negatively impact
performance. For this reason, this PR also breaks out some of the
functions from `salt/utils/__init__.py` into new/existing modules under
`salt/utils/`. The long term goal will be that the modules within this
directory do not depend on importing `salt.utils`.
A summary of the changes in this PR is as follows:
* Moves the following functions from `salt.utils` to new locations
(including a deprecation warning if invoked from `salt.utils`):
`to_bytes`, `to_str`, `to_unicode`, `str_to_num`, `is_quoted`,
`dequote`, `is_hex`, `is_bin_str`, `rand_string`,
`contains_whitespace`, `clean_kwargs`, `invalid_kwargs`, `which`,
`which_bin`, `path_join`, `shlex_split`, `rand_str`, `is_windows`,
`is_proxy`, `is_linux`, `is_darwin`, `is_sunos`, `is_smartos`,
`is_smartos_globalzone`, `is_smartos_zone`, `is_freebsd`, `is_netbsd`,
`is_openbsd`, `is_aix`
* Moves the functions already deprecated by @rallytime to the bottom of
`salt/utils/__init__.py` for better organization, so we can keep the
deprecated ones separate from the ones yet to be deprecated as we
continue to break up `salt.utils`
* Updates `salt/*.py` and all files under `salt/client/` to use explicit
unicode string literals.
* Gets rid of implicit imports of `salt.utils` (e.g. `from salt.utils
import foo` becomes `import salt.utils.foo as foo`).
* Renames the `test.rand_str` function to `test.random_hash` to more
accurately reflect what it does
* Modifies `salt.utils.stringutils.random()` (née `salt.utils.rand_string()`)
such that it returns a string matching the passed size. Previously
this function would get `size` bytes from `os.urandom()`,
base64-encode it, and return the result, which would in most cases not
be equal to the passed size.
2017-07-25 01:47:15 +00:00
|
|
|
from salt.ext import six
|
2014-02-12 14:30:20 +00:00
|
|
|
|
|
|
|
# Import salt libs
|
2017-03-21 17:15:36 +00:00
|
|
|
import salt.serializers.configparser as configparser
|
|
|
|
import salt.serializers.json as json
|
|
|
|
import salt.serializers.yaml as yaml
|
|
|
|
import salt.serializers.yamlex as yamlex
|
|
|
|
import salt.serializers.msgpack as msgpack
|
|
|
|
import salt.serializers.python as python
|
2017-11-22 14:27:14 +00:00
|
|
|
import salt.serializers.toml as toml
|
2017-06-27 08:53:35 +00:00
|
|
|
from salt.serializers.yaml import EncryptedString
|
2015-05-28 23:48:00 +00:00
|
|
|
from salt.serializers import SerializationError
|
2014-02-12 14:30:20 +00:00
|
|
|
from salt.utils.odict import OrderedDict
|
|
|
|
|
2017-02-24 19:02:08 +00:00
|
|
|
# Import test support libs
|
|
|
|
from tests.support.helpers import flaky
|
|
|
|
|
2014-02-12 14:30:20 +00:00
|
|
|
SKIP_MESSAGE = '%s is unavailable, do prerequisites have been met?'
|
|
|
|
|
|
|
|
|
2017-02-24 19:02:08 +00:00
|
|
|
@flaky(condition=six.PY3)
|
2014-02-12 14:30:20 +00:00
|
|
|
class TestSerializers(TestCase):
|
|
|
|
@skipIf(not json.available, SKIP_MESSAGE % 'json')
|
|
|
|
def test_serialize_json(self):
|
|
|
|
data = {
|
|
|
|
"foo": "bar"
|
|
|
|
}
|
|
|
|
serialized = json.serialize(data)
|
|
|
|
assert serialized == '{"foo": "bar"}', serialized
|
|
|
|
|
|
|
|
deserialized = json.deserialize(serialized)
|
|
|
|
assert deserialized == data, deserialized
|
|
|
|
|
|
|
|
@skipIf(not yaml.available, SKIP_MESSAGE % 'yaml')
|
|
|
|
def test_serialize_yaml(self):
|
|
|
|
data = {
|
2017-06-27 08:53:35 +00:00
|
|
|
"foo": "bar",
|
|
|
|
"encrypted_data": EncryptedString("foo")
|
2014-02-12 14:30:20 +00:00
|
|
|
}
|
|
|
|
serialized = yaml.serialize(data)
|
2017-06-27 08:53:35 +00:00
|
|
|
assert serialized == '{encrypted_data: !encrypted foo, foo: bar}', serialized
|
2014-02-12 14:30:20 +00:00
|
|
|
|
|
|
|
deserialized = yaml.deserialize(serialized)
|
|
|
|
assert deserialized == data, deserialized
|
|
|
|
|
|
|
|
@skipIf(not yaml.available, SKIP_MESSAGE % 'sls')
|
|
|
|
def test_serialize_sls(self):
|
|
|
|
data = {
|
|
|
|
"foo": "bar"
|
|
|
|
}
|
2014-05-30 16:14:47 +00:00
|
|
|
serialized = yamlex.serialize(data)
|
2014-02-12 14:30:20 +00:00
|
|
|
assert serialized == '{foo: bar}', serialized
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
deserialized = yamlex.deserialize(serialized)
|
2014-02-12 14:30:20 +00:00
|
|
|
assert deserialized == data, deserialized
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
@skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
|
2014-02-12 14:30:20 +00:00
|
|
|
def test_serialize_complex_sls(self):
|
|
|
|
data = OrderedDict([
|
|
|
|
("foo", 1),
|
|
|
|
("bar", 2),
|
|
|
|
("baz", True),
|
|
|
|
])
|
2014-05-30 16:14:47 +00:00
|
|
|
serialized = yamlex.serialize(data)
|
2014-02-12 14:30:20 +00:00
|
|
|
assert serialized == '{foo: 1, bar: 2, baz: true}', serialized
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
deserialized = yamlex.deserialize(serialized)
|
2014-02-12 14:30:20 +00:00
|
|
|
assert deserialized == data, deserialized
|
|
|
|
|
|
|
|
@skipIf(not yaml.available, SKIP_MESSAGE % 'yaml')
|
2014-05-30 16:14:47 +00:00
|
|
|
@skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
|
2014-02-12 14:30:20 +00:00
|
|
|
def test_compare_sls_vs_yaml(self):
|
|
|
|
src = '{foo: 1, bar: 2, baz: {qux: true}}'
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_data = yamlex.deserialize(src)
|
2014-02-12 14:30:20 +00:00
|
|
|
yml_data = yaml.deserialize(src)
|
|
|
|
|
|
|
|
# ensure that sls & yaml have the same base
|
|
|
|
assert isinstance(sls_data, dict)
|
|
|
|
assert isinstance(yml_data, dict)
|
|
|
|
assert sls_data == yml_data
|
|
|
|
|
|
|
|
# ensure that sls is ordered, while yaml not
|
|
|
|
assert isinstance(sls_data, OrderedDict)
|
|
|
|
assert not isinstance(yml_data, OrderedDict)
|
|
|
|
|
|
|
|
@skipIf(not yaml.available, SKIP_MESSAGE % 'yaml')
|
2014-05-30 16:14:47 +00:00
|
|
|
@skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
|
2014-02-12 14:30:20 +00:00
|
|
|
def test_compare_sls_vs_yaml_with_jinja(self):
|
|
|
|
tpl = '{{ data }}'
|
|
|
|
env = jinja2.Environment()
|
|
|
|
src = '{foo: 1, bar: 2, baz: {qux: true}}'
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_src = env.from_string(tpl).render(data=yamlex.deserialize(src))
|
2014-02-12 14:30:20 +00:00
|
|
|
yml_src = env.from_string(tpl).render(data=yaml.deserialize(src))
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_data = yamlex.deserialize(sls_src)
|
2014-02-12 14:30:20 +00:00
|
|
|
yml_data = yaml.deserialize(yml_src)
|
|
|
|
|
|
|
|
# ensure that sls & yaml have the same base
|
|
|
|
assert isinstance(sls_data, dict)
|
|
|
|
assert isinstance(yml_data, dict)
|
2016-09-30 13:28:49 +00:00
|
|
|
# The below has been commented out because something the loader test
|
|
|
|
# is modifying the yaml renderer to render things to unicode. Without
|
|
|
|
# running the loader test, the below passes. Even reloading the module
|
|
|
|
# from disk does not reset its internal state (per the Python docs).
|
|
|
|
##
|
|
|
|
#assert sls_data == yml_data
|
2014-02-12 14:30:20 +00:00
|
|
|
|
|
|
|
# ensure that sls is ordered, while yaml not
|
|
|
|
assert isinstance(sls_data, OrderedDict)
|
|
|
|
assert not isinstance(yml_data, OrderedDict)
|
|
|
|
|
|
|
|
# prove that yaml does not handle well with OrderedDict
|
|
|
|
# while sls is jinja friendly.
|
|
|
|
obj = OrderedDict([
|
|
|
|
('foo', 1),
|
|
|
|
('bar', 2),
|
|
|
|
('baz', {'qux': True})
|
|
|
|
])
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_obj = yamlex.deserialize(yamlex.serialize(obj))
|
2014-02-12 14:30:20 +00:00
|
|
|
try:
|
|
|
|
yml_obj = yaml.deserialize(yaml.serialize(obj))
|
|
|
|
except SerializationError:
|
|
|
|
# BLAAM! yaml was unable to serialize OrderedDict,
|
|
|
|
# but it's not the purpose of the current test.
|
|
|
|
yml_obj = obj.copy()
|
|
|
|
|
|
|
|
sls_src = env.from_string(tpl).render(data=sls_obj)
|
|
|
|
yml_src = env.from_string(tpl).render(data=yml_obj)
|
|
|
|
|
|
|
|
final_obj = yaml.deserialize(sls_src)
|
|
|
|
assert obj == final_obj
|
|
|
|
|
|
|
|
# BLAAM! yml_src is not valid !
|
2015-03-12 16:45:55 +00:00
|
|
|
final_obj = OrderedDict(yaml.deserialize(yml_src))
|
2014-02-12 14:30:20 +00:00
|
|
|
assert obj != final_obj
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
@skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
|
2014-02-17 12:22:32 +00:00
|
|
|
def test_sls_aggregate(self):
|
|
|
|
src = dedent("""
|
|
|
|
a: lol
|
|
|
|
foo: !aggregate hello
|
|
|
|
bar: !aggregate [1, 2, 3]
|
|
|
|
baz: !aggregate
|
|
|
|
a: 42
|
|
|
|
b: 666
|
|
|
|
c: the beast
|
|
|
|
""").strip()
|
|
|
|
|
|
|
|
# test that !aggregate is correctly parsed
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_obj = yamlex.deserialize(src)
|
2014-02-17 12:22:32 +00:00
|
|
|
assert sls_obj == {
|
|
|
|
'a': 'lol',
|
|
|
|
'foo': ['hello'],
|
|
|
|
'bar': [1, 2, 3],
|
|
|
|
'baz': {
|
|
|
|
'a': 42,
|
|
|
|
'b': 666,
|
|
|
|
'c': 'the beast'
|
|
|
|
}
|
|
|
|
}, sls_obj
|
|
|
|
|
|
|
|
assert dedent("""
|
|
|
|
a: lol
|
|
|
|
foo: [hello]
|
|
|
|
bar: [1, 2, 3]
|
|
|
|
baz: {a: 42, b: 666, c: the beast}
|
2014-05-30 16:14:47 +00:00
|
|
|
""").strip() == yamlex.serialize(sls_obj), sls_obj
|
2014-02-17 12:22:32 +00:00
|
|
|
|
|
|
|
# test that !aggregate aggregates scalars
|
|
|
|
src = dedent("""
|
|
|
|
placeholder: !aggregate foo
|
|
|
|
placeholder: !aggregate bar
|
|
|
|
placeholder: !aggregate baz
|
|
|
|
""").strip()
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_obj = yamlex.deserialize(src)
|
2014-02-17 12:22:32 +00:00
|
|
|
assert sls_obj == {'placeholder': ['foo', 'bar', 'baz']}, sls_obj
|
|
|
|
|
|
|
|
# test that !aggregate aggregates lists
|
|
|
|
src = dedent("""
|
|
|
|
placeholder: !aggregate foo
|
|
|
|
placeholder: !aggregate [bar, baz]
|
|
|
|
placeholder: !aggregate []
|
|
|
|
placeholder: !aggregate ~
|
|
|
|
""").strip()
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_obj = yamlex.deserialize(src)
|
2014-02-17 12:22:32 +00:00
|
|
|
assert sls_obj == {'placeholder': ['foo', 'bar', 'baz']}, sls_obj
|
|
|
|
|
|
|
|
# test that !aggregate aggregates dicts
|
|
|
|
src = dedent("""
|
|
|
|
placeholder: !aggregate {foo: 42}
|
|
|
|
placeholder: !aggregate {bar: null}
|
|
|
|
placeholder: !aggregate {baz: inga}
|
|
|
|
""").strip()
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_obj = yamlex.deserialize(src)
|
2014-02-21 08:52:31 +00:00
|
|
|
assert sls_obj == {
|
|
|
|
'placeholder': {
|
|
|
|
'foo': 42,
|
|
|
|
'bar': None,
|
|
|
|
'baz': 'inga'
|
|
|
|
}
|
|
|
|
}, sls_obj
|
2014-02-17 12:22:32 +00:00
|
|
|
|
|
|
|
# test that !aggregate aggregates deep dicts
|
|
|
|
src = dedent("""
|
|
|
|
placeholder: {foo: !aggregate {foo: 42}}
|
|
|
|
placeholder: {foo: !aggregate {bar: null}}
|
|
|
|
placeholder: {foo: !aggregate {baz: inga}}
|
|
|
|
""").strip()
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_obj = yamlex.deserialize(src)
|
2014-02-20 16:50:49 +00:00
|
|
|
assert sls_obj == {
|
|
|
|
'placeholder': {
|
|
|
|
'foo': {
|
|
|
|
'foo': 42,
|
|
|
|
'bar': None,
|
|
|
|
'baz': 'inga'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, sls_obj
|
|
|
|
|
2014-02-21 08:52:31 +00:00
|
|
|
# test that {foo: !aggregate bar} and {!aggregate foo: bar}
|
|
|
|
# are roughly equivalent.
|
|
|
|
src = dedent("""
|
|
|
|
placeholder: {!aggregate foo: {foo: 42}}
|
|
|
|
placeholder: {!aggregate foo: {bar: null}}
|
|
|
|
placeholder: {!aggregate foo: {baz: inga}}
|
|
|
|
""").strip()
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_obj = yamlex.deserialize(src)
|
2014-02-21 08:52:31 +00:00
|
|
|
assert sls_obj == {
|
|
|
|
'placeholder': {
|
|
|
|
'foo': {
|
|
|
|
'foo': 42,
|
|
|
|
'bar': None,
|
|
|
|
'baz': 'inga'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, sls_obj
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
@skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
|
2014-02-21 08:52:31 +00:00
|
|
|
def test_sls_reset(self):
|
|
|
|
src = dedent("""
|
|
|
|
placeholder: {!aggregate foo: {foo: 42}}
|
|
|
|
placeholder: {!aggregate foo: {bar: null}}
|
|
|
|
!reset placeholder: {!aggregate foo: {baz: inga}}
|
|
|
|
""").strip()
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_obj = yamlex.deserialize(src)
|
2014-02-21 08:52:31 +00:00
|
|
|
assert sls_obj == {
|
|
|
|
'placeholder': {
|
|
|
|
'foo': {
|
|
|
|
'baz': 'inga'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, sls_obj
|
2014-02-20 16:50:49 +00:00
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
@skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
|
2014-02-20 16:50:49 +00:00
|
|
|
def test_sls_repr(self):
|
2014-02-21 12:49:54 +00:00
|
|
|
"""
|
|
|
|
Ensure that obj __repr__ and __str__ methods are yaml friendly.
|
|
|
|
"""
|
2014-02-20 16:50:49 +00:00
|
|
|
def convert(obj):
|
2014-05-30 16:14:47 +00:00
|
|
|
return yamlex.deserialize(yamlex.serialize(obj))
|
2014-02-20 16:50:49 +00:00
|
|
|
sls_obj = convert(OrderedDict([('foo', 'bar'), ('baz', 'qux')]))
|
|
|
|
|
|
|
|
# ensure that repr and str are yaml friendly
|
|
|
|
assert sls_obj.__str__() == '{foo: bar, baz: qux}'
|
|
|
|
assert sls_obj.__repr__() == '{foo: bar, baz: qux}'
|
|
|
|
|
|
|
|
# ensure that repr and str are already quoted
|
|
|
|
assert sls_obj['foo'].__str__() == '"bar"'
|
|
|
|
assert sls_obj['foo'].__repr__() == '"bar"'
|
2014-02-17 12:22:32 +00:00
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
@skipIf(not yamlex.available, SKIP_MESSAGE % 'sls')
|
2014-02-21 12:49:54 +00:00
|
|
|
def test_sls_micking_file_merging(self):
|
|
|
|
def convert(obj):
|
2014-05-30 16:14:47 +00:00
|
|
|
return yamlex.deserialize(yamlex.serialize(obj))
|
2014-02-21 12:49:54 +00:00
|
|
|
|
|
|
|
# let say that we have 2 pillar files
|
|
|
|
|
|
|
|
src1 = dedent("""
|
|
|
|
a: first
|
|
|
|
b: !aggregate first
|
|
|
|
c:
|
|
|
|
subkey1: first
|
|
|
|
subkey2: !aggregate first
|
|
|
|
""").strip()
|
|
|
|
|
|
|
|
src2 = dedent("""
|
|
|
|
a: second
|
|
|
|
b: !aggregate second
|
|
|
|
c:
|
|
|
|
subkey2: !aggregate second
|
|
|
|
subkey3: second
|
|
|
|
""").strip()
|
|
|
|
|
2014-05-30 16:14:47 +00:00
|
|
|
sls_obj1 = yamlex.deserialize(src1)
|
|
|
|
sls_obj2 = yamlex.deserialize(src2)
|
|
|
|
sls_obj3 = yamlex.merge_recursive(sls_obj1, sls_obj2)
|
2014-02-21 12:49:54 +00:00
|
|
|
|
|
|
|
assert sls_obj3 == {
|
|
|
|
'a': 'second',
|
|
|
|
'b': ['first', 'second'],
|
|
|
|
'c': {
|
|
|
|
'subkey2': ['first', 'second'],
|
|
|
|
'subkey3': 'second'
|
|
|
|
}
|
|
|
|
}, sls_obj3
|
|
|
|
|
2014-02-12 14:30:20 +00:00
|
|
|
@skipIf(not msgpack.available, SKIP_MESSAGE % 'msgpack')
|
|
|
|
def test_msgpack(self):
|
|
|
|
data = OrderedDict([
|
|
|
|
("foo", 1),
|
|
|
|
("bar", 2),
|
|
|
|
("baz", True),
|
|
|
|
])
|
|
|
|
serialized = msgpack.serialize(data)
|
|
|
|
deserialized = msgpack.deserialize(serialized)
|
|
|
|
assert deserialized == data, deserialized
|
|
|
|
|
2015-11-15 20:04:27 +00:00
|
|
|
@skipIf(not python.available, SKIP_MESSAGE % 'python')
|
|
|
|
def test_serialize_python(self):
|
|
|
|
data = {'foo': 'bar'}
|
|
|
|
serialized = python.serialize(data)
|
Update file state/execution modules and associated files with unicode_literals
This updates the file state and execution modules to use
unicode_literals. Since the serializers and the cmd module are touched
by the file state/exec module, those are also updated here, as well as
the cmd state module, for good measure.
Additionally, I found that salt.utils.data.decode_dict (and decode_list)
are misnamed for what they actually do. Since they *encode* the
contents, the functions should be named encode_dict and encode_list,
respectively. And we also should have counterparts which actually
decode, so I've added them. The compatibility functions from salt.utils
still use the old "decode" names to preserve backward compatibility, but
they now invoke the renamed "encode" functions in salt.utils.data. Note
that this means that the compatibility functions
salt.utils.decode_dict/list, and their cognates in salt.utils.data now
do different things, but since the move to salt.utils.data is also
happening in the Oxygen release this is as good a time as any to correct
this oversight.
I've updated the jinja filter docs to include information on the renamed
jinja filters, and also added a section on jinja filter renaming to the
Oxygen release notes. There was another filter that I renamed during the
process of moving functions from salt.utils which I did not properly
document in the release notes, so this is now included along with the
others.
2017-12-12 16:30:33 +00:00
|
|
|
expected = "{u'foo': u'bar'}" if six.PY2 else "{'foo': 'bar'}"
|
|
|
|
assert serialized == expected, serialized
|
2015-11-15 20:04:27 +00:00
|
|
|
|
|
|
|
@skipIf(not configparser.available, SKIP_MESSAGE % 'configparser')
|
|
|
|
def test_configparser(self):
|
|
|
|
data = {'foo': {'bar': 'baz'}}
|
|
|
|
# configparser appends empty lines
|
|
|
|
serialized = configparser.serialize(data).strip()
|
|
|
|
assert serialized == "[foo]\nbar = baz", serialized
|
|
|
|
|
2015-11-15 20:14:42 +00:00
|
|
|
deserialized = configparser.deserialize(serialized)
|
|
|
|
assert deserialized == data, deserialized
|
2017-11-22 14:27:14 +00:00
|
|
|
|
|
|
|
@skipIf(not toml.available, SKIP_MESSAGE % 'toml')
|
|
|
|
def test_serialize_toml(self):
|
|
|
|
data = {
|
|
|
|
"foo": "bar"
|
|
|
|
}
|
|
|
|
serialized = toml.serialize(data)
|
|
|
|
assert serialized == 'foo = "bar"\n', serialized
|
|
|
|
|
|
|
|
deserialized = toml.deserialize(serialized)
|
|
|
|
assert deserialized == data, deserialized
|