Merge lp:~zyga/lava-test/support-lava-command into lp:lava-test/0.0

Proposed by Zygmunt Krynicki
Status: Rejected
Rejected by: Neil Williams
Proposed branch: lp:~zyga/lava-test/support-lava-command
Merge into: lp:lava-test/0.0
Diff against target: 529 lines (+478/-3)
5 files modified
doc/changes.rst (+9/-0)
lava/__init__.py (+3/-0)
lava/test/commands.py (+453/-0)
lava_test/commands.py (+1/-3)
setup.py (+12/-0)
To merge this branch: bzr merge lp:~zyga/lava-test/support-lava-command
Reviewer Review Type Date Requested Status
Linaro Validation Team Pending
Review via email: mp+100471@code.launchpad.net

Description of the change

This branch adds support for lava top-level command by duplicating all of the commands there. Some commands are altered to make the user interface better.

The old lava-test implementation is left untouched.

To post a comment you must log in.
Revision history for this message
Marcin Juszkiewicz (hrw) wrote :

Looks nice. I have linaro-json part already applied in my packages.

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

ping

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

This seems OK, but I assume it's quite a duplication of code?

Unmerged revisions

142. By Zygmunt Krynicki

Make lava test parse display what it is looking for

141. By Zygmunt Krynicki

Don't import and print version of linaro-json

140. By Zygmunt Krynicki

Create hierarchical version of all commands

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'doc/changes.rst'
2--- doc/changes.rst 2012-03-27 12:42:34 +0000
3+++ doc/changes.rst 2012-04-02 16:50:25 +0000
4@@ -6,6 +6,15 @@
5 Version 0.7 (UNRELEASED)
6 ========================
7
8+* Add a hierachical version of all commands. Now ``lava-test`` can be invoked as ``lava test``.
9+* Rename ``lava-test register-test`` to ``lava test register`` and ``lava-test
10+ unregister-test`` to ``lava test unregister``.
11+* Rename ``lava-test list-tests`` to ``lava test list``.
12+* Merge ``lava test list-installed`` with ``lava test list``. The optional
13+ --installed argument will now do what was previously a separate command.
14+* Change how ``lava test reset`` displays progress
15+* Make ``lava test parse`` display the files it is looking for
16+
17 .. _version_0_6:
18
19 Version 0.6
20
21=== added directory 'lava'
22=== added file 'lava/__init__.py'
23--- lava/__init__.py 1970-01-01 00:00:00 +0000
24+++ lava/__init__.py 2012-04-02 16:50:25 +0000
25@@ -0,0 +1,3 @@
26+__import__('pkg_resources').declare_namespace(__name__)
27+# DO NOT ADD ANYTHING TO THIS FILE!
28+# IT MUST STAY AS IS (empty apart from the two lines above)
29
30=== added directory 'lava/test'
31=== added file 'lava/test/__init__.py'
32=== added file 'lava/test/commands.py'
33--- lava/test/commands.py 1970-01-01 00:00:00 +0000
34+++ lava/test/commands.py 2012-04-02 16:50:25 +0000
35@@ -0,0 +1,453 @@
36+"""
37+lava.test.commands
38+====================
39+
40+LAVA command definitions used by lava-test
41+"""
42+
43+import logging
44+import os
45+import shutil
46+import subprocess
47+
48+from lava.tool.command import Command as LavaCommand, CommandGroup
49+from lava.tool.errors import CommandError
50+
51+from lava_test.api.observers import (
52+ ITestInstallerObserver,
53+ ITestRunnerObserver)
54+from lava_test.core.artifacts import TestArtifacts
55+from lava_test.core.config import get_config
56+from lava_test.core.loader import TestLoader
57+from lava_test.utils import Cache
58+
59+
60+__all__ = ["test", "list_", "install", "uninstall", "run", "parse", "show",
61+ "register", "unregister", "reset"]
62+
63+
64+class test(CommandGroup):
65+ """
66+ Access test commands.
67
68+ LAVA Test is a generic test wrapper framework used by the LAVA stack to run
69+ third party tests.
70+ """
71+
72+ namespace = "lava.test.commands"
73+
74+
75+class ObserverMethods(ITestInstallerObserver, ITestRunnerObserver):
76+ """
77+ Helper class that implements observer methods
78+ """
79+
80+ def about_to_install_packages(self, package_list):
81+ if self.args.quiet:
82+ return
83+ self.say("Installing packages: {0}", ", ".join(package_list))
84+
85+ def about_to_run_shell_command(self, cmd):
86+ if self.args.quiet:
87+ return
88+ self.say("Running shell command: {0!r}", cmd)
89+
90+ def about_to_download_file(self, url):
91+ if self.args.quiet:
92+ return
93+ self.say("Downloading file from: {0!r}", url)
94+
95+ def did_install_packages(self, package_list):
96+ pass
97+
98+ def did_run_shell_command(self, cmd, returncode):
99+ if returncode is None:
100+ self.say("Command {0!r} was terminated prematurely", cmd)
101+ elif returncode != 0:
102+ self.say("Command {0!r} returned non-zero exit status {1}",
103+ cmd, returncode)
104+
105+ def did_download_file(self, url):
106+ pass
107+
108+ def display_subprocess_output(self, stream_name, line):
109+ if self.args.quiet_subcommands:
110+ return
111+ if stream_name == 'stdout':
112+ self.say('(stdout) {0}', line.rstrip())
113+ elif stream_name == 'stderr':
114+ self.say('(stderr) {0}', line.rstrip())
115+
116+
117+class Command(LavaCommand, ObserverMethods):
118+
119+ def __init__(self, parser, args):
120+ super(Command, self).__init__(parser, args)
121+ self._config = get_config()
122+ self._test_loader = TestLoader(self._config)
123+ # XXX: check how this interacts with top-level lava logger
124+ if self.args.verbose:
125+ logging.root.setLevel(logging.DEBUG)
126+
127+ @classmethod
128+ def register_arguments(cls, parser):
129+ parser.add_argument(
130+ "-v", "--verbose",
131+ action="store_true",
132+ default=False,
133+ help="Be verbose about undertaken actions")
134+ parser.add_argument(
135+ "-q", "--quiet",
136+ action="store_true",
137+ default=False,
138+ help="Be less verbose about undertaken actions")
139+ parser.add_argument(
140+ "-Q", "--quiet-subcommands",
141+ action="store_true",
142+ default=False,
143+ help="Hide the output of all sub-commands (including tests)")
144+
145+
146+class TestAffectingCommand(Command):
147+
148+ INSTALL_REQUIRED = False
149+
150+ @classmethod
151+ def register_arguments(cls, parser):
152+ super(TestAffectingCommand, cls).register_arguments(parser)
153+ parser.add_argument(
154+ "test_id", help="Test or test suite identifier")
155+
156+ def invoke(self):
157+ try:
158+ test = self._test_loader[self.args.test_id]
159+ except KeyError:
160+ try:
161+ test = self._test_loader[self.args.test_id.replace('-', '_')]
162+ except KeyError:
163+ raise CommandError(
164+ "There is no test with the specified ID")
165+ return self.invoke_with_test(test)
166+
167+
168+class list_(Command):
169+ """
170+ List available tests
171+
172+ .. program:: lava-test list-tests
173+
174+ Lists all available tests, grouping them by provider.
175+ """
176+
177+ @classmethod
178+ def get_name(cls):
179+ return "list"
180+
181+ @classmethod
182+ def register_arguments(cls, parser):
183+ super(list_, cls).register_arguments(parser)
184+ parser.add_argument(
185+ "--installed",
186+ action="store_true",
187+ default=False,
188+ help="Display only installed tests")
189+
190+ def invoke(self):
191+ for provider in self._test_loader.get_providers():
192+ test_list = [provider[test_id] for test_id in provider]
193+ if not test_list:
194+ continue
195+ self.say("{0}", provider.description)
196+ for test in test_list:
197+ if self.args.installed and not test.is_installed:
198+ continue
199+ self.say(" - {test_id}", test_id=test.test_id)
200+
201+
202+class install(TestAffectingCommand):
203+ """
204+ Install a test program
205+ """
206+
207+ def invoke_with_test(self, test):
208+ if test.is_installed:
209+ raise CommandError("This test is already installed")
210+ try:
211+ test.install(self)
212+ except (subprocess.CalledProcessError, RuntimeError) as ex:
213+ raise CommandError(str(ex))
214+
215+
216+class uninstall(TestAffectingCommand):
217+ """
218+ Uninstall a test program
219+ """
220+
221+ def invoke_with_test(self, test):
222+ if not test.is_installed:
223+ raise CommandError("This test is not installed")
224+ test.uninstall()
225+
226+
227+class run(TestAffectingCommand):
228+ """
229+ Run a previously installed test program
230+ """
231+
232+ @classmethod
233+ def register_arguments(cls, parser):
234+ super(run, cls).register_arguments(parser)
235+ group = parser.add_argument_group("initial bundle configuration")
236+ group.add_argument(
237+ "-S", "--skip-software-context",
238+ default=False,
239+ action="store_true",
240+ help=("Do not store the software context in the"
241+ " initial bundle. Typically this saves OS"
242+ " image name and all the installed software"
243+ " packages."))
244+ group.add_argument(
245+ "-H", "--skip-hardware-context",
246+ default=False,
247+ action="store_true",
248+ help=("Do not store the hardware context in the"
249+ " initial bundle. Typically this saves CPU,"
250+ " memory and USB device information."))
251+ group.add_argument(
252+ "--trusted-time",
253+ default=False,
254+ action="store_true",
255+ help=("Indicate that the real time clock has"
256+ " accurate data. This can differentiate"
257+ " test results created on embedded devices"
258+ " that often have inaccurate real time"
259+ " clock settings."))
260+ group.add_argument(
261+ "--analyzer-assigned-uuid",
262+ default=None,
263+ metavar="UUID",
264+ help=("Set the analyzer_assigned_uuid to the specified value."
265+ " This will prevent the test device from attempting"
266+ " to generate an UUID by itself. This option may be"
267+ " required if the test device has unreliable real"
268+ " time clock (no battery backed, not ensure to be"
269+ " up-to-date) and unreliable/random hardware ethernet "
270+ " address."))
271+
272+ group = parser.add_argument_group("complete bundle configuration")
273+ group.add_argument(
274+ "-o", "--output",
275+ default=None,
276+ metavar="FILE",
277+ help=("After running the test parse the result"
278+ " artifacts, fuse them with the initial"
279+ " bundle and finally save the complete bundle"
280+ " to the specified FILE."))
281+ group.add_argument(
282+ "-A", "--skip-attachments",
283+ default=False,
284+ action="store_true",
285+ help=("Do not store standard output and standard"
286+ " error log files as attachments. This"
287+ " option is only affecting the bundle"
288+ " created with --output, the initial bundle"
289+ " is not affected as it never stores any"
290+ " attachments."))
291+
292+ parser.add_argument(
293+ "-t", "--test-options",
294+ default=None,
295+ help=(
296+ "Override the default test options."
297+ " The value is passed verbatim to test definition. Typically"
298+ " this is simply used in shell commands by expanding the"
299+ " string $(OPTIONS). Please refer to the built-in"
300+ " peacekeeper.py for examples. Depending on your shell you"
301+ " probably have to escape spaces and other special"
302+ " characters if you wish to include them in your argument"
303+ " options."))
304+
305+ def invoke_with_test(self, test):
306+ # Validate analyzer_assigned_uuid
307+ if self.args.analyzer_assigned_uuid:
308+ import uuid
309+ try:
310+ self.analyzer_assigned_uuid = str(
311+ uuid.UUID(self.args.analyzer_assigned_uuid))
312+ except ValueError as exc:
313+ self.parser.error("--analyzer-assigned-uuid: %s" % exc)
314+ if not test.is_installed:
315+ raise CommandError("The specified test is not installed")
316+ try:
317+ artifacts, run_fail = test.run(
318+ self, test_options=self.args.test_options)
319+ except subprocess.CalledProcessError as ex:
320+ if ex.returncode is None:
321+ raise CommandError("Command %r was aborted" % ex.cmd)
322+ else:
323+ raise CommandError(str(ex))
324+ except RuntimeError as ex:
325+ raise CommandError(str(ex))
326+ self.say("run complete, result_id is {0!r}", artifacts.result_id)
327+ try:
328+ artifacts.create_initial_bundle(
329+ self.args.skip_software_context,
330+ self.args.skip_hardware_context,
331+ self.args.trusted_time,
332+ self.args.analyzer_assigned_uuid)
333+ except ImportError as exc:
334+ msg_template = (
335+ "Unable to probe for software context. Install the '%s'"
336+ " package or invoke lava-test run with"
337+ " '--skip-software-context'")
338+ if exc.message == "No module named apt":
339+ raise CommandError(msg_template % "python-apt")
340+ elif exc.message == "No module named lsb_release":
341+ raise CommandError(msg_template % "lsb-release")
342+ else:
343+ raise
344+ artifacts.save_bundle()
345+ if self.args.output:
346+ parse_results = test.parse(artifacts)
347+ artifacts.incorporate_parse_results(parse_results)
348+ if not self.args.skip_attachments:
349+ artifacts.attach_standard_files_to_bundle()
350+ artifacts.save_bundle_as(self.args.output)
351+ if run_fail:
352+ raise CommandError(
353+ 'Some of test steps returned non-zero exit code')
354+
355+
356+class parse(TestAffectingCommand):
357+ """
358+ Parse the results of previous test run
359+ """
360+
361+ @classmethod
362+ def register_arguments(cls, parser):
363+ super(parse, cls).register_arguments(parser)
364+ parser.add_argument(
365+ "result_id",
366+ help="Test run result identifier")
367+ group = parser.add_argument_group("complete bundle configuration")
368+ group.add_argument(
369+ "-o", "--output",
370+ default=None,
371+ metavar="FILE",
372+ help=("After running the test parse the result"
373+ " artifacts, fuse them with the initial"
374+ " bundle and finally save the complete bundle"
375+ " to the specified FILE."))
376+ group.add_argument(
377+ "-A", "--skip-attachments",
378+ default=False,
379+ action="store_true",
380+ help=("Do not store standard output and standard"
381+ " error log files as attachments. This"
382+ " option is only affecting the bundle"
383+ " created with --output, the initial bundle"
384+ " is not affected as it never stores any"
385+ " attachments."))
386+
387+ def invoke_with_test(self, test):
388+ artifacts = TestArtifacts(
389+ self.args.test_id, self.args.result_id, self._config)
390+ self.say("Loading test data from {0}", artifacts.bundle_pathname)
391+ if not os.path.exists(artifacts.bundle_pathname):
392+ raise CommandError("Specified result does not exist")
393+ artifacts.load_bundle()
394+ parse_results = test.parse(artifacts)
395+ artifacts.incorporate_parse_results(parse_results)
396+ self.say("Parsed {0} test results",
397+ len(artifacts.bundle["test_runs"][0]["test_results"]))
398+ logging.debug(artifacts.dumps_bundle())
399+ if self.args.output:
400+ if not self.args.skip_attachments:
401+ artifacts.attach_standard_files_to_bundle()
402+ artifacts.save_bundle_as(self.args.output)
403+
404+
405+class show(Command):
406+ """
407+ Display the output from a previous test run
408+ """
409+
410+ @classmethod
411+ def register_arguments(cls, parser):
412+ super(show, cls).register_arguments(parser)
413+ parser.add_argument(
414+ "result_id", help="Test run result identifier")
415+
416+ def invoke(self):
417+ artifacts = TestArtifacts(None, self.args.result_id, self._config)
418+ if not os.path.exists(artifacts.results_dir):
419+ raise CommandError("Specified result does not exist")
420+ if os.path.exists(artifacts.stdout_pathname):
421+ with open(artifacts.stdout_pathname, "rt") as stream:
422+ for line in iter(stream.readline, ''):
423+ self.display_subprocess_output("stdout", line)
424+ if os.path.exists(artifacts.stderr_pathname):
425+ with open(artifacts.stderr_pathname, "rt") as stream:
426+ for line in iter(stream.readline, ''):
427+ self.display_subprocess_output("stderr", line)
428+
429+
430+class register(Command):
431+ """
432+ Register remote test
433+ """
434+
435+ @classmethod
436+ def register_arguments(cls, parser):
437+ super(register, cls).register_arguments(parser)
438+ parser.add_argument(
439+ "test_url", help="Url for test definition file")
440+
441+ def invoke(self):
442+ try:
443+ from lava_test.core.providers import RegistryProvider
444+ RegistryProvider.register_remote_test(self.args.test_url)
445+ except ValueError as exc:
446+ raise CommandError("Unable to register test: %s" % exc)
447+ except KeyError:
448+ raise CommandError("There is no test_url")
449+
450+
451+class unregister(Command):
452+ """
453+ Remove a declarative test from the registry
454
455+ This command does the reverse of lava-test register. You need to pass the
456+ same URL you've used in `lava-test register-test`
457+ """
458+
459+ @classmethod
460+ def register_arguments(cls, parser):
461+ super(unregister, cls).register_arguments(parser)
462+ parser.add_argument(
463+ "url",
464+ metavar="URL",
465+ help="URL of the test definition file")
466+
467+ def invoke(self):
468+ from lava_test.core.providers import RegistryProvider
469+ try:
470+ RegistryProvider.unregister_remote_test(self.args.url)
471+ except ValueError:
472+ raise CommandError("This test is not registered")
473+
474+
475+class reset(Command):
476+ """
477+ Reset the lava-test environment by removing cached items, all registered
478+ simple declarative tests and configuration files
479+ """
480+
481+ def invoke(self):
482+ self.say("Deleting {0}", self._config.configdir)
483+ shutil.rmtree(self._config.configdir, ignore_errors=True)
484+ self.say("Deleting {0}", self._config.installdir)
485+ shutil.rmtree(self._config.installdir, ignore_errors=True)
486+ self.say("Deleting {0}", self._config.resultsdir)
487+ shutil.rmtree(self._config.resultsdir, ignore_errors=True)
488+ cache = Cache.get_instance()
489+ self.say("Deleting {0}", cache.cache_dir)
490+ shutil.rmtree(cache.cache_dir, ignore_errors=True)
491
492=== modified file 'lava_test/commands.py'
493--- lava_test/commands.py 2012-03-19 19:24:05 +0000
494+++ lava_test/commands.py 2012-04-02 16:50:25 +0000
495@@ -400,12 +400,10 @@
496 import lava_tool
497 import lava_test
498 import linaro_dashboard_bundle
499- import linaro_json
500 return [
501 lava_test,
502 lava_tool,
503- linaro_dashboard_bundle,
504- linaro_json]
505+ linaro_dashboard_bundle]
506
507
508 class register_test(Command):
509
510=== modified file 'setup.py'
511--- setup.py 2012-03-02 14:21:13 +0000
512+++ setup.py 2012-04-02 16:50:25 +0000
513@@ -32,6 +32,18 @@
514 entry_points="""
515 [console_scripts]
516 lava-test=lava_test.main:main
517+ [lava.commands]
518+ test=lava.test.commands:test
519+ [lava.test.commands]
520+ list=lava.test.commands:list_
521+ install=lava.test.commands:install
522+ uninstall=lava.test.commands:uninstall
523+ run=lava.test.commands:run
524+ parse=lava.test.commands:parse
525+ show=lava.test.commands:show
526+ register=lava.test.commands:register
527+ unregister=lava.test.commands:unregister
528+ reset=lava.test.commands:reset
529 [lava_test.commands]
530 version=lava_test.commands:version
531 list-tests=lava_test.commands:list_tests

Subscribers

People subscribed via source and target branches