| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package principal |
| |
| import ( |
| "reflect" |
| "sync" |
| "testing" |
| "time" |
| ) |
| |
| // manualTrigger provides a gc trigger that can be signaled manually |
| type manualTrigger struct { |
| gcShouldBeNext bool |
| lock sync.Mutex |
| cond *sync.Cond |
| gcHasRun bool |
| } |
| |
| func newManualTrigger() *manualTrigger { |
| mt := &manualTrigger{ |
| gcShouldBeNext: true, |
| } |
| mt.cond = sync.NewCond(&mt.lock) |
| return mt |
| } |
| |
| // policyTrigger is the trigger that should be provided in GC policy config. |
| // It waits until it receives a signal and then returns a chan time.Time |
| // that resolves immediately. |
| func (mt *manualTrigger) waitForNextGc() <-chan time.Time { |
| mt.lock.Lock() |
| if !mt.gcHasRun { |
| mt.gcHasRun = true |
| } else { |
| mt.gcShouldBeNext = false // hand off control |
| mt.cond.Broadcast() |
| for !mt.gcShouldBeNext { |
| mt.cond.Wait() |
| } |
| } |
| mt.lock.Unlock() |
| |
| trigger := make(chan time.Time, 1) |
| trigger <- time.Time{} |
| return trigger |
| } |
| |
| // next should be called to trigger the next policy trigger event. |
| func (mt *manualTrigger) next() { |
| mt.lock.Lock() |
| mt.gcShouldBeNext = true // hand off control |
| mt.cond.Broadcast() |
| for mt.gcShouldBeNext { |
| mt.cond.Wait() |
| } |
| mt.lock.Unlock() |
| } |
| |
| // Test just to confirm it signals in order as expected. |
| func TestManualTrigger(t *testing.T) { |
| mt := newManualTrigger() |
| |
| countTriggers := 0 |
| go func() { |
| for i := 1; i <= 100; i++ { |
| <-mt.waitForNextGc() |
| countTriggers++ |
| } |
| }() |
| |
| for i := 1; i <= 99; i++ { |
| mt.next() |
| if countTriggers != i { |
| t.Errorf("Expected %d triggers, got %d", i, countTriggers) |
| } |
| } |
| } |
| |
| func TestBlessingsCache(t *testing.T) { |
| notificationCh := make(chan []BlessingsCacheMessage, 1) |
| notifier := func(msg []BlessingsCacheMessage) { |
| notificationCh <- msg |
| } |
| |
| mt := newManualTrigger() |
| |
| // Create a BlessingsCache with a GC policy that we can trigger on demand. |
| onDemandGCPolicy := &BlessingsCacheGCPolicy{ |
| nextTrigger: mt.waitForNextGc, |
| } |
| bc := NewBlessingsCache(notifier, onDemandGCPolicy) |
| |
| // Blessings for the tests. |
| jsBlessA := &JsBlessings{ |
| Handle: 1, |
| PublicKey: "A", |
| } |
| jsBlessB := &JsBlessings{ |
| Handle: 2, |
| PublicKey: "B", |
| } |
| jsBlessC := &JsBlessings{ |
| Handle: 4, |
| PublicKey: "C", |
| } |
| |
| // First do puts and make sure the ids are reasonable. |
| idA := bc.Put(jsBlessA) |
| expectAddMessage(t, notificationCh, 1, jsBlessA) |
| idB := bc.Put(jsBlessB) |
| expectAddMessage(t, notificationCh, 2, jsBlessB) |
| if idA == idB { |
| t.Errorf("A and B unexpectedly had same id: %v", idA) |
| } |
| |
| idA2 := bc.Put(jsBlessA) |
| expectNoMessage(t, notificationCh) |
| if idA2 != idA { |
| t.Errorf("A and A2 expected to have same id, but they were %v and %v", |
| idA, idA2) |
| } |
| |
| // Now perform GC. Check that the values are still in the cache. |
| mt.next() |
| expectNoMessage(t, notificationCh) |
| |
| idGc1A := bc.Put(jsBlessA) |
| idGc1B := bc.Put(jsBlessB) |
| expectNoMessage(t, notificationCh) |
| if idA != idGc1A { |
| t.Errorf("Expected to get same id after one gc of A, but got %v and %v", |
| idA, idGc1A) |
| } |
| if idB != idGc1B { |
| t.Errorf("Expected to get same id after one gc of B, but got %v and %v", |
| idB, idGc1B) |
| } |
| |
| // Now perform GC to clear the dirty bits. |
| mt.next() |
| expectNoMessage(t, notificationCh) |
| |
| // Update B and add C. |
| idGc2B := bc.Put(jsBlessB) |
| expectNoMessage(t, notificationCh) |
| if idB != idGc2B { |
| t.Errorf("Expected to get same id after two gcs of B, but got %v and %v", |
| idB, idGc2B) |
| } |
| idC := bc.Put(jsBlessC) |
| expectAddMessage(t, notificationCh, 3, jsBlessC) |
| if idC == idA || idC == idB { |
| t.Error("C was unexpectedly the same as A or B") |
| } |
| |
| // Perform GC. A should be removed but B and C should stay. |
| mt.next() |
| expectDeleteMessage(t, notificationCh, BlessingsCacheDeleteMessage{CacheId: 1, DeleteAfter: 3}) |
| if idB != bc.Put(jsBlessB) { |
| t.Errorf("B seems to have been cleaned up as it was given a new id") |
| } |
| if idC != bc.Put(jsBlessC) { |
| t.Errorf("C seems to have been cleaned up as it was given a new id") |
| } |
| expectNoMessage(t, notificationCh) |
| |
| // Perform GC twice to remove the other items. |
| mt.next() |
| expectNoMessage(t, notificationCh) |
| mt.next() |
| expectDeleteMessage(t, notificationCh, |
| BlessingsCacheDeleteMessage{CacheId: 2, DeleteAfter: 4}, |
| BlessingsCacheDeleteMessage{CacheId: 3, DeleteAfter: 2}) |
| |
| // No notifications should occur on further GCs. |
| mt.next() |
| mt.next() |
| mt.next() |
| // Note that this should only be reached after the second GC is done because |
| // the GC trigger channel has size 1. |
| expectNoMessage(t, notificationCh) |
| |
| bc.Stop() |
| } |
| |
| func expectNoMessage(t *testing.T, notificationCh chan []BlessingsCacheMessage) { |
| select { |
| case <-notificationCh: |
| t.Errorf("Got message when none expected") |
| default: |
| } |
| } |
| |
| func expectAddMessage(t *testing.T, notificationCh chan []BlessingsCacheMessage, id BlessingsId, bless *JsBlessings) { |
| select { |
| case notifications := <-notificationCh: |
| if len(notifications) != 1 { |
| t.Fatalf("Got invalid add message with %d messages", len(notifications)) |
| } |
| addMsg := notifications[0].(BlessingsCacheMessageAdd).Value |
| if got, want := addMsg.CacheId, id; got != want { |
| t.Errorf("Unexpected id in add message: %v. Wanted: %v", got, want) |
| } |
| if got, want := addMsg.Blessings, *bless; !reflect.DeepEqual(got, want) { |
| t.Errorf("Blessings unexpectedly not equal. Got %v, want %v", got, want) |
| } |
| case <-time.After(10 * time.Second): |
| t.Fatalf("Timed out waiting for notification") |
| } |
| } |
| |
| func expectDeleteMessage(t *testing.T, notificationCh chan []BlessingsCacheMessage, expected ...BlessingsCacheDeleteMessage) { |
| select { |
| case notifications := <-notificationCh: |
| if len(notifications) != len(expected) { |
| t.Fatalf("Got %d notifications but expected %d delete notifications", len(notifications), len(expected)) |
| } |
| for _, notification := range notifications { |
| delNotif := notification.(BlessingsCacheMessageDelete).Value |
| var foundMatch bool |
| for _, expectedNotif := range expected { |
| if reflect.DeepEqual(delNotif, expectedNotif) { |
| foundMatch = true |
| } |
| } |
| if !foundMatch { |
| t.Errorf("Unexpected delete notification: %v", delNotif) |
| } |
| } |
| case <-time.After(10 * time.Second): |
| t.Fatalf("Timed out waiting for notification") |
| } |
| } |