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

Code before:
	const testKey = "/foo/a"
	putResp, err := client.Put(ctx, testKey, "val")
	if err != nil {
		t.Fatalf("client.Put(%q, \"val\") failed: %v", testKey, err)
	}
	if err := c.WaitForRevision(ctx, putResp.Header.Revision); err != nil {
		t.Fatalf("cache never caught up to rev %d: %v", putResp.Header.Revision, err)
	}

	appliedRev := putResp.Header.Revision
	var latestRev int64
	for i := 0; i < 5; i++ {
		r, err := client.Put(ctx, fmt.Sprintf("/bar/x%d", i), fmt.Sprintf("%d", i))
		if err != nil {
			t.Fatalf("Put(/bar/x%d): %v", i, err)
		}
		latestRev = r.Header.Revision
	}
	if latestRev <= appliedRev {
		t.Fatalf("test setup failed: outside rev (%d) did not advance past applied (%d)", latestRev, appliedRev)
	}
	if err := client.RequestProgress(ctx); err != nil {
		t.Fatalf("RequestProgress: %v", err)
	}

	tests := []struct {
		name        string
		key         string
		opts        []clientv3.OpOption
		expectError bool
	}{
		{
			name:        "single key within prefix",
			key:         "/foo/a",
			opts:        []clientv3.OpOption{clientv3.WithSerializable()},
			expectError: false,
		},
		{
			name:        "single key outside prefix returns error",
			key:         "/bar/a",
			opts:        []clientv3.OpOption{clientv3.WithSerializable()},
			expectError: true,
		},
		{
			name:        "single key at observed progress rev",
			key:         "/foo/a",
			opts:        []clientv3.OpOption{clientv3.WithSerializable(), clientv3.WithRev(latestRev)},
			expectError: false,
		},
		{
			name:        "prefix() within cache prefix",

Review comment (performance):
Please run those tests also against normal etcd client. So we need to split the `TestCacheWithPrefixGet` into tests that validate that both client and cache have consistent behavior when it comes to progress outside the prefix, but also test that shows that cache reject requests for keys outside prefix.

Code after:
		name:         "range [/foo/a, /foo/c)",
		key:          "/foo/a",
		opts:         []clientv3.OpOption{clientv3.WithSerializable(), clientv3.WithRange("/foo/c")},
		wantKVs:      []*mvccpb.KeyValue{Rev8PutFooA.Kv, Rev7TxnPutFooB.Kv},
		wantRevision: 8,
	},
	{
		name:         "range [/foo/a, /foo/d) at rev=5",
		key:          "/foo/a",
		opts:         []clientv3.OpOption{clientv3.WithSerializable(), clientv3.WithRange("/foo/d"), clientv3.WithRev(5)},
		wantKVs:      []*mvccpb.KeyValue{Rev2PutFooA.Kv, Rev3PutFooB.Kv, Rev4PutFooC.Kv},
		wantRevision: 8,
	},
	{
		name:      "range [/foo/a, /foo/c) at rev=9 (future), returns error",
		key:       "/foo/a",
		opts:      []clientv3.OpOption{clientv3.WithSerializable(), clientv3.WithRange("/foo/c"), clientv3.WithRev(9)},
		expectErr: rpctypes.ErrFutureRev,
	},
	{
		name:         "fromKey /foo/b",
		key:          "/foo/b",
		opts:         []clientv3.OpOption{clientv3.WithSerializable(), clientv3.WithFromKey()},
		wantKVs:      []*mvccpb.KeyValue{Rev7TxnPutFooB.Kv, Rev4PutFooC.Kv},
		wantRevision: 8,
	},
	{
		name:         "fromKey /foo/b at rev=7",
		key:          "/foo/b",
		opts:         []clientv3.OpOption{clientv3.WithSerializable(), clientv3.WithFromKey(), clientv3.WithRev(7)},
		wantKVs:      []*mvccpb.KeyValue{Rev7TxnPutFooB.Kv, Rev4PutFooC.Kv},
		wantRevision: 8,
	},
	{
		name:      "fromKey /foo/b at rev=9 (future), returns error",
		key:       "/foo/b",
		opts:      []clientv3.OpOption{clientv3.WithSerializable(), clientv3.WithFromKey(), clientv3.WithRev(9)},
		expectErr: rpctypes.ErrFutureRev,
	},
}

func TestCacheWithPrefixGetInScope(t *testing.T) {
	integration.BeforeTest(t)
	clus := integration.NewCluster(t, &integration.ClusterConfig{Size: 1})
	t.Cleanup(func() { clus.Terminate(t) })
	cli := clus.Client(0)

	testWithPrefixGet(t, cli, func() Getter {
		c, err := cache.New(cli, "/foo")
		if err != nil {
			t.Fatalf("cache.New: %v", err)