Merge lp:~le-chi-thu/lava-test/using-lava-tool-2 into lp:lava-test/0.0

Proposed by Le Chi Thu
Status: Merged
Merged at revision: 93
Proposed branch: lp:~le-chi-thu/lava-test/using-lava-tool-2
Merge into: lp:lava-test/0.0
Diff against target: 7538 lines (+3730/-2909)
79 files modified
.bzrignore (+1/-0)
MANIFEST.in (+0/-1)
README (+3/-11)
abrek/api.py (+0/-76)
abrek/builtins.py (+0/-175)
abrek/bundle.py (+0/-44)
abrek/cache.py (+0/-73)
abrek/command.py (+0/-171)
abrek/config.py (+0/-93)
abrek/dashboard.py (+0/-206)
abrek/hwprofile.py (+0/-216)
abrek/providers.py (+0/-145)
abrek/results.py (+0/-111)
abrek/swprofile.py (+0/-65)
abrek/testdef.py (+0/-435)
bin/lava-test (+0/-31)
doc/changes.rst (+25/-0)
doc/conf.py (+211/-0)
doc/index.rst (+81/-0)
doc/installation.rst (+66/-0)
doc/reference.rst (+107/-0)
doc/todo.rst (+4/-0)
doc/usage.rst (+195/-0)
examples/power-management-tests.json (+1/-1)
examples/stream.json (+1/-1)
lava_test/__init__.py (+1/-1)
lava_test/api/__init__.py (+24/-0)
lava_test/api/core.py (+164/-0)
lava_test/api/delegates.py (+119/-0)
lava_test/api/observers.py (+120/-0)
lava_test/commands.py (+383/-0)
lava_test/core/artifacts.py (+277/-0)
lava_test/core/config.py (+97/-0)
lava_test/core/hwprofile.py (+223/-0)
lava_test/core/installers.py (+105/-0)
lava_test/core/loader.py (+83/-0)
lava_test/core/parsers.py (+147/-0)
lava_test/core/providers.py (+165/-0)
lava_test/core/runners.py (+66/-0)
lava_test/core/swprofile.py (+72/-0)
lava_test/core/tests.py (+166/-0)
lava_test/extcmd.py (+108/-0)
lava_test/main.py (+33/-16)
lava_test/test_definitions/bootchart.py (+9/-6)
lava_test/test_definitions/firefox.py (+10/-5)
lava_test/test_definitions/glmemperf.py (+11/-6)
lava_test/test_definitions/gmpbench.py (+11/-6)
lava_test/test_definitions/gtkperf.py (+11/-6)
lava_test/test_definitions/ltp.py (+11/-6)
lava_test/test_definitions/peacekeeper.py (+11/-6)
lava_test/test_definitions/posixtestsuite.py (+11/-6)
lava_test/test_definitions/pwrmgmt.py (+25/-29)
lava_test/test_definitions/pybench.py (+11/-6)
lava_test/test_definitions/smem.py (+10/-6)
lava_test/test_definitions/stream.py (+11/-6)
lava_test/test_definitions/tiobench.py (+11/-6)
lava_test/test_definitions/x11perf.py (+11/-6)
lava_test/test_definitions/xrestop.py (+9/-6)
lava_test/utils.py (+126/-40)
setup.py (+25/-5)
tests/__init__.py (+6/-10)
tests/fixtures.py (+1/-1)
tests/imposters.py (+11/-10)
tests/test_abrekcmd.py (+0/-137)
tests/test_abrektest.py (+0/-48)
tests/test_abrektestinstaller.py (+0/-60)
tests/test_abrektestparser.py (+0/-65)
tests/test_abrektestrunner.py (+0/-104)
tests/test_builtins.py (+0/-66)
tests/test_dashboard.py (+0/-213)
tests/test_hwprofile.py (+13/-13)
tests/test_lavatest_commands.py (+62/-0)
tests/test_lavatest_test.py (+55/-0)
tests/test_lavatest_testinstaller.py (+63/-0)
tests/test_lavatest_testparser.py (+70/-0)
tests/test_lavatest_testrunner.py (+73/-0)
tests/test_main.py (+0/-32)
tests/test_results.py (+0/-117)
tests/test_swprofile.py (+4/-4)
To merge this branch: bzr merge lp:~le-chi-thu/lava-test/using-lava-tool-2
Reviewer Review Type Date Requested Status
Linaro Validation Team Pending
Review via email: mp+75272@code.launchpad.net

Description of the change

This is the update from review using-lava-tool pm.

I synced changes in trunk.

* Added register-test, unregister-test commands
* Renamed artefacts to artifacts
* Updated Copyright 2011
* Load the configuration if python logging configuration exist.

To post a comment you must log in.
96. By Le Chi Thu <email address hidden> <email address hidden>

Fixed type error. get_config()

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2011-06-21 19:38:29 +0000
3+++ .bzrignore 2011-09-13 22:44:36 +0000
4@@ -5,3 +5,4 @@
5 *~
6 *.tmp
7 *.py[co]
8+build
9
10=== modified file 'MANIFEST.in'
11--- MANIFEST.in 2011-06-21 19:38:29 +0000
12+++ MANIFEST.in 2011-09-13 22:44:36 +0000
13@@ -1,4 +1,3 @@
14 include COPYING
15 include README
16 include .testr.conf
17-include bin/lava-test
18\ No newline at end of file
19
20=== modified file 'README'
21--- README 2011-08-03 14:11:26 +0000
22+++ README 2011-09-13 22:44:36 +0000
23@@ -3,20 +3,11 @@
24 automatically installed, executed, and the results can be parsed and
25 uploaded to an external server.
26
27-External dependency
28--------------------
29-The following debian packages are needed:
30-* python-setuptools
31-* python-apt
32-* usbutils
33-* python-testrepository - for running unit tests
34-
35 How to install from the source code
36 ===================================
37
38 1. Run: ./setup.py install
39
40-
41 How to setup from the source code for development
42 =================================================
43
44@@ -32,7 +23,7 @@
45 3. Add <home>/.local.bin in your PATH
46
47
48-To install build-in tests
49+To install built-in tests
50 =========================
51 1. Run: lava-test list-tests
52 2. Run: lava-test install <test>
53@@ -47,4 +38,5 @@
54 To install test define with a json file
55 =======================================
56 1. Run: lava-test register-test file://localhost/<..>/examples/stream.json
57-2. Run: lava-test list-tests
58\ No newline at end of file
59+2. Run: lava-test list-tests
60+
61
62=== removed file 'abrek/api.py'
63--- abrek/api.py 2011-06-28 12:51:57 +0000
64+++ abrek/api.py 1970-01-01 00:00:00 +0000
65@@ -1,76 +0,0 @@
66-"""
67-Public API for extending Abrek
68-"""
69-from abc import abstractmethod, abstractproperty
70-
71-class ITestProvider(object):
72- """
73- Abrek test provider.
74-
75- Abstract source of abrek tests.
76- """
77-
78- @abstractmethod
79- def __init__(self, config):
80- """
81- Initialize test provider with the specified configuration object. The
82- configuration object is obtained from the abrek providers registry.
83- """
84-
85- @abstractmethod
86- def __iter__(self):
87- """
88- Iterates over instances of ITest exposed by this provider
89- """
90-
91- @abstractmethod
92- def __getitem__(self, test_name):
93- """
94- Return an instance of ITest with the specified name
95- """
96-
97- @abstractproperty
98- def description(self):
99- """
100- The description string used by abrek list-tests
101- """
102-
103-
104-class ITest(object):
105- """
106- Abrek test.
107-
108- Something that can be installed and invoked by abre.
109- """
110-
111- @abstractmethod
112- def install(self):
113- """
114- Install the test suite.
115-
116- This creates an install directory under the user's XDG_DATA_HOME
117- directory to mark that the test is installed. The installer's
118- install() method is then called from this directory to complete any
119- test specific install that may be needed.
120- """
121-
122- @abstractmethod
123- def uninstall(self):
124- """
125- Uninstall the test suite.
126-
127- Uninstalling just recursively removes the test specific directory under
128- the user's XDG_DATA_HOME directory. This will both mark the test as
129- removed, and clean up any files that were downloaded or installed under
130- that directory. Dependencies are intentionally not removed by this.
131- """
132-
133- @abstractmethod
134- def run(self, quiet=False):
135- # TODO: Document me
136- pass
137-
138- @abstractmethod
139- def parse(self, resultname):
140- # TODO: Document me
141- pass
142
143=== removed file 'abrek/builtins.py'
144--- abrek/builtins.py 2011-08-03 14:25:13 +0000
145+++ abrek/builtins.py 1970-01-01 00:00:00 +0000
146@@ -1,175 +0,0 @@
147-# Copyright (c) 2010 Linaro
148-#
149-# This program is free software: you can redistribute it and/or modify
150-# it under the terms of the GNU General Public License as published by
151-# the Free Software Foundation, either version 3 of the License, or
152-# (at your option) any later version.
153-#
154-# This program is distributed in the hope that it will be useful,
155-# but WITHOUT ANY WARRANTY; without even the implied warranty of
156-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
157-# GNU General Public License for more details.
158-#
159-# You should have received a copy of the GNU General Public License
160-# along with this program. If not, see <http://www.gnu.org/licenses/>.
161-
162-import os
163-import sys
164-from optparse import make_option
165-
166-import abrek.command
167-import abrek.testdef
168-from abrek.config import get_config
169-
170-
171-class cmd_version(abrek.command.AbrekCmd):
172- """
173- Show the version of abrek
174- """
175- def run(self):
176- import abrek
177- print abrek.__version__
178-
179-
180-class cmd_help(abrek.command.AbrekCmd):
181- """ Get help on abrek commands
182-
183- If the command name is ommited, calling the help command will return a
184- list of valid commands.
185- """
186- arglist = ['command', 'subcommand']
187- def run(self):
188- if len(self.args) < 1:
189- print "Available commands:"
190- for cmd in abrek.command.get_all_cmds():
191- print " %s" % cmd
192- print
193- print "To access extended help on a command use 'abrek help " \
194- "[command]'"
195- return
196- command_name = self.args.pop(0)
197- cmd = abrek.command.get_command(command_name)
198- if not cmd:
199- print "No command found for '%s'" % command_name
200- return
201- while self.args:
202- subcommand_name = self.args.pop(0)
203- cmd = cmd.get_subcommand(subcommand_name)
204- if not cmd:
205- print "No sub-command of '%s' found for '%s'" % (
206- command_name, subcommand_name)
207- return
208- command_name += ' ' + subcommand_name
209- print cmd.help()
210-
211-
212-class cmd_install(abrek.command.AbrekCmd):
213- """
214- Install a test
215- """
216- arglist = ['*testname']
217-
218- def run(self):
219- self.checkroot()
220- if len(self.args) != 1:
221- print "please specify the name of the test to install"
222- sys.exit(1)
223- test = abrek.testdef.testloader(self.args[0])
224- try:
225- test.install()
226- except RuntimeError as strerror:
227- print "Test installation error: %s" % strerror
228- sys.exit(1)
229-
230-
231-class cmd_run(abrek.command.AbrekCmd):
232- """
233- Run tests
234- """
235- arglist = ['*testname']
236- options = [make_option('-q', '--quiet', action='store_true',
237- default=False, dest='quiet'),
238- make_option('-o', '--output', action='store',
239- default=None, metavar="FILE",
240- help="Store processed test output to FILE")]
241-
242- def run(self):
243- self.checkroot()
244- if len(self.args) != 1:
245- print "please specify the name of the test to run"
246- sys.exit(1)
247- test = abrek.testdef.testloader(self.args[0])
248- try:
249- result_id = test.run(quiet=self.opts.quiet)
250- if self.opts.output:
251- from abrek.dashboard import generate_bundle
252- import json
253- bundle = generate_bundle(result_id)
254- with open(self.opts.output, "wt") as stream:
255- json.dump(bundle, stream)
256- except Exception as strerror:
257- print "Test execution error: %s" % strerror
258- sys.exit(1)
259-
260-
261-class cmd_uninstall(abrek.command.AbrekCmd):
262- """
263- Uninstall a test
264- """
265- arglist = ['*testname']
266-
267- def run(self):
268- if len(self.args) != 1:
269- print "please specify the name of the test to uninstall"
270- sys.exit(1)
271- test = abrek.testdef.testloader(self.args[0])
272- try:
273- test.uninstall()
274- except Exception as strerror:
275- print "Test uninstall error: %s" % strerror
276- sys.exit(1)
277-
278-
279-class cmd_list_installed(abrek.command.AbrekCmd):
280- """
281- List tests that are currently installed
282- """
283- def run(self):
284- config = get_config()
285- print "Installed tests:"
286- try:
287- for dir in os.listdir(config.installdir):
288- print dir
289- except OSError:
290- print "No tests installed"
291-
292-
293-class cmd_list_tests(abrek.command.AbrekCmd):
294- """
295- List all known tests
296- """
297- def run(self):
298- from abrek.testdef import TestLoader
299- for provider in TestLoader().get_providers():
300- print provider.description
301- for test in provider:
302- print " - %s" % test
303-
304-
305-class cmd_register_test(abrek.command.AbrekCmd):
306- """
307- Register declarative tests
308- """
309-
310- arglist = ['test_url']
311-
312- def run(self):
313- if len(self.args) != 1:
314- self.parser.error("You need to provide an URL to a test definition file")
315- test_url = self.args[0]
316- from abrek.providers import RegistryProvider
317- try:
318- RegistryProvider.register_remote_test(test_url)
319- except ValueError as exc:
320- print "Unable to register test: %s" % exc
321- sys.exit(1)
322
323=== removed file 'abrek/bundle.py'
324--- abrek/bundle.py 2011-04-19 16:56:01 +0000
325+++ abrek/bundle.py 1970-01-01 00:00:00 +0000
326@@ -1,44 +0,0 @@
327-# Copyright (c) 2011 Linaro
328-#
329-# This program is free software: you can redistribute it and/or modify
330-# it under the terms of the GNU General Public License as published by
331-# the Free Software Foundation, either version 3 of the License, or
332-# (at your option) any later version.
333-#
334-# This program is distributed in the hope that it will be useful,
335-# but WITHOUT ANY WARRANTY; without even the implied warranty of
336-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
337-# GNU General Public License for more details.
338-#
339-# You should have received a copy of the GNU General Public License
340-# along with this program. If not, see <http://www.gnu.org/licenses/>.
341-
342-"""
343-This module attempts to use the linaro-dashboard-bundle package, if possible.
344-
345-Using that package adds proper support for loading and saving bundle
346-documents. In particular it supports loosles decimals, better, more stable
347-load-modify-write cycles, data validation, transparent migration and many
348-other features.
349-
350-It is not a hard dependency to make it possible to run abrek from a checkout
351-without having to install (too many) dependencies.
352-"""
353-
354-try:
355- from linaro_dashboard_bundle import DocumentIO
356-except ImportError:
357- import json
358-
359- class DocumentIO(object):
360- """ Bare replacement DocumentIO without any fancy features """
361-
362- @classmethod
363- def dumps(cls, doc):
364- return json.dumps(doc, indent=2)
365-
366- @classmethod
367- def loads(cls, text):
368- doc = json.loads(text)
369- fmt = doc.get("format")
370- return fmt, doc
371\ No newline at end of file
372
373=== removed file 'abrek/cache.py'
374--- abrek/cache.py 2011-08-16 19:18:30 +0000
375+++ abrek/cache.py 1970-01-01 00:00:00 +0000
376@@ -1,73 +0,0 @@
377-"""
378-Cache module for Abrek
379-"""
380-import contextlib
381-import hashlib
382-import os
383-import urllib2
384-
385-
386-class AbrekCache(object):
387- """
388- Cache class for Abrek
389- """
390-
391- _instance = None
392-
393- def __init__(self):
394- home = os.environ.get('HOME', '/')
395- basecache = os.environ.get('XDG_CACHE_HOME',
396- os.path.join(home, '.cache'))
397- self.cache_dir = os.path.join(basecache, 'abrek')
398-
399- @classmethod
400- def get_instance(cls):
401- if cls._instance is None:
402- cls._instance = cls()
403- return cls._instance
404-
405- def open_cached(self, key, mode="r"):
406- """
407- Acts like open() but the pathname is relative to the
408- abrek-specific cache directory.
409- """
410- if "w" in mode and not os.path.exists(self.cache_dir):
411- os.makedirs(self.cache_dir)
412- if os.path.isabs(key):
413- raise ValueError("key cannot be an absolute path")
414- try:
415- stream = open(os.path.join(self.cache_dir, key), mode)
416- yield stream
417- finally:
418- stream.close()
419-
420- def _key_for_url(self, url):
421- return hashlib.sha1(url).hexdigest()
422-
423- def _refresh_url_cache(self, key, url):
424- with contextlib.nested(
425- contextlib.closing(urllib2.urlopen(url)),
426- self.open_cached(key, "wb")) as (in_stream, out_stream):
427- out_stream.write(in_stream.read())
428-
429- @contextlib.contextmanager
430- def open_cached_url(self, url):
431- """
432- Like urlopen.open() but the content may be cached.
433- """
434- # Do not cache local files, this is not what users would expect
435-
436- # workaround - not using cache at all.
437- # TODO: fix this and use the cache
438- # if url.startswith("file://"):
439- if True:
440- stream = urllib2.urlopen(url)
441- else:
442- key = self._key_for_url(url)
443- try:
444- stream = self.open_cached(key, "rb")
445- except IOError as exc:
446- self._refresh_url_cache(key, url)
447- stream = self.open_cached(key, "rb")
448- yield stream
449- stream.close()
450
451=== removed file 'abrek/command.py'
452--- abrek/command.py 2011-08-03 14:25:13 +0000
453+++ abrek/command.py 1970-01-01 00:00:00 +0000
454@@ -1,171 +0,0 @@
455-# Copyright (c) 2010 Linaro
456-#
457-# This program is free software: you can redistribute it and/or modify
458-# it under the terms of the GNU General Public License as published by
459-# the Free Software Foundation, either version 3 of the License, or
460-# (at your option) any later version.
461-#
462-# This program is distributed in the hope that it will be useful,
463-# but WITHOUT ANY WARRANTY; without even the implied warranty of
464-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
465-# GNU General Public License for more details.
466-#
467-# You should have received a copy of the GNU General Public License
468-# along with this program. If not, see <http://www.gnu.org/licenses/>.
469-
470-from optparse import OptionParser
471-import os
472-import sys
473-
474-
475-class _AbrekOptionParser(OptionParser):
476- """
477- This is just to override the epilog formatter to allow newlines
478- """
479- def format_epilog(self, formatter):
480- return self.epilog
481-
482-
483-class AbrekCmd(object):
484- """ Base class for commands that can be passed to Abrek.
485-
486- Commands added to abrek should inherit from AbrekCmd. To allow for
487- autodiscovery, the name of the class should begin with cmd_.
488-
489- Arguments allowed by the command can be specified in the 'arglist'.
490- These arguments will automatically be listed in the help for that
491- command. Required arguments should begin with a '*'. For example:
492- arglist = ['*requiredarg', 'optionalarg']
493-
494- Options may also be specified by using the 'options' list. To add
495- arguments, you must use the make_option() function from optparse.
496- For example:
497- options = [make_option("-b", "--bar", dest="bar")]
498-
499- Commands also support subcommands. A subcommand is similar to a
500- command in abrek, and it should also inherit from AbrekCmd. However,
501- a subcommand class should not begin with cmd_. Instead, it should
502- be tied to the command that uses it, using the 'subcmds' dict.
503- For example:
504- class subcmd_bar(AbrekCmd):
505- pass
506- class cmd_foo(AbrekCmd):
507- subcmds = {'bar':subcmd_bar()}
508- pass
509- """
510- options = []
511- arglist = []
512-
513- def __init__(self, name_prefix=''):
514- self._name_prefix = name_prefix
515- self.parser = _AbrekOptionParser(usage=self._usage(),
516- epilog=self._desc())
517- for opt in self.options:
518- self.parser.add_option(opt)
519-
520- def main(self, argv):
521- (self.opts, self.args) = self.parser.parse_args(argv)
522- return self.run()
523-
524- def name(self):
525- return self._name_prefix + _convert_command_name(self.__class__.__name__)
526-
527- def run(self):
528- raise NotImplementedError("%s: command defined but not implemented!" %
529- self.name())
530-
531- def _usage(self):
532- usagestr = "Usage: lava-test %s" % self.name()
533- for arg in self.arglist:
534- if arg[0] == '*':
535- usagestr += " %s" % arg[1:].upper()
536- else:
537- usagestr += " [%s]" % arg.upper()
538- return usagestr
539-
540- def _desc(self):
541- from inspect import getdoc
542- docstr = getdoc(self)
543- if not docstr:
544- return ""
545- description = "\nDescription:\n"
546- description += docstr + "\n"
547- return description
548-
549- def help(self):
550- #For some reason, format_help includes an extra \n
551- return self.parser.format_help()[:-1]
552-
553- def get_subcommand(self, name):
554- return None
555-
556- def checkroot(self):
557- if os.getuid() != 0:
558- print >> sys.stderr, ("**** WARNING: ROOT PERMISSIONS ARE OFTEN"
559- "REQUIRED FOR THIS OPERATION ****")
560-
561-
562-class AbrekCmdWithSubcommands(AbrekCmd):
563-
564- arglist = ['subcommand']
565-
566- def main(self, argv):
567- if not argv:
568- print "Missing sub-command." + self._list_subcmds()
569- else:
570- subcmd = self.get_subcommand(argv[0])
571- if subcmd is None:
572- # This line might print the help and raise SystemExit if
573- # --help is passed or if an invalid option was passed.
574- opts, args = self.parser.parse_args(argv)
575- # If it didn't, complain.
576- print "'%s' not found as a sub-command of '%s'" % (
577- args[0], self.name()) + self._list_subcmds()
578- else:
579- return subcmd.main(argv[1:])
580-
581- def get_subcommand(self, name):
582- subcmd_cls = getattr(self, 'cmd_' + name.replace('_', '-'), None)
583- if subcmd_cls is None:
584- return None
585- return subcmd_cls(self.name() + ' ')
586-
587- def _usage(self):
588- usagestr = AbrekCmd._usage(self)
589- usagestr += self._list_subcmds()
590- return usagestr
591-
592- def _list_subcmds(self):
593- subcmds = []
594- for attrname in self.__class__.__dict__.keys():
595- if attrname.startswith('cmd_'):
596- subcmds.append(_convert_command_name(attrname))
597- if not subcmds:
598- return ''
599- return "\n\nAvailable sub-commands:\n " + "\n ".join(subcmds)
600-
601-
602-def _convert_command_name(cmd):
603- return cmd[4:].replace('_','-')
604-
605-
606-def _find_commands(module):
607- cmds = {}
608- for name, func in module.__dict__.iteritems():
609- if name.startswith("cmd_"):
610- real_name = _convert_command_name(name)
611- cmds[real_name] = func()
612- return cmds
613-
614-
615-def get_all_cmds():
616- from abrek import builtins, dashboard, results
617- cmds = _find_commands(builtins)
618- cmds.update(_find_commands(dashboard))
619- cmds.update(_find_commands(results))
620- return cmds
621-
622-
623-def get_command(cmd_name):
624- cmds = get_all_cmds()
625- return cmds.get(cmd_name)
626
627=== removed file 'abrek/config.py'
628--- abrek/config.py 2011-08-03 09:06:20 +0000
629+++ abrek/config.py 1970-01-01 00:00:00 +0000
630@@ -1,93 +0,0 @@
631-# Copyright (c) 2010 Linaro
632-#
633-# This program is free software: you can redistribute it and/or modify
634-# it under the terms of the GNU General Public License as published by
635-# the Free Software Foundation, either version 3 of the License, or
636-# (at your option) any later version.
637-#
638-# This program is distributed in the hope that it will be useful,
639-# but WITHOUT ANY WARRANTY; without even the implied warranty of
640-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
641-# GNU General Public License for more details.
642-#
643-# You should have received a copy of the GNU General Public License
644-# along with this program. If not, see <http://www.gnu.org/licenses/>.
645-
646-import os
647-import json
648-
649-
650-class AbrekConfig(object):
651-
652- def __init__(self):
653- home = os.environ.get('HOME', '/')
654- baseconfig = os.environ.get('XDG_CONFIG_HOME',
655- os.path.join(home, '.config'))
656- basedata = os.environ.get('XDG_DATA_HOME',
657- os.path.join(home, '.local', 'share'))
658- self.configdir = os.path.join(baseconfig, 'abrek')
659- self.installdir = os.path.join(basedata, 'abrek', 'installed-tests')
660- self.resultsdir = os.path.join(basedata, 'abrek', 'results')
661- self.registry = self._load_registry()
662-
663- @property
664- def _registry_pathname(self):
665- return os.path.join(self.configdir, "registry.json")
666-
667- def _load_registry(self):
668- try:
669- with open(self._registry_pathname) as stream:
670- return json.load(stream)
671- except (IOError, ValueError) as exc:
672- return self._get_default_registry()
673-
674- def _save_registry(self):
675- if not os.path.exists(self.configdir):
676- os.makedirs(self.configdir)
677- with open(self._registry_pathname, "wt") as stream:
678- json.dump(self.registry, stream, indent=2)
679-
680- def _get_default_registry(self):
681- return {
682- "format": "Abrek Test Registry 1.0 Experimental",
683- "providers": [
684- {
685- "entry_point": "abrek.providers:BuiltInProvider",
686- },
687- {
688- "entry_point": "abrek.providers:PkgResourcesProvider",
689- },
690- {
691- "entry_point": "abrek.providers:RegistryProvider",
692- "config": {
693- "entries": []
694- }
695- }
696- ]
697- }
698-
699- def get_provider_config(self, entry_point_name):
700- if "providers" not in self.registry:
701- self.registry["providers"] = []
702- for provider_info in self.registry["providers"]:
703- if provider_info.get("entry_point") == entry_point_name:
704- break
705- else:
706- provider_info = {"entry_point": entry_point_name}
707- self.registry["providers"].append(provider_info)
708- if "config" not in provider_info:
709- provider_info["config"] = {}
710- return provider_info["config"]
711-
712-
713-_config = None
714-
715-def get_config():
716- global _config
717- if _config is not None:
718- return _config
719- return AbrekConfig()
720-
721-def set_config(config):
722- global _config
723- _config = config
724
725=== removed file 'abrek/dashboard.py'
726--- abrek/dashboard.py 2011-08-09 09:35:19 +0000
727+++ abrek/dashboard.py 1970-01-01 00:00:00 +0000
728@@ -1,206 +0,0 @@
729-# Copyright (c) 2010 Linaro
730-#
731-# This program is free software: you can redistribute it and/or modify
732-# it under the terms of the GNU General Public License as published by
733-# the Free Software Foundation, either version 3 of the License, or
734-# (at your option) any later version.
735-#
736-# This program is distributed in the hope that it will be useful,
737-# but WITHOUT ANY WARRANTY; without even the implied warranty of
738-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
739-# GNU General Public License for more details.
740-#
741-# You should have received a copy of the GNU General Public License
742-# along with this program. If not, see <http://www.gnu.org/licenses/>.
743-
744-import base64
745-import os
746-import socket
747-import sys
748-import urllib
749-import xmlrpclib
750-from ConfigParser import ConfigParser, NoOptionError
751-from getpass import getpass
752-from optparse import make_option
753-
754-from abrek.bundle import DocumentIO
755-from abrek.command import AbrekCmd, AbrekCmdWithSubcommands
756-from abrek.config import get_config
757-from abrek.testdef import testloader
758-
759-
760-class DashboardConfig(object):
761- """
762- Read and write dashboard configuration data
763- """
764-
765- def __init__(self, section="Default Server"):
766- self.dashboardconf = ConfigParser()
767- self.section = section
768- self.config = get_config()
769- self.path = os.path.join(self.config.configdir, "dashboard.conf")
770- if os.path.exists(self.path):
771- self.dashboardconf.read(self.path)
772- if not (self.section in self.dashboardconf.sections()):
773- self.dashboardconf.add_section(self.section)
774-
775- def set_host(self, host):
776- self.dashboardconf.set(self.section, 'host', host)
777-
778- def get_host(self):
779- try:
780- host = self.dashboardconf.get(self.section, 'host')
781- return host
782- except NoOptionError:
783- return ""
784-
785- host = property(get_host, set_host)
786-
787- def set_user(self, user):
788- self.dashboardconf.set(self.section, 'user', user)
789-
790- def get_user(self):
791- try:
792- user = self.dashboardconf.get(self.section, 'user')
793- return user
794- except NoOptionError:
795- return ""
796-
797- user = property(get_user, set_user)
798-
799- def set_password(self, password):
800- #Not exactly secure, but better than storing in plaintext
801- password = base64.encodestring(password).strip()
802- self.dashboardconf.set(self.section,'password', password)
803-
804- def get_password(self):
805- try:
806- password = self.dashboardconf.get(self.section, 'password')
807- return base64.decodestring(password)
808- except NoOptionError:
809- return ""
810-
811- password = property(get_password, set_password)
812-
813- def write(self):
814- """
815- write the dashboard configuration out to the config file
816- """
817- if not os.path.exists(self.config.configdir):
818- os.makedirs(self.config.configdir)
819- with open(self.path, "w") as fd:
820- self.dashboardconf.write(fd)
821-
822-
823-class cmd_dashboard(AbrekCmdWithSubcommands):
824- """
825- Connect to the Launch-control dashboard
826- """
827-
828- class cmd_setup(AbrekCmd):
829- """
830- Configure information needed to push results to the dashboard
831- """
832- options = [make_option("-u", "--user", dest="user"),
833- make_option("-p", "--password", dest="password")]
834- arglist = ["*server"]
835-
836- def run(self):
837- if len(self.args) != 1:
838- print "You must specify a server"
839- sys.exit(1)
840- config = DashboardConfig()
841- if self.opts.user:
842- user = self.opts.user
843- else:
844- user = raw_input("Username: ")
845- if self.opts.password:
846- password = self.opts.password
847- else:
848- password = getpass()
849- config.host = self.args[0]
850- config.user = user
851- config.password = password
852- config.write()
853-
854-
855- class cmd_put(AbrekCmd):
856- """
857- Push the results from a test to the server
858- The stream name must include slashes (e.g. /anonymous/foo/)
859- """
860- arglist = ["*stream", "*result"]
861-
862- def run(self):
863- if len(self.args) != 2:
864- print "You must specify a stream and a result"
865- sys.exit(1)
866- stream_name = self.args[0]
867- result_name = self.args[1]
868- bundle = generate_bundle(result_name)
869- db_config = DashboardConfig()
870- hosturl = urllib.basejoin(db_config.host, "xml-rpc/")
871- try:
872- server = xmlrpclib.Server(hosturl)
873- except IOError, e:
874- print ("Error connecting to server at '%s'. Error was: %s, "
875- "please run 'lava-test dashboard setup [host]'" % (
876- hosturl, e))
877- sys.exit(1)
878- try:
879- result = server.put(DocumentIO.dumps(bundle), result_name,
880- stream_name)
881- print "Bundle successfully uploaded to id: %s" % result
882- except xmlrpclib.Fault as strerror:
883- print "Error uploading bundle: %s" % strerror.faultString
884- sys.exit(1)
885- except socket.error as strerror:
886- print "Unable to connect to host: %s" % strerror
887- sys.exit(1)
888- except xmlrpclib.ProtocolError as strerror:
889- print "Connection error: %s" % strerror
890- sys.exit(1)
891-
892-
893- class cmd_bundle(AbrekCmd):
894- """
895- Print JSON output that can be imported into the dashboard
896- """
897- arglist = ["*result"]
898-
899- def run(self):
900- if len(self.args) != 1:
901- print "You must specify a result"
902- sys.exit(1)
903- bundle = generate_bundle(self.args[0])
904- try:
905- print DocumentIO.dumps(bundle)
906- except IOError:
907- pass
908-
909-
910-def generate_bundle(result):
911- config = get_config()
912- resultdir = os.path.join(config.resultsdir, result)
913- if not os.path.exists(resultdir):
914- # FIXME: UI and sys.exit mixed with internal implementation, yuck
915- print "Result directory not found"
916- sys.exit(1)
917- with open(os.path.join(resultdir, "testdata.json")) as stream:
918- bundle_text = stream.read()
919- with open(os.path.join(resultdir, "testoutput.log")) as stream:
920- output_text = stream.read()
921- fmt, bundle = DocumentIO.loads(bundle_text)
922- test = testloader(bundle['test_runs'][0]['test_id'])
923- test.parse(result)
924- bundle['test_runs'][0].update(test.parser.results)
925- bundle['test_runs'][0]["attachments"] = [
926- {
927- "pathname": "testoutput.log",
928- "mime_type": "text/plain",
929- "content": base64.standard_b64encode(output_text)
930- }
931- ]
932- return bundle
933-
934-
935
936=== removed file 'abrek/hwprofile.py'
937--- abrek/hwprofile.py 2011-05-27 02:39:03 +0000
938+++ abrek/hwprofile.py 1970-01-01 00:00:00 +0000
939@@ -1,216 +0,0 @@
940-# Copyright (c) 2010 Linaro
941-#
942-# This program is free software: you can redistribute it and/or modify
943-# it under the terms of the GNU General Public License as published by
944-# the Free Software Foundation, either version 3 of the License, or
945-# (at your option) any later version.
946-#
947-# This program is distributed in the hope that it will be useful,
948-# but WITHOUT ANY WARRANTY; without even the implied warranty of
949-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
950-# GNU General Public License for more details.
951-#
952-# You should have received a copy of the GNU General Public License
953-# along with this program. If not, see <http://www.gnu.org/licenses/>.
954-
955-import re
956-import sys
957-from subprocess import Popen, PIPE
958-from utils import read_file, get_machine_type
959-
960-INTEL_KEYMAP = {
961- 'vendor_id': 'cpu_vendor_name',
962- 'cpu family': 'cpu_family',
963- 'model': 'cpu_model',
964- 'model name': 'cpu_model_name',
965- 'stepping': 'cpu_stepping',
966- 'cpu MHz': 'cpu_mhz',
967- 'flags': 'cpu_features',
968-}
969-
970-INTEL_VALMAP = {
971- 'cpu family': int,
972- 'model': int,
973- 'stepping': int,
974- 'cpu MHz': float,
975-}
976-
977-ARM_KEYMAP = {
978- 'Processor': 'cpu_model_name',
979- 'Features': 'cpu_features',
980- 'CPU implementer': 'cpu_implementer',
981- 'CPU architecture': 'cpu_architecture',
982- 'CPU variant': 'cpu_variant',
983- 'CPU part': 'cpu_part',
984- 'CPU revision': 'cpu_revision',
985-}
986-
987-ARM_VALMAP = {
988- 'CPU implementer': lambda value: int(value, 16),
989- 'CPU architecture': int,
990- 'CPU variant': lambda value: int(value, 16),
991- 'CPU part': lambda value: int(value, 16),
992- 'CPU revision': int,
993-}
994-
995-
996-def _translate_cpuinfo(keymap, valmap, key, value):
997- """
998- Translate a key and value using keymap and valmap passed in
999- """
1000- newkey = keymap.get(key, key)
1001- newval = valmap.get(key, lambda x: x)(value)
1002- return newkey, newval
1003-
1004-def get_cpu_devs():
1005- """
1006- Return a list of CPU devices
1007- """
1008- pattern = re.compile('^(?P<key>.+?)\s*:\s*(?P<value>.*)$')
1009- cpunum = 0
1010- devices = []
1011- cpudevs = []
1012- cpudevs.append({})
1013- machine = get_machine_type()
1014- if machine in ('i686', 'x86_64'):
1015- keymap, valmap = INTEL_KEYMAP, INTEL_VALMAP
1016- elif machine.startswith('arm'):
1017- keymap, valmap = ARM_KEYMAP, ARM_VALMAP
1018-
1019- try:
1020- cpuinfo = read_file("/proc/cpuinfo")
1021- for line in cpuinfo.splitlines():
1022- match = pattern.match(line)
1023- if match:
1024- key, value = match.groups()
1025- key, value = _translate_cpuinfo(keymap, valmap,
1026- key, value)
1027- if cpudevs[cpunum].get(key):
1028- cpunum += 1
1029- cpudevs.append({})
1030- cpudevs[cpunum][key] = value
1031- for c in range(len(cpudevs)):
1032- device = {}
1033- device['device_type'] = 'device.cpu'
1034- device['description'] = 'Processor #{0}'.format(c)
1035- device['attributes'] = cpudevs[c]
1036- devices.append(device)
1037- except IOError:
1038- print >> sys.stderr, "WARNING: Could not read cpu information"
1039- return devices
1040-
1041-
1042-def get_board_devs():
1043- """
1044- Return a list of board devices
1045- """
1046- devices = []
1047- attributes = {}
1048- device = {}
1049- machine = get_machine_type()
1050- if machine in ('i686', 'x86_64'):
1051- try:
1052- description = read_file('/sys/class/dmi/id/board_name') or None
1053- vendor = read_file('/sys/class/dmi/id/board_vendor') or None
1054- version = read_file('/sys/class/dmi/id/board_version') or None
1055- if description:
1056- device['description'] = description.strip()
1057- if vendor:
1058- attributes['vendor'] = vendor.strip()
1059- if version:
1060- attributes['version'] = version.strip()
1061- except IOError:
1062- print >> sys.stderr, "WARNING: Could not read board information"
1063- return devices
1064- elif machine.startswith('arm'):
1065- try:
1066- cpuinfo = read_file("/proc/cpuinfo")
1067- if cpuinfo is None:
1068- return devices
1069- pattern = re.compile("^Hardware\s*:\s*(?P<description>.+)$", re.M)
1070- match = pattern.search(cpuinfo)
1071- if match is None:
1072- return devices
1073- device['description'] = match.group('description')
1074- except IOError:
1075- print >> sys.stderr, "WARNING: Could not read board information"
1076- return devices
1077- else:
1078- return devices
1079- if attributes:
1080- device['attributes'] = attributes
1081- device['device_type'] = 'device.board'
1082- devices.append(device)
1083- return devices
1084-
1085-def get_mem_devs():
1086- """ Return a list of memory devices
1087-
1088- This returns up to two items, one for physical RAM and another for swap
1089- """
1090- pattern = re.compile('^(?P<key>.+?)\s*:\s*(?P<value>.+) kB$', re.M)
1091-
1092- devices = []
1093- try:
1094- meminfo = read_file("/proc/meminfo")
1095- for match in pattern.finditer(meminfo):
1096- key, value = match.groups()
1097- if key not in ('MemTotal', 'SwapTotal'):
1098- continue
1099- capacity = int(value) << 10 #Kernel reports in 2^10 units
1100- if capacity == 0:
1101- continue
1102- if key == 'MemTotal':
1103- kind = 'RAM'
1104- else:
1105- kind = 'swap'
1106- description = "{capacity}MiB of {kind}".format(
1107- capacity=capacity >> 20, kind=kind)
1108- device = {}
1109- device['description'] = description
1110- device['attributes'] = {'capacity': str(capacity), 'kind': kind}
1111- device['device_type'] = "device.mem"
1112- devices.append(device)
1113- except IOError:
1114- print >> sys.stderr, "WARNING: Could not read memory information"
1115- return devices
1116-
1117-def get_usb_devs():
1118- """
1119- Return a list of usb devices
1120- """
1121- pattern = re.compile(
1122- "^Bus \d{3} Device \d{3}: ID (?P<vendor_id>[0-9a-f]{4}):"
1123- "(?P<product_id>[0-9a-f]{4}) (?P<description>.*)$")
1124- devices = []
1125- try:
1126- for line in Popen('lsusb', stdout=PIPE).communicate()[0].splitlines():
1127- match = pattern.match(line)
1128- if match:
1129- vendor_id, product_id, description = match.groups()
1130- attributes = {}
1131- device = {}
1132- attributes['vendor_id'] = int(vendor_id, 16)
1133- attributes['product_id'] = int(product_id, 16)
1134- device['attributes'] = attributes
1135- device['description'] = description
1136- device['device_type'] = 'device.usb'
1137- devices.append(device)
1138- except OSError:
1139- print >> sys.stderr, "WARNING: Could not read usb device information, \
1140-unable to run lsusb, please install usbutils package"
1141- return devices
1142-
1143-def get_hardware_context():
1144- """
1145- Return a dict with all of the hardware profile information gathered
1146- """
1147- hardware_context = {}
1148- devices = []
1149- devices.extend(get_cpu_devs())
1150- devices.extend(get_board_devs())
1151- devices.extend(get_mem_devs())
1152- devices.extend(get_usb_devs())
1153- hardware_context['devices'] = devices
1154- return hardware_context
1155-
1156
1157=== removed file 'abrek/providers.py'
1158--- abrek/providers.py 2011-08-17 13:34:33 +0000
1159+++ abrek/providers.py 1970-01-01 00:00:00 +0000
1160@@ -1,145 +0,0 @@
1161-"""
1162-Test providers.
1163-
1164-Allow listing and loading of tests in a generic way.
1165-"""
1166-
1167-from pkg_resources import working_set
1168-
1169-from abrek.api import ITestProvider
1170-from abrek.cache import AbrekCache
1171-from abrek.config import get_config
1172-from abrek.testdef import AbrekDeclarativeTest
1173-
1174-
1175-class BuiltInProvider(ITestProvider):
1176- """
1177- Test provider that provides tests shipped in the Abrek source tree
1178- """
1179-
1180- _builtin_tests = [
1181- 'bootchart',
1182- 'firefox',
1183- 'glmemperf',
1184- 'gmpbench',
1185- 'gtkperf',
1186- 'ltp',
1187- 'peacekeeper'
1188- 'posixtestsuite',
1189- 'pwrmgmt',
1190- 'pybench',
1191- 'smem',
1192- 'stream',
1193- 'tiobench',
1194- 'x11perf',
1195- 'xrestop',
1196- ]
1197-
1198- def __init__(self, config):
1199- pass
1200-
1201- @property
1202- def description(self):
1203- return "Tests built directly into Abrek:"
1204-
1205- def __iter__(self):
1206- return iter(self._builtin_tests)
1207-
1208- def __getitem__(self, test_name):
1209- try:
1210- module = __import__("abrek.test_definitions.%s" % test_name, fromlist=[''])
1211- except ImportError:
1212- raise KeyError(test_name)
1213- else:
1214- return module.testobj
1215-
1216-
1217-class PkgResourcesProvider(ITestProvider):
1218- """
1219- Test provider that provides tests declared in pkg_resources working_set
1220-
1221- By default it looks at the 'abrek.test_definitions' name space but it can
1222- be changed with custom 'namespace' configuration entry.
1223- """
1224-
1225- def __init__(self, config):
1226- self._config = config
1227-
1228- @property
1229- def namespace(self):
1230- return self._config.get("namespace", "abrek.test_definitions")
1231-
1232- @property
1233- def description(self):
1234- return "Tests provided by installed python packages:"
1235-
1236- def __iter__(self):
1237- for entry_point in working_set.iter_entry_points(self.namespace):
1238- yield entry_point.name
1239-
1240- def __getitem__(self, test_name):
1241- for entry_point in working_set.iter_entry_points(self.namespace, test_name):
1242- return entry_point.load().testobj
1243- raise KeyError(test_name)
1244-
1245-
1246-class RegistryProvider(ITestProvider):
1247- """
1248- Test provider that provides declarative tests listed in the test registry.
1249- """
1250- def __init__(self, config):
1251- self._config = config
1252- self._cache = None
1253-
1254- @property
1255- def entries(self):
1256- """
1257- List of URLs to AbrekDeclarativeTest description files
1258- """
1259- return self._config.get("entries", [])
1260-
1261- @property
1262- def description(self):
1263- return "Tests provided by Abrek registry:"
1264-
1265- @classmethod
1266- def register_remote_test(self, test_url):
1267- config = get_config() # This is a different config object from self._config
1268- provider_config = config.get_provider_config("abrek.providers:RegistryProvider")
1269- if "entries" not in provider_config:
1270- provider_config["entries"] = []
1271- if test_url not in provider_config["entries"]:
1272- provider_config["entries"].append(test_url)
1273- config._save_registry()
1274- else:
1275- raise ValueError("This test is already registered")
1276-
1277- def _load_remote_test(self, test_url):
1278- """
1279- Load AbrekDeclarativeTest from a (possibly cached copy of) test_url
1280- """
1281- cache = AbrekCache.get_instance()
1282- with cache.open_cached_url(test_url) as stream:
1283- return AbrekDeclarativeTest.load_from_stream(stream)
1284-
1285- def _fill_cache(self):
1286- """
1287- Fill the cache of all remote tests
1288- """
1289- if self._cache is not None:
1290- return
1291- self._cache = {}
1292- for test_url in self.entries:
1293- test = self._load_remote_test(test_url)
1294- if test.testname in self._cache:
1295- raise ValueError("Duplicate test %s declared" % test.testname)
1296- self._cache[test.testname] = test
1297-
1298- def __iter__(self):
1299- self._fill_cache()
1300- for test_name in self._cache.iterkeys():
1301- yield test_name
1302-
1303- def __getitem__(self, test_name):
1304- self._fill_cache()
1305- return self._cache[test_name]
1306
1307=== removed file 'abrek/results.py'
1308--- abrek/results.py 2010-10-14 13:57:35 +0000
1309+++ abrek/results.py 1970-01-01 00:00:00 +0000
1310@@ -1,111 +0,0 @@
1311-# Copyright (c) 2010 Linaro
1312-#
1313-# This program is free software: you can redistribute it and/or modify
1314-# it under the terms of the GNU General Public License as published by
1315-# the Free Software Foundation, either version 3 of the License, or
1316-# (at your option) any later version.
1317-#
1318-# This program is distributed in the hope that it will be useful,
1319-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1320-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1321-# GNU General Public License for more details.
1322-#
1323-# You should have received a copy of the GNU General Public License
1324-# along with this program. If not, see <http://www.gnu.org/licenses/>.
1325-
1326-import os
1327-import shutil
1328-import sys
1329-from optparse import make_option
1330-
1331-from abrek.command import AbrekCmd, AbrekCmdWithSubcommands
1332-from abrek.config import get_config
1333-from abrek.utils import read_file
1334-
1335-
1336-class cmd_results(AbrekCmdWithSubcommands):
1337- """
1338- Operate on results of previous test runs stored locally
1339- """
1340-
1341- class cmd_list(AbrekCmd):
1342- """
1343- List results of previous runs
1344- """
1345- def run(self):
1346- config = get_config()
1347- print "Saved results:"
1348- try:
1349- for dir in os.listdir(config.resultsdir):
1350- print dir
1351- except OSError:
1352- print "No results found"
1353-
1354-
1355- class cmd_show(AbrekCmd):
1356- """
1357- Display the output from a previous test run
1358- """
1359- arglist = ['*result']
1360- def run(self):
1361- if len(self.args) != 1:
1362- print "please specify the name of the result dir"
1363- sys.exit(1)
1364- config = get_config()
1365- resultsdir = os.path.join(config.resultsdir, self.args[0])
1366- testoutput = os.path.join(resultsdir, "testoutput.log")
1367- if not os.path.exists(testoutput):
1368- print "No result found for '%s'" % self.args[0]
1369- sys.exit(1)
1370- try:
1371- print(read_file(testoutput))
1372- except IOError:
1373- pass
1374-
1375-
1376- class cmd_remove(AbrekCmd):
1377- """
1378- Remove the results of a previous test run
1379- """
1380- arglist = ['*result']
1381- options = [make_option('-f', '--force', action='store_true',
1382- dest='force')]
1383- def run(self):
1384- if len(self.args) != 1:
1385- print "please specify the name of the result dir"
1386- sys.exit(1)
1387- config = get_config()
1388- resultsdir = os.path.join(config.resultsdir, self.args[0])
1389- if not os.path.exists(resultsdir):
1390- print "No result found for '%s'" % self.args[0]
1391- sys.exit(1)
1392- if not self.opts.force:
1393- print "Delete result '%s' for good? [Y/N]" % self.args[0],
1394- response = raw_input()
1395- if response[0].upper() != 'Y':
1396- sys.exit(0)
1397- shutil.rmtree(resultsdir)
1398-
1399-
1400- class cmd_rename(AbrekCmd):
1401- """
1402- Rename the results from a previous test run
1403- """
1404- arglist = ['*source', '*destination']
1405-
1406- def run(self):
1407- if len(self.args) != 2:
1408- print "please specify the name of the result, and the new name"
1409- sys.exit(1)
1410- config = get_config()
1411- srcdir = os.path.join(config.resultsdir, self.args[0])
1412- destdir = os.path.join(config.resultsdir, self.args[1])
1413- if not os.path.exists(srcdir):
1414- print "Result directory not found"
1415- sys.exit(1)
1416- if os.path.exists(destdir):
1417- print "Destination result name already exists"
1418- sys.exit(1)
1419- shutil.move(srcdir, destdir)
1420-
1421-
1422
1423=== removed file 'abrek/swprofile.py'
1424--- abrek/swprofile.py 2011-07-22 04:00:04 +0000
1425+++ abrek/swprofile.py 1970-01-01 00:00:00 +0000
1426@@ -1,65 +0,0 @@
1427-# Copyright (c) 2010 Linaro
1428-#
1429-# This program is free software: you can redistribute it and/or modify
1430-# it under the terms of the GNU General Public License as published by
1431-# the Free Software Foundation, either version 3 of the License, or
1432-# (at your option) any later version.
1433-#
1434-# This program is distributed in the hope that it will be useful,
1435-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1436-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1437-# GNU General Public License for more details.
1438-#
1439-# You should have received a copy of the GNU General Public License
1440-# along with this program. If not, see <http://www.gnu.org/licenses/>.
1441-
1442-import apt
1443-import lsb_release
1444-from utils import read_file
1445-
1446-def get_packages(apt_cache=None):
1447- """ Get information about the packages installed
1448-
1449- apt_cache - if not provided, this will be read from the system
1450- """
1451- if apt_cache == None:
1452- apt_cache = apt.Cache()
1453- packages = []
1454- for apt_pkg in apt_cache:
1455- if hasattr(apt_pkg, 'is_installed'):
1456- is_installed = apt_pkg.is_installed
1457- else:
1458- is_installed = apt_pkg.isInstalled # old style API
1459- if is_installed:
1460- pkg = {"name":apt_pkg.name, "version":apt_pkg.installed.version}
1461- packages.append(pkg)
1462- return packages
1463-
1464-def get_software_context(apt_cache=None, lsb_information=None):
1465- """ Return dict used for storing software_context information
1466-
1467- test_id - Unique identifier for this test
1468- time_check - whether or not a check was performed to see if
1469- the time on the system was synced with a time server
1470- apt_cache - if not provided, this will be read from the system
1471- lsb_information - if not provided, this will be read from the system
1472- """
1473- software_context = {}
1474- software_context['image'] = get_image(lsb_information)
1475- software_context['packages'] = get_packages(apt_cache)
1476- return software_context
1477-
1478-def get_image(lsb_information=None):
1479- """ Get information about the image we are running
1480-
1481- If /etc/buildstamp exists, get the image id from that. Otherwise
1482- just use the lsb-release description for a rough idea.
1483- """
1484- try:
1485- buildstamp = read_file("/etc/buildstamp")
1486- name = buildstamp.splitlines()[1]
1487- except IOError:
1488- if lsb_information == None:
1489- lsb_information = lsb_release.get_distro_information()
1490- name = lsb_information['DESCRIPTION']
1491- return {"name":name}
1492
1493=== removed file 'abrek/testdef.py'
1494--- abrek/testdef.py 2011-08-18 20:09:56 +0000
1495+++ abrek/testdef.py 1970-01-01 00:00:00 +0000
1496@@ -1,435 +0,0 @@
1497-# Copyright (c) 2010 Linaro
1498-#
1499-# This program is free software: you can redistribute it and/or modify
1500-# it under the terms of the GNU General Public License as published by
1501-# the Free Software Foundation, either version 3 of the License, or
1502-# (at your option) any later version.
1503-#
1504-# This program is distributed in the hope that it will be useful,
1505-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1506-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1507-# GNU General Public License for more details.
1508-#
1509-# You should have received a copy of the GNU General Public License
1510-# along with this program. If not, see <http://www.gnu.org/licenses/>.
1511-
1512-import hashlib
1513-import json
1514-import os
1515-import re
1516-import shutil
1517-import decimal
1518-import sys
1519-import time
1520-from commands import getstatusoutput
1521-from datetime import datetime
1522-from uuid import uuid4
1523-
1524-from abrek import swprofile, hwprofile
1525-from abrek.api import ITest
1526-from abrek.bundle import DocumentIO
1527-from abrek.config import get_config
1528-from abrek.utils import geturl, run_and_log, write_file
1529-
1530-
1531-class AbrekTest(ITest):
1532- """Base class for defining tests.
1533-
1534- This can be used by test definition files to create an object that
1535- contains the building blocks for installing tests, running them,
1536- and parsing the results.
1537-
1538- testname - name of the test or test suite
1539- version - version of the test or test suite
1540- installer - AbrekInstaller instance to use
1541- runner - AbrekRunner instance to use
1542- parser - AbrekParser instance to use
1543- """
1544- def __init__(self, testname, version="", installer=None, runner=None,
1545- parser=None):
1546- self.testname = testname
1547- self.version = version
1548- self.installer = installer
1549- self.runner = runner
1550- self.parser = parser
1551- self.origdir = os.path.abspath(os.curdir)
1552-
1553- def install(self):
1554- """Install the test suite.
1555-
1556- This creates an install directory under the user's XDG_DATA_HOME
1557- directory to mark that the test is installed. The installer's
1558- install() method is then called from this directory to complete any
1559- test specific install that may be needed.
1560- """
1561- if not self.installer:
1562- raise RuntimeError("no installer defined for '%s'" %
1563- self.testname)
1564- config = get_config()
1565- installdir = os.path.join(config.installdir, self.testname)
1566- if os.path.exists(installdir):
1567- raise RuntimeError("%s is already installed" % self.testname)
1568- os.makedirs(installdir)
1569- os.chdir(installdir)
1570- try:
1571- self.installer.install()
1572- except Exception as strerror:
1573- self.uninstall()
1574- raise
1575- finally:
1576- os.chdir(self.origdir)
1577-
1578- def uninstall(self):
1579- """Uninstall the test suite.
1580-
1581- Uninstalling just recursively removes the test specific directory
1582- under the user's XDG_DATA_HOME directory. This will both mark
1583- the test as removed, and clean up any files that were downloaded
1584- or installed under that directory. Dependencies are intentionally
1585- not removed by this.
1586- """
1587- os.chdir(self.origdir)
1588- config = get_config()
1589- path = os.path.join(config.installdir, self.testname)
1590- if os.path.exists(path):
1591- shutil.rmtree(path)
1592-
1593- def _savetestdata(self, analyzer_assigned_uuid):
1594- TIMEFORMAT = '%Y-%m-%dT%H:%M:%SZ'
1595- bundle = {
1596- 'format': 'Dashboard Bundle Format 1.2',
1597- 'test_runs': [
1598- {
1599- 'test_id': self.testname,
1600- 'analyzer_assigned_date': self.runner.starttime.strftime(TIMEFORMAT),
1601- 'analyzer_assigned_uuid': analyzer_assigned_uuid,
1602- 'time_check_performed': False,
1603- 'hardware_context': hwprofile.get_hardware_context(),
1604- 'software_context': swprofile.get_software_context(),
1605- 'test_results': []
1606- }
1607- ]
1608- }
1609- filename = os.path.join(self.resultsdir, 'testdata.json')
1610- write_file(DocumentIO.dumps(bundle), filename)
1611-
1612- def run(self, quiet=False):
1613- if not self.runner:
1614- raise RuntimeError("no test runner defined for '%s'" %
1615- self.testname)
1616- config = get_config()
1617- uuid = str(uuid4())
1618- installdir = os.path.join(config.installdir, self.testname)
1619- resultname = (self.testname +
1620- str(time.mktime(datetime.utcnow().timetuple())))
1621- self.resultsdir = os.path.join(config.resultsdir, resultname)
1622- os.makedirs(self.resultsdir)
1623- try:
1624- os.chdir(installdir)
1625- self.runner.run(self.resultsdir, quiet=quiet)
1626- self._savetestdata(uuid)
1627- finally:
1628- os.chdir(self.origdir)
1629- result_id = os.path.basename(self.resultsdir)
1630- print("ABREK TEST RUN COMPLETE: Result id is '%s'" % result_id)
1631- return result_id
1632-
1633- def parse(self, resultname):
1634- if not self.parser:
1635- raise RuntimeError("no test parser defined for '%s'" %
1636- self.testname)
1637- config = get_config()
1638- resultsdir = os.path.join(config.resultsdir, resultname)
1639- os.chdir(resultsdir)
1640- self.parser.parse()
1641- os.chdir(self.origdir)
1642-
1643-
1644-class AbrekDeclarativeTest(AbrekTest):
1645- """
1646- Declarative test is like AbrekTest but cannot contain any python code and
1647- is completely encapsulated in .json file.
1648- """
1649-
1650- @classmethod
1651- def load_from_stream(cls, stream):
1652- return cls(json.load(stream))
1653-
1654- def save_to_stream(self, stream):
1655- json.dumps(self.about, stream, indent="2")
1656-
1657- def __init__(self, about):
1658- self.about = about
1659- super(AbrekDeclarativeTest, self).__init__(self.about.get('test_id'))
1660- self.installer = AbrekTestInstaller(**self.about.get('install', {}))
1661- self.runner = AbrekTestRunner(**self.about.get('run', {}))
1662- if self.about.get('parse', {}).get('native', False) is True:
1663- self.parser = AbrekNativeTestParser(self)
1664- else:
1665- self.parser = AbrekForeignTestParser(**self.about.get('parse', {}))
1666-
1667-
1668-class AbrekTestInstaller(object):
1669- """Base class for defining an installer object.
1670-
1671- This class can be used as-is for simple installers, or extended for more
1672- advanced funcionality.
1673-
1674- steps - list of steps to be executed in a shell
1675- deps - list of dependencies to apt-get install before running the steps
1676- url - location from which the test suite should be downloaded
1677- md5 - md5sum to check the integrety of the download
1678- """
1679- def __init__(self, steps=[], deps=[], url="", md5="", **kwargs):
1680- self.steps = steps
1681- self.deps = deps
1682- self.url = url
1683- self.md5 = md5
1684-
1685- def _installdeps(self):
1686- if not self.deps:
1687- return 0
1688- cmd = "sudo apt-get install -y %s" % " ".join(self.deps)
1689- rc, output = getstatusoutput(cmd)
1690- if rc:
1691- raise RuntimeError("Dependency installation failed. %d : %s" %(rc,output))
1692-
1693- def _download(self):
1694- """Download the file specified by the url and check the md5.
1695-
1696- Returns the path and filename if successful, otherwise return None
1697- """
1698- if not self.url:
1699- return 0
1700- filename = geturl(self.url)
1701- #If the file does not exist, then the download was not successful
1702- if not os.path.exists(filename):
1703- return None
1704- if self.md5:
1705- checkmd5 = hashlib.md5()
1706- with open(filename, 'rb') as fd:
1707- data = fd.read(0x10000)
1708- while data:
1709- checkmd5.update(data)
1710- data = fd.read(0x10000)
1711- if checkmd5.hexdigest() != self.md5:
1712- raise RuntimeError("Unexpected md5sum downloading %s" %
1713- filename)
1714- return None
1715- return filename
1716-
1717- def _runsteps(self):
1718- for cmd in self.steps:
1719- rc, output = getstatusoutput(cmd)
1720- if rc:
1721- raise RuntimeError("Run step '%s' failed. %d : %s" %(cmd,rc,output))
1722-
1723-
1724- def install(self):
1725- self._installdeps()
1726- self._download()
1727- self._runsteps()
1728-
1729-
1730-class AbrekTestRunner(object):
1731- """Base class for defining an test runner object.
1732-
1733- This class can be used as-is for simple execution with the expectation
1734- that the run() method will be called from the directory where the test
1735- was installed. Steps, if used, should handle changing directories from
1736- there to the directory where the test was extracted if necessary.
1737- This class can also be extended for more advanced funcionality.
1738-
1739- steps - list of steps to be executed in a shell
1740- """
1741- def __init__(self, steps=[]):
1742- self.steps = steps
1743- self.testoutput = []
1744-
1745- def _runsteps(self, resultsdir, quiet=False):
1746- outputlog = os.path.join(resultsdir, 'testoutput.log')
1747- with open(outputlog, 'a') as fd:
1748- for cmd in self.steps:
1749- run_and_log(cmd, fd, quiet)
1750-
1751- def run(self, resultsdir, quiet=False):
1752- self.starttime = datetime.utcnow()
1753- self._runsteps(resultsdir, quiet=quiet)
1754- self.endtime = datetime.utcnow()
1755-
1756-
1757-class AbrekForeignTestParser(object):
1758- """Base class for defining a test parser
1759-
1760- This class can be used as-is for simple results parsers, but will
1761- likely need to be extended slightly for many. If used as it is,
1762- the parse() method should be called while already in the results
1763- directory and assumes that a file for test output will exist called
1764- testoutput.log.
1765-
1766- pattern - regexp pattern to identify important elements of test output
1767- For example: If your testoutput had lines that look like:
1768- "test01: PASS", then you could use a pattern like this:
1769- "^(?P<testid>\w+):\W+(?P<result>\w+)"
1770- This would result in identifying "test01" as testid and
1771- "PASS" as result. Once parse() has been called,
1772- self.results.test_results[] contains a list of dicts of all the
1773- key,value pairs found for each test result
1774- fixupdict - dict of strings to convert test results to standard strings
1775- For example: if you want to standardize on having pass/fail results
1776- in lower case, but your test outputs them in upper case, you could
1777- use a fixupdict of something like: {'PASS':'pass','FAIL':'fail'}
1778- appendall - Append a dict to the test_results entry for each result.
1779- For example: if you would like to add units="MB/s" to each result:
1780- appendall={'units':'MB/s'}
1781- """
1782- def __init__(self, pattern=None, fixupdict=None, appendall={}):
1783- self.pattern = pattern
1784- self.fixupdict = fixupdict
1785- self.results = {'test_results':[]}
1786- self.appendall = appendall
1787-
1788- def _find_testid(self, id):
1789- for x in self.results['test_results']:
1790- if x['testid'] == id:
1791- return self.results['test_results'].index(x)
1792-
1793- def parse(self):
1794- """Parse test output to gather results
1795-
1796- Use the pattern specified when the class was instantiated to look
1797- through the results line-by-line and find lines that match it.
1798- Results are then stored in self.results. If a fixupdict was supplied
1799- it is used to convert test result strings to a standard format.
1800- """
1801- filename = "testoutput.log"
1802- try:
1803- pat = re.compile(self.pattern)
1804- except Exception as strerror:
1805- raise RuntimeError(
1806- "AbrekTestParser - Invalid regular expression '%s' - %s" % (
1807- self.pattern,strerror))
1808-
1809- with open(filename, 'r') as stream:
1810- for lineno, line in enumerate(stream, 1):
1811- match = pat.search(line)
1812- if not match:
1813- continue
1814- data = match.groupdict()
1815- if 'measurement' in data:
1816- data['measurement'] = decimal.Decimal(data['measurement'])
1817- data["log_filename"] = filename
1818- data["log_lineno"] = lineno
1819- self.results['test_results'].append(data)
1820- if self.fixupdict:
1821- self.fixresults(self.fixupdict)
1822- if self.appendall:
1823- self.appendtoall(self.appendall)
1824- self.fixmeasurements()
1825- self.fixids()
1826-
1827- def append(self, testid, entry):
1828- """Appends a dict to the test_results entry for a specified testid
1829-
1830- This lets you add a dict to the entry for a specific testid
1831- entry should be a dict, updates it in place
1832- """
1833- index = self._find_testid(testid)
1834- self.results['test_results'][index].update(entry)
1835-
1836- def appendtoall(self, entry):
1837- """Append entry to each item in the test_results.
1838-
1839- entry - dict of key,value pairs to add to each item in the
1840- test_results
1841- """
1842- for t in self.results['test_results']:
1843- t.update(entry)
1844-
1845- def fixresults(self, fixupdict):
1846- """Convert results to a known, standard format
1847-
1848- pass it a dict of keys/values to replace
1849- For instance:
1850- {"TPASS":"pass", "TFAIL":"fail"}
1851- This is really only used for qualitative tests
1852- """
1853- for t in self.results['test_results']:
1854- if t.has_key("result"):
1855- t['result'] = fixupdict[t['result']]
1856-
1857- def fixmeasurements(self):
1858- """Measurements are often read as strings, but need to be float
1859- """
1860- for id in self.results['test_results']:
1861- if id.has_key('measurement'):
1862- id['measurement'] = float(id['measurement'])
1863-
1864- def fixids(self):
1865- """
1866- Convert spaces to _ in test_case_id and remove illegal characters
1867- """
1868- badchars = "[^a-zA-Z0-9\._-]"
1869- for id in self.results['test_results']:
1870- if id.has_key('test_case_id'):
1871- id['test_case_id'] = id['test_case_id'].replace(" ", "_")
1872- id['test_case_id'] = re.sub(badchars, "", id['test_case_id'])
1873-
1874-
1875-AbrekTestParser = AbrekForeignTestParser
1876-
1877-
1878-class AbrekNativeTestParser(object):
1879-
1880- def __init__(self, test_def):
1881- self.test_def = test_def
1882-
1883- def parse(self):
1884- raise NotImplementedError(self.parse)
1885-
1886-
1887-class TestLoader(object):
1888- """
1889- Test loader.
1890-
1891- Encapsulates Abrek's knowledge of available tests.
1892-
1893- Test can be loaded by name with TetsLoader.get_test_by_name. Test can also
1894- be listed by TestLoader.get_providers() and then iterating over tests
1895- returned by each provider.
1896- """
1897-
1898- def __init__(self):
1899- self._config = get_config()
1900-
1901- def get_providers(self):
1902- """
1903- Return a generator of available providers
1904- """
1905- import pkg_resources
1906- for provider_info in self._config.registry.get("providers", []):
1907- entry_point_name = provider_info.get("entry_point")
1908- module_name, attrs = entry_point_name.split(':', 1)
1909- attrs = attrs.split('.')
1910- try:
1911- entry_point = pkg_resources.EntryPoint(entry_point_name, module_name, attrs,
1912- dist=pkg_resources.get_distribution("lava-test"))
1913- provider_cls = entry_point.load()
1914- provider = provider_cls(provider_info.get("config", {}))
1915- yield provider
1916- except pkg_resources.DistributionNotFound as ex:
1917- raise RuntimeError("lava-test is not properly set up. Please read the REAMME file")
1918-
1919- def get_test_by_name(self, test_name):
1920- """
1921- Lookup a test with the specified name
1922- """
1923- for provider in self.get_providers():
1924- try:
1925- return provider[test_name]
1926- except KeyError:
1927- pass
1928- raise ValueError("No such test %r" % test_name)
1929-
1930-
1931-testloader = TestLoader().get_test_by_name
1932
1933=== removed directory 'bin'
1934=== removed file 'bin/lava-test'
1935--- bin/lava-test 2011-06-10 04:49:54 +0000
1936+++ bin/lava-test 1970-01-01 00:00:00 +0000
1937@@ -1,31 +0,0 @@
1938-#!/usr/bin/python
1939-
1940-# Copyright (c) 2010 Linaro
1941-#
1942-# This program is free software: you can redistribute it and/or modify
1943-# it under the terms of the GNU General Public License as published by
1944-# the Free Software Foundation, either version 3 of the License, or
1945-# (at your option) any later version.
1946-#
1947-# This program is distributed in the hope that it will be useful,
1948-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1949-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1950-# GNU General Public License for more details.
1951-#
1952-# You should have received a copy of the GNU General Public License
1953-# along with this program. If not, see <http://www.gnu.org/licenses/>.
1954-
1955-import os
1956-import sys
1957-
1958-ABREK_BINDIR=os.path.abspath(os.path.dirname(os.path.realpath(sys.argv[0])))
1959-ABREK_ROOT=os.path.dirname(ABREK_BINDIR)
1960-ABREK_DIR=os.path.join(ABREK_ROOT,'abrek')
1961-if os.path.exists(ABREK_DIR) and ABREK_ROOT not in sys.path:
1962- sys.path.insert(0, ABREK_ROOT)
1963-
1964-import abrek.main
1965-
1966-if __name__ == '__main__':
1967- exit_code = abrek.main.main(sys.argv)
1968- sys.exit(exit_code)
1969
1970=== added directory 'doc'
1971=== added file 'doc/changes.rst'
1972--- doc/changes.rst 1970-01-01 00:00:00 +0000
1973+++ doc/changes.rst 2011-09-13 22:44:36 +0000
1974@@ -0,0 +1,25 @@
1975+Version History
1976+***************
1977+
1978+.. _version_0_2:
1979+
1980+Version 0.2
1981+===========
1982+
1983+* Rewrite most of the code and deprecate old Abrek interfaces. This allowed us
1984+ to clean up the API, rethink some of the design and integrate the code better
1985+ with other parts of LAVA.
1986+
1987+* Improved documentation and code reference. LAVA Test should now have
1988+ sufficient documentation to help new users and contributors alike.
1989+
1990+* Support for installing and running out-of-tree tests.
1991+
1992+* Ability to define parsers that add new attachments.
1993+
1994+* Unified command line interface with other lava tools thanks to lava-tool.
1995+
1996+Version 0.1
1997+===========
1998+
1999+* Initial release (as Abrek)
2000
2001=== added file 'doc/conf.py'
2002--- doc/conf.py 1970-01-01 00:00:00 +0000
2003+++ doc/conf.py 2011-09-13 22:44:36 +0000
2004@@ -0,0 +1,211 @@
2005+# -*- coding: utf-8 -*-
2006+#
2007+# Linaro JSON documentation build configuration file, created by
2008+# sphinx-quickstart on Mon Dec 27 16:39:47 2010.
2009+#
2010+# This file is execfile()d with the current directory set to its containing dir.
2011+#
2012+# Note that not all possible configuration values are present in this
2013+# autogenerated file.
2014+#
2015+# All configuration values have a default; values that are commented out
2016+# serve to show the default.
2017+
2018+import sys
2019+import os
2020+
2021+# If extensions (or modules to document with autodoc) are in another directory,
2022+# add these directories to sys.path here. If the directory is relative to the
2023+# documentation root, use os.path.abspath to make it absolute, like shown here.
2024+sys.path.append(os.path.abspath('..'))
2025+
2026+# -- General configuration -----------------------------------------------------
2027+
2028+# Add any Sphinx extension module names here, as strings. They can be extensions
2029+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
2030+extensions = [
2031+ 'sphinx.ext.autodoc',
2032+ 'sphinx.ext.doctest',
2033+ 'sphinx.ext.intersphinx',
2034+ 'sphinx.ext.todo',
2035+ 'sphinx.ext.coverage',
2036+ 'sphinx.ext.viewcode']
2037+
2038+# Configuration for sphinx.ext.todo
2039+
2040+todo_include_todos = True
2041+
2042+# Add any paths that contain templates here, relative to this directory.
2043+templates_path = []
2044+
2045+# The suffix of source filenames.
2046+source_suffix = '.rst'
2047+
2048+# The encoding of source files.
2049+#source_encoding = 'utf-8'
2050+
2051+# The master toctree document.
2052+master_doc = 'index'
2053+
2054+# General information about the project.
2055+project = u'LAVA Test'
2056+copyright = u'2010-2011, Linaro Limited'
2057+
2058+# The version info for the project you're documenting, acts as replacement for
2059+# |version| and |release|, also used in various other places throughout the
2060+# built documents.
2061+#
2062+# The short X.Y version.
2063+import versiontools
2064+import lava_test
2065+version = "%d.%d" % lava_test.__version__[0:2]
2066+# The full version, including alpha/beta/rc tags.
2067+release = versiontools.format_version(lava_test.__version__)
2068+
2069+# The language for content autogenerated by Sphinx. Refer to documentation
2070+# for a list of supported languages.
2071+#language = None
2072+
2073+# There are two options for replacing |today|: either, you set today to some
2074+# non-false value, then it is used:
2075+#today = ''
2076+# Else, today_fmt is used as the format for a strftime call.
2077+#today_fmt = '%B %d, %Y'
2078+
2079+# List of documents that shouldn't be included in the build.
2080+#unused_docs = []
2081+
2082+# List of directories, relative to source directory, that shouldn't be searched
2083+# for source files.
2084+exclude_trees = []
2085+
2086+# The reST default role (used for this markup: `text`) to use for all documents.
2087+#default_role = None
2088+
2089+# If true, '()' will be appended to :func: etc. cross-reference text.
2090+#add_function_parentheses = True
2091+
2092+# If true, the current module name will be prepended to all description
2093+# unit titles (such as .. function::).
2094+#add_module_names = True
2095+
2096+# If true, sectionauthor and moduleauthor directives will be shown in the
2097+# output. They are ignored by default.
2098+#show_authors = False
2099+
2100+# The name of the Pygments (syntax highlighting) style to use.
2101+pygments_style = 'sphinx'
2102+
2103+# A list of ignored prefixes for module index sorting.
2104+#modindex_common_prefix = []
2105+
2106+
2107+# -- Options for HTML output ---------------------------------------------------
2108+
2109+# The theme to use for HTML and HTML Help pages. Major themes that come with
2110+# Sphinx are currently 'default' and 'sphinxdoc'.
2111+html_theme = 'default'
2112+
2113+# Theme options are theme-specific and customize the look and feel of a theme
2114+# further. For a list of options available for each theme, see the
2115+# documentation.
2116+#html_theme_options = {}
2117+
2118+# Add any paths that contain custom themes here, relative to this directory.
2119+#html_theme_path = []
2120+
2121+# The name for this set of Sphinx documents. If None, it defaults to
2122+# "<project> v<release> documentation".
2123+#html_title = None
2124+
2125+# A shorter title for the navigation bar. Default is the same as html_title.
2126+#html_short_title = None
2127+
2128+# The name of an image file (relative to this directory) to place at the top
2129+# of the sidebar.
2130+#html_logo = None
2131+
2132+# The name of an image file (within the static path) to use as favicon of the
2133+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
2134+# pixels large.
2135+#html_favicon = None
2136+
2137+# Add any paths that contain custom static files (such as style sheets) here,
2138+# relative to this directory. They are copied after the builtin static files,
2139+# so a file named "default.css" will overwrite the builtin "default.css".
2140+html_static_path = []
2141+
2142+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
2143+# using the given strftime format.
2144+#html_last_updated_fmt = '%b %d, %Y'
2145+
2146+# If true, SmartyPants will be used to convert quotes and dashes to
2147+# typographically correct entities.
2148+#html_use_smartypants = True
2149+
2150+# Custom sidebar templates, maps document names to template names.
2151+#html_sidebars = {}
2152+
2153+# Additional templates that should be rendered to pages, maps page names to
2154+# template names.
2155+#html_additional_pages = {}
2156+
2157+# If false, no module index is generated.
2158+#html_use_modindex = True
2159+
2160+# If false, no index is generated.
2161+#html_use_index = True
2162+
2163+# If true, the index is split into individual pages for each letter.
2164+#html_split_index = False
2165+
2166+# If true, links to the reST sources are added to the pages.
2167+#html_show_sourcelink = True
2168+
2169+# If true, an OpenSearch description file will be output, and all pages will
2170+# contain a <link> tag referring to it. The value of this option must be the
2171+# base URL from which the finished HTML is served.
2172+#html_use_opensearch = ''
2173+
2174+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
2175+#html_file_suffix = ''
2176+
2177+# Output file base name for HTML help builder.
2178+htmlhelp_basename = 'LAVATestDocumentation'
2179+
2180+
2181+# -- Options for LaTeX output --------------------------------------------------
2182+
2183+# The paper size ('letter' or 'a4').
2184+#latex_paper_size = 'letter'
2185+
2186+# The font size ('10pt', '11pt' or '12pt').
2187+#latex_font_size = '10pt'
2188+
2189+# Grouping the document tree into LaTeX files. List of tuples
2190+# (source start file, target name, title, author, documentclass [howto/manual]).
2191+latex_documents = [
2192+ ('index', 'LAVA Test.tex', u'LAVA Test Documentation',
2193+ u'Zygmunt Krynicki', 'manual'),
2194+]
2195+
2196+# The name of an image file (relative to this directory) to place at the top of
2197+# the title page.
2198+#latex_logo = None
2199+
2200+# For "manual" documents, if this is true, then toplevel headings are parts,
2201+# not chapters.
2202+#latex_use_parts = False
2203+
2204+# Additional stuff for the LaTeX preamble.
2205+#latex_preamble = ''
2206+
2207+# Documents to append as an appendix to all manuals.
2208+#latex_appendices = []
2209+
2210+# If false, no module index is generated.
2211+#latex_use_modindex = True
2212+
2213+
2214+# Example configuration for intersphinx: refer to the Python standard library.
2215+intersphinx_mapping = {'http://docs.python.org/': None}
2216
2217=== added file 'doc/index.rst'
2218--- doc/index.rst 1970-01-01 00:00:00 +0000
2219+++ doc/index.rst 2011-09-13 22:44:36 +0000
2220@@ -0,0 +1,81 @@
2221+=======================
2222+LAVA Test Documentation
2223+=======================
2224+
2225+LAVA Test is a wrapper framework exposing unified API and command line
2226+interface for running arbitrary tests and storing the results in a structured
2227+manner.
2228+
2229+LAVA Test is a part of the LAVA stack and can be used with other LAVA
2230+components, most notably the dispatcher (for setting up the test environment
2231+and controlling execution of multiple tests) and the dashboard (for storing
2232+
2233+.. seealso:: To learn more about LAVA see https://launchpad.net/lava
2234+
2235+60 second example
2236+=================
2237+
2238+This example will run on Ubuntu Lucid and beyond::
2239+
2240+ $ sudo add-apt-repository ppa:linaro-validation/ppa
2241+ $ sudo apt-get update
2242+ $ sudo apt-get install lava-test
2243+ $ lava-test install stream
2244+ $ lava-test run stream
2245+
2246+.. seealso:: For a more thorough description see :ref:`usage`
2247+.. seealso:: For detailed installation istructions see :ref:`installation`
2248+
2249+Features
2250+========
2251+
2252+* Ability to enumerate, install, run and remove tests on a Linux-based system.
2253+* Support for benchmarks as well as pass/fail tests.
2254+* Support for capturing environment information such as installed software and
2255+ hardware information and recording that in a machine-readable manner.
2256+* Store results in raw form (log files) as well as Linaro Dashboard Bundle
2257+ format that can be uploaded to the LAVA Dashboard for archiving and analysis.
2258+* Extensible API for adding new tests (:class:`~lava_test.api.core.ITest`) or even
2259+ collections of tests (:class:`~lava_test.api.core.ITestProvider`).
2260+* Ever-growing collection of freely available and generic tests and benchmarks
2261+
2262+.. seealso:: See what's new in :ref:`version_0_2`
2263+
2264+
2265+Latest documentation
2266+====================
2267+
2268+This documentation my be out of date, we try to make sure that all the latest
2269+and greatest releases are always documented on http://lava-test.readthedocs.org/
2270+
2271+
2272+Source code, bugs and patches
2273+=============================
2274+
2275+The project is maintained on Launchpad at http://launchpad.net/lava-test/.
2276+
2277+You can get the source code with bazaar using ``bzr branch lp:lava-test``.
2278+Patches can be submitted using Launchpad merge proposals (for introduction to
2279+this and topic see https://help.launchpad.net/Code/Review).
2280+
2281+Please report all bugs at https://bugs.launchpad.net/lava-test/+filebug.
2282+
2283+Most of the team is usually available in ``#linaro`` on ``irc.freenode.net``.
2284+Feel free to drop by to chat and ask questions.
2285+
2286+
2287+Indices and tables
2288+==================
2289+
2290+.. toctree::
2291+ :maxdepth: 2
2292+
2293+ installation.rst
2294+ changes.rst
2295+ usage.rst
2296+ reference.rst
2297+ todo.rst
2298+
2299+* :ref:`genindex`
2300+* :ref:`modindex`
2301+* :ref:`search`
2302
2303=== added file 'doc/installation.rst'
2304--- doc/installation.rst 1970-01-01 00:00:00 +0000
2305+++ doc/installation.rst 2011-09-13 22:44:36 +0000
2306@@ -0,0 +1,66 @@
2307+
2308+.. _installation:
2309+
2310+Installation
2311+============
2312+
2313+Prerequisites
2314+^^^^^^^^^^^^^
2315+
2316+The following debian packages are needed to use LAVA Test:
2317+
2318+* python-setuptools
2319+* python-apt
2320+* usbutils
2321+* python-testrepository - for running unit tests
2322+* python-sphinx - for building documentation
2323+
2324+
2325+Installation Options
2326+^^^^^^^^^^^^^^^^^^^^
2327+
2328+There are several installation options available:
2329+
2330+
2331+Using Ubuntu PPAs
2332+-----------------
2333+
2334+For Ubuntu 10.04 onward there is a stable PPA (personal package archive):
2335+
2336+* ppa:linaro-validation/ppa
2337+
2338+To add a ppa to an Ubuntu system use the add-apt-repository command::
2339+
2340+ sudo add-apt-repository ppa:linaro-validation/ppa
2341+
2342+After you add the PPA you need to update your package cache::
2343+
2344+ sudo apt-get update
2345+
2346+Finally you can install the package, it is called `lava-test`::
2347+
2348+ sudo apt-get install lava-test
2349+
2350+
2351+Using Python Package Index
2352+--------------------------
2353+
2354+This package is being actively maintained and published in the `Python Package
2355+Index <http://http://pypi.python.org>`_. You can install it if you have `pip
2356+<http://pip.openplans.org/>`_ tool using just one line::
2357+
2358+ pip install lava-test
2359+
2360+
2361+Using source tarball
2362+--------------------
2363+
2364+To install from source you must first obtain a source tarball from either pypi
2365+or from `Launchpad <http://launchpad.net/>`_. To install the package unpack the
2366+tarball and run::
2367+
2368+ python setup.py install
2369+
2370+You can pass ``--user`` if you prefer to do a local (non system-wide)
2371+installation. Note that executable programs are placed in ``~/.local/bin/`` and
2372+this directory is not on ``PATH`` by default.
2373
2374=== added file 'doc/reference.rst'
2375--- doc/reference.rst 1970-01-01 00:00:00 +0000
2376+++ doc/reference.rst 2011-09-13 22:44:36 +0000
2377@@ -0,0 +1,107 @@
2378+.. _reference:
2379+
2380+=========
2381+Reference
2382+=========
2383+
2384+.. _command_reference:
2385+
2386+Command Reference
2387+=================
2388+
2389+.. automodule:: lava_test.commands
2390+ :members:
2391+
2392+.. todo::
2393+
2394+ * Describe basic commands
2395+ * Describe arguments and options to each command in detail
2396+
2397+Pathnames and files
2398+===================
2399+
2400+LAVA Test uses the following files:
2401+
2402+* ``$XDG_CONFIG_HOME/lava_test/`` -- configuration files
2403+* ``$XDG_DATA_HOME/lava_test/installed-tests`` -- installed test programs
2404+* ``$XDG_DATA_HOME/lava_test/results`` -- artifacts of running tests
2405+* ``$XDG_CACHE_HOME/lava_test/`` -- download cache
2406+
2407+.. _code_reference:
2408+
2409+Code reference
2410+==============
2411+
2412+.. todo::
2413+
2414+ * Describe general code layout
2415+ * Describe key API integration points (on a separate page if needed for clarity)
2416+ * Provide an example test and walk the reader through the meaning of each part
2417+
2418+Abstract Interfaces
2419+^^^^^^^^^^^^^^^^^^^
2420+
2421+.. automodule:: lava_test.api.core
2422+ :members:
2423+
2424+.. automodule:: lava_test.api.delegates
2425+ :members:
2426+
2427+.. automodule:: lava_test.api.observers
2428+ :members:
2429+
2430+Test definitions and test providers
2431+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2432+
2433+.. automodule:: lava_test.core.providers
2434+ :members:
2435+
2436+.. automodule:: lava_test.core.tests
2437+ :members:
2438+
2439+Test components (installers, runners and parsers)
2440+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2441+
2442+.. automodule:: lava_test.core.installers
2443+ :members:
2444+
2445+.. automodule:: lava_test.core.runners
2446+ :members:
2447+
2448+.. automodule:: lava_test.core.parsers
2449+ :members:
2450+
2451+Core Modules
2452+^^^^^^^^^^^^
2453+
2454+.. automodule:: lava_test.core.artifacts
2455+ :members:
2456+
2457+.. automodule:: lava_test.core.config
2458+ :members:
2459+
2460+Environment Scanners
2461+^^^^^^^^^^^^^^^^^^^^
2462+
2463+.. automodule:: lava_test.core.hwprofile
2464+ :members:
2465+
2466+.. automodule:: lava_test.core.swprofile
2467+ :members:
2468+
2469+Utilities
2470+^^^^^^^^^
2471+
2472+.. automodule:: lava_test.utils
2473+ :members:
2474+
2475+.. automodule:: lava_test.extcmd
2476+ :members:
2477+
2478+
2479+Abrek compatibility
2480+===================
2481+
2482+.. automodule:: abrek.testdef
2483+ :members:
2484+
2485
2486=== added file 'doc/todo.rst'
2487--- doc/todo.rst 1970-01-01 00:00:00 +0000
2488+++ doc/todo.rst 2011-09-13 22:44:36 +0000
2489@@ -0,0 +1,4 @@
2490+List of items that need work
2491+============================
2492+
2493+.. todolist::
2494
2495=== added file 'doc/usage.rst'
2496--- doc/usage.rst 1970-01-01 00:00:00 +0000
2497+++ doc/usage.rst 2011-09-13 22:44:36 +0000
2498@@ -0,0 +1,195 @@
2499+.. _usage:
2500+
2501+=====
2502+Usage
2503+=====
2504+
2505+Workflow Overview
2506+=================
2507+
2508+LAVA Test can be used in several different ways. Most notably those are
2509+standalone (without the LAVA dispatcher) and managed (when LAVA Test is
2510+installed and controlled by the LAVA dispatcher).
2511+
2512+Standalone usage
2513+^^^^^^^^^^^^^^^^
2514+
2515+In standalone mode a human operator installs LAVA Test on some device
2516+(development board, laptop or other computer or a virtual machine), installs
2517+the tests that are to be executed and then executes them manually (by manually
2518+running LAVA test, the actual tests are non-interactive).
2519+
2520+Using LAVA to develop and run new tests
2521++++++++++++++++++++++++++++++++++++++++
2522+
2523+This mode is useful for test development (adding new tests, developing custom
2524+tests especially tailored for LAVA, etc.). Here the typical cycle depends on
2525+how the tests is wrapped for usage by LAVA and what the test developer is
2526+focusing on.
2527+
2528+While developing the actual test the typical set of commands might look like
2529+this::
2530+
2531+ $ lava-test install my-custom-test
2532+ $ lava-test run my-custom-test
2533+ $ lava-test uninstall my-custom-test
2534+
2535+Here the developer could observe changes to the test program (that is
2536+presumably compiled and copied somewhere by the install stage).
2537+
2538+Using LAVA to analyze test results
2539+++++++++++++++++++++++++++++++++++
2540+
2541+Developing the test is only half of the story. The other half is developing
2542+LAVA Test integration code, most importantly the artefact parser / analyzer.
2543+This part has to be implemented in python (unlike the test program that can be
2544+implemented in any language and technology). Here the developer is focusing on
2545+refining the parser to see if the outcome is as indented. Assuming that earlier
2546+the developer ran the test at least once and wrote down the result identifier
2547+the set of commands one might use is::
2548+
2549+ $ lava-test parse my-custom-test my-custom-test.2011-08-19T23:53:21Z | pager
2550+
2551+Here the developer has to pass both the identifier of the test
2552+(``my-custom-test``) as well as the identifier of the actual result. While
2553+currently the result identifier starts with the test identifier we wanted to
2554+avoid magic values like that so both are needed. The test defines which
2555+artefact parser to use. The result id is used to locate leftovers from running
2556+that specific test at some previous point in time.
2557+
2558+By default parse will print the bundle to standard output for inspection. It
2559+should be redirected to a pager for easier verification.
2560+
2561+.. note::
2562+
2563+ While the syntax of the bundle created with `lava-test parse` is always
2564+ correct (or, if the parser does something really, really strange, a
2565+ detailed error is reported) the actual contents may not be what you
2566+ intended it to be. Parsers are ultimately fragile as they mostly deal with
2567+ unstructured or semi-structured free-form text that most test programs seem
2568+ to produce. The ultimate goal of a developer should be to produce
2569+ unambiguous, machine readable format. This level of integration would allow
2570+ to wrap a whole class of tests in one go (such as all xUnit-XML speaking
2571+ test frameworks).
2572+
2573+Usage with the dispatcher
2574+^^^^^^^^^^^^^^^^^^^^^^^^^
2575+
2576+The dispatcher is useful for automating LAVA Test environment setup, describing
2577+test scenarios (the list of tests to invoke) and finally storing the results in
2578+the LAVA dashboard.
2579+
2580+Typically this mode is based on the following sequence of commands:
2581+
2582+#. Install lava-test (from PPA or source) along with the required dependencies
2583+#. (optional) for out of tree tests install the additional `test definition` package
2584+#. Install the test or tests that are to be invoked with ``lava-tool install``.
2585+#. Run, parse and store in one go with ``lava-tool run --output=FILE``.
2586+
2587+Here the whole setup is non-interactive and at the end the dispatcher can copy
2588+the output bundle for additional processing.
2589+
2590+Automation considerations
2591+^^^^^^^^^^^^^^^^^^^^^^^^^
2592+
2593+.. _wrapping_existing_test_or_benchmark:
2594+
2595+Wrapping existing test or benchmark
2596+===================================
2597+
2598+LAVA Test can be extended in several different ways. There is no best method,
2599+each has some pros and cons. In general we welcome any freely redistributable,
2600+generic tests. Those enrich the LAVA ecosystem and by providing useful
2601+out-of-the-box features to our users.
2602+
2603+Technically all tests are hidden behind a set of abstract interfaces that tell
2604+LAVA Test what to do in response to operator or dispatcher actions. The primary
2605+interface is :class:`~lava_test.api.core.ITest` and the three principal
2606+methods: :meth:`~lava_test.api.core.ITest.install`,
2607+:meth:`~lava_test.api.core.ITest.run`,
2608+:meth:`~lava_test.api.core.ITest.parse`.
2609+
2610+In practice it is usually much easier to instantiate our pluggable delegate
2611+test (:class:`lava_test.core.tests.Test`) and define the three delegates that
2612+know how to install, run and parse. Again for each step we have a base class
2613+that can be easily customized or even used directly as is. Those classes are
2614+:class:`~lava_test.core.installers.TestInstaller`,
2615+:class:`~lava_test.core.runners.TestRunner` and
2616+:class:`~lava_test.core.parsers.TestParser`. They all implement well-defined
2617+interfaces (specified in :mod:`lava_test.api.delegates`) so if you wish to
2618+customize them you should become familiar with the API requirements first.
2619+
2620+Contributing new tests to LAVA
2621+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2622+
2623+The most direct way to add a new test is to contribute patches to LAVA Test
2624+itself. This method will simply add a new test definition to the collection of
2625+available tests.
2626+
2627+This method is recommended for generic tests that rarely change and are
2628+suitable for wide variety of hardware and software (assuming basic Linux-like
2629+system, Android tests are a special case).
2630+
2631+The advantage is that those tests can be invoked out of the box and will be
2632+maintained by the LAVA team. The disadvantage is that all changes to those
2633+tests need to follow Linaro development work flow, get reviewed and finally
2634+merged. Depending on your situation this may be undesired.
2635+
2636+.. todo::
2637+
2638+ Describe how tests are discovered, loaded and used. It would be
2639+ nice to have a tutorial that walks the user through wrapping a
2640+ simple pass/fail test.
2641+
2642+Maintaining out-of-tree tests
2643+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2644+
2645+For some kinds of tests (proprietary, non-generic, in rapid development, fused
2646+with application code) contributing their definition to upstream LAVA Test
2647+project would be impractical.
2648+
2649+In such cases the test maintainer can still leverage LAVA to actually run and
2650+process the test without being entangled in the review process or going through
2651+any public channel.
2652+
2653+Because LAVA Test supports pluggable test providers it is easy to add a new
2654+source of test definitions. Fortunately we ship with a very useful generic
2655+out-of-tree test provider based on the python `pkg_resources` system.
2656+
2657+Any python package (that is a module or package and the corresponding setup.py
2658+and .egg_info) can define LAVA Test extensions using the `pkg_resurces` entry
2659+points system.
2660+
2661+To do this write your test program as you normally would, write the LAVA Test
2662+integration code and put this into your integration package setup.py::
2663+
2664+ setup(
2665+ ...,
2666+ entry_points="""[lava_test.test_definitions]
2667+ my_test_id=my_package.my_module
2668+ """)
2669+
2670+Here we'd define an entry point in the ``lava_test.test_definitions`` namespace
2671+that LAVA Test searches by default. In that namespace we define one object
2672+``my_test_id`` which points at the module ``my_package.my_module``. LAVA Test
2673+will discover this entry point, import the relevant module and make the test
2674+definition available.
2675+
2676+Maintaining simple declarative tests
2677+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2678+
2679+By registering pure declarative tests at runtime.
2680+
2681+.. todo::
2682+
2683+ Describe how to use declarative tests. It would be a nice
2684+ extension of the tutorial once the user feels comfortable with
2685+ the initial python-based version.
2686+
2687+Writing new tests from scratch
2688+==============================
2689+
2690+.. todo::
2691+
2692+ Describe considerations for test writers. Using native test
2693+ format with human-readable output adapters.
2694
2695=== modified file 'examples/power-management-tests.json'
2696--- examples/power-management-tests.json 2011-06-28 12:51:57 +0000
2697+++ examples/power-management-tests.json 2011-09-13 22:44:36 +0000
2698@@ -1,5 +1,5 @@
2699 {
2700- "format": "Abrek Test Definition 1.0 Experimental",
2701+ "format": "Lava-Test Test Definition 1.0",
2702 "test_id": "linaro.pmwg",
2703 "install": {
2704 "steps": ["bzr get lp:~zkrynicki/+junk/linaro-pm-qa-tests"],
2705
2706=== modified file 'examples/stream.json'
2707--- examples/stream.json 2011-06-28 13:31:48 +0000
2708+++ examples/stream.json 2011-09-13 22:44:36 +0000
2709@@ -1,5 +1,5 @@
2710 {
2711- "format": "Abrek Test Definition Format 1.0 Experimental",
2712+ "format": "LAVA-Test Test Definition Format",
2713 "test_id": "stream-json",
2714 "install": {
2715 "url": "http://www.cs.virginia.edu/stream/FTP/Code/stream.c",
2716
2717=== renamed directory 'abrek' => 'lava_test'
2718=== modified file 'lava_test/__init__.py'
2719--- abrek/__init__.py 2011-08-20 02:20:37 +0000
2720+++ lava_test/__init__.py 2011-09-13 22:44:36 +0000
2721@@ -13,4 +13,4 @@
2722 # You should have received a copy of the GNU General Public License
2723 # along with this program. If not, see <http://www.gnu.org/licenses/>.
2724
2725-__version__ = "0.2.0"
2726+__version__ = (0, 2, 0, "dev", 0)
2727
2728=== added directory 'lava_test/api'
2729=== added file 'lava_test/api/__init__.py'
2730--- lava_test/api/__init__.py 1970-01-01 00:00:00 +0000
2731+++ lava_test/api/__init__.py 2011-09-13 22:44:36 +0000
2732@@ -0,0 +1,24 @@
2733+# Copyright (c) 2010, 2011 Linaro
2734+#
2735+# This program is free software: you can redistribute it and/or modify
2736+# it under the terms of the GNU General Public License as published by
2737+# the Free Software Foundation, either version 3 of the License, or
2738+# (at your option) any later version.
2739+#
2740+# This program is distributed in the hope that it will be useful,
2741+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2742+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2743+# GNU General Public License for more details.
2744+#
2745+# You should have received a copy of the GNU General Public License
2746+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2747+
2748+from abc import ABCMeta
2749+
2750+
2751+class _Interface(object):
2752+ """
2753+ Interface class for simplifying usage of interface meta-classes
2754+ """
2755+
2756+ __metaclass__ = ABCMeta
2757
2758=== added file 'lava_test/api/core.py'
2759--- lava_test/api/core.py 1970-01-01 00:00:00 +0000
2760+++ lava_test/api/core.py 2011-09-13 22:44:36 +0000
2761@@ -0,0 +1,164 @@
2762+# Copyright (c) 2010, 2011 Linaro
2763+#
2764+# This program is free software: you can redistribute it and/or modify
2765+# it under the terms of the GNU General Public License as published by
2766+# the Free Software Foundation, either version 3 of the License, or
2767+# (at your option) any later version.
2768+#
2769+# This program is distributed in the hope that it will be useful,
2770+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2771+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2772+# GNU General Public License for more details.
2773+#
2774+# You should have received a copy of the GNU General Public License
2775+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2776+
2777+"""
2778+:mod:`lava_test.api.core` -- Interface classes for core LAVA Test features
2779+==========================================================================
2780+
2781+.. module: lava_test.api.core
2782+
2783+ :synopsis: Interface classes for core LAVA Test features
2784+"""
2785+
2786+from abc import abstractmethod, abstractproperty
2787+
2788+from lava_test.api import _Interface
2789+
2790+class ITest(_Interface):
2791+ """
2792+ Abstract test definition.
2793+
2794+ Test definitions allow lava-test to install, remove, run and parse log
2795+ files of automatic tests. While the interface can be implemented directly
2796+ you should use :class:`lava_test.core.tests.Test` that implements the core
2797+ logic and allow you to customize the parts that are needed by providing
2798+ delegates implementing :class:`~lava_test.api.delegates.ITestInstaller`,
2799+ :class:`~lava_test.api.delegates.ITestRunner` and
2800+ :class:`~lava_test.api.delegates.ITestParser`.
2801+
2802+ .. seealso:: :ref:`wrapping_existing_test_or_benchmark`
2803+ """
2804+
2805+ @abstractproperty
2806+ def is_installed(self):
2807+ """
2808+ True if this test is installed
2809+
2810+ .. versionadded:: 0.2
2811+ """
2812+
2813+ @abstractmethod
2814+ def install(self, observer):
2815+ """
2816+ Install the test program.
2817+
2818+ This creates an install directory under the user's XDG_DATA_HOME
2819+ directory to mark that the test is installed. The installer's
2820+ install() method is then called from this directory to complete any
2821+ test specific install that may be needed.
2822+
2823+ :param observer:
2824+ Observer object that makes it possible to monitor the actions
2825+ performed by the test installer.
2826+ :type observer: :class:`~lava_test.api.observers.ITestInstallerObserver`
2827+
2828+ .. versionadded:: 0.2
2829+ """
2830+
2831+ @abstractmethod
2832+ def uninstall(self):
2833+ """
2834+ Remove the test program
2835+
2836+ Recursively remove test specific directory under the user's
2837+ ``XDG_DATA_HOME directory``. This will both mark the test as removed,
2838+ and clean up any files that were downloaded or installed under that
2839+ directory. Dependencies are intentionally not removed by this.
2840+
2841+ .. versionadded:: 0.1
2842+ """
2843+
2844+ @abstractmethod
2845+ def run(self, observer):
2846+ """
2847+ Run the test program and store artifacts.
2848+
2849+ :param observer:
2850+ Observer object that makes it possible to monitor the actions
2851+ performed by the test runner.
2852+ :type observer: :class:`~lava_test.api.observers.ITestRunnerObserver`
2853+ :return: Test run artifacts
2854+ :rtype: :class:`~lava_test.core.artifacts.TestArtifacts`.
2855+
2856+ .. versionadded:: 0.2
2857+ """
2858+
2859+ @abstractmethod
2860+ def parse(self, artifacts):
2861+ """
2862+ Parse the artifacts of an earlier run.
2863+
2864+ :param artifacts: Object that describes which files should be parsed.
2865+ :type artifacts: :class:`~lava_test.core.artifacts.TestArtifacts`
2866+ :return:
2867+ A dictionary with all the parsed data. In particular this is a
2868+ TestRun part of the dashboard bundle so it should have the
2869+ test_results list of all the results parsed from the artifacts.
2870+ :rtype: :class:`dict`
2871+
2872+ .. versionadded:: 0.2
2873+ """
2874+
2875+
2876+class ITestProvider(_Interface):
2877+ """
2878+ Source of ITest instances.
2879+
2880+ Test providers can be used to make lava-test aware of arbitrary collections
2881+ of tests that can be installed and invoked. Internally lava-test uses this
2882+ class to offer built-in tests (via the
2883+ :class:`~lava_test.providers.BuiltInProvider`), out-of-tree tests (via the
2884+ :class:`~lava_test.providers.PkgResourcesProvider`) and declarative tests
2885+ (via the :class:`~lava_test.providers.RegistryProvider`).
2886+
2887+ Normally this is not something you would need to implement. If you have a
2888+ large collection of existing tests that can be somehow adapted in bulk, or
2889+ you have your own internal registry of tests that could be adapted this way
2890+ then you might use this interface to simplify test discovery.
2891+
2892+ Test providers need to be registered using pkg-resources entry-point
2893+ feature and then added to the lava-test configuration file. See
2894+ :class:`lava_test.config.LavaTestConfig` for details.
2895+
2896+ .. versionadded:: 0.2
2897+ """
2898+
2899+ @abstractmethod
2900+ def __init__(self, config):
2901+ """
2902+ Initialize test provider with the specified configuration object. The
2903+ configuration object is obtained from the test tool providers registry.
2904+ """
2905+
2906+ @abstractmethod
2907+ def __iter__(self):
2908+ """
2909+ Iterates over instances of ITest exposed by this provider
2910+ """
2911+
2912+ @abstractmethod
2913+ def __getitem__(self, test_id):
2914+ """
2915+ Return an instance of ITest with the specified id
2916+ """
2917+
2918+ @abstractproperty
2919+ def description(self):
2920+ """
2921+ The description string used by `lava-test list-tests`
2922+ """
2923+
2924+
2925+__all__ = ['ITest', 'ITestProvider']
2926
2927=== added file 'lava_test/api/delegates.py'
2928--- lava_test/api/delegates.py 1970-01-01 00:00:00 +0000
2929+++ lava_test/api/delegates.py 2011-09-13 22:44:36 +0000
2930@@ -0,0 +1,119 @@
2931+# Copyright (c) 2010, 2011 Linaro
2932+#
2933+# This program is free software: you can redistribute it and/or modify
2934+# it under the terms of the GNU General Public License as published by
2935+# the Free Software Foundation, either version 3 of the License, or
2936+# (at your option) any later version.
2937+#
2938+# This program is distributed in the hope that it will be useful,
2939+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2940+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2941+# GNU General Public License for more details.
2942+#
2943+# You should have received a copy of the GNU General Public License
2944+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2945+
2946+
2947+"""
2948+:mod:`lava_test.api.delegates` -- Interface classes for test delegates
2949+======================================================================
2950+
2951+.. module: lava_test.api.delegates
2952+
2953+ :synopsis: Interface classes for test delegates
2954+"""
2955+
2956+from abc import abstractmethod, abstractproperty
2957+
2958+from lava_test.api import _Interface
2959+
2960+
2961+class ITestInstaller(_Interface):
2962+ """
2963+ Test installer delegate class.
2964+
2965+ Wraps the knowledge on how to install a test. It is most helpful with
2966+ :class:`~lava_test.core.tests.Test` that delegates actual actions to helper
2967+ classes.
2968+
2969+ .. versionadded:: 0.2
2970+ """
2971+
2972+ @abstractmethod
2973+ def install(self, observer):
2974+ """
2975+ Install the test program.
2976+
2977+ :param observer:
2978+ Observer object that makes it possible to monitor the actions
2979+ performed by the test installer.
2980+ :type observer: :class:`~lava_test.api.observers.ITestInstallerObserver`
2981+
2982+ .. versionadded:: 0.2
2983+ """
2984+
2985+
2986+class ITestRunner(_Interface):
2987+ """
2988+ Test runner delegate.
2989+
2990+ Wraps the knowledge on how to run a test. It is most helpful with
2991+ :class:`lava_test.core.tests.Test` that delegates actual actions to
2992+ helper classes.
2993+
2994+ .. versionadded:: 0.2
2995+ """
2996+
2997+ @abstractmethod
2998+ def run(self, artifacts, observer):
2999+ """
3000+ Run the test and create artifacts (typically log files).
3001+
3002+ Artifacts must be created in the directory specified by various methods
3003+ and properties of of :class:`lava_test.core.TestArtifacts`.
3004+
3005+ :param artifacts:
3006+ Object that describes where to store test run artifacts
3007+ :type artifacts: :class:`~lava_test.core.artifacts.TestArtifacts`.
3008+ :param observer:
3009+ Observer object that makes it possible to monitor the actions
3010+ performed by the test runner.
3011+ :type observer: :class:`~lava_test.api.observers.ITestRunnerObserver`
3012+
3013+ .. versionadded:: 0.2
3014+ """
3015+
3016+
3017+class ITestParser(_Interface):
3018+ """
3019+ Test artefact parser delegate.
3020+
3021+ Wraps the knowledge on how to parse the artifacts of a previous test run.
3022+ It is most helpful with :class:`~lava_test.core.tests.Test` that delegates
3023+ actual actions to helper classes.
3024+
3025+ .. versionadded:: 0.2
3026+ """
3027+
3028+ @abstractmethod
3029+ def parse(self, artifacts):
3030+ """
3031+ Parse the artifacts of a previous test run and return a dictionary with
3032+ a partial TestRun object.
3033+
3034+ :param artifacts:
3035+ Object that describes where to find test run artifacts
3036+ :type artifacts: :class:`~lava_test.core.artifacts.TestArtifacts`.
3037+
3038+ .. versionadded:: 0.2
3039+ """
3040+
3041+ @abstractproperty
3042+ def results(self):
3043+ """
3044+ Results dictionary to be merged with TestRun object inside the bundle.
3045+
3046+ .. seealso:: :meth:`~lava_test.core.artifacts.TestArtifacts.incorporate_parse_results`
3047+
3048+ .. versionadded:: 0.1
3049+ """
3050
3051=== added file 'lava_test/api/observers.py'
3052--- lava_test/api/observers.py 1970-01-01 00:00:00 +0000
3053+++ lava_test/api/observers.py 2011-09-13 22:44:36 +0000
3054@@ -0,0 +1,120 @@
3055+# Copyright (c) 2010, 2011 Linaro
3056+#
3057+# This program is free software: you can redistribute it and/or modify
3058+# it under the terms of the GNU General Public License as published by
3059+# the Free Software Foundation, either version 3 of the License, or
3060+# (at your option) any later version.
3061+#
3062+# This program is distributed in the hope that it will be useful,
3063+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3064+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3065+# GNU General Public License for more details.
3066+#
3067+# You should have received a copy of the GNU General Public License
3068+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3069+
3070+
3071+"""
3072+:mod:`lava_test.api.observers` -- Interface classes for observer classes
3073+========================================================================
3074+
3075+.. module: lava_test.api.observers
3076+ :synopsis: Interface classes for observer classes
3077+"""
3078+
3079+from abc import abstractmethod
3080+
3081+from lava_test.api import _Interface
3082+
3083+
3084+class IShellCommandObserver(_Interface):
3085+ """
3086+ Shell command runner observer class.
3087+
3088+ Allows the caller to observe shell commands that occur during some
3089+ operation. It is used by the command line UI.
3090+
3091+ .. versionadded:: 0.2
3092+ """
3093+
3094+ @abstractmethod
3095+ def about_to_run_shell_command(self, cmd):
3096+ """
3097+ Method called when a shell command is about to be invoked by the
3098+ observed object.
3099+
3100+ .. versionadded:: 0.2
3101+ """
3102+
3103+ @abstractmethod
3104+ def did_run_shell_command(self, cmd, returncode):
3105+ """
3106+ Method called when a shell command has been invoked by the observed
3107+ object.
3108+
3109+ .. versionadded:: 0.2
3110+ """
3111+
3112+ @abstractmethod
3113+ def display_subprocess_output(self, stream_name, line):
3114+ """
3115+ Method called for each line of stdout/stderr as obtained from a
3116+ subprocess.
3117+
3118+ .. versionadded:: 0.2
3119+ """
3120+
3121+
3122+class ITestInstallerObserver(IShellCommandObserver):
3123+ """
3124+ Test installer observer class.
3125+
3126+ Allows the caller to observe interesting actions that occur during
3127+ installation process. It is used by the command line UI.
3128+
3129+ .. versionadded:: 0.2
3130+ """
3131+
3132+ @abstractmethod
3133+ def about_to_install_packages(self, package_list):
3134+ """
3135+ Method called when a list of packages is about to be installed by the
3136+ installer
3137+
3138+ .. versionadded:: 0.2
3139+ """
3140+
3141+ @abstractmethod
3142+ def did_install_packages(self, package_list):
3143+ """
3144+ Method called when a package has been installed by the installer
3145+
3146+ .. versionadded:: 0.2
3147+ """
3148+
3149+ @abstractmethod
3150+ def about_to_download_file(self, url):
3151+ """
3152+ Method called when a file is about to be downloaded
3153+
3154+ .. versionadded:: 0.2
3155+ """
3156+
3157+ @abstractmethod
3158+ def did_download_file(self, url):
3159+ """
3160+ Method called when a file has been downloaded
3161+
3162+ .. versionadded:: 0.2
3163+ """
3164+
3165+
3166+class ITestRunnerObserver(IShellCommandObserver):
3167+ """
3168+ Test runner observer class.
3169+
3170+ Allows the caller to observe interesting actions that occur during testing
3171+ process. It is used by the command line UI.
3172+
3173+ .. versionadded:: 0.2
3174+ """
3175
3176=== added file 'lava_test/commands.py'
3177--- lava_test/commands.py 1970-01-01 00:00:00 +0000
3178+++ lava_test/commands.py 2011-09-13 22:44:36 +0000
3179@@ -0,0 +1,383 @@
3180+# Copyright (c) 2010, 2011 Linaro
3181+#
3182+# This program is free software: you can redistribute it and/or modify
3183+# it under the terms of the GNU General Public License as published by
3184+# the Free Software Foundation, either version 3 of the License, or
3185+# (at your option) any later version.
3186+#
3187+# This program is distributed in the hope that it will be useful,
3188+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3189+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3190+# GNU General Public License for more details.
3191+#
3192+# You should have received a copy of the GNU General Public License
3193+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3194+
3195+
3196+import os
3197+import subprocess
3198+
3199+from lava_tool.interface import Command as LavaCommand
3200+from lava_tool.interface import LavaCommandError
3201+import versiontools
3202+
3203+from lava_test.api.observers import (
3204+ ITestInstallerObserver,
3205+ ITestRunnerObserver)
3206+from lava_test.core.artifacts import TestArtifacts
3207+from lava_test.core.config import get_config
3208+from lava_test.core.loader import TestLoader
3209+
3210+
3211+class Command(LavaCommand, ITestInstallerObserver, ITestRunnerObserver):
3212+
3213+ def __init__(self, parser, args):
3214+ super(Command, self).__init__(parser, args)
3215+ self._config = get_config()
3216+ self._test_loader = TestLoader(self._config)
3217+
3218+ @classmethod
3219+ def register_arguments(cls, parser):
3220+ parser.add_argument(
3221+ "-q", "--quiet",
3222+ action="store_true",
3223+ default=False,
3224+ help="Be less verbose about undertaken actions")
3225+ parser.add_argument(
3226+ "-Q", "--quiet-subcommands",
3227+ action="store_true",
3228+ default=False,
3229+ help="Hide the output of all sub-commands (including tests)")
3230+
3231+ def say(self, text, *args, **kwargs):
3232+ print "LAVA:", text.format(*args, **kwargs)
3233+
3234+ def about_to_install_packages(self, package_list):
3235+ if self.args.quiet:
3236+ return
3237+ self.say("Installing packages: {0}", ", ".join(package_list))
3238+
3239+ def about_to_run_shell_command(self, cmd):
3240+ if self.args.quiet:
3241+ return
3242+ self.say("Running shell command: {0!r}", cmd)
3243+
3244+ def about_to_download_file(self, url):
3245+ if self.args.quiet:
3246+ return
3247+ self.say("Downloading file from: {0!r}", url)
3248+
3249+ def did_install_packages(self, package_list):
3250+ pass
3251+
3252+ def did_run_shell_command(self, cmd, returncode):
3253+ if returncode is None:
3254+ self.say("Command {0!r} was terminated prematurely", cmd)
3255+ elif returncode != 0:
3256+ self.say("Command {0!r} returned non-zero exit status {1}",
3257+ cmd, returncode)
3258+
3259+ def did_download_file(self, url):
3260+ pass
3261+
3262+ def display_subprocess_output(self, stream_name, line):
3263+ if self.args.quiet_subcommands:
3264+ return
3265+ if stream_name == 'stdout':
3266+ self.say('(stdout) {0}', line.rstrip())
3267+ elif stream_name == 'stderr':
3268+ self.say('(stderr) {0}', line.rstrip())
3269+
3270+
3271+class list_tests(Command):
3272+ """
3273+ List available tests
3274+
3275+ .. program:: lava-test list-tests
3276+
3277+ Lists all available tests, grouping them by provider.
3278+ """
3279+
3280+ def invoke(self):
3281+ for provider in self._test_loader.get_providers():
3282+ test_list = [provider[test_id] for test_id in provider]
3283+ if not test_list:
3284+ continue
3285+ self.say("{0}", provider.description)
3286+ for test in test_list:
3287+ self.say(" - {test_id}", test_id=test.test_id)
3288+
3289+
3290+class list_installed(Command):
3291+ """
3292+ List installed tests
3293+ """
3294+
3295+ def invoke(self):
3296+ for provider in self._test_loader.get_providers():
3297+ test_list = [provider[test_id] for test_id in provider]
3298+ if not test_list:
3299+ continue
3300+ self.say("{0}", provider.description)
3301+ count = 0
3302+ for test in test_list:
3303+ if not test.is_installed:
3304+ continue
3305+ self.say(" - {test_id}", test_id=test.test_id)
3306+ count += 1
3307+ if not count:
3308+ self.say("No tests installed")
3309+
3310+
3311+
3312+class TestAffectingCommand(Command):
3313+
3314+ INSTALL_REQUIRED = False
3315+
3316+ @classmethod
3317+ def register_arguments(cls, parser):
3318+ super(TestAffectingCommand, cls).register_arguments(parser)
3319+ parser.add_argument("test_id",
3320+ help="Test or test suite identifier")
3321+
3322+ def invoke(self):
3323+ try:
3324+ test = self._test_loader[self.args.test_id]
3325+ except KeyError:
3326+ raise LavaCommandError("There is no test with the specified ID")
3327+ return self.invoke_with_test(test)
3328+
3329+
3330+class install(TestAffectingCommand):
3331+ """
3332+ Install a test program
3333+ """
3334+
3335+ def invoke_with_test(self, test):
3336+ if test.is_installed:
3337+ raise LavaCommandError("This test is already installed")
3338+ try:
3339+ test.install(self)
3340+ except (subprocess.CalledProcessError, RuntimeError) as ex:
3341+ raise LavaCommandError(str(ex))
3342+
3343+
3344+class uninstall(TestAffectingCommand):
3345+ """
3346+ Uninstall a test program
3347+ """
3348+
3349+ def invoke_with_test(self, test):
3350+ if not test.is_installed:
3351+ raise LavaCommandError("This test is not installed")
3352+ test.uninstall()
3353+
3354+
3355+class run(TestAffectingCommand):
3356+ """
3357+ Run a previously installed test program
3358+ """
3359+
3360+ @classmethod
3361+ def register_arguments(cls, parser):
3362+ super(run, cls).register_arguments(parser)
3363+ group = parser.add_argument_group("initial bundle configuration")
3364+ group.add_argument("-S", "--skip-software-context",
3365+ default=False,
3366+ action="store_true",
3367+ help=("Do not store the software context in the"
3368+ " initial bundle. Typically this saves OS"
3369+ " image name and all the installed software"
3370+ " packages."))
3371+ group.add_argument("-H", "--skip-hardware-context",
3372+ default=False,
3373+ action="store_true",
3374+ help=("Do not store the hardware context in the"
3375+ " initial bundle. Typically this saves CPU,"
3376+ " memory and USB device information."))
3377+ group.add_argument("--trusted-time",
3378+ default=False,
3379+ action="store_true",
3380+ help=("Indicate that the real time clock has"
3381+ " accurate data. This can differentiate"
3382+ " test results created on embedded devices"
3383+ " that often have inaccurate real time"
3384+ " clock settings."))
3385+ group = parser.add_argument_group("complete bundle configuration")
3386+ group.add_argument("-o", "--output",
3387+ default=None,
3388+ metavar="FILE",
3389+ help=("After running the test parse the result"
3390+ " artifacts, fuse them with the initial"
3391+ " bundle and finally save the complete bundle"
3392+ " to the specified FILE."))
3393+ group.add_argument("-A", "--skip-attachments",
3394+ default=False,
3395+ action="store_true",
3396+ help=("Do not store standard output and standard"
3397+ " error log files as attachments. This"
3398+ " option is only affecting the bundle"
3399+ " created with --output, the initial bundle"
3400+ " is not affected as it never stores any"
3401+ " attachments."))
3402+
3403+ def invoke_with_test(self, test):
3404+ if not test.is_installed:
3405+ raise LavaCommandError("The specified test is not installed")
3406+ try:
3407+ artifacts = test.run(self)
3408+ except subprocess.CalledProcessError as ex:
3409+ if ex.returncode is None:
3410+ raise LavaCommandError("Command %r was aborted" % ex.cmd)
3411+ else:
3412+ raise LavaCommandError(str(ex))
3413+ except RuntimeError as ex:
3414+ raise LavaCommandError(str(ex))
3415+ self.say("run complete, result_id is {0!r}", artifacts.result_id)
3416+ artifacts.create_initial_bundle(
3417+ self.args.skip_software_context,
3418+ self.args.skip_hardware_context,
3419+ self.args.trusted_time)
3420+ artifacts.save_bundle()
3421+ if self.args.output:
3422+ parse_results = test.parse(artifacts)
3423+ artifacts.incorporate_parse_results(parse_results)
3424+ if not self.args.skip_attachments:
3425+ artifacts.attach_standard_files_to_bundle()
3426+ artifacts.save_bundle_as(self.args.output)
3427+
3428+
3429+class parse(TestAffectingCommand):
3430+ """
3431+ Parse the results of previous test run
3432+ """
3433+
3434+ @classmethod
3435+ def register_arguments(cls, parser):
3436+ super(parse, cls).register_arguments(parser)
3437+ parser.add_argument("result_id",
3438+ help="Test run result identifier")
3439+ group = parser.add_argument_group("complete bundle configuration")
3440+ group.add_argument("-o", "--output",
3441+ default=None,
3442+ metavar="FILE",
3443+ help=("After running the test parse the result"
3444+ " artifacts, fuse them with the initial"
3445+ " bundle and finally save the complete bundle"
3446+ " to the specified FILE."))
3447+ group.add_argument("-A", "--skip-attachments",
3448+ default=False,
3449+ action="store_true",
3450+ help=("Do not store standard output and standard"
3451+ " error log files as attachments. This"
3452+ " option is only affecting the bundle"
3453+ " created with --output, the initial bundle"
3454+ " is not affected as it never stores any"
3455+ " attachments."))
3456+
3457+ def invoke_with_test(self, test):
3458+ artifacts = TestArtifacts(
3459+ self.args.test_id, self.args.result_id, self._config)
3460+ if not os.path.exists(artifacts.bundle_pathname):
3461+ raise LavaCommandError("Specified result does not exist")
3462+ artifacts.load_bundle()
3463+ parse_results = test.parse(artifacts)
3464+ artifacts.incorporate_parse_results(parse_results)
3465+ self.say("Parsed {0} test results",
3466+ len(artifacts.bundle["test_runs"][0]["test_results"]))
3467+ print artifacts.dumps_bundle()
3468+ if self.args.output:
3469+ if not self.args.skip_attachments:
3470+ artifacts.attach_standard_files_to_bundle()
3471+ artifacts.save_bundle_as(self.args.output)
3472+
3473+
3474+class show(Command):
3475+ """
3476+ Display the output from a previous test run
3477+ """
3478+
3479+ @classmethod
3480+ def register_arguments(cls, parser):
3481+ super(show, cls).register_arguments(parser)
3482+ parser.add_argument("result_id",
3483+ help="Test run result identifier")
3484+
3485+ def invoke(self):
3486+ artifacts = TestArtifacts(None, self.args.result_id, self._config)
3487+ if not os.path.exists(artifacts.results_dir):
3488+ raise LavaCommandError("Specified result does not exist")
3489+ if os.path.exists(artifacts.stdout_pathname):
3490+ with open(artifacts.stdout_pathname, "rt") as stream:
3491+ for line in iter(stream.readline, ''):
3492+ self.display_subprocess_output("stdout", line)
3493+ if os.path.exists(artifacts.stderr_pathname):
3494+ with open(artifacts.stderr_pathname, "rt") as stream:
3495+ for line in iter(stream.readline, ''):
3496+ self.display_subprocess_output("stderr", line)
3497+
3498+
3499+class version(Command):
3500+ """
3501+ Show LAVA Test version
3502+ """
3503+
3504+ def invoke(self):
3505+ self.say("version details:")
3506+ for framework in self._get_frameworks():
3507+ self.say(" - {framework}: {version}",
3508+ framework=framework.__name__,
3509+ version=versiontools.format_version(
3510+ framework.__version__, framework))
3511+
3512+ def _get_frameworks(self):
3513+ import lava_tool
3514+ import lava_test
3515+ import linaro_dashboard_bundle
3516+ import linaro_json
3517+ return [
3518+ lava_test,
3519+ lava_tool,
3520+ linaro_dashboard_bundle,
3521+ linaro_json]
3522+
3523+
3524+class register_test(Command):
3525+ """
3526+ Register remote test
3527+ """
3528+
3529+ @classmethod
3530+ def register_arguments(cls, parser):
3531+ super(register_test, cls).register_arguments(parser)
3532+ parser.add_argument("test_url",
3533+ help="Url for test definition file")
3534+
3535+ def invoke(self):
3536+ try:
3537+ from lava_test.core.providers import RegistryProvider
3538+ RegistryProvider.register_remote_test(self.args.test_url)
3539+ except ValueError as exc:
3540+ raise LavaCommandError("Unable to register test: %s" % exc)
3541+ except KeyError:
3542+ raise LavaCommandError("There is no test_url")
3543+
3544+class unregister_test(Command):
3545+ """
3546+ Unregister remote test
3547+ """
3548+
3549+ @classmethod
3550+ def register_arguments(cls, parser):
3551+ super(unregister_test, cls).register_arguments(parser)
3552+ parser.add_argument("test_url",
3553+ help="Url for test definition file")
3554+
3555+ def invoke(self):
3556+ try:
3557+ from lava_test.core.providers import RegistryProvider
3558+ RegistryProvider.unregister_remote_test(self.args.test_url)
3559+ except ValueError as exc:
3560+ raise LavaCommandError("Unable to unregister test: %s" % exc)
3561+ except KeyError:
3562+ raise LavaCommandError("There is no test_url")
3563
3564=== added directory 'lava_test/core'
3565=== added file 'lava_test/core/__init__.py'
3566=== added file 'lava_test/core/artifacts.py'
3567--- lava_test/core/artifacts.py 1970-01-01 00:00:00 +0000
3568+++ lava_test/core/artifacts.py 2011-09-13 22:44:36 +0000
3569@@ -0,0 +1,277 @@
3570+# Copyright (c) 2010, 2011 Linaro
3571+#
3572+# This program is free software: you can redistribute it and/or modify
3573+# it under the terms of the GNU General Public License as published by
3574+# the Free Software Foundation, either version 3 of the License, or
3575+# (at your option) any later version.
3576+#
3577+# This program is distributed in the hope that it will be useful,
3578+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3579+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3580+# GNU General Public License for more details.
3581+#
3582+# You should have received a copy of the GNU General Public License
3583+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3584+
3585+from __future__ import absolute_import
3586+
3587+import base64
3588+import datetime
3589+import logging
3590+import os
3591+import uuid
3592+
3593+from linaro_dashboard_bundle.io import DocumentIO
3594+
3595+from lava_test.core import hwprofile, swprofile
3596+from lava_test.utils import merge_dict, mkdir_p
3597+
3598+
3599+class TestArtifacts(object):
3600+ """
3601+ Class representing test run artifacts, that is, static leftovers
3602+ independent of the wrapper class that encapsulates test handling.
3603+
3604+ .. versionadded:: 0.2
3605+ """
3606+
3607+ def __init__(self, test_id, result_id, config):
3608+ self._test_id = test_id
3609+ self._result_id = result_id
3610+ self._config = config
3611+ self._bundle = None
3612+
3613+ @classmethod
3614+ def allocate(cls, test_id, config):
3615+ """
3616+ Allocate new test artifacts object that corresponds to the specified
3617+ test_id. This constructs a new result_id and creates the corresponding
3618+ filesystem directory that holds those artifacts.
3619+
3620+ .. versionadded:: 0.2
3621+ """
3622+ result_id = (
3623+ "{test_id}.{time.tm_year:04}-{time.tm_mon:02}-{time.tm_mday:02}T"
3624+ "{time.tm_hour:02}:{time.tm_min:02}:{time.tm_sec:02}Z").format(
3625+ test_id=test_id,
3626+ time=datetime.datetime.utcnow().timetuple())
3627+ self = cls(test_id, result_id, config)
3628+ logging.debug("Creating result directory: %r", self.results_dir)
3629+ mkdir_p(self.results_dir)
3630+ return self
3631+
3632+ @property
3633+ def test_id(self):
3634+ """
3635+ The ID of the test this run is associated with
3636+
3637+ .. versionadded:: 0.2
3638+ """
3639+ return self._test_id
3640+
3641+ @property
3642+ def result_id(self):
3643+ """
3644+ The ID of the test run.
3645+
3646+ This field is different from analyzer_assigned_uuid at this time but
3647+ may change in the future. The purpose of this field is to identify the
3648+ test run and be able to locate attachments/log files/bundle on the file
3649+ system.
3650+
3651+ .. versionadded:: 0.2
3652+ """
3653+ return self._result_id
3654+
3655+ @property
3656+ def results_dir(self):
3657+ """
3658+ Pathname of a directory with test run artifacts (log files, crash
3659+ dumps, etc).
3660+
3661+ .. versionadded:: 0.2
3662+ """
3663+ return os.path.join(self._config.resultsdir, self.result_id)
3664+
3665+ def load_bundle(self):
3666+ """
3667+ Load the results bundle from disk.
3668+
3669+ The bundle is also validated if linaro-dashboard-bundle library is
3670+ installed.
3671+ """
3672+ with open(self.bundle_pathname, 'rt') as stream:
3673+ self._bundle = DocumentIO.load(stream)[1]
3674+
3675+ def dumps_bundle(self):
3676+ return DocumentIO.dumps(self._bundle)
3677+
3678+ def save_bundle(self):
3679+ """
3680+ Save the results bundle to the disk
3681+
3682+ The bundle is also validated if linaro-dashboard-bundle library is
3683+ installed.
3684+ """
3685+ self.save_bundle_as(self.bundle_pathname)
3686+
3687+ def save_bundle_as(self, pathname):
3688+ """
3689+ Save the results bundle to the specified file on disk.
3690+
3691+ The bundle should have been created or loaded earlier
3692+ """
3693+ with open(pathname, 'wt') as stream:
3694+ DocumentIO.dump(stream, self._bundle)
3695+
3696+ @property
3697+ def bundle(self):
3698+ """
3699+ The deserialized bundle object.
3700+
3701+ This can be either created with create_bundle() or loaded
3702+ from disk with load_bundle()
3703+ """
3704+ return self._bundle
3705+
3706+ def create_initial_bundle(self,
3707+ skip_software_context=False,
3708+ skip_hardware_context=False,
3709+ time_check_performed=False):
3710+ """
3711+ Create the bundle object.
3712+
3713+ This creates a typical bundle structure. Optionally it can also add
3714+ software and hardware context information.
3715+
3716+ For a complete bundle you may want to add attachments and incorporate
3717+ parse results by calling appropriate methods after loading or creating
3718+ the initial bundle.
3719+ """
3720+ TIMEFORMAT = '%Y-%m-%dT%H:%M:%SZ'
3721+ # Generate UUID and analyzer_assigned_date for the test run
3722+ analyzer_assigned_uuid = str(uuid.uuid1())
3723+ analyzer_assigned_date = datetime.datetime.utcnow()
3724+ # Create basic test run structure
3725+ test_run = {
3726+ 'test_id': self.test_id,
3727+ 'analyzer_assigned_date': analyzer_assigned_date.strftime(
3728+ TIMEFORMAT),
3729+ 'analyzer_assigned_uuid': analyzer_assigned_uuid,
3730+ 'time_check_performed': time_check_performed,
3731+ "test_results": [],
3732+ "attachments": [],
3733+ }
3734+ # Store hardware and software context if requested
3735+ if not skip_software_context:
3736+ test_run['software_context'] = swprofile.get_software_context()
3737+ if not skip_hardware_context:
3738+ test_run['hardware_context'] = hwprofile.get_hardware_context()
3739+ # Create the bundle object
3740+ self._bundle = {
3741+ 'format': 'Dashboard Bundle Format 1.2',
3742+ 'test_runs': [test_run]}
3743+
3744+ @property
3745+ def test_run(self):
3746+ try:
3747+ return self._bundle["test_runs"][0]
3748+ except KeyError:
3749+ raise AttributeError("test_run can be accessed only after you load"
3750+ " or create an initial bundle")
3751+
3752+ def attach_file(self, real_pathname, stored_pathname, mime_type):
3753+ """
3754+ Append an attachment to the test run.
3755+
3756+ The file is only attached if real_pathname designates an existing,
3757+ nonempty file. If the mime_type starts with 'text/' the file is opened
3758+ in text mode, otherwise binary mode is used.
3759+ """
3760+ if not os.path.exists(real_pathname):
3761+ return
3762+ if mime_type.startswith('text/'):
3763+ mode = 'rt'
3764+ else:
3765+ mode = 'rb'
3766+ with open(real_pathname, mode) as stream:
3767+ data = stream.read()
3768+ if not data:
3769+ return
3770+ self.test_run['attachments'].append({
3771+ "pathname": stored_pathname,
3772+ "mime_type": mime_type,
3773+ "content": base64.standard_b64encode(data)})
3774+
3775+ def incorporate_parse_results(self, parse_results):
3776+ """
3777+ Merge the data returned by the test parser into the current test run.
3778+
3779+ Non-overlapping data is simply added. Overlapping data is either merged
3780+ (lists are extended, dictionaries are recursively merged) or
3781+ overwritten (all other types).
3782+ """
3783+ assert isinstance(parse_results, dict)
3784+ # Use whatever the parser gave us to improve the results
3785+ logging.debug("Using parser data to enrich test run details")
3786+ merge_dict(self.test_run, parse_results)
3787+
3788+ def attach_standard_files_to_bundle(self):
3789+ """
3790+ Attach standard output and standard error log files to the bundle.
3791+
3792+ Both files are only attached if exist and non-empty. The attachments
3793+ are actually associated with a test run, not a bundle, but the
3794+ description is good enough for simplicity.
3795+ """
3796+ self.attach_file(self.stdout_pathname, "testoutput.log", "text/plain")
3797+ self.attach_file(self.stderr_pathname, "testoutput.err", "text/plain")
3798+
3799+ @property
3800+ def bundle_pathname(self):
3801+ """
3802+ Pathname of the result bundle.
3803+
3804+ The bundle contains the snapshot of environment information as well as
3805+ test identity and is created when you invoke ITest.run().
3806+
3807+ The bundle file name is always "testdata.json"
3808+
3809+ .. versionadded:: 0.2
3810+ """
3811+ return self.get_artefact_pathname("testdata.json")
3812+
3813+ @property
3814+ def stdout_pathname(self):
3815+ """
3816+ Pathname of the log file of the standard output as returned by the test
3817+ program.
3818+
3819+ The log file name is always "testoutput.log"
3820+
3821+ .. versionadded:: 0.2
3822+ """
3823+ return self.get_artefact_pathname("testoutput.log")
3824+
3825+ @property
3826+ def stderr_pathname(self):
3827+ """
3828+ Pathname of the log file of the standard output as returned by the test
3829+ program.
3830+
3831+ The log file name is always "testoutput.err"
3832+
3833+ .. versionadded:: 0.2
3834+ """
3835+ return self.get_artefact_pathname("testoutput.err")
3836+
3837+ def get_artefact_pathname(self, artefact_name):
3838+ """
3839+ Return a pathname of a test run artefact file.
3840+
3841+ This is more useful than hard-coding the path as it allows the test
3842+ runner not to worry about the location of the results directory.
3843+
3844+ .. versionadded:: 0.2
3845+ """
3846+ return os.path.join(self.results_dir, artefact_name)
3847
3848=== added file 'lava_test/core/config.py'
3849--- lava_test/core/config.py 1970-01-01 00:00:00 +0000
3850+++ lava_test/core/config.py 2011-09-13 22:44:36 +0000
3851@@ -0,0 +1,97 @@
3852+# Copyright (c) 2010, 2011 Linaro
3853+#
3854+# This program is free software: you can redistribute it and/or modify
3855+# it under the terms of the GNU General Public License as published by
3856+# the Free Software Foundation, either version 3 of the License, or
3857+# (at your option) any later version.
3858+#
3859+# This program is distributed in the hope that it will be useful,
3860+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3861+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3862+# GNU General Public License for more details.
3863+#
3864+# You should have received a copy of the GNU General Public License
3865+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3866+
3867+import os
3868+import json
3869+
3870+
3871+class LavaTestConfig(object):
3872+
3873+ def __init__(self):
3874+ home = os.environ.get('HOME', '/')
3875+ baseconfig = os.environ.get('XDG_CONFIG_HOME',
3876+ os.path.join(home, '.config'))
3877+ basedata = os.environ.get('XDG_DATA_HOME',
3878+ os.path.join(home, '.local', 'share'))
3879+ self.configdir = os.path.join(baseconfig, 'lava_test')
3880+ self.installdir = os.path.join(basedata, 'lava_test', 'installed-tests')
3881+ self.resultsdir = os.path.join(basedata, 'lava_test', 'results')
3882+ self.registry = self._load_registry()
3883+
3884+ @property
3885+ def _registry_pathname(self):
3886+ return os.path.join(self.configdir, "registry.json")
3887+
3888+ def _load_registry(self):
3889+ try:
3890+ with open(self._registry_pathname) as stream:
3891+ return json.load(stream)
3892+ except (IOError, ValueError):
3893+ return self._get_default_registry()
3894+
3895+ def _save_registry(self):
3896+ if not os.path.exists(self.configdir):
3897+ os.makedirs(self.configdir)
3898+ with open(self._registry_pathname, "wt") as stream:
3899+ json.dump(self.registry, stream, indent=2)
3900+
3901+ def _get_default_registry(self):
3902+ return {
3903+ "format": "Lava-Test Test Registry 1.0",
3904+ "providers": [{
3905+ "entry_point": "lava_test.core.providers:BuiltInProvider"
3906+ }, {
3907+ "entry_point": "lava_test.core.providers:PkgResourcesProvider",
3908+ "config": {"namespace": "lava_test.test_definitions" }
3909+ },
3910+ {
3911+ "entry_point": "lava_test.core.providers:RegistryProvider",
3912+ "config": {"entries": [] }
3913+ }]
3914+ }
3915+
3916+ def get_provider_config(self, entry_point_name):
3917+ if "providers" not in self.registry:
3918+ self.registry["providers"] = []
3919+ for provider_info in self.registry["providers"]:
3920+ if provider_info.get("entry_point") == entry_point_name:
3921+ break
3922+ else:
3923+ provider_info = {"entry_point": entry_point_name}
3924+ self.registry["providers"].append(provider_info)
3925+ if "config" not in provider_info:
3926+ provider_info["config"] = {}
3927+ return provider_info["config"]
3928+
3929+ def get_logging_config_file(self):
3930+ logging_file = os.path.join(self.configdir, "logging.conf")
3931+ if os.path.exists(logging_file):
3932+ return logging_file
3933+ else:
3934+ return None
3935+
3936+_config = None
3937+
3938+
3939+def get_config():
3940+ global _config
3941+ if _config is not None:
3942+ return _config
3943+ return LavaTestConfig()
3944+
3945+
3946+def set_config(config):
3947+ global _config
3948+ _config = config
3949
3950=== added file 'lava_test/core/hwprofile.py'
3951--- lava_test/core/hwprofile.py 1970-01-01 00:00:00 +0000
3952+++ lava_test/core/hwprofile.py 2011-09-13 22:44:36 +0000
3953@@ -0,0 +1,223 @@
3954+# Copyright (c) 2010, 2011 Linaro
3955+#
3956+# This program is free software: you can redistribute it and/or modify
3957+# it under the terms of the GNU General Public License as published by
3958+# the Free Software Foundation, either version 3 of the License, or
3959+# (at your option) any later version.
3960+#
3961+# This program is distributed in the hope that it will be useful,
3962+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3963+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3964+# GNU General Public License for more details.
3965+#
3966+# You should have received a copy of the GNU General Public License
3967+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3968+
3969+import re
3970+import sys
3971+from subprocess import Popen, PIPE
3972+from lava_test.utils import read_file, get_machine_type
3973+
3974+
3975+INTEL_KEYMAP = {
3976+ 'vendor_id': 'cpu_vendor_name',
3977+ 'cpu family': 'cpu_family',
3978+ 'model': 'cpu_model',
3979+ 'model name': 'cpu_model_name',
3980+ 'stepping': 'cpu_stepping',
3981+ 'cpu MHz': 'cpu_mhz',
3982+ 'flags': 'cpu_features',
3983+}
3984+
3985+
3986+INTEL_VALMAP = {
3987+ 'cpu family': int,
3988+ 'model': int,
3989+ 'stepping': int,
3990+ 'cpu MHz': float,
3991+}
3992+
3993+
3994+ARM_KEYMAP = {
3995+ 'Processor': 'cpu_model_name',
3996+ 'Features': 'cpu_features',
3997+ 'CPU implementer': 'cpu_implementer',
3998+ 'CPU architecture': 'cpu_architecture',
3999+ 'CPU variant': 'cpu_variant',
4000+ 'CPU part': 'cpu_part',
4001+ 'CPU revision': 'cpu_revision',
4002+}
4003+
4004+
4005+ARM_VALMAP = {
4006+ 'CPU implementer': lambda value: int(value, 16),
4007+ 'CPU architecture': int,
4008+ 'CPU variant': lambda value: int(value, 16),
4009+ 'CPU part': lambda value: int(value, 16),
4010+ 'CPU revision': int,
4011+}
4012+
4013+
4014+def _translate_cpuinfo(keymap, valmap, key, value):
4015+ """
4016+ Translate a key and value using keymap and valmap passed in
4017+ """
4018+ newkey = keymap.get(key, key)
4019+ newval = valmap.get(key, lambda x: x)(value)
4020+ return newkey, newval
4021+
4022+
4023+def get_cpu_devs():
4024+ """
4025+ Return a list of CPU devices
4026+ """
4027+ pattern = re.compile('^(?P<key>.+?)\s*:\s*(?P<value>.*)$')
4028+ cpunum = 0
4029+ devices = []
4030+ cpudevs = []
4031+ cpudevs.append({})
4032+ machine = get_machine_type()
4033+ if machine in ('i686', 'x86_64'):
4034+ keymap, valmap = INTEL_KEYMAP, INTEL_VALMAP
4035+ elif machine.startswith('arm'):
4036+ keymap, valmap = ARM_KEYMAP, ARM_VALMAP
4037+
4038+ try:
4039+ cpuinfo = read_file("/proc/cpuinfo")
4040+ for line in cpuinfo.splitlines():
4041+ match = pattern.match(line)
4042+ if match:
4043+ key, value = match.groups()
4044+ key, value = _translate_cpuinfo(keymap, valmap,
4045+ key, value)
4046+ if cpudevs[cpunum].get(key):
4047+ cpunum += 1
4048+ cpudevs.append({})
4049+ cpudevs[cpunum][key] = value
4050+ for c in range(len(cpudevs)):
4051+ device = {}
4052+ device['device_type'] = 'device.cpu'
4053+ device['description'] = 'Processor #{0}'.format(c)
4054+ device['attributes'] = cpudevs[c]
4055+ devices.append(device)
4056+ except IOError:
4057+ print >> sys.stderr, "WARNING: Could not read cpu information"
4058+ return devices
4059+
4060+
4061+def get_board_devs():
4062+ """
4063+ Return a list of board devices
4064+ """
4065+ devices = []
4066+ attributes = {}
4067+ device = {}
4068+ machine = get_machine_type()
4069+ if machine in ('i686', 'x86_64'):
4070+ try:
4071+ description = read_file('/sys/class/dmi/id/board_name') or None
4072+ vendor = read_file('/sys/class/dmi/id/board_vendor') or None
4073+ version = read_file('/sys/class/dmi/id/board_version') or None
4074+ if description:
4075+ device['description'] = description.strip()
4076+ if vendor:
4077+ attributes['vendor'] = vendor.strip()
4078+ if version:
4079+ attributes['version'] = version.strip()
4080+ except IOError:
4081+ print >> sys.stderr, "WARNING: Could not read board information"
4082+ return devices
4083+ elif machine.startswith('arm'):
4084+ try:
4085+ cpuinfo = read_file("/proc/cpuinfo")
4086+ if cpuinfo is None:
4087+ return devices
4088+ pattern = re.compile("^Hardware\s*:\s*(?P<description>.+)$", re.M)
4089+ match = pattern.search(cpuinfo)
4090+ if match is None:
4091+ return devices
4092+ device['description'] = match.group('description')
4093+ except IOError:
4094+ print >> sys.stderr, "WARNING: Could not read board information"
4095+ return devices
4096+ else:
4097+ return devices
4098+ if attributes:
4099+ device['attributes'] = attributes
4100+ device['device_type'] = 'device.board'
4101+ devices.append(device)
4102+ return devices
4103+
4104+
4105+def get_mem_devs():
4106+ """ Return a list of memory devices
4107+
4108+ This returns up to two items, one for physical RAM and another for swap
4109+ """
4110+ pattern = re.compile('^(?P<key>.+?)\s*:\s*(?P<value>.+) kB$', re.M)
4111+
4112+ devices = []
4113+ try:
4114+ meminfo = read_file("/proc/meminfo")
4115+ for match in pattern.finditer(meminfo):
4116+ key, value = match.groups()
4117+ if key not in ('MemTotal', 'SwapTotal'):
4118+ continue
4119+ capacity = int(value) << 10 # Kernel reports in 2^10 units
4120+ if capacity == 0:
4121+ continue
4122+ if key == 'MemTotal':
4123+ kind = 'RAM'
4124+ else:
4125+ kind = 'swap'
4126+ description = "{capacity}MiB of {kind}".format(
4127+ capacity=capacity >> 20, kind=kind)
4128+ device = {}
4129+ device['description'] = description
4130+ device['attributes'] = {'capacity': str(capacity), 'kind': kind}
4131+ device['device_type'] = "device.mem"
4132+ devices.append(device)
4133+ except IOError:
4134+ print >> sys.stderr, "WARNING: Could not read memory information"
4135+ return devices
4136+
4137+
4138+def get_usb_devs():
4139+ """
4140+ Return a list of usb devices
4141+ """
4142+ pattern = re.compile(
4143+ "^Bus \d{3} Device \d{3}: ID (?P<vendor_id>[0-9a-f]{4}):"
4144+ "(?P<product_id>[0-9a-f]{4}) (?P<description>.*)$")
4145+ devices = []
4146+ try:
4147+ for line in Popen('lsusb', stdout=PIPE).communicate()[0].splitlines():
4148+ match = pattern.match(line)
4149+ if match:
4150+ vendor_id, product_id, description = match.groups()
4151+ attributes = {}
4152+ device = {}
4153+ attributes['vendor_id'] = int(vendor_id, 16)
4154+ attributes['product_id'] = int(product_id, 16)
4155+ device['attributes'] = attributes
4156+ device['description'] = description
4157+ device['device_type'] = 'device.usb'
4158+ devices.append(device)
4159+ except OSError:
4160+ print >> sys.stderr, "WARNING: Could not read usb device information, \
4161+unable to run lsusb, please install usbutils package"
4162+ return devices
4163+
4164+
4165+def get_hardware_context():
4166+ """
4167+ Return a dict with all of the hardware profile information gathered
4168+ """
4169+ hardware_context = {}
4170+ devices = []
4171+ devices.extend(get_cpu_devs())
4172+ devices.extend(get_board_devs())
4173+ devices.extend(get_mem_devs())
4174+ devices.extend(get_usb_devs())
4175+ hardware_context['devices'] = devices
4176+ return hardware_context
4177
4178=== added file 'lava_test/core/installers.py'
4179--- lava_test/core/installers.py 1970-01-01 00:00:00 +0000
4180+++ lava_test/core/installers.py 2011-09-13 22:44:36 +0000
4181@@ -0,0 +1,105 @@
4182+# Copyright (c) 2010, 2011 Linaro
4183+#
4184+# This program is free software: you can redistribute it and/or modify
4185+# it under the terms of the GNU General Public License as published by
4186+# the Free Software Foundation, either version 3 of the License, or
4187+# (at your option) any later version.
4188+#
4189+# This program is distributed in the hope that it will be useful,
4190+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4191+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4192+# GNU General Public License for more details.
4193+#
4194+# You should have received a copy of the GNU General Public License
4195+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4196+
4197+import hashlib
4198+import os
4199+
4200+from lava_test.api.delegates import ITestInstaller
4201+from lava_test.extcmd import ExternalCommandWithDelegate
4202+from lava_test.utils import geturl
4203+
4204+
4205+class TestInstaller(ITestInstaller):
4206+ """
4207+ Base class for defining an installer object.
4208+
4209+ This class can be used as-is for simple installers, or extended
4210+ for more advanced functionality.
4211+
4212+ :ivar steps:
4213+ List of steps to be executed in a shell
4214+
4215+ :ivar deps:
4216+ List of Debian or Ubuntu packages to apt-get install before running the
4217+ steps.
4218+
4219+ :ivar url:
4220+ Location from which the test suite should be downloaded.
4221+
4222+ :ivar md5:
4223+ The md5sum to check the integrety of the download
4224+ """
4225+ def __init__(self, steps=None, deps=None, url=None, md5=None, **kwargs):
4226+ self.steps = steps or []
4227+ self.deps = deps or []
4228+ self.url = url
4229+ self.md5 = md5
4230+
4231+ def __repr__(self):
4232+ return "<%s steps=%r deps=%r url=%r md5=%r>" % (
4233+ self.__class__.__name__,
4234+ self.steps, self.deps, self.url, self.md5)
4235+
4236+ def _run_shell_cmd(self, cmd, observer):
4237+ if observer: observer.about_to_run_shell_command(cmd)
4238+ extcmd = ExternalCommandWithDelegate(observer)
4239+ returncode = extcmd.check_call(cmd, shell=True)
4240+ if observer: observer.did_run_shell_command(cmd, returncode)
4241+
4242+ def _installdeps(self, observer):
4243+ if self.deps:
4244+ if observer: observer.about_to_install_packages(self.deps)
4245+ # XXX: Possible point of target-specific package installation
4246+ cmd = "sudo apt-get install -y " + " ".join(self.deps)
4247+ self._run_shell_cmd(cmd, observer)
4248+ if observer: observer.did_install_packages(self.deps)
4249+
4250+ def _download(self, observer):
4251+ """
4252+ Download the file specified by the url and check the md5.
4253+
4254+ Returns the path and filename if successful, otherwise return None
4255+ """
4256+ if not self.url:
4257+ return
4258+ if observer: observer.about_to_download_file(self.url)
4259+ filename = geturl(self.url)
4260+ # If the file does not exist, then the download was not
4261+ # successful
4262+ if not os.path.exists(filename):
4263+ raise RuntimeError(
4264+ "Failed to download %r" % self.url)
4265+ if observer: observer.did_download_file(self.url)
4266+ if self.md5:
4267+ checkmd5 = hashlib.md5()
4268+ with open(filename, 'rb') as fd:
4269+ data = fd.read(0x10000)
4270+ while data:
4271+ checkmd5.update(data)
4272+ data = fd.read(0x10000)
4273+ if checkmd5.hexdigest() != self.md5:
4274+ raise RuntimeError(
4275+ "md5sum mismatch of file %r, got %s expected %s" % (
4276+ filename, checkmd5.hexdigest(), self.md5))
4277+ return filename
4278+
4279+ def _runsteps(self, observer):
4280+ for cmd in self.steps:
4281+ self._run_shell_cmd(cmd, observer)
4282+
4283+ def install(self, observer=None):
4284+ self._installdeps(observer)
4285+ self._download(observer)
4286+ self._runsteps(observer)
4287
4288=== added file 'lava_test/core/loader.py'
4289--- lava_test/core/loader.py 1970-01-01 00:00:00 +0000
4290+++ lava_test/core/loader.py 2011-09-13 22:44:36 +0000
4291@@ -0,0 +1,83 @@
4292+# Copyright (c) 2010, 2011 Linaro
4293+#
4294+# This program is free software: you can redistribute it and/or modify
4295+# it under the terms of the GNU General Public License as published by
4296+# the Free Software Foundation, either version 3 of the License, or
4297+# (at your option) any later version.
4298+#
4299+# This program is distributed in the hope that it will be useful,
4300+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4301+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4302+# GNU General Public License for more details.
4303+#
4304+# You should have received a copy of the GNU General Public License
4305+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4306+
4307+from __future__ import absolute_import
4308+from lava_test.core.config import get_config
4309+
4310+class TestLoader(object):
4311+ """
4312+ Test loader.
4313+
4314+ Encapsulates LAVA Test's knowledge of available tests.
4315+
4316+ Test can be loaded by name with
4317+ :meth:`lava_test.core.loader.TestLoader.__getitem__()`. Test can also be
4318+ listed by :meth:`lava_test.core.loader.TestLoader.get_providers()` and then
4319+ iterating over tests returned by each provider.
4320+ """
4321+
4322+ def __init__(self, config):
4323+ self._config = config
4324+
4325+ def get_providers(self):
4326+ """
4327+ Return a generator of available providers
4328+ """
4329+ import pkg_resources
4330+ for provider_info in self._config.registry.get("providers", []):
4331+ entry_point_name = provider_info.get("entry_point")
4332+ module_name, attrs = entry_point_name.split(':', 1)
4333+ attrs = attrs.split('.')
4334+ try:
4335+ entry_point = pkg_resources.EntryPoint(
4336+ entry_point_name, module_name, attrs,
4337+ dist=pkg_resources.get_distribution("lava-test"))
4338+ provider_cls = entry_point.load()
4339+ provider = provider_cls(provider_info.get("config", {}))
4340+ yield provider
4341+ except pkg_resources.DistributionNotFound:
4342+ raise RuntimeError(
4343+ "lava-test is not properly set up."
4344+ " Please read the README file")
4345+ except ImportError, err:
4346+ print "Couldn't load module : %s . Maybe configuration needs to be updated" % module_name
4347+ print "The configuration is stored at %s" %(get_config().configdir)
4348+
4349+
4350+
4351+ def __getitem__(self, test_id):
4352+ """
4353+ Lookup a test with the specified test_id
4354+ """
4355+ for provider in self.get_providers():
4356+ try:
4357+ return provider[test_id]
4358+ except KeyError:
4359+ pass
4360+ raise KeyError(test_id)
4361+
4362+ def get_test_by_name(self, test_id):
4363+ """
4364+ Lookup a test with the specified name
4365+
4366+ .. deprecated:: 0.2
4367+ Use __getitem__ instead
4368+ """
4369+ for provider in self.get_providers():
4370+ try:
4371+ return provider[test_id]
4372+ except KeyError:
4373+ pass
4374+ raise ValueError("No such test %r" % test_id)
4375
4376=== added file 'lava_test/core/parsers.py'
4377--- lava_test/core/parsers.py 1970-01-01 00:00:00 +0000
4378+++ lava_test/core/parsers.py 2011-09-13 22:44:36 +0000
4379@@ -0,0 +1,147 @@
4380+# Copyright (c) 2010, 2011 Linaro
4381+#
4382+# This program is free software: you can redistribute it and/or modify
4383+# it under the terms of the GNU General Public License as published by
4384+# the Free Software Foundation, either version 3 of the License, or
4385+# (at your option) any later version.
4386+#
4387+# This program is distributed in the hope that it will be useful,
4388+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4389+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4390+# GNU General Public License for more details.
4391+#
4392+# You should have received a copy of the GNU General Public License
4393+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4394+
4395+import decimal
4396+import os
4397+import re
4398+
4399+from lava_test.api.delegates import ITestParser
4400+
4401+
4402+class TestParser(ITestParser):
4403+ """
4404+ Base class for defining a test parser
4405+
4406+ This class can be used as-is for simple results parsers, but will likely
4407+ need to be extended slightly for many. If used as it is, the parse()
4408+ method should be called while already in the results directory and assumes
4409+ that a file for test output will exist called testoutput.log.
4410+
4411+ :ivar pattern:
4412+ regexp pattern to identify important elements of test output For
4413+ example: If your testoutput had lines that look like: "test01: PASS"
4414+ then you could use a pattern like this:
4415+ "^(?P<testid>\w+):\W+(?P<result>\w+)" This would result in
4416+ identifying "test01" as testid and "PASS" as result. Once parse()
4417+ has been called, self.results.test_results[] contains a list of
4418+ dicts of all the key,value pairs found for each test result.
4419+
4420+ :ivar fixupdict:
4421+ Dict of strings to convert test results to standard strings For
4422+ example: if you want to standardize on having pass/fail results in
4423+ lower case, but your test outputs them in upper case, you could use a
4424+ fixupdict of something like: {'PASS':'pass','FAIL':'fail'}
4425+
4426+ :ivar appendall:
4427+ Append a dict to the test_results entry for each result.
4428+ For example: if you would like to add units="MB/s" to each result:
4429+ appendall={'units':'MB/s'}
4430+
4431+ :ivar results:
4432+ Dictionary of data that was scrubbed from the log file for this test
4433+ run. Most notably it contains the test_results array.
4434+ """
4435+ def __init__(self, pattern=None, fixupdict=None, appendall={}):
4436+ if pattern is not None:
4437+ try:
4438+ re.compile(pattern)
4439+ except Exception as ex:
4440+ raise ValueError(
4441+ "Invalid regular expression %r: %s", pattern, ex)
4442+ self._results = {'test_results': []}
4443+ self.pattern = pattern
4444+ self.fixupdict = fixupdict
4445+ self.appendall = appendall
4446+
4447+ def __repr__(self):
4448+ return "<%s pattern=%r fixupdict=%r appendall=%r>" % (
4449+ self.__class__.__name__,
4450+ self.pattern, self.fixupdict, self.appendall)
4451+
4452+ @property
4453+ def results(self):
4454+ return self._results
4455+
4456+ def parse(self, artifacts):
4457+ if os.path.exists(artifacts.stdout_pathname):
4458+ return self.parse_pathname(
4459+ artifacts.stdout_pathname,
4460+ os.path.relpath(artifacts.stdout_pathname,
4461+ artifacts.results_dir))
4462+ if os.path.exists(artifacts.stderr_pathname):
4463+ return self.parse_pathname(
4464+ artifacts.stderr_pathname,
4465+ os.path.relpath(artifacts.stderr_pathname,
4466+ artifacts.results_dir))
4467+
4468+ def parse_pathname(self, pathname, relative_pathname=None):
4469+ with open(pathname, 'rt') as stream:
4470+ for lineno, line in enumerate(stream, 1):
4471+ match = re.search(self.pattern, line)
4472+ if not match:
4473+ continue
4474+ data = match.groupdict()
4475+ data["log_filename"] = relative_pathname or pathname
4476+ data["log_lineno"] = lineno
4477+ self._results['test_results'].append(
4478+ self.analyze_test_result(data))
4479+ return self.results
4480+
4481+ @property
4482+ def badchars(self):
4483+ return "[^a-zA-Z0-9\._-]"
4484+
4485+ def analyze_test_result(self, data):
4486+ """
4487+ Analyze sigle match (typically single line) and convert it into a
4488+ proper test result object.
4489+
4490+ Currently this method does the following transformations:
4491+ * measurement is converted to decimal if present
4492+ * test_case_id is rewritten to strip badchars
4493+ * test_case_id is rewritten to convert spaces to underscores
4494+ * result is transformed using fixuptdict, if defined
4495+ * appendall information is added, if defined
4496+ """
4497+ if 'measurement' in data:
4498+ try:
4499+ data['measurement'] = decimal.Decimal(data['measurement'])
4500+ except decimal.InvalidOperation:
4501+ del data['measurement']
4502+ if 'test_case_id' in data:
4503+ data['test_case_id'] = re.sub(self.badchars, "",
4504+ data['test_case_id'])
4505+ data['test_case_id'] = data['test_case_id'].replace(" ", "_")
4506+ if 'result' in data and self.fixupdict:
4507+ data['result'] = self.fixupdict[data['result']]
4508+ if self.appendall:
4509+ data.update(self.appendall)
4510+ return data
4511+
4512+
4513+class NativeTestParser(ITestParser):
4514+ """
4515+ Unfinished native test parser.
4516+
4517+ This was meant to be a pass-through for tests that directly create bundles
4518+ """
4519+ def __init__(self, test_def):
4520+ self.test_def = test_def
4521+
4522+ def parse(self, artifacts):
4523+ raise NotImplementedError()
4524+
4525+ def results(self):
4526+ raise NotImplementedError()
4527
4528=== added file 'lava_test/core/providers.py'
4529--- lava_test/core/providers.py 1970-01-01 00:00:00 +0000
4530+++ lava_test/core/providers.py 2011-09-13 22:44:36 +0000
4531@@ -0,0 +1,165 @@
4532+# Copyright (c) 2010, 2011 Linaro
4533+#
4534+# This program is free software: you can redistribute it and/or modify
4535+# it under the terms of the GNU General Public License as published by
4536+# the Free Software Foundation, either version 3 of the License, or
4537+# (at your option) any later version.
4538+#
4539+# This program is distributed in the hope that it will be useful,
4540+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4541+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4542+# GNU General Public License for more details.
4543+#
4544+# You should have received a copy of the GNU General Public License
4545+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4546+
4547+from lava_test.api.core import ITestProvider
4548+from lava_test.core.config import get_config
4549+from lava_test.core.tests import DeclarativeTest
4550+from lava_test.utils import Cache
4551+
4552+
4553+class BuiltInProvider(ITestProvider):
4554+ """
4555+ Test provider that provides tests shipped in the Lava-Test source tree
4556+ """
4557+
4558+ _builtin_tests = [
4559+ 'glmemperf',
4560+ 'gmpbench',
4561+ 'gtkperf',
4562+ 'ltp',
4563+ 'posixtestsuite',
4564+ 'pwrmgmt',
4565+ 'stream',
4566+ 'tiobench',
4567+ 'x11perf',
4568+ ]
4569+
4570+ def __init__(self, config):
4571+ pass
4572+
4573+ @property
4574+ def description(self):
4575+ return "Tests built directly into LAVA Test:"
4576+
4577+ def __iter__(self):
4578+ return iter(self._builtin_tests)
4579+
4580+ def __getitem__(self, test_id):
4581+ if test_id not in self._builtin_tests:
4582+ raise KeyError(test_id)
4583+ module = __import__("lava_test.test_definitions.%s" % test_id,
4584+ fromlist=[''])
4585+ return module.testobj
4586+
4587+
4588+class PkgResourcesProvider(ITestProvider):
4589+ """
4590+ Test provider that provides tests declared in pkg_resources working_set
4591+
4592+ By default it looks at the 'lava_test.test_definitions' name space but it can
4593+ be changed with custom 'namespace' configuration entry.
4594+ """
4595+
4596+ def __init__(self, config):
4597+ self._config = config
4598+
4599+ @property
4600+ def namespace(self):
4601+ return self._config.get("namespace", "lava_test.test_definitions")
4602+
4603+ @property
4604+ def description(self):
4605+ return ("Tests provided by installed python packages"
4606+ " (from namespace {0}):").format(self.namespace)
4607+
4608+ def __iter__(self):
4609+ from pkg_resources import working_set
4610+ for entry_point in working_set.iter_entry_points(self.namespace):
4611+ yield entry_point.name
4612+
4613+ def __getitem__(self, test_name):
4614+ from pkg_resources import working_set
4615+ for entry_point in working_set.iter_entry_points(self.namespace,
4616+ test_name):
4617+ return entry_point.load().testobj
4618+ raise KeyError(test_name)
4619+
4620+
4621+class RegistryProvider(ITestProvider):
4622+ """
4623+ Test provider that provides declarative tests listed in the test registry.
4624+ """
4625+ def __init__(self, config):
4626+ self._config = config
4627+ self._cache = None
4628+
4629+ @property
4630+ def entries(self):
4631+ """
4632+ List of URLs to DeclarativeTest description files
4633+ """
4634+ return self._config.get("entries", [])
4635+
4636+ @property
4637+ def description(self):
4638+ return "Tests provided by LAVA Test registry:"
4639+
4640+ @classmethod
4641+ def register_remote_test(self, test_url):
4642+ config = get_config() # This is a different config object from
4643+ # self._config
4644+ provider_config = config.get_provider_config(
4645+ "lava_test.core.providers:RegistryProvider")
4646+ if "entries" not in provider_config:
4647+ provider_config["entries"] = []
4648+ if test_url not in provider_config["entries"]:
4649+ provider_config["entries"].append(test_url)
4650+ config._save_registry()
4651+ else:
4652+ raise ValueError("This test is already registered")
4653+
4654+ @classmethod
4655+ def unregister_remote_test(self, test_url):
4656+ config = get_config() # This is a different config object from
4657+ # self._config
4658+ provider_config = config.get_provider_config(
4659+ "lava_test.core.providers:RegistryProvider")
4660+ if "entries" not in provider_config:
4661+ provider_config["entries"] = []
4662+ if test_url in provider_config["entries"]:
4663+ provider_config["entries"].remove(test_url)
4664+ config._save_registry()
4665+ else:
4666+ raise ValueError("This test is not registered")
4667+
4668+ def _load_remote_test(self, test_url):
4669+ """
4670+ Load DeclarativeTest from a (possibly cached copy of) test_url
4671+ """
4672+ cache = Cache.get_instance()
4673+ with cache.open_cached_url(test_url) as stream:
4674+ return DeclarativeTest.load_from_stream(stream)
4675+
4676+ def _fill_cache(self):
4677+ """
4678+ Fill the cache of all remote tests
4679+ """
4680+ if self._cache is not None:
4681+ return
4682+ self._cache = {}
4683+ for test_url in self.entries:
4684+ test = self._load_remote_test(test_url)
4685+ if test.test_id in self._cache:
4686+ raise ValueError("Duplicate test %s declared" % test.test_id)
4687+ self._cache[test.test_id] = test
4688+
4689+ def __iter__(self):
4690+ self._fill_cache()
4691+ for test_id in self._cache.iterkeys():
4692+ yield test_id
4693+
4694+ def __getitem__(self, test_id):
4695+ self._fill_cache()
4696+ return self._cache[test_id]
4697
4698=== added file 'lava_test/core/runners.py'
4699--- lava_test/core/runners.py 1970-01-01 00:00:00 +0000
4700+++ lava_test/core/runners.py 2011-09-13 22:44:36 +0000
4701@@ -0,0 +1,66 @@
4702+# Copyright (c) 2010, 2011 Linaro
4703+#
4704+# This program is free software: you can redistribute it and/or modify
4705+# it under the terms of the GNU General Public License as published by
4706+# the Free Software Foundation, either version 3 of the License, or
4707+# (at your option) any later version.
4708+#
4709+# This program is distributed in the hope that it will be useful,
4710+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4711+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4712+# GNU General Public License for more details.
4713+#
4714+# You should have received a copy of the GNU General Public License
4715+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4716+
4717+import datetime
4718+
4719+from lava_test.api.delegates import ITestRunner
4720+from lava_test.extcmd import (DisplayDelegate, ExternalCommandWithDelegate)
4721+
4722+
4723+class TestRunner(ITestRunner):
4724+ """
4725+ Base class for defining an test runner object.
4726+
4727+ This class can be used as-is for simple execution with the expectation that
4728+ the run() method will be called from the directory where the test was
4729+ installed. Steps, if used, should handle changing directories from there to
4730+ the directory where the test was extracted if necessary. This class can
4731+ also be extended for more advanced functionality.
4732+
4733+ :ivar steps:
4734+ list of shell commands to execute
4735+ """
4736+ def __init__(self, steps=None):
4737+ self.steps = steps or []
4738+ self.testoutput = [] # XXX: is this still used?
4739+
4740+ def __repr__(self):
4741+ return "<%s steps=%r>" % (self.__class__.__name__, self.steps)
4742+
4743+ def _run_lava_test_steps(self, artifacts, observer):
4744+ stdout = open(artifacts.stdout_pathname, 'at')
4745+ stderr = open(artifacts.stderr_pathname, 'at')
4746+ delegate = DisplayDelegate(stdout, stderr, observer)
4747+ extcmd = ExternalCommandWithDelegate(delegate)
4748+ try:
4749+ for cmd in self.steps:
4750+ if observer: observer.about_to_run_shell_command(cmd)
4751+ returncode = extcmd.call(cmd, shell=True)
4752+ if observer: observer.did_run_shell_command(cmd, returncode)
4753+ finally:
4754+ stdout.close()
4755+ stderr.close()
4756+
4757+ def run(self, artifacts, observer=None):
4758+ """
4759+ Run the test program by executing steps in sequence.
4760+
4761+ .. seealso::
4762+
4763+ :meth:`~lava_test.api.delegates.TestRunner.run`
4764+ """
4765+ self.starttime = datetime.datetime.utcnow()
4766+ self._run_lava_test_steps(artifacts, observer)
4767+ self.endtime = datetime.datetime.utcnow()
4768
4769=== added file 'lava_test/core/swprofile.py'
4770--- lava_test/core/swprofile.py 1970-01-01 00:00:00 +0000
4771+++ lava_test/core/swprofile.py 2011-09-13 22:44:36 +0000
4772@@ -0,0 +1,72 @@
4773+# Copyright (c) 2010, 2011 Linaro
4774+#
4775+# This program is free software: you can redistribute it and/or modify
4776+# it under the terms of the GNU General Public License as published by
4777+# the Free Software Foundation, either version 3 of the License, or
4778+# (at your option) any later version.
4779+#
4780+# This program is distributed in the hope that it will be useful,
4781+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4782+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4783+# GNU General Public License for more details.
4784+#
4785+# You should have received a copy of the GNU General Public License
4786+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4787+
4788+import apt
4789+
4790+from lava_test.utils import read_file
4791+
4792+
4793+def get_packages(apt_cache=None):
4794+ """ Get information about the packages installed
4795+
4796+ apt_cache - if not provided, this will be read from the system
4797+ """
4798+ if apt_cache == None:
4799+ apt_cache = apt.Cache()
4800+ packages = []
4801+ for apt_pkg in apt_cache:
4802+ if hasattr(apt_pkg, 'is_installed'):
4803+ is_installed = apt_pkg.is_installed
4804+ else:
4805+ is_installed = apt_pkg.isInstalled # old style API
4806+ if is_installed:
4807+ pkg = {
4808+ "name": apt_pkg.name,
4809+ "version": apt_pkg.installed.version}
4810+ packages.append(pkg)
4811+ return packages
4812+
4813+
4814+def get_software_context(apt_cache=None, lsb_information=None):
4815+ """ Return dict used for storing software_context information
4816+
4817+ test_id - Unique identifier for this test
4818+ time_check - whether or not a check was performed to see if
4819+ the time on the system was synced with a time server
4820+ apt_cache - if not provided, this will be read from the system
4821+ lsb_information - if not provided, this will be read from the system
4822+ """
4823+ software_context = {}
4824+ software_context['image'] = get_image(lsb_information)
4825+ software_context['packages'] = get_packages(apt_cache)
4826+ return software_context
4827+
4828+
4829+def get_image(lsb_information=None):
4830+ """ Get information about the image we are running
4831+
4832+ If /etc/buildstamp exists, get the image id from that. Otherwise
4833+ just use the lsb-release description for a rough idea.
4834+ """
4835+ try:
4836+ buildstamp = read_file("/etc/buildstamp")
4837+ name = buildstamp.splitlines()[1]
4838+ except IOError:
4839+ import lsb_release
4840+
4841+ if lsb_information == None:
4842+ lsb_information = lsb_release.get_distro_information()
4843+ name = lsb_information['DESCRIPTION']
4844+ return {"name": name}
4845
4846=== added file 'lava_test/core/tests.py'
4847--- lava_test/core/tests.py 1970-01-01 00:00:00 +0000
4848+++ lava_test/core/tests.py 2011-09-13 22:44:36 +0000
4849@@ -0,0 +1,166 @@
4850+# Copyright (c) 2010, 2011 Linaro
4851+#
4852+# This program is free software: you can redistribute it and/or modify
4853+# it under the terms of the GNU General Public License as published by
4854+# the Free Software Foundation, either version 3 of the License, or
4855+# (at your option) any later version.
4856+#
4857+# This program is distributed in the hope that it will be useful,
4858+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4859+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4860+# GNU General Public License for more details.
4861+#
4862+# You should have received a copy of the GNU General Public License
4863+# along with this program. If not, see <http://www.gnu.org/licenses/>.
4864+
4865+from __future__ import absolute_import
4866+
4867+import json
4868+import logging
4869+import os
4870+import shutil
4871+
4872+from lava_test.api.core import ITest
4873+from lava_test.core.artifacts import TestArtifacts
4874+from lava_test.core.config import get_config
4875+from lava_test.core.installers import TestInstaller
4876+from lava_test.core.parsers import TestParser, NativeTestParser
4877+from lava_test.core.runners import TestRunner
4878+from lava_test.utils import changed_directory
4879+
4880+
4881+class Test(ITest):
4882+ """
4883+ Reusable class for defining tests.
4884+
4885+ This class uses composition instead of inheritance. You should be able to
4886+ customize the parts you care about by providing delegate objects. This
4887+ class can be used by test definition files to create an object that
4888+ contains the building blocks for installing tests, running them, and
4889+ parsing the results.
4890+
4891+ :ivar test_id:
4892+ Name of the test or test suite
4893+ :ivar test_version:
4894+ Version of the test or test suite
4895+ :ivar installer:
4896+ ITestInstaller instance to use
4897+ :ivar runner:
4898+ ITestRunner instance to use
4899+ :ivar parser:
4900+ ITestParser instance to use
4901+ """
4902+
4903+ def __init__(self, test_id, test_version=None,
4904+ installer=None, runner=None, parser=None):
4905+ self._test_id = test_id
4906+ self._test_version = test_version
4907+ # Delegate objects
4908+ self.installer = installer
4909+ self.runner = runner
4910+ self.parser = parser
4911+ # Config instance
4912+ self._config = get_config()
4913+
4914+ def __repr__(self):
4915+ return ("<%s test_id=%r test_version=%r installer=%r runner=%r"
4916+ " parser=%r>") % (
4917+ self.__class__.__name__, self.test_id, self.test_version,
4918+ self.installer, self.runner, self.parser)
4919+
4920+ @property
4921+ def test_id(self):
4922+ """
4923+ Return the ID of the test.
4924+ """
4925+ return self._test_id
4926+
4927+ @property
4928+ def test_version(self):
4929+ """
4930+ Return the version of the test
4931+ """
4932+ return self._test_version
4933+
4934+ @property
4935+ def install_dir(self):
4936+ """
4937+ Pathname of a directory with binary and data files installed by the
4938+ test.
4939+
4940+ .. versionadded:: 0.2
4941+ """
4942+ return os.path.join(self._config.installdir, self.test_id)
4943+
4944+ @property
4945+ def is_installed(self):
4946+ return os.path.exists(self.install_dir)
4947+
4948+ def install(self, observer=None):
4949+ if self.is_installed:
4950+ raise RuntimeError(
4951+ "%s is already installed" % self.test_id)
4952+ if not self.installer:
4953+ raise RuntimeError(
4954+ "no installer defined for '%s'" % self.test_id)
4955+ with changed_directory(self.install_dir):
4956+ try:
4957+ logging.debug(
4958+ "Invoking %r.install(...)", self.installer)
4959+ self.installer.install(observer)
4960+ except:
4961+ self.uninstall()
4962+ raise
4963+
4964+ def uninstall(self):
4965+ logging.debug("Removing test %r", self.test_id)
4966+ if os.path.exists(self.install_dir):
4967+ shutil.rmtree(self.install_dir)
4968+
4969+ def run(self, observer=None):
4970+ if not self.runner:
4971+ raise RuntimeError(
4972+ "no test runner defined for '%s'" % self.test_id)
4973+ artifacts = TestArtifacts.allocate(self.test_id, self._config)
4974+ with changed_directory(self.install_dir):
4975+ logging.debug(
4976+ "Invoking %r.run_and_store_artifacts(...)",
4977+ self.runner, observer)
4978+ self.runner.run(artifacts, observer)
4979+ return artifacts
4980+
4981+ def parse(self, artifacts):
4982+ if self.parser:
4983+ logging.debug("Invoking %r.parse()", self.parser)
4984+ with changed_directory(artifacts.results_dir, False):
4985+ self.parser.parse(artifacts)
4986+ return self.parser.results
4987+
4988+
4989+class DeclarativeTest(Test):
4990+ """
4991+ Declaretive ITest implementation.
4992+
4993+ Declarative test is like :class:`lava_test.core.tests.Test` but cannot
4994+ contain any python code and is completely encapsulated in a .json file.
4995+
4996+ The idea is to write .json files that assemble a Test instance using
4997+ readily-available TestInstaller, TestRunner and TestParser subclasses.
4998+ """
4999+
5000+ def __init__(self, about):
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches