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

Side by Side Diff: mojo/public/cpp/system/tests/core_unittest.cc

Issue 814543006: Move //mojo/{public, edk} underneath //third_party (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase Created 5 years, 11 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 | « mojo/public/cpp/system/tests/BUILD.gn ('k') | mojo/public/cpp/system/tests/macros_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 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 // This file tests the C++ Mojo system core wrappers.
6 // TODO(vtl): Maybe rename "CoreCppTest" -> "CoreTest" if/when this gets
7 // compiled into a different binary from the C API tests.
8
9 #include "mojo/public/cpp/system/core.h"
10
11 #include <stddef.h>
12
13 #include <map>
14
15 #include "mojo/public/cpp/system/macros.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17
18 namespace mojo {
19 namespace {
20
21 const MojoHandleSignals kSignalReadableWritable =
22 MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE;
23
24 const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE |
25 MOJO_HANDLE_SIGNAL_WRITABLE |
26 MOJO_HANDLE_SIGNAL_PEER_CLOSED;
27
28 TEST(CoreCppTest, GetTimeTicksNow) {
29 const MojoTimeTicks start = GetTimeTicksNow();
30 EXPECT_NE(static_cast<MojoTimeTicks>(0), start)
31 << "GetTimeTicksNow should return nonzero value";
32 }
33
34 TEST(CoreCppTest, Basic) {
35 // Basic |Handle| implementation:
36 {
37 EXPECT_EQ(MOJO_HANDLE_INVALID, kInvalidHandleValue);
38
39 Handle h0;
40 EXPECT_EQ(kInvalidHandleValue, h0.value());
41 EXPECT_EQ(kInvalidHandleValue, *h0.mutable_value());
42 EXPECT_FALSE(h0.is_valid());
43
44 Handle h1(static_cast<MojoHandle>(123));
45 EXPECT_EQ(static_cast<MojoHandle>(123), h1.value());
46 EXPECT_EQ(static_cast<MojoHandle>(123), *h1.mutable_value());
47 EXPECT_TRUE(h1.is_valid());
48 *h1.mutable_value() = static_cast<MojoHandle>(456);
49 EXPECT_EQ(static_cast<MojoHandle>(456), h1.value());
50 EXPECT_TRUE(h1.is_valid());
51
52 h1.swap(h0);
53 EXPECT_EQ(static_cast<MojoHandle>(456), h0.value());
54 EXPECT_TRUE(h0.is_valid());
55 EXPECT_FALSE(h1.is_valid());
56
57 h1.set_value(static_cast<MojoHandle>(789));
58 h0.swap(h1);
59 EXPECT_EQ(static_cast<MojoHandle>(789), h0.value());
60 EXPECT_TRUE(h0.is_valid());
61 EXPECT_EQ(static_cast<MojoHandle>(456), h1.value());
62 EXPECT_TRUE(h1.is_valid());
63
64 // Make sure copy constructor works.
65 Handle h2(h0);
66 EXPECT_EQ(static_cast<MojoHandle>(789), h2.value());
67 // And assignment.
68 h2 = h1;
69 EXPECT_EQ(static_cast<MojoHandle>(456), h2.value());
70
71 // Make sure that we can put |Handle|s into |std::map|s.
72 h0 = Handle(static_cast<MojoHandle>(987));
73 h1 = Handle(static_cast<MojoHandle>(654));
74 h2 = Handle(static_cast<MojoHandle>(321));
75 Handle h3;
76 std::map<Handle, int> handle_to_int;
77 handle_to_int[h0] = 0;
78 handle_to_int[h1] = 1;
79 handle_to_int[h2] = 2;
80 handle_to_int[h3] = 3;
81
82 EXPECT_EQ(4u, handle_to_int.size());
83 EXPECT_FALSE(handle_to_int.find(h0) == handle_to_int.end());
84 EXPECT_EQ(0, handle_to_int[h0]);
85 EXPECT_FALSE(handle_to_int.find(h1) == handle_to_int.end());
86 EXPECT_EQ(1, handle_to_int[h1]);
87 EXPECT_FALSE(handle_to_int.find(h2) == handle_to_int.end());
88 EXPECT_EQ(2, handle_to_int[h2]);
89 EXPECT_FALSE(handle_to_int.find(h3) == handle_to_int.end());
90 EXPECT_EQ(3, handle_to_int[h3]);
91 EXPECT_TRUE(handle_to_int.find(Handle(static_cast<MojoHandle>(13579))) ==
92 handle_to_int.end());
93
94 // TODO(vtl): With C++11, support |std::unordered_map|s, etc. (Or figure out
95 // how to support the variations of |hash_map|.)
96 }
97
98 // |Handle|/|ScopedHandle| functions:
99 {
100 ScopedHandle h;
101
102 EXPECT_EQ(kInvalidHandleValue, h.get().value());
103
104 // This should be a no-op.
105 Close(h.Pass());
106
107 // It should still be invalid.
108 EXPECT_EQ(kInvalidHandleValue, h.get().value());
109
110 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
111 Wait(h.get(), ~MOJO_HANDLE_SIGNAL_NONE, 1000000, nullptr));
112
113 std::vector<Handle> wh;
114 wh.push_back(h.get());
115 std::vector<MojoHandleSignals> sigs;
116 sigs.push_back(~MOJO_HANDLE_SIGNAL_NONE);
117 WaitManyResult wait_many_result =
118 WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE, nullptr);
119 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, wait_many_result.result);
120 EXPECT_TRUE(wait_many_result.IsIndexValid());
121 EXPECT_FALSE(wait_many_result.AreSignalsStatesValid());
122
123 // Make sure that our specialized template correctly handles |NULL| as well
124 // as |nullptr|.
125 wait_many_result = WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE, NULL);
126 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, wait_many_result.result);
127 EXPECT_EQ(0u, wait_many_result.index);
128 EXPECT_TRUE(wait_many_result.IsIndexValid());
129 EXPECT_FALSE(wait_many_result.AreSignalsStatesValid());
130 }
131
132 // |MakeScopedHandle| (just compilation tests):
133 {
134 EXPECT_FALSE(MakeScopedHandle(Handle()).is_valid());
135 EXPECT_FALSE(MakeScopedHandle(MessagePipeHandle()).is_valid());
136 EXPECT_FALSE(MakeScopedHandle(DataPipeProducerHandle()).is_valid());
137 EXPECT_FALSE(MakeScopedHandle(DataPipeConsumerHandle()).is_valid());
138 EXPECT_FALSE(MakeScopedHandle(SharedBufferHandle()).is_valid());
139 }
140
141 // |MessagePipeHandle|/|ScopedMessagePipeHandle| functions:
142 {
143 MessagePipeHandle h_invalid;
144 EXPECT_FALSE(h_invalid.is_valid());
145 EXPECT_EQ(
146 MOJO_RESULT_INVALID_ARGUMENT,
147 WriteMessageRaw(
148 h_invalid, nullptr, 0, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
149 char buffer[10] = {0};
150 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
151 WriteMessageRaw(h_invalid,
152 buffer,
153 sizeof(buffer),
154 nullptr,
155 0,
156 MOJO_WRITE_MESSAGE_FLAG_NONE));
157 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
158 ReadMessageRaw(h_invalid,
159 nullptr,
160 nullptr,
161 nullptr,
162 nullptr,
163 MOJO_READ_MESSAGE_FLAG_NONE));
164 uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
165 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
166 ReadMessageRaw(h_invalid,
167 buffer,
168 &buffer_size,
169 nullptr,
170 nullptr,
171 MOJO_READ_MESSAGE_FLAG_NONE));
172
173 // Basic tests of waiting and closing.
174 MojoHandle hv0 = kInvalidHandleValue;
175 {
176 ScopedMessagePipeHandle h0;
177 ScopedMessagePipeHandle h1;
178 EXPECT_FALSE(h0.get().is_valid());
179 EXPECT_FALSE(h1.get().is_valid());
180
181 CreateMessagePipe(nullptr, &h0, &h1);
182 EXPECT_TRUE(h0.get().is_valid());
183 EXPECT_TRUE(h1.get().is_valid());
184 EXPECT_NE(h0.get().value(), h1.get().value());
185 // Save the handle values, so we can check that things got closed
186 // correctly.
187 hv0 = h0.get().value();
188 MojoHandle hv1 = h1.get().value();
189 MojoHandleSignalsState state;
190
191 EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
192 Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, 0, &state));
193
194 EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
195 EXPECT_EQ(kSignalAll, state.satisfiable_signals);
196
197 std::vector<Handle> wh;
198 wh.push_back(h0.get());
199 wh.push_back(h1.get());
200 std::vector<MojoHandleSignals> sigs;
201 sigs.push_back(MOJO_HANDLE_SIGNAL_READABLE);
202 sigs.push_back(MOJO_HANDLE_SIGNAL_WRITABLE);
203 std::vector<MojoHandleSignalsState> states(sigs.size());
204 WaitManyResult wait_many_result = WaitMany(wh, sigs, 1000, &states);
205 EXPECT_EQ(MOJO_RESULT_OK, wait_many_result.result);
206 EXPECT_EQ(1u, wait_many_result.index);
207 EXPECT_TRUE(wait_many_result.IsIndexValid());
208 EXPECT_TRUE(wait_many_result.AreSignalsStatesValid());
209 EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[0].satisfied_signals);
210 EXPECT_EQ(kSignalAll, states[0].satisfiable_signals);
211 EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[1].satisfied_signals);
212 EXPECT_EQ(kSignalAll, states[1].satisfiable_signals);
213
214 // Test closing |h1| explicitly.
215 Close(h1.Pass());
216 EXPECT_FALSE(h1.get().is_valid());
217
218 // Make sure |h1| is closed.
219 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
220 Wait(Handle(hv1), ~MOJO_HANDLE_SIGNAL_NONE,
221 MOJO_DEADLINE_INDEFINITE, nullptr));
222
223 EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
224 Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE,
225 MOJO_DEADLINE_INDEFINITE, &state));
226
227 EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
228 EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
229 }
230 // |hv0| should have been closed when |h0| went out of scope, so this close
231 // should fail.
232 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0));
233
234 // Actually test writing/reading messages.
235 {
236 ScopedMessagePipeHandle h0;
237 ScopedMessagePipeHandle h1;
238 CreateMessagePipe(nullptr, &h0, &h1);
239
240 const char kHello[] = "hello";
241 const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
242 EXPECT_EQ(MOJO_RESULT_OK,
243 WriteMessageRaw(h0.get(),
244 kHello,
245 kHelloSize,
246 nullptr,
247 0,
248 MOJO_WRITE_MESSAGE_FLAG_NONE));
249
250 MojoHandleSignalsState state;
251 EXPECT_EQ(MOJO_RESULT_OK, Wait(h1.get(), MOJO_HANDLE_SIGNAL_READABLE,
252 MOJO_DEADLINE_INDEFINITE, &state));
253 EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
254 EXPECT_EQ(kSignalAll, state.satisfiable_signals);
255
256 char buffer[10] = {0};
257 uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
258 EXPECT_EQ(MOJO_RESULT_OK,
259 ReadMessageRaw(h1.get(),
260 buffer,
261 &buffer_size,
262 nullptr,
263 nullptr,
264 MOJO_READ_MESSAGE_FLAG_NONE));
265 EXPECT_EQ(kHelloSize, buffer_size);
266 EXPECT_STREQ(kHello, buffer);
267
268 // Send a handle over the previously-establish message pipe. Use the
269 // |MessagePipe| wrapper (to test it), which automatically creates a
270 // message pipe.
271 MessagePipe mp;
272
273 // Write a message to |mp.handle0|, before we send |mp.handle1|.
274 const char kWorld[] = "world!";
275 const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
276 EXPECT_EQ(MOJO_RESULT_OK,
277 WriteMessageRaw(mp.handle0.get(),
278 kWorld,
279 kWorldSize,
280 nullptr,
281 0,
282 MOJO_WRITE_MESSAGE_FLAG_NONE));
283
284 // Send |mp.handle1| over |h1| to |h0|.
285 MojoHandle handles[5];
286 handles[0] = mp.handle1.release().value();
287 EXPECT_NE(kInvalidHandleValue, handles[0]);
288 EXPECT_FALSE(mp.handle1.get().is_valid());
289 uint32_t handles_count = 1;
290 EXPECT_EQ(MOJO_RESULT_OK,
291 WriteMessageRaw(h1.get(),
292 kHello,
293 kHelloSize,
294 handles,
295 handles_count,
296 MOJO_WRITE_MESSAGE_FLAG_NONE));
297 // |handles[0]| should actually be invalid now.
298 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handles[0]));
299
300 // Read "hello" and the sent handle.
301 EXPECT_EQ(MOJO_RESULT_OK, Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE,
302 MOJO_DEADLINE_INDEFINITE, &state));
303 EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
304 EXPECT_EQ(kSignalAll, state.satisfiable_signals);
305
306 memset(buffer, 0, sizeof(buffer));
307 buffer_size = static_cast<uint32_t>(sizeof(buffer));
308 for (size_t i = 0; i < MOJO_ARRAYSIZE(handles); i++)
309 handles[i] = kInvalidHandleValue;
310 handles_count = static_cast<uint32_t>(MOJO_ARRAYSIZE(handles));
311 EXPECT_EQ(MOJO_RESULT_OK,
312 ReadMessageRaw(h0.get(),
313 buffer,
314 &buffer_size,
315 handles,
316 &handles_count,
317 MOJO_READ_MESSAGE_FLAG_NONE));
318 EXPECT_EQ(kHelloSize, buffer_size);
319 EXPECT_STREQ(kHello, buffer);
320 EXPECT_EQ(1u, handles_count);
321 EXPECT_NE(kInvalidHandleValue, handles[0]);
322
323 // Read from the sent/received handle.
324 mp.handle1.reset(MessagePipeHandle(handles[0]));
325 // Save |handles[0]| to check that it gets properly closed.
326 hv0 = handles[0];
327
328 EXPECT_EQ(MOJO_RESULT_OK,
329 Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE,
330 MOJO_DEADLINE_INDEFINITE, &state));
331 EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
332 EXPECT_EQ(kSignalAll, state.satisfiable_signals);
333
334 memset(buffer, 0, sizeof(buffer));
335 buffer_size = static_cast<uint32_t>(sizeof(buffer));
336 for (size_t i = 0; i < MOJO_ARRAYSIZE(handles); i++)
337 handles[i] = kInvalidHandleValue;
338 handles_count = static_cast<uint32_t>(MOJO_ARRAYSIZE(handles));
339 EXPECT_EQ(MOJO_RESULT_OK,
340 ReadMessageRaw(mp.handle1.get(),
341 buffer,
342 &buffer_size,
343 handles,
344 &handles_count,
345 MOJO_READ_MESSAGE_FLAG_NONE));
346 EXPECT_EQ(kWorldSize, buffer_size);
347 EXPECT_STREQ(kWorld, buffer);
348 EXPECT_EQ(0u, handles_count);
349 }
350 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0));
351 }
352
353 // TODO(vtl): Test |CloseRaw()|.
354 // TODO(vtl): Test |reset()| more thoroughly?
355 }
356
357 TEST(CoreCppTest, TearDownWithMessagesEnqueued) {
358 // Tear down a message pipe which still has a message enqueued, with the
359 // message also having a valid message pipe handle.
360 {
361 ScopedMessagePipeHandle h0;
362 ScopedMessagePipeHandle h1;
363 CreateMessagePipe(nullptr, &h0, &h1);
364
365 // Send a handle over the previously-establish message pipe.
366 ScopedMessagePipeHandle h2;
367 ScopedMessagePipeHandle h3;
368 CreateMessagePipe(nullptr, &h2, &h3);
369
370 // Write a message to |h2|, before we send |h3|.
371 const char kWorld[] = "world!";
372 const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
373 EXPECT_EQ(MOJO_RESULT_OK,
374 WriteMessageRaw(h2.get(),
375 kWorld,
376 kWorldSize,
377 nullptr,
378 0,
379 MOJO_WRITE_MESSAGE_FLAG_NONE));
380 // And also a message to |h3|.
381 EXPECT_EQ(MOJO_RESULT_OK,
382 WriteMessageRaw(h3.get(),
383 kWorld,
384 kWorldSize,
385 nullptr,
386 0,
387 MOJO_WRITE_MESSAGE_FLAG_NONE));
388
389 // Send |h3| over |h1| to |h0|.
390 const char kHello[] = "hello";
391 const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
392 MojoHandle h3_value;
393 h3_value = h3.release().value();
394 EXPECT_NE(kInvalidHandleValue, h3_value);
395 EXPECT_FALSE(h3.get().is_valid());
396 EXPECT_EQ(MOJO_RESULT_OK,
397 WriteMessageRaw(h1.get(),
398 kHello,
399 kHelloSize,
400 &h3_value,
401 1,
402 MOJO_WRITE_MESSAGE_FLAG_NONE));
403 // |h3_value| should actually be invalid now.
404 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value));
405
406 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value()));
407 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value()));
408 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value()));
409 }
410
411 // Do this in a different order: make the enqueued message pipe handle only
412 // half-alive.
413 {
414 ScopedMessagePipeHandle h0;
415 ScopedMessagePipeHandle h1;
416 CreateMessagePipe(nullptr, &h0, &h1);
417
418 // Send a handle over the previously-establish message pipe.
419 ScopedMessagePipeHandle h2;
420 ScopedMessagePipeHandle h3;
421 CreateMessagePipe(nullptr, &h2, &h3);
422
423 // Write a message to |h2|, before we send |h3|.
424 const char kWorld[] = "world!";
425 const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
426 EXPECT_EQ(MOJO_RESULT_OK,
427 WriteMessageRaw(h2.get(),
428 kWorld,
429 kWorldSize,
430 nullptr,
431 0,
432 MOJO_WRITE_MESSAGE_FLAG_NONE));
433 // And also a message to |h3|.
434 EXPECT_EQ(MOJO_RESULT_OK,
435 WriteMessageRaw(h3.get(),
436 kWorld,
437 kWorldSize,
438 nullptr,
439 0,
440 MOJO_WRITE_MESSAGE_FLAG_NONE));
441
442 // Send |h3| over |h1| to |h0|.
443 const char kHello[] = "hello";
444 const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
445 MojoHandle h3_value;
446 h3_value = h3.release().value();
447 EXPECT_NE(kInvalidHandleValue, h3_value);
448 EXPECT_FALSE(h3.get().is_valid());
449 EXPECT_EQ(MOJO_RESULT_OK,
450 WriteMessageRaw(h1.get(),
451 kHello,
452 kHelloSize,
453 &h3_value,
454 1,
455 MOJO_WRITE_MESSAGE_FLAG_NONE));
456 // |h3_value| should actually be invalid now.
457 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value));
458
459 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value()));
460 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value()));
461 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value()));
462 }
463 }
464
465 TEST(CoreCppTest, ScopedHandleMoveCtor) {
466 ScopedSharedBufferHandle buffer1;
467 EXPECT_EQ(MOJO_RESULT_OK, CreateSharedBuffer(nullptr, 1024, &buffer1));
468 EXPECT_TRUE(buffer1.is_valid());
469
470 ScopedSharedBufferHandle buffer2;
471 EXPECT_EQ(MOJO_RESULT_OK, CreateSharedBuffer(nullptr, 1024, &buffer2));
472 EXPECT_TRUE(buffer2.is_valid());
473
474 // If this fails to close buffer1, ScopedHandleBase::CloseIfNecessary() will
475 // assert.
476 buffer1 = buffer2.Pass();
477
478 EXPECT_TRUE(buffer1.is_valid());
479 EXPECT_FALSE(buffer2.is_valid());
480 }
481
482 TEST(CoreCppTest, ScopedHandleMoveCtorSelf) {
483 ScopedSharedBufferHandle buffer1;
484 EXPECT_EQ(MOJO_RESULT_OK, CreateSharedBuffer(nullptr, 1024, &buffer1));
485 EXPECT_TRUE(buffer1.is_valid());
486
487 buffer1 = buffer1.Pass();
488
489 EXPECT_TRUE(buffer1.is_valid());
490 }
491
492 // TODO(vtl): Write data pipe tests.
493
494 } // namespace
495 } // namespace mojo
OLDNEW
« no previous file with comments | « mojo/public/cpp/system/tests/BUILD.gn ('k') | mojo/public/cpp/system/tests/macros_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698