Repository: etcd-io/etcd
File: server/etcdserver/server_test.go (Go)

Code before:
			Params: []any{raftpb.ConfChange{
				Type:   raftpb.ConfChangeRemoveNode,
				NodeID: nodeID,
			}},
		},
		// This action is expected to fail validation, thus NodeID is set to 0
		{
			Name: "ApplyConfChange",
			Params: []any{raftpb.ConfChange{
				Type:    raftpb.ConfChangeUpdateNode,
				Context: memberData,
				NodeID:  0,
			}},
		},
	}

	confState := raftpb.ConfState{}

	t.Log("Applying entries for the first time")
	srv.apply(entries, &confState, nil)
	if got, _ := n.Wait(len(want)); !reflect.DeepEqual(got, want) {
		t.Errorf("actions don't match\n got  %+v\n want %+v", got, want)
	}

	t.Log("Simulating etcd restart by clearing v2 store")
	srv.cluster.SetStore(v2store.New())

	t.Log("Reapplying same entries after restart")
	srv.apply(entries, &confState, nil)
	if got, _ := n.Wait(2 * len(want)); !reflect.DeepEqual(got[len(want):], want) {
		t.Errorf("actions don't match\n got  %+v\n want %+v", got, want)
	}
}

func newServer(t *testing.T, recorder *nodeRecorder) *EtcdServer {
	lg := zaptest.NewLogger(t)
	be, _ := betesting.NewDefaultTmpBackend(t)
	t.Cleanup(func() {
		betesting.Close(t, be)
	})
	srv := &EtcdServer{
		lgMu:         new(sync.RWMutex),
		lg:           zaptest.NewLogger(t),
		r:            *newRaftNode(raftNodeConfig{lg: lg, Node: recorder}),
		cluster:      membership.NewCluster(lg),
		consistIndex: cindex.NewConsistentIndex(be),
	}
	srv.cluster.SetBackend(schema.NewMembershipBackend(lg, be))
	srv.cluster.SetStore(v2store.New())
	srv.beHooks = serverstorage.NewBackendHooks(lg, srv.consistIndex)
	srv.r.transport = newNopTransporter()

Review comment (suggestion):
The previous case for v2store makes sense, because etcd might not generate v2 snapshot at all before restarting.

But It doesn't make sense for v3store. If data in v3store gets lost after restart, it's a disaster for etcd. etcd won't bootstrap successfully in e2e test.

For this case, we just need to define two different `want` for two applies as mentioned in https://github.com/etcd-io/etcd/pull/20139#issuecomment-3723331402

Code after:
			Name: "ApplyConfChange",
			Params: []any{raftpb.ConfChange{
				Type:    raftpb.ConfChangeUpdateNode,
				Context: memberData,
				NodeID:  0,
			}},
		},
	}

	confState := raftpb.ConfState{}

	t.Log("Applying entries for the first time")
	srv.apply(entries, &confState, nil)
	if got, _ := n.Wait(len(want)); !reflect.DeepEqual(got, want) {
		t.Errorf("actions don't match\n got  %+v\n want %+v", got, want)
	}

	t.Log("Simulating etcd restart by clearing v3 store")
	be, _ := betesting.NewDefaultTmpBackend(t)
	t.Cleanup(func() {
		betesting.Close(t, be)
	})
	lg := zaptest.NewLogger(t)
	srv.cluster.SetBackend(schema.NewMembershipBackend(lg, be))
	srv.beHooks = serverstorage.NewBackendHooks(lg, srv.consistIndex)
	srv.consistIndex.SetBackend(be)

	t.Log("Reapplying same entries after restart")
	srv.apply(entries, &confState, nil)
	if got, _ := n.Wait(2 * len(want)); !reflect.DeepEqual(got[len(want):], want) {
		t.Errorf("actions don't match\n got  %+v\n want %+v", got, want)
	}
}

func newServer(t *testing.T, recorder *nodeRecorder) *EtcdServer {
	lg := zaptest.NewLogger(t)
	be, _ := betesting.NewDefaultTmpBackend(t)
	t.Cleanup(func() {
		betesting.Close(t, be)
	})
	srv := &EtcdServer{
		lgMu:         new(sync.RWMutex),
		lg:           zaptest.NewLogger(t),
		r:            *newRaftNode(raftNodeConfig{lg: lg, Node: recorder}),
		cluster:      membership.NewCluster(lg),
		consistIndex: cindex.NewConsistentIndex(be),
	}
	srv.cluster.SetBackend(schema.NewMembershipBackend(lg, be))
	srv.beHooks = serverstorage.NewBackendHooks(lg, srv.consistIndex)
	srv.r.transport = newNopTransporter()
	srv.w = mockwait.NewNop()