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