Merge pull request #154 from hoegaarden/proper_fake_for_etcd

Proper fake for etcd
This commit is contained in:
Antoine Pelisse 2017-12-05 13:23:47 -08:00 committed by GitHub
commit d56d6ba9a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 506 additions and 77 deletions

View File

@ -1,39 +0,0 @@
package main
import (
"fmt"
"os"
"regexp"
"time"
)
func main() {
expectedArgs := []*regexp.Regexp{
regexp.MustCompile("^--debug$"),
regexp.MustCompile("^--advertise-client-urls$"),
regexp.MustCompile("^our etcd url$"),
regexp.MustCompile("^--listen-client-urls$"),
regexp.MustCompile("^our etcd url$"),
regexp.MustCompile("^--data-dir$"),
regexp.MustCompile("^.+"),
}
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 dandy")
fmt.Fprintln(os.Stderr, "serving insecure client requests on 127.0.0.1:2379")
time.Sleep(10 * time.Minute)
}

View File

@ -16,7 +16,7 @@ import (
func TestIntegration(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Integration Suite")
RunSpecs(t, "DemoCLI Integration Suite")
}
var (

View File

@ -9,7 +9,7 @@ import (
"github.com/onsi/gomega/gexec"
)
var _ = Describe("Integration", func() {
var _ = Describe("DemoCLI Integration", func() {
It("can give us a helpful help message", func() {
helpfulMessage := `This is a demo kubernetes CLI, which interacts with the kubernetes API.`

View File

@ -2,6 +2,7 @@ package test
import (
"fmt"
"io"
"os/exec"
"time"
@ -12,13 +13,13 @@ import (
// Etcd knows how to run an etcd server. Set it up with the path to a precompiled binary.
type Etcd struct {
// The path to the etcd binary
Path string
EtcdURL string
session *gexec.Session
ProcessStarter simpleSessionStarter
DataDirManager dataDirManager
session SimpleSession
stdOut *gbytes.Buffer
stdErr *gbytes.Buffer
dataDirManager dataDirManager
}
type dataDirManager interface {
@ -26,13 +27,42 @@ type dataDirManager interface {
Destroy() error
}
//go:generate counterfeiter . dataDirManager
// SimpleSession describes a CLI session. You can get output, and you can kill it. It is implemented by *gexec.Session.
type SimpleSession interface {
Buffer() *gbytes.Buffer
ExitCode() int
Wait(timeout ...interface{}) *gexec.Session
Terminate() *gexec.Session
}
//go:generate counterfeiter . SimpleSession
type simpleSessionStarter func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error)
// NewEtcd constructs an Etcd Fixture Process
func NewEtcd(pathToEtcd string, etcdURL string) *Etcd {
starter := func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
return gexec.Start(command, out, err)
}
etcd := &Etcd{
Path: pathToEtcd,
EtcdURL: etcdURL,
ProcessStarter: starter,
DataDirManager: NewTempDirManager(),
}
return etcd
}
// Start starts the etcd, waits for it to come up, and returns an error, if occoured.
func (e *Etcd) Start() error {
e.dataDirManager = NewTempDirManager()
e.stdOut = gbytes.NewBuffer()
e.stdErr = gbytes.NewBuffer()
dataDir, err := e.dataDirManager.Create()
dataDir, err := e.DataDirManager.Create()
if err != nil {
return err
}
@ -51,7 +81,7 @@ func (e *Etcd) Start() error {
timedOut := time.After(20 * time.Second)
command := exec.Command(e.Path, args...)
e.session, err = gexec.Start(command, e.stdOut, e.stdErr)
e.session, err = e.ProcessStarter(command, e.stdOut, e.stdErr)
if err != nil {
return err
}
@ -67,8 +97,9 @@ func (e *Etcd) Start() error {
// Stop stops this process gracefully, waits for its termination, and cleans up the data directory.
func (e *Etcd) Stop() {
if e.session != nil {
e.session.Terminate().Wait(20 * time.Second)
err := e.dataDirManager.Destroy()
e.session.Terminate()
e.session.Wait(20 * time.Second)
err := e.DataDirManager.Destroy()
gomega.Expect(err).NotTo(gomega.HaveOccurred())
}
}

View File

@ -1,61 +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("Etcd", func() {
var (
fakeSession *testfakes.FakeSimpleSession
fakeDataDirManager *testfakes.FakeDataDirManager
etcd *Etcd
)
BeforeEach(func() {
fakeSession = &testfakes.FakeSimpleSession{}
fakeDataDirManager = &testfakes.FakeDataDirManager{}
etcd = &Etcd{
Path: "",
EtcdURL: "our etcd url",
DataDirManager: fakeDataDirManager,
}
})
Context("when given a path to a binary that runs for a long time", func() {
It("can start and stop that binary", func() {
pathToFakeEtcd, err := gexec.Build("k8s.io/kubectl/pkg/framework/test/assets/fakeetcd")
Expect(err).NotTo(HaveOccurred())
etcd := &Etcd{
Path: pathToFakeEtcd,
EtcdURL: "our etcd url",
sessionBuffer := gbytes.NewBuffer()
fmt.Fprintf(sessionBuffer, "Everything is dandy")
fakeSession.BufferReturns(sessionBuffer)
fakeSession.ExitCodeReturnsOnCall(0, -1)
fakeSession.ExitCodeReturnsOnCall(1, 143)
etcd.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
fmt.Fprint(err, "serving insecure client requests on 127.0.0.1:2379")
return fakeSession, nil
}
By("Starting the Etcd Server")
err = etcd.Start()
err := etcd.Start()
Expect(err).NotTo(HaveOccurred())
Eventually(etcd).Should(gbytes.Say("Everything is dandy"))
Expect(fakeSession.ExitCodeCallCount()).To(Equal(0))
Expect(etcd).NotTo(gexec.Exit())
Expect(fakeSession.ExitCodeCallCount()).To(Equal(1))
Expect(fakeDataDirManager.CreateCallCount()).To(Equal(1))
By("Stopping the Etcd Server")
etcd.Stop()
Expect(etcd).To(gexec.Exit(143))
})
})
Context("when no path is given", func() {
It("fails with a helpful error", func() {
etcd := &Etcd{}
err := etcd.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(fakeDataDirManager.DestroyCallCount()).To(Equal(1))
})
})
Context("when given a path to a non-executable", func() {
It("fails with a helpful error", func() {
etcd := &Etcd{
Path: "./etcd.go",
Context("when the data directory cannot be created", func() {
It("propagates the error, and does not start the process", func() {
fakeDataDirManager.CreateReturnsOnCall(0, "", fmt.Errorf("Error on directory creation."))
processStarterCounter := 0
etcd.ProcessStarter = func(Command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
processStarterCounter += 1
return fakeSession, nil
}
err := etcd.Start()
Expect(err).To(MatchError(ContainSubstring("./etcd.go: permission denied")))
Expect(err).To(MatchError(ContainSubstring("Error on directory creation.")))
Expect(processStarterCounter).To(Equal(0))
})
})
Context("when the starter returns an error", func() {
It("propagates the error", func() {
etcd.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
return nil, fmt.Errorf("Some error in the starter.")
}
err := etcd.Start()
Expect(err).To(MatchError(ContainSubstring("Some error in the starter.")))
})
})
Context("when we try to stop a server that hasn't been started", func() {
It("does not panic", func() {
etcd := &Etcd{}
It("is a noop and does not call exit on the session", func() {
etcd.ProcessStarter = func(command *exec.Cmd, out, err io.Writer) (SimpleSession, error) {
return fakeSession, nil
}
etcd.Stop()
Expect(fakeSession.ExitCodeCallCount()).To(Equal(0))
})
})
})

View File

@ -21,11 +21,10 @@ type FixtureProcess interface {
// NewFixtures will give you a Fixtures struct that's properly wired together.
func NewFixtures(pathToEtcd, pathToAPIServer string) *Fixtures {
etcdURL := "http://127.0.0.1:2379"
etcd := NewEtcd(pathToEtcd, etcdURL)
return &Fixtures{
Etcd: &Etcd{
Path: pathToEtcd,
EtcdURL: etcdURL,
},
Etcd: etcd,
APIServer: &APIServer{
Path: pathToAPIServer,
EtcdURL: etcdURL,

View File

@ -14,7 +14,7 @@ import (
func TestIntegration(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Integration Suite")
RunSpecs(t, "Framework Integration Suite")
}
var (

View File

@ -10,7 +10,7 @@ import (
"k8s.io/kubectl/pkg/framework/test"
)
var _ = Describe("Integration", func() {
var _ = Describe("The Testing Framework", func() {
It("Successfully manages the fixtures lifecycle", func() {
fixtures := test.NewFixtures(defaultPathToEtcd, defaultPathToApiserver)

View File

@ -0,0 +1,140 @@
// Code generated by counterfeiter. DO NOT EDIT.
package testfakes
import (
"sync"
)
type FakeDataDirManager 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 *FakeDataDirManager) 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 *FakeDataDirManager) CreateCallCount() int {
fake.createMutex.RLock()
defer fake.createMutex.RUnlock()
return len(fake.createArgsForCall)
}
func (fake *FakeDataDirManager) CreateReturns(result1 string, result2 error) {
fake.CreateStub = nil
fake.createReturns = struct {
result1 string
result2 error
}{result1, result2}
}
func (fake *FakeDataDirManager) 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 *FakeDataDirManager) 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 *FakeDataDirManager) DestroyCallCount() int {
fake.destroyMutex.RLock()
defer fake.destroyMutex.RUnlock()
return len(fake.destroyArgsForCall)
}
func (fake *FakeDataDirManager) DestroyReturns(result1 error) {
fake.DestroyStub = nil
fake.destroyReturns = struct {
result1 error
}{result1}
}
func (fake *FakeDataDirManager) 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 *FakeDataDirManager) 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 *FakeDataDirManager) 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)
}

View File

@ -0,0 +1,253 @@
// Code generated by counterfeiter. DO NOT EDIT.
package testfakes
import (
"sync"
"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec"
"k8s.io/kubectl/pkg/framework/test"
)
type FakeSimpleSession struct {
BufferStub func() *gbytes.Buffer
bufferMutex sync.RWMutex
bufferArgsForCall []struct{}
bufferReturns struct {
result1 *gbytes.Buffer
}
bufferReturnsOnCall map[int]struct {
result1 *gbytes.Buffer
}
ExitCodeStub func() int
exitCodeMutex sync.RWMutex
exitCodeArgsForCall []struct{}
exitCodeReturns struct {
result1 int
}
exitCodeReturnsOnCall map[int]struct {
result1 int
}
WaitStub func(timeout ...interface{}) *gexec.Session
waitMutex sync.RWMutex
waitArgsForCall []struct {
timeout []interface{}
}
waitReturns struct {
result1 *gexec.Session
}
waitReturnsOnCall map[int]struct {
result1 *gexec.Session
}
TerminateStub func() *gexec.Session
terminateMutex sync.RWMutex
terminateArgsForCall []struct{}
terminateReturns struct {
result1 *gexec.Session
}
terminateReturnsOnCall map[int]struct {
result1 *gexec.Session
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeSimpleSession) Buffer() *gbytes.Buffer {
fake.bufferMutex.Lock()
ret, specificReturn := fake.bufferReturnsOnCall[len(fake.bufferArgsForCall)]
fake.bufferArgsForCall = append(fake.bufferArgsForCall, struct{}{})
fake.recordInvocation("Buffer", []interface{}{})
fake.bufferMutex.Unlock()
if fake.BufferStub != nil {
return fake.BufferStub()
}
if specificReturn {
return ret.result1
}
return fake.bufferReturns.result1
}
func (fake *FakeSimpleSession) BufferCallCount() int {
fake.bufferMutex.RLock()
defer fake.bufferMutex.RUnlock()
return len(fake.bufferArgsForCall)
}
func (fake *FakeSimpleSession) BufferReturns(result1 *gbytes.Buffer) {
fake.BufferStub = nil
fake.bufferReturns = struct {
result1 *gbytes.Buffer
}{result1}
}
func (fake *FakeSimpleSession) BufferReturnsOnCall(i int, result1 *gbytes.Buffer) {
fake.BufferStub = nil
if fake.bufferReturnsOnCall == nil {
fake.bufferReturnsOnCall = make(map[int]struct {
result1 *gbytes.Buffer
})
}
fake.bufferReturnsOnCall[i] = struct {
result1 *gbytes.Buffer
}{result1}
}
func (fake *FakeSimpleSession) ExitCode() int {
fake.exitCodeMutex.Lock()
ret, specificReturn := fake.exitCodeReturnsOnCall[len(fake.exitCodeArgsForCall)]
fake.exitCodeArgsForCall = append(fake.exitCodeArgsForCall, struct{}{})
fake.recordInvocation("ExitCode", []interface{}{})
fake.exitCodeMutex.Unlock()
if fake.ExitCodeStub != nil {
return fake.ExitCodeStub()
}
if specificReturn {
return ret.result1
}
return fake.exitCodeReturns.result1
}
func (fake *FakeSimpleSession) ExitCodeCallCount() int {
fake.exitCodeMutex.RLock()
defer fake.exitCodeMutex.RUnlock()
return len(fake.exitCodeArgsForCall)
}
func (fake *FakeSimpleSession) ExitCodeReturns(result1 int) {
fake.ExitCodeStub = nil
fake.exitCodeReturns = struct {
result1 int
}{result1}
}
func (fake *FakeSimpleSession) ExitCodeReturnsOnCall(i int, result1 int) {
fake.ExitCodeStub = nil
if fake.exitCodeReturnsOnCall == nil {
fake.exitCodeReturnsOnCall = make(map[int]struct {
result1 int
})
}
fake.exitCodeReturnsOnCall[i] = struct {
result1 int
}{result1}
}
func (fake *FakeSimpleSession) Wait(timeout ...interface{}) *gexec.Session {
fake.waitMutex.Lock()
ret, specificReturn := fake.waitReturnsOnCall[len(fake.waitArgsForCall)]
fake.waitArgsForCall = append(fake.waitArgsForCall, struct {
timeout []interface{}
}{timeout})
fake.recordInvocation("Wait", []interface{}{timeout})
fake.waitMutex.Unlock()
if fake.WaitStub != nil {
return fake.WaitStub(timeout...)
}
if specificReturn {
return ret.result1
}
return fake.waitReturns.result1
}
func (fake *FakeSimpleSession) WaitCallCount() int {
fake.waitMutex.RLock()
defer fake.waitMutex.RUnlock()
return len(fake.waitArgsForCall)
}
func (fake *FakeSimpleSession) WaitArgsForCall(i int) []interface{} {
fake.waitMutex.RLock()
defer fake.waitMutex.RUnlock()
return fake.waitArgsForCall[i].timeout
}
func (fake *FakeSimpleSession) WaitReturns(result1 *gexec.Session) {
fake.WaitStub = nil
fake.waitReturns = struct {
result1 *gexec.Session
}{result1}
}
func (fake *FakeSimpleSession) WaitReturnsOnCall(i int, result1 *gexec.Session) {
fake.WaitStub = nil
if fake.waitReturnsOnCall == nil {
fake.waitReturnsOnCall = make(map[int]struct {
result1 *gexec.Session
})
}
fake.waitReturnsOnCall[i] = struct {
result1 *gexec.Session
}{result1}
}
func (fake *FakeSimpleSession) Terminate() *gexec.Session {
fake.terminateMutex.Lock()
ret, specificReturn := fake.terminateReturnsOnCall[len(fake.terminateArgsForCall)]
fake.terminateArgsForCall = append(fake.terminateArgsForCall, struct{}{})
fake.recordInvocation("Terminate", []interface{}{})
fake.terminateMutex.Unlock()
if fake.TerminateStub != nil {
return fake.TerminateStub()
}
if specificReturn {
return ret.result1
}
return fake.terminateReturns.result1
}
func (fake *FakeSimpleSession) TerminateCallCount() int {
fake.terminateMutex.RLock()
defer fake.terminateMutex.RUnlock()
return len(fake.terminateArgsForCall)
}
func (fake *FakeSimpleSession) TerminateReturns(result1 *gexec.Session) {
fake.TerminateStub = nil
fake.terminateReturns = struct {
result1 *gexec.Session
}{result1}
}
func (fake *FakeSimpleSession) TerminateReturnsOnCall(i int, result1 *gexec.Session) {
fake.TerminateStub = nil
if fake.terminateReturnsOnCall == nil {
fake.terminateReturnsOnCall = make(map[int]struct {
result1 *gexec.Session
})
}
fake.terminateReturnsOnCall[i] = struct {
result1 *gexec.Session
}{result1}
}
func (fake *FakeSimpleSession) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.bufferMutex.RLock()
defer fake.bufferMutex.RUnlock()
fake.exitCodeMutex.RLock()
defer fake.exitCodeMutex.RUnlock()
fake.waitMutex.RLock()
defer fake.waitMutex.RUnlock()
fake.terminateMutex.RLock()
defer fake.terminateMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeSimpleSession) 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)
}
var _ test.SimpleSession = new(FakeSimpleSession)