Merge lp:~bcsaller/pyjuju/sane_output_test_option into lp:pyjuju

Proposed by Benjamin Saller
Status: Work in progress
Proposed branch: lp:~bcsaller/pyjuju/sane_output_test_option
Merge into: lp:pyjuju
Diff against target: 226 lines (+135/-3)
2 files modified
juju/hooks/commands.py (+37/-3)
juju/hooks/tests/test_invoker.py (+98/-0)
To merge this branch: bzr merge lp:~bcsaller/pyjuju/sane_output_test_option
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+106067@code.launchpad.net

Description of the change

Adds support for --test to cli utils

This branch bundles two related changes, support for --test on relation-get, unit-get and config-get.
This should be similar to the additions made in the golang version.

Also this changes to always use a formatter for output and the smart output formatter was changed to
YAML dump collection types, normal string values are still printed w/o serialization.

https://codereview.appspot.com/6216053/

To post a comment you must log in.
Revision history for this message
Benjamin Saller (bcsaller) wrote :

Reviewers: mp+106067_code.launchpad.net,

Message:
Please take a look.

Description:
Adds support for --test and doesn't output Python reprs

This branch bundles two related changes, support for --test on
relation-get, unit-get and config-get.
This should be similar to the additions made in the golang version.

Also this changes to always use a formatter for output and the smart
output formatter was changed to
YAML dump collection types, normal string values are still printed w/o
serialization.

https://code.launchpad.net/~bcsaller/juju/sane_output_test_option/+merge/106067

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/6216053/

Affected files:
   A [revision details]
   M juju/hooks/cli.py
   M juju/hooks/commands.py
   M juju/hooks/tests/test_invoker.py

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Some details here are not clear to me. I'd appreciate having a chat
about this feature with you before this goes in.

https://codereview.appspot.com/6216053/diff/1/juju/hooks/cli.py
File juju/hooks/cli.py (right):

https://codereview.appspot.com/6216053/diff/1/juju/hooks/cli.py#newcode228
juju/hooks/cli.py:228: # yaml dump container types
What was your thinking here? I believe it should dump yaml in all
cases. i.e. "true", not "True".

https://codereview.appspot.com/6216053/diff/1/juju/hooks/commands.py
File juju/hooks/commands.py (right):

https://codereview.appspot.com/6216053/diff/1/juju/hooks/commands.py#newcode80
juju/hooks/commands.py:80: v = json.dumps(v)
json?

https://codereview.appspot.com/6216053/

Revision history for this message
Clint Byrum (clint-fewbar) wrote :

I've been thinking about this, and this is a backward incompatible change, I'm afraid.

Up until now, charms w/ boolean configs have been able to count on "True" or "False" and have been written as such.

This will change the case, and that is, unfortunately, not acceptable in the planned "fixes only" release of Juju.

The --test is fantastic, and should be added. But I'm afraid if we do the other bit without bumping to a new "major release" we will have many many broken charms very suddenly.

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

its backwards incompatible for charms, which we should fix and encourage extant users to update. the existing output is broken in that it depends on no standard except the implicit one by python's str/repr output. The whole 'smart' format stuff was decidedly not.

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Mail sent to canonical-juju. This bug will be fixed, we have no option. The decision of when to fix it is your hands, Clint.

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

bug in base formatter.

https://codereview.appspot.com/6216053/diff/1/juju/hooks/cli.py
File juju/hooks/cli.py (right):

https://codereview.appspot.com/6216053/diff/1/juju/hooks/cli.py#newcode220
juju/hooks/cli.py:220: formatter(result, stream)
None is not a callable type, this tries a double output if formatter is
None. please default formatter to the correct default output.

https://codereview.appspot.com/6216053/

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

it would be nice to rebase this on current trunk where the formatting work has been done in a compatible fashion, and slim this branch down to just the --test option which is still useful

Revision history for this message
Benjamin Saller (bcsaller) wrote :

Please take a look.

https://codereview.appspot.com/6216053/diff/1/juju/hooks/cli.py
File juju/hooks/cli.py (right):

https://codereview.appspot.com/6216053/diff/1/juju/hooks/cli.py#newcode228
juju/hooks/cli.py:228: # yaml dump container types
On 2012/05/17 02:42:44, niemeyer wrote:
> What was your thinking here? I believe it should dump yaml in all
cases. i.e.
> "true", not "True".

You're correct that non-string types should appear here.

https://codereview.appspot.com/6216053/

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

I'm still a little unclear as to the desired behavior outcome and its
testing with this branch, as it currently tested it doesn't seem
particularly useful.

https://codereview.appspot.com/6216053/diff/6001/juju/hooks/commands.py
File juju/hooks/commands.py (right):

https://codereview.appspot.com/6216053/diff/6001/juju/hooks/commands.py#newcode18
juju/hooks/commands.py:18: class TestOptionMixin(object):
missing doc string describing purpose/usage.

https://codereview.appspot.com/6216053/diff/6001/juju/hooks/commands.py#newcode34
juju/hooks/commands.py:34: self.handle_test(result)
there's a lot of unesc. method delegation here. just inline the
test_true/handle_test behavior.

The use of the mixin, also feels like its adding complexity here, around
subclass ordering and proper lineage to customize_parser vs. render
(which have inverse call lineages). Incorporating into the base class
might be cleaner via class flag switch.

https://codereview.appspot.com/6216053/diff/6001/juju/hooks/commands.py#newcode114
juju/hooks/commands.py:114: def render(self, result):
Why is this returning None, why is it implemented at all? ditto for the
two other implementations of the same here.

https://codereview.appspot.com/6216053/diff/6001/juju/hooks/tests/test_invoker.py
File juju/hooks/tests/test_invoker.py (right):

https://codereview.appspot.com/6216053/diff/6001/juju/hooks/tests/test_invoker.py#newcode356
juju/hooks/tests/test_invoker.py:356:
this is testing something very different then what the cli usage would
commonly be, namely the cli usage would be testing for values unset.

For values set, a null value seems valid, has the go team documented the
behavior of this cli param?

https://codereview.appspot.com/6216053/

Unmerged revisions

556. By Benjamin Saller

test option support in cli hooks

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'juju/hooks/commands.py'
2--- juju/hooks/commands.py 2012-05-03 16:46:40 +0000
3+++ juju/hooks/commands.py 2012-07-03 18:49:20 +0000
4@@ -1,3 +1,4 @@
5+import json
6 import logging
7 import os
8 import pipes
9@@ -14,7 +15,28 @@
10 BAD_CHARS = re.compile("[\-\./:()<>|?*]|(\\\)")
11
12
13-class RelationGetCli(CommandLineClient):
14+class TestOptionMixin(object):
15+ def customize_parser(self):
16+ self.parser.add_argument("--test",
17+ action="store_true",
18+ default=False,
19+ help="Exit code indicates if the value "
20+ "is present and not empty")
21+
22+ def tests_true(self, result):
23+ return 0 if bool(result) else 1
24+
25+ def handle_test(self, result):
26+ self.exit_code = self.tests_true(result)
27+
28+ def render(self, result):
29+ if self.options.test is True:
30+ self.handle_test(result)
31+ else:
32+ super(TestOptionMixin, self).render(result)
33+
34+
35+class RelationGetCli(TestOptionMixin, CommandLineClient):
36 keyvalue_pairs = False
37
38 def customize_parser(self):
39@@ -24,6 +46,7 @@
40 "-r", dest="relation_id", default="", metavar="RELATION ID")
41 self.parser.add_argument("settings_name", default="", nargs="?")
42 self.parser.add_argument("unit_name", default=remote_unit, nargs="?")
43+ super(RelationGetCli, self).customize_parser()
44
45 @inlineCallbacks
46 def run(self):
47@@ -88,6 +111,9 @@
48 self.options.relation_id,
49 self.options.keyvalue_pairs)
50
51+ def render(self, result):
52+ return None
53+
54
55 def relation_set():
56 """Entry point for relation-set."""
57@@ -179,11 +205,12 @@
58 sys.exit(client())
59
60
61-class ConfigGetCli(CommandLineClient):
62+class ConfigGetCli(TestOptionMixin, CommandLineClient):
63 keyvalue_pairs = False
64
65 def customize_parser(self):
66 self.parser.add_argument("option_name", default="", nargs="?")
67+ super(ConfigGetCli, self).customize_parser()
68
69 @inlineCallbacks
70 def run(self):
71@@ -213,6 +240,9 @@
72 return self.client.open_port(
73 self.options.client_id, *self.options.port_protocol)
74
75+ def render(self, result):
76+ return None
77+
78
79 def open_port():
80 """Entry point for open-port."""
81@@ -234,6 +264,9 @@
82 return self.client.close_port(
83 self.options.client_id, *self.options.port_protocol)
84
85+ def render(self, result):
86+ return None
87+
88
89 def close_port():
90 """Entry point for close-port."""
91@@ -241,11 +274,12 @@
92 sys.exit(client())
93
94
95-class UnitGetCli(CommandLineClient):
96+class UnitGetCli(TestOptionMixin, CommandLineClient):
97 keyvalue_pairs = False
98
99 def customize_parser(self):
100 self.parser.add_argument("setting_name")
101+ super(UnitGetCli, self).customize_parser()
102
103 @inlineCallbacks
104 def run(self):
105
106=== modified file 'juju/hooks/tests/test_invoker.py'
107--- juju/hooks/tests/test_invoker.py 2012-07-03 07:30:53 +0000
108+++ juju/hooks/tests/test_invoker.py 2012-07-03 18:49:20 +0000
109@@ -340,6 +340,49 @@
110 self.assertEqual('VAR_NAME=rabbit\n\n', data)
111
112 @defer.inlineCallbacks
113+ def test_relation_get_test_option(self):
114+ yield self.build_default_relationships()
115+
116+ hook_log = self.capture_logging("hook")
117+
118+ # Populate and verify some data we will
119+ # later extract with the hook
120+ values = {"string": "rabbit hole",
121+ "emptylist": [],
122+ "emptystring": "",
123+ "zero": 0,
124+ "number": 1}
125+ yield self.wordpress_relation.set_data(values)
126+
127+ exe = yield self.ua.get_invoker(
128+ "db:42", "add", "mysql/0", self.mysql_relation,
129+ client_id="client_id")
130+ exe.environment["JUJU_REMOTE_UNIT"] = "wordpress/0"
131+
132+ @defer.inlineCallbacks
133+ def test_exit(variable, expected):
134+ # --test can return a bad exit code from the POV of
135+ # the invoker, this is fine, its just the way I'm using
136+ # it to get at the value thats otherwise fine in the
137+ # hook itself
138+ try:
139+ result = yield exe(self.create_hook("relation-get",
140+ "--test %s" % variable))
141+ self.assertEqual(result, expected)
142+ except errors.CharmInvocationError:
143+ if expected == 1:
144+ pass
145+ data = hook_log.getvalue()
146+ self.assertEqual(data, "")
147+
148+ yield test_exit("string", 0)
149+ yield test_exit("emptylist", 1)
150+ yield test_exit("emptystring", 1)
151+ yield test_exit("zero", 1)
152+ yield test_exit("number", 0)
153+
154+
155+ @defer.inlineCallbacks
156 def test_relation_get_format_shell_bad_vars(self):
157 """If illegal values are make somehow available warn."""
158 yield self.build_default_relationships()
159@@ -433,6 +476,49 @@
160 self.assertEqual(data["name"], "rabbit")
161 self.assertEqual(data["forgotten"], "lyrics")
162
163+ @defer.inlineCallbacks
164+ def test_config_get_test_option(self):
165+ """Validate that config-get returns expected values."""
166+ yield self.build_default_relationships()
167+
168+ hook_log = self.capture_logging("hook")
169+
170+ # Populate and verify some data we will
171+ # later extract with the hook
172+ expected = {"string": "rabbit",
173+ "empty": "",
174+ "emptylist": []}
175+
176+ exe = yield self.ua.get_invoker(
177+ "db:42", "add", "mysql/0", self.mysql_relation,
178+ client_id="client_id")
179+
180+ context = yield self.ua.get_context("client_id")
181+ config = yield context.get_config()
182+ config.update(expected)
183+ yield config.write()
184+
185+ # invoke relation-get and verify the result
186+ @defer.inlineCallbacks
187+ def test_exit(variable, expected):
188+ # --test can return a bad exit code from the POV of
189+ # the invoker, this is fine, its just the way I'm using
190+ # it to get at the value thats otherwise fine in the
191+ # hook itself
192+ try:
193+ result = yield exe(self.create_hook("config-get",
194+ "--test %s" % variable))
195+ self.assertEqual(result, expected)
196+ except errors.CharmInvocationError:
197+ if expected == 1:
198+ pass
199+ data = hook_log.getvalue()
200+ self.assertEqual(data, "")
201+
202+ yield test_exit("string", 0)
203+ yield test_exit("emptylist", 1)
204+ yield test_exit("emptystring", 1)
205+
206
207 class RelationInvokerTestBase(InvokerTestBase, RelationTestBase):
208
209@@ -684,6 +770,18 @@
210 self.assertEqual("mysql-0.example.com", data.strip())
211
212 @defer.inlineCallbacks
213+ def test_unit_get_test_option(self):
214+ """Private addresses can be tested for."""
215+ hook_log = self.capture_logging("hook")
216+ exe = yield self.ua.get_invoker(
217+ "database:42", "add", "mysql/0", self.relation,
218+ client_id="client_id")
219+ result = yield exe(self.create_hook("unit-get", "--test private-address"))
220+ self.assertEqual(result, 0)
221+ data = hook_log.getvalue()
222+ self.assertEqual("", data.strip())
223+
224+ @defer.inlineCallbacks
225 def test_spawn_cli_get_unit_unknown_public_address(self):
226 """If for some hysterical raison, the public address hasn't been set.
227

Subscribers

People subscribed via source and target branches

to status/vote changes: