1675
1684
"database:42", "add", "mysql/0", self.relation,
1676
1685
client_id="client_id")
1678
# Byte strings are also supported by staying completely in
1679
# YAML, so test that corner case. This also means users need
1680
# to present valid YAML for any input:
1681
data_file = self.makeFile(
1682
yaml.safe_dump("But when I do drink, I prefer \xCA\xFE"))
1687
# Test the support of raw strings, both from a file and from
1688
# command line. Unicode can also be used - this is just
1689
# rendered as UTF-8 in the shell; the source here is also
1690
# UTF-8 - note it is not a Unicode string, it's a bytestring.
1691
data_file = self.make_zipped_file()
1683
1692
set_hook = self.create_hook(
1684
1693
"relation-set",
1685
"b=true i=42 f=1.23 s=ascii u=中文 d=@%s" % data_file)
1694
"b=true f=1.23 i=42 s=ascii u=中文 d=@%s "
1695
"r=\"$(echo -en 'But when I do drink, I prefer \\xCA\\xFE')\"" % (
1686
1697
yield exe(set_hook)
1687
1699
result = yield exe(self.create_hook("relation-get", "- mysql/0"))
1688
1700
self.assertEqual(result, 0)
1690
# YAML guarantees that the keys will be sorted
1691
# lexicographically; note that we output UTF-8 for Unicode
1692
# when dumping YAML, so our source text (with this test file
1693
# in UTF-8 itself) matches the output text, as seen in the
1694
# characters for "zhongwen" (Chinese language).
1702
# relation-get - uses YAML to dump keys. YAML guarantees that
1703
# the keys will be sorted lexicographically; note that we
1704
# output UTF-8 for Unicode when dumping YAML, so our source
1705
# text (with this test file in UTF-8 itself) matches the
1706
# output text, as seen in the characters for "zhongwen"
1707
# (Chinese language).
1695
1708
self.assertEqual(
1696
1709
hook_log.getvalue(),
1698
"d: !!binary |\n QnV0IHdoZW4gSSBkbyBkcmluaywgSSBwcmVmZXIgyv4=\n"
1711
"d: !!binary |\n H4sICLuLQVAC/3RtcHJmeVAwZQBLTEoGAMJBJDUDAAAA\n"
1701
1714
"private-address: mysql-0.example.com\n"
1715
"r: !!binary |\n QnV0IHdoZW4gSSBkbyBkcmluaywgSSBwcmVmZXIgyv4=\n"
1705
# Log lines are not simply converted into Unicode, as in v1 format
1719
# Note: backslashes are necessarily doubled here; r"XYZ"
1720
# strings don't help with hexescapes
1706
1721
self.assertLogLines(
1707
1722
hook_debug_log.getvalue(),
1708
1723
["Flushed values for hook %r on 'database:42'" % (
1709
1724
os.path.basename(set_hook),),
1710
" Setting changed: 'b'=True (was unset)",
1711
" Setting changed: 'd'='But when I do drink, "
1725
" Setting changed: 'b'='true' (was unset)",
1726
" Setting changed: 'd'='\\x1f\\x8b\\x08\\x08\\xbb\\x8bAP\\x02"
1727
"\\xfftmprfyP0e\\x00KLJ\\x06\\x00\\xc2A$5\\x03"
1728
"\\x00\\x00\\x00' (was unset)",
1729
" Setting changed: 'f'='1.23' (was unset)",
1730
" Setting changed: 'i'='42' (was unset)",
1731
" Setting changed: 'r'='But when I do drink, "
1712
1732
"I prefer \\xca\\xfe' (was unset)",
1713
" Setting changed: 'f'=1.23 (was unset)",
1714
" Setting changed: 'i'=42 (was unset)",
1715
1733
" Setting changed: 's'='ascii' (was unset)",
1716
" Setting changed: 'u'=u'\\u4e2d\\u6587' (was unset)"])
1718
# Also ensure that invalid YAML is rejected; unlike earlier,
1719
# this was not encoded with yaml.safe_dump
1720
data_file = self.makeFile(
1721
"But when I do drink, I prefer \xCA\xFE")
1722
hook = self.create_hook("relation-set", "d=@%s" % data_file)
1723
e = yield self.assertFailure(exe(hook), errors.CharmInvocationError)
1724
self.assertEqual(str(e), "Error processing %r: exit code 1." % hook)
1726
"yaml.reader.ReaderError: \'utf8\' codec can\'t decode byte #xca: "
1727
"invalid continuation byte\n in \"<string>\", position 30",
1728
hook_log.getvalue())
1734
" Setting changed: 'u'=u'\\u4e2d\\u6587' (was unset)"
1737
@defer.inlineCallbacks
1738
def test_exact_roundtrip_binary_data(self):
1739
"""Verify that binary data, including \x00, is roundtripped exactly"""
1740
hook_log = capture_separate_log("hook", level=logging.INFO)
1741
exe = yield self.ua.get_invoker(
1742
"database:42", "add", "mysql/0", self.relation,
1743
client_id="client_id")
1744
data_file = self.make_zipped_file()
1746
# relation-set can only read null bytes from a file; bash
1747
# would otherwise silently drop
1748
set_hook = self.create_hook("relation-set", "zipped=@%s" % (
1750
result = yield exe(set_hook)
1751
self.assertEqual(result, 0)
1753
# Abuse the create_hook method a little bit by adding a pipe
1754
get_hook = self.create_hook("relation-get", "zipped mysql/0 | zcat")
1755
result = yield exe(get_hook)
1756
self.assertEqual(result, 0)
1758
# Using the hook log for this verification does generate one
1759
# extra \n (seen elsewhere in our tests), but this is just
1760
# test noise: we are guaranteed roundtrip fidelity by using
1761
# the picky tool that is zcat - no extraneous data accepted.
1762
self.assertEqual(hook_log.getvalue(), "abc\n")
1764
@defer.inlineCallbacks
1765
def test_json_output(self):
1766
"""Verify roundtripping"""
1767
hook_log = capture_separate_log("hook", level=logging.INFO)
1768
exe = yield self.ua.get_invoker(
1769
"database:42", "add", "mysql/0", self.relation,
1770
client_id="client_id")
1772
# Test the support of raw strings, both from a file and from
1773
# command line. In addition, test Unicode indirectly by using
1774
# UTF-8. Because the source of this file is marked as UTF-8,
1775
# we can embed such characters directly in bytestrings, not
1776
# just Unicode strings. This also works within the context of
1778
raw = "But when I do drink, I prefer \xca\xfe"
1779
data_file = self.makeFile(raw)
1780
set_hook = self.create_hook(
1782
"b=true f=1.23 i=42 s=ascii u=中文 d=@%s "
1783
"r=\"$(echo -en 'But when I do drink, I prefer \\xCA\\xFE')\"" % (
1787
result = yield exe(self.create_hook(
1788
"relation-get", "--format=json - mysql/0"))
1789
self.assertEqual(result, 0)
1791
# YAML serialization internally has converted (transparently)
1792
# UTF-8 to Unicode, which can be rendered by JSON. However the
1793
# "cafe" bytestring is invalid JSON, so verify that it's been
1795
encoded = base64.b64encode(raw)
1797
hook_log.getvalue(),
1802
'"private-address": "mysql-0.example.com", '
1805
'"u": "\\u4e2d\\u6587"}\n' % (encoded, encoded))
1807
@defer.inlineCallbacks
1808
def common_relation_set(self):
1809
hook_log = capture_separate_log("hook", level=logging.INFO)
1810
exe = yield self.ua.get_invoker(
1811
"database:42", "add", "mysql/0",
1812
self.relation, client_id="client_id")
1813
raw = "But when I do drink, I prefer \xCA\xFE"
1814
data_file = self.makeFile(raw)
1815
set_hook = self.create_hook(
1817
"s='some text' u=中文 d=@%s "
1818
"r=\"$(echo -en 'But when I do drink, I prefer \\xCA\\xFE')\"" % (
1820
result = yield exe(set_hook)
1821
self.assertEqual(result, 0)
1822
defer.returnValue((exe, hook_log))
1824
@defer.inlineCallbacks
1825
def test_relation_get_ascii(self):
1826
"""Verify that ascii data is roundtripped"""
1827
exe, hook_log = yield self.common_relation_set()
1828
result = yield exe(self.create_hook("relation-get", "s mysql/0"))
1829
self.assertEqual(result, 0)
1830
self.assertEqual(hook_log.getvalue(), "some text\n")
1832
@defer.inlineCallbacks
1833
def test_relation_get_raw(self):
1834
"""Verify that raw data is roundtripped"""
1835
exe, hook_log = yield self.common_relation_set()
1836
result = yield exe(self.create_hook("relation-get", "r mysql/0"))
1837
self.assertEqual(result, 0)
1839
hook_log.getvalue(), "But when I do drink, I prefer \xca\xfe\n")
1841
@defer.inlineCallbacks
1842
def test_relation_get_unicode(self):
1843
"""Verify Unicode is roundtripped (via UTF-8) through the shell"""
1844
exe, hook_log = yield self.common_relation_set()
1846
result = yield exe(self.create_hook("relation-get", "u mysql/0"))
1847
self.assertEqual(result, 0)
1848
self.assertEqual(hook_log.getvalue(), "中文\n")
1850
@defer.inlineCallbacks
1851
def setup_config(self):
1852
hook_log = self.capture_logging("hook")
1853
exe = yield self.ua.get_invoker(
1854
"db:42", "add", "mysql/0", self.relation, client_id="client_id")
1855
context = yield self.ua.get_context("client_id")
1856
config = yield context.get_config()
1857
with open(self.make_zipped_file(), "rb") as f:
1864
# uses UTF-8 encoding in this test script
1866
# use high byte and null byte characters
1869
yield config.write()
1870
defer.returnValue((exe, hook_log))
1872
@defer.inlineCallbacks
1873
def test_config_get_boolean(self):
1874
"""Validate that config-get returns lowercase names of booleans."""
1875
exe, hook_log = yield self.setup_config()
1876
result = yield exe(self.create_hook("config-get", "b"))
1877
self.assertEqual(result, 0)
1878
self.assertEqual(hook_log.getvalue(), "true\n")
1880
@defer.inlineCallbacks
1881
def test_config_get_float(self):
1882
"""Validate that config-get returns floats without quotes."""
1883
exe, hook_log = yield self.setup_config()
1884
result = yield exe(self.create_hook("config-get", "f"))
1885
self.assertEqual(result, 0)
1886
self.assertEqual(hook_log.getvalue(), "1.23\n")
1888
@defer.inlineCallbacks
1889
def test_config_get_int(self):
1890
"""Validate that config-get returns ints without quotes."""
1891
exe, hook_log = yield self.setup_config()
1892
result = yield exe(self.create_hook("config-get", "i"))
1893
self.assertEqual(result, 0)
1894
self.assertEqual(hook_log.getvalue(), "42\n")
1896
@defer.inlineCallbacks
1897
def test_config_get_ascii(self):
1898
"""Validate that config-get returns ascii strings."""
1899
exe, hook_log = yield self.setup_config()
1900
result = yield exe(self.create_hook("config-get", "s"))
1901
self.assertEqual(result, 0)
1902
self.assertEqual(hook_log.getvalue(), "some text\n")
1904
@defer.inlineCallbacks
1905
def test_config_get_raw(self):
1906
"""Validate config-get can work with high and null bytes."""
1907
exe, hook_log = yield self.setup_config()
1908
result = yield exe(self.create_hook("config-get", "r | zcat"))
1909
self.assertEqual(result, 0)
1910
self.assertEqual(hook_log.getvalue(), "abc\n")
1912
@defer.inlineCallbacks
1913
def test_config_get_unicode(self):
1914
"""Validate that config-get returns raw strings containing UTF-8."""
1915
exe, hook_log = yield self.setup_config()
1916
result = yield exe(self.create_hook("config-get", "u"))
1917
self.assertEqual(result, 0)
1918
self.assertEqual(hook_log.getvalue(), "中文\n")