| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package dscache | 5 package dscache |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bytes" | 8 "bytes" |
| 9 "encoding/binary" | 9 "encoding/binary" |
| 10 "errors" | 10 "errors" |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 59 | 59 |
| 60 Convey("Test dscache", t, func() { | 60 Convey("Test dscache", t, func() { |
| 61 c := mathrand.Set(context.Background(), rand.New(rand.NewSource(
1))) | 61 c := mathrand.Set(context.Background(), rand.New(rand.NewSource(
1))) |
| 62 clk := testclock.New(zeroTime) | 62 clk := testclock.New(zeroTime) |
| 63 c = clock.Set(c, clk) | 63 c = clock.Set(c, clk) |
| 64 c = memory.Use(c) | 64 c = memory.Use(c) |
| 65 | 65 |
| 66 dsUnder := datastore.Get(c) | 66 dsUnder := datastore.Get(c) |
| 67 mc := memcache.Get(c) | 67 mc := memcache.Get(c) |
| 68 | 68 |
| 69 itmFor := func(i int, k *datastore.Key) memcache.Item { | |
| 70 return mc.NewItem(MakeMemcacheKey(i, k)) | |
| 71 } | |
| 72 | |
| 73 shardsForKey := func(k *datastore.Key) int { | 69 shardsForKey := func(k *datastore.Key) int { |
| 74 last := k.Last() | 70 last := k.Last() |
| 75 if last.Kind == "shardObj" { | 71 if last.Kind == "shardObj" { |
| 76 return int(last.IntID) | 72 return int(last.IntID) |
| 77 } | 73 } |
| 78 if last.Kind == "noCacheObj" { | 74 if last.Kind == "noCacheObj" { |
| 79 return 0 | 75 return 0 |
| 80 } | 76 } |
| 81 return DefaultShards | 77 return DefaultShards |
| 82 } | 78 } |
| (...skipping 18 matching lines...) Expand all Loading... |
| 101 } | 97 } |
| 102 encoded := append([]byte{0}, serialize.ToBytes(p
m)...) | 98 encoded := append([]byte{0}, serialize.ToBytes(p
m)...) |
| 103 | 99 |
| 104 o := object{ID: 1, Value: "hi"} | 100 o := object{ID: 1, Value: "hi"} |
| 105 So(ds.Put(&o), ShouldBeNil) | 101 So(ds.Put(&o), ShouldBeNil) |
| 106 | 102 |
| 107 o = object{ID: 1} | 103 o = object{ID: 1} |
| 108 So(dsUnder.Get(&o), ShouldBeNil) | 104 So(dsUnder.Get(&o), ShouldBeNil) |
| 109 So(o.Value, ShouldEqual, "hi") | 105 So(o.Value, ShouldEqual, "hi") |
| 110 | 106 |
| 111 » » » » itm := itmFor(0, ds.KeyForObj(&o)) | 107 » » » » itm, err := mc.Get(MakeMemcacheKey(0, ds.KeyForO
bj(&o))) |
| 112 » » » » So(mc.Get(itm), ShouldEqual, memcache.ErrCacheMi
ss) | 108 » » » » So(err, ShouldEqual, memcache.ErrCacheMiss) |
| 113 | 109 |
| 114 o = object{ID: 1} | 110 o = object{ID: 1} |
| 115 So(ds.Get(&o), ShouldBeNil) | 111 So(ds.Get(&o), ShouldBeNil) |
| 116 So(o.Value, ShouldEqual, "hi") | 112 So(o.Value, ShouldEqual, "hi") |
| 117 » » » » So(mc.Get(itm), ShouldBeNil) | 113 |
| 114 » » » » itm, err = mc.Get(itm.Key()) |
| 115 » » » » So(err, ShouldBeNil) |
| 118 So(itm.Value(), ShouldResemble, encoded) | 116 So(itm.Value(), ShouldResemble, encoded) |
| 119 | 117 |
| 120 Convey("now we don't need the datastore!", func(
) { | 118 Convey("now we don't need the datastore!", func(
) { |
| 121 o := object{ID: 1} | 119 o := object{ID: 1} |
| 122 | 120 |
| 123 // delete it, bypassing the cache filter
. Don't do this in production | 121 // delete it, bypassing the cache filter
. Don't do this in production |
| 124 // unless you want a crappy cache. | 122 // unless you want a crappy cache. |
| 125 So(dsUnder.Delete(ds.KeyForObj(&o)), Sho
uldBeNil) | 123 So(dsUnder.Delete(ds.KeyForObj(&o)), Sho
uldBeNil) |
| 126 | 124 |
| 127 » » » » » itm := itmFor(0, ds.KeyForObj(&o)) | 125 » » » » » itm, err := mc.Get(MakeMemcacheKey(0, ds
.KeyForObj(&o))) |
| 128 » » » » » So(mc.Get(itm), ShouldBeNil) | 126 » » » » » So(err, ShouldBeNil) |
| 129 So(itm.Value(), ShouldResemble, encoded) | 127 So(itm.Value(), ShouldResemble, encoded) |
| 130 | 128 |
| 131 So(ds.Get(&o), ShouldBeNil) | 129 So(ds.Get(&o), ShouldBeNil) |
| 132 So(o.Value, ShouldEqual, "hi") | 130 So(o.Value, ShouldEqual, "hi") |
| 133 }) | 131 }) |
| 134 | 132 |
| 135 Convey("deleting it properly records that fact,
however", func() { | 133 Convey("deleting it properly records that fact,
however", func() { |
| 136 o := object{ID: 1} | 134 o := object{ID: 1} |
| 137 So(ds.Delete(ds.KeyForObj(&o)), ShouldBe
Nil) | 135 So(ds.Delete(ds.KeyForObj(&o)), ShouldBe
Nil) |
| 138 | 136 |
| 139 » » » » » itm := itmFor(0, ds.KeyForObj(&o)) | 137 » » » » » itm, err := mc.Get(MakeMemcacheKey(0, ds
.KeyForObj(&o))) |
| 140 » » » » » So(mc.Get(itm), ShouldEqual, memcache.Er
rCacheMiss) | 138 » » » » » So(err, ShouldEqual, memcache.ErrCacheMi
ss) |
| 141 So(ds.Get(&o), ShouldEqual, datastore.Er
rNoSuchEntity) | 139 So(ds.Get(&o), ShouldEqual, datastore.Er
rNoSuchEntity) |
| 142 | 140 |
| 143 » » » » » So(mc.Get(itm), ShouldBeNil) | 141 » » » » » itm, err = mc.Get(itm.Key()) |
| 142 » » » » » So(err, ShouldBeNil) |
| 144 So(itm.Value(), ShouldResemble, []byte{}
) | 143 So(itm.Value(), ShouldResemble, []byte{}
) |
| 145 | 144 |
| 146 // this one hits memcache | 145 // this one hits memcache |
| 147 So(ds.Get(&o), ShouldEqual, datastore.Er
rNoSuchEntity) | 146 So(ds.Get(&o), ShouldEqual, datastore.Er
rNoSuchEntity) |
| 148 }) | 147 }) |
| 149 }) | 148 }) |
| 150 | 149 |
| 151 Convey("compression works", func() { | 150 Convey("compression works", func() { |
| 152 o := object{ID: 2, Value: `¯\_(ツ)_/¯`} | 151 o := object{ID: 2, Value: `¯\_(ツ)_/¯`} |
| 153 data := make([]byte, 4000) | 152 data := make([]byte, 4000) |
| 154 for i := range data { | 153 for i := range data { |
| 155 const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXY
Zabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()" | 154 const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXY
Zabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()" |
| 156 data[i] = alpha[i%len(alpha)] | 155 data[i] = alpha[i%len(alpha)] |
| 157 } | 156 } |
| 158 o.BigData = data | 157 o.BigData = data |
| 159 | 158 |
| 160 So(ds.Put(&o), ShouldBeNil) | 159 So(ds.Put(&o), ShouldBeNil) |
| 161 So(ds.Get(&o), ShouldBeNil) | 160 So(ds.Get(&o), ShouldBeNil) |
| 162 | 161 |
| 163 » » » » itm := itmFor(0, ds.KeyForObj(&o)) | 162 » » » » itm, err := mc.Get(MakeMemcacheKey(0, ds.KeyForO
bj(&o))) |
| 164 » » » » So(mc.Get(itm), ShouldBeNil) | 163 » » » » So(err, ShouldBeNil) |
| 165 | 164 |
| 166 So(itm.Value()[0], ShouldEqual, ZlibCompression) | 165 So(itm.Value()[0], ShouldEqual, ZlibCompression) |
| 167 So(len(itm.Value()), ShouldEqual, 653) // a bit
smaller than 4k | 166 So(len(itm.Value()), ShouldEqual, 653) // a bit
smaller than 4k |
| 168 | 167 |
| 169 // ensure the next Get comes from the cache | 168 // ensure the next Get comes from the cache |
| 170 So(dsUnder.Delete(ds.KeyForObj(&o)), ShouldBeNil
) | 169 So(dsUnder.Delete(ds.KeyForObj(&o)), ShouldBeNil
) |
| 171 | 170 |
| 172 o = object{ID: 2} | 171 o = object{ID: 2} |
| 173 So(ds.Get(&o), ShouldBeNil) | 172 So(ds.Get(&o), ShouldBeNil) |
| 174 So(o.Value, ShouldEqual, `¯\_(ツ)_/¯`) | 173 So(o.Value, ShouldEqual, `¯\_(ツ)_/¯`) |
| (...skipping 16 matching lines...) Expand all Loading... |
| 191 o := &object{ID: 1} | 190 o := &object{ID: 1} |
| 192 So(ds.Get(o), ShouldBeNil) | 191 So(ds.Get(o), ShouldBeNil) |
| 193 So(o.Value, ShouldEqual, "else") | 192 So(o.Value, ShouldEqual, "else") |
| 194 o.Value = "txn" | 193 o.Value = "txn" |
| 195 So(ds.Put(o), ShouldBeNil) | 194 So(ds.Put(o), ShouldBeNil) |
| 196 | 195 |
| 197 So(ds.Delete(ds.KeyForObj(&objec
t{ID: 2})), ShouldBeNil) | 196 So(ds.Delete(ds.KeyForObj(&objec
t{ID: 2})), ShouldBeNil) |
| 198 return nil | 197 return nil |
| 199 }, &datastore.TransactionOptions{XG: tru
e}), ShouldBeNil) | 198 }, &datastore.TransactionOptions{XG: tru
e}), ShouldBeNil) |
| 200 | 199 |
| 201 » » » » » So(mc.Get(itmFor(0, ds.KeyForObj(&object
{ID: 1}))), | 200 » » » » » _, err := mc.Get(MakeMemcacheKey(0, ds.K
eyForObj(&object{ID: 1}))) |
| 202 » » » » » » ShouldEqual, memcache.ErrCacheMi
ss) | 201 » » » » » So(err, ShouldEqual, memcache.ErrCacheMi
ss) |
| 203 » » » » » So(mc.Get(itmFor(0, ds.KeyForObj(&object
{ID: 2}))), | 202 » » » » » _, err = mc.Get(MakeMemcacheKey(0, ds.Ke
yForObj(&object{ID: 2}))) |
| 204 » » » » » » ShouldEqual, memcache.ErrCacheMi
ss) | 203 » » » » » So(err, ShouldEqual, memcache.ErrCacheMi
ss) |
| 205 o := &object{ID: 1} | 204 o := &object{ID: 1} |
| 206 So(ds.Get(o), ShouldBeNil) | 205 So(ds.Get(o), ShouldBeNil) |
| 207 So(o.Value, ShouldEqual, "txn") | 206 So(o.Value, ShouldEqual, "txn") |
| 208 }) | 207 }) |
| 209 | 208 |
| 210 Convey("errors don't invalidate", func() { | 209 Convey("errors don't invalidate", func() { |
| 211 // populate an object @ ID1 | 210 // populate an object @ ID1 |
| 212 So(ds.Put(&object{ID: 1, Value: "somethi
ng"}), ShouldBeNil) | 211 So(ds.Put(&object{ID: 1, Value: "somethi
ng"}), ShouldBeNil) |
| 213 So(ds.Get(&object{ID: 1}), ShouldBeNil) | 212 So(ds.Get(&object{ID: 1}), ShouldBeNil) |
| 214 So(numMemcacheItems(), ShouldEqual, 1) | 213 So(numMemcacheItems(), ShouldEqual, 1) |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 275 type model struct { | 274 type model struct { |
| 276 ID int64 `gae:"$id"` | 275 ID int64 `gae:"$id"` |
| 277 DSCacheExp int64 `gae:"$dscache.
expiration,7"` | 276 DSCacheExp int64 `gae:"$dscache.
expiration,7"` |
| 278 | 277 |
| 279 Value string | 278 Value string |
| 280 } | 279 } |
| 281 | 280 |
| 282 So(ds.Put(&model{ID: 1, Value: "mooo"}),
ShouldBeNil) | 281 So(ds.Put(&model{ID: 1, Value: "mooo"}),
ShouldBeNil) |
| 283 So(ds.Get(&model{ID: 1}), ShouldBeNil) | 282 So(ds.Get(&model{ID: 1}), ShouldBeNil) |
| 284 | 283 |
| 285 » » » » » itm := itmFor(0, ds.KeyForObj(&model{ID:
1})) | 284 » » » » » itm, err := mc.Get(MakeMemcacheKey(0, ds
.KeyForObj(&model{ID: 1}))) |
| 286 » » » » » So(mc.Get(itm), ShouldBeNil) | 285 » » » » » So(err, ShouldBeNil) |
| 287 | 286 |
| 288 clk.Add(10 * time.Second) | 287 clk.Add(10 * time.Second) |
| 289 » » » » » So(mc.Get(itm), ShouldEqual, memcache.Er
rCacheMiss) | 288 » » » » » _, err = mc.Get(itm.Key()) |
| 289 » » » » » So(err, ShouldEqual, memcache.ErrCacheMi
ss) |
| 290 }) | 290 }) |
| 291 }) | 291 }) |
| 292 | 292 |
| 293 Convey("screw cases", func() { | 293 Convey("screw cases", func() { |
| 294 Convey("memcache contains bogus value (simulated
failed AddMulti)", func() { | 294 Convey("memcache contains bogus value (simulated
failed AddMulti)", func() { |
| 295 o := &object{ID: 1, Value: "spleen"} | 295 o := &object{ID: 1, Value: "spleen"} |
| 296 So(ds.Put(o), ShouldBeNil) | 296 So(ds.Put(o), ShouldBeNil) |
| 297 | 297 |
| 298 sekret := []byte("I am a banana") | 298 sekret := []byte("I am a banana") |
| 299 » » » » » itm := itmFor(0, ds.KeyForObj(o)).SetVal
ue(sekret) | 299 » » » » » itm := mc.NewItem(MakeMemcacheKey(0, ds.
KeyForObj(o))).SetValue(sekret) |
| 300 So(mc.Set(itm), ShouldBeNil) | 300 So(mc.Set(itm), ShouldBeNil) |
| 301 | 301 |
| 302 o = &object{ID: 1} | 302 o = &object{ID: 1} |
| 303 So(ds.Get(o), ShouldBeNil) | 303 So(ds.Get(o), ShouldBeNil) |
| 304 So(o.Value, ShouldEqual, "spleen") | 304 So(o.Value, ShouldEqual, "spleen") |
| 305 | 305 |
| 306 » » » » » So(mc.Get(itm), ShouldBeNil) | 306 » » » » » itm, err := mc.Get(itm.Key()) |
| 307 » » » » » So(err, ShouldBeNil) |
| 307 So(itm.Flags(), ShouldEqual, ItemUKNONWN
) | 308 So(itm.Flags(), ShouldEqual, ItemUKNONWN
) |
| 308 So(itm.Value(), ShouldResemble, sekret) | 309 So(itm.Value(), ShouldResemble, sekret) |
| 309 }) | 310 }) |
| 310 | 311 |
| 311 Convey("memcache contains bogus value (corrupt e
ntry)", func() { | 312 Convey("memcache contains bogus value (corrupt e
ntry)", func() { |
| 312 o := &object{ID: 1, Value: "spleen"} | 313 o := &object{ID: 1, Value: "spleen"} |
| 313 So(ds.Put(o), ShouldBeNil) | 314 So(ds.Put(o), ShouldBeNil) |
| 314 | 315 |
| 315 sekret := []byte("I am a banana") | 316 sekret := []byte("I am a banana") |
| 316 » » » » » itm := (itmFor(0, ds.KeyForObj(o)). | 317 » » » » » itm := (mc.NewItem(MakeMemcacheKey(0, ds
.KeyForObj(o))). |
| 317 SetValue(sekret). | 318 SetValue(sekret). |
| 318 SetFlags(uint32(ItemHasData))) | 319 SetFlags(uint32(ItemHasData))) |
| 319 So(mc.Set(itm), ShouldBeNil) | 320 So(mc.Set(itm), ShouldBeNil) |
| 320 | 321 |
| 321 o = &object{ID: 1} | 322 o = &object{ID: 1} |
| 322 So(ds.Get(o), ShouldBeNil) | 323 So(ds.Get(o), ShouldBeNil) |
| 323 So(o.Value, ShouldEqual, "spleen") | 324 So(o.Value, ShouldEqual, "spleen") |
| 324 | 325 |
| 325 » » » » » So(mc.Get(itm), ShouldBeNil) | 326 » » » » » itm, err := mc.Get(itm.Key()) |
| 327 » » » » » So(err, ShouldBeNil) |
| 326 So(itm.Flags(), ShouldEqual, ItemHasData
) | 328 So(itm.Flags(), ShouldEqual, ItemHasData
) |
| 327 So(itm.Value(), ShouldResemble, sekret) | 329 So(itm.Value(), ShouldResemble, sekret) |
| 328 }) | 330 }) |
| 329 | 331 |
| 330 Convey("other entity has the lock", func() { | 332 Convey("other entity has the lock", func() { |
| 331 o := &object{ID: 1, Value: "spleen"} | 333 o := &object{ID: 1, Value: "spleen"} |
| 332 So(ds.Put(o), ShouldBeNil) | 334 So(ds.Put(o), ShouldBeNil) |
| 333 | 335 |
| 334 sekret := []byte("r@vmarod!#)%9T") | 336 sekret := []byte("r@vmarod!#)%9T") |
| 335 » » » » » itm := (itmFor(0, ds.KeyForObj(o)). | 337 » » » » » itm := (mc.NewItem(MakeMemcacheKey(0, ds
.KeyForObj(o))). |
| 336 SetValue(sekret). | 338 SetValue(sekret). |
| 337 SetFlags(uint32(ItemHasLock))) | 339 SetFlags(uint32(ItemHasLock))) |
| 338 So(mc.Set(itm), ShouldBeNil) | 340 So(mc.Set(itm), ShouldBeNil) |
| 339 | 341 |
| 340 o = &object{ID: 1} | 342 o = &object{ID: 1} |
| 341 So(ds.Get(o), ShouldBeNil) | 343 So(ds.Get(o), ShouldBeNil) |
| 342 So(o.Value, ShouldEqual, "spleen") | 344 So(o.Value, ShouldEqual, "spleen") |
| 343 | 345 |
| 344 » » » » » So(mc.Get(itm), ShouldBeNil) | 346 » » » » » itm, err := mc.Get(itm.Key()) |
| 347 » » » » » So(err, ShouldBeNil) |
| 345 So(itm.Flags(), ShouldEqual, ItemHasLock
) | 348 So(itm.Flags(), ShouldEqual, ItemHasLock
) |
| 346 So(itm.Value(), ShouldResemble, sekret) | 349 So(itm.Value(), ShouldResemble, sekret) |
| 347 }) | 350 }) |
| 348 | 351 |
| 349 Convey("massive entities can't be cached", func(
) { | 352 Convey("massive entities can't be cached", func(
) { |
| 350 o := &object{ID: 1, Value: "spleen"} | 353 o := &object{ID: 1, Value: "spleen"} |
| 351 mr := mathrand.Get(c) | 354 mr := mathrand.Get(c) |
| 352 numRounds := (internalValueSizeLimit / 8
) * 2 | 355 numRounds := (internalValueSizeLimit / 8
) * 2 |
| 353 buf := bytes.Buffer{} | 356 buf := bytes.Buffer{} |
| 354 for i := 0; i < numRounds; i++ { | 357 for i := 0; i < numRounds; i++ { |
| 355 So(binary.Write(&buf, binary.Lit
tleEndian, mr.Int63()), ShouldBeNil) | 358 So(binary.Write(&buf, binary.Lit
tleEndian, mr.Int63()), ShouldBeNil) |
| 356 } | 359 } |
| 357 o.BigData = buf.Bytes() | 360 o.BigData = buf.Bytes() |
| 358 So(ds.Put(o), ShouldBeNil) | 361 So(ds.Put(o), ShouldBeNil) |
| 359 | 362 |
| 360 o.BigData = nil | 363 o.BigData = nil |
| 361 So(ds.Get(o), ShouldBeNil) | 364 So(ds.Get(o), ShouldBeNil) |
| 362 | 365 |
| 363 » » » » » itm := itmFor(0, ds.KeyForObj(o)) | 366 » » » » » itm, err := mc.Get(MakeMemcacheKey(0, ds
.KeyForObj(o))) |
| 364 » » » » » So(mc.Get(itm), ShouldBeNil) | 367 » » » » » So(err, ShouldBeNil) |
| 365 | 368 |
| 366 // Is locked until the next put, forcing
all access to the datastore. | 369 // Is locked until the next put, forcing
all access to the datastore. |
| 367 So(itm.Value(), ShouldResemble, []byte{}
) | 370 So(itm.Value(), ShouldResemble, []byte{}
) |
| 368 So(itm.Flags(), ShouldEqual, ItemHasLock
) | 371 So(itm.Flags(), ShouldEqual, ItemHasLock
) |
| 369 | 372 |
| 370 o.BigData = []byte("hi :)") | 373 o.BigData = []byte("hi :)") |
| 371 So(ds.Put(o), ShouldBeNil) | 374 So(ds.Put(o), ShouldBeNil) |
| 372 So(ds.Get(o), ShouldBeNil) | 375 So(ds.Get(o), ShouldBeNil) |
| 373 | 376 |
| 374 » » » » » So(mc.Get(itm), ShouldBeNil) | 377 » » » » » itm, err = mc.Get(itm.Key()) |
| 378 » » » » » So(err, ShouldBeNil) |
| 375 So(itm.Flags(), ShouldEqual, ItemHasData
) | 379 So(itm.Flags(), ShouldEqual, ItemHasData
) |
| 376 }) | 380 }) |
| 377 | 381 |
| 378 Convey("failure on Setting memcache locks is a h
ard stop", func() { | 382 Convey("failure on Setting memcache locks is a h
ard stop", func() { |
| 379 c, fb := featureBreaker.FilterMC(c, nil) | 383 c, fb := featureBreaker.FilterMC(c, nil) |
| 380 fb.BreakFeatures(nil, "SetMulti") | 384 fb.BreakFeatures(nil, "SetMulti") |
| 381 ds := datastore.Get(c) | 385 ds := datastore.Get(c) |
| 382 So(ds.Put(&object{ID: 1}).Error(), Shoul
dContainSubstring, "SetMulti") | 386 So(ds.Put(&object{ID: 1}).Error(), Shoul
dContainSubstring, "SetMulti") |
| 383 }) | 387 }) |
| 384 | 388 |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 448 InstanceEnabledStatic = false | 452 InstanceEnabledStatic = false |
| 449 defer func() { | 453 defer func() { |
| 450 InstanceEnabledStatic = true | 454 InstanceEnabledStatic = true |
| 451 }() | 455 }() |
| 452 | 456 |
| 453 c := context.Background() | 457 c := context.Background() |
| 454 newC := FilterRDS(c, nil) | 458 newC := FilterRDS(c, nil) |
| 455 So(newC, ShouldEqual, c) | 459 So(newC, ShouldEqual, c) |
| 456 }) | 460 }) |
| 457 } | 461 } |
| OLD | NEW |