Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(454)

Side by Side Diff: recipe_modules/raw_io/api.py

Issue 2678343010: [raw_io] prevent output_text from returning `unicode` object. (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698