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

Side by Side Diff: components/sync/core/processor_entity_tracker_unittest.cc

Issue 2412193002: [Sync] Move SharedModelTypeProcessor to model_impl/. (Closed)
Patch Set: Address comment from Sky. Created 4 years, 2 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
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 #include "components/sync/core/processor_entity_tracker.h"
6
7 #include <utility>
8
9 #include "base/memory/ptr_util.h"
10 #include "components/sync/base/model_type.h"
11 #include "components/sync/base/time.h"
12 #include "components/sync/core/non_blocking_sync_common.h"
13 #include "components/sync/protocol/sync.pb.h"
14 #include "components/sync/syncable/syncable_util.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16
17 namespace syncer {
18
19 namespace {
20
21 const char kKey[] = "key";
22 const char kHash[] = "hash";
23 const char kId[] = "id";
24 const char kName[] = "name";
25 const char kValue1[] = "value1";
26 const char kValue2[] = "value2";
27 const char kValue3[] = "value3";
28
29 sync_pb::EntitySpecifics GenerateSpecifics(const std::string& name,
30 const std::string& value) {
31 sync_pb::EntitySpecifics specifics;
32 specifics.mutable_preference()->set_name(name);
33 specifics.mutable_preference()->set_value(value);
34 return specifics;
35 }
36
37 std::unique_ptr<EntityData> GenerateEntityData(const std::string& hash,
38 const std::string& name,
39 const std::string& value) {
40 std::unique_ptr<EntityData> entity_data(new EntityData());
41 entity_data->client_tag_hash = hash;
42 entity_data->specifics = GenerateSpecifics(name, value);
43 entity_data->non_unique_name = name;
44 return entity_data;
45 }
46
47 UpdateResponseData GenerateUpdate(const ProcessorEntityTracker& entity,
48 const std::string& hash,
49 const std::string& id,
50 const std::string& name,
51 const std::string& value,
52 const base::Time& mtime,
53 int64_t version) {
54 std::unique_ptr<EntityData> data = GenerateEntityData(hash, name, value);
55 data->id = id;
56 data->modification_time = mtime;
57 UpdateResponseData update;
58 update.entity = data->PassToPtr();
59 update.response_version = version;
60 return update;
61 }
62
63 UpdateResponseData GenerateTombstone(const ProcessorEntityTracker& entity,
64 const std::string& hash,
65 const std::string& id,
66 const std::string& name,
67 const base::Time& mtime,
68 int64_t version) {
69 std::unique_ptr<EntityData> data = base::MakeUnique<EntityData>();
70 data->client_tag_hash = hash;
71 data->non_unique_name = name;
72 data->id = id;
73 data->modification_time = mtime;
74 UpdateResponseData update;
75 update.entity = data->PassToPtr();
76 update.response_version = version;
77 return update;
78 }
79
80 CommitResponseData GenerateAckData(const CommitRequestData& request,
81 const std::string id,
82 int64_t version) {
83 CommitResponseData response;
84 response.id = id;
85 response.client_tag_hash = request.entity->client_tag_hash;
86 response.sequence_number = request.sequence_number;
87 response.response_version = version;
88 response.specifics_hash = request.specifics_hash;
89 return response;
90 }
91
92 } // namespace
93
94 // Some simple sanity tests for the ProcessorEntityTracker.
95 //
96 // A lot of the more complicated sync logic is implemented in the
97 // SharedModelTypeProcessor that owns the ProcessorEntityTracker. We can't unit
98 // test it here.
99 //
100 // Instead, we focus on simple tests to make sure that variables are getting
101 // properly intialized and flags properly set. Anything more complicated would
102 // be a redundant and incomplete version of the SharedModelTypeProcessor tests.
103 class ProcessorEntityTrackerTest : public ::testing::Test {
104 public:
105 ProcessorEntityTrackerTest()
106 : ctime_(base::Time::Now() - base::TimeDelta::FromSeconds(1)) {}
107
108 std::unique_ptr<ProcessorEntityTracker> CreateNew() {
109 return ProcessorEntityTracker::CreateNew(kKey, kHash, "", ctime_);
110 }
111
112 std::unique_ptr<ProcessorEntityTracker> CreateSynced() {
113 std::unique_ptr<ProcessorEntityTracker> entity = CreateNew();
114 entity->RecordAcceptedUpdate(
115 GenerateUpdate(*entity, kHash, kId, kName, kValue1, ctime_, 1));
116 DCHECK(!entity->IsUnsynced());
117 return entity;
118 }
119
120 const base::Time ctime_;
121 };
122
123 // Test the state of the default new tracker.
124 TEST_F(ProcessorEntityTrackerTest, DefaultTracker) {
125 std::unique_ptr<ProcessorEntityTracker> entity = CreateNew();
126
127 EXPECT_EQ(kKey, entity->storage_key());
128 EXPECT_EQ(kHash, entity->metadata().client_tag_hash());
129 EXPECT_EQ("", entity->metadata().server_id());
130 EXPECT_FALSE(entity->metadata().is_deleted());
131 EXPECT_EQ(0, entity->metadata().sequence_number());
132 EXPECT_EQ(0, entity->metadata().acked_sequence_number());
133 EXPECT_EQ(kUncommittedVersion, entity->metadata().server_version());
134 EXPECT_EQ(TimeToProtoTime(ctime_), entity->metadata().creation_time());
135 EXPECT_EQ(0, entity->metadata().modification_time());
136 EXPECT_TRUE(entity->metadata().specifics_hash().empty());
137 EXPECT_TRUE(entity->metadata().base_specifics_hash().empty());
138
139 EXPECT_FALSE(entity->IsUnsynced());
140 EXPECT_FALSE(entity->RequiresCommitRequest());
141 EXPECT_FALSE(entity->RequiresCommitData());
142 EXPECT_FALSE(entity->CanClearMetadata());
143 EXPECT_FALSE(entity->UpdateIsReflection(1));
144 EXPECT_FALSE(entity->HasCommitData());
145 }
146
147 // Test creating and commiting a new local item.
148 TEST_F(ProcessorEntityTrackerTest, NewLocalItem) {
149 std::unique_ptr<ProcessorEntityTracker> entity = CreateNew();
150 entity->MakeLocalChange(GenerateEntityData(kHash, kName, kValue1));
151
152 EXPECT_EQ("", entity->metadata().server_id());
153 EXPECT_FALSE(entity->metadata().is_deleted());
154 EXPECT_EQ(1, entity->metadata().sequence_number());
155 EXPECT_EQ(0, entity->metadata().acked_sequence_number());
156 EXPECT_EQ(kUncommittedVersion, entity->metadata().server_version());
157 EXPECT_NE(0, entity->metadata().modification_time());
158 EXPECT_FALSE(entity->metadata().specifics_hash().empty());
159 EXPECT_TRUE(entity->metadata().base_specifics_hash().empty());
160
161 EXPECT_TRUE(entity->IsUnsynced());
162 EXPECT_TRUE(entity->RequiresCommitRequest());
163 EXPECT_FALSE(entity->RequiresCommitData());
164 EXPECT_FALSE(entity->CanClearMetadata());
165 EXPECT_FALSE(entity->UpdateIsReflection(1));
166 EXPECT_TRUE(entity->HasCommitData());
167
168 EXPECT_EQ(kValue1, entity->commit_data()->specifics.preference().value());
169
170 // Generate a commit request. The metadata should not change.
171 const sync_pb::EntityMetadata metadata_v1 = entity->metadata();
172 CommitRequestData request;
173 entity->InitializeCommitRequestData(&request);
174 EXPECT_EQ(metadata_v1.SerializeAsString(),
175 entity->metadata().SerializeAsString());
176
177 EXPECT_TRUE(entity->IsUnsynced());
178 EXPECT_FALSE(entity->RequiresCommitRequest());
179 EXPECT_FALSE(entity->RequiresCommitData());
180 EXPECT_FALSE(entity->CanClearMetadata());
181 EXPECT_FALSE(entity->UpdateIsReflection(1));
182 EXPECT_TRUE(entity->HasCommitData());
183
184 const EntityData& data = request.entity.value();
185 EXPECT_EQ("", data.id);
186 EXPECT_EQ(kHash, data.client_tag_hash);
187 EXPECT_EQ(kName, data.non_unique_name);
188 EXPECT_EQ(kValue1, data.specifics.preference().value());
189 EXPECT_EQ(TimeToProtoTime(ctime_), TimeToProtoTime(data.creation_time));
190 EXPECT_EQ(entity->metadata().modification_time(),
191 TimeToProtoTime(data.modification_time));
192 EXPECT_FALSE(data.is_deleted());
193 EXPECT_EQ(1, request.sequence_number);
194 EXPECT_EQ(kUncommittedVersion, request.base_version);
195 EXPECT_EQ(entity->metadata().specifics_hash(), request.specifics_hash);
196
197 // Ack the commit.
198 entity->ReceiveCommitResponse(GenerateAckData(request, kId, 1));
199
200 EXPECT_EQ(kId, entity->metadata().server_id());
201 EXPECT_FALSE(entity->metadata().is_deleted());
202 EXPECT_EQ(1, entity->metadata().sequence_number());
203 EXPECT_EQ(1, entity->metadata().acked_sequence_number());
204 EXPECT_EQ(1, entity->metadata().server_version());
205 EXPECT_EQ(metadata_v1.creation_time(), entity->metadata().creation_time());
206 EXPECT_EQ(metadata_v1.modification_time(),
207 entity->metadata().modification_time());
208 EXPECT_FALSE(entity->metadata().specifics_hash().empty());
209 EXPECT_TRUE(entity->metadata().base_specifics_hash().empty());
210
211 EXPECT_FALSE(entity->IsUnsynced());
212 EXPECT_FALSE(entity->RequiresCommitRequest());
213 EXPECT_FALSE(entity->RequiresCommitData());
214 EXPECT_FALSE(entity->CanClearMetadata());
215 EXPECT_TRUE(entity->UpdateIsReflection(1));
216 EXPECT_FALSE(entity->HasCommitData());
217 }
218
219 // Test state for a newly synced server item.
220 TEST_F(ProcessorEntityTrackerTest, NewServerItem) {
221 std::unique_ptr<ProcessorEntityTracker> entity = CreateNew();
222
223 const base::Time mtime = base::Time::Now();
224 entity->RecordAcceptedUpdate(
225 GenerateUpdate(*entity, kHash, kId, kName, kValue1, mtime, 10));
226
227 EXPECT_EQ(kId, entity->metadata().server_id());
228 EXPECT_FALSE(entity->metadata().is_deleted());
229 EXPECT_EQ(0, entity->metadata().sequence_number());
230 EXPECT_EQ(0, entity->metadata().acked_sequence_number());
231 EXPECT_EQ(10, entity->metadata().server_version());
232 EXPECT_EQ(TimeToProtoTime(mtime), entity->metadata().modification_time());
233 EXPECT_FALSE(entity->metadata().specifics_hash().empty());
234 EXPECT_TRUE(entity->metadata().base_specifics_hash().empty());
235
236 EXPECT_FALSE(entity->IsUnsynced());
237 EXPECT_FALSE(entity->RequiresCommitRequest());
238 EXPECT_FALSE(entity->RequiresCommitData());
239 EXPECT_FALSE(entity->CanClearMetadata());
240 EXPECT_TRUE(entity->UpdateIsReflection(9));
241 EXPECT_TRUE(entity->UpdateIsReflection(10));
242 EXPECT_FALSE(entity->UpdateIsReflection(11));
243 EXPECT_FALSE(entity->HasCommitData());
244 }
245
246 // Test state for a tombstone received for a previously unknown item.
247 TEST_F(ProcessorEntityTrackerTest, NewServerTombstone) {
248 std::unique_ptr<ProcessorEntityTracker> entity = CreateNew();
249
250 const base::Time mtime = base::Time::Now();
251 entity->RecordAcceptedUpdate(
252 GenerateTombstone(*entity, kHash, kId, kName, mtime, 1));
253
254 EXPECT_EQ(kId, entity->metadata().server_id());
255 EXPECT_TRUE(entity->metadata().is_deleted());
256 EXPECT_EQ(0, entity->metadata().sequence_number());
257 EXPECT_EQ(0, entity->metadata().acked_sequence_number());
258 EXPECT_EQ(1, entity->metadata().server_version());
259 EXPECT_EQ(TimeToProtoTime(mtime), entity->metadata().modification_time());
260 EXPECT_TRUE(entity->metadata().specifics_hash().empty());
261 EXPECT_TRUE(entity->metadata().base_specifics_hash().empty());
262
263 EXPECT_FALSE(entity->IsUnsynced());
264 EXPECT_FALSE(entity->RequiresCommitRequest());
265 EXPECT_FALSE(entity->RequiresCommitData());
266 EXPECT_TRUE(entity->CanClearMetadata());
267 EXPECT_TRUE(entity->UpdateIsReflection(1));
268 EXPECT_FALSE(entity->UpdateIsReflection(2));
269 EXPECT_FALSE(entity->HasCommitData());
270 }
271
272 // Apply a deletion update to a synced item.
273 TEST_F(ProcessorEntityTrackerTest, ServerTombstone) {
274 // Start with a non-deleted state with version 1.
275 std::unique_ptr<ProcessorEntityTracker> entity = CreateSynced();
276 // A deletion update one version later.
277 const base::Time mtime = base::Time::Now();
278 entity->RecordAcceptedUpdate(
279 GenerateTombstone(*entity, kHash, kId, kName, mtime, 2));
280
281 EXPECT_TRUE(entity->metadata().is_deleted());
282 EXPECT_EQ(0, entity->metadata().sequence_number());
283 EXPECT_EQ(0, entity->metadata().acked_sequence_number());
284 EXPECT_EQ(2, entity->metadata().server_version());
285 EXPECT_EQ(TimeToProtoTime(mtime), entity->metadata().modification_time());
286 EXPECT_TRUE(entity->metadata().specifics_hash().empty());
287 EXPECT_TRUE(entity->metadata().base_specifics_hash().empty());
288
289 EXPECT_FALSE(entity->IsUnsynced());
290 EXPECT_FALSE(entity->RequiresCommitRequest());
291 EXPECT_FALSE(entity->RequiresCommitData());
292 EXPECT_TRUE(entity->CanClearMetadata());
293 EXPECT_TRUE(entity->UpdateIsReflection(2));
294 EXPECT_FALSE(entity->UpdateIsReflection(3));
295 EXPECT_FALSE(entity->HasCommitData());
296 }
297
298 // Test a local change of a synced item.
299 TEST_F(ProcessorEntityTrackerTest, LocalChange) {
300 std::unique_ptr<ProcessorEntityTracker> entity = CreateSynced();
301 const int64_t mtime_v0 = entity->metadata().modification_time();
302 const std::string specifics_hash_v0 = entity->metadata().specifics_hash();
303
304 // Make a local change with different specifics.
305 entity->MakeLocalChange(GenerateEntityData(kHash, kName, kValue2));
306
307 const int64_t mtime_v1 = entity->metadata().modification_time();
308 const std::string specifics_hash_v1 = entity->metadata().specifics_hash();
309
310 EXPECT_FALSE(entity->metadata().is_deleted());
311 EXPECT_EQ(1, entity->metadata().sequence_number());
312 EXPECT_EQ(0, entity->metadata().acked_sequence_number());
313 EXPECT_EQ(1, entity->metadata().server_version());
314 EXPECT_LT(mtime_v0, mtime_v1);
315 EXPECT_NE(specifics_hash_v0, specifics_hash_v1);
316 EXPECT_EQ(specifics_hash_v0, entity->metadata().base_specifics_hash());
317
318 EXPECT_TRUE(entity->IsUnsynced());
319 EXPECT_TRUE(entity->RequiresCommitRequest());
320 EXPECT_FALSE(entity->RequiresCommitData());
321 EXPECT_FALSE(entity->CanClearMetadata());
322 EXPECT_TRUE(entity->HasCommitData());
323
324 // Make a commit.
325 CommitRequestData request;
326 entity->InitializeCommitRequestData(&request);
327
328 EXPECT_EQ(kId, request.entity->id);
329 EXPECT_FALSE(entity->RequiresCommitRequest());
330
331 // Ack the commit.
332 entity->ReceiveCommitResponse(GenerateAckData(request, kId, 2));
333
334 EXPECT_EQ(1, entity->metadata().sequence_number());
335 EXPECT_EQ(1, entity->metadata().acked_sequence_number());
336 EXPECT_EQ(2, entity->metadata().server_version());
337 EXPECT_EQ(mtime_v1, entity->metadata().modification_time());
338 EXPECT_EQ(specifics_hash_v1, entity->metadata().specifics_hash());
339 EXPECT_EQ("", entity->metadata().base_specifics_hash());
340
341 EXPECT_FALSE(entity->IsUnsynced());
342 EXPECT_FALSE(entity->RequiresCommitRequest());
343 EXPECT_FALSE(entity->RequiresCommitData());
344 EXPECT_FALSE(entity->CanClearMetadata());
345 EXPECT_FALSE(entity->HasCommitData());
346 }
347
348 // Test a local deletion of a synced item.
349 TEST_F(ProcessorEntityTrackerTest, LocalDeletion) {
350 std::unique_ptr<ProcessorEntityTracker> entity = CreateSynced();
351 const int64_t mtime = entity->metadata().modification_time();
352 const std::string specifics_hash = entity->metadata().specifics_hash();
353
354 // Make a local delete.
355 entity->Delete();
356
357 EXPECT_TRUE(entity->metadata().is_deleted());
358 EXPECT_EQ(1, entity->metadata().sequence_number());
359 EXPECT_EQ(0, entity->metadata().acked_sequence_number());
360 EXPECT_EQ(1, entity->metadata().server_version());
361 EXPECT_LT(mtime, entity->metadata().modification_time());
362 EXPECT_TRUE(entity->metadata().specifics_hash().empty());
363 EXPECT_EQ(specifics_hash, entity->metadata().base_specifics_hash());
364
365 EXPECT_TRUE(entity->IsUnsynced());
366 EXPECT_TRUE(entity->RequiresCommitRequest());
367 EXPECT_FALSE(entity->RequiresCommitData());
368 EXPECT_FALSE(entity->CanClearMetadata());
369 EXPECT_FALSE(entity->HasCommitData());
370
371 // Generate a commit request. The metadata should not change.
372 const sync_pb::EntityMetadata metadata_v1 = entity->metadata();
373 CommitRequestData request;
374 entity->InitializeCommitRequestData(&request);
375 EXPECT_EQ(metadata_v1.SerializeAsString(),
376 entity->metadata().SerializeAsString());
377
378 EXPECT_TRUE(entity->IsUnsynced());
379 EXPECT_FALSE(entity->RequiresCommitRequest());
380 EXPECT_FALSE(entity->RequiresCommitData());
381 EXPECT_FALSE(entity->CanClearMetadata());
382 EXPECT_FALSE(entity->HasCommitData());
383
384 const EntityData& data = request.entity.value();
385 EXPECT_EQ(kId, data.id);
386 EXPECT_EQ(kHash, data.client_tag_hash);
387 EXPECT_EQ("", data.non_unique_name);
388 EXPECT_EQ(TimeToProtoTime(ctime_), TimeToProtoTime(data.creation_time));
389 EXPECT_EQ(entity->metadata().modification_time(),
390 TimeToProtoTime(data.modification_time));
391 EXPECT_TRUE(data.is_deleted());
392 EXPECT_EQ(1, request.sequence_number);
393 EXPECT_EQ(1, request.base_version);
394 EXPECT_EQ(entity->metadata().specifics_hash(), request.specifics_hash);
395
396 // Ack the deletion.
397 entity->ReceiveCommitResponse(GenerateAckData(request, kId, 2));
398
399 EXPECT_TRUE(entity->metadata().is_deleted());
400 EXPECT_EQ(1, entity->metadata().sequence_number());
401 EXPECT_EQ(1, entity->metadata().acked_sequence_number());
402 EXPECT_EQ(2, entity->metadata().server_version());
403 EXPECT_EQ(metadata_v1.modification_time(),
404 entity->metadata().modification_time());
405 EXPECT_TRUE(entity->metadata().specifics_hash().empty());
406 EXPECT_TRUE(entity->metadata().base_specifics_hash().empty());
407
408 EXPECT_FALSE(entity->IsUnsynced());
409 EXPECT_FALSE(entity->RequiresCommitRequest());
410 EXPECT_FALSE(entity->RequiresCommitData());
411 EXPECT_TRUE(entity->CanClearMetadata());
412 EXPECT_FALSE(entity->HasCommitData());
413 }
414
415 // Test that hashes and sequence numbers are handled correctly for the "commit
416 // commit, ack ack" case.
417 TEST_F(ProcessorEntityTrackerTest, LocalChangesInterleaved) {
418 std::unique_ptr<ProcessorEntityTracker> entity = CreateSynced();
419 const std::string specifics_hash_v0 = entity->metadata().specifics_hash();
420
421 // Make the first change.
422 entity->MakeLocalChange(GenerateEntityData(kHash, kName, kValue2));
423 const std::string specifics_hash_v1 = entity->metadata().specifics_hash();
424
425 EXPECT_EQ(1, entity->metadata().sequence_number());
426 EXPECT_EQ(0, entity->metadata().acked_sequence_number());
427 EXPECT_NE(specifics_hash_v0, specifics_hash_v1);
428 EXPECT_EQ(specifics_hash_v0, entity->metadata().base_specifics_hash());
429
430 // Request the first commit.
431 CommitRequestData request_v1;
432 entity->InitializeCommitRequestData(&request_v1);
433
434 // Make the second change.
435 entity->MakeLocalChange(GenerateEntityData(kHash, kName, kValue3));
436 const std::string specifics_hash_v2 = entity->metadata().specifics_hash();
437
438 EXPECT_EQ(2, entity->metadata().sequence_number());
439 EXPECT_EQ(0, entity->metadata().acked_sequence_number());
440 EXPECT_NE(specifics_hash_v1, specifics_hash_v2);
441 EXPECT_EQ(specifics_hash_v0, entity->metadata().base_specifics_hash());
442
443 // Request the second commit.
444 CommitRequestData request_v2;
445 entity->InitializeCommitRequestData(&request_v2);
446
447 EXPECT_TRUE(entity->IsUnsynced());
448 EXPECT_FALSE(entity->RequiresCommitRequest());
449 EXPECT_FALSE(entity->RequiresCommitData());
450 EXPECT_FALSE(entity->CanClearMetadata());
451 EXPECT_TRUE(entity->HasCommitData());
452
453 // Ack the first commit.
454 entity->ReceiveCommitResponse(GenerateAckData(request_v1, kId, 2));
455
456 EXPECT_EQ(2, entity->metadata().sequence_number());
457 EXPECT_EQ(1, entity->metadata().acked_sequence_number());
458 EXPECT_EQ(2, entity->metadata().server_version());
459 EXPECT_EQ(specifics_hash_v2, entity->metadata().specifics_hash());
460 EXPECT_EQ(specifics_hash_v1, entity->metadata().base_specifics_hash());
461
462 EXPECT_TRUE(entity->IsUnsynced());
463 EXPECT_FALSE(entity->RequiresCommitRequest());
464 EXPECT_FALSE(entity->RequiresCommitData());
465 EXPECT_FALSE(entity->CanClearMetadata());
466 EXPECT_TRUE(entity->HasCommitData());
467
468 // Ack the second commit.
469 entity->ReceiveCommitResponse(GenerateAckData(request_v2, kId, 3));
470
471 EXPECT_EQ(2, entity->metadata().sequence_number());
472 EXPECT_EQ(2, entity->metadata().acked_sequence_number());
473 EXPECT_EQ(3, entity->metadata().server_version());
474 EXPECT_EQ(specifics_hash_v2, entity->metadata().specifics_hash());
475 EXPECT_EQ("", entity->metadata().base_specifics_hash());
476
477 EXPECT_FALSE(entity->IsUnsynced());
478 EXPECT_FALSE(entity->RequiresCommitRequest());
479 EXPECT_FALSE(entity->RequiresCommitData());
480 EXPECT_FALSE(entity->CanClearMetadata());
481 EXPECT_FALSE(entity->HasCommitData());
482 }
483
484 } // namespace syncer
OLDNEW
« no previous file with comments | « components/sync/core/processor_entity_tracker.cc ('k') | components/sync/core/shared_model_type_processor.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698