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

Side by Side Diff: client/tests/isolate_storage_test.py

Issue 2953253003: Replace custom blob gRPC API with ByteStream (Closed)
Patch Set: Import ndb directly to test code Created 3 years, 5 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 | « client/proto/isolate_bot_pb2_grpc.py ('k') | client/third_party/cachetools/README.swarming » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
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 import binascii 6 import binascii
7 import os
8 import re
9 import sys
7 import time 10 import time
8 import unittest 11 import unittest
9 import sys
10 12
11 # Somehow this lets us find isolate_storage 13 # Somehow this lets us find isolate_storage
12 import net_utils 14 import net_utils
13 15
14 from depot_tools import auto_stub 16 from depot_tools import auto_stub
15 import isolate_storage 17 import isolate_storage
16 import test_utils 18 import test_utils
17 19
18 20 class ByteStreamStubMock(object):
19 class FileServiceStubMock(object):
20 """Replacement for real gRPC stub 21 """Replacement for real gRPC stub
21 22
22 We can't mock *within* the real stub to replace individual functions, plus 23 We can't mock *within* the real stub to replace individual functions, plus
23 we'd have to mock __init__ every time anyway. So this class replaces the 24 we'd have to mock __init__ every time anyway. So this class replaces the
24 entire stub. As for the functions, they implement default happy path 25 entire stub. As for the functions, they implement default happy path
25 behaviour where possible, and are not implemented otherwise. 26 behaviour where possible, and are not implemented otherwise.
26 """ 27 """
27 def __init__(self, _channel): 28 def __init__(self, _channel):
28 self._push_requests = [] 29 self._push_requests = []
29 self._contains_requests = [] 30 self._contains_requests = []
30 31
31 def FetchBlobs(self, request, timeout=None): 32 def Read(self, request, timeout=None):
33 del request, timeout
32 raise NotImplementedError() 34 raise NotImplementedError()
33 35
34 def PushBlobs(self, requests): 36 def Write(self, requests, timeout=None):
37 del timeout
38 nb = 0
35 for r in requests: 39 for r in requests:
40 nb += len(r.data)
36 self._push_requests.append(r.__deepcopy__()) 41 self._push_requests.append(r.__deepcopy__())
37 response = isolate_storage.isolate_bot_pb2.PushBlobsReply() 42 resp = isolate_storage.bytestream_pb2.WriteResponse()
38 response.status.succeeded = True 43 resp.committed_size = nb
39 return response 44 return resp
40
41 def Contains(self, request, timeout=None):
42 del timeout
43 response = isolate_storage.isolate_bot_pb2.ContainsReply()
44 self._contains_requests.append(request.__deepcopy__())
45 response.status.succeeded = True
46 return response
47 45
48 def popContainsRequests(self): 46 def popContainsRequests(self):
49 cr = self._contains_requests 47 cr = self._contains_requests
50 self._contains_requests = [] 48 self._contains_requests = []
51 return cr 49 return cr
52 50
53 def popPushRequests(self): 51 def popPushRequests(self):
54 pr = self._push_requests 52 pr = self._push_requests
55 self._push_requests = [] 53 self._push_requests = []
56 return pr 54 return pr
57 55
56 def raiseError(code):
57 raise isolate_storage.grpc.RpcError(
58 'cannot turn this into a real code yet: %s' % code)
58 59
59 class IsolateStorageTest(auto_stub.TestCase): 60 class IsolateStorageTest(auto_stub.TestCase):
60 def get_server(self): 61 def get_server(self):
61 return isolate_storage.IsolateServerGrpc('grpc-proxy.luci.com', 62 os.environ['ISOLATED_GRPC_PROXY'] = 'https://luci.com/client/bob'
63 return isolate_storage.IsolateServerGrpc('https://luci.appspot.com',
62 'default-gzip') 64 'default-gzip')
63 65
64 def testFetchHappySimple(self): 66 def testFetchHappySimple(self):
65 """Fetch: if we get a few chunks with the right offset, everything works""" 67 """Fetch: if we get a few chunks with the right offset, everything works"""
66 def FetchBlobs(self, request, timeout=None): 68 def Read(self, request, timeout=None):
67 del timeout 69 del timeout
68 self.request = request 70 self.request = request
69 response = isolate_storage.isolate_bot_pb2.FetchBlobsReply() 71 response = isolate_storage.bytestream_pb2.ReadResponse()
70 response.status.succeeded = True
71 for i in range(0, 3): 72 for i in range(0, 3):
72 response.data.data = str(i) 73 response.data = str(i)
73 response.data.offset = i
74 yield response 74 yield response
75 self.mock(FileServiceStubMock, 'FetchBlobs', FetchBlobs) 75 self.mock(ByteStreamStubMock, 'Read', Read)
76 76
77 s = self.get_server() 77 s = self.get_server()
78 replies = s.fetch('abc123') 78 replies = s.fetch('abc123')
79 response = replies.next() 79 response = replies.next()
80 self.assertEqual(binascii.unhexlify('abc123'),
81 s._stub.request.digest[0].digest)
82 self.assertEqual('0', response) 80 self.assertEqual('0', response)
83 response = replies.next() 81 response = replies.next()
84 self.assertEqual('1', response) 82 self.assertEqual('1', response)
85 response = replies.next() 83 response = replies.next()
86 self.assertEqual('2', response) 84 self.assertEqual('2', response)
87 85
88 def testFetchHappyZeroLengthBlob(self): 86 def testFetchHappyZeroLengthBlob(self):
89 """Fetch: if we get a zero-length blob, everything works""" 87 """Fetch: if we get a zero-length blob, everything works"""
90 def FetchBlobs(self, request, timeout=None): 88 def Read(self, request, timeout=None):
91 del timeout 89 del timeout
92 self.request = request 90 self.request = request
93 response = isolate_storage.isolate_bot_pb2.FetchBlobsReply() 91 response = isolate_storage.bytestream_pb2.ReadResponse()
94 response.status.succeeded = True 92 response.data = ''
95 response.data.data = ''
96 yield response 93 yield response
97 self.mock(FileServiceStubMock, 'FetchBlobs', FetchBlobs) 94 self.mock(ByteStreamStubMock, 'Read', Read)
98 95
99 s = self.get_server() 96 s = self.get_server()
100 replies = s.fetch('abc123') 97 replies = s.fetch('abc123')
101 response = replies.next() 98 reply = replies.next()
102 self.assertEqual(binascii.unhexlify('abc123'), 99 self.assertEqual(0, len(reply))
103 s._stub.request.digest[0].digest)
104 self.assertEqual(0, len(response))
105 100
106 def testFetchThrowsOnWrongOffset(self): 101 def testFetchThrowsOnFailure(self):
107 """Fetch: if we get a chunk with the wrong offset, we throw an exception""" 102 """Fetch: if something goes wrong in Isolate, we throw an exception"""
108 def FetchBlobs(self, request, timeout=None): 103 def Read(self, request, timeout=None):
109 del timeout 104 del timeout
110 self.request = request 105 self.request = request
111 response = isolate_storage.isolate_bot_pb2.FetchBlobsReply() 106 raiseError(isolate_storage.grpc.StatusCode.INTERNAL)
112 response.status.succeeded = True 107 self.mock(ByteStreamStubMock, 'Read', Read)
113 response.data.data = str(42)
114 response.data.offset = 1
115 yield response
116 self.mock(FileServiceStubMock, 'FetchBlobs', FetchBlobs)
117 108
118 s = self.get_server() 109 s = self.get_server()
119 replies = s.fetch('abc123') 110 replies = s.fetch('abc123')
120 with self.assertRaises(IOError):
121 _response = replies.next()
122
123 def testFetchThrowsOnFailure(self):
124 """Fetch: if something goes wrong in Isolate, we throw an exception"""
125 def FetchBlobs(self, request, timeout=None):
126 del timeout
127 self.request = request
128 response = isolate_storage.isolate_bot_pb2.FetchBlobsReply()
129 response.status.succeeded = False
130 yield response
131 self.mock(FileServiceStubMock, 'FetchBlobs', FetchBlobs)
132
133 s = self.get_server()
134 replies = s.fetch('abc123')
135 with self.assertRaises(IOError): 111 with self.assertRaises(IOError):
136 _response = replies.next() 112 _response = replies.next()
137 113
138 def testFetchThrowsCorrectExceptionOnGrpcFailure(self): 114 def testFetchThrowsCorrectExceptionOnGrpcFailure(self):
139 """Fetch: if something goes wrong in gRPC, we throw an IOError""" 115 """Fetch: if something goes wrong in gRPC, we throw an IOError"""
140 def FetchBlobs(_self, _request, timeout=None): 116 def Read(_self, _request, timeout=None):
141 del timeout 117 del timeout
142 raise isolate_storage.grpc.RpcError('proxy died during initial fetch :(') 118 raise isolate_storage.grpc.RpcError('proxy died during initial fetch :(')
143 self.mock(FileServiceStubMock, 'FetchBlobs', FetchBlobs) 119 self.mock(ByteStreamStubMock, 'Read', Read)
144 120
145 s = self.get_server() 121 s = self.get_server()
146 replies = s.fetch('abc123') 122 replies = s.fetch('abc123')
147 with self.assertRaises(IOError): 123 with self.assertRaises(IOError):
148 _response = replies.next() 124 _response = replies.next()
149 125
150 def testFetchThrowsCorrectExceptionOnStreamingGrpcFailure(self): 126 def testFetchThrowsCorrectExceptionOnStreamingGrpcFailure(self):
151 """Fetch: if something goes wrong in gRPC, we throw an IOError""" 127 """Fetch: if something goes wrong in gRPC, we throw an IOError"""
152 def FetchBlobs(self, request, timeout=None): 128 def Read(self, request, timeout=None):
153 del timeout 129 del timeout
154 self.request = request 130 self.request = request
155 response = isolate_storage.isolate_bot_pb2.FetchBlobsReply() 131 response = isolate_storage.bytestream_pb2.ReadResponse()
156 response.status.succeeded = True
157 for i in range(0, 3): 132 for i in range(0, 3):
158 if i is 2: 133 if i is 2:
159 raise isolate_storage.grpc.RpcError( 134 raise isolate_storage.grpc.RpcError(
160 'proxy died during fetch stream :(') 135 'proxy died during fetch stream :(')
161 response.data.data = str(i) 136 response.data = str(i)
162 response.data.offset = i
163 yield response 137 yield response
164 self.mock(FileServiceStubMock, 'FetchBlobs', FetchBlobs) 138 self.mock(ByteStreamStubMock, 'Read', Read)
165 139
166 s = self.get_server() 140 s = self.get_server()
167 with self.assertRaises(IOError): 141 with self.assertRaises(IOError):
168 for _response in s.fetch('abc123'): 142 for _response in s.fetch('abc123'):
169 pass 143 pass
170 144
171 def testPushHappySingleSmall(self): 145 def testPushHappySingleSmall(self):
172 """Push: send one chunk of small data""" 146 """Push: send one chunk of small data"""
173 s = self.get_server() 147 s = self.get_server()
174 i = isolate_storage.Item(digest='abc123', size=4) 148 i = isolate_storage.Item(digest='abc123', size=4)
175 s.push(i, isolate_storage._IsolateServerGrpcPushState(), '1234') 149 s.push(i, isolate_storage._IsolateServerGrpcPushState(), '1234')
176 requests = s._stub.popPushRequests() 150 requests = s._stub.popPushRequests()
177 self.assertEqual(1, len(requests)) 151 self.assertEqual(1, len(requests))
178 self.assertEqual(binascii.unhexlify('abc123'), 152 m = re.search('client/bob/uploads/.*/blobs/abc123/4',
179 requests[0].data.digest.digest) 153 requests[0].resource_name)
180 self.assertEqual(4, requests[0].data.digest.size_bytes) 154 self.assertTrue(m)
181 self.assertEqual('1234', requests[0].data.data) 155 self.assertEqual('1234', requests[0].data)
156 self.assertEqual(0, requests[0].write_offset)
157 self.assertTrue(requests[0].finish_write)
182 158
183 def testPushHappySingleBig(self): 159 def testPushHappySingleBig(self):
184 """Push: send one chunk of big data by splitting it into two""" 160 """Push: send one chunk of big data by splitting it into two"""
185 self.mock(isolate_storage, 'NET_IO_FILE_CHUNK', 3) 161 self.mock(isolate_storage, 'NET_IO_FILE_CHUNK', 3)
186 s = self.get_server() 162 s = self.get_server()
187 i = isolate_storage.Item(digest='abc123', size=4) 163 i = isolate_storage.Item(digest='abc123', size=4)
188 s.push(i, isolate_storage._IsolateServerGrpcPushState(), '1234') 164 s.push(i, isolate_storage._IsolateServerGrpcPushState(), '1234')
189 requests = s._stub.popPushRequests() 165 requests = s._stub.popPushRequests()
190 self.assertEqual(2, len(requests)) 166 self.assertEqual(2, len(requests))
191 self.assertEqual(binascii.unhexlify('abc123'), 167 m = re.search('client/bob/uploads/.*/blobs/abc123/4',
192 requests[0].data.digest.digest) 168 requests[0].resource_name)
193 self.assertEqual(4, requests[0].data.digest.size_bytes) 169 self.assertTrue(m)
194 self.assertEqual('123', requests[0].data.data) 170 self.assertEqual('123', requests[0].data)
195 self.assertFalse(requests[1].data.HasField('digest')) 171 self.assertEqual(0, requests[0].write_offset)
196 self.assertEqual('4', requests[1].data.data) 172 self.assertFalse(requests[0].finish_write)
173 self.assertEqual('4', requests[1].data)
174 self.assertEqual(3, requests[1].write_offset)
175 self.assertTrue(requests[1].finish_write)
197 176
198 def testPushHappyMultiSmall(self): 177 def testPushHappyMultiSmall(self):
199 """Push: sends multiple small chunks""" 178 """Push: sends multiple small chunks"""
200 s = self.get_server() 179 s = self.get_server()
201 i = isolate_storage.Item(digest='abc123', size=4) 180 i = isolate_storage.Item(digest='abc123', size=4)
202 s.push(i, isolate_storage._IsolateServerGrpcPushState(), ['12', '34']) 181 s.push(i, isolate_storage._IsolateServerGrpcPushState(), ['12', '34'])
203 requests = s._stub.popPushRequests() 182 requests = s._stub.popPushRequests()
204 self.assertEqual(2, len(requests)) 183 self.assertEqual(2, len(requests))
205 self.assertEqual(binascii.unhexlify('abc123'), 184 m = re.search('client/bob/uploads/.*/blobs/abc123/4',
206 requests[0].data.digest.digest) 185 requests[0].resource_name)
207 self.assertEqual(4, requests[0].data.digest.size_bytes) 186 self.assertTrue(m)
208 self.assertEqual('12', requests[0].data.data) 187 self.assertEqual('12', requests[0].data)
209 self.assertFalse(requests[1].data.HasField('digest')) 188 self.assertEqual(0, requests[0].write_offset)
210 self.assertEqual('34', requests[1].data.data) 189 self.assertFalse(requests[0].finish_write)
190 self.assertEqual('34', requests[1].data)
191 self.assertEqual(2, requests[1].write_offset)
192 self.assertTrue(requests[1].finish_write)
211 193
212 def testPushHappyMultiBig(self): 194 def testPushHappyMultiBig(self):
213 """Push: sends multiple chunks, each of which have to be split""" 195 """Push: sends multiple chunks, each of which have to be split"""
214 self.mock(isolate_storage, 'NET_IO_FILE_CHUNK', 2) 196 self.mock(isolate_storage, 'NET_IO_FILE_CHUNK', 2)
215 s = self.get_server() 197 s = self.get_server()
216 i = isolate_storage.Item(digest='abc123', size=6) 198 i = isolate_storage.Item(digest='abc123', size=6)
217 s.push(i, isolate_storage._IsolateServerGrpcPushState(), ['123', '456']) 199 s.push(i, isolate_storage._IsolateServerGrpcPushState(), ['123', '456'])
218 requests = s._stub.popPushRequests() 200 requests = s._stub.popPushRequests()
219 self.assertEqual(4, len(requests)) 201 self.assertEqual(4, len(requests))
220 self.assertEqual(binascii.unhexlify('abc123'), 202 m = re.search('client/bob/uploads/.*/blobs/abc123/6',
221 requests[0].data.digest.digest) 203 requests[0].resource_name)
222 self.assertEqual(6, requests[0].data.digest.size_bytes) 204 self.assertTrue(m)
223 self.assertEqual('12', requests[0].data.data) 205 self.assertEqual(0, requests[0].write_offset)
224 self.assertFalse(requests[1].data.HasField('digest')) 206 self.assertEqual('12', requests[0].data)
225 self.assertEqual('3', requests[1].data.data) 207 self.assertFalse(requests[0].finish_write)
226 self.assertEqual('45', requests[2].data.data) 208 self.assertEqual(2, requests[1].write_offset)
227 self.assertEqual('6', requests[3].data.data) 209 self.assertEqual('3', requests[1].data)
210 self.assertFalse(requests[1].finish_write)
211 self.assertEqual(3, requests[2].write_offset)
212 self.assertEqual('45', requests[2].data)
213 self.assertFalse(requests[2].finish_write)
214 self.assertEqual(5, requests[3].write_offset)
215 self.assertEqual('6', requests[3].data)
216 self.assertTrue(requests[3].finish_write)
228 217
229 def testPushHappyZeroLengthBlob(self): 218 def testPushHappyZeroLengthBlob(self):
230 """Push: send a zero-length blob""" 219 """Push: send a zero-length blob"""
231 s = self.get_server() 220 s = self.get_server()
232 i = isolate_storage.Item(digest='abc123', size=0) 221 i = isolate_storage.Item(digest='abc123', size=0)
233 s.push(i, isolate_storage._IsolateServerGrpcPushState(), '') 222 s.push(i, isolate_storage._IsolateServerGrpcPushState(), '')
234 requests = s._stub.popPushRequests() 223 requests = s._stub.popPushRequests()
235 self.assertEqual(1, len(requests)) 224 self.assertEqual(1, len(requests))
236 self.assertEqual(binascii.unhexlify('abc123'), 225 m = re.search('client/bob/uploads/.*/blobs/abc123/0',
237 requests[0].data.digest.digest) 226 requests[0].resource_name)
238 self.assertEqual(0, requests[0].data.digest.size_bytes) 227 self.assertTrue(m)
239 self.assertEqual('', requests[0].data.data) 228 self.assertEqual(0, requests[0].write_offset)
229 self.assertEqual('', requests[0].data)
230 self.assertTrue(requests[0].finish_write)
240 231
241 def testPushThrowsOnFailure(self): 232 def testPushThrowsOnFailure(self):
242 """Push: if something goes wrong in Isolate, we throw an exception""" 233 """Push: if something goes wrong in Isolate, we throw an exception"""
243 def PushBlobs(self, request, timeout=None): 234 def Write(self, request, timeout=None):
244 del request, timeout, self 235 del request, timeout, self
245 response = isolate_storage.isolate_bot_pb2.PushBlobsReply() 236 raiseError(isolate_storage.grpc.StatusCode.INTERNAL_ERROR)
246 response.status.succeeded = False 237 self.mock(ByteStreamStubMock, 'Write', Write)
247 return response
248 self.mock(FileServiceStubMock, 'PushBlobs', PushBlobs)
249 238
250 s = self.get_server() 239 s = self.get_server()
251 i = isolate_storage.Item(digest='abc123', size=0) 240 i = isolate_storage.Item(digest='abc123', size=0)
252 with self.assertRaises(IOError): 241 with self.assertRaises(IOError):
253 s.push(i, isolate_storage._IsolateServerGrpcPushState(), '1234') 242 s.push(i, isolate_storage._IsolateServerGrpcPushState(), '1234')
254 243
255 def testPushThrowsCorrectExceptionOnGrpcFailure(self): 244 def testPushThrowsCorrectExceptionOnGrpcFailure(self):
256 """Push: if something goes wrong in Isolate, we throw an exception""" 245 """Push: if something goes wrong in Isolate, we throw an exception"""
257 def PushBlobs(_self, _request, timeout=None): 246 def Write(_self, _request, timeout=None):
258 del timeout 247 del timeout
259 raise isolate_storage.grpc.RpcError('proxy died during push :(') 248 raise isolate_storage.grpc.RpcError('proxy died during push :(')
260 self.mock(FileServiceStubMock, 'PushBlobs', PushBlobs) 249 self.mock(ByteStreamStubMock, 'Write', Write)
261 250
262 s = self.get_server() 251 s = self.get_server()
263 i = isolate_storage.Item(digest='abc123', size=0) 252 i = isolate_storage.Item(digest='abc123', size=0)
264 with self.assertRaises(IOError): 253 with self.assertRaises(IOError):
265 s.push(i, isolate_storage._IsolateServerGrpcPushState(), '1234') 254 s.push(i, isolate_storage._IsolateServerGrpcPushState(), '1234')
266 255
267 def testContainsHappySimple(self):
268 """Contains: basic sanity check"""
269 items = []
270 for i in range(0, 3):
271 digest = ''.join(['a', str(i)])
272 i = isolate_storage.Item(digest=digest, size=1)
273 items.append(i)
274 s = self.get_server()
275 response = s.contains(items)
276 self.assertEqual(0, len(response))
277 requests = s._stub.popContainsRequests()
278 self.assertEqual(3, len(requests[0].digest))
279 self.assertEqual('\xa0', requests[0].digest[0].digest)
280 self.assertEqual('\xa1', requests[0].digest[1].digest)
281 self.assertEqual('\xa2', requests[0].digest[2].digest)
282
283 def testContainsMissingSimple(self):
284 """Contains: the digests are missing"""
285 def Contains(self, request, timeout=None):
286 del timeout, self
287 response = isolate_storage.isolate_bot_pb2.ContainsReply()
288 response.status.succeeded = False
289 response.status.error = (
290 isolate_storage.isolate_bot_pb2.BlobStatus.MISSING_DIGEST)
291 for d in request.digest:
292 msg = response.status.missing_digest.add()
293 msg.CopyFrom(d)
294 return response
295 self.mock(FileServiceStubMock, 'Contains', Contains)
296
297 items = []
298 for i in range(0, 3):
299 digest = ''.join(['a', str(i)])
300 i = isolate_storage.Item(digest=digest, size=1)
301 items.append(i)
302 s = self.get_server()
303 response = s.contains(items)
304 self.assertEqual(3, len(response))
305 self.assertTrue(items[0] in response)
306 self.assertTrue(items[1] in response)
307 self.assertTrue(items[2] in response)
308
309 def testContainsThrowsCorrectExceptionOnGrpcFailure(self):
310 """Contains: the digests are missing"""
311 def Contains(_self, _request, timeout=None):
312 del timeout
313 raise isolate_storage.grpc.RpcError('proxy died during contains :(')
314 self.mock(FileServiceStubMock, 'Contains', Contains)
315
316 items = []
317 for i in range(0, 3):
318 digest = ''.join(['a', str(i)])
319 i = isolate_storage.Item(digest=digest, size=1)
320 items.append(i)
321 s = self.get_server()
322 with self.assertRaises(IOError):
323 _response = s.contains(items)
324
325 256
326 if __name__ == '__main__': 257 if __name__ == '__main__':
327 if not isolate_storage.grpc: 258 if not isolate_storage.grpc:
328 # Don't print to stderr or return error code as this will 259 # Don't print to stderr or return error code as this will
329 # show up as a warning and fail in presubmit. 260 # show up as a warning and fail in presubmit.
330 print('gRPC could not be loaded; skipping tests') 261 print('gRPC could not be loaded; skipping tests')
331 sys.exit(0) 262 sys.exit(0)
332 isolate_storage.isolate_bot_pb2.FileServiceStub = FileServiceStubMock 263 isolate_storage.bytestream_pb2.ByteStreamStub = ByteStreamStubMock
333 test_utils.main() 264 test_utils.main()
OLDNEW
« no previous file with comments | « client/proto/isolate_bot_pb2_grpc.py ('k') | client/third_party/cachetools/README.swarming » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698