From 4638a53893e6049dabbbd04abb9e0e29af5207f3 Mon Sep 17 00:00:00 2001 From: Suhas Karanth Date: Sun, 7 Jun 2020 18:15:40 +0530 Subject: [PATCH] No variadic return types (#126) While extracting the method and parameters from the method signature, only check and apply the variadic flag for the input parameters and not for the return arguments. Issue details: For an interface with variadic arguments and slice return type, moq was generating an invalid mock: type I interface { Func(params ...interface{}) []byte } // ... type IMock struct { // FuncFunc mocks the Func method. FuncFunc func(params ...interface{}) ...byte // calls tracks calls to the methods. calls struct { // Func holds details about calls to the Func method. Func []struct { // Params is the params argument value. Params []interface{} } } } On attempting to generate the mock in such an instance, the command would fail on the formatting step: m.Mock: go/format: 35:30: expected ';', found '...' (and 3 more errors) See https://github.com/matryer/moq/issues/124, https://github.com/matryer/moq/pull/125. --- pkg/moq/moq.go | 44 ++++++----- pkg/moq/moq_test.go | 24 +++++- pkg/moq/testpackages/variadic/echoer.go | 5 ++ .../variadic/testdata/echoer.golden.go | 76 +++++++++++++++++++ 4 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 pkg/moq/testpackages/variadic/echoer.go create mode 100644 pkg/moq/testpackages/variadic/testdata/echoer.golden.go diff --git a/pkg/moq/moq.go b/pkg/moq/moq.go index 30cfc50..2a55991 100644 --- a/pkg/moq/moq.go +++ b/pkg/moq/moq.go @@ -10,6 +10,7 @@ import ( "os" "path" "path/filepath" + "strconv" "strings" "text/template" @@ -133,8 +134,7 @@ func (m *Mocker) Mock(w io.Writer, names ...string) error { Name: meth.Name(), } obj.Methods = append(obj.Methods, method) - method.Params = m.extractArgs(sig, sig.Params(), "in%d") - method.Returns = m.extractArgs(sig, sig.Results(), "out%d") + method.Params, method.Returns = m.extractArgs(sig) } doc.Objects = append(doc.Objects, obj) } @@ -182,26 +182,30 @@ func (m *Mocker) packageQualifier(pkg *types.Package) string { return pkg.Name() } -func (m *Mocker) extractArgs(sig *types.Signature, list *types.Tuple, nameFormat string) []*param { - var params []*param - listLen := list.Len() - for ii := 0; ii < listLen; ii++ { - p := list.At(ii) - name := p.Name() - if name == "" { - name = fmt.Sprintf(nameFormat, ii+1) - } - typename := types.TypeString(p.Type(), m.packageQualifier) +func (m *Mocker) extractArgs(sig *types.Signature) (params, results []*param) { + pp := sig.Params() + for i := 0; i < pp.Len(); i++ { + p := m.buildParam(pp.At(i), "in"+strconv.Itoa(i+1)) // check for final variadic argument - variadic := sig.Variadic() && ii == listLen-1 && typename[0:2] == "[]" - param := ¶m{ - Name: name, - Type: typename, - Variadic: variadic, - } - params = append(params, param) + p.Variadic = sig.Variadic() && i == pp.Len()-1 && p.Type[0:2] == "[]" + params = append(params, p) } - return params + + rr := sig.Results() + for i := 0; i < rr.Len(); i++ { + results = append(results, m.buildParam(rr.At(i), "out"+strconv.Itoa(i+1))) + } + + return +} + +func (m *Mocker) buildParam(v *types.Var, fallbackName string) *param { + name := v.Name() + if name == "" { + name = fallbackName + } + typ := types.TypeString(v.Type(), m.packageQualifier) + return ¶m{Name: name, Type: typ} } func pkgInfoFromPath(srcDir string, mode packages.LoadMode) (*packages.Package, error) { diff --git a/pkg/moq/moq_test.go b/pkg/moq/moq_test.go index 1141419..cf1eed0 100644 --- a/pkg/moq/moq_test.go +++ b/pkg/moq/moq_test.go @@ -191,7 +191,7 @@ func TestNotCreatingEmptyDirWhenPkgIsGiven(t *testing.T) { } } -// TestVeradicArguments tests to ensure variadic work as +// TestVariadicArguments tests to ensure variadic work as // expected. // see https://github.com/matryer/moq/issues/5 func TestVariadicArguments(t *testing.T) { @@ -219,6 +219,26 @@ func TestVariadicArguments(t *testing.T) { } } +// TestSliceResult tests to ensure slice return data type works as +// expected. +// see https://github.com/matryer/moq/issues/124 +func TestSliceResult(t *testing.T) { + m, err := New(Config{SrcDir: "testpackages/variadic"}) + if err != nil { + t.Fatalf("moq.New: %s", err) + } + + var buf bytes.Buffer + if err = m.Mock(&buf, "Echoer"); err != nil { + t.Errorf("m.Mock: %s", err) + } + + golden := filepath.Join("testpackages/variadic/testdata", "echoer.golden.go") + if err := matchGoldenFile(golden, buf.Bytes()); err != nil { + t.Errorf("check golden file: %s", err) + } +} + func TestNothingToReturn(t *testing.T) { m, err := New(Config{SrcDir: "testpackages/example"}) if err != nil { @@ -320,7 +340,7 @@ func TestFormatter(t *testing.T) { func matchGoldenFile(goldenFile string, actual []byte) error { // To update golden files, run the following: - // go test -v -run ^$ github.com/matryer/moq/pkg/moq -update + // go test -v -run '^$' github.com/matryer/moq/pkg/moq -update if *update { if err := ioutil.WriteFile(goldenFile, actual, 0644); err != nil { return fmt.Errorf("write: %s: %s", goldenFile, err) diff --git a/pkg/moq/testpackages/variadic/echoer.go b/pkg/moq/testpackages/variadic/echoer.go new file mode 100644 index 0000000..b830b65 --- /dev/null +++ b/pkg/moq/testpackages/variadic/echoer.go @@ -0,0 +1,5 @@ +package variadic + +type Echoer interface { + Echo(ss ...string) []string +} diff --git a/pkg/moq/testpackages/variadic/testdata/echoer.golden.go b/pkg/moq/testpackages/variadic/testdata/echoer.golden.go new file mode 100644 index 0000000..a127ad5 --- /dev/null +++ b/pkg/moq/testpackages/variadic/testdata/echoer.golden.go @@ -0,0 +1,76 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package variadic + +import ( + "sync" +) + +var ( + lockEchoerMockEcho sync.RWMutex +) + +// Ensure, that EchoerMock does implement Echoer. +// If this is not the case, regenerate this file with moq. +var _ Echoer = &EchoerMock{} + +// EchoerMock is a mock implementation of Echoer. +// +// func TestSomethingThatUsesEchoer(t *testing.T) { +// +// // make and configure a mocked Echoer +// mockedEchoer := &EchoerMock{ +// EchoFunc: func(ss ...string) []string { +// panic("mock out the Echo method") +// }, +// } +// +// // use mockedEchoer in code that requires Echoer +// // and then make assertions. +// +// } +type EchoerMock struct { + // EchoFunc mocks the Echo method. + EchoFunc func(ss ...string) []string + + // calls tracks calls to the methods. + calls struct { + // Echo holds details about calls to the Echo method. + Echo []struct { + // Ss is the ss argument value. + Ss []string + } + } +} + +// Echo calls EchoFunc. +func (mock *EchoerMock) Echo(ss ...string) []string { + if mock.EchoFunc == nil { + panic("EchoerMock.EchoFunc: method is nil but Echoer.Echo was just called") + } + callInfo := struct { + Ss []string + }{ + Ss: ss, + } + lockEchoerMockEcho.Lock() + mock.calls.Echo = append(mock.calls.Echo, callInfo) + lockEchoerMockEcho.Unlock() + return mock.EchoFunc(ss...) +} + +// EchoCalls gets all the calls that were made to Echo. +// Check the length with: +// len(mockedEchoer.EchoCalls()) +func (mock *EchoerMock) EchoCalls() []struct { + Ss []string +} { + var calls []struct { + Ss []string + } + lockEchoerMockEcho.RLock() + calls = mock.calls.Echo + lockEchoerMockEcho.RUnlock() + return calls +}