Merge lp:~nskaggs/juju-ci-tools/add-sane-args-defaults into lp:juju-ci-tools

Proposed by Nicholas Skaggs
Status: Superseded
Proposed branch: lp:~nskaggs/juju-ci-tools/add-sane-args-defaults
Merge into: lp:juju-ci-tools
Diff against target: 234 lines (+147/-19)
2 files modified
tests/test_utility.py (+65/-0)
utility.py (+82/-19)
To merge this branch: bzr merge lp:~nskaggs/juju-ci-tools/add-sane-args-defaults
Reviewer Review Type Date Requested Status
Christopher Lee (community) Needs Fixing
Martin Packman (community) Needs Fixing
Review via email: mp+296358@code.launchpad.net

This proposal has been superseded by a proposal from 2016-07-08.

Description of the change

Make juju_bin, temp_env_name and logs all as optional arguments with sane defaults

To post a comment you must log in.
Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

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)?

Revision history for this message
Martin Packman (gz) wrote :

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_testing_arguments, TestAddBasicTestingArguments should be changed.

$ rm -rf /tmp/logs
$ touch /tmp/logs
$ make test
...
FAILED (failures=2, errors=37)

Some other comments inline.

review: Needs Fixing
Revision history for this message
Aaron Bentley (abentley) wrote :

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.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

Replies.

Revision history for this message
Aaron Bentley (abentley) wrote :

You don't seem to have replied to me.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

@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).

Revision history for this message
Aaron Bentley (abentley) wrote :

> 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.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

> > 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.

Revision history for this message
Aaron Bentley (abentley) wrote :

Additional temp_env_name comment.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

Addressed uniqueness issue with temp_env_name

Revision history for this message
Martin Packman (gz) wrote :

Most changes seem fine, but we still have the test isolation issue from creating the logdir I pointed out before:

ubuntu@go:~/juju-ci-tools$ bzr st
modified:
  tests/test_utility.py
  utility.py
pending merge tips: (use -v to see all merge revisions)
  Nicholas Skaggs 2016-06-07 make temp_env_name truly unique

ubuntu@go:~/juju-ci-tools$ make test
...
FAILED (failures=2, errors=39)

ubuntu@go:~/juju-ci-tools$ bzr st
modified:
  tests/test_utility.py
  utility.py
unknown:
  baz/
  d/
  log/
  log_dir/
  test_utility_20160608144450_logs/
pending merge tips: (use -v to see all merge revisions)
  Nicholas Skaggs 2016-06-07 make temp_env_name truly unique

review: Needs Fixing
Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

@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?

Revision history for this message
Martin Packman (gz) wrote :

See my first comment for instructions. Tests should not fail based on the contents on /tmp.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

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.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

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.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

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.

Revision history for this message
Christopher Lee (veebers) wrote :

Some comments inline.

review: Needs Fixing
Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

Replies inline.

Revision history for this message
Christopher Lee (veebers) wrote :

Comment re: readability

review: Needs Fixing
Revision history for this message
Christopher Lee (veebers) wrote :

Reply to comments

Revision history for this message
Christopher Lee (veebers) wrote :

LGTM

review: Approve
Revision history for this message
Christopher Lee (veebers) wrote :

> LGTM
Actually I take that back. I have concerns on calling --help creating a directory.

review: Needs Fixing
1469. By Nicholas Skaggs

fix for bug #1601987

1470. By Nicholas Skaggs

fix help test

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

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

1476. By Nicholas Skaggs

restore _generate_default_clean_dir

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'tests/test_utility.py'
--- tests/test_utility.py 2016-05-26 16:28:21 +0000
+++ tests/test_utility.py 2016-07-07 21:48:09 +0000
@@ -11,6 +11,7 @@
11import logging11import logging
12import os12import os
13import socket13import socket
14from tempfile import mkdtemp
14from time import time15from time import time
15import warnings16import warnings
1617
@@ -361,6 +362,38 @@
361362
362class TestAddBasicTestingArguments(TestCase):363class TestAddBasicTestingArguments(TestCase):
363364
365 def test_no_args(self):
366 cmd_line = []
367 parser = add_basic_testing_arguments(ArgumentParser())
368 with patch('utility.os.makedirs'):
369 with patch('utility.os.getenv', return_value=''):
370 args = parser.parse_args(cmd_line)
371 self.assertEqual(args.env, 'lxd')
372 self.assertEqual(args.juju_bin, '/usr/bin/juju')
373
374 logs_arg = args.logs.split("/")
375 logs_ts = logs_arg[4]
376 self.assertEqual(logs_arg[1:4], ['tmp', 'test_utility', 'logs'])
377 self.assertTrue(logs_ts, datetime.strptime(logs_ts, "%Y%m%d%H%M%S"))
378
379 temp_env_name_arg = args.temp_env_name.split("_")
380 temp_env_name_ts = temp_env_name_arg[2]
381 self.assertEqual(temp_env_name_arg[0:2], ['test', 'utility'])
382 self.assertTrue(temp_env_name_ts,
383 datetime.strptime(temp_env_name_ts, "%Y%m%d%H%M%S"))
384 self.assertEqual(temp_env_name_arg[3:5], ['temp', 'env'])
385
386 self.assertEqual(logs_ts, temp_env_name_ts)
387
388 def test_default_binary(self):
389 cmd_line = []
390 with patch('utility.os.makedirs'):
391 with patch('utility.os.getenv', return_value='/tmp'):
392 with patch('utility.os.path.isfile', return_value=True):
393 parser = add_basic_testing_arguments(ArgumentParser())
394 args = parser.parse_args(cmd_line)
395 self.assertEqual(args.juju_bin, '/tmp/bin/juju')
396
364 def test_positional_args(self):397 def test_positional_args(self):
365 cmd_line = ['local', '/foo/juju', '/tmp/logs', 'testtest']398 cmd_line = ['local', '/foo/juju', '/tmp/logs', 'testtest']
366 parser = add_basic_testing_arguments(ArgumentParser())399 parser = add_basic_testing_arguments(ArgumentParser())
@@ -408,6 +441,38 @@
408 self.assertEqual(warned, [])441 self.assertEqual(warned, [])
409 self.assertEqual("", self.log_stream.getvalue())442 self.assertEqual("", self.log_stream.getvalue())
410443
444 def test_warn_on_nonexistent_directory_creation(self):
445 with warnings.catch_warnings(record=True) as warned:
446 log_dir = mkdtemp()
447 os.rmdir(log_dir)
448 cmd_line = ['local', '/foo/juju', log_dir, 'testtest']
449 parser = add_basic_testing_arguments(ArgumentParser())
450 parser.parse_args(cmd_line)
451 self.assertEqual(len(warned), 1)
452 self.assertRegexpMatches(
453 str(warned[0].message),
454 r"Not a directory " + log_dir)
455 self.assertEqual("", self.log_stream.getvalue())
456
457 def test_warn_on_directory_creation_failure(self):
458 with warnings.catch_warnings(record=True) as warned:
459 with patch('utility.os.makedirs', side_effect=OSError):
460 cmd_line = []
461 try:
462 parser = add_basic_testing_arguments(ArgumentParser())
463 parser.parse_args(cmd_line)
464 except OSError:
465 # we catch our thrown OSError
466 pass
467 else:
468 self.fail('No exception thrown after' +
469 ' directory creation failure')
470 self.assertEqual(len(warned), 1)
471 self.assertRegexpMatches(
472 str(warned[0].message),
473 r"Failed to create logging directory: /tmp/test_utility/" +
474 "logs/.*. Please specify empty folder or try again")
475
411 def test_debug(self):476 def test_debug(self):
412 cmd_line = ['local', '/foo/juju', '/tmp/logs', 'testtest', '--debug']477 cmd_line = ['local', '/foo/juju', '/tmp/logs', 'testtest', '--debug']
413 parser = add_basic_testing_arguments(ArgumentParser())478 parser = add_basic_testing_arguments(ArgumentParser())
414479
=== modified file 'utility.py'
--- utility.py 2016-06-27 11:41:04 +0000
+++ utility.py 2016-07-07 21:48:09 +0000
@@ -104,18 +104,20 @@
104 try:104 try:
105 contents = os.listdir(maybe_dir)105 contents = os.listdir(maybe_dir)
106 except OSError as e:106 except OSError as e:
107 if e.errno != errno.ENOENT:107 if e.errno == errno.ENOENT:
108 raise108 # we don't raise this error due to tests abusing /tmp/logs
109 # GZ 2016-03-01: We may want to raise or just create the dir here, but109 warnings.warn('Not a directory {}'.format(maybe_dir))
110 # that confuses expectations of all existing parse_args tests.110 if e.errno == errno.EEXIST:
111 warnings.warn('Directory {} already exists'.format(maybe_dir))
111 else:112 else:
112 if contents and contents != ["empty"]:113 if contents and contents != ["empty"]:
113 warnings.warn("Directory %r has existing contents." % (maybe_dir,))114 warnings.warn(
115 'Directory {!r} has existing contents.'.format(maybe_dir))
114 return maybe_dir116 return maybe_dir
115117
116118
117def pause(seconds):119def pause(seconds):
118 print_now('Sleeping for %d seconds.' % seconds)120 print_now('Sleeping for {:d} seconds.'.format(seconds))
119 sleep(seconds)121 sleep(seconds)
120122
121123
@@ -194,7 +196,7 @@
194 except socket.gaierror as e:196 except socket.gaierror as e:
195 print_now(str(e))197 print_now(str(e))
196 except Exception as e:198 except Exception as e:
197 print_now('Unexpected %r: %s' % (type(e), e))199 print_now('Unexpected {!r}: {}'.format((type(e), e)))
198 raise200 raise
199 else:201 else:
200 conn.close()202 conn.close()
@@ -290,14 +292,59 @@
290 return subprocess.check_output(command)292 return subprocess.check_output(command)
291293
292294
295def _get_test_name_from_filename():
296 try:
297 calling_file = sys._getframe(2).f_back.f_globals['__file__']
298 return os.path.splitext(os.path.basename(calling_file))[0]
299 except:
300 return 'unknown_test'
301
302
303def _generate_default_clean_dir(timestamp):
304 """Creates a new unique directory for logging and returns the name"""
305 log_dir = os.path.join('/tmp', _get_test_name_from_filename(),
306 'logs', timestamp)
307 try:
308 os.makedirs(log_dir)
309 warnings.warn('"Created logging directory {}'.format(log_dir))
310 except OSError as e:
311 if e.errno == errno.EEXIST:
312 pass
313 else:
314 warnings.warn('Failed to create logging directory: ' +
315 log_dir +
316 ". Please specify empty folder or try again")
317 raise
318 return log_dir
319
320
321def _generate_default_temp_env_name(timestamp):
322 """Creates a new unique name for environment and returns the name"""
323 return '{}_{}_temp_env'.format(_get_test_name_from_filename(), timestamp)
324
325
326def _generate_default_binary():
327 """Returns GOPATH juju binary if it exists, otherwise /usr/bin/juju"""
328 if os.getenv('GOPATH'):
329 go_bin = os.getenv('GOPATH') + '/bin/juju'
330 if os.path.isfile(go_bin):
331 return go_bin
332
333 return '/usr/bin/juju'
334
335
293def add_basic_testing_arguments(parser, using_jes=False):336def add_basic_testing_arguments(parser, using_jes=False):
294 """Returns the parser loaded with basic testing arguments.337 """Returns the parser loaded with basic testing arguments.
295338
296 The basic testing arguments, used in conjuction with boot_context ensures339 The basic testing arguments, used in conjuction with boot_context ensures
297 a test can be run in any supported substrate in parallel.340 a test can be run in any supported substrate in parallel.
298341
299 This helper adds 4 positional arguments that define the minimum needed342 This helper adds 4 positional arguments that defines the minimum needed
300 to run a test script: env, juju_bin, logs, and temp_env_name.343 to run a test script.
344
345 These arguments (env, juju_bin, logs, temp_env_name) allow you to specify
346 specifics for which env, juju binary, which folder for logging and an
347 environment name for your test respectively.
301348
302 There are many optional args that either update the env's config or349 There are many optional args that either update the env's config or
303 manipulate the juju command line options to test in controlled situations350 manipulate the juju command line options to test in controlled situations
@@ -308,16 +355,32 @@
308 :param parser: an ArgumentParser.355 :param parser: an ArgumentParser.
309 :param using_jes: whether args should be tailored for JES testing.356 :param using_jes: whether args should be tailored for JES testing.
310 """357 """
311 # Required positional arguments.358
312 parser.add_argument(359 # Optional postional arguments
313 'env',360 # generate timestamp for use with default args
314 help='The juju environment to base the temp test environment on.')361 timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
315 parser.add_argument(362 parser.add_argument(
316 'juju_bin', help='Full path to the Juju binary.')363 'env', nargs='?',
317 parser.add_argument(364 help='The juju environment to base the temp test environment on.',
318 'logs', type=_clean_dir, help='A directory in which to store logs.')365 default='lxd')
319 parser.add_argument(366 parser.add_argument('juju_bin', nargs='?',
320 'temp_env_name', help='A temporary test environment name.')367 help='Full path to the Juju binary. By default, this'
368 ' will use $GOPATH/bin/juju or /usr/bin/juju in that'
369 ' order.',
370 default=_generate_default_binary())
371 parser.add_argument('logs', nargs='?', type=_clean_dir,
372 help='A directory in which to store logs. By default,'
373 ' this will generate a directory named after the test'
374 ' and under /tmp, with a timestamp of the run. '
375 ' /tmp/test_name/logs/timestamp',
376 default=_generate_default_clean_dir(timestamp))
377 parser.add_argument('temp_env_name', nargs='?',
378 help='A temporary test environment name. By default, '
379 ' this will generate an enviroment name using the '
380 ' timestamp and testname. '
381 ' test_name_timestamp_temp_env',
382 default=_generate_default_temp_env_name(timestamp))
383
321 # Optional keyword arguments.384 # Optional keyword arguments.
322 parser.add_argument('--debug', action='store_true',385 parser.add_argument('--debug', action='store_true',
323 help='Pass --debug to Juju.')386 help='Pass --debug to Juju.')

Subscribers

People subscribed via source and target branches