OLD | NEW |
(Empty) | |
| 1 # Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 import contextlib |
| 6 import logging |
| 7 import os |
| 8 import tempfile |
| 9 |
| 10 from devil.utils import reraiser_thread |
| 11 |
| 12 |
| 13 class Datatype(object): |
| 14 HTML = 'html' |
| 15 IMAGE = 'image' |
| 16 TEXT = 'text' |
| 17 |
| 18 |
| 19 class OutputManager(object): |
| 20 |
| 21 def __init__(self): |
| 22 """OutputManager Constructor. |
| 23 |
| 24 This class provides a simple interface to save test output. Subclasses |
| 25 of this will allow users to save test results in the cloud or locally. |
| 26 """ |
| 27 self._allow_upload = False |
| 28 self._thread_group = None |
| 29 |
| 30 @contextlib.contextmanager |
| 31 def ArchivedTempfile( |
| 32 self, out_filename, out_subdir, datatype=Datatype.TEXT): |
| 33 """Archive file contents asynchonously and then deletes file. |
| 34 |
| 35 Args: |
| 36 out_filename: Name for saved file. |
| 37 out_subdir: Directory to save |out_filename| to. |
| 38 datatype: Datatype of file. |
| 39 |
| 40 Returns: |
| 41 An ArchivedFile file. This file will be uploaded async when the context |
| 42 manager exits. AFTER the context manager exits, you can get the link to |
| 43 where the file will be stored using the Link() API. You can use typical |
| 44 file APIs to write and flish the ArchivedFile. You can also use file.name |
| 45 to get the local filepath to where the underlying file exists. If you do |
| 46 this, you are responsible of flushing the file before exiting the context |
| 47 manager. |
| 48 """ |
| 49 if not self._allow_upload: |
| 50 raise Exception('Must run |SetUp| before attempting to upload!') |
| 51 |
| 52 f = self._CreateArchivedFile(out_filename, out_subdir, datatype) |
| 53 try: |
| 54 yield f |
| 55 finally: |
| 56 f.PrepareArchive() |
| 57 |
| 58 def archive(): |
| 59 try: |
| 60 f.Archive() |
| 61 finally: |
| 62 os.remove(f.name) |
| 63 |
| 64 thread = reraiser_thread.ReraiserThread(func=archive) |
| 65 thread.start() |
| 66 self._thread_group.Add(thread) |
| 67 |
| 68 def _CreateArchivedFile(self, out_filename, out_subdir, datatype): |
| 69 """Returns an instance of ArchivedFile.""" |
| 70 raise NotImplementedError |
| 71 |
| 72 def SetUp(self): |
| 73 self._allow_upload = True |
| 74 self._thread_group = reraiser_thread.ReraiserThreadGroup() |
| 75 |
| 76 def TearDown(self): |
| 77 self._allow_upload = False |
| 78 logging.info('Finishing archiving output.') |
| 79 self._thread_group.JoinAll() |
| 80 |
| 81 def __enter__(self): |
| 82 self.SetUp() |
| 83 return self |
| 84 |
| 85 def __exit__(self, _exc_type, _exc_val, _exc_tb): |
| 86 self.TearDown() |
| 87 |
| 88 |
| 89 class ArchivedFile(object): |
| 90 |
| 91 def __init__(self, out_filename, out_subdir, datatype): |
| 92 self._out_filename = out_filename |
| 93 self._out_subdir = out_subdir |
| 94 self._datatype = datatype |
| 95 |
| 96 self._f = tempfile.NamedTemporaryFile(delete=False) |
| 97 self._ready_to_archive = False |
| 98 |
| 99 @property |
| 100 def name(self): |
| 101 return self._f.name |
| 102 |
| 103 def write(self, *args, **kwargs): |
| 104 if self._ready_to_archive: |
| 105 raise Exception('Cannot write to file after archiving has begun!') |
| 106 self._f.write(*args, **kwargs) |
| 107 |
| 108 def flush(self, *args, **kwargs): |
| 109 if self._ready_to_archive: |
| 110 raise Exception('Cannot flush file after archiving has begun!') |
| 111 self._f.flush(*args, **kwargs) |
| 112 |
| 113 def Link(self): |
| 114 """Returns location of archived file.""" |
| 115 if not self._ready_to_archive: |
| 116 raise Exception('Cannot get link to archived file before archiving ' |
| 117 'has begun') |
| 118 return self._Link() |
| 119 |
| 120 def _Link(self): |
| 121 """Note for when overriding this function. |
| 122 |
| 123 This function will certainly be called before the file |
| 124 has finished being archived. Therefore, this needs to be able to know the |
| 125 exact location of the archived file before it is finished being archived. |
| 126 """ |
| 127 raise NotImplementedError |
| 128 |
| 129 def PrepareArchive(self): |
| 130 """Meant to be called synchronously to prepare file for async archiving.""" |
| 131 self.flush() |
| 132 self._ready_to_archive = True |
| 133 self._PrepareArchive() |
| 134 |
| 135 def _PrepareArchive(self): |
| 136 """Note for when overriding this function. |
| 137 |
| 138 This function is needed for things such as computing the location of |
| 139 content addressed files. This is called after the file is written but |
| 140 before archiving has begun. |
| 141 """ |
| 142 pass |
| 143 |
| 144 def Archive(self): |
| 145 """Archives file.""" |
| 146 if not self._ready_to_archive: |
| 147 raise Exception('File is not ready to archive. Be sure you are not ' |
| 148 'writing to the file and PrepareArchive has been called') |
| 149 self._Archive() |
| 150 |
| 151 def _Archive(self): |
| 152 raise NotImplementedError |
OLD | NEW |