Merge lp:~allenap/maas/argparse-the-annoying--bug-1557434 into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5173
Proposed branch: lp:~allenap/maas/argparse-the-annoying--bug-1557434
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 201 lines (+72/-17)
4 files modified
buildout.cfg (+21/-14)
src/maascli/__init__.py (+8/-1)
src/maascli/tests/test_cli.py (+1/-1)
src/maascli/tests/test_integration.py (+42/-1)
To merge this branch: bzr merge lp:~allenap/maas/argparse-the-annoying--bug-1557434
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+299936@code.launchpad.net

Commit message

For the MAAS CLI, mimic the error behaviour provided by argparse 1.1 on PyPI.

This differs substantially from the argparse 1.1 found in the standard library. This branch also forces the development environment to use argparse as found in the standard library instead of the one pulled in by unittest2.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Just tested this locally. The behavior is *much* better. Many thanks for this.

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

Thanks!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'buildout.cfg'
2--- buildout.cfg 2016-07-07 18:52:38 +0000
3+++ buildout.cfg 2016-07-13 11:35:29 +0000
4@@ -45,15 +45,22 @@
5 testscenarios
6 testtools
7 initialization =
8- ${common:path-sort}
9+ ${common:path-munge}
10 ${common:warnings}
11 ${common:environment}
12-path-sort =
13- from pathlib import Path
14- from sys import path, prefix
15- prefix = Path(prefix)
16- sortkey = lambda path: prefix in Path(path).parents
17- path.sort(key=sortkey)
18+path-munge =
19+ import pathlib, sys
20+ # Eliminate argparse usage outside of the standard library. This is
21+ # needed because some deps <cough>unittest2<cough> explicitly require
22+ # argparse, which zc.buildout then dutifully installs. Unfortunately
23+ # argparse 1.1 from PyPI differs substantially to argparse 1.1 in the
24+ # standard library. For consistency we want the latter.
25+ p_argparse_egg = lambda path: pathlib.Path(path).match("*/argparse-*.egg")
26+ sys.path[:] = [path for path in sys.path if not p_argparse_egg(path)]
27+ # Sort system paths towards the end of sys.path so that deps defined
28+ # here are used in preference to those installed system-wide.
29+ p_sys_prefix = lambda path, p=pathlib.Path: p(sys.prefix) in p(path).parents
30+ sys.path.sort(key=p_sys_prefix)
31 environment =
32 from os import environ
33 environ.setdefault("MAAS_ROOT", "${buildout:directory}/run")
34@@ -80,7 +87,7 @@
35 extra-paths =
36 ${common:extra-paths}
37 initialization =
38- ${common:path-sort}
39+ ${common:path-munge}
40 interpreter =
41 entry-points = database=postgresfixture.main:main
42 scripts = database
43@@ -144,7 +151,7 @@
44 eggs =
45 ${region:eggs}
46 initialization =
47- ${common:path-sort}
48+ ${common:path-munge}
49 entry-points =
50 maas=maascli:main
51 extra-paths =
52@@ -160,7 +167,7 @@
53 entry-points =
54 test.cli=maastesting.noseplug:main
55 initialization =
56- ${common:path-sort}
57+ ${common:path-munge}
58 ${common:warnings}
59 options = [
60 "--with-resources",
61@@ -196,7 +203,7 @@
62 entry-points =
63 test.testing=maastesting.noseplug:main
64 initialization =
65- ${common:path-sort}
66+ ${common:path-munge}
67 ${common:warnings}
68 options = [
69 "--with-resources",
70@@ -283,7 +290,7 @@
71 scripts =
72 test.e2e
73 initialization =
74- ${common:path-sort}
75+ ${common:path-munge}
76 from os import environ
77 environ.setdefault("MAAS_RACK_DEVELOP", "TRUE")
78 environ.setdefault("MAAS_ROOT", "${buildout:directory}/run-e2e")
79@@ -298,7 +305,7 @@
80 entry-points =
81 flake8=flake8.run:main
82 initialization =
83- ${common:path-sort}
84+ ${common:path-munge}
85 ${common:warnings}
86
87 [coverage]
88@@ -308,7 +315,7 @@
89 entry-points =
90 coverage=coverage.cmdline:main
91 initialization =
92- ${common:path-sort}
93+ ${common:path-munge}
94 ${common:warnings}
95 scripts =
96 coverage
97
98=== modified file 'src/maascli/__init__.py'
99--- src/maascli/__init__.py 2016-03-28 13:54:47 +0000
100+++ src/maascli/__init__.py 2016-07-13 11:35:29 +0000
101@@ -25,9 +25,16 @@
102
103 try:
104 options = parser.parse_args(argv[1:])
105- options.execute(options)
106+ if hasattr(options, "execute"):
107+ options.execute(options)
108+ else:
109+ # This mimics the error behaviour provided by argparse 1.1 from
110+ # PyPI (which differs from argparse 1.1 in the standard library).
111+ parser.error("too few arguments")
112 except KeyboardInterrupt:
113 raise SystemExit(1)
114+ except SystemExit:
115+ raise # Pass-through.
116 except Exception as error:
117 if options.debug:
118 raise
119
120=== modified file 'src/maascli/tests/test_cli.py'
121--- src/maascli/tests/test_cli.py 2016-06-22 17:03:02 +0000
122+++ src/maascli/tests/test_cli.py 2016-07-13 11:35:29 +0000
123@@ -26,7 +26,7 @@
124 from testtools.matchers import DocTestMatches
125
126
127-class TestRegisterommands(MAASTestCase):
128+class TestRegisterCommands(MAASTestCase):
129 """Tests for registers CLI commands."""
130
131 def test_registers_subparsers(self):
132
133=== modified file 'src/maascli/tests/test_integration.py'
134--- src/maascli/tests/test_integration.py 2015-12-01 18:12:59 +0000
135+++ src/maascli/tests/test_integration.py 2016-07-13 11:35:29 +0000
136@@ -1,4 +1,4 @@
137-# Copyright 2012-2015 Canonical Ltd. This software is licensed under the
138+# Copyright 2012-2016 Canonical Ltd. This software is licensed under the
139 # GNU Affero General Public License version 3 (see the file LICENSE).
140
141 """Integration-test the `maascli` command."""
142@@ -6,14 +6,23 @@
143 __all__ = []
144
145 import os.path
146+import random
147 from subprocess import (
148 CalledProcessError,
149 check_output,
150 STDOUT,
151 )
152+from textwrap import dedent
153
154+from maascli import main
155+from maascli.config import ProfileConfig
156+from maascli.testing.config import make_configs
157+from maascli.utils import handler_command_name
158 from maastesting import root
159+from maastesting.fixtures import CaptureStandardIO
160+from maastesting.matchers import DocTestMatches
161 from maastesting.testcase import MAASTestCase
162+from testtools.matchers import Equals
163
164
165 def locate_maascli():
166@@ -54,3 +63,35 @@
167 else:
168 # The test is that we get here without error.
169 pass
170+
171+
172+class TestMain(MAASTestCase):
173+ """Tests of `maascli.main` directly."""
174+
175+ def fake_profile(self):
176+ """Fake a profile."""
177+ configs = make_configs() # Instance of FakeConfig.
178+ self.patch(ProfileConfig, 'open').return_value = configs
179+ return configs
180+
181+ def test_complains_about_too_few_arguments(self):
182+ configs = self.fake_profile()
183+ [profile_name] = configs
184+ resources = configs[profile_name]["description"]["resources"]
185+ resource_name = random.choice(resources)["name"]
186+ command = "maas", profile_name, handler_command_name(resource_name)
187+
188+ with CaptureStandardIO() as stdio:
189+ error = self.assertRaises(SystemExit, main, command)
190+
191+ self.assertThat(error.code, Equals(2))
192+ self.assertThat(
193+ stdio.getError(),
194+ DocTestMatches(dedent(
195+ """\
196+ usage: maas [-h] COMMAND ...
197+ <BLANKLINE>
198+ ...
199+ <BLANKLINE>
200+ too few arguments
201+ """)))