| OLD | NEW |
| 1 # -*- encoding: utf-8 -*- | 1 # -*- encoding: utf-8 -*- |
| 2 # Copyright 2014 The LUCI Authors. All rights reserved. | 2 # Copyright 2014 The LUCI Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 # that can be found in the LICENSE file. | 4 # that can be found in the LICENSE file. |
| 5 | 5 |
| 6 from recipe_engine import recipe_api | 6 from recipe_engine import recipe_api |
| 7 from recipe_engine import util as recipe_util | 7 from recipe_engine import util as recipe_util |
| 8 | 8 |
| 9 import os | 9 import os |
| 10 import shutil | 10 import shutil |
| 11 import tempfile | 11 import tempfile |
| 12 | 12 |
| 13 | 13 |
| 14 class InputDataPlaceholder(recipe_util.InputPlaceholder): | 14 class InputDataPlaceholder(recipe_util.InputPlaceholder): |
| 15 def __init__(self, data, suffix): | 15 def __init__(self, data, suffix): |
| 16 self.data = data | 16 self.data = data |
| 17 self.suffix = suffix | 17 self.suffix = suffix |
| 18 self._backing_file = None | 18 self._backing_file = None |
| 19 super(InputDataPlaceholder, self).__init__() | 19 super(InputDataPlaceholder, self).__init__() |
| 20 | 20 |
| 21 @property | 21 @property |
| 22 def backing_file(self): | 22 def backing_file(self): |
| 23 return self._backing_file | 23 return self._backing_file |
| 24 | 24 |
| 25 def render(self, test): | 25 def render(self, test): |
| 26 assert not self._backing_file, 'Placeholder can be used only once' | 26 assert not self._backing_file, 'Placeholder can be used only once' |
| 27 if test.enabled: | 27 if test.enabled: |
| 28 # cheat and pretend like we're going to pass the data on the | 28 # cheat and pretend like we're going to pass the data on the |
| 29 # cmdline for test expectation purposes. | 29 # cmdline for test expectation purposes. |
| 30 self._backing_file = self.data | 30 self._backing_file = self.encode(self.data) |
| 31 else: # pragma: no cover | 31 else: # pragma: no cover |
| 32 input_fd, self._backing_file = tempfile.mkstemp(suffix=self.suffix) | 32 input_fd, self._backing_file = tempfile.mkstemp(suffix=self.suffix) |
| 33 | 33 |
| 34 os.write(input_fd, self.encode(self.data)) | 34 os.write(input_fd, self.encode(self.data)) |
| 35 os.close(input_fd) | 35 os.close(input_fd) |
| 36 return [self._backing_file] | 36 return [self._backing_file] |
| 37 | 37 |
| 38 def cleanup(self, test_enabled): | 38 def cleanup(self, test_enabled): |
| 39 assert self._backing_file is not None | 39 assert self._backing_file is not None |
| 40 if not test_enabled: # pragma: no cover | 40 if not test_enabled: # pragma: no cover |
| 41 try: | 41 try: |
| 42 os.unlink(self._backing_file) | 42 os.unlink(self._backing_file) |
| 43 except OSError: | 43 except OSError: |
| 44 pass | 44 pass |
| 45 self._backing_file = None | 45 self._backing_file = None |
| 46 | 46 |
| 47 # Not covered because it's called in a branch in render() which isn't tested | 47 def encode(self, data): |
| 48 # as well. | |
| 49 def encode(self, data): # pragma: no cover | |
| 50 """ Encodes data to be written out, when rendering this placeholder. | 48 """ Encodes data to be written out, when rendering this placeholder. |
| 51 """ | 49 """ |
| 52 return data | 50 return data |
| 53 | 51 |
| 54 class InputTextPlaceholder(InputDataPlaceholder): | 52 class InputTextPlaceholder(InputDataPlaceholder): |
| 55 """ A input placeholder which expects to write out text. | 53 """ A input placeholder which expects to write out text. |
| 56 """ | 54 """ |
| 57 def __init__(self, data, suffix): | 55 def __init__(self, data, suffix): |
| 58 super(InputTextPlaceholder, self).__init__(data, suffix) | 56 super(InputTextPlaceholder, self).__init__(data, suffix) |
| 59 assert isinstance(data, basestring) | 57 assert isinstance(data, basestring) |
| 60 | 58 |
| 61 # Not covered because it's called in a branch in render() which isn't tested | 59 def encode(self, data): |
| 62 # as well. | |
| 63 def encode(self, data): # pragma: no cover | |
| 64 # Sometimes users give us invalid utf-8 data. They shouldn't, but it does | 60 # Sometimes users give us invalid utf-8 data. They shouldn't, but it does |
| 65 # happen every once and a while. Just ignore it, and replace with �. | 61 # happen every once and a while. Just ignore it, and replace with �. |
| 66 # We're assuming users only want to write text data out. | 62 # We're assuming users only want to write text data out. |
| 67 decoded = self.data.decode('utf-8', 'replace') | 63 decoded = self.data.decode('utf-8', 'replace') |
| 68 return decoded.encode('utf-8') | 64 return decoded.encode('utf-8') |
| 69 | 65 |
| 70 | 66 |
| 71 | |
| 72 class OutputDataPlaceholder(recipe_util.OutputPlaceholder): | 67 class OutputDataPlaceholder(recipe_util.OutputPlaceholder): |
| 73 def __init__(self, suffix, leak_to, name=None): | 68 def __init__(self, suffix, leak_to, name=None): |
| 74 self.suffix = suffix | 69 self.suffix = suffix |
| 75 self.leak_to = leak_to | 70 self.leak_to = leak_to |
| 76 self._backing_file = None | 71 self._backing_file = None |
| 77 super(OutputDataPlaceholder, self).__init__(name=name) | 72 super(OutputDataPlaceholder, self).__init__(name=name) |
| 78 | 73 |
| 79 @property | 74 @property |
| 80 def backing_file(self): | 75 def backing_file(self): |
| 81 return self._backing_file | 76 return self._backing_file |
| 82 | 77 |
| 83 def render(self, test): | 78 def render(self, test): |
| 84 assert not self._backing_file, 'Placeholder can be used only once' | 79 assert not self._backing_file, 'Placeholder can be used only once' |
| 85 if self.leak_to: | 80 if self.leak_to: |
| 86 self._backing_file = str(self.leak_to) | 81 self._backing_file = str(self.leak_to) |
| 87 return [self._backing_file] | 82 return [self._backing_file] |
| 88 if test.enabled: | 83 if test.enabled: |
| 89 self._backing_file = '/path/to/tmp/' + self.suffix.lstrip('.') | 84 self._backing_file = '/path/to/tmp/' + self.suffix.lstrip('.') |
| 90 else: # pragma: no cover | 85 else: # pragma: no cover |
| 91 output_fd, self._backing_file = tempfile.mkstemp(suffix=self.suffix) | 86 output_fd, self._backing_file = tempfile.mkstemp(suffix=self.suffix) |
| 92 os.close(output_fd) | 87 os.close(output_fd) |
| 93 return [self._backing_file] | 88 return [self._backing_file] |
| 94 | 89 |
| 95 def result(self, presentation, test): | 90 def result(self, presentation, test): |
| 96 assert self._backing_file | 91 assert self._backing_file |
| 97 if test.enabled: | 92 if test.enabled: |
| 98 self._backing_file = None | 93 self._backing_file = None |
| 99 return test.data | 94 return self.decode(test.data) |
| 100 else: # pragma: no cover | 95 else: # pragma: no cover |
| 101 try: | 96 try: |
| 102 with open(self._backing_file, 'rb') as f: | 97 with open(self._backing_file, 'rb') as f: |
| 103 return self.decode(f.read()) | 98 return self.decode(f.read()) |
| 104 finally: | 99 finally: |
| 105 if not self.leak_to: | 100 if not self.leak_to: |
| 106 os.unlink(self._backing_file) | 101 os.unlink(self._backing_file) |
| 107 self._backing_file = None | 102 self._backing_file = None |
| 108 | 103 |
| 109 # Not covered because it's called in a branch in result() which isn't tested | 104 def decode(self, result): |
| 110 # as well. | |
| 111 def decode(self, result): # pragma: no cover | |
| 112 """ Decodes data to be read in, when getting the result of this placeholder. | 105 """ Decodes data to be read in, when getting the result of this placeholder. |
| 113 """ | 106 """ |
| 114 return result | 107 return result |
| 115 | 108 |
| 116 class OutputTextPlaceholder(OutputDataPlaceholder): | 109 class OutputTextPlaceholder(OutputDataPlaceholder): |
| 117 """ A output placeholder which expects to write out text. | 110 """ A output placeholder which expects to write out text. |
| 118 """ | 111 """ |
| 119 # Not covered because it's called in a branch in result() which isn't tested | 112 def decode(self, result): |
| 120 # as well. | 113 # This ensures that the raw result bytes we got are, in fact, valid utf-8, |
| 121 def decode(self, result): # pragma: no cover | 114 # replacing invalid bytes with �. Because python2's unicode support is |
| 122 return result.decode('utf-8', 'replace') | 115 # wonky, we re-encode the now-valid-utf-8 back into a str object so that |
| 116 # users don't need to deal with `unicode` objects. |
| 117 return result.decode('utf-8', 'replace').encode('utf-8') |
| 123 | 118 |
| 124 class OutputDataDirPlaceholder(recipe_util.OutputPlaceholder): | 119 class OutputDataDirPlaceholder(recipe_util.OutputPlaceholder): |
| 125 def __init__(self, suffix, leak_to, name=None): | 120 def __init__(self, suffix, leak_to, name=None): |
| 126 self.suffix = suffix | 121 self.suffix = suffix |
| 127 self.leak_to = leak_to | 122 self.leak_to = leak_to |
| 128 self._backing_dir = None | 123 self._backing_dir = None |
| 129 super(OutputDataDirPlaceholder, self).__init__(name=name) | 124 super(OutputDataDirPlaceholder, self).__init__(name=name) |
| 130 | 125 |
| 131 @property | 126 @property |
| 132 def backing_file(self): # pragma: no cover | 127 def backing_file(self): # pragma: no cover |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 228 """Returns a directory Placeholder for use as a step argument. | 223 """Returns a directory Placeholder for use as a step argument. |
| 229 | 224 |
| 230 If 'leak_to' is None, the placeholder is backed by a temporary dir with | 225 If 'leak_to' is None, the placeholder is backed by a temporary dir with |
| 231 a suffix 'suffix'. The dir is deleted when the step finishes. | 226 a suffix 'suffix'. The dir is deleted when the step finishes. |
| 232 | 227 |
| 233 If 'leak_to' is not None, then it should be a Path and placeholder | 228 If 'leak_to' is not None, then it should be a Path and placeholder |
| 234 redirects IO to a dir at that path. Once step finishes, the dir is | 229 redirects IO to a dir at that path. Once step finishes, the dir is |
| 235 NOT deleted (i.e. it's 'leaking'). 'suffix' is ignored in that case. | 230 NOT deleted (i.e. it's 'leaking'). 'suffix' is ignored in that case. |
| 236 """ | 231 """ |
| 237 return OutputDataDirPlaceholder(suffix, leak_to, name=name) | 232 return OutputDataDirPlaceholder(suffix, leak_to, name=name) |
| OLD | NEW |