Chromium Code Reviews| Index: build/android/pylib/base/output_manager.py |
| diff --git a/build/android/pylib/base/output_manager.py b/build/android/pylib/base/output_manager.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..8418a0ee6f32cd9e3bdf9841a67544854d5664c6 |
| --- /dev/null |
| +++ b/build/android/pylib/base/output_manager.py |
| @@ -0,0 +1,144 @@ |
| +# Copyright 2017 The Chromium Authors. All rights reserved. |
| +# 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(object): |
| + HTML = 'html' |
| + IMAGE = 'image' |
| + TEXT = 'text' |
| + |
| + |
| +class OutputManager(object): |
| + |
| + 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) |
| + yield f |
| + f.PrepareArchive() |
|
jbudorick
2017/08/23 16:16:19
The remainder of this function won't execute if an
mikecase (-- gone --)
2017/08/24 05:29:07
Done. GOod catch
|
| + |
| + def archive(): |
| + try: |
| + f.Archive() |
| + finally: |
| + os.remove(f.name) |
| + |
| + thread = reraiser_thread.ReraiserThread(func=archive) |
| + thread.start() |
| + self._thread_group.Add(thread) |
| + |
| + def _CreateArchivedFile(self, out_filename, out_subdir, datatype): |
| + """Returns an instance of ArchivedFile.""" |
| + raise NotImplementedError |
| + |
| + 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() |
| + |
|
jbudorick
2017/08/23 16:16:19
super nit: +1 blank line
mikecase (-- gone --)
2017/08/24 05:29:07
Done
|
| +class ArchivedFile(object): |
| + |
| + def __init__(self, out_filename, out_subdir, datatype): |
| + self._out_filename = out_filename |
| + self._out_subdir = out_subdir |
| + self._datatype = datatype |
| + |
| + self._f = tempfile.NamedTemporaryFile(delete=False) |
|
jbudorick
2017/08/23 16:16:19
Does this ever get removed?
mikecase (-- gone --)
2017/08/24 05:29:07
Yes. so, I think Im doing it an alright way.
os.r
jbudorick
2017/08/24 13:18:45
ah, ok.
|
| + self.name = self._f.name |
|
jbudorick
2017/08/23 16:16:19
nit: @property for this?
mikecase (-- gone --)
2017/08/24 05:29:07
Good idea, for some reason I forgot about those...
|
| + self._ready_to_archive = False |
| + |
| + 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' |
|
jbudorick
2017/08/23 16:16:19
super nit: +1 space after archiving, otherwise thi
mikecase (-- gone --)
2017/08/24 05:29:07
Done
|
| + '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. |
| + """ |
| + pass |
| + |
| + def Archive(self): |
| + """Archives file.""" |
| + assert self._ready_to_archive |
|
jbudorick
2017/08/23 16:16:20
I think this should be an Exception, analogous to
mikecase (-- gone --)
2017/08/24 05:29:07
Well, this isnt well documented, but the only thin
|
| + self._Archive() |
| + |
| + def _Archive(self): |
| + raise NotImplementedError |