1
# Copyright (c) 2010, 2011 Linaro
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.
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.
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/>.
16
from __future__ import absolute_import
24
from linaro_dashboard_bundle.io import DocumentIO
26
from lava_test.core import hwprofile, swprofile
27
from lava_test.utils import merge_dict, mkdir_p
30
class TestArtifacts(object):
32
Class representing test run artifacts, that is, static leftovers
33
independent of the wrapper class that encapsulates test handling.
38
def __init__(self, test_id, result_id, config):
39
self._test_id = test_id
40
self._result_id = result_id
45
def allocate(cls, test_id, config):
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.
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(
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)
66
The ID of the test this run is associated with
75
The ID of the test run.
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
84
return self._result_id
87
def results_dir(self):
89
Pathname of a directory with test run artifacts (log files, crash
94
return os.path.join(self._config.resultsdir, self.result_id)
96
def load_bundle(self):
98
Load the results bundle from disk.
100
The bundle is also validated if linaro-dashboard-bundle library is
103
with open(self.bundle_pathname, 'rt') as stream:
104
self._bundle = DocumentIO.load(stream)[1]
106
def dumps_bundle(self):
107
return DocumentIO.dumps(self._bundle)
109
def save_bundle(self):
111
Save the results bundle to the disk
113
The bundle is also validated if linaro-dashboard-bundle library is
116
self.save_bundle_as(self.bundle_pathname)
118
def save_bundle_as(self, pathname):
120
Save the results bundle to the specified file on disk.
122
The bundle should have been created or loaded earlier
124
with open(pathname, 'wt') as stream:
125
DocumentIO.dump(stream, self._bundle)
130
The deserialized bundle object.
132
This can be either created with create_bundle() or loaded
133
from disk with load_bundle()
137
def create_initial_bundle(self,
138
skip_software_context=False,
139
skip_hardware_context=False,
140
time_check_performed=False):
142
Create the bundle object.
144
This creates a typical bundle structure. Optionally it can also add
145
software and hardware context information.
147
For a complete bundle you may want to add attachments and incorporate
148
parse results by calling appropriate methods after loading or creating
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
157
'test_id': self.test_id,
158
'analyzer_assigned_date': analyzer_assigned_date.strftime(
160
'analyzer_assigned_uuid': analyzer_assigned_uuid,
161
'time_check_performed': time_check_performed,
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
172
'format': 'Dashboard Bundle Format 1.2',
173
'test_runs': [test_run]}
178
return self._bundle["test_runs"][0]
180
raise AttributeError("test_run can be accessed only after you load"
181
" or create an initial bundle")
183
def attach_file(self, real_pathname, stored_pathname, mime_type):
185
Append an attachment to the test run.
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.
191
if not os.path.exists(real_pathname):
193
if mime_type.startswith('text/'):
197
with open(real_pathname, mode) as stream:
201
self.test_run['attachments'].append({
202
"pathname": stored_pathname,
203
"mime_type": mime_type,
204
"content": base64.standard_b64encode(data)})
206
def incorporate_parse_results(self, parse_results):
208
Merge the data returned by the test parser into the current test run.
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).
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)
219
def attach_standard_files_to_bundle(self):
221
Attach standard output and standard error log files to the bundle.
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.
227
self.attach_file(self.stdout_pathname, "testoutput.log", "text/plain")
228
self.attach_file(self.stderr_pathname, "testoutput.err", "text/plain")
231
def bundle_pathname(self):
233
Pathname of the result bundle.
235
The bundle contains the snapshot of environment information as well as
236
test identity and is created when you invoke ITest.run().
238
The bundle file name is always "testdata.json"
240
.. versionadded:: 0.2
242
return self.get_artefact_pathname("testdata.json")
245
def stdout_pathname(self):
247
Pathname of the log file of the standard output as returned by the test
250
The log file name is always "testoutput.log"
252
.. versionadded:: 0.2
254
return self.get_artefact_pathname("testoutput.log")
257
def stderr_pathname(self):
259
Pathname of the log file of the standard output as returned by the test
262
The log file name is always "testoutput.err"
264
.. versionadded:: 0.2
266
return self.get_artefact_pathname("testoutput.err")
268
def get_artefact_pathname(self, artefact_name):
270
Return a pathname of a test run artefact file.
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.
275
.. versionadded:: 0.2
277
return os.path.join(self.results_dir, artefact_name)