| 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..885186cf03a254f7fd951358f2149f37172378e4
|
| --- /dev/null
|
| +++ b/build/android/pylib/base/output_manager.py
|
| @@ -0,0 +1,152 @@
|
| +# 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)
|
| + try:
|
| + yield f
|
| + finally:
|
| + f.PrepareArchive()
|
| +
|
| + 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()
|
| +
|
| +
|
| +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)
|
| + self._ready_to_archive = False
|
| +
|
| + @property
|
| + def name(self):
|
| + return self._f.name
|
| +
|
| + 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.
|
| + """
|
| + pass
|
| +
|
| + 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
|
|
|