Repository: etcd-io/etcd
File: tests/e2e/ctl_v3_member_test.go (Go)

Code before:
					}
				}
				break
			}
		})
		require.NoError(t, aerr)

		t.Logf("[%d] Promoting member (%x)", i, memberID)
		etcdctl := epc.Procs[0].Etcdctl()
		resp, err := etcdctl.MemberPromote(ctx, memberID)
		require.NoError(t, err)

		for _, m := range resp.Members {
			if m.ID == memberID {
				promotedMembers = append(promotedMembers, m)
			}
		}
	}

	t.Log("Ensure all members are voting members from user perspective")
	ensureAllMembersAreVotingMembers(t, epc)

	return epc, promotedMembers
}

func mustSaveMemberIntoBbolt(t *testing.T, dataDir string, protoMember *etcdserverpb.Member) {
	dbPath := datadir.ToBackendFileName(dataDir)
	db, err := bbolt.Open(dbPath, 0666, nil)
	require.NoError(t, err)
	defer func() {
		require.NoError(t, db.Close())
	}()

	m := &membership.Member{
		ID: types.ID(protoMember.ID),
		RaftAttributes: membership.RaftAttributes{
			PeerURLs:  protoMember.PeerURLs,
			IsLearner: protoMember.IsLearner,
		},
		Attributes: membership.Attributes{
			Name:       protoMember.Name,
			ClientURLs: protoMember.ClientURLs,
		},
	}

	err = db.Update(func(tx *bbolt.Tx) error {
		b := tx.Bucket(schema.Members.Name())

		mkey := []byte(m.ID.String())
		mvalue, err := json.Marshal(m)
		require.NoError(t, err)

Review comment (bug):
```diff
+func mustTweakMemberInfoByBbolt(t *testing.T, dataDir string, memberID uint64, tweakFn func(*membership.Member)) {
+       dbPath := datadir.ToBackendFileName(dataDir)
+       db, err := bbolt.Open(dbPath, 0666, nil)
+       require.NoError(t, err)
+       defer func() {
+               require.NoError(t, db.Close())
+       }()
+
+       err = db.Update(func(tx *bbolt.Tx) error {
+               mKey := []byte(types.ID(memberID).String())
+
+               b := tx.Bucket(schema.Members.Name())
+               mValue := b.Get(mKey)
+               if mValue == nil {
+                       return fmt.Errorf("member(%x) not found", memberID)
+               }
+
+               m := &membership.Member{}
+               if err := json.Unmarshal(mValue, m); err != nil {
+                       return fmt.Errorf("failed to unmarshal (%s) into member: %w", string(mValue), err)
+               }
+
+               tweakFn(m)
+
+               mValue, err = json.Marshal(m)
+               if err != nil {
+                       return fmt.Errorf("failed to marshal into json: %w", err)
+               }
+               return b.Put(mKey, mValue)
+       })
+       require.NoError(t, err)
+}
```

I am thinking introduce tweakFn to apply patch instead of passing whole object.


Code after:
					}
				}
				break
			}
		})
		require.NoError(t, aerr)

		t.Logf("[%d] Promoting member (%x)", i, memberID)
		etcdctl := epc.Procs[0].Etcdctl()
		resp, merr := etcdctl.MemberPromote(ctx, memberID)
		require.NoError(t, merr)

		for _, m := range resp.Members {
			if m.ID == memberID {
				promotedMembers = append(promotedMembers, m)
			}
		}
	}

	t.Log("Ensure all members are voting members from user perspective")
	ensureAllMembersAreVotingMembers(t, epc)

	return epc, promotedMembers
}

func mustSaveMemberIntoBbolt(t *testing.T, dataDir string, protoMember *etcdserverpb.Member) {
	dbPath := datadir.ToBackendFileName(dataDir)
	db, err := bbolt.Open(dbPath, 0o666, nil)
	require.NoError(t, err)
	defer func() {
		require.NoError(t, db.Close())
	}()

	m := &membership.Member{
		ID: types.ID(protoMember.ID),
		RaftAttributes: membership.RaftAttributes{
			PeerURLs:  protoMember.PeerURLs,
			IsLearner: protoMember.IsLearner,
		},
		Attributes: membership.Attributes{
			Name:       protoMember.Name,
			ClientURLs: protoMember.ClientURLs,
		},
	}

	err = db.Update(func(tx *bbolt.Tx) error {
		b := tx.Bucket(schema.Members.Name())

		mkey := []byte(m.ID.String())
		mvalue, jerr := json.Marshal(m)
		require.NoError(t, jerr)