Merge lp:~nskaggs/juju-ci-tools/add-sane-args-defaults into lp:juju-ci-tools
- add-sane-args-defaults
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 1513 | ||||||||
Proposed branch: | lp:~nskaggs/juju-ci-tools/add-sane-args-defaults | ||||||||
Merge into: | lp:juju-ci-tools | ||||||||
Diff against target: |
256 lines (+91/-64) 4 files modified
deploy_stack.py (+28/-0) tests/test_deploy_stack.py (+32/-0) tests/test_utility.py (+23/-37) utility.py (+8/-27) |
||||||||
To merge this branch: | bzr merge lp:~nskaggs/juju-ci-tools/add-sane-args-defaults | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Packman (community) | Approve | ||
Christopher Lee | Pending | ||
Review via email: mp+299588@code.launchpad.net |
This proposal supersedes a proposal from 2016-06-02.
Commit message
Description of the change
Make juju_bin, temp_env_name and logs all as optional arguments with sane defaults
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal | # |
Martin Packman (gz) wrote : Posted in a previous version of this proposal | # |
The general idea of having sane defaults for the positional arguments I'm down with, I think we could even default 'env' to 'lxd' or something.
Given this changes add_basic_
$ rm -rf /tmp/logs
$ touch /tmp/logs
$ make test
...
FAILED (failures=2, errors=37)
Some other comments inline.
Aaron Bentley (abentley) wrote : Posted in a previous version of this proposal | # |
It seems strange to provide a default for the juju under test. It is, after all, the thing we are testing, and if we don't want to specify it, we might as well not run the test at all.
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal | # |
Replies.
Aaron Bentley (abentley) wrote : Posted in a previous version of this proposal | # |
You don't seem to have replied to me.
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal | # |
@abentley, I suppose that is the crux of the argument for /usr/bin/juju vs $GOPATH/bin/juju. Odds are you care enough to specify. However, in the case of someone wanting to run the tests locally, they might not particularly care beyond "use the juju I have".
However, I went full bore on Martin's idea of going ahead and removing env too and making a sane default for it. I think the ability to just be able to run our tests is useful. For those of us who will actually attempt to do CI, we'll always want to specify everything, but we're presumably not the primary audience here (other than our localized test writing).
Aaron Bentley (abentley) wrote : Posted in a previous version of this proposal | # |
> However, in the case of
> someone wanting to run the tests locally, they might not particularly care
> beyond "use the juju I have".
Who is that person? Are they a juju developer?
If so, I think they are probably
1. writing a test for a feature they have written, or
2. running a test to make sure their changes don't break it
In both cases, they most likely have a custom binary that they want to test against.
So I still don't see who you're thinking would want to omit the path to the juju binary.
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal | # |
> > However, in the case of
> > someone wanting to run the tests locally, they might not particularly care
> > beyond "use the juju I have".
>
> Who is that person? Are they a juju developer?
Anyone who wishes to run tests locally; aka the maas guys, charm guys, openstack team, docs guys, juju developers. Granted ATM, few if any outside of our team run these tests locally.
> If so, I think they are probably
> 1. writing a test for a feature they have written, or
> 2. running a test to make sure their changes don't break it
>
> In both cases, they most likely have a custom binary that they want to test
> against.
I will grant you that indeed that a developer will have a custom binary in either case. Hence I was trying to phrase it as 'use the juju I have'. I won't pretend to know how many binaries developers tend to keep on there system; thus it depends on the developer if we can pick a sane default for them or not. It's likely they'll specify things.
If a developer has the juju binary they are hacking on set in a single place, it would be nice to just use that to enable that workflow, if indeed it exists. That would be an argument for $GOPATH. For the others listed above, I think /usr/bin/juju would be there expectation.
However, even if we assume developers and ourselves will always specify (and I agree that is likely), I do see value in having things just work when executed beyond enabling others. Removing all required arguments does that, so I would argue for adding the default on that basis alone. If we are left with only one argument, and we can reasonably remove it, let's do so. Lowering the barrier to running tests is the goal; albeit without breaking ourselves.
Aaron Bentley (abentley) wrote : Posted in a previous version of this proposal | # |
Additional temp_env_name comment.
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal | # |
Addressed uniqueness issue with temp_env_name
Martin Packman (gz) wrote : Posted in a previous version of this proposal | # |
Most changes seem fine, but we still have the test isolation issue from creating the logdir I pointed out before:
ubuntu@
modified:
tests/
utility.py
pending merge tips: (use -v to see all merge revisions)
Nicholas Skaggs 2016-06-07 make temp_env_name truly unique
ubuntu@
...
FAILED (failures=2, errors=39)
ubuntu@
modified:
tests/
utility.py
unknown:
baz/
d/
log/
log_dir/
test_
pending merge tips: (use -v to see all merge revisions)
Nicholas Skaggs 2016-06-07 make temp_env_name truly unique
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal | # |
@mgz, I don't get any errors running this. I think you are pointing out I shouldn't be creating a folder during test runs; absolutely correct, so I've patched that out. I also will add some more tests to ensure __clean_dir() works as it should given I've modified it as well. Is there something else you are referring to?
Martin Packman (gz) wrote : Posted in a previous version of this proposal | # |
See my first comment for instructions. Tests should not fail based on the contents on /tmp.
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal | # |
I fixed everything in test_utility to use a temp dir instead of a hardcoded path. In a seperate MP I'll fix the rest of trunk with the same fix.
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal | # |
Ok, I did this in a simpler way -- only throw an exception if the directory doesn't exist, and we fail to create it. We continue on with warnings otherwise. Tests pass, new tests confirm my expectations.
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal | # |
This fixes the /tmp/logs problem by not raising the issue. The only remaining annoyance is -h or --help will create a directory and leave the warning (since the default functions still run). Also, running make test will spam blank folders in /tmp. Comments welcome.
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal | # |
Some comments inline.
Nicholas Skaggs (nskaggs) wrote : Posted in a previous version of this proposal | # |
Replies inline.
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal | # |
Comment re: readability
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal | # |
Reply to comments
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal | # |
LGTM
Christopher Lee (veebers) wrote : Posted in a previous version of this proposal | # |
> LGTM
Actually I take that back. I have concerns on calling --help creating a directory.
Nicholas Skaggs (nskaggs) wrote : | # |
Creating a new MP to hash out the --help creating a directory issue. Continuing down the road of abusing things, this works to the extent we don't need to changed the existing tests. Given the constraint of not wanting to change the tests, I'm not sure there's something better. Chris gave the suggestion to just check sys.argv, which is better than my thought of parsing args twice :-)
- 1469. By Nicholas Skaggs
-
fix for bug #1601987
- 1470. By Nicholas Skaggs
-
fix help test
Nicholas Skaggs (nskaggs) wrote : | # |
@mgz or @veebers, I'd like to get this landed as there's a bug now, bug #1601987, that needs fixed. Can you review again?
Martin Packman (gz) wrote : | # |
Sorry Nicholas.
I think the current approach has gone past the point of sanity, we just have to split the parsing from the post-parsing configuration.
Am well aware I am the one who started us down the current path _clean_dir as a cunning hack, but we just should not be doing anything to the environment as a side effect of arg parsing.
- 1471. By Nicholas Skaggs
-
re-merge trunk
- 1472. By Nicholas Skaggs
-
remove directory creation as part of arg parsing. By default if nothing is specified, we will us the current directory
Nicholas Skaggs (nskaggs) wrote : | # |
@mgz, are you "ok" with chucking out generating a directory then and just defaulting to something we know exists? I've updated to use cwd, but we could also use temp. If this also doesn't work, I'll make specifying the directory required again and submit.
- 1473. By Nicholas Skaggs
-
move directory creation to bootstrap manager
- 1474. By Nicholas Skaggs
-
flake8
- 1475. By Nicholas Skaggs
-
patch out directory creation during no_args test
Nicholas Skaggs (nskaggs) wrote : | # |
This is ready again for review. Nothing is leftover after running make test, no directories are created when you specify arguments, and no directories are made by running help. This uses bootstrap manager instead of hacking arg parser :-)
- 1476. By Nicholas Skaggs
-
restore _generate_
default_ clean_dir
Martin Packman (gz) wrote : | # |
Okay, so this is an improvement, lets land it.
Preview Diff
1 | === modified file 'deploy_stack.py' |
2 | --- deploy_stack.py 2016-07-18 17:06:54 +0000 |
3 | +++ deploy_stack.py 2016-07-21 13:37:06 +0000 |
4 | @@ -7,6 +7,11 @@ |
5 | contextmanager, |
6 | nested, |
7 | ) |
8 | +from datetime import ( |
9 | + datetime, |
10 | +) |
11 | + |
12 | +import errno |
13 | import glob |
14 | import logging |
15 | import os |
16 | @@ -487,7 +492,30 @@ |
17 | self.known_hosts['0'] = bootstrap_host |
18 | |
19 | @classmethod |
20 | + def _generate_default_clean_dir(cls, temp_env_name): |
21 | + """Creates a new unique directory for logging and returns name""" |
22 | + logging.info('Environment {}'.format(temp_env_name)) |
23 | + test_name = temp_env_name.split('-')[0] |
24 | + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") |
25 | + log_dir = os.path.join('/tmp', test_name, 'logs', timestamp) |
26 | + |
27 | + try: |
28 | + os.makedirs(log_dir) |
29 | + logging.info('Created logging directory {}'.format(log_dir)) |
30 | + except OSError as e: |
31 | + if e.errno == errno.EEXIST: |
32 | + logging.warn('"Directory {} already exists'.format(log_dir)) |
33 | + else: |
34 | + raise('Failed to create logging directory: {} ' + |
35 | + log_dir + |
36 | + '. Please specify empty folder or try again') |
37 | + return log_dir |
38 | + |
39 | + @classmethod |
40 | def from_args(cls, args): |
41 | + if not args.logs: |
42 | + args.logs = cls._generate_default_clean_dir(args.temp_env_name) |
43 | + |
44 | if args.juju_bin == 'FAKE': |
45 | env = SimpleEnvironment.from_config(args.env) |
46 | from tests.test_jujupy import fake_juju_client # Circular imports |
47 | |
48 | === modified file 'tests/test_deploy_stack.py' |
49 | --- tests/test_deploy_stack.py 2016-07-05 14:15:27 +0000 |
50 | +++ tests/test_deploy_stack.py 2016-07-21 13:37:06 +0000 |
51 | @@ -2,6 +2,9 @@ |
52 | Namespace, |
53 | ) |
54 | from contextlib import contextmanager |
55 | +from datetime import ( |
56 | + datetime, |
57 | +) |
58 | import json |
59 | import logging |
60 | import os |
61 | @@ -1034,6 +1037,35 @@ |
62 | self.assertEqual(jes_enabled, bs_manager.jes_enabled) |
63 | self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts) |
64 | |
65 | + def test_no_args(self): |
66 | + args = Namespace( |
67 | + env='foo', juju_bin='bar', debug=True, temp_env_name='baz', |
68 | + bootstrap_host='example.org', machine=['example.com'], |
69 | + series='angsty', agent_url='qux', agent_stream='escaped', |
70 | + region='eu-west-northwest-5', logs=None, keep_env=True) |
71 | + with patch('deploy_stack.client_from_config') as fc_mock: |
72 | + with patch('utility.os.makedirs'): |
73 | + bs_manager = BootstrapManager.from_args(args) |
74 | + fc_mock.assert_called_once_with('foo', 'bar', debug=True) |
75 | + self.assertEqual('baz', bs_manager.temp_env_name) |
76 | + self.assertIs(fc_mock.return_value, bs_manager.client) |
77 | + self.assertIs(fc_mock.return_value, bs_manager.tear_down_client) |
78 | + self.assertEqual('example.org', bs_manager.bootstrap_host) |
79 | + self.assertEqual(['example.com'], bs_manager.machines) |
80 | + self.assertEqual('angsty', bs_manager.series) |
81 | + self.assertEqual('qux', bs_manager.agent_url) |
82 | + self.assertEqual('escaped', bs_manager.agent_stream) |
83 | + self.assertEqual('eu-west-northwest-5', bs_manager.region) |
84 | + self.assertIs(True, bs_manager.keep_env) |
85 | + logs_arg = bs_manager.log_dir.split("/") |
86 | + logs_ts = logs_arg[4] |
87 | + self.assertEqual(logs_arg[1:4], ['tmp', 'baz', 'logs']) |
88 | + self.assertTrue(logs_ts, datetime.strptime(logs_ts, "%Y%m%d%H%M%S")) |
89 | + jes_enabled = bs_manager.client.is_jes_enabled.return_value |
90 | + self.assertEqual(jes_enabled, bs_manager.permanent) |
91 | + self.assertEqual(jes_enabled, bs_manager.jes_enabled) |
92 | + self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts) |
93 | + |
94 | def test_jes_not_permanent(self): |
95 | with self.assertRaisesRegexp(ValueError, 'Cannot set permanent False' |
96 | ' if jes_enabled is True.'): |
97 | |
98 | === modified file 'tests/test_utility.py' |
99 | --- tests/test_utility.py 2016-07-07 21:47:48 +0000 |
100 | +++ tests/test_utility.py 2016-07-21 13:37:06 +0000 |
101 | @@ -365,33 +365,26 @@ |
102 | def test_no_args(self): |
103 | cmd_line = [] |
104 | parser = add_basic_testing_arguments(ArgumentParser()) |
105 | - with patch('utility.os.makedirs'): |
106 | - with patch('utility.os.getenv', return_value=''): |
107 | - args = parser.parse_args(cmd_line) |
108 | + with patch('utility.os.getenv', return_value=''): |
109 | + args = parser.parse_args(cmd_line) |
110 | self.assertEqual(args.env, 'lxd') |
111 | self.assertEqual(args.juju_bin, '/usr/bin/juju') |
112 | |
113 | - logs_arg = args.logs.split("/") |
114 | - logs_ts = logs_arg[4] |
115 | - self.assertEqual(logs_arg[1:4], ['tmp', 'test_utility', 'logs']) |
116 | - self.assertTrue(logs_ts, datetime.strptime(logs_ts, "%Y%m%d%H%M%S")) |
117 | + self.assertEqual(args.logs, None) |
118 | |
119 | - temp_env_name_arg = args.temp_env_name.split("_") |
120 | - temp_env_name_ts = temp_env_name_arg[2] |
121 | - self.assertEqual(temp_env_name_arg[0:2], ['test', 'utility']) |
122 | + temp_env_name_arg = args.temp_env_name.split("-") |
123 | + temp_env_name_ts = temp_env_name_arg[1] |
124 | + self.assertEqual(temp_env_name_arg[0:1], ['testutility']) |
125 | self.assertTrue(temp_env_name_ts, |
126 | datetime.strptime(temp_env_name_ts, "%Y%m%d%H%M%S")) |
127 | - self.assertEqual(temp_env_name_arg[3:5], ['temp', 'env']) |
128 | - |
129 | - self.assertEqual(logs_ts, temp_env_name_ts) |
130 | + self.assertEqual(temp_env_name_arg[2:4], ['temp', 'env']) |
131 | |
132 | def test_default_binary(self): |
133 | cmd_line = [] |
134 | - with patch('utility.os.makedirs'): |
135 | - with patch('utility.os.getenv', return_value='/tmp'): |
136 | - with patch('utility.os.path.isfile', return_value=True): |
137 | - parser = add_basic_testing_arguments(ArgumentParser()) |
138 | - args = parser.parse_args(cmd_line) |
139 | + with patch('utility.os.getenv', return_value='/tmp'): |
140 | + with patch('utility.os.path.isfile', return_value=True): |
141 | + parser = add_basic_testing_arguments(ArgumentParser()) |
142 | + args = parser.parse_args(cmd_line) |
143 | self.assertEqual(args.juju_bin, '/tmp/bin/juju') |
144 | |
145 | def test_positional_args(self): |
146 | @@ -441,6 +434,18 @@ |
147 | self.assertEqual(warned, []) |
148 | self.assertEqual("", self.log_stream.getvalue()) |
149 | |
150 | + def test_no_warn_on_help(self): |
151 | + """Special case help should not generate a warning""" |
152 | + with warnings.catch_warnings(record=True) as warned: |
153 | + with patch('utility.sys.exit'): |
154 | + parser = add_basic_testing_arguments(ArgumentParser()) |
155 | + cmd_line = ['-h'] |
156 | + parser.parse_args(cmd_line) |
157 | + cmd_line = ['--help'] |
158 | + parser.parse_args(cmd_line) |
159 | + |
160 | + self.assertEqual(warned, []) |
161 | + |
162 | def test_warn_on_nonexistent_directory_creation(self): |
163 | with warnings.catch_warnings(record=True) as warned: |
164 | log_dir = mkdtemp() |
165 | @@ -454,25 +459,6 @@ |
166 | r"Not a directory " + log_dir) |
167 | self.assertEqual("", self.log_stream.getvalue()) |
168 | |
169 | - def test_warn_on_directory_creation_failure(self): |
170 | - with warnings.catch_warnings(record=True) as warned: |
171 | - with patch('utility.os.makedirs', side_effect=OSError): |
172 | - cmd_line = [] |
173 | - try: |
174 | - parser = add_basic_testing_arguments(ArgumentParser()) |
175 | - parser.parse_args(cmd_line) |
176 | - except OSError: |
177 | - # we catch our thrown OSError |
178 | - pass |
179 | - else: |
180 | - self.fail('No exception thrown after' + |
181 | - ' directory creation failure') |
182 | - self.assertEqual(len(warned), 1) |
183 | - self.assertRegexpMatches( |
184 | - str(warned[0].message), |
185 | - r"Failed to create logging directory: /tmp/test_utility/" + |
186 | - "logs/.*. Please specify empty folder or try again") |
187 | - |
188 | def test_debug(self): |
189 | cmd_line = ['local', '/foo/juju', '/tmp/logs', 'testtest', '--debug'] |
190 | parser = add_basic_testing_arguments(ArgumentParser()) |
191 | |
192 | === modified file 'utility.py' |
193 | --- utility.py 2016-07-07 22:00:51 +0000 |
194 | +++ utility.py 2016-07-21 13:37:06 +0000 |
195 | @@ -300,27 +300,12 @@ |
196 | return 'unknown_test' |
197 | |
198 | |
199 | -def _generate_default_clean_dir(timestamp): |
200 | - """Creates a new unique directory for logging and returns the name""" |
201 | - log_dir = os.path.join('/tmp', _get_test_name_from_filename(), |
202 | - 'logs', timestamp) |
203 | - try: |
204 | - os.makedirs(log_dir) |
205 | - warnings.warn('"Created logging directory {}'.format(log_dir)) |
206 | - except OSError as e: |
207 | - if e.errno == errno.EEXIST: |
208 | - pass |
209 | - else: |
210 | - warnings.warn('Failed to create logging directory: ' + |
211 | - log_dir + |
212 | - ". Please specify empty folder or try again") |
213 | - raise |
214 | - return log_dir |
215 | - |
216 | - |
217 | -def _generate_default_temp_env_name(timestamp): |
218 | +def _generate_default_temp_env_name(): |
219 | """Creates a new unique name for environment and returns the name""" |
220 | - return '{}_{}_temp_env'.format(_get_test_name_from_filename(), timestamp) |
221 | + # we need to sanitize the name |
222 | + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") |
223 | + test_name = re.sub('[^a-zA-Z]', '', _get_test_name_from_filename()) |
224 | + return '{}-{}-temp-env'.format(test_name, timestamp) |
225 | |
226 | |
227 | def _generate_default_binary(): |
228 | @@ -357,8 +342,6 @@ |
229 | """ |
230 | |
231 | # Optional postional arguments |
232 | - # generate timestamp for use with default args |
233 | - timestamp = datetime.now().strftime("%Y%m%d%H%M%S") |
234 | parser.add_argument( |
235 | 'env', nargs='?', |
236 | help='The juju environment to base the temp test environment on.', |
237 | @@ -370,16 +353,14 @@ |
238 | default=_generate_default_binary()) |
239 | parser.add_argument('logs', nargs='?', type=_clean_dir, |
240 | help='A directory in which to store logs. By default,' |
241 | - ' this will generate a directory named after the test' |
242 | - ' and under /tmp, with a timestamp of the run. ' |
243 | - ' /tmp/test_name/logs/timestamp', |
244 | - default=_generate_default_clean_dir(timestamp)) |
245 | + ' this will use the current directory', |
246 | + default=None) |
247 | parser.add_argument('temp_env_name', nargs='?', |
248 | help='A temporary test environment name. By default, ' |
249 | ' this will generate an enviroment name using the ' |
250 | ' timestamp and testname. ' |
251 | ' test_name_timestamp_temp_env', |
252 | - default=_generate_default_temp_env_name(timestamp)) |
253 | + default=_generate_default_temp_env_name()) |
254 | |
255 | # Optional keyword arguments. |
256 | parser.add_argument('--debug', action='store_true', |
Reviewers note; I'm tempted to add a timestamp to the environment name or otherwise make it more unique. Ideas welcome. Would you find that useful or annoying (especially since it may not be the exact same ts as the logging folder)?