Repository: etcd-io/etcd
File: cache/cache_test.go (Go)

Code before:
	secondSnapshot := &clientv3.GetResponse{
		Header: &pb.ResponseHeader{Revision: 20},
		Kvs:    []*mvccpb.KeyValue{{Key: []byte("foo"), ModRevision: 20}},
	}
	fakeClient := &clientv3.Client{
		Watcher: newMockWatcher(16),
		KV:      newKVStub(firstSnapshot, secondSnapshot),
	}
	cache, err := New(fakeClient, "")
	if err != nil {
		t.Fatalf("New cache: %v", err)
	}
	defer cache.Close()
	mw := fakeClient.Watcher.(*mockWatcher)

	t.Log("Phase 1: initial getWatch bootstrap")
	mw.triggerCreatedNotify()
	<-mw.registered
	if err = cache.WaitReady(t.Context()); err != nil {
		t.Fatalf("initial WaitReady: %v", err)
	}

	t.Log("Phase 2: simulate compaction")
	mw.errorCompacted(10)
	waitForResetReady(t, cache, 100*time.Millisecond)
	if gotRev := cache.store.LatestRev(); gotRev != 0 {
		t.Fatalf("store not reset after compaction: got latestRev=%d, want 0", gotRev)
	}

	t.Log("Phase 3: resync after compaction")
	mw.triggerCreatedNotify()
	if err = cache.WaitReady(t.Context()); err != nil {
		t.Fatalf("second WaitReady: %v", err)
	}

	expectSnapshotRev := int64(20)
	expectedWatchStart := secondSnapshot.Header.Revision + 1
	if gotWatchStart := mw.lastStartRev; gotWatchStart != expectedWatchStart {
		t.Errorf("Watch started at rev=%d; want %d", gotWatchStart, expectedWatchStart)
	}

	ctx, cancel := context.WithTimeout(t.Context(), time.Second)
	defer cancel()
	if err = cache.WaitForRevision(ctx, expectSnapshotRev); err != nil {
		t.Fatalf("cache never reached rev=%d: %v", expectSnapshotRev, err)
	}

	gotSnapshot, err := cache.Get(t.Context(), "foo", clientv3.WithSerializable())
	if err != nil {
		t.Fatalf("Get after resync: %v", err)
	}

Review comment (suggestion):
Can you test Get request instead of inspecting internals? We should expect that Get should timeout waiting for ready.

Code after:
	}
	secondSnapshot := &clientv3.GetResponse{
		Header: &pb.ResponseHeader{Revision: 20},
		Kvs:    []*mvccpb.KeyValue{{Key: []byte("foo"), ModRevision: 20}},
	}
	fakeClient := &clientv3.Client{
		Watcher: newMockWatcher(16),
		KV:      newKVStub(firstSnapshot, secondSnapshot),
	}
	cache, err := New(fakeClient, "")
	if err != nil {
		t.Fatalf("New cache: %v", err)
	}
	defer cache.Close()
	mw := fakeClient.Watcher.(*mockWatcher)

	t.Log("Phase 1: initial getWatch bootstrap")
	mw.triggerCreatedNotify()
	<-mw.registered
	if err = cache.WaitReady(t.Context()); err != nil {
		t.Fatalf("initial WaitReady: %v", err)
	}

	t.Log("Phase 2: simulate compaction")
	mw.errorCompacted(10)

	waitUntil(t, time.Second, 10*time.Millisecond, func() bool { return !cache.Ready() })
	start := time.Now()

	ctxGet, cancelGet := context.WithTimeout(t.Context(), 100*time.Millisecond)
	defer cancelGet()
	_, err = cache.Get(ctxGet, "foo", clientv3.WithSerializable())
	if !errors.Is(err, context.DeadlineExceeded) {
		t.Fatalf("expected Get() to timeout waiting for ready after compaction, got %v", err)
	}

	t.Log("Phase 3: resync after compaction")
	mw.triggerCreatedNotify()
	if err = cache.WaitReady(t.Context()); err != nil {
		t.Fatalf("second WaitReady: %v", err)
	}
	elapsed := time.Since(start)
	if elapsed > time.Second {
		t.Fatalf("cache was unready for %v; want:  < 1 s", elapsed)
	}

	expectSnapshotRev := int64(20)
	expectedWatchStart := secondSnapshot.Header.Revision + 1
	if gotWatchStart := mw.lastStartRev; gotWatchStart != expectedWatchStart {
		t.Errorf("Watch started at rev=%d; want %d", gotWatchStart, expectedWatchStart)
	}