package embed import ( "strings" "testing" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.etcd.io/etcd/server/v3/embed" ) func TestNewMember(t *testing.T) { testCases := map[string]struct { input *Config expectError bool }{ "Success": { input: &Config{ Name: "test-new-member-name", }, expectError: false, }, "Failure_NoName": { input: &Config{}, expectError: true, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { _, err := NewMember(tc.input) if tc.expectError { assert.Error(t, err) return } assert.NoError(t, err) }) } } func TestInitialize(t *testing.T) { memberName := "test-initialize-name" logLevel := "warn" dataDir := strings.Join([]string{"default.etcd", memberName}, ".") cfg := &Config{ Name: memberName, LogLevel: logLevel, } m, err := NewMember(cfg) require.NoError(t, err) m.initialize() assert.Equal(t, memberName, m.config.Name) assert.Equal(t, logLevel, m.config.embed.LogLevel) assert.Equal(t, dataDir, m.config.embed.Dir) } func TestPrepareNew(t *testing.T) { memberName := "test-prepare-new-name" dataDir := strings.Join([]string{"default.etcd", memberName}, ".") cfg := &Config{ Name: memberName, } m, err := NewMember(cfg) require.NoError(t, err) m.initialize() fs := afero.NewOsFs() require.NoError(t, fs.MkdirAll(dataDir, 0600)) _, err = afero.ReadDir(fs, dataDir) require.NoError(t, err) require.NoError(t, m.prepareNew()) _, err = afero.ReadDir(fs, dataDir) assert.Error(t, err) assert.NotNil(t, m.config.embed.ListenPeerUrls[0].Host) assert.NotNil(t, m.config.embed.ListenClientUrls[0].Host) assert.NotNil(t, m.config.embed.AdvertisePeerUrls[0].Host) assert.NotNil(t, m.config.embed.AdvertiseClientUrls[0].Host) } func TestPreStartStateFlow(t *testing.T) { testCases := map[string]struct { fn func(m *Member) error expectedEndState string }{ "Prepare_PreStart": { fn: func(m *Member) error { return m.prepare() }, expectedEndState: preStart, }, "Start_PreStart": { fn: func(m *Member) error { var errCh chan error return m.start(errCh) }, expectedEndState: running, }, "Stop_PreStart": { fn: func(m *Member) error { return m.stop() }, expectedEndState: preStart, }, "Close_PreStart": { fn: func(m *Member) error { return m.close() }, expectedEndState: preStart, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { cfg := &Config{ Name: strings.Join([]string{"test-prepare", name}, "-"), } m, err := NewMember(cfg) require.NoError(t, err) require.NoError(t, m.prepare()) m.config.embed.InitialCluster = strings.Join([]string{m.config.Name, m.config.embed.AdvertisePeerUrls[0].String()}, "=") m.etcd = &embed.Etcd{} m.state = &preStartState{} require.NoError(t, tc.fn(m)) assert.Equal(t, tc.expectedEndState, m.state.String()) }) } } func TestRunningStateFlow(t *testing.T) { testCases := map[string]struct { fn func(m *Member) error expectedEndState string }{ "Prepare_Running": { fn: func(m *Member) error { return m.prepare() }, expectedEndState: running, }, "Start_Running": { fn: func(m *Member) error { var errCh chan error return m.start(errCh) }, expectedEndState: running, }, "Stop_Running": { fn: func(m *Member) error { return m.stop() }, expectedEndState: stopped, }, "Close_Running": { fn: func(m *Member) error { return m.close() }, expectedEndState: terminated, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { cfg := &Config{ Name: strings.Join([]string{"test-prepare", name}, "-"), } m, err := NewMember(cfg) require.NoError(t, err) require.NoError(t, m.prepare()) m.config.embed.InitialCluster = strings.Join([]string{m.config.Name, m.config.embed.AdvertisePeerUrls[0].String()}, "=") m.etcd = &embed.Etcd{} var errCh chan error require.NoError(t, m.start(errCh)) m.state = &runningState{} require.NoError(t, tc.fn(m)) assert.Equal(t, tc.expectedEndState, m.state.String()) }) } } func TestStoppedStateFlow(t *testing.T) { //nolint:dupl testCases := map[string]struct { fn func(m *Member) error expectedEndState string }{ "Prepare_Stopped": { fn: func(m *Member) error { return m.prepare() }, expectedEndState: stopped, }, "Start_Stopped": { fn: func(m *Member) error { var errCh chan error return m.start(errCh) }, expectedEndState: running, }, "Stop_Stopped": { fn: func(m *Member) error { return m.stop() }, expectedEndState: stopped, }, "Close_Stopped": { fn: func(m *Member) error { return m.close() }, expectedEndState: terminated, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { m := createMember(t, name) var errCh chan error require.NoError(t, m.start(errCh)) require.NoError(t, m.stop()) m.state = &stoppedState{} require.NoError(t, tc.fn(m)) assert.Equal(t, tc.expectedEndState, m.state.String()) }) } } func TestTerminatedStateFlow(t *testing.T) { //nolint:dupl testCases := map[string]struct { fn func(m *Member) error expectedEndState string }{ "Prepare_Terminated": { fn: func(m *Member) error { return m.prepare() }, expectedEndState: terminated, }, "Start_Terminated": { fn: func(m *Member) error { var errCh chan error return m.start(errCh) }, expectedEndState: running, }, "Stop_Terminated": { fn: func(m *Member) error { return m.stop() }, expectedEndState: terminated, }, "Close_Terminated": { fn: func(m *Member) error { return m.close() }, expectedEndState: terminated, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { m := createMember(t, name) var errCh chan error require.NoError(t, m.start(errCh)) require.NoError(t, m.close()) m.state = &terminatedState{} require.NoError(t, tc.fn(m)) assert.Equal(t, tc.expectedEndState, m.state.String()) }) } } func createMember(t *testing.T, name string) *Member { cfg := &Config{ Name: strings.Join([]string{"test-prepare", name}, "-"), } m, err := NewMember(cfg) require.NoError(t, err) require.NoError(t, m.prepare()) m.config.embed.InitialCluster = strings.Join([]string{m.config.Name, m.config.embed.AdvertisePeerUrls[0].String()}, "=") m.etcd = &embed.Etcd{} return m }