diff --git a/pkg/framework/test/apiserver.go b/pkg/framework/test/apiserver.go index 785c5cc14..d6c8ada2f 100644 --- a/pkg/framework/test/apiserver.go +++ b/pkg/framework/test/apiserver.go @@ -5,6 +5,8 @@ import ( "os/exec" "time" + "io" + "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" @@ -15,10 +17,11 @@ type APIServer struct { // The path to the apiserver binary Path string EtcdURL string - session *gexec.Session + ProcessStarter simpleSessionStarter + CertDirManager certDirManager + session SimpleSession stdOut *gbytes.Buffer stdErr *gbytes.Buffer - certDirManager certDirManager } type certDirManager interface { @@ -26,13 +29,29 @@ type certDirManager interface { Destroy() error } +//go:generate counterfeiter . certDirManager + +func NewAPIServer(pathToAPIServer, etcdURL string) *APIServer { + starter := func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) { + return gexec.Start(command, out, err) + } + + apiserver := &APIServer{ + Path: pathToAPIServer, + EtcdURL: etcdURL, + ProcessStarter: starter, + CertDirManager: NewTempDirManager(), + } + + return apiserver +} + // Start starts the apiserver, waits for it to come up, and returns an error, if occoured. func (s *APIServer) Start() error { - s.certDirManager = NewTempDirManager() s.stdOut = gbytes.NewBuffer() s.stdErr = gbytes.NewBuffer() - certDir, err := s.certDirManager.Create() + certDir, err := s.CertDirManager.Create() if err != nil { return err } @@ -55,7 +74,7 @@ func (s *APIServer) Start() error { timedOut := time.After(20 * time.Second) command := exec.Command(s.Path, args...) - s.session, err = gexec.Start(command, s.stdOut, s.stdErr) + s.session, err = s.ProcessStarter(command, s.stdOut, s.stdErr) if err != nil { return err } @@ -71,8 +90,9 @@ func (s *APIServer) Start() error { // Stop stops this process gracefully, waits for its termination, and cleans up the cert directory. func (s *APIServer) Stop() { if s.session != nil { - s.session.Terminate().Wait(20 * time.Second) - err := s.certDirManager.Destroy() + s.session.Terminate() + s.session.Wait(20 * time.Second) + err := s.CertDirManager.Destroy() gomega.Expect(err).NotTo(gomega.HaveOccurred()) } } diff --git a/pkg/framework/test/apiserver_test.go b/pkg/framework/test/apiserver_test.go index 2efcf50e1..090c1b77a 100644 --- a/pkg/framework/test/apiserver_test.go +++ b/pkg/framework/test/apiserver_test.go @@ -1,62 +1,106 @@ package test_test import ( + "io" + "os/exec" + . "k8s.io/kubectl/pkg/framework/test" + "fmt" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" + "k8s.io/kubectl/pkg/framework/test/testfakes" ) var _ = Describe("Apiserver", func() { + var ( + fakeSession *testfakes.FakeSimpleSession + fakeCertDirManager *testfakes.FakeCertDirManager + apiServer *APIServer + ) + + BeforeEach(func() { + fakeSession = &testfakes.FakeSimpleSession{} + fakeCertDirManager = &testfakes.FakeCertDirManager{} + + apiServer = &APIServer{ + Path: "", + EtcdURL: "the etcd url", + CertDirManager: fakeCertDirManager, + } + }) Context("when given a path to a binary that runs for a long time", func() { It("can start and stop that binary", func() { - pathToFakeAPIServer, err := gexec.Build("k8s.io/kubectl/pkg/framework/test/assets/fakeapiserver") - Expect(err).NotTo(HaveOccurred()) - apiServer := &APIServer{ - Path: pathToFakeAPIServer, - EtcdURL: "the etcd url", + sessionBuffer := gbytes.NewBuffer() + fmt.Fprint(sessionBuffer, "Everything is fine") + fakeSession.BufferReturns(sessionBuffer) + + fakeSession.ExitCodeReturnsOnCall(0, -1) + fakeSession.ExitCodeReturnsOnCall(1, 143) + + apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) { + fmt.Fprint(err, "Serving insecurely on 127.0.0.1:8080") + return fakeSession, nil } By("Starting the API Server") - err = apiServer.Start() + err := apiServer.Start() Expect(err).NotTo(HaveOccurred()) Eventually(apiServer).Should(gbytes.Say("Everything is fine")) + Expect(fakeSession.ExitCodeCallCount()).To(Equal(0)) Expect(apiServer).NotTo(gexec.Exit()) + Expect(fakeSession.ExitCodeCallCount()).To(Equal(1)) + Expect(fakeCertDirManager.CreateCallCount()).To(Equal(1)) By("Stopping the API Server") apiServer.Stop() Expect(apiServer).To(gexec.Exit(143)) - }) - - }) - - Context("when no path is given", func() { - It("fails with a helpful error", func() { - apiServer := &APIServer{} - err := apiServer.Start() - Expect(err).To(MatchError(ContainSubstring("no such file or directory"))) + Expect(fakeSession.TerminateCallCount()).To(Equal(1)) + Expect(fakeSession.WaitCallCount()).To(Equal(1)) + Expect(fakeSession.ExitCodeCallCount()).To(Equal(2)) + Expect(fakeCertDirManager.DestroyCallCount()).To(Equal(1)) }) }) - Context("when given a path to a non-executable", func() { - It("fails with a helpful error", func() { - apiServer := &APIServer{ - Path: "./apiserver.go", - EtcdURL: "the etcd url", + Context("when the certificate directory cannot be created", func() { + It("propagates the error, and does not start the process", func() { + fakeCertDirManager.CreateReturnsOnCall(0, "", fmt.Errorf("Error on cert directory creation.")) + + processStarterCounter := 0 + apiServer.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) { + processStarterCounter += 1 + return fakeSession, nil } + err := apiServer.Start() - Expect(err).To(MatchError(ContainSubstring("./apiserver.go: permission denied"))) + Expect(err).To(MatchError(ContainSubstring("Error on cert directory creation."))) + Expect(processStarterCounter).To(Equal(0)) + }) + }) + + Context("when the starter returns an error", func() { + It("propagates the error", func() { + apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) { + return nil, fmt.Errorf("Some error in the apiserver starter.") + } + + err := apiServer.Start() + Expect(err).To(MatchError(ContainSubstring("Some error in the apiserver starter."))) }) }) Context("when we try to stop a server that hasn't been started", func() { - It("does not panic", func() { - server := &APIServer{} - server.Stop() + It("is a noop and does not call exit on the session", func() { + apiServer.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) { + return fakeSession, nil + } + apiServer.Stop() + Expect(fakeSession.ExitCodeCallCount()).To(Equal(0)) }) }) }) diff --git a/pkg/framework/test/assets/fakeapiserver/apiserver.go b/pkg/framework/test/assets/fakeapiserver/apiserver.go deleted file mode 100644 index 47bbf8b7e..000000000 --- a/pkg/framework/test/assets/fakeapiserver/apiserver.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "fmt" - "os" - "regexp" - "time" -) - -func main() { - expectedArgs := []*regexp.Regexp{ - regexp.MustCompile("^--authorization-mode=Node,RBAC$"), - regexp.MustCompile("^--runtime-config=admissionregistration.k8s.io/v1alpha1$"), - regexp.MustCompile("^--v=3$"), - regexp.MustCompile("^--vmodule=$"), - regexp.MustCompile("^--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,SecurityContextDeny,DefaultStorageClass,DefaultTolerationSeconds,GenericAdmissionWebhook,ResourceQuota$"), - regexp.MustCompile("^--admission-control-config-file=$"), - regexp.MustCompile("^--bind-address=0.0.0.0$"), - regexp.MustCompile("^--insecure-bind-address=127.0.0.1$"), - regexp.MustCompile("^--insecure-port=8080$"), - regexp.MustCompile("^--storage-backend=etcd3$"), - regexp.MustCompile("^--etcd-servers=the etcd url$"), - regexp.MustCompile("^--cert-dir=.*"), - } - numExpectedArgs := len(expectedArgs) - numGivenArgs := len(os.Args) - 1 - - if numGivenArgs < numExpectedArgs { - fmt.Printf("Expected at least %d args, only got %d\n", numExpectedArgs, numGivenArgs) - os.Exit(2) - } - - for i, argRegexp := range expectedArgs { - givenArg := os.Args[i+1] - if !argRegexp.MatchString(givenArg) { - fmt.Printf("Expected arg '%s' to match '%s'\n", givenArg, argRegexp.String()) - os.Exit(1) - } - } - fmt.Println("Everything is fine") - fmt.Fprintln(os.Stderr, "Serving insecurely on 127.0.0.1:8080") - - time.Sleep(10 * time.Minute) -} diff --git a/pkg/framework/test/fixtures.go b/pkg/framework/test/fixtures.go index 59182f98a..9ba0487a1 100644 --- a/pkg/framework/test/fixtures.go +++ b/pkg/framework/test/fixtures.go @@ -22,13 +22,9 @@ type FixtureProcess interface { func NewFixtures(pathToEtcd, pathToAPIServer string) *Fixtures { etcdURL := "http://127.0.0.1:2379" - etcd := NewEtcd(pathToEtcd, etcdURL) return &Fixtures{ - Etcd: etcd, - APIServer: &APIServer{ - Path: pathToAPIServer, - EtcdURL: etcdURL, - }, + Etcd: NewEtcd(pathToEtcd, etcdURL), + APIServer: NewAPIServer(pathToAPIServer, etcdURL), } } diff --git a/pkg/framework/test/testfakes/fake_cert_dir_manager.go b/pkg/framework/test/testfakes/fake_cert_dir_manager.go new file mode 100644 index 000000000..5e288112b --- /dev/null +++ b/pkg/framework/test/testfakes/fake_cert_dir_manager.go @@ -0,0 +1,140 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package testfakes + +import ( + "sync" +) + +type FakeCertDirManager struct { + CreateStub func() (string, error) + createMutex sync.RWMutex + createArgsForCall []struct{} + createReturns struct { + result1 string + result2 error + } + createReturnsOnCall map[int]struct { + result1 string + result2 error + } + DestroyStub func() error + destroyMutex sync.RWMutex + destroyArgsForCall []struct{} + destroyReturns struct { + result1 error + } + destroyReturnsOnCall map[int]struct { + result1 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeCertDirManager) Create() (string, error) { + fake.createMutex.Lock() + ret, specificReturn := fake.createReturnsOnCall[len(fake.createArgsForCall)] + fake.createArgsForCall = append(fake.createArgsForCall, struct{}{}) + fake.recordInvocation("Create", []interface{}{}) + fake.createMutex.Unlock() + if fake.CreateStub != nil { + return fake.CreateStub() + } + if specificReturn { + return ret.result1, ret.result2 + } + return fake.createReturns.result1, fake.createReturns.result2 +} + +func (fake *FakeCertDirManager) CreateCallCount() int { + fake.createMutex.RLock() + defer fake.createMutex.RUnlock() + return len(fake.createArgsForCall) +} + +func (fake *FakeCertDirManager) CreateReturns(result1 string, result2 error) { + fake.CreateStub = nil + fake.createReturns = struct { + result1 string + result2 error + }{result1, result2} +} + +func (fake *FakeCertDirManager) CreateReturnsOnCall(i int, result1 string, result2 error) { + fake.CreateStub = nil + if fake.createReturnsOnCall == nil { + fake.createReturnsOnCall = make(map[int]struct { + result1 string + result2 error + }) + } + fake.createReturnsOnCall[i] = struct { + result1 string + result2 error + }{result1, result2} +} + +func (fake *FakeCertDirManager) Destroy() error { + fake.destroyMutex.Lock() + ret, specificReturn := fake.destroyReturnsOnCall[len(fake.destroyArgsForCall)] + fake.destroyArgsForCall = append(fake.destroyArgsForCall, struct{}{}) + fake.recordInvocation("Destroy", []interface{}{}) + fake.destroyMutex.Unlock() + if fake.DestroyStub != nil { + return fake.DestroyStub() + } + if specificReturn { + return ret.result1 + } + return fake.destroyReturns.result1 +} + +func (fake *FakeCertDirManager) DestroyCallCount() int { + fake.destroyMutex.RLock() + defer fake.destroyMutex.RUnlock() + return len(fake.destroyArgsForCall) +} + +func (fake *FakeCertDirManager) DestroyReturns(result1 error) { + fake.DestroyStub = nil + fake.destroyReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeCertDirManager) DestroyReturnsOnCall(i int, result1 error) { + fake.DestroyStub = nil + if fake.destroyReturnsOnCall == nil { + fake.destroyReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.destroyReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeCertDirManager) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.createMutex.RLock() + defer fake.createMutex.RUnlock() + fake.destroyMutex.RLock() + defer fake.destroyMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeCertDirManager) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +}