Index: scripts/slave/recipe_modules/raw_io/api.py |
diff --git a/scripts/slave/recipe_modules/raw_io/api.py b/scripts/slave/recipe_modules/raw_io/api.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2cbece1950d9ab0e6ebfa02a891ee8855bb196d8 |
--- /dev/null |
+++ b/scripts/slave/recipe_modules/raw_io/api.py |
@@ -0,0 +1,159 @@ |
+# Copyright 2014 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. |
+ |
+from recipe_engine import recipe_api |
+from recipe_engine import util as recipe_util |
+ |
+import os |
+import shutil |
+import tempfile |
+ |
+ |
+class InputDataPlaceholder(recipe_util.Placeholder): |
+ def __init__(self, data, suffix): |
+ assert isinstance(data, basestring) |
+ self.data = data |
+ self.suffix = suffix |
+ self._backing_file = None |
+ super(InputDataPlaceholder, self).__init__() |
+ |
+ @property |
+ def backing_file(self): |
+ return self._backing_file |
+ |
+ def render(self, test): |
+ assert not self._backing_file, 'Placeholder can be used only once' |
+ if test.enabled: |
+ # cheat and pretend like we're going to pass the data on the |
+ # cmdline for test expectation purposes. |
+ self._backing_file = self.data |
+ else: # pragma: no cover |
+ input_fd, self._backing_file = tempfile.mkstemp(suffix=self.suffix) |
+ os.write(input_fd, self.data) |
+ os.close(input_fd) |
+ return [self._backing_file] |
+ |
+ def result(self, presentation, test): |
+ assert self._backing_file |
+ exists = os.path.exists(self._backing_file) |
+ if not test.enabled and exists: # pragma: no cover |
+ os.unlink(self._backing_file) |
+ self._backing_file = None |
+ |
+ |
+class OutputDataPlaceholder(recipe_util.Placeholder): |
+ def __init__(self, suffix, leak_to): |
+ self.suffix = suffix |
+ self.leak_to = leak_to |
+ self._backing_file = None |
+ super(OutputDataPlaceholder, self).__init__() |
+ |
+ @property |
+ def backing_file(self): |
+ return self._backing_file |
+ |
+ def render(self, test): |
+ assert not self._backing_file, 'Placeholder can be used only once' |
+ if self.leak_to: |
+ self._backing_file = str(self.leak_to) |
+ return [self._backing_file] |
+ if test.enabled: |
+ self._backing_file = '/path/to/tmp/' + self.suffix.lstrip('.') |
+ else: # pragma: no cover |
+ output_fd, self._backing_file = tempfile.mkstemp(suffix=self.suffix) |
+ os.close(output_fd) |
+ return [self._backing_file] |
+ |
+ def result(self, presentation, test): |
+ assert self._backing_file |
+ if test.enabled: |
+ self._backing_file = None |
+ return test.data |
+ else: # pragma: no cover |
+ try: |
+ with open(self._backing_file, 'rb') as f: |
+ return f.read() |
+ finally: |
+ if not self.leak_to: |
+ os.unlink(self._backing_file) |
+ self._backing_file = None |
+ |
+ |
+class OutputDataDirPlaceholder(recipe_util.Placeholder): |
+ def __init__(self, suffix, leak_to): |
+ self.suffix = suffix |
+ self.leak_to = leak_to |
+ self._backing_dir = None |
+ super(OutputDataDirPlaceholder, self).__init__() |
+ |
+ @property |
+ def backing_file(self): # pragma: no cover |
+ raise ValueError('Output dir placeholders can not be used for stdin, ' |
+ 'stdout or stderr') |
+ |
+ def render(self, test): |
+ assert not self._backing_dir, 'Placeholder can be used only once' |
+ if self.leak_to: |
+ self._backing_dir = str(self.leak_to) |
+ return [self._backing_dir] |
+ if test.enabled: |
+ self._backing_dir = '/path/to/tmp/' + self.suffix |
+ else: # pragma: no cover |
+ self._backing_dir = tempfile.mkdtemp(suffix=self.suffix) |
+ return [self._backing_dir] |
+ |
+ def result(self, presentation, test): |
+ assert self._backing_dir |
+ if test.enabled: |
+ self._backing_dir = None |
+ return test.data or {} |
+ else: # pragma: no cover |
+ try: |
+ all_files = {} |
+ for dir_path, _, files in os.walk(self._backing_dir): |
+ for filename in files: |
+ abs_path = os.path.join(dir_path, filename) |
+ rel_path = os.path.relpath(abs_path, self._backing_dir) |
+ with open(abs_path, 'rb') as f: |
+ all_files[rel_path] = f.read() |
+ return all_files |
+ finally: |
+ if not self.leak_to: |
+ shutil.rmtree(self._backing_dir) |
+ self._backing_dir = None |
+ |
+ |
+class RawIOApi(recipe_api.RecipeApi): |
+ @recipe_util.returns_placeholder |
+ @staticmethod |
+ def input(data, suffix=''): |
+ return InputDataPlaceholder(data, suffix) |
+ |
+ @recipe_util.returns_placeholder |
+ @staticmethod |
+ def output(suffix='', leak_to=None): |
+ """Returns a Placeholder for use as a step argument, or for std{out,err}. |
+ |
+ If 'leak_to' is None, the placeholder is backed by a temporary file with |
+ a suffix 'suffix'. The file is deleted when the step finishes. |
+ |
+ If 'leak_to' is not None, then it should be a Path and placeholder |
+ redirects IO to a file at that path. Once step finishes, the file is |
+ NOT deleted (i.e. it's 'leaking'). 'suffix' is ignored in that case. |
+ """ |
+ return OutputDataPlaceholder(suffix, leak_to) |
+ |
+ @recipe_util.returns_placeholder |
+ @staticmethod |
+ def output_dir(suffix='', leak_to=None): |
+ """Returns a directory Placeholder for use as a step argument. |
+ |
+ If 'leak_to' is None, the placeholder is backed by a temporary dir with |
+ a suffix 'suffix'. The dir is deleted when the step finishes. |
+ |
+ If 'leak_to' is not None, then it should be a Path and placeholder |
+ redirects IO to a dir at that path. Once step finishes, the dir is |
+ NOT deleted (i.e. it's 'leaking'). 'suffix' is ignored in that case. |
+ """ |
+ return OutputDataDirPlaceholder(suffix, leak_to) |