~linaro-validation/lava-test/packaging

« back to all changes in this revision

Viewing changes to lava_test/core/artifacts.py

  • Committer: Paul Larson
  • Date: 2011-10-24 16:30:11 UTC
  • Revision ID: paul.larson@canonical.com-20111024163011-n0dhz6b6wlu2zofw
Tags: upstream-0.3.1
ImportĀ upstreamĀ versionĀ 0.3.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
from __future__ import absolute_import
 
17
 
 
18
import base64
 
19
import datetime
 
20
import logging
 
21
import os
 
22
import uuid
 
23
 
 
24
from linaro_dashboard_bundle.io import DocumentIO
 
25
 
 
26
from lava_test.core import hwprofile, swprofile
 
27
from lava_test.utils import merge_dict, mkdir_p
 
28
 
 
29
 
 
30
class 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)