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
build / android / pylib / base / output_manager.py [blame]
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import contextlib
import logging
import os
import tempfile
from devil.utils import reraiser_thread
class Datatype:
HTML = 'text/html'
JSON = 'application/json'
PNG = 'image/png'
TEXT = 'text/plain'
class OutputManager:
def __init__(self):
"""OutputManager Constructor.
This class provides a simple interface to save test output. Subclasses
of this will allow users to save test results in the cloud or locally.
"""
self._allow_upload = False
self._thread_group = None
@contextlib.contextmanager
def ArchivedTempfile(
self, out_filename, out_subdir, datatype=Datatype.TEXT):
"""Archive file contents asynchonously and then deletes file.
Args:
out_filename: Name for saved file.
out_subdir: Directory to save |out_filename| to.
datatype: Datatype of file.
Returns:
An ArchivedFile file. This file will be uploaded async when the context
manager exits. AFTER the context manager exits, you can get the link to
where the file will be stored using the Link() API. You can use typical
file APIs to write and flish the ArchivedFile. You can also use file.name
to get the local filepath to where the underlying file exists. If you do
this, you are responsible of flushing the file before exiting the context
manager.
"""
if not self._allow_upload:
raise Exception('Must run |SetUp| before attempting to upload!')
f = self.CreateArchivedFile(out_filename, out_subdir, datatype)
try:
yield f
finally:
self.ArchiveArchivedFile(f, delete=True)
def CreateArchivedFile(self, out_filename, out_subdir,
datatype=Datatype.TEXT):
"""Returns an instance of ArchivedFile."""
return self._CreateArchivedFile(out_filename, out_subdir, datatype)
def _CreateArchivedFile(self, out_filename, out_subdir, datatype):
raise NotImplementedError
def ArchiveArchivedFile(self, archived_file, delete=False):
"""Archive an ArchivedFile instance and optionally delete it."""
if not isinstance(archived_file, ArchivedFile):
raise Exception('Excepting an instance of ArchivedFile, got %s.' %
type(archived_file))
archived_file.PrepareArchive()
def archive():
try:
archived_file.Archive()
finally:
if delete:
archived_file.Delete()
thread = reraiser_thread.ReraiserThread(func=archive)
thread.start()
self._thread_group.Add(thread)
def SetUp(self):
self._allow_upload = True
self._thread_group = reraiser_thread.ReraiserThreadGroup()
def TearDown(self):
self._allow_upload = False
logging.info('Finishing archiving output.')
self._thread_group.JoinAll()
def __enter__(self):
self.SetUp()
return self
def __exit__(self, _exc_type, _exc_val, _exc_tb):
self.TearDown()
class ArchivedFile:
def __init__(self, out_filename, out_subdir, datatype):
self._out_filename = out_filename
self._out_subdir = out_subdir
self._datatype = datatype
mode = 'w+'
if datatype == Datatype.PNG:
mode = 'w+b'
self._f = tempfile.NamedTemporaryFile(mode=mode, delete=False)
self._ready_to_archive = False
@property
def name(self):
return self._f.name
def fileno(self, *args, **kwargs):
if self._ready_to_archive:
raise Exception('Cannot retrieve the integer file descriptor '
'after archiving has begun!')
return self._f.fileno(*args, **kwargs)
def write(self, *args, **kwargs):
if self._ready_to_archive:
raise Exception('Cannot write to file after archiving has begun!')
self._f.write(*args, **kwargs)
def flush(self, *args, **kwargs):
if self._ready_to_archive:
raise Exception('Cannot flush file after archiving has begun!')
self._f.flush(*args, **kwargs)
def Link(self):
"""Returns location of archived file."""
if not self._ready_to_archive:
raise Exception('Cannot get link to archived file before archiving '
'has begun')
return self._Link()
def _Link(self):
"""Note for when overriding this function.
This function will certainly be called before the file
has finished being archived. Therefore, this needs to be able to know the
exact location of the archived file before it is finished being archived.
"""
raise NotImplementedError
def PrepareArchive(self):
"""Meant to be called synchronously to prepare file for async archiving."""
self.flush()
self._ready_to_archive = True
self._PrepareArchive()
def _PrepareArchive(self):
"""Note for when overriding this function.
This function is needed for things such as computing the location of
content addressed files. This is called after the file is written but
before archiving has begun.
"""
def Archive(self):
"""Archives file."""
if not self._ready_to_archive:
raise Exception('File is not ready to archive. Be sure you are not '
'writing to the file and PrepareArchive has been called')
self._Archive()
def _Archive(self):
raise NotImplementedError
def Delete(self):
"""Deletes the backing file."""
self._f.close()
os.remove(self.name)