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
=== modified file '.bzrignore'
--- .bzrignore 2011-06-21 19:38:29 +0000
+++ .bzrignore 2011-09-13 22:44:36 +0000
@@ -5,3 +5,4 @@
5*~5*~
6*.tmp6*.tmp
7*.py[co]7*.py[co]
8build
89
=== modified file 'MANIFEST.in'
--- MANIFEST.in 2011-06-21 19:38:29 +0000
+++ MANIFEST.in 2011-09-13 22:44:36 +0000
@@ -1,4 +1,3 @@
1include COPYING1include COPYING
2include README2include README
3include .testr.conf3include .testr.conf
4include bin/lava-test
5\ No newline at end of file4\ No newline at end of file
65
=== modified file 'README'
--- README 2011-08-03 14:11:26 +0000
+++ README 2011-09-13 22:44:36 +0000
@@ -3,20 +3,11 @@
3automatically installed, executed, and the results can be parsed and3automatically installed, executed, and the results can be parsed and
4uploaded to an external server.4uploaded to an external server.
55
6External dependency
7-------------------
8The following debian packages are needed:
9* python-setuptools
10* python-apt
11* usbutils
12* python-testrepository - for running unit tests
13
14How to install from the source code6How to install from the source code
15===================================7===================================
168
171. Run: ./setup.py install91. Run: ./setup.py install
1810
19
20How to setup from the source code for development11How to setup from the source code for development
21=================================================12=================================================
2213
@@ -32,7 +23,7 @@
323. Add <home>/.local.bin in your PATH233. Add <home>/.local.bin in your PATH
3324
3425
35To install build-in tests26To install built-in tests
36=========================27=========================
371. Run: lava-test list-tests281. Run: lava-test list-tests
382. Run: lava-test install <test>292. Run: lava-test install <test>
@@ -47,4 +38,5 @@
47To install test define with a json file38To install test define with a json file
48=======================================39=======================================
491. Run: lava-test register-test file://localhost/<..>/examples/stream.json401. Run: lava-test register-test file://localhost/<..>/examples/stream.json
502. Run: lava-test list-tests
51\ No newline at end of file41\ No newline at end of file
422. Run: lava-test list-tests
43
5244
=== removed file 'abrek/api.py'
--- abrek/api.py 2011-06-28 12:51:57 +0000
+++ abrek/api.py 1970-01-01 00:00:00 +0000
@@ -1,76 +0,0 @@
1"""
2Public API for extending Abrek
3"""
4from abc import abstractmethod, abstractproperty
5
6class ITestProvider(object):
7 """
8 Abrek test provider.
9
10 Abstract source of abrek tests.
11 """
12
13 @abstractmethod
14 def __init__(self, config):
15 """
16 Initialize test provider with the specified configuration object. The
17 configuration object is obtained from the abrek providers registry.
18 """
19
20 @abstractmethod
21 def __iter__(self):
22 """
23 Iterates over instances of ITest exposed by this provider
24 """
25
26 @abstractmethod
27 def __getitem__(self, test_name):
28 """
29 Return an instance of ITest with the specified name
30 """
31
32 @abstractproperty
33 def description(self):
34 """
35 The description string used by abrek list-tests
36 """
37
38
39class ITest(object):
40 """
41 Abrek test.
42
43 Something that can be installed and invoked by abre.
44 """
45
46 @abstractmethod
47 def install(self):
48 """
49 Install the test suite.
50
51 This creates an install directory under the user's XDG_DATA_HOME
52 directory to mark that the test is installed. The installer's
53 install() method is then called from this directory to complete any
54 test specific install that may be needed.
55 """
56
57 @abstractmethod
58 def uninstall(self):
59 """
60 Uninstall the test suite.
61
62 Uninstalling just recursively removes the test specific directory under
63 the user's XDG_DATA_HOME directory. This will both mark the test as
64 removed, and clean up any files that were downloaded or installed under
65 that directory. Dependencies are intentionally not removed by this.
66 """
67
68 @abstractmethod
69 def run(self, quiet=False):
70 # TODO: Document me
71 pass
72
73 @abstractmethod
74 def parse(self, resultname):
75 # TODO: Document me
76 pass
770
=== removed file 'abrek/builtins.py'
--- abrek/builtins.py 2011-08-03 14:25:13 +0000
+++ abrek/builtins.py 1970-01-01 00:00:00 +0000
@@ -1,175 +0,0 @@
1# Copyright (c) 2010 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import os
17import sys
18from optparse import make_option
19
20import abrek.command
21import abrek.testdef
22from abrek.config import get_config
23
24
25class cmd_version(abrek.command.AbrekCmd):
26 """
27 Show the version of abrek
28 """
29 def run(self):
30 import abrek
31 print abrek.__version__
32
33
34class cmd_help(abrek.command.AbrekCmd):
35 """ Get help on abrek commands
36
37 If the command name is ommited, calling the help command will return a
38 list of valid commands.
39 """
40 arglist = ['command', 'subcommand']
41 def run(self):
42 if len(self.args) < 1:
43 print "Available commands:"
44 for cmd in abrek.command.get_all_cmds():
45 print " %s" % cmd
46 print
47 print "To access extended help on a command use 'abrek help " \
48 "[command]'"
49 return
50 command_name = self.args.pop(0)
51 cmd = abrek.command.get_command(command_name)
52 if not cmd:
53 print "No command found for '%s'" % command_name
54 return
55 while self.args:
56 subcommand_name = self.args.pop(0)
57 cmd = cmd.get_subcommand(subcommand_name)
58 if not cmd:
59 print "No sub-command of '%s' found for '%s'" % (
60 command_name, subcommand_name)
61 return
62 command_name += ' ' + subcommand_name
63 print cmd.help()
64
65
66class cmd_install(abrek.command.AbrekCmd):
67 """
68 Install a test
69 """
70 arglist = ['*testname']
71
72 def run(self):
73 self.checkroot()
74 if len(self.args) != 1:
75 print "please specify the name of the test to install"
76 sys.exit(1)
77 test = abrek.testdef.testloader(self.args[0])
78 try:
79 test.install()
80 except RuntimeError as strerror:
81 print "Test installation error: %s" % strerror
82 sys.exit(1)
83
84
85class cmd_run(abrek.command.AbrekCmd):
86 """
87 Run tests
88 """
89 arglist = ['*testname']
90 options = [make_option('-q', '--quiet', action='store_true',
91 default=False, dest='quiet'),
92 make_option('-o', '--output', action='store',
93 default=None, metavar="FILE",
94 help="Store processed test output to FILE")]
95
96 def run(self):
97 self.checkroot()
98 if len(self.args) != 1:
99 print "please specify the name of the test to run"
100 sys.exit(1)
101 test = abrek.testdef.testloader(self.args[0])
102 try:
103 result_id = test.run(quiet=self.opts.quiet)
104 if self.opts.output:
105 from abrek.dashboard import generate_bundle
106 import json
107 bundle = generate_bundle(result_id)
108 with open(self.opts.output, "wt") as stream:
109 json.dump(bundle, stream)
110 except Exception as strerror:
111 print "Test execution error: %s" % strerror
112 sys.exit(1)
113
114
115class cmd_uninstall(abrek.command.AbrekCmd):
116 """
117 Uninstall a test
118 """
119 arglist = ['*testname']
120
121 def run(self):
122 if len(self.args) != 1:
123 print "please specify the name of the test to uninstall"
124 sys.exit(1)
125 test = abrek.testdef.testloader(self.args[0])
126 try:
127 test.uninstall()
128 except Exception as strerror:
129 print "Test uninstall error: %s" % strerror
130 sys.exit(1)
131
132
133class cmd_list_installed(abrek.command.AbrekCmd):
134 """
135 List tests that are currently installed
136 """
137 def run(self):
138 config = get_config()
139 print "Installed tests:"
140 try:
141 for dir in os.listdir(config.installdir):
142 print dir
143 except OSError:
144 print "No tests installed"
145
146
147class cmd_list_tests(abrek.command.AbrekCmd):
148 """
149 List all known tests
150 """
151 def run(self):
152 from abrek.testdef import TestLoader
153 for provider in TestLoader().get_providers():
154 print provider.description
155 for test in provider:
156 print " - %s" % test
157
158
159class cmd_register_test(abrek.command.AbrekCmd):
160 """
161 Register declarative tests
162 """
163
164 arglist = ['test_url']
165
166 def run(self):
167 if len(self.args) != 1:
168 self.parser.error("You need to provide an URL to a test definition file")
169 test_url = self.args[0]
170 from abrek.providers import RegistryProvider
171 try:
172 RegistryProvider.register_remote_test(test_url)
173 except ValueError as exc:
174 print "Unable to register test: %s" % exc
175 sys.exit(1)
1760
=== removed file 'abrek/bundle.py'
--- abrek/bundle.py 2011-04-19 16:56:01 +0000
+++ abrek/bundle.py 1970-01-01 00:00:00 +0000
@@ -1,44 +0,0 @@
1# Copyright (c) 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16"""
17This module attempts to use the linaro-dashboard-bundle package, if possible.
18
19Using that package adds proper support for loading and saving bundle
20documents. In particular it supports loosles decimals, better, more stable
21load-modify-write cycles, data validation, transparent migration and many
22other features.
23
24It is not a hard dependency to make it possible to run abrek from a checkout
25without having to install (too many) dependencies.
26"""
27
28try:
29 from linaro_dashboard_bundle import DocumentIO
30except ImportError:
31 import json
32
33 class DocumentIO(object):
34 """ Bare replacement DocumentIO without any fancy features """
35
36 @classmethod
37 def dumps(cls, doc):
38 return json.dumps(doc, indent=2)
39
40 @classmethod
41 def loads(cls, text):
42 doc = json.loads(text)
43 fmt = doc.get("format")
44 return fmt, doc
45\ No newline at end of file0\ No newline at end of file
461
=== removed file 'abrek/cache.py'
--- abrek/cache.py 2011-08-16 19:18:30 +0000
+++ abrek/cache.py 1970-01-01 00:00:00 +0000
@@ -1,73 +0,0 @@
1"""
2Cache module for Abrek
3"""
4import contextlib
5import hashlib
6import os
7import urllib2
8
9
10class AbrekCache(object):
11 """
12 Cache class for Abrek
13 """
14
15 _instance = None
16
17 def __init__(self):
18 home = os.environ.get('HOME', '/')
19 basecache = os.environ.get('XDG_CACHE_HOME',
20 os.path.join(home, '.cache'))
21 self.cache_dir = os.path.join(basecache, 'abrek')
22
23 @classmethod
24 def get_instance(cls):
25 if cls._instance is None:
26 cls._instance = cls()
27 return cls._instance
28
29 def open_cached(self, key, mode="r"):
30 """
31 Acts like open() but the pathname is relative to the
32 abrek-specific cache directory.
33 """
34 if "w" in mode and not os.path.exists(self.cache_dir):
35 os.makedirs(self.cache_dir)
36 if os.path.isabs(key):
37 raise ValueError("key cannot be an absolute path")
38 try:
39 stream = open(os.path.join(self.cache_dir, key), mode)
40 yield stream
41 finally:
42 stream.close()
43
44 def _key_for_url(self, url):
45 return hashlib.sha1(url).hexdigest()
46
47 def _refresh_url_cache(self, key, url):
48 with contextlib.nested(
49 contextlib.closing(urllib2.urlopen(url)),
50 self.open_cached(key, "wb")) as (in_stream, out_stream):
51 out_stream.write(in_stream.read())
52
53 @contextlib.contextmanager
54 def open_cached_url(self, url):
55 """
56 Like urlopen.open() but the content may be cached.
57 """
58 # Do not cache local files, this is not what users would expect
59
60 # workaround - not using cache at all.
61 # TODO: fix this and use the cache
62 # if url.startswith("file://"):
63 if True:
64 stream = urllib2.urlopen(url)
65 else:
66 key = self._key_for_url(url)
67 try:
68 stream = self.open_cached(key, "rb")
69 except IOError as exc:
70 self._refresh_url_cache(key, url)
71 stream = self.open_cached(key, "rb")
72 yield stream
73 stream.close()
740
=== removed file 'abrek/command.py'
--- abrek/command.py 2011-08-03 14:25:13 +0000
+++ abrek/command.py 1970-01-01 00:00:00 +0000
@@ -1,171 +0,0 @@
1# Copyright (c) 2010 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from optparse import OptionParser
17import os
18import sys
19
20
21class _AbrekOptionParser(OptionParser):
22 """
23 This is just to override the epilog formatter to allow newlines
24 """
25 def format_epilog(self, formatter):
26 return self.epilog
27
28
29class AbrekCmd(object):
30 """ Base class for commands that can be passed to Abrek.
31
32 Commands added to abrek should inherit from AbrekCmd. To allow for
33 autodiscovery, the name of the class should begin with cmd_.
34
35 Arguments allowed by the command can be specified in the 'arglist'.
36 These arguments will automatically be listed in the help for that
37 command. Required arguments should begin with a '*'. For example:
38 arglist = ['*requiredarg', 'optionalarg']
39
40 Options may also be specified by using the 'options' list. To add
41 arguments, you must use the make_option() function from optparse.
42 For example:
43 options = [make_option("-b", "--bar", dest="bar")]
44
45 Commands also support subcommands. A subcommand is similar to a
46 command in abrek, and it should also inherit from AbrekCmd. However,
47 a subcommand class should not begin with cmd_. Instead, it should
48 be tied to the command that uses it, using the 'subcmds' dict.
49 For example:
50 class subcmd_bar(AbrekCmd):
51 pass
52 class cmd_foo(AbrekCmd):
53 subcmds = {'bar':subcmd_bar()}
54 pass
55 """
56 options = []
57 arglist = []
58
59 def __init__(self, name_prefix=''):
60 self._name_prefix = name_prefix
61 self.parser = _AbrekOptionParser(usage=self._usage(),
62 epilog=self._desc())
63 for opt in self.options:
64 self.parser.add_option(opt)
65
66 def main(self, argv):
67 (self.opts, self.args) = self.parser.parse_args(argv)
68 return self.run()
69
70 def name(self):
71 return self._name_prefix + _convert_command_name(self.__class__.__name__)
72
73 def run(self):
74 raise NotImplementedError("%s: command defined but not implemented!" %
75 self.name())
76
77 def _usage(self):
78 usagestr = "Usage: lava-test %s" % self.name()
79 for arg in self.arglist:
80 if arg[0] == '*':
81 usagestr += " %s" % arg[1:].upper()
82 else:
83 usagestr += " [%s]" % arg.upper()
84 return usagestr
85
86 def _desc(self):
87 from inspect import getdoc
88 docstr = getdoc(self)
89 if not docstr:
90 return ""
91 description = "\nDescription:\n"
92 description += docstr + "\n"
93 return description
94
95 def help(self):
96 #For some reason, format_help includes an extra \n
97 return self.parser.format_help()[:-1]
98
99 def get_subcommand(self, name):
100 return None
101
102 def checkroot(self):
103 if os.getuid() != 0:
104 print >> sys.stderr, ("**** WARNING: ROOT PERMISSIONS ARE OFTEN"
105 "REQUIRED FOR THIS OPERATION ****")
106
107
108class AbrekCmdWithSubcommands(AbrekCmd):
109
110 arglist = ['subcommand']
111
112 def main(self, argv):
113 if not argv:
114 print "Missing sub-command." + self._list_subcmds()
115 else:
116 subcmd = self.get_subcommand(argv[0])
117 if subcmd is None:
118 # This line might print the help and raise SystemExit if
119 # --help is passed or if an invalid option was passed.
120 opts, args = self.parser.parse_args(argv)
121 # If it didn't, complain.
122 print "'%s' not found as a sub-command of '%s'" % (
123 args[0], self.name()) + self._list_subcmds()
124 else:
125 return subcmd.main(argv[1:])
126
127 def get_subcommand(self, name):
128 subcmd_cls = getattr(self, 'cmd_' + name.replace('_', '-'), None)
129 if subcmd_cls is None:
130 return None
131 return subcmd_cls(self.name() + ' ')
132
133 def _usage(self):
134 usagestr = AbrekCmd._usage(self)
135 usagestr += self._list_subcmds()
136 return usagestr
137
138 def _list_subcmds(self):
139 subcmds = []
140 for attrname in self.__class__.__dict__.keys():
141 if attrname.startswith('cmd_'):
142 subcmds.append(_convert_command_name(attrname))
143 if not subcmds:
144 return ''
145 return "\n\nAvailable sub-commands:\n " + "\n ".join(subcmds)
146
147
148def _convert_command_name(cmd):
149 return cmd[4:].replace('_','-')
150
151
152def _find_commands(module):
153 cmds = {}
154 for name, func in module.__dict__.iteritems():
155 if name.startswith("cmd_"):
156 real_name = _convert_command_name(name)
157 cmds[real_name] = func()
158 return cmds
159
160
161def get_all_cmds():
162 from abrek import builtins, dashboard, results
163 cmds = _find_commands(builtins)
164 cmds.update(_find_commands(dashboard))
165 cmds.update(_find_commands(results))
166 return cmds
167
168
169def get_command(cmd_name):
170 cmds = get_all_cmds()
171 return cmds.get(cmd_name)
1720
=== removed file 'abrek/config.py'
--- abrek/config.py 2011-08-03 09:06:20 +0000
+++ abrek/config.py 1970-01-01 00:00:00 +0000
@@ -1,93 +0,0 @@
1# Copyright (c) 2010 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import os
17import json
18
19
20class AbrekConfig(object):
21
22 def __init__(self):
23 home = os.environ.get('HOME', '/')
24 baseconfig = os.environ.get('XDG_CONFIG_HOME',
25 os.path.join(home, '.config'))
26 basedata = os.environ.get('XDG_DATA_HOME',
27 os.path.join(home, '.local', 'share'))
28 self.configdir = os.path.join(baseconfig, 'abrek')
29 self.installdir = os.path.join(basedata, 'abrek', 'installed-tests')
30 self.resultsdir = os.path.join(basedata, 'abrek', 'results')
31 self.registry = self._load_registry()
32
33 @property
34 def _registry_pathname(self):
35 return os.path.join(self.configdir, "registry.json")
36
37 def _load_registry(self):
38 try:
39 with open(self._registry_pathname) as stream:
40 return json.load(stream)
41 except (IOError, ValueError) as exc:
42 return self._get_default_registry()
43
44 def _save_registry(self):
45 if not os.path.exists(self.configdir):
46 os.makedirs(self.configdir)
47 with open(self._registry_pathname, "wt") as stream:
48 json.dump(self.registry, stream, indent=2)
49
50 def _get_default_registry(self):
51 return {
52 "format": "Abrek Test Registry 1.0 Experimental",
53 "providers": [
54 {
55 "entry_point": "abrek.providers:BuiltInProvider",
56 },
57 {
58 "entry_point": "abrek.providers:PkgResourcesProvider",
59 },
60 {
61 "entry_point": "abrek.providers:RegistryProvider",
62 "config": {
63 "entries": []
64 }
65 }
66 ]
67 }
68
69 def get_provider_config(self, entry_point_name):
70 if "providers" not in self.registry:
71 self.registry["providers"] = []
72 for provider_info in self.registry["providers"]:
73 if provider_info.get("entry_point") == entry_point_name:
74 break
75 else:
76 provider_info = {"entry_point": entry_point_name}
77 self.registry["providers"].append(provider_info)
78 if "config" not in provider_info:
79 provider_info["config"] = {}
80 return provider_info["config"]
81
82
83_config = None
84
85def get_config():
86 global _config
87 if _config is not None:
88 return _config
89 return AbrekConfig()
90
91def set_config(config):
92 global _config
93 _config = config
940
=== removed file 'abrek/dashboard.py'
--- abrek/dashboard.py 2011-08-09 09:35:19 +0000
+++ abrek/dashboard.py 1970-01-01 00:00:00 +0000
@@ -1,206 +0,0 @@
1# Copyright (c) 2010 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import base64
17import os
18import socket
19import sys
20import urllib
21import xmlrpclib
22from ConfigParser import ConfigParser, NoOptionError
23from getpass import getpass
24from optparse import make_option
25
26from abrek.bundle import DocumentIO
27from abrek.command import AbrekCmd, AbrekCmdWithSubcommands
28from abrek.config import get_config
29from abrek.testdef import testloader
30
31
32class DashboardConfig(object):
33 """
34 Read and write dashboard configuration data
35 """
36
37 def __init__(self, section="Default Server"):
38 self.dashboardconf = ConfigParser()
39 self.section = section
40 self.config = get_config()
41 self.path = os.path.join(self.config.configdir, "dashboard.conf")
42 if os.path.exists(self.path):
43 self.dashboardconf.read(self.path)
44 if not (self.section in self.dashboardconf.sections()):
45 self.dashboardconf.add_section(self.section)
46
47 def set_host(self, host):
48 self.dashboardconf.set(self.section, 'host', host)
49
50 def get_host(self):
51 try:
52 host = self.dashboardconf.get(self.section, 'host')
53 return host
54 except NoOptionError:
55 return ""
56
57 host = property(get_host, set_host)
58
59 def set_user(self, user):
60 self.dashboardconf.set(self.section, 'user', user)
61
62 def get_user(self):
63 try:
64 user = self.dashboardconf.get(self.section, 'user')
65 return user
66 except NoOptionError:
67 return ""
68
69 user = property(get_user, set_user)
70
71 def set_password(self, password):
72 #Not exactly secure, but better than storing in plaintext
73 password = base64.encodestring(password).strip()
74 self.dashboardconf.set(self.section,'password', password)
75
76 def get_password(self):
77 try:
78 password = self.dashboardconf.get(self.section, 'password')
79 return base64.decodestring(password)
80 except NoOptionError:
81 return ""
82
83 password = property(get_password, set_password)
84
85 def write(self):
86 """
87 write the dashboard configuration out to the config file
88 """
89 if not os.path.exists(self.config.configdir):
90 os.makedirs(self.config.configdir)
91 with open(self.path, "w") as fd:
92 self.dashboardconf.write(fd)
93
94
95class cmd_dashboard(AbrekCmdWithSubcommands):
96 """
97 Connect to the Launch-control dashboard
98 """
99
100 class cmd_setup(AbrekCmd):
101 """
102 Configure information needed to push results to the dashboard
103 """
104 options = [make_option("-u", "--user", dest="user"),
105 make_option("-p", "--password", dest="password")]
106 arglist = ["*server"]
107
108 def run(self):
109 if len(self.args) != 1:
110 print "You must specify a server"
111 sys.exit(1)
112 config = DashboardConfig()
113 if self.opts.user:
114 user = self.opts.user
115 else:
116 user = raw_input("Username: ")
117 if self.opts.password:
118 password = self.opts.password
119 else:
120 password = getpass()
121 config.host = self.args[0]
122 config.user = user
123 config.password = password
124 config.write()
125
126
127 class cmd_put(AbrekCmd):
128 """
129 Push the results from a test to the server
130 The stream name must include slashes (e.g. /anonymous/foo/)
131 """
132 arglist = ["*stream", "*result"]
133
134 def run(self):
135 if len(self.args) != 2:
136 print "You must specify a stream and a result"
137 sys.exit(1)
138 stream_name = self.args[0]
139 result_name = self.args[1]
140 bundle = generate_bundle(result_name)
141 db_config = DashboardConfig()
142 hosturl = urllib.basejoin(db_config.host, "xml-rpc/")
143 try:
144 server = xmlrpclib.Server(hosturl)
145 except IOError, e:
146 print ("Error connecting to server at '%s'. Error was: %s, "
147 "please run 'lava-test dashboard setup [host]'" % (
148 hosturl, e))
149 sys.exit(1)
150 try:
151 result = server.put(DocumentIO.dumps(bundle), result_name,
152 stream_name)
153 print "Bundle successfully uploaded to id: %s" % result
154 except xmlrpclib.Fault as strerror:
155 print "Error uploading bundle: %s" % strerror.faultString
156 sys.exit(1)
157 except socket.error as strerror:
158 print "Unable to connect to host: %s" % strerror
159 sys.exit(1)
160 except xmlrpclib.ProtocolError as strerror:
161 print "Connection error: %s" % strerror
162 sys.exit(1)
163
164
165 class cmd_bundle(AbrekCmd):
166 """
167 Print JSON output that can be imported into the dashboard
168 """
169 arglist = ["*result"]
170
171 def run(self):
172 if len(self.args) != 1:
173 print "You must specify a result"
174 sys.exit(1)
175 bundle = generate_bundle(self.args[0])
176 try:
177 print DocumentIO.dumps(bundle)
178 except IOError:
179 pass
180
181
182def generate_bundle(result):
183 config = get_config()
184 resultdir = os.path.join(config.resultsdir, result)
185 if not os.path.exists(resultdir):
186 # FIXME: UI and sys.exit mixed with internal implementation, yuck
187 print "Result directory not found"
188 sys.exit(1)
189 with open(os.path.join(resultdir, "testdata.json")) as stream:
190 bundle_text = stream.read()
191 with open(os.path.join(resultdir, "testoutput.log")) as stream:
192 output_text = stream.read()
193 fmt, bundle = DocumentIO.loads(bundle_text)
194 test = testloader(bundle['test_runs'][0]['test_id'])
195 test.parse(result)
196 bundle['test_runs'][0].update(test.parser.results)
197 bundle['test_runs'][0]["attachments"] = [
198 {
199 "pathname": "testoutput.log",
200 "mime_type": "text/plain",
201 "content": base64.standard_b64encode(output_text)
202 }
203 ]
204 return bundle
205
206
2070
=== removed file 'abrek/hwprofile.py'
--- abrek/hwprofile.py 2011-05-27 02:39:03 +0000
+++ abrek/hwprofile.py 1970-01-01 00:00:00 +0000
@@ -1,216 +0,0 @@
1# Copyright (c) 2010 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import re
17import sys
18from subprocess import Popen, PIPE
19from utils import read_file, get_machine_type
20
21INTEL_KEYMAP = {
22 'vendor_id': 'cpu_vendor_name',
23 'cpu family': 'cpu_family',
24 'model': 'cpu_model',
25 'model name': 'cpu_model_name',
26 'stepping': 'cpu_stepping',
27 'cpu MHz': 'cpu_mhz',
28 'flags': 'cpu_features',
29}
30
31INTEL_VALMAP = {
32 'cpu family': int,
33 'model': int,
34 'stepping': int,
35 'cpu MHz': float,
36}
37
38ARM_KEYMAP = {
39 'Processor': 'cpu_model_name',
40 'Features': 'cpu_features',
41 'CPU implementer': 'cpu_implementer',
42 'CPU architecture': 'cpu_architecture',
43 'CPU variant': 'cpu_variant',
44 'CPU part': 'cpu_part',
45 'CPU revision': 'cpu_revision',
46}
47
48ARM_VALMAP = {
49 'CPU implementer': lambda value: int(value, 16),
50 'CPU architecture': int,
51 'CPU variant': lambda value: int(value, 16),
52 'CPU part': lambda value: int(value, 16),
53 'CPU revision': int,
54}
55
56
57def _translate_cpuinfo(keymap, valmap, key, value):
58 """
59 Translate a key and value using keymap and valmap passed in
60 """
61 newkey = keymap.get(key, key)
62 newval = valmap.get(key, lambda x: x)(value)
63 return newkey, newval
64
65def get_cpu_devs():
66 """
67 Return a list of CPU devices
68 """
69 pattern = re.compile('^(?P<key>.+?)\s*:\s*(?P<value>.*)$')
70 cpunum = 0
71 devices = []
72 cpudevs = []
73 cpudevs.append({})
74 machine = get_machine_type()
75 if machine in ('i686', 'x86_64'):
76 keymap, valmap = INTEL_KEYMAP, INTEL_VALMAP
77 elif machine.startswith('arm'):
78 keymap, valmap = ARM_KEYMAP, ARM_VALMAP
79
80 try:
81 cpuinfo = read_file("/proc/cpuinfo")
82 for line in cpuinfo.splitlines():
83 match = pattern.match(line)
84 if match:
85 key, value = match.groups()
86 key, value = _translate_cpuinfo(keymap, valmap,
87 key, value)
88 if cpudevs[cpunum].get(key):
89 cpunum += 1
90 cpudevs.append({})
91 cpudevs[cpunum][key] = value
92 for c in range(len(cpudevs)):
93 device = {}
94 device['device_type'] = 'device.cpu'
95 device['description'] = 'Processor #{0}'.format(c)
96 device['attributes'] = cpudevs[c]
97 devices.append(device)
98 except IOError:
99 print >> sys.stderr, "WARNING: Could not read cpu information"
100 return devices
101
102
103def get_board_devs():
104 """
105 Return a list of board devices
106 """
107 devices = []
108 attributes = {}
109 device = {}
110 machine = get_machine_type()
111 if machine in ('i686', 'x86_64'):
112 try:
113 description = read_file('/sys/class/dmi/id/board_name') or None
114 vendor = read_file('/sys/class/dmi/id/board_vendor') or None
115 version = read_file('/sys/class/dmi/id/board_version') or None
116 if description:
117 device['description'] = description.strip()
118 if vendor:
119 attributes['vendor'] = vendor.strip()
120 if version:
121 attributes['version'] = version.strip()
122 except IOError:
123 print >> sys.stderr, "WARNING: Could not read board information"
124 return devices
125 elif machine.startswith('arm'):
126 try:
127 cpuinfo = read_file("/proc/cpuinfo")
128 if cpuinfo is None:
129 return devices
130 pattern = re.compile("^Hardware\s*:\s*(?P<description>.+)$", re.M)
131 match = pattern.search(cpuinfo)
132 if match is None:
133 return devices
134 device['description'] = match.group('description')
135 except IOError:
136 print >> sys.stderr, "WARNING: Could not read board information"
137 return devices
138 else:
139 return devices
140 if attributes:
141 device['attributes'] = attributes
142 device['device_type'] = 'device.board'
143 devices.append(device)
144 return devices
145
146def get_mem_devs():
147 """ Return a list of memory devices
148
149 This returns up to two items, one for physical RAM and another for swap
150 """
151 pattern = re.compile('^(?P<key>.+?)\s*:\s*(?P<value>.+) kB$', re.M)
152
153 devices = []
154 try:
155 meminfo = read_file("/proc/meminfo")
156 for match in pattern.finditer(meminfo):
157 key, value = match.groups()
158 if key not in ('MemTotal', 'SwapTotal'):
159 continue
160 capacity = int(value) << 10 #Kernel reports in 2^10 units
161 if capacity == 0:
162 continue
163 if key == 'MemTotal':
164 kind = 'RAM'
165 else:
166 kind = 'swap'
167 description = "{capacity}MiB of {kind}".format(
168 capacity=capacity >> 20, kind=kind)
169 device = {}
170 device['description'] = description
171 device['attributes'] = {'capacity': str(capacity), 'kind': kind}
172 device['device_type'] = "device.mem"
173 devices.append(device)
174 except IOError:
175 print >> sys.stderr, "WARNING: Could not read memory information"
176 return devices
177
178def get_usb_devs():
179 """
180 Return a list of usb devices
181 """
182 pattern = re.compile(
183 "^Bus \d{3} Device \d{3}: ID (?P<vendor_id>[0-9a-f]{4}):"
184 "(?P<product_id>[0-9a-f]{4}) (?P<description>.*)$")
185 devices = []
186 try:
187 for line in Popen('lsusb', stdout=PIPE).communicate()[0].splitlines():
188 match = pattern.match(line)
189 if match:
190 vendor_id, product_id, description = match.groups()
191 attributes = {}
192 device = {}
193 attributes['vendor_id'] = int(vendor_id, 16)
194 attributes['product_id'] = int(product_id, 16)
195 device['attributes'] = attributes
196 device['description'] = description
197 device['device_type'] = 'device.usb'
198 devices.append(device)
199 except OSError:
200 print >> sys.stderr, "WARNING: Could not read usb device information, \
201unable to run lsusb, please install usbutils package"
202 return devices
203
204def get_hardware_context():
205 """
206 Return a dict with all of the hardware profile information gathered
207 """
208 hardware_context = {}
209 devices = []
210 devices.extend(get_cpu_devs())
211 devices.extend(get_board_devs())
212 devices.extend(get_mem_devs())
213 devices.extend(get_usb_devs())
214 hardware_context['devices'] = devices
215 return hardware_context
216
2170
=== removed file 'abrek/providers.py'
--- abrek/providers.py 2011-08-17 13:34:33 +0000
+++ abrek/providers.py 1970-01-01 00:00:00 +0000
@@ -1,145 +0,0 @@
1"""
2Test providers.
3
4Allow listing and loading of tests in a generic way.
5"""
6
7from pkg_resources import working_set
8
9from abrek.api import ITestProvider
10from abrek.cache import AbrekCache
11from abrek.config import get_config
12from abrek.testdef import AbrekDeclarativeTest
13
14
15class BuiltInProvider(ITestProvider):
16 """
17 Test provider that provides tests shipped in the Abrek source tree
18 """
19
20 _builtin_tests = [
21 'bootchart',
22 'firefox',
23 'glmemperf',
24 'gmpbench',
25 'gtkperf',
26 'ltp',
27 'peacekeeper'
28 'posixtestsuite',
29 'pwrmgmt',
30 'pybench',
31 'smem',
32 'stream',
33 'tiobench',
34 'x11perf',
35 'xrestop',
36 ]
37
38 def __init__(self, config):
39 pass
40
41 @property
42 def description(self):
43 return "Tests built directly into Abrek:"
44
45 def __iter__(self):
46 return iter(self._builtin_tests)
47
48 def __getitem__(self, test_name):
49 try:
50 module = __import__("abrek.test_definitions.%s" % test_name, fromlist=[''])
51 except ImportError:
52 raise KeyError(test_name)
53 else:
54 return module.testobj
55
56
57class PkgResourcesProvider(ITestProvider):
58 """
59 Test provider that provides tests declared in pkg_resources working_set
60
61 By default it looks at the 'abrek.test_definitions' name space but it can
62 be changed with custom 'namespace' configuration entry.
63 """
64
65 def __init__(self, config):
66 self._config = config
67
68 @property
69 def namespace(self):
70 return self._config.get("namespace", "abrek.test_definitions")
71
72 @property
73 def description(self):
74 return "Tests provided by installed python packages:"
75
76 def __iter__(self):
77 for entry_point in working_set.iter_entry_points(self.namespace):
78 yield entry_point.name
79
80 def __getitem__(self, test_name):
81 for entry_point in working_set.iter_entry_points(self.namespace, test_name):
82 return entry_point.load().testobj
83 raise KeyError(test_name)
84
85
86class RegistryProvider(ITestProvider):
87 """
88 Test provider that provides declarative tests listed in the test registry.
89 """
90 def __init__(self, config):
91 self._config = config
92 self._cache = None
93
94 @property
95 def entries(self):
96 """
97 List of URLs to AbrekDeclarativeTest description files
98 """
99 return self._config.get("entries", [])
100
101 @property
102 def description(self):
103 return "Tests provided by Abrek registry:"
104
105 @classmethod
106 def register_remote_test(self, test_url):
107 config = get_config() # This is a different config object from self._config
108 provider_config = config.get_provider_config("abrek.providers:RegistryProvider")
109 if "entries" not in provider_config:
110 provider_config["entries"] = []
111 if test_url not in provider_config["entries"]:
112 provider_config["entries"].append(test_url)
113 config._save_registry()
114 else:
115 raise ValueError("This test is already registered")
116
117 def _load_remote_test(self, test_url):
118 """
119 Load AbrekDeclarativeTest from a (possibly cached copy of) test_url
120 """
121 cache = AbrekCache.get_instance()
122 with cache.open_cached_url(test_url) as stream:
123 return AbrekDeclarativeTest.load_from_stream(stream)
124
125 def _fill_cache(self):
126 """
127 Fill the cache of all remote tests
128 """
129 if self._cache is not None:
130 return
131 self._cache = {}
132 for test_url in self.entries:
133 test = self._load_remote_test(test_url)
134 if test.testname in self._cache:
135 raise ValueError("Duplicate test %s declared" % test.testname)
136 self._cache[test.testname] = test
137
138 def __iter__(self):
139 self._fill_cache()
140 for test_name in self._cache.iterkeys():
141 yield test_name
142
143 def __getitem__(self, test_name):
144 self._fill_cache()
145 return self._cache[test_name]
1460
=== removed file 'abrek/results.py'
--- abrek/results.py 2010-10-14 13:57:35 +0000
+++ abrek/results.py 1970-01-01 00:00:00 +0000
@@ -1,111 +0,0 @@
1# Copyright (c) 2010 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import os
17import shutil
18import sys
19from optparse import make_option
20
21from abrek.command import AbrekCmd, AbrekCmdWithSubcommands
22from abrek.config import get_config
23from abrek.utils import read_file
24
25
26class cmd_results(AbrekCmdWithSubcommands):
27 """
28 Operate on results of previous test runs stored locally
29 """
30
31 class cmd_list(AbrekCmd):
32 """
33 List results of previous runs
34 """
35 def run(self):
36 config = get_config()
37 print "Saved results:"
38 try:
39 for dir in os.listdir(config.resultsdir):
40 print dir
41 except OSError:
42 print "No results found"
43
44
45 class cmd_show(AbrekCmd):
46 """
47 Display the output from a previous test run
48 """
49 arglist = ['*result']
50 def run(self):
51 if len(self.args) != 1:
52 print "please specify the name of the result dir"
53 sys.exit(1)
54 config = get_config()
55 resultsdir = os.path.join(config.resultsdir, self.args[0])
56 testoutput = os.path.join(resultsdir, "testoutput.log")
57 if not os.path.exists(testoutput):
58 print "No result found for '%s'" % self.args[0]
59 sys.exit(1)
60 try:
61 print(read_file(testoutput))
62 except IOError:
63 pass
64
65
66 class cmd_remove(AbrekCmd):
67 """
68 Remove the results of a previous test run
69 """
70 arglist = ['*result']
71 options = [make_option('-f', '--force', action='store_true',
72 dest='force')]
73 def run(self):
74 if len(self.args) != 1:
75 print "please specify the name of the result dir"
76 sys.exit(1)
77 config = get_config()
78 resultsdir = os.path.join(config.resultsdir, self.args[0])
79 if not os.path.exists(resultsdir):
80 print "No result found for '%s'" % self.args[0]
81 sys.exit(1)
82 if not self.opts.force:
83 print "Delete result '%s' for good? [Y/N]" % self.args[0],
84 response = raw_input()
85 if response[0].upper() != 'Y':
86 sys.exit(0)
87 shutil.rmtree(resultsdir)
88
89
90 class cmd_rename(AbrekCmd):
91 """
92 Rename the results from a previous test run
93 """
94 arglist = ['*source', '*destination']
95
96 def run(self):
97 if len(self.args) != 2:
98 print "please specify the name of the result, and the new name"
99 sys.exit(1)
100 config = get_config()
101 srcdir = os.path.join(config.resultsdir, self.args[0])
102 destdir = os.path.join(config.resultsdir, self.args[1])
103 if not os.path.exists(srcdir):
104 print "Result directory not found"
105 sys.exit(1)
106 if os.path.exists(destdir):
107 print "Destination result name already exists"
108 sys.exit(1)
109 shutil.move(srcdir, destdir)
110
111
1120
=== removed file 'abrek/swprofile.py'
--- abrek/swprofile.py 2011-07-22 04:00:04 +0000
+++ abrek/swprofile.py 1970-01-01 00:00:00 +0000
@@ -1,65 +0,0 @@
1# Copyright (c) 2010 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import apt
17import lsb_release
18from utils import read_file
19
20def get_packages(apt_cache=None):
21 """ Get information about the packages installed
22
23 apt_cache - if not provided, this will be read from the system
24 """
25 if apt_cache == None:
26 apt_cache = apt.Cache()
27 packages = []
28 for apt_pkg in apt_cache:
29 if hasattr(apt_pkg, 'is_installed'):
30 is_installed = apt_pkg.is_installed
31 else:
32 is_installed = apt_pkg.isInstalled # old style API
33 if is_installed:
34 pkg = {"name":apt_pkg.name, "version":apt_pkg.installed.version}
35 packages.append(pkg)
36 return packages
37
38def get_software_context(apt_cache=None, lsb_information=None):
39 """ Return dict used for storing software_context information
40
41 test_id - Unique identifier for this test
42 time_check - whether or not a check was performed to see if
43 the time on the system was synced with a time server
44 apt_cache - if not provided, this will be read from the system
45 lsb_information - if not provided, this will be read from the system
46 """
47 software_context = {}
48 software_context['image'] = get_image(lsb_information)
49 software_context['packages'] = get_packages(apt_cache)
50 return software_context
51
52def get_image(lsb_information=None):
53 """ Get information about the image we are running
54
55 If /etc/buildstamp exists, get the image id from that. Otherwise
56 just use the lsb-release description for a rough idea.
57 """
58 try:
59 buildstamp = read_file("/etc/buildstamp")
60 name = buildstamp.splitlines()[1]
61 except IOError:
62 if lsb_information == None:
63 lsb_information = lsb_release.get_distro_information()
64 name = lsb_information['DESCRIPTION']
65 return {"name":name}
660
=== removed file 'abrek/testdef.py'
--- abrek/testdef.py 2011-08-18 20:09:56 +0000
+++ abrek/testdef.py 1970-01-01 00:00:00 +0000
@@ -1,435 +0,0 @@
1# Copyright (c) 2010 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import hashlib
17import json
18import os
19import re
20import shutil
21import decimal
22import sys
23import time
24from commands import getstatusoutput
25from datetime import datetime
26from uuid import uuid4
27
28from abrek import swprofile, hwprofile
29from abrek.api import ITest
30from abrek.bundle import DocumentIO
31from abrek.config import get_config
32from abrek.utils import geturl, run_and_log, write_file
33
34
35class AbrekTest(ITest):
36 """Base class for defining tests.
37
38 This can be used by test definition files to create an object that
39 contains the building blocks for installing tests, running them,
40 and parsing the results.
41
42 testname - name of the test or test suite
43 version - version of the test or test suite
44 installer - AbrekInstaller instance to use
45 runner - AbrekRunner instance to use
46 parser - AbrekParser instance to use
47 """
48 def __init__(self, testname, version="", installer=None, runner=None,
49 parser=None):
50 self.testname = testname
51 self.version = version
52 self.installer = installer
53 self.runner = runner
54 self.parser = parser
55 self.origdir = os.path.abspath(os.curdir)
56
57 def install(self):
58 """Install the test suite.
59
60 This creates an install directory under the user's XDG_DATA_HOME
61 directory to mark that the test is installed. The installer's
62 install() method is then called from this directory to complete any
63 test specific install that may be needed.
64 """
65 if not self.installer:
66 raise RuntimeError("no installer defined for '%s'" %
67 self.testname)
68 config = get_config()
69 installdir = os.path.join(config.installdir, self.testname)
70 if os.path.exists(installdir):
71 raise RuntimeError("%s is already installed" % self.testname)
72 os.makedirs(installdir)
73 os.chdir(installdir)
74 try:
75 self.installer.install()
76 except Exception as strerror:
77 self.uninstall()
78 raise
79 finally:
80 os.chdir(self.origdir)
81
82 def uninstall(self):
83 """Uninstall the test suite.
84
85 Uninstalling just recursively removes the test specific directory
86 under the user's XDG_DATA_HOME directory. This will both mark
87 the test as removed, and clean up any files that were downloaded
88 or installed under that directory. Dependencies are intentionally
89 not removed by this.
90 """
91 os.chdir(self.origdir)
92 config = get_config()
93 path = os.path.join(config.installdir, self.testname)
94 if os.path.exists(path):
95 shutil.rmtree(path)
96
97 def _savetestdata(self, analyzer_assigned_uuid):
98 TIMEFORMAT = '%Y-%m-%dT%H:%M:%SZ'
99 bundle = {
100 'format': 'Dashboard Bundle Format 1.2',
101 'test_runs': [
102 {
103 'test_id': self.testname,
104 'analyzer_assigned_date': self.runner.starttime.strftime(TIMEFORMAT),
105 'analyzer_assigned_uuid': analyzer_assigned_uuid,
106 'time_check_performed': False,
107 'hardware_context': hwprofile.get_hardware_context(),
108 'software_context': swprofile.get_software_context(),
109 'test_results': []
110 }
111 ]
112 }
113 filename = os.path.join(self.resultsdir, 'testdata.json')
114 write_file(DocumentIO.dumps(bundle), filename)
115
116 def run(self, quiet=False):
117 if not self.runner:
118 raise RuntimeError("no test runner defined for '%s'" %
119 self.testname)
120 config = get_config()
121 uuid = str(uuid4())
122 installdir = os.path.join(config.installdir, self.testname)
123 resultname = (self.testname +
124 str(time.mktime(datetime.utcnow().timetuple())))
125 self.resultsdir = os.path.join(config.resultsdir, resultname)
126 os.makedirs(self.resultsdir)
127 try:
128 os.chdir(installdir)
129 self.runner.run(self.resultsdir, quiet=quiet)
130 self._savetestdata(uuid)
131 finally:
132 os.chdir(self.origdir)
133 result_id = os.path.basename(self.resultsdir)
134 print("ABREK TEST RUN COMPLETE: Result id is '%s'" % result_id)
135 return result_id
136
137 def parse(self, resultname):
138 if not self.parser:
139 raise RuntimeError("no test parser defined for '%s'" %
140 self.testname)
141 config = get_config()
142 resultsdir = os.path.join(config.resultsdir, resultname)
143 os.chdir(resultsdir)
144 self.parser.parse()
145 os.chdir(self.origdir)
146
147
148class AbrekDeclarativeTest(AbrekTest):
149 """
150 Declarative test is like AbrekTest but cannot contain any python code and
151 is completely encapsulated in .json file.
152 """
153
154 @classmethod
155 def load_from_stream(cls, stream):
156 return cls(json.load(stream))
157
158 def save_to_stream(self, stream):
159 json.dumps(self.about, stream, indent="2")
160
161 def __init__(self, about):
162 self.about = about
163 super(AbrekDeclarativeTest, self).__init__(self.about.get('test_id'))
164 self.installer = AbrekTestInstaller(**self.about.get('install', {}))
165 self.runner = AbrekTestRunner(**self.about.get('run', {}))
166 if self.about.get('parse', {}).get('native', False) is True:
167 self.parser = AbrekNativeTestParser(self)
168 else:
169 self.parser = AbrekForeignTestParser(**self.about.get('parse', {}))
170
171
172class AbrekTestInstaller(object):
173 """Base class for defining an installer object.
174
175 This class can be used as-is for simple installers, or extended for more
176 advanced funcionality.
177
178 steps - list of steps to be executed in a shell
179 deps - list of dependencies to apt-get install before running the steps
180 url - location from which the test suite should be downloaded
181 md5 - md5sum to check the integrety of the download
182 """
183 def __init__(self, steps=[], deps=[], url="", md5="", **kwargs):
184 self.steps = steps
185 self.deps = deps
186 self.url = url
187 self.md5 = md5
188
189 def _installdeps(self):
190 if not self.deps:
191 return 0
192 cmd = "sudo apt-get install -y %s" % " ".join(self.deps)
193 rc, output = getstatusoutput(cmd)
194 if rc:
195 raise RuntimeError("Dependency installation failed. %d : %s" %(rc,output))
196
197 def _download(self):
198 """Download the file specified by the url and check the md5.
199
200 Returns the path and filename if successful, otherwise return None
201 """
202 if not self.url:
203 return 0
204 filename = geturl(self.url)
205 #If the file does not exist, then the download was not successful
206 if not os.path.exists(filename):
207 return None
208 if self.md5:
209 checkmd5 = hashlib.md5()
210 with open(filename, 'rb') as fd:
211 data = fd.read(0x10000)
212 while data:
213 checkmd5.update(data)
214 data = fd.read(0x10000)
215 if checkmd5.hexdigest() != self.md5:
216 raise RuntimeError("Unexpected md5sum downloading %s" %
217 filename)
218 return None
219 return filename
220
221 def _runsteps(self):
222 for cmd in self.steps:
223 rc, output = getstatusoutput(cmd)
224 if rc:
225 raise RuntimeError("Run step '%s' failed. %d : %s" %(cmd,rc,output))
226
227
228 def install(self):
229 self._installdeps()
230 self._download()
231 self._runsteps()
232
233
234class AbrekTestRunner(object):
235 """Base class for defining an test runner object.
236
237 This class can be used as-is for simple execution with the expectation
238 that the run() method will be called from the directory where the test
239 was installed. Steps, if used, should handle changing directories from
240 there to the directory where the test was extracted if necessary.
241 This class can also be extended for more advanced funcionality.
242
243 steps - list of steps to be executed in a shell
244 """
245 def __init__(self, steps=[]):
246 self.steps = steps
247 self.testoutput = []
248
249 def _runsteps(self, resultsdir, quiet=False):
250 outputlog = os.path.join(resultsdir, 'testoutput.log')
251 with open(outputlog, 'a') as fd:
252 for cmd in self.steps:
253 run_and_log(cmd, fd, quiet)
254
255 def run(self, resultsdir, quiet=False):
256 self.starttime = datetime.utcnow()
257 self._runsteps(resultsdir, quiet=quiet)
258 self.endtime = datetime.utcnow()
259
260
261class AbrekForeignTestParser(object):
262 """Base class for defining a test parser
263
264 This class can be used as-is for simple results parsers, but will
265 likely need to be extended slightly for many. If used as it is,
266 the parse() method should be called while already in the results
267 directory and assumes that a file for test output will exist called
268 testoutput.log.
269
270 pattern - regexp pattern to identify important elements of test output
271 For example: If your testoutput had lines that look like:
272 "test01: PASS", then you could use a pattern like this:
273 "^(?P<testid>\w+):\W+(?P<result>\w+)"
274 This would result in identifying "test01" as testid and
275 "PASS" as result. Once parse() has been called,
276 self.results.test_results[] contains a list of dicts of all the
277 key,value pairs found for each test result
278 fixupdict - dict of strings to convert test results to standard strings
279 For example: if you want to standardize on having pass/fail results
280 in lower case, but your test outputs them in upper case, you could
281 use a fixupdict of something like: {'PASS':'pass','FAIL':'fail'}
282 appendall - Append a dict to the test_results entry for each result.
283 For example: if you would like to add units="MB/s" to each result:
284 appendall={'units':'MB/s'}
285 """
286 def __init__(self, pattern=None, fixupdict=None, appendall={}):
287 self.pattern = pattern
288 self.fixupdict = fixupdict
289 self.results = {'test_results':[]}
290 self.appendall = appendall
291
292 def _find_testid(self, id):
293 for x in self.results['test_results']:
294 if x['testid'] == id:
295 return self.results['test_results'].index(x)
296
297 def parse(self):
298 """Parse test output to gather results
299
300 Use the pattern specified when the class was instantiated to look
301 through the results line-by-line and find lines that match it.
302 Results are then stored in self.results. If a fixupdict was supplied
303 it is used to convert test result strings to a standard format.
304 """
305 filename = "testoutput.log"
306 try:
307 pat = re.compile(self.pattern)
308 except Exception as strerror:
309 raise RuntimeError(
310 "AbrekTestParser - Invalid regular expression '%s' - %s" % (
311 self.pattern,strerror))
312
313 with open(filename, 'r') as stream:
314 for lineno, line in enumerate(stream, 1):
315 match = pat.search(line)
316 if not match:
317 continue
318 data = match.groupdict()
319 if 'measurement' in data:
320 data['measurement'] = decimal.Decimal(data['measurement'])
321 data["log_filename"] = filename
322 data["log_lineno"] = lineno
323 self.results['test_results'].append(data)
324 if self.fixupdict:
325 self.fixresults(self.fixupdict)
326 if self.appendall:
327 self.appendtoall(self.appendall)
328 self.fixmeasurements()
329 self.fixids()
330
331 def append(self, testid, entry):
332 """Appends a dict to the test_results entry for a specified testid
333
334 This lets you add a dict to the entry for a specific testid
335 entry should be a dict, updates it in place
336 """
337 index = self._find_testid(testid)
338 self.results['test_results'][index].update(entry)
339
340 def appendtoall(self, entry):
341 """Append entry to each item in the test_results.
342
343 entry - dict of key,value pairs to add to each item in the
344 test_results
345 """
346 for t in self.results['test_results']:
347 t.update(entry)
348
349 def fixresults(self, fixupdict):
350 """Convert results to a known, standard format
351
352 pass it a dict of keys/values to replace
353 For instance:
354 {"TPASS":"pass", "TFAIL":"fail"}
355 This is really only used for qualitative tests
356 """
357 for t in self.results['test_results']:
358 if t.has_key("result"):
359 t['result'] = fixupdict[t['result']]
360
361 def fixmeasurements(self):
362 """Measurements are often read as strings, but need to be float
363 """
364 for id in self.results['test_results']:
365 if id.has_key('measurement'):
366 id['measurement'] = float(id['measurement'])
367
368 def fixids(self):
369 """
370 Convert spaces to _ in test_case_id and remove illegal characters
371 """
372 badchars = "[^a-zA-Z0-9\._-]"
373 for id in self.results['test_results']:
374 if id.has_key('test_case_id'):
375 id['test_case_id'] = id['test_case_id'].replace(" ", "_")
376 id['test_case_id'] = re.sub(badchars, "", id['test_case_id'])
377
378
379AbrekTestParser = AbrekForeignTestParser
380
381
382class AbrekNativeTestParser(object):
383
384 def __init__(self, test_def):
385 self.test_def = test_def
386
387 def parse(self):
388 raise NotImplementedError(self.parse)
389
390
391class TestLoader(object):
392 """
393 Test loader.
394
395 Encapsulates Abrek's knowledge of available tests.
396
397 Test can be loaded by name with TetsLoader.get_test_by_name. Test can also
398 be listed by TestLoader.get_providers() and then iterating over tests
399 returned by each provider.
400 """
401
402 def __init__(self):
403 self._config = get_config()
404
405 def get_providers(self):
406 """
407 Return a generator of available providers
408 """
409 import pkg_resources
410 for provider_info in self._config.registry.get("providers", []):
411 entry_point_name = provider_info.get("entry_point")
412 module_name, attrs = entry_point_name.split(':', 1)
413 attrs = attrs.split('.')
414 try:
415 entry_point = pkg_resources.EntryPoint(entry_point_name, module_name, attrs,
416 dist=pkg_resources.get_distribution("lava-test"))
417 provider_cls = entry_point.load()
418 provider = provider_cls(provider_info.get("config", {}))
419 yield provider
420 except pkg_resources.DistributionNotFound as ex:
421 raise RuntimeError("lava-test is not properly set up. Please read the REAMME file")
422
423 def get_test_by_name(self, test_name):
424 """
425 Lookup a test with the specified name
426 """
427 for provider in self.get_providers():
428 try:
429 return provider[test_name]
430 except KeyError:
431 pass
432 raise ValueError("No such test %r" % test_name)
433
434
435testloader = TestLoader().get_test_by_name
4360
=== removed directory 'bin'
=== removed file 'bin/lava-test'
--- bin/lava-test 2011-06-10 04:49:54 +0000
+++ bin/lava-test 1970-01-01 00:00:00 +0000
@@ -1,31 +0,0 @@
1#!/usr/bin/python
2
3# Copyright (c) 2010 Linaro
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import os
19import sys
20
21ABREK_BINDIR=os.path.abspath(os.path.dirname(os.path.realpath(sys.argv[0])))
22ABREK_ROOT=os.path.dirname(ABREK_BINDIR)
23ABREK_DIR=os.path.join(ABREK_ROOT,'abrek')
24if os.path.exists(ABREK_DIR) and ABREK_ROOT not in sys.path:
25 sys.path.insert(0, ABREK_ROOT)
26
27import abrek.main
28
29if __name__ == '__main__':
30 exit_code = abrek.main.main(sys.argv)
31 sys.exit(exit_code)
320
=== added directory 'doc'
=== added file 'doc/changes.rst'
--- doc/changes.rst 1970-01-01 00:00:00 +0000
+++ doc/changes.rst 2011-09-13 22:44:36 +0000
@@ -0,0 +1,25 @@
1Version History
2***************
3
4.. _version_0_2:
5
6Version 0.2
7===========
8
9* Rewrite most of the code and deprecate old Abrek interfaces. This allowed us
10 to clean up the API, rethink some of the design and integrate the code better
11 with other parts of LAVA.
12
13* Improved documentation and code reference. LAVA Test should now have
14 sufficient documentation to help new users and contributors alike.
15
16* Support for installing and running out-of-tree tests.
17
18* Ability to define parsers that add new attachments.
19
20* Unified command line interface with other lava tools thanks to lava-tool.
21
22Version 0.1
23===========
24
25* Initial release (as Abrek)
026
=== added file 'doc/conf.py'
--- doc/conf.py 1970-01-01 00:00:00 +0000
+++ doc/conf.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,211 @@
1# -*- coding: utf-8 -*-
2#
3# Linaro JSON documentation build configuration file, created by
4# sphinx-quickstart on Mon Dec 27 16:39:47 2010.
5#
6# This file is execfile()d with the current directory set to its containing dir.
7#
8# Note that not all possible configuration values are present in this
9# autogenerated file.
10#
11# All configuration values have a default; values that are commented out
12# serve to show the default.
13
14import sys
15import os
16
17# If extensions (or modules to document with autodoc) are in another directory,
18# add these directories to sys.path here. If the directory is relative to the
19# documentation root, use os.path.abspath to make it absolute, like shown here.
20sys.path.append(os.path.abspath('..'))
21
22# -- General configuration -----------------------------------------------------
23
24# Add any Sphinx extension module names here, as strings. They can be extensions
25# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
26extensions = [
27 'sphinx.ext.autodoc',
28 'sphinx.ext.doctest',
29 'sphinx.ext.intersphinx',
30 'sphinx.ext.todo',
31 'sphinx.ext.coverage',
32 'sphinx.ext.viewcode']
33
34# Configuration for sphinx.ext.todo
35
36todo_include_todos = True
37
38# Add any paths that contain templates here, relative to this directory.
39templates_path = []
40
41# The suffix of source filenames.
42source_suffix = '.rst'
43
44# The encoding of source files.
45#source_encoding = 'utf-8'
46
47# The master toctree document.
48master_doc = 'index'
49
50# General information about the project.
51project = u'LAVA Test'
52copyright = u'2010-2011, Linaro Limited'
53
54# The version info for the project you're documenting, acts as replacement for
55# |version| and |release|, also used in various other places throughout the
56# built documents.
57#
58# The short X.Y version.
59import versiontools
60import lava_test
61version = "%d.%d" % lava_test.__version__[0:2]
62# The full version, including alpha/beta/rc tags.
63release = versiontools.format_version(lava_test.__version__)
64
65# The language for content autogenerated by Sphinx. Refer to documentation
66# for a list of supported languages.
67#language = None
68
69# There are two options for replacing |today|: either, you set today to some
70# non-false value, then it is used:
71#today = ''
72# Else, today_fmt is used as the format for a strftime call.
73#today_fmt = '%B %d, %Y'
74
75# List of documents that shouldn't be included in the build.
76#unused_docs = []
77
78# List of directories, relative to source directory, that shouldn't be searched
79# for source files.
80exclude_trees = []
81
82# The reST default role (used for this markup: `text`) to use for all documents.
83#default_role = None
84
85# If true, '()' will be appended to :func: etc. cross-reference text.
86#add_function_parentheses = True
87
88# If true, the current module name will be prepended to all description
89# unit titles (such as .. function::).
90#add_module_names = True
91
92# If true, sectionauthor and moduleauthor directives will be shown in the
93# output. They are ignored by default.
94#show_authors = False
95
96# The name of the Pygments (syntax highlighting) style to use.
97pygments_style = 'sphinx'
98
99# A list of ignored prefixes for module index sorting.
100#modindex_common_prefix = []
101
102
103# -- Options for HTML output ---------------------------------------------------
104
105# The theme to use for HTML and HTML Help pages. Major themes that come with
106# Sphinx are currently 'default' and 'sphinxdoc'.
107html_theme = 'default'
108
109# Theme options are theme-specific and customize the look and feel of a theme
110# further. For a list of options available for each theme, see the
111# documentation.
112#html_theme_options = {}
113
114# Add any paths that contain custom themes here, relative to this directory.
115#html_theme_path = []
116
117# The name for this set of Sphinx documents. If None, it defaults to
118# "<project> v<release> documentation".
119#html_title = None
120
121# A shorter title for the navigation bar. Default is the same as html_title.
122#html_short_title = None
123
124# The name of an image file (relative to this directory) to place at the top
125# of the sidebar.
126#html_logo = None
127
128# The name of an image file (within the static path) to use as favicon of the
129# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
130# pixels large.
131#html_favicon = None
132
133# Add any paths that contain custom static files (such as style sheets) here,
134# relative to this directory. They are copied after the builtin static files,
135# so a file named "default.css" will overwrite the builtin "default.css".
136html_static_path = []
137
138# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
139# using the given strftime format.
140#html_last_updated_fmt = '%b %d, %Y'
141
142# If true, SmartyPants will be used to convert quotes and dashes to
143# typographically correct entities.
144#html_use_smartypants = True
145
146# Custom sidebar templates, maps document names to template names.
147#html_sidebars = {}
148
149# Additional templates that should be rendered to pages, maps page names to
150# template names.
151#html_additional_pages = {}
152
153# If false, no module index is generated.
154#html_use_modindex = True
155
156# If false, no index is generated.
157#html_use_index = True
158
159# If true, the index is split into individual pages for each letter.
160#html_split_index = False
161
162# If true, links to the reST sources are added to the pages.
163#html_show_sourcelink = True
164
165# If true, an OpenSearch description file will be output, and all pages will
166# contain a <link> tag referring to it. The value of this option must be the
167# base URL from which the finished HTML is served.
168#html_use_opensearch = ''
169
170# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
171#html_file_suffix = ''
172
173# Output file base name for HTML help builder.
174htmlhelp_basename = 'LAVATestDocumentation'
175
176
177# -- Options for LaTeX output --------------------------------------------------
178
179# The paper size ('letter' or 'a4').
180#latex_paper_size = 'letter'
181
182# The font size ('10pt', '11pt' or '12pt').
183#latex_font_size = '10pt'
184
185# Grouping the document tree into LaTeX files. List of tuples
186# (source start file, target name, title, author, documentclass [howto/manual]).
187latex_documents = [
188 ('index', 'LAVA Test.tex', u'LAVA Test Documentation',
189 u'Zygmunt Krynicki', 'manual'),
190]
191
192# The name of an image file (relative to this directory) to place at the top of
193# the title page.
194#latex_logo = None
195
196# For "manual" documents, if this is true, then toplevel headings are parts,
197# not chapters.
198#latex_use_parts = False
199
200# Additional stuff for the LaTeX preamble.
201#latex_preamble = ''
202
203# Documents to append as an appendix to all manuals.
204#latex_appendices = []
205
206# If false, no module index is generated.
207#latex_use_modindex = True
208
209
210# Example configuration for intersphinx: refer to the Python standard library.
211intersphinx_mapping = {'http://docs.python.org/': None}
0212
=== added file 'doc/index.rst'
--- doc/index.rst 1970-01-01 00:00:00 +0000
+++ doc/index.rst 2011-09-13 22:44:36 +0000
@@ -0,0 +1,81 @@
1=======================
2LAVA Test Documentation
3=======================
4
5LAVA Test is a wrapper framework exposing unified API and command line
6interface for running arbitrary tests and storing the results in a structured
7manner.
8
9LAVA Test is a part of the LAVA stack and can be used with other LAVA
10components, most notably the dispatcher (for setting up the test environment
11and controlling execution of multiple tests) and the dashboard (for storing
12
13.. seealso:: To learn more about LAVA see https://launchpad.net/lava
14
1560 second example
16=================
17
18This example will run on Ubuntu Lucid and beyond::
19
20 $ sudo add-apt-repository ppa:linaro-validation/ppa
21 $ sudo apt-get update
22 $ sudo apt-get install lava-test
23 $ lava-test install stream
24 $ lava-test run stream
25
26.. seealso:: For a more thorough description see :ref:`usage`
27.. seealso:: For detailed installation istructions see :ref:`installation`
28
29Features
30========
31
32* Ability to enumerate, install, run and remove tests on a Linux-based system.
33* Support for benchmarks as well as pass/fail tests.
34* Support for capturing environment information such as installed software and
35 hardware information and recording that in a machine-readable manner.
36* Store results in raw form (log files) as well as Linaro Dashboard Bundle
37 format that can be uploaded to the LAVA Dashboard for archiving and analysis.
38* Extensible API for adding new tests (:class:`~lava_test.api.core.ITest`) or even
39 collections of tests (:class:`~lava_test.api.core.ITestProvider`).
40* Ever-growing collection of freely available and generic tests and benchmarks
41
42.. seealso:: See what's new in :ref:`version_0_2`
43
44
45Latest documentation
46====================
47
48This documentation my be out of date, we try to make sure that all the latest
49and greatest releases are always documented on http://lava-test.readthedocs.org/
50
51
52Source code, bugs and patches
53=============================
54
55The project is maintained on Launchpad at http://launchpad.net/lava-test/.
56
57You can get the source code with bazaar using ``bzr branch lp:lava-test``.
58Patches can be submitted using Launchpad merge proposals (for introduction to
59this and topic see https://help.launchpad.net/Code/Review).
60
61Please report all bugs at https://bugs.launchpad.net/lava-test/+filebug.
62
63Most of the team is usually available in ``#linaro`` on ``irc.freenode.net``.
64Feel free to drop by to chat and ask questions.
65
66
67Indices and tables
68==================
69
70.. toctree::
71 :maxdepth: 2
72
73 installation.rst
74 changes.rst
75 usage.rst
76 reference.rst
77 todo.rst
78
79* :ref:`genindex`
80* :ref:`modindex`
81* :ref:`search`
082
=== added file 'doc/installation.rst'
--- doc/installation.rst 1970-01-01 00:00:00 +0000
+++ doc/installation.rst 2011-09-13 22:44:36 +0000
@@ -0,0 +1,66 @@
1
2.. _installation:
3
4Installation
5============
6
7Prerequisites
8^^^^^^^^^^^^^
9
10The following debian packages are needed to use LAVA Test:
11
12* python-setuptools
13* python-apt
14* usbutils
15* python-testrepository - for running unit tests
16* python-sphinx - for building documentation
17
18
19Installation Options
20^^^^^^^^^^^^^^^^^^^^
21
22There are several installation options available:
23
24
25Using Ubuntu PPAs
26-----------------
27
28For Ubuntu 10.04 onward there is a stable PPA (personal package archive):
29
30* ppa:linaro-validation/ppa
31
32To add a ppa to an Ubuntu system use the add-apt-repository command::
33
34 sudo add-apt-repository ppa:linaro-validation/ppa
35
36After you add the PPA you need to update your package cache::
37
38 sudo apt-get update
39
40Finally you can install the package, it is called `lava-test`::
41
42 sudo apt-get install lava-test
43
44
45Using Python Package Index
46--------------------------
47
48This package is being actively maintained and published in the `Python Package
49Index <http://http://pypi.python.org>`_. You can install it if you have `pip
50<http://pip.openplans.org/>`_ tool using just one line::
51
52 pip install lava-test
53
54
55Using source tarball
56--------------------
57
58To install from source you must first obtain a source tarball from either pypi
59or from `Launchpad <http://launchpad.net/>`_. To install the package unpack the
60tarball and run::
61
62 python setup.py install
63
64You can pass ``--user`` if you prefer to do a local (non system-wide)
65installation. Note that executable programs are placed in ``~/.local/bin/`` and
66this directory is not on ``PATH`` by default.
067
=== added file 'doc/reference.rst'
--- doc/reference.rst 1970-01-01 00:00:00 +0000
+++ doc/reference.rst 2011-09-13 22:44:36 +0000
@@ -0,0 +1,107 @@
1.. _reference:
2
3=========
4Reference
5=========
6
7.. _command_reference:
8
9Command Reference
10=================
11
12.. automodule:: lava_test.commands
13 :members:
14
15.. todo::
16
17 * Describe basic commands
18 * Describe arguments and options to each command in detail
19
20Pathnames and files
21===================
22
23LAVA Test uses the following files:
24
25* ``$XDG_CONFIG_HOME/lava_test/`` -- configuration files
26* ``$XDG_DATA_HOME/lava_test/installed-tests`` -- installed test programs
27* ``$XDG_DATA_HOME/lava_test/results`` -- artifacts of running tests
28* ``$XDG_CACHE_HOME/lava_test/`` -- download cache
29
30.. _code_reference:
31
32Code reference
33==============
34
35.. todo::
36
37 * Describe general code layout
38 * Describe key API integration points (on a separate page if needed for clarity)
39 * Provide an example test and walk the reader through the meaning of each part
40
41Abstract Interfaces
42^^^^^^^^^^^^^^^^^^^
43
44.. automodule:: lava_test.api.core
45 :members:
46
47.. automodule:: lava_test.api.delegates
48 :members:
49
50.. automodule:: lava_test.api.observers
51 :members:
52
53Test definitions and test providers
54^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
55
56.. automodule:: lava_test.core.providers
57 :members:
58
59.. automodule:: lava_test.core.tests
60 :members:
61
62Test components (installers, runners and parsers)
63^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
64
65.. automodule:: lava_test.core.installers
66 :members:
67
68.. automodule:: lava_test.core.runners
69 :members:
70
71.. automodule:: lava_test.core.parsers
72 :members:
73
74Core Modules
75^^^^^^^^^^^^
76
77.. automodule:: lava_test.core.artifacts
78 :members:
79
80.. automodule:: lava_test.core.config
81 :members:
82
83Environment Scanners
84^^^^^^^^^^^^^^^^^^^^
85
86.. automodule:: lava_test.core.hwprofile
87 :members:
88
89.. automodule:: lava_test.core.swprofile
90 :members:
91
92Utilities
93^^^^^^^^^
94
95.. automodule:: lava_test.utils
96 :members:
97
98.. automodule:: lava_test.extcmd
99 :members:
100
101
102Abrek compatibility
103===================
104
105.. automodule:: abrek.testdef
106 :members:
107
0108
=== added file 'doc/todo.rst'
--- doc/todo.rst 1970-01-01 00:00:00 +0000
+++ doc/todo.rst 2011-09-13 22:44:36 +0000
@@ -0,0 +1,4 @@
1List of items that need work
2============================
3
4.. todolist::
05
=== added file 'doc/usage.rst'
--- doc/usage.rst 1970-01-01 00:00:00 +0000
+++ doc/usage.rst 2011-09-13 22:44:36 +0000
@@ -0,0 +1,195 @@
1.. _usage:
2
3=====
4Usage
5=====
6
7Workflow Overview
8=================
9
10LAVA Test can be used in several different ways. Most notably those are
11standalone (without the LAVA dispatcher) and managed (when LAVA Test is
12installed and controlled by the LAVA dispatcher).
13
14Standalone usage
15^^^^^^^^^^^^^^^^
16
17In standalone mode a human operator installs LAVA Test on some device
18(development board, laptop or other computer or a virtual machine), installs
19the tests that are to be executed and then executes them manually (by manually
20running LAVA test, the actual tests are non-interactive).
21
22Using LAVA to develop and run new tests
23+++++++++++++++++++++++++++++++++++++++
24
25This mode is useful for test development (adding new tests, developing custom
26tests especially tailored for LAVA, etc.). Here the typical cycle depends on
27how the tests is wrapped for usage by LAVA and what the test developer is
28focusing on.
29
30While developing the actual test the typical set of commands might look like
31this::
32
33 $ lava-test install my-custom-test
34 $ lava-test run my-custom-test
35 $ lava-test uninstall my-custom-test
36
37Here the developer could observe changes to the test program (that is
38presumably compiled and copied somewhere by the install stage).
39
40Using LAVA to analyze test results
41++++++++++++++++++++++++++++++++++
42
43Developing the test is only half of the story. The other half is developing
44LAVA Test integration code, most importantly the artefact parser / analyzer.
45This part has to be implemented in python (unlike the test program that can be
46implemented in any language and technology). Here the developer is focusing on
47refining the parser to see if the outcome is as indented. Assuming that earlier
48the developer ran the test at least once and wrote down the result identifier
49the set of commands one might use is::
50
51 $ lava-test parse my-custom-test my-custom-test.2011-08-19T23:53:21Z | pager
52
53Here the developer has to pass both the identifier of the test
54(``my-custom-test``) as well as the identifier of the actual result. While
55currently the result identifier starts with the test identifier we wanted to
56avoid magic values like that so both are needed. The test defines which
57artefact parser to use. The result id is used to locate leftovers from running
58that specific test at some previous point in time.
59
60By default parse will print the bundle to standard output for inspection. It
61should be redirected to a pager for easier verification.
62
63.. note::
64
65 While the syntax of the bundle created with `lava-test parse` is always
66 correct (or, if the parser does something really, really strange, a
67 detailed error is reported) the actual contents may not be what you
68 intended it to be. Parsers are ultimately fragile as they mostly deal with
69 unstructured or semi-structured free-form text that most test programs seem
70 to produce. The ultimate goal of a developer should be to produce
71 unambiguous, machine readable format. This level of integration would allow
72 to wrap a whole class of tests in one go (such as all xUnit-XML speaking
73 test frameworks).
74
75Usage with the dispatcher
76^^^^^^^^^^^^^^^^^^^^^^^^^
77
78The dispatcher is useful for automating LAVA Test environment setup, describing
79test scenarios (the list of tests to invoke) and finally storing the results in
80the LAVA dashboard.
81
82Typically this mode is based on the following sequence of commands:
83
84#. Install lava-test (from PPA or source) along with the required dependencies
85#. (optional) for out of tree tests install the additional `test definition` package
86#. Install the test or tests that are to be invoked with ``lava-tool install``.
87#. Run, parse and store in one go with ``lava-tool run --output=FILE``.
88
89Here the whole setup is non-interactive and at the end the dispatcher can copy
90the output bundle for additional processing.
91
92Automation considerations
93^^^^^^^^^^^^^^^^^^^^^^^^^
94
95.. _wrapping_existing_test_or_benchmark:
96
97Wrapping existing test or benchmark
98===================================
99
100LAVA Test can be extended in several different ways. There is no best method,
101each has some pros and cons. In general we welcome any freely redistributable,
102generic tests. Those enrich the LAVA ecosystem and by providing useful
103out-of-the-box features to our users.
104
105Technically all tests are hidden behind a set of abstract interfaces that tell
106LAVA Test what to do in response to operator or dispatcher actions. The primary
107interface is :class:`~lava_test.api.core.ITest` and the three principal
108methods: :meth:`~lava_test.api.core.ITest.install`,
109:meth:`~lava_test.api.core.ITest.run`,
110:meth:`~lava_test.api.core.ITest.parse`.
111
112In practice it is usually much easier to instantiate our pluggable delegate
113test (:class:`lava_test.core.tests.Test`) and define the three delegates that
114know how to install, run and parse. Again for each step we have a base class
115that can be easily customized or even used directly as is. Those classes are
116:class:`~lava_test.core.installers.TestInstaller`,
117:class:`~lava_test.core.runners.TestRunner` and
118:class:`~lava_test.core.parsers.TestParser`. They all implement well-defined
119interfaces (specified in :mod:`lava_test.api.delegates`) so if you wish to
120customize them you should become familiar with the API requirements first.
121
122Contributing new tests to LAVA
123^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
124
125The most direct way to add a new test is to contribute patches to LAVA Test
126itself. This method will simply add a new test definition to the collection of
127available tests.
128
129This method is recommended for generic tests that rarely change and are
130suitable for wide variety of hardware and software (assuming basic Linux-like
131system, Android tests are a special case).
132
133The advantage is that those tests can be invoked out of the box and will be
134maintained by the LAVA team. The disadvantage is that all changes to those
135tests need to follow Linaro development work flow, get reviewed and finally
136merged. Depending on your situation this may be undesired.
137
138.. todo::
139
140 Describe how tests are discovered, loaded and used. It would be
141 nice to have a tutorial that walks the user through wrapping a
142 simple pass/fail test.
143
144Maintaining out-of-tree tests
145^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
146
147For some kinds of tests (proprietary, non-generic, in rapid development, fused
148with application code) contributing their definition to upstream LAVA Test
149project would be impractical.
150
151In such cases the test maintainer can still leverage LAVA to actually run and
152process the test without being entangled in the review process or going through
153any public channel.
154
155Because LAVA Test supports pluggable test providers it is easy to add a new
156source of test definitions. Fortunately we ship with a very useful generic
157out-of-tree test provider based on the python `pkg_resources` system.
158
159Any python package (that is a module or package and the corresponding setup.py
160and .egg_info) can define LAVA Test extensions using the `pkg_resurces` entry
161points system.
162
163To do this write your test program as you normally would, write the LAVA Test
164integration code and put this into your integration package setup.py::
165
166 setup(
167 ...,
168 entry_points="""[lava_test.test_definitions]
169 my_test_id=my_package.my_module
170 """)
171
172Here we'd define an entry point in the ``lava_test.test_definitions`` namespace
173that LAVA Test searches by default. In that namespace we define one object
174``my_test_id`` which points at the module ``my_package.my_module``. LAVA Test
175will discover this entry point, import the relevant module and make the test
176definition available.
177
178Maintaining simple declarative tests
179^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
180
181By registering pure declarative tests at runtime.
182
183.. todo::
184
185 Describe how to use declarative tests. It would be a nice
186 extension of the tutorial once the user feels comfortable with
187 the initial python-based version.
188
189Writing new tests from scratch
190==============================
191
192.. todo::
193
194 Describe considerations for test writers. Using native test
195 format with human-readable output adapters.
0196
=== modified file 'examples/power-management-tests.json'
--- examples/power-management-tests.json 2011-06-28 12:51:57 +0000
+++ examples/power-management-tests.json 2011-09-13 22:44:36 +0000
@@ -1,5 +1,5 @@
1{1{
2 "format": "Abrek Test Definition 1.0 Experimental",2 "format": "Lava-Test Test Definition 1.0",
3 "test_id": "linaro.pmwg",3 "test_id": "linaro.pmwg",
4 "install": {4 "install": {
5 "steps": ["bzr get lp:~zkrynicki/+junk/linaro-pm-qa-tests"],5 "steps": ["bzr get lp:~zkrynicki/+junk/linaro-pm-qa-tests"],
66
=== modified file 'examples/stream.json'
--- examples/stream.json 2011-06-28 13:31:48 +0000
+++ examples/stream.json 2011-09-13 22:44:36 +0000
@@ -1,5 +1,5 @@
1{1{
2 "format": "Abrek Test Definition Format 1.0 Experimental",2 "format": "LAVA-Test Test Definition Format",
3 "test_id": "stream-json",3 "test_id": "stream-json",
4 "install": {4 "install": {
5 "url": "http://www.cs.virginia.edu/stream/FTP/Code/stream.c",5 "url": "http://www.cs.virginia.edu/stream/FTP/Code/stream.c",
66
=== renamed directory 'abrek' => 'lava_test'
=== modified file 'lava_test/__init__.py'
--- abrek/__init__.py 2011-08-20 02:20:37 +0000
+++ lava_test/__init__.py 2011-09-13 22:44:36 +0000
@@ -13,4 +13,4 @@
13# You should have received a copy of the GNU General Public License13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.14# along with this program. If not, see <http://www.gnu.org/licenses/>.
1515
16__version__ = "0.2.0"16__version__ = (0, 2, 0, "dev", 0)
1717
=== added directory 'lava_test/api'
=== added file 'lava_test/api/__init__.py'
--- lava_test/api/__init__.py 1970-01-01 00:00:00 +0000
+++ lava_test/api/__init__.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,24 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from abc import ABCMeta
17
18
19class _Interface(object):
20 """
21 Interface class for simplifying usage of interface meta-classes
22 """
23
24 __metaclass__ = ABCMeta
025
=== added file 'lava_test/api/core.py'
--- lava_test/api/core.py 1970-01-01 00:00:00 +0000
+++ lava_test/api/core.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,164 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16"""
17:mod:`lava_test.api.core` -- Interface classes for core LAVA Test features
18==========================================================================
19
20.. module: lava_test.api.core
21
22 :synopsis: Interface classes for core LAVA Test features
23"""
24
25from abc import abstractmethod, abstractproperty
26
27from lava_test.api import _Interface
28
29class ITest(_Interface):
30 """
31 Abstract test definition.
32
33 Test definitions allow lava-test to install, remove, run and parse log
34 files of automatic tests. While the interface can be implemented directly
35 you should use :class:`lava_test.core.tests.Test` that implements the core
36 logic and allow you to customize the parts that are needed by providing
37 delegates implementing :class:`~lava_test.api.delegates.ITestInstaller`,
38 :class:`~lava_test.api.delegates.ITestRunner` and
39 :class:`~lava_test.api.delegates.ITestParser`.
40
41 .. seealso:: :ref:`wrapping_existing_test_or_benchmark`
42 """
43
44 @abstractproperty
45 def is_installed(self):
46 """
47 True if this test is installed
48
49 .. versionadded:: 0.2
50 """
51
52 @abstractmethod
53 def install(self, observer):
54 """
55 Install the test program.
56
57 This creates an install directory under the user's XDG_DATA_HOME
58 directory to mark that the test is installed. The installer's
59 install() method is then called from this directory to complete any
60 test specific install that may be needed.
61
62 :param observer:
63 Observer object that makes it possible to monitor the actions
64 performed by the test installer.
65 :type observer: :class:`~lava_test.api.observers.ITestInstallerObserver`
66
67 .. versionadded:: 0.2
68 """
69
70 @abstractmethod
71 def uninstall(self):
72 """
73 Remove the test program
74
75 Recursively remove test specific directory under the user's
76 ``XDG_DATA_HOME directory``. This will both mark the test as removed,
77 and clean up any files that were downloaded or installed under that
78 directory. Dependencies are intentionally not removed by this.
79
80 .. versionadded:: 0.1
81 """
82
83 @abstractmethod
84 def run(self, observer):
85 """
86 Run the test program and store artifacts.
87
88 :param observer:
89 Observer object that makes it possible to monitor the actions
90 performed by the test runner.
91 :type observer: :class:`~lava_test.api.observers.ITestRunnerObserver`
92 :return: Test run artifacts
93 :rtype: :class:`~lava_test.core.artifacts.TestArtifacts`.
94
95 .. versionadded:: 0.2
96 """
97
98 @abstractmethod
99 def parse(self, artifacts):
100 """
101 Parse the artifacts of an earlier run.
102
103 :param artifacts: Object that describes which files should be parsed.
104 :type artifacts: :class:`~lava_test.core.artifacts.TestArtifacts`
105 :return:
106 A dictionary with all the parsed data. In particular this is a
107 TestRun part of the dashboard bundle so it should have the
108 test_results list of all the results parsed from the artifacts.
109 :rtype: :class:`dict`
110
111 .. versionadded:: 0.2
112 """
113
114
115class ITestProvider(_Interface):
116 """
117 Source of ITest instances.
118
119 Test providers can be used to make lava-test aware of arbitrary collections
120 of tests that can be installed and invoked. Internally lava-test uses this
121 class to offer built-in tests (via the
122 :class:`~lava_test.providers.BuiltInProvider`), out-of-tree tests (via the
123 :class:`~lava_test.providers.PkgResourcesProvider`) and declarative tests
124 (via the :class:`~lava_test.providers.RegistryProvider`).
125
126 Normally this is not something you would need to implement. If you have a
127 large collection of existing tests that can be somehow adapted in bulk, or
128 you have your own internal registry of tests that could be adapted this way
129 then you might use this interface to simplify test discovery.
130
131 Test providers need to be registered using pkg-resources entry-point
132 feature and then added to the lava-test configuration file. See
133 :class:`lava_test.config.LavaTestConfig` for details.
134
135 .. versionadded:: 0.2
136 """
137
138 @abstractmethod
139 def __init__(self, config):
140 """
141 Initialize test provider with the specified configuration object. The
142 configuration object is obtained from the test tool providers registry.
143 """
144
145 @abstractmethod
146 def __iter__(self):
147 """
148 Iterates over instances of ITest exposed by this provider
149 """
150
151 @abstractmethod
152 def __getitem__(self, test_id):
153 """
154 Return an instance of ITest with the specified id
155 """
156
157 @abstractproperty
158 def description(self):
159 """
160 The description string used by `lava-test list-tests`
161 """
162
163
164__all__ = ['ITest', 'ITestProvider']
0165
=== added file 'lava_test/api/delegates.py'
--- lava_test/api/delegates.py 1970-01-01 00:00:00 +0000
+++ lava_test/api/delegates.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,119 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16
17"""
18:mod:`lava_test.api.delegates` -- Interface classes for test delegates
19======================================================================
20
21.. module: lava_test.api.delegates
22
23 :synopsis: Interface classes for test delegates
24"""
25
26from abc import abstractmethod, abstractproperty
27
28from lava_test.api import _Interface
29
30
31class ITestInstaller(_Interface):
32 """
33 Test installer delegate class.
34
35 Wraps the knowledge on how to install a test. It is most helpful with
36 :class:`~lava_test.core.tests.Test` that delegates actual actions to helper
37 classes.
38
39 .. versionadded:: 0.2
40 """
41
42 @abstractmethod
43 def install(self, observer):
44 """
45 Install the test program.
46
47 :param observer:
48 Observer object that makes it possible to monitor the actions
49 performed by the test installer.
50 :type observer: :class:`~lava_test.api.observers.ITestInstallerObserver`
51
52 .. versionadded:: 0.2
53 """
54
55
56class ITestRunner(_Interface):
57 """
58 Test runner delegate.
59
60 Wraps the knowledge on how to run a test. It is most helpful with
61 :class:`lava_test.core.tests.Test` that delegates actual actions to
62 helper classes.
63
64 .. versionadded:: 0.2
65 """
66
67 @abstractmethod
68 def run(self, artifacts, observer):
69 """
70 Run the test and create artifacts (typically log files).
71
72 Artifacts must be created in the directory specified by various methods
73 and properties of of :class:`lava_test.core.TestArtifacts`.
74
75 :param artifacts:
76 Object that describes where to store test run artifacts
77 :type artifacts: :class:`~lava_test.core.artifacts.TestArtifacts`.
78 :param observer:
79 Observer object that makes it possible to monitor the actions
80 performed by the test runner.
81 :type observer: :class:`~lava_test.api.observers.ITestRunnerObserver`
82
83 .. versionadded:: 0.2
84 """
85
86
87class ITestParser(_Interface):
88 """
89 Test artefact parser delegate.
90
91 Wraps the knowledge on how to parse the artifacts of a previous test run.
92 It is most helpful with :class:`~lava_test.core.tests.Test` that delegates
93 actual actions to helper classes.
94
95 .. versionadded:: 0.2
96 """
97
98 @abstractmethod
99 def parse(self, artifacts):
100 """
101 Parse the artifacts of a previous test run and return a dictionary with
102 a partial TestRun object.
103
104 :param artifacts:
105 Object that describes where to find test run artifacts
106 :type artifacts: :class:`~lava_test.core.artifacts.TestArtifacts`.
107
108 .. versionadded:: 0.2
109 """
110
111 @abstractproperty
112 def results(self):
113 """
114 Results dictionary to be merged with TestRun object inside the bundle.
115
116 .. seealso:: :meth:`~lava_test.core.artifacts.TestArtifacts.incorporate_parse_results`
117
118 .. versionadded:: 0.1
119 """
0120
=== added file 'lava_test/api/observers.py'
--- lava_test/api/observers.py 1970-01-01 00:00:00 +0000
+++ lava_test/api/observers.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,120 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16
17"""
18:mod:`lava_test.api.observers` -- Interface classes for observer classes
19========================================================================
20
21.. module: lava_test.api.observers
22 :synopsis: Interface classes for observer classes
23"""
24
25from abc import abstractmethod
26
27from lava_test.api import _Interface
28
29
30class IShellCommandObserver(_Interface):
31 """
32 Shell command runner observer class.
33
34 Allows the caller to observe shell commands that occur during some
35 operation. It is used by the command line UI.
36
37 .. versionadded:: 0.2
38 """
39
40 @abstractmethod
41 def about_to_run_shell_command(self, cmd):
42 """
43 Method called when a shell command is about to be invoked by the
44 observed object.
45
46 .. versionadded:: 0.2
47 """
48
49 @abstractmethod
50 def did_run_shell_command(self, cmd, returncode):
51 """
52 Method called when a shell command has been invoked by the observed
53 object.
54
55 .. versionadded:: 0.2
56 """
57
58 @abstractmethod
59 def display_subprocess_output(self, stream_name, line):
60 """
61 Method called for each line of stdout/stderr as obtained from a
62 subprocess.
63
64 .. versionadded:: 0.2
65 """
66
67
68class ITestInstallerObserver(IShellCommandObserver):
69 """
70 Test installer observer class.
71
72 Allows the caller to observe interesting actions that occur during
73 installation process. It is used by the command line UI.
74
75 .. versionadded:: 0.2
76 """
77
78 @abstractmethod
79 def about_to_install_packages(self, package_list):
80 """
81 Method called when a list of packages is about to be installed by the
82 installer
83
84 .. versionadded:: 0.2
85 """
86
87 @abstractmethod
88 def did_install_packages(self, package_list):
89 """
90 Method called when a package has been installed by the installer
91
92 .. versionadded:: 0.2
93 """
94
95 @abstractmethod
96 def about_to_download_file(self, url):
97 """
98 Method called when a file is about to be downloaded
99
100 .. versionadded:: 0.2
101 """
102
103 @abstractmethod
104 def did_download_file(self, url):
105 """
106 Method called when a file has been downloaded
107
108 .. versionadded:: 0.2
109 """
110
111
112class ITestRunnerObserver(IShellCommandObserver):
113 """
114 Test runner observer class.
115
116 Allows the caller to observe interesting actions that occur during testing
117 process. It is used by the command line UI.
118
119 .. versionadded:: 0.2
120 """
0121
=== added file 'lava_test/commands.py'
--- lava_test/commands.py 1970-01-01 00:00:00 +0000
+++ lava_test/commands.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,383 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16
17import os
18import subprocess
19
20from lava_tool.interface import Command as LavaCommand
21from lava_tool.interface import LavaCommandError
22import versiontools
23
24from lava_test.api.observers import (
25 ITestInstallerObserver,
26 ITestRunnerObserver)
27from lava_test.core.artifacts import TestArtifacts
28from lava_test.core.config import get_config
29from lava_test.core.loader import TestLoader
30
31
32class Command(LavaCommand, ITestInstallerObserver, ITestRunnerObserver):
33
34 def __init__(self, parser, args):
35 super(Command, self).__init__(parser, args)
36 self._config = get_config()
37 self._test_loader = TestLoader(self._config)
38
39 @classmethod
40 def register_arguments(cls, parser):
41 parser.add_argument(
42 "-q", "--quiet",
43 action="store_true",
44 default=False,
45 help="Be less verbose about undertaken actions")
46 parser.add_argument(
47 "-Q", "--quiet-subcommands",
48 action="store_true",
49 default=False,
50 help="Hide the output of all sub-commands (including tests)")
51
52 def say(self, text, *args, **kwargs):
53 print "LAVA:", text.format(*args, **kwargs)
54
55 def about_to_install_packages(self, package_list):
56 if self.args.quiet:
57 return
58 self.say("Installing packages: {0}", ", ".join(package_list))
59
60 def about_to_run_shell_command(self, cmd):
61 if self.args.quiet:
62 return
63 self.say("Running shell command: {0!r}", cmd)
64
65 def about_to_download_file(self, url):
66 if self.args.quiet:
67 return
68 self.say("Downloading file from: {0!r}", url)
69
70 def did_install_packages(self, package_list):
71 pass
72
73 def did_run_shell_command(self, cmd, returncode):
74 if returncode is None:
75 self.say("Command {0!r} was terminated prematurely", cmd)
76 elif returncode != 0:
77 self.say("Command {0!r} returned non-zero exit status {1}",
78 cmd, returncode)
79
80 def did_download_file(self, url):
81 pass
82
83 def display_subprocess_output(self, stream_name, line):
84 if self.args.quiet_subcommands:
85 return
86 if stream_name == 'stdout':
87 self.say('(stdout) {0}', line.rstrip())
88 elif stream_name == 'stderr':
89 self.say('(stderr) {0}', line.rstrip())
90
91
92class list_tests(Command):
93 """
94 List available tests
95
96 .. program:: lava-test list-tests
97
98 Lists all available tests, grouping them by provider.
99 """
100
101 def invoke(self):
102 for provider in self._test_loader.get_providers():
103 test_list = [provider[test_id] for test_id in provider]
104 if not test_list:
105 continue
106 self.say("{0}", provider.description)
107 for test in test_list:
108 self.say(" - {test_id}", test_id=test.test_id)
109
110
111class list_installed(Command):
112 """
113 List installed tests
114 """
115
116 def invoke(self):
117 for provider in self._test_loader.get_providers():
118 test_list = [provider[test_id] for test_id in provider]
119 if not test_list:
120 continue
121 self.say("{0}", provider.description)
122 count = 0
123 for test in test_list:
124 if not test.is_installed:
125 continue
126 self.say(" - {test_id}", test_id=test.test_id)
127 count += 1
128 if not count:
129 self.say("No tests installed")
130
131
132
133class TestAffectingCommand(Command):
134
135 INSTALL_REQUIRED = False
136
137 @classmethod
138 def register_arguments(cls, parser):
139 super(TestAffectingCommand, cls).register_arguments(parser)
140 parser.add_argument("test_id",
141 help="Test or test suite identifier")
142
143 def invoke(self):
144 try:
145 test = self._test_loader[self.args.test_id]
146 except KeyError:
147 raise LavaCommandError("There is no test with the specified ID")
148 return self.invoke_with_test(test)
149
150
151class install(TestAffectingCommand):
152 """
153 Install a test program
154 """
155
156 def invoke_with_test(self, test):
157 if test.is_installed:
158 raise LavaCommandError("This test is already installed")
159 try:
160 test.install(self)
161 except (subprocess.CalledProcessError, RuntimeError) as ex:
162 raise LavaCommandError(str(ex))
163
164
165class uninstall(TestAffectingCommand):
166 """
167 Uninstall a test program
168 """
169
170 def invoke_with_test(self, test):
171 if not test.is_installed:
172 raise LavaCommandError("This test is not installed")
173 test.uninstall()
174
175
176class run(TestAffectingCommand):
177 """
178 Run a previously installed test program
179 """
180
181 @classmethod
182 def register_arguments(cls, parser):
183 super(run, cls).register_arguments(parser)
184 group = parser.add_argument_group("initial bundle configuration")
185 group.add_argument("-S", "--skip-software-context",
186 default=False,
187 action="store_true",
188 help=("Do not store the software context in the"
189 " initial bundle. Typically this saves OS"
190 " image name and all the installed software"
191 " packages."))
192 group.add_argument("-H", "--skip-hardware-context",
193 default=False,
194 action="store_true",
195 help=("Do not store the hardware context in the"
196 " initial bundle. Typically this saves CPU,"
197 " memory and USB device information."))
198 group.add_argument("--trusted-time",
199 default=False,
200 action="store_true",
201 help=("Indicate that the real time clock has"
202 " accurate data. This can differentiate"
203 " test results created on embedded devices"
204 " that often have inaccurate real time"
205 " clock settings."))
206 group = parser.add_argument_group("complete bundle configuration")
207 group.add_argument("-o", "--output",
208 default=None,
209 metavar="FILE",
210 help=("After running the test parse the result"
211 " artifacts, fuse them with the initial"
212 " bundle and finally save the complete bundle"
213 " to the specified FILE."))
214 group.add_argument("-A", "--skip-attachments",
215 default=False,
216 action="store_true",
217 help=("Do not store standard output and standard"
218 " error log files as attachments. This"
219 " option is only affecting the bundle"
220 " created with --output, the initial bundle"
221 " is not affected as it never stores any"
222 " attachments."))
223
224 def invoke_with_test(self, test):
225 if not test.is_installed:
226 raise LavaCommandError("The specified test is not installed")
227 try:
228 artifacts = test.run(self)
229 except subprocess.CalledProcessError as ex:
230 if ex.returncode is None:
231 raise LavaCommandError("Command %r was aborted" % ex.cmd)
232 else:
233 raise LavaCommandError(str(ex))
234 except RuntimeError as ex:
235 raise LavaCommandError(str(ex))
236 self.say("run complete, result_id is {0!r}", artifacts.result_id)
237 artifacts.create_initial_bundle(
238 self.args.skip_software_context,
239 self.args.skip_hardware_context,
240 self.args.trusted_time)
241 artifacts.save_bundle()
242 if self.args.output:
243 parse_results = test.parse(artifacts)
244 artifacts.incorporate_parse_results(parse_results)
245 if not self.args.skip_attachments:
246 artifacts.attach_standard_files_to_bundle()
247 artifacts.save_bundle_as(self.args.output)
248
249
250class parse(TestAffectingCommand):
251 """
252 Parse the results of previous test run
253 """
254
255 @classmethod
256 def register_arguments(cls, parser):
257 super(parse, cls).register_arguments(parser)
258 parser.add_argument("result_id",
259 help="Test run result identifier")
260 group = parser.add_argument_group("complete bundle configuration")
261 group.add_argument("-o", "--output",
262 default=None,
263 metavar="FILE",
264 help=("After running the test parse the result"
265 " artifacts, fuse them with the initial"
266 " bundle and finally save the complete bundle"
267 " to the specified FILE."))
268 group.add_argument("-A", "--skip-attachments",
269 default=False,
270 action="store_true",
271 help=("Do not store standard output and standard"
272 " error log files as attachments. This"
273 " option is only affecting the bundle"
274 " created with --output, the initial bundle"
275 " is not affected as it never stores any"
276 " attachments."))
277
278 def invoke_with_test(self, test):
279 artifacts = TestArtifacts(
280 self.args.test_id, self.args.result_id, self._config)
281 if not os.path.exists(artifacts.bundle_pathname):
282 raise LavaCommandError("Specified result does not exist")
283 artifacts.load_bundle()
284 parse_results = test.parse(artifacts)
285 artifacts.incorporate_parse_results(parse_results)
286 self.say("Parsed {0} test results",
287 len(artifacts.bundle["test_runs"][0]["test_results"]))
288 print artifacts.dumps_bundle()
289 if self.args.output:
290 if not self.args.skip_attachments:
291 artifacts.attach_standard_files_to_bundle()
292 artifacts.save_bundle_as(self.args.output)
293
294
295class show(Command):
296 """
297 Display the output from a previous test run
298 """
299
300 @classmethod
301 def register_arguments(cls, parser):
302 super(show, cls).register_arguments(parser)
303 parser.add_argument("result_id",
304 help="Test run result identifier")
305
306 def invoke(self):
307 artifacts = TestArtifacts(None, self.args.result_id, self._config)
308 if not os.path.exists(artifacts.results_dir):
309 raise LavaCommandError("Specified result does not exist")
310 if os.path.exists(artifacts.stdout_pathname):
311 with open(artifacts.stdout_pathname, "rt") as stream:
312 for line in iter(stream.readline, ''):
313 self.display_subprocess_output("stdout", line)
314 if os.path.exists(artifacts.stderr_pathname):
315 with open(artifacts.stderr_pathname, "rt") as stream:
316 for line in iter(stream.readline, ''):
317 self.display_subprocess_output("stderr", line)
318
319
320class version(Command):
321 """
322 Show LAVA Test version
323 """
324
325 def invoke(self):
326 self.say("version details:")
327 for framework in self._get_frameworks():
328 self.say(" - {framework}: {version}",
329 framework=framework.__name__,
330 version=versiontools.format_version(
331 framework.__version__, framework))
332
333 def _get_frameworks(self):
334 import lava_tool
335 import lava_test
336 import linaro_dashboard_bundle
337 import linaro_json
338 return [
339 lava_test,
340 lava_tool,
341 linaro_dashboard_bundle,
342 linaro_json]
343
344
345class register_test(Command):
346 """
347 Register remote test
348 """
349
350 @classmethod
351 def register_arguments(cls, parser):
352 super(register_test, cls).register_arguments(parser)
353 parser.add_argument("test_url",
354 help="Url for test definition file")
355
356 def invoke(self):
357 try:
358 from lava_test.core.providers import RegistryProvider
359 RegistryProvider.register_remote_test(self.args.test_url)
360 except ValueError as exc:
361 raise LavaCommandError("Unable to register test: %s" % exc)
362 except KeyError:
363 raise LavaCommandError("There is no test_url")
364
365class unregister_test(Command):
366 """
367 Unregister remote test
368 """
369
370 @classmethod
371 def register_arguments(cls, parser):
372 super(unregister_test, cls).register_arguments(parser)
373 parser.add_argument("test_url",
374 help="Url for test definition file")
375
376 def invoke(self):
377 try:
378 from lava_test.core.providers import RegistryProvider
379 RegistryProvider.unregister_remote_test(self.args.test_url)
380 except ValueError as exc:
381 raise LavaCommandError("Unable to unregister test: %s" % exc)
382 except KeyError:
383 raise LavaCommandError("There is no test_url")
0384
=== added directory 'lava_test/core'
=== added file 'lava_test/core/__init__.py'
=== added file 'lava_test/core/artifacts.py'
--- lava_test/core/artifacts.py 1970-01-01 00:00:00 +0000
+++ lava_test/core/artifacts.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,277 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from __future__ import absolute_import
17
18import base64
19import datetime
20import logging
21import os
22import uuid
23
24from linaro_dashboard_bundle.io import DocumentIO
25
26from lava_test.core import hwprofile, swprofile
27from lava_test.utils import merge_dict, mkdir_p
28
29
30class TestArtifacts(object):
31 """
32 Class representing test run artifacts, that is, static leftovers
33 independent of the wrapper class that encapsulates test handling.
34
35 .. versionadded:: 0.2
36 """
37
38 def __init__(self, test_id, result_id, config):
39 self._test_id = test_id
40 self._result_id = result_id
41 self._config = config
42 self._bundle = None
43
44 @classmethod
45 def allocate(cls, test_id, config):
46 """
47 Allocate new test artifacts object that corresponds to the specified
48 test_id. This constructs a new result_id and creates the corresponding
49 filesystem directory that holds those artifacts.
50
51 .. versionadded:: 0.2
52 """
53 result_id = (
54 "{test_id}.{time.tm_year:04}-{time.tm_mon:02}-{time.tm_mday:02}T"
55 "{time.tm_hour:02}:{time.tm_min:02}:{time.tm_sec:02}Z").format(
56 test_id=test_id,
57 time=datetime.datetime.utcnow().timetuple())
58 self = cls(test_id, result_id, config)
59 logging.debug("Creating result directory: %r", self.results_dir)
60 mkdir_p(self.results_dir)
61 return self
62
63 @property
64 def test_id(self):
65 """
66 The ID of the test this run is associated with
67
68 .. versionadded:: 0.2
69 """
70 return self._test_id
71
72 @property
73 def result_id(self):
74 """
75 The ID of the test run.
76
77 This field is different from analyzer_assigned_uuid at this time but
78 may change in the future. The purpose of this field is to identify the
79 test run and be able to locate attachments/log files/bundle on the file
80 system.
81
82 .. versionadded:: 0.2
83 """
84 return self._result_id
85
86 @property
87 def results_dir(self):
88 """
89 Pathname of a directory with test run artifacts (log files, crash
90 dumps, etc).
91
92 .. versionadded:: 0.2
93 """
94 return os.path.join(self._config.resultsdir, self.result_id)
95
96 def load_bundle(self):
97 """
98 Load the results bundle from disk.
99
100 The bundle is also validated if linaro-dashboard-bundle library is
101 installed.
102 """
103 with open(self.bundle_pathname, 'rt') as stream:
104 self._bundle = DocumentIO.load(stream)[1]
105
106 def dumps_bundle(self):
107 return DocumentIO.dumps(self._bundle)
108
109 def save_bundle(self):
110 """
111 Save the results bundle to the disk
112
113 The bundle is also validated if linaro-dashboard-bundle library is
114 installed.
115 """
116 self.save_bundle_as(self.bundle_pathname)
117
118 def save_bundle_as(self, pathname):
119 """
120 Save the results bundle to the specified file on disk.
121
122 The bundle should have been created or loaded earlier
123 """
124 with open(pathname, 'wt') as stream:
125 DocumentIO.dump(stream, self._bundle)
126
127 @property
128 def bundle(self):
129 """
130 The deserialized bundle object.
131
132 This can be either created with create_bundle() or loaded
133 from disk with load_bundle()
134 """
135 return self._bundle
136
137 def create_initial_bundle(self,
138 skip_software_context=False,
139 skip_hardware_context=False,
140 time_check_performed=False):
141 """
142 Create the bundle object.
143
144 This creates a typical bundle structure. Optionally it can also add
145 software and hardware context information.
146
147 For a complete bundle you may want to add attachments and incorporate
148 parse results by calling appropriate methods after loading or creating
149 the initial bundle.
150 """
151 TIMEFORMAT = '%Y-%m-%dT%H:%M:%SZ'
152 # Generate UUID and analyzer_assigned_date for the test run
153 analyzer_assigned_uuid = str(uuid.uuid1())
154 analyzer_assigned_date = datetime.datetime.utcnow()
155 # Create basic test run structure
156 test_run = {
157 'test_id': self.test_id,
158 'analyzer_assigned_date': analyzer_assigned_date.strftime(
159 TIMEFORMAT),
160 'analyzer_assigned_uuid': analyzer_assigned_uuid,
161 'time_check_performed': time_check_performed,
162 "test_results": [],
163 "attachments": [],
164 }
165 # Store hardware and software context if requested
166 if not skip_software_context:
167 test_run['software_context'] = swprofile.get_software_context()
168 if not skip_hardware_context:
169 test_run['hardware_context'] = hwprofile.get_hardware_context()
170 # Create the bundle object
171 self._bundle = {
172 'format': 'Dashboard Bundle Format 1.2',
173 'test_runs': [test_run]}
174
175 @property
176 def test_run(self):
177 try:
178 return self._bundle["test_runs"][0]
179 except KeyError:
180 raise AttributeError("test_run can be accessed only after you load"
181 " or create an initial bundle")
182
183 def attach_file(self, real_pathname, stored_pathname, mime_type):
184 """
185 Append an attachment to the test run.
186
187 The file is only attached if real_pathname designates an existing,
188 nonempty file. If the mime_type starts with 'text/' the file is opened
189 in text mode, otherwise binary mode is used.
190 """
191 if not os.path.exists(real_pathname):
192 return
193 if mime_type.startswith('text/'):
194 mode = 'rt'
195 else:
196 mode = 'rb'
197 with open(real_pathname, mode) as stream:
198 data = stream.read()
199 if not data:
200 return
201 self.test_run['attachments'].append({
202 "pathname": stored_pathname,
203 "mime_type": mime_type,
204 "content": base64.standard_b64encode(data)})
205
206 def incorporate_parse_results(self, parse_results):
207 """
208 Merge the data returned by the test parser into the current test run.
209
210 Non-overlapping data is simply added. Overlapping data is either merged
211 (lists are extended, dictionaries are recursively merged) or
212 overwritten (all other types).
213 """
214 assert isinstance(parse_results, dict)
215 # Use whatever the parser gave us to improve the results
216 logging.debug("Using parser data to enrich test run details")
217 merge_dict(self.test_run, parse_results)
218
219 def attach_standard_files_to_bundle(self):
220 """
221 Attach standard output and standard error log files to the bundle.
222
223 Both files are only attached if exist and non-empty. The attachments
224 are actually associated with a test run, not a bundle, but the
225 description is good enough for simplicity.
226 """
227 self.attach_file(self.stdout_pathname, "testoutput.log", "text/plain")
228 self.attach_file(self.stderr_pathname, "testoutput.err", "text/plain")
229
230 @property
231 def bundle_pathname(self):
232 """
233 Pathname of the result bundle.
234
235 The bundle contains the snapshot of environment information as well as
236 test identity and is created when you invoke ITest.run().
237
238 The bundle file name is always "testdata.json"
239
240 .. versionadded:: 0.2
241 """
242 return self.get_artefact_pathname("testdata.json")
243
244 @property
245 def stdout_pathname(self):
246 """
247 Pathname of the log file of the standard output as returned by the test
248 program.
249
250 The log file name is always "testoutput.log"
251
252 .. versionadded:: 0.2
253 """
254 return self.get_artefact_pathname("testoutput.log")
255
256 @property
257 def stderr_pathname(self):
258 """
259 Pathname of the log file of the standard output as returned by the test
260 program.
261
262 The log file name is always "testoutput.err"
263
264 .. versionadded:: 0.2
265 """
266 return self.get_artefact_pathname("testoutput.err")
267
268 def get_artefact_pathname(self, artefact_name):
269 """
270 Return a pathname of a test run artefact file.
271
272 This is more useful than hard-coding the path as it allows the test
273 runner not to worry about the location of the results directory.
274
275 .. versionadded:: 0.2
276 """
277 return os.path.join(self.results_dir, artefact_name)
0278
=== added file 'lava_test/core/config.py'
--- lava_test/core/config.py 1970-01-01 00:00:00 +0000
+++ lava_test/core/config.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,97 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import os
17import json
18
19
20class LavaTestConfig(object):
21
22 def __init__(self):
23 home = os.environ.get('HOME', '/')
24 baseconfig = os.environ.get('XDG_CONFIG_HOME',
25 os.path.join(home, '.config'))
26 basedata = os.environ.get('XDG_DATA_HOME',
27 os.path.join(home, '.local', 'share'))
28 self.configdir = os.path.join(baseconfig, 'lava_test')
29 self.installdir = os.path.join(basedata, 'lava_test', 'installed-tests')
30 self.resultsdir = os.path.join(basedata, 'lava_test', 'results')
31 self.registry = self._load_registry()
32
33 @property
34 def _registry_pathname(self):
35 return os.path.join(self.configdir, "registry.json")
36
37 def _load_registry(self):
38 try:
39 with open(self._registry_pathname) as stream:
40 return json.load(stream)
41 except (IOError, ValueError):
42 return self._get_default_registry()
43
44 def _save_registry(self):
45 if not os.path.exists(self.configdir):
46 os.makedirs(self.configdir)
47 with open(self._registry_pathname, "wt") as stream:
48 json.dump(self.registry, stream, indent=2)
49
50 def _get_default_registry(self):
51 return {
52 "format": "Lava-Test Test Registry 1.0",
53 "providers": [{
54 "entry_point": "lava_test.core.providers:BuiltInProvider"
55 }, {
56 "entry_point": "lava_test.core.providers:PkgResourcesProvider",
57 "config": {"namespace": "lava_test.test_definitions" }
58 },
59 {
60 "entry_point": "lava_test.core.providers:RegistryProvider",
61 "config": {"entries": [] }
62 }]
63 }
64
65 def get_provider_config(self, entry_point_name):
66 if "providers" not in self.registry:
67 self.registry["providers"] = []
68 for provider_info in self.registry["providers"]:
69 if provider_info.get("entry_point") == entry_point_name:
70 break
71 else:
72 provider_info = {"entry_point": entry_point_name}
73 self.registry["providers"].append(provider_info)
74 if "config" not in provider_info:
75 provider_info["config"] = {}
76 return provider_info["config"]
77
78 def get_logging_config_file(self):
79 logging_file = os.path.join(self.configdir, "logging.conf")
80 if os.path.exists(logging_file):
81 return logging_file
82 else:
83 return None
84
85_config = None
86
87
88def get_config():
89 global _config
90 if _config is not None:
91 return _config
92 return LavaTestConfig()
93
94
95def set_config(config):
96 global _config
97 _config = config
098
=== added file 'lava_test/core/hwprofile.py'
--- lava_test/core/hwprofile.py 1970-01-01 00:00:00 +0000
+++ lava_test/core/hwprofile.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,223 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import re
17import sys
18from subprocess import Popen, PIPE
19from lava_test.utils import read_file, get_machine_type
20
21
22INTEL_KEYMAP = {
23 'vendor_id': 'cpu_vendor_name',
24 'cpu family': 'cpu_family',
25 'model': 'cpu_model',
26 'model name': 'cpu_model_name',
27 'stepping': 'cpu_stepping',
28 'cpu MHz': 'cpu_mhz',
29 'flags': 'cpu_features',
30}
31
32
33INTEL_VALMAP = {
34 'cpu family': int,
35 'model': int,
36 'stepping': int,
37 'cpu MHz': float,
38}
39
40
41ARM_KEYMAP = {
42 'Processor': 'cpu_model_name',
43 'Features': 'cpu_features',
44 'CPU implementer': 'cpu_implementer',
45 'CPU architecture': 'cpu_architecture',
46 'CPU variant': 'cpu_variant',
47 'CPU part': 'cpu_part',
48 'CPU revision': 'cpu_revision',
49}
50
51
52ARM_VALMAP = {
53 'CPU implementer': lambda value: int(value, 16),
54 'CPU architecture': int,
55 'CPU variant': lambda value: int(value, 16),
56 'CPU part': lambda value: int(value, 16),
57 'CPU revision': int,
58}
59
60
61def _translate_cpuinfo(keymap, valmap, key, value):
62 """
63 Translate a key and value using keymap and valmap passed in
64 """
65 newkey = keymap.get(key, key)
66 newval = valmap.get(key, lambda x: x)(value)
67 return newkey, newval
68
69
70def get_cpu_devs():
71 """
72 Return a list of CPU devices
73 """
74 pattern = re.compile('^(?P<key>.+?)\s*:\s*(?P<value>.*)$')
75 cpunum = 0
76 devices = []
77 cpudevs = []
78 cpudevs.append({})
79 machine = get_machine_type()
80 if machine in ('i686', 'x86_64'):
81 keymap, valmap = INTEL_KEYMAP, INTEL_VALMAP
82 elif machine.startswith('arm'):
83 keymap, valmap = ARM_KEYMAP, ARM_VALMAP
84
85 try:
86 cpuinfo = read_file("/proc/cpuinfo")
87 for line in cpuinfo.splitlines():
88 match = pattern.match(line)
89 if match:
90 key, value = match.groups()
91 key, value = _translate_cpuinfo(keymap, valmap,
92 key, value)
93 if cpudevs[cpunum].get(key):
94 cpunum += 1
95 cpudevs.append({})
96 cpudevs[cpunum][key] = value
97 for c in range(len(cpudevs)):
98 device = {}
99 device['device_type'] = 'device.cpu'
100 device['description'] = 'Processor #{0}'.format(c)
101 device['attributes'] = cpudevs[c]
102 devices.append(device)
103 except IOError:
104 print >> sys.stderr, "WARNING: Could not read cpu information"
105 return devices
106
107
108def get_board_devs():
109 """
110 Return a list of board devices
111 """
112 devices = []
113 attributes = {}
114 device = {}
115 machine = get_machine_type()
116 if machine in ('i686', 'x86_64'):
117 try:
118 description = read_file('/sys/class/dmi/id/board_name') or None
119 vendor = read_file('/sys/class/dmi/id/board_vendor') or None
120 version = read_file('/sys/class/dmi/id/board_version') or None
121 if description:
122 device['description'] = description.strip()
123 if vendor:
124 attributes['vendor'] = vendor.strip()
125 if version:
126 attributes['version'] = version.strip()
127 except IOError:
128 print >> sys.stderr, "WARNING: Could not read board information"
129 return devices
130 elif machine.startswith('arm'):
131 try:
132 cpuinfo = read_file("/proc/cpuinfo")
133 if cpuinfo is None:
134 return devices
135 pattern = re.compile("^Hardware\s*:\s*(?P<description>.+)$", re.M)
136 match = pattern.search(cpuinfo)
137 if match is None:
138 return devices
139 device['description'] = match.group('description')
140 except IOError:
141 print >> sys.stderr, "WARNING: Could not read board information"
142 return devices
143 else:
144 return devices
145 if attributes:
146 device['attributes'] = attributes
147 device['device_type'] = 'device.board'
148 devices.append(device)
149 return devices
150
151
152def get_mem_devs():
153 """ Return a list of memory devices
154
155 This returns up to two items, one for physical RAM and another for swap
156 """
157 pattern = re.compile('^(?P<key>.+?)\s*:\s*(?P<value>.+) kB$', re.M)
158
159 devices = []
160 try:
161 meminfo = read_file("/proc/meminfo")
162 for match in pattern.finditer(meminfo):
163 key, value = match.groups()
164 if key not in ('MemTotal', 'SwapTotal'):
165 continue
166 capacity = int(value) << 10 # Kernel reports in 2^10 units
167 if capacity == 0:
168 continue
169 if key == 'MemTotal':
170 kind = 'RAM'
171 else:
172 kind = 'swap'
173 description = "{capacity}MiB of {kind}".format(
174 capacity=capacity >> 20, kind=kind)
175 device = {}
176 device['description'] = description
177 device['attributes'] = {'capacity': str(capacity), 'kind': kind}
178 device['device_type'] = "device.mem"
179 devices.append(device)
180 except IOError:
181 print >> sys.stderr, "WARNING: Could not read memory information"
182 return devices
183
184
185def get_usb_devs():
186 """
187 Return a list of usb devices
188 """
189 pattern = re.compile(
190 "^Bus \d{3} Device \d{3}: ID (?P<vendor_id>[0-9a-f]{4}):"
191 "(?P<product_id>[0-9a-f]{4}) (?P<description>.*)$")
192 devices = []
193 try:
194 for line in Popen('lsusb', stdout=PIPE).communicate()[0].splitlines():
195 match = pattern.match(line)
196 if match:
197 vendor_id, product_id, description = match.groups()
198 attributes = {}
199 device = {}
200 attributes['vendor_id'] = int(vendor_id, 16)
201 attributes['product_id'] = int(product_id, 16)
202 device['attributes'] = attributes
203 device['description'] = description
204 device['device_type'] = 'device.usb'
205 devices.append(device)
206 except OSError:
207 print >> sys.stderr, "WARNING: Could not read usb device information, \
208unable to run lsusb, please install usbutils package"
209 return devices
210
211
212def get_hardware_context():
213 """
214 Return a dict with all of the hardware profile information gathered
215 """
216 hardware_context = {}
217 devices = []
218 devices.extend(get_cpu_devs())
219 devices.extend(get_board_devs())
220 devices.extend(get_mem_devs())
221 devices.extend(get_usb_devs())
222 hardware_context['devices'] = devices
223 return hardware_context
0224
=== added file 'lava_test/core/installers.py'
--- lava_test/core/installers.py 1970-01-01 00:00:00 +0000
+++ lava_test/core/installers.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,105 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import hashlib
17import os
18
19from lava_test.api.delegates import ITestInstaller
20from lava_test.extcmd import ExternalCommandWithDelegate
21from lava_test.utils import geturl
22
23
24class TestInstaller(ITestInstaller):
25 """
26 Base class for defining an installer object.
27
28 This class can be used as-is for simple installers, or extended
29 for more advanced functionality.
30
31 :ivar steps:
32 List of steps to be executed in a shell
33
34 :ivar deps:
35 List of Debian or Ubuntu packages to apt-get install before running the
36 steps.
37
38 :ivar url:
39 Location from which the test suite should be downloaded.
40
41 :ivar md5:
42 The md5sum to check the integrety of the download
43 """
44 def __init__(self, steps=None, deps=None, url=None, md5=None, **kwargs):
45 self.steps = steps or []
46 self.deps = deps or []
47 self.url = url
48 self.md5 = md5
49
50 def __repr__(self):
51 return "<%s steps=%r deps=%r url=%r md5=%r>" % (
52 self.__class__.__name__,
53 self.steps, self.deps, self.url, self.md5)
54
55 def _run_shell_cmd(self, cmd, observer):
56 if observer: observer.about_to_run_shell_command(cmd)
57 extcmd = ExternalCommandWithDelegate(observer)
58 returncode = extcmd.check_call(cmd, shell=True)
59 if observer: observer.did_run_shell_command(cmd, returncode)
60
61 def _installdeps(self, observer):
62 if self.deps:
63 if observer: observer.about_to_install_packages(self.deps)
64 # XXX: Possible point of target-specific package installation
65 cmd = "sudo apt-get install -y " + " ".join(self.deps)
66 self._run_shell_cmd(cmd, observer)
67 if observer: observer.did_install_packages(self.deps)
68
69 def _download(self, observer):
70 """
71 Download the file specified by the url and check the md5.
72
73 Returns the path and filename if successful, otherwise return None
74 """
75 if not self.url:
76 return
77 if observer: observer.about_to_download_file(self.url)
78 filename = geturl(self.url)
79 # If the file does not exist, then the download was not
80 # successful
81 if not os.path.exists(filename):
82 raise RuntimeError(
83 "Failed to download %r" % self.url)
84 if observer: observer.did_download_file(self.url)
85 if self.md5:
86 checkmd5 = hashlib.md5()
87 with open(filename, 'rb') as fd:
88 data = fd.read(0x10000)
89 while data:
90 checkmd5.update(data)
91 data = fd.read(0x10000)
92 if checkmd5.hexdigest() != self.md5:
93 raise RuntimeError(
94 "md5sum mismatch of file %r, got %s expected %s" % (
95 filename, checkmd5.hexdigest(), self.md5))
96 return filename
97
98 def _runsteps(self, observer):
99 for cmd in self.steps:
100 self._run_shell_cmd(cmd, observer)
101
102 def install(self, observer=None):
103 self._installdeps(observer)
104 self._download(observer)
105 self._runsteps(observer)
0106
=== added file 'lava_test/core/loader.py'
--- lava_test/core/loader.py 1970-01-01 00:00:00 +0000
+++ lava_test/core/loader.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,83 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from __future__ import absolute_import
17from lava_test.core.config import get_config
18
19class TestLoader(object):
20 """
21 Test loader.
22
23 Encapsulates LAVA Test's knowledge of available tests.
24
25 Test can be loaded by name with
26 :meth:`lava_test.core.loader.TestLoader.__getitem__()`. Test can also be
27 listed by :meth:`lava_test.core.loader.TestLoader.get_providers()` and then
28 iterating over tests returned by each provider.
29 """
30
31 def __init__(self, config):
32 self._config = config
33
34 def get_providers(self):
35 """
36 Return a generator of available providers
37 """
38 import pkg_resources
39 for provider_info in self._config.registry.get("providers", []):
40 entry_point_name = provider_info.get("entry_point")
41 module_name, attrs = entry_point_name.split(':', 1)
42 attrs = attrs.split('.')
43 try:
44 entry_point = pkg_resources.EntryPoint(
45 entry_point_name, module_name, attrs,
46 dist=pkg_resources.get_distribution("lava-test"))
47 provider_cls = entry_point.load()
48 provider = provider_cls(provider_info.get("config", {}))
49 yield provider
50 except pkg_resources.DistributionNotFound:
51 raise RuntimeError(
52 "lava-test is not properly set up."
53 " Please read the README file")
54 except ImportError, err:
55 print "Couldn't load module : %s . Maybe configuration needs to be updated" % module_name
56 print "The configuration is stored at %s" %(get_config().configdir)
57
58
59
60 def __getitem__(self, test_id):
61 """
62 Lookup a test with the specified test_id
63 """
64 for provider in self.get_providers():
65 try:
66 return provider[test_id]
67 except KeyError:
68 pass
69 raise KeyError(test_id)
70
71 def get_test_by_name(self, test_id):
72 """
73 Lookup a test with the specified name
74
75 .. deprecated:: 0.2
76 Use __getitem__ instead
77 """
78 for provider in self.get_providers():
79 try:
80 return provider[test_id]
81 except KeyError:
82 pass
83 raise ValueError("No such test %r" % test_id)
084
=== added file 'lava_test/core/parsers.py'
--- lava_test/core/parsers.py 1970-01-01 00:00:00 +0000
+++ lava_test/core/parsers.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,147 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import decimal
17import os
18import re
19
20from lava_test.api.delegates import ITestParser
21
22
23class TestParser(ITestParser):
24 """
25 Base class for defining a test parser
26
27 This class can be used as-is for simple results parsers, but will likely
28 need to be extended slightly for many. If used as it is, the parse()
29 method should be called while already in the results directory and assumes
30 that a file for test output will exist called testoutput.log.
31
32 :ivar pattern:
33 regexp pattern to identify important elements of test output For
34 example: If your testoutput had lines that look like: "test01: PASS"
35 then you could use a pattern like this:
36 "^(?P<testid>\w+):\W+(?P<result>\w+)" This would result in
37 identifying "test01" as testid and "PASS" as result. Once parse()
38 has been called, self.results.test_results[] contains a list of
39 dicts of all the key,value pairs found for each test result.
40
41 :ivar fixupdict:
42 Dict of strings to convert test results to standard strings For
43 example: if you want to standardize on having pass/fail results in
44 lower case, but your test outputs them in upper case, you could use a
45 fixupdict of something like: {'PASS':'pass','FAIL':'fail'}
46
47 :ivar appendall:
48 Append a dict to the test_results entry for each result.
49 For example: if you would like to add units="MB/s" to each result:
50 appendall={'units':'MB/s'}
51
52 :ivar results:
53 Dictionary of data that was scrubbed from the log file for this test
54 run. Most notably it contains the test_results array.
55 """
56 def __init__(self, pattern=None, fixupdict=None, appendall={}):
57 if pattern is not None:
58 try:
59 re.compile(pattern)
60 except Exception as ex:
61 raise ValueError(
62 "Invalid regular expression %r: %s", pattern, ex)
63 self._results = {'test_results': []}
64 self.pattern = pattern
65 self.fixupdict = fixupdict
66 self.appendall = appendall
67
68 def __repr__(self):
69 return "<%s pattern=%r fixupdict=%r appendall=%r>" % (
70 self.__class__.__name__,
71 self.pattern, self.fixupdict, self.appendall)
72
73 @property
74 def results(self):
75 return self._results
76
77 def parse(self, artifacts):
78 if os.path.exists(artifacts.stdout_pathname):
79 return self.parse_pathname(
80 artifacts.stdout_pathname,
81 os.path.relpath(artifacts.stdout_pathname,
82 artifacts.results_dir))
83 if os.path.exists(artifacts.stderr_pathname):
84 return self.parse_pathname(
85 artifacts.stderr_pathname,
86 os.path.relpath(artifacts.stderr_pathname,
87 artifacts.results_dir))
88
89 def parse_pathname(self, pathname, relative_pathname=None):
90 with open(pathname, 'rt') as stream:
91 for lineno, line in enumerate(stream, 1):
92 match = re.search(self.pattern, line)
93 if not match:
94 continue
95 data = match.groupdict()
96 data["log_filename"] = relative_pathname or pathname
97 data["log_lineno"] = lineno
98 self._results['test_results'].append(
99 self.analyze_test_result(data))
100 return self.results
101
102 @property
103 def badchars(self):
104 return "[^a-zA-Z0-9\._-]"
105
106 def analyze_test_result(self, data):
107 """
108 Analyze sigle match (typically single line) and convert it into a
109 proper test result object.
110
111 Currently this method does the following transformations:
112 * measurement is converted to decimal if present
113 * test_case_id is rewritten to strip badchars
114 * test_case_id is rewritten to convert spaces to underscores
115 * result is transformed using fixuptdict, if defined
116 * appendall information is added, if defined
117 """
118 if 'measurement' in data:
119 try:
120 data['measurement'] = decimal.Decimal(data['measurement'])
121 except decimal.InvalidOperation:
122 del data['measurement']
123 if 'test_case_id' in data:
124 data['test_case_id'] = re.sub(self.badchars, "",
125 data['test_case_id'])
126 data['test_case_id'] = data['test_case_id'].replace(" ", "_")
127 if 'result' in data and self.fixupdict:
128 data['result'] = self.fixupdict[data['result']]
129 if self.appendall:
130 data.update(self.appendall)
131 return data
132
133
134class NativeTestParser(ITestParser):
135 """
136 Unfinished native test parser.
137
138 This was meant to be a pass-through for tests that directly create bundles
139 """
140 def __init__(self, test_def):
141 self.test_def = test_def
142
143 def parse(self, artifacts):
144 raise NotImplementedError()
145
146 def results(self):
147 raise NotImplementedError()
0148
=== added file 'lava_test/core/providers.py'
--- lava_test/core/providers.py 1970-01-01 00:00:00 +0000
+++ lava_test/core/providers.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,165 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from lava_test.api.core import ITestProvider
17from lava_test.core.config import get_config
18from lava_test.core.tests import DeclarativeTest
19from lava_test.utils import Cache
20
21
22class BuiltInProvider(ITestProvider):
23 """
24 Test provider that provides tests shipped in the Lava-Test source tree
25 """
26
27 _builtin_tests = [
28 'glmemperf',
29 'gmpbench',
30 'gtkperf',
31 'ltp',
32 'posixtestsuite',
33 'pwrmgmt',
34 'stream',
35 'tiobench',
36 'x11perf',
37 ]
38
39 def __init__(self, config):
40 pass
41
42 @property
43 def description(self):
44 return "Tests built directly into LAVA Test:"
45
46 def __iter__(self):
47 return iter(self._builtin_tests)
48
49 def __getitem__(self, test_id):
50 if test_id not in self._builtin_tests:
51 raise KeyError(test_id)
52 module = __import__("lava_test.test_definitions.%s" % test_id,
53 fromlist=[''])
54 return module.testobj
55
56
57class PkgResourcesProvider(ITestProvider):
58 """
59 Test provider that provides tests declared in pkg_resources working_set
60
61 By default it looks at the 'lava_test.test_definitions' name space but it can
62 be changed with custom 'namespace' configuration entry.
63 """
64
65 def __init__(self, config):
66 self._config = config
67
68 @property
69 def namespace(self):
70 return self._config.get("namespace", "lava_test.test_definitions")
71
72 @property
73 def description(self):
74 return ("Tests provided by installed python packages"
75 " (from namespace {0}):").format(self.namespace)
76
77 def __iter__(self):
78 from pkg_resources import working_set
79 for entry_point in working_set.iter_entry_points(self.namespace):
80 yield entry_point.name
81
82 def __getitem__(self, test_name):
83 from pkg_resources import working_set
84 for entry_point in working_set.iter_entry_points(self.namespace,
85 test_name):
86 return entry_point.load().testobj
87 raise KeyError(test_name)
88
89
90class RegistryProvider(ITestProvider):
91 """
92 Test provider that provides declarative tests listed in the test registry.
93 """
94 def __init__(self, config):
95 self._config = config
96 self._cache = None
97
98 @property
99 def entries(self):
100 """
101 List of URLs to DeclarativeTest description files
102 """
103 return self._config.get("entries", [])
104
105 @property
106 def description(self):
107 return "Tests provided by LAVA Test registry:"
108
109 @classmethod
110 def register_remote_test(self, test_url):
111 config = get_config() # This is a different config object from
112 # self._config
113 provider_config = config.get_provider_config(
114 "lava_test.core.providers:RegistryProvider")
115 if "entries" not in provider_config:
116 provider_config["entries"] = []
117 if test_url not in provider_config["entries"]:
118 provider_config["entries"].append(test_url)
119 config._save_registry()
120 else:
121 raise ValueError("This test is already registered")
122
123 @classmethod
124 def unregister_remote_test(self, test_url):
125 config = get_config() # This is a different config object from
126 # self._config
127 provider_config = config.get_provider_config(
128 "lava_test.core.providers:RegistryProvider")
129 if "entries" not in provider_config:
130 provider_config["entries"] = []
131 if test_url in provider_config["entries"]:
132 provider_config["entries"].remove(test_url)
133 config._save_registry()
134 else:
135 raise ValueError("This test is not registered")
136
137 def _load_remote_test(self, test_url):
138 """
139 Load DeclarativeTest from a (possibly cached copy of) test_url
140 """
141 cache = Cache.get_instance()
142 with cache.open_cached_url(test_url) as stream:
143 return DeclarativeTest.load_from_stream(stream)
144
145 def _fill_cache(self):
146 """
147 Fill the cache of all remote tests
148 """
149 if self._cache is not None:
150 return
151 self._cache = {}
152 for test_url in self.entries:
153 test = self._load_remote_test(test_url)
154 if test.test_id in self._cache:
155 raise ValueError("Duplicate test %s declared" % test.test_id)
156 self._cache[test.test_id] = test
157
158 def __iter__(self):
159 self._fill_cache()
160 for test_id in self._cache.iterkeys():
161 yield test_id
162
163 def __getitem__(self, test_id):
164 self._fill_cache()
165 return self._cache[test_id]
0166
=== added file 'lava_test/core/runners.py'
--- lava_test/core/runners.py 1970-01-01 00:00:00 +0000
+++ lava_test/core/runners.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,66 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import datetime
17
18from lava_test.api.delegates import ITestRunner
19from lava_test.extcmd import (DisplayDelegate, ExternalCommandWithDelegate)
20
21
22class TestRunner(ITestRunner):
23 """
24 Base class for defining an test runner object.
25
26 This class can be used as-is for simple execution with the expectation that
27 the run() method will be called from the directory where the test was
28 installed. Steps, if used, should handle changing directories from there to
29 the directory where the test was extracted if necessary. This class can
30 also be extended for more advanced functionality.
31
32 :ivar steps:
33 list of shell commands to execute
34 """
35 def __init__(self, steps=None):
36 self.steps = steps or []
37 self.testoutput = [] # XXX: is this still used?
38
39 def __repr__(self):
40 return "<%s steps=%r>" % (self.__class__.__name__, self.steps)
41
42 def _run_lava_test_steps(self, artifacts, observer):
43 stdout = open(artifacts.stdout_pathname, 'at')
44 stderr = open(artifacts.stderr_pathname, 'at')
45 delegate = DisplayDelegate(stdout, stderr, observer)
46 extcmd = ExternalCommandWithDelegate(delegate)
47 try:
48 for cmd in self.steps:
49 if observer: observer.about_to_run_shell_command(cmd)
50 returncode = extcmd.call(cmd, shell=True)
51 if observer: observer.did_run_shell_command(cmd, returncode)
52 finally:
53 stdout.close()
54 stderr.close()
55
56 def run(self, artifacts, observer=None):
57 """
58 Run the test program by executing steps in sequence.
59
60 .. seealso::
61
62 :meth:`~lava_test.api.delegates.TestRunner.run`
63 """
64 self.starttime = datetime.datetime.utcnow()
65 self._run_lava_test_steps(artifacts, observer)
66 self.endtime = datetime.datetime.utcnow()
067
=== added file 'lava_test/core/swprofile.py'
--- lava_test/core/swprofile.py 1970-01-01 00:00:00 +0000
+++ lava_test/core/swprofile.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,72 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import apt
17
18from lava_test.utils import read_file
19
20
21def get_packages(apt_cache=None):
22 """ Get information about the packages installed
23
24 apt_cache - if not provided, this will be read from the system
25 """
26 if apt_cache == None:
27 apt_cache = apt.Cache()
28 packages = []
29 for apt_pkg in apt_cache:
30 if hasattr(apt_pkg, 'is_installed'):
31 is_installed = apt_pkg.is_installed
32 else:
33 is_installed = apt_pkg.isInstalled # old style API
34 if is_installed:
35 pkg = {
36 "name": apt_pkg.name,
37 "version": apt_pkg.installed.version}
38 packages.append(pkg)
39 return packages
40
41
42def get_software_context(apt_cache=None, lsb_information=None):
43 """ Return dict used for storing software_context information
44
45 test_id - Unique identifier for this test
46 time_check - whether or not a check was performed to see if
47 the time on the system was synced with a time server
48 apt_cache - if not provided, this will be read from the system
49 lsb_information - if not provided, this will be read from the system
50 """
51 software_context = {}
52 software_context['image'] = get_image(lsb_information)
53 software_context['packages'] = get_packages(apt_cache)
54 return software_context
55
56
57def get_image(lsb_information=None):
58 """ Get information about the image we are running
59
60 If /etc/buildstamp exists, get the image id from that. Otherwise
61 just use the lsb-release description for a rough idea.
62 """
63 try:
64 buildstamp = read_file("/etc/buildstamp")
65 name = buildstamp.splitlines()[1]
66 except IOError:
67 import lsb_release
68
69 if lsb_information == None:
70 lsb_information = lsb_release.get_distro_information()
71 name = lsb_information['DESCRIPTION']
72 return {"name": name}
073
=== added file 'lava_test/core/tests.py'
--- lava_test/core/tests.py 1970-01-01 00:00:00 +0000
+++ lava_test/core/tests.py 2011-09-13 22:44:36 +0000
@@ -0,0 +1,166 @@
1# Copyright (c) 2010, 2011 Linaro
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from __future__ import absolute_import
17
18import json
19import logging
20import os
21import shutil
22
23from lava_test.api.core import ITest
24from lava_test.core.artifacts import TestArtifacts
25from lava_test.core.config import get_config
26from lava_test.core.installers import TestInstaller
27from lava_test.core.parsers import TestParser, NativeTestParser
28from lava_test.core.runners import TestRunner
29from lava_test.utils import changed_directory
30
31
32class Test(ITest):
33 """
34 Reusable class for defining tests.
35
36 This class uses composition instead of inheritance. You should be able to
37 customize the parts you care about by providing delegate objects. This
38 class can be used by test definition files to create an object that
39 contains the building blocks for installing tests, running them, and
40 parsing the results.
41
42 :ivar test_id:
43 Name of the test or test suite
44 :ivar test_version:
45 Version of the test or test suite
46 :ivar installer:
47 ITestInstaller instance to use
48 :ivar runner:
49 ITestRunner instance to use
50 :ivar parser:
51 ITestParser instance to use
52 """
53
54 def __init__(self, test_id, test_version=None,
55 installer=None, runner=None, parser=None):
56 self._test_id = test_id
57 self._test_version = test_version
58 # Delegate objects
59 self.installer = installer
60 self.runner = runner
61 self.parser = parser
62 # Config instance
63 self._config = get_config()
64
65 def __repr__(self):
66 return ("<%s test_id=%r test_version=%r installer=%r runner=%r"
67 " parser=%r>") % (
68 self.__class__.__name__, self.test_id, self.test_version,
69 self.installer, self.runner, self.parser)
70
71 @property
72 def test_id(self):
73 """
74 Return the ID of the test.
75 """
76 return self._test_id
77
78 @property
79 def test_version(self):
80 """
81 Return the version of the test
82 """
83 return self._test_version
84
85 @property
86 def install_dir(self):
87 """
88 Pathname of a directory with binary and data files installed by the
89 test.
90
91 .. versionadded:: 0.2
92 """
93 return os.path.join(self._config.installdir, self.test_id)
94
95 @property
96 def is_installed(self):
97 return os.path.exists(self.install_dir)
98
99 def install(self, observer=None):
100 if self.is_installed:
101 raise RuntimeError(
102 "%s is already installed" % self.test_id)
103 if not self.installer:
104 raise RuntimeError(
105 "no installer defined for '%s'" % self.test_id)
106 with changed_directory(self.install_dir):
107 try:
108 logging.debug(
109 "Invoking %r.install(...)", self.installer)
110 self.installer.install(observer)
111 except:
112 self.uninstall()
113 raise
114
115 def uninstall(self):
116 logging.debug("Removing test %r", self.test_id)
117 if os.path.exists(self.install_dir):
118 shutil.rmtree(self.install_dir)
119
120 def run(self, observer=None):
121 if not self.runner:
122 raise RuntimeError(
123 "no test runner defined for '%s'" % self.test_id)
124 artifacts = TestArtifacts.allocate(self.test_id, self._config)
125 with changed_directory(self.install_dir):
126 logging.debug(
127 "Invoking %r.run_and_store_artifacts(...)",
128 self.runner, observer)
129 self.runner.run(artifacts, observer)
130 return artifacts
131
132 def parse(self, artifacts):
133 if self.parser:
134 logging.debug("Invoking %r.parse()", self.parser)
135 with changed_directory(artifacts.results_dir, False):
136 self.parser.parse(artifacts)
137 return self.parser.results
138
139
140class DeclarativeTest(Test):
141 """
142 Declaretive ITest implementation.
143
144 Declarative test is like :class:`lava_test.core.tests.Test` but cannot
145 contain any python code and is completely encapsulated in a .json file.
146
147 The idea is to write .json files that assemble a Test instance using
148 readily-available TestInstaller, TestRunner and TestParser subclasses.
149 """
150
151 def __init__(self, about):
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches