2 # Copyright (c) 2014, 2016, 2019 Intel Corporation
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 """ Base classes for backends
25 This module provides mixins and base classes for backend modules.
35 from framework
import options
36 from . import compression
37 from framework
.results
import TestResult
38 from framework
.status
import INCOMPLETE
41 @contextlib.contextmanager
42 def write_compressed(filename
):
43 """Write a the final result using desired compression.
45 This helper function reads the piglit.conf to decide whether to use
46 compression, and what type of compression to use.
48 Currently it implements no compression
51 mode
= compression
.get_mode()
53 # if the suffix (final .xxx) is a known compression suffix
54 suffix
= os
.path
.splitext(filename
)[1]
55 if suffix
in compression
.COMPRESSION_SUFFIXES
:
56 filename
= '{}.{}'.format(os
.path
.splitext(filename
)[0], mode
)
58 filename
= '{}.{}'.format(filename
, mode
)
60 with compression
.COMPRESSORS
[mode
](filename
) as f
:
64 class Backend(metaclass
=abc
.ABCMeta
):
65 """ Abstract base class for summary backends
67 This class provides an abstract ancestor for classes implementing backends,
68 providing a light public API. The goal of this API is to be "just enough",
69 not a generic writing solution. To that end it provides two public methods,
70 'finalize', and 'write_test'. These two methods are designed to be just
71 enough to write a backend without needing format specific options.
73 Any locking that is necessary should be done in the child classes, as not
74 all potential backends need locking (for example, a SQL based backend might
75 be thread safe and not need to be locked during write)
79 def __init__(self
, dest
, metadata
, **kwargs
):
80 """ Generic constructor
82 This method should setup the container and open any files or connections
83 as necessary. It should not however, write anything into the backend
84 store, that job is for the initialize method.
86 In addition it takes keyword arguments that define options for the
87 backends. Options should be prefixed to identify which backends they
88 apply to. For example, a json specific value should be passed as
89 json_*, while a file specific value should be passed as file_*)
92 dest -- the place to write the results to. This should be correctly
93 handled based on the backend, the example is calls open() on a
94 file, but other backends might want different options
99 def initialize(self
, metadata
):
100 """ Write initial metadata and setup
102 This method is used to write metadata into the backend store and do any
103 other initial backend writing that is required. This method and the
104 finalize() method are bookends, one starts, the other finishes.
107 metadata -- a dict or dict-like object that contains metadata to be
108 written into the backend
113 def finalize(self
, metadata
=None):
114 """ Write final metadata into to the store and close it
116 This method writes any final metadata into the store, what can be
117 written is implementation specific, backends are free to ignore any
118 data that is not applicable.
120 metadata is not required, and Backend derived classes need to handle
121 being passed None correctly.
124 metadata -- Any metadata to be written in after the tests, should be a
125 dict or dict-like object
131 def write_test(self
, name
):
132 """ Write a test into the backend store
134 This method writes an actual test into the backend store.
136 Should be a context manager, used with the with statement. It should
137 first write an incomplete status value, then yield and object that will
138 overwrite that value with the final value. That object needs to take a
139 'data' parameter which is a result.TestResult object.
142 name -- the name of the test to be written
143 data -- a TestResult object representing the test data
148 class FileBackend(Backend
):
149 """ A baseclass for file based backends
151 This class provides a few methods and setup required for a file based
155 dest -- a folder to store files in
158 file_start_count -- controls the counter for the test result files.
159 Whatever this is set to will be the first name of the
160 tests. It is important for resumes that this is not
161 overlapping as the Inheriting classes assume they are
165 def __init__(self
, dest
, file_start_count
=0, **kwargs
):
167 self
._counter
= itertools
.count(file_start_count
)
168 self
._write
_final
= write_compressed
170 __INCOMPLETE
= TestResult(result
=INCOMPLETE
)
172 def __fsync(self
, file_
):
173 """ Sync the file to disk
175 If options.OPTIONS.sync is truthy this will sync self._file to disk
179 if options
.OPTIONS
.sync
:
180 os
.fsync(file_
.fileno())
183 def _write(self
, f
, name
, data
):
184 """Method that writes a TestResult into a result file."""
186 @abc.abstractproperty
187 def _file_extension(self
):
188 """The file extension of the backend."""
190 @contextlib.contextmanager
191 def write_test(self
, name
):
194 When this context manager is opened it will first write a placeholder
195 file with the status incomplete.
197 When it is called to write the final result it will create a temporary
198 file, write to that file, then move that file over the original,
199 incomplete status file. This helps to make the operation atomic, as
200 long as the filesystem continues running and the result was valid in
201 the original file it will be valid at the end
205 tfile
= file_
+ '.tmp'
206 with
open(tfile
, 'w') as f
:
207 self
._write
(f
, name
, val
)
209 shutil
.move(tfile
, file_
)
211 file_
= os
.path
.join(self
._dest
, 'tests', '{}.{}'.format(
212 next(self
._counter
), self
._file
_extension
))
214 with
open(file_
, 'w') as f
:
215 self
._write
(f
, name
, self
.__INCOMPLETE
)