1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
# Copyright (c) 2014, 2016 Intel Corporation
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
""" Base classes for backends
This module provides mixins and base classes for backend modules.
"""
from __future__ import (
absolute_import, division, print_function, unicode_literals
)
import abc
import contextlib
import itertools
import os
import shutil
import six
from framework import options
from . import compression
from framework.results import TestResult
from framework.status import INCOMPLETE
@contextlib.contextmanager
def write_compressed(filename):
"""Write a the final result using desired compression.
This helper function reads the piglit.conf to decide whether to use
compression, and what type of compression to use.
Currently it implements no compression
"""
mode = compression.get_mode()
if mode != 'none':
# if the suffix (final .xxx) is a knwon compression suffix
suffix = os.path.splitext(filename)[1]
if suffix in compression.COMPRESSION_SUFFIXES:
filename = '{}.{}'.format(os.path.splitext(filename)[0], mode)
else:
filename = '{}.{}'.format(filename, mode)
with compression.COMPRESSORS[mode](filename) as f:
yield f
@six.add_metaclass(abc.ABCMeta)
class Backend(object):
""" Abstract base class for summary backends
This class provides an abstract ancestor for classes implementing backends,
providing a light public API. The goal of this API is to be "just enough",
not a generic writing solution. To that end it provides two public methods,
'finalize', and 'write_test'. These two methods are designed to be just
enough to write a backend without needing format specific options.
Any locking that is necessary should be done in the child classes, as not
all potential backends need locking (for example, a SQL based backend might
be thread safe and not need to be locked during write)
"""
@abc.abstractmethod
def __init__(self, dest, metadata, **kwargs):
""" Generic constructor
This method should setup the container and open any files or conections
as necissary. It should not however, write anything into the backend
store, that job is for the iniitalize method.
In addition it takes keyword arguments that define options for the
backends. Options should be prefixed to identify which backends they
apply to. For example, a json specific value should be passed as
json_*, while a file specific value should be passed as file_*)
Arguments:
dest -- the place to write the results to. This should be correctly
handled based on the backend, the example is calls open() on a
file, but other backends might want different options
"""
@abc.abstractmethod
def initialize(self, metadata):
""" Write initial metadata and setup
This method is used to write metadata into the backend store and do any
other initial backend writing that is required. This method and the
finalize() method are bookends, one starts, the other finishes.
Arguments:
metadata -- a dict or dict-like object that contains metadata to be
written into the backend
"""
@abc.abstractmethod
def finalize(self, metadata=None):
""" Write final metadata into to the store and close it
This method writes any final metadata into the store, what can be
written is implementation specific, backends are free to ignore any
data that is not applicable.
metadata is not required, and Backend derived classes need to handle
being passed None correctly.
Keyword Arguments:
metadata -- Any metadata to be written in after the tests, should be a
dict or dict-like object
"""
@abc.abstractmethod
def write_test(self, name):
""" Write a test into the backend store
This method writes an actual test into the backend store.
Should be a context manager, used with the with statement. It should
first write an incomplete status value, then yield and object that will
overwrite that value with the final value. That object needs to take a
'data' paramter whic is a result.TestResult object.
Arguments:
name -- the name of the test to be written
data -- a TestResult object representing the test data
"""
class FileBackend(Backend):
""" A baseclass for file based backends
This class provides a few methods and setup required for a file based
backend.
Arguments:
dest -- a folder to store files in
Keyword Arguments:
file_start_count -- controls the counter for the test result files.
Whatever this is set to will be the first name of the
tests. It is important for resumes that this is not
overlapping as the Inheriting classes assume they are
not. Default: 0
"""
def __init__(self, dest, file_start_count=0, **kwargs):
self._dest = dest
self._counter = itertools.count(file_start_count)
self._write_final = write_compressed
__INCOMPLETE = TestResult(result=INCOMPLETE)
def __fsync(self, file_):
""" Sync the file to disk
If options.OPTIONS.sync is truthy this will sync self._file to disk
"""
file_.flush()
if options.OPTIONS.sync:
os.fsync(file_.fileno())
@abc.abstractmethod
def _write(self, f, name, data):
"""Method that writes a TestResult into a result file."""
@abc.abstractproperty
def _file_extension(self):
"""The file extension of the backend."""
@contextlib.contextmanager
def write_test(self, name):
"""Write a test.
When this context manager is opened it will first write a placeholder
file with the status incomplete.
When it is called to write the finall result it will create a temporary
file, write to that file, then move that file over the original,
incomplete status file. This helps to make the operation atomic, as
long as the filesystem continues running and the result was valid in
the original file it will be valid at the end
"""
def finish(val):
tfile = file_ + '.tmp'
with open(tfile, 'w') as f:
self._write(f, name, val)
self.__fsync(f)
shutil.move(tfile, file_)
file_ = os.path.join(self._dest, 'tests', '{}.{}'.format(
next(self._counter), self._file_extension))
with open(file_, 'w') as f:
self._write(f, name, self.__INCOMPLETE)
self.__fsync(f)
yield finish
|