Skip to content
This repository was archived by the owner on Jun 27, 2023. It is now read-only.

Commit 0d6884b

Browse files
authored
Merge pull request #157 from hypnoglow/fix-mockgen-custom-import-paths
Fix mockgen for package name not matching import path
2 parents 58cd061 + bdab667 commit 0d6884b

File tree

8 files changed

+287
-15
lines changed

8 files changed

+287
-15
lines changed

mockgen/parse.go

+25-15
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,14 @@ func (p *fileParser) parseAuxFiles(auxFiles string) error {
119119
if len(parts) != 2 {
120120
return fmt.Errorf("bad aux file spec: %v", kv)
121121
}
122-
file, err := parser.ParseFile(p.fileSet, parts[1], nil, 0)
122+
pkg, fpath := parts[0], parts[1]
123+
124+
file, err := parser.ParseFile(p.fileSet, fpath, nil, 0)
123125
if err != nil {
124126
return err
125127
}
126128
p.auxFiles = append(p.auxFiles, file)
127-
p.addAuxInterfacesFromFile(parts[0], file)
129+
p.addAuxInterfacesFromFile(pkg, file)
128130
}
129131
return nil
130132
}
@@ -138,6 +140,8 @@ func (p *fileParser) addAuxInterfacesFromFile(pkg string, file *ast.File) {
138140
}
139141
}
140142

143+
// parseFile loads all file imports and auxiliary files import into the
144+
// fileParser, parses all file interfaces and returns package model.
141145
func (p *fileParser) parseFile(file *ast.File) (*model.Package, error) {
142146
allImports := importsOfFile(file)
143147
// Don't stomp imports provided by -imports. Those should take precedence.
@@ -170,6 +174,8 @@ func (p *fileParser) parseFile(file *ast.File) (*model.Package, error) {
170174
}, nil
171175
}
172176

177+
// parsePackage loads package specified by path, parses it and populates
178+
// corresponding imports and importedInterfaces into the fileParser.
173179
func (p *fileParser) parsePackage(path string) error {
174180
var pkgs map[string]*ast.Package
175181
if imp, err := build.Import(path, p.srcDir, build.FindOnly); err != nil {
@@ -417,30 +423,34 @@ func (p *fileParser) parseType(pkg string, typ ast.Expr) (model.Type, error) {
417423
// importsOfFile returns a map of package name to import path
418424
// of the imports in file.
419425
func importsOfFile(file *ast.File) map[string]string {
420-
/* We have to make guesses about some imports, because imports are not required
421-
* to have names. Named imports are always certain. Unnamed imports are guessed
422-
* to have a name of the last path component; if the last path component has dots,
423-
* the first dot-delimited field is used as the name.
424-
*/
425-
426426
m := make(map[string]string)
427427
for _, is := range file.Imports {
428-
var pkg string
428+
var pkgName string
429429
importPath := is.Path.Value[1 : len(is.Path.Value)-1] // remove quotes
430430

431431
if is.Name != nil {
432+
// Named imports are always certain.
432433
if is.Name.Name == "_" {
433434
continue
434435
}
435-
pkg = removeDot(is.Name.Name)
436+
pkgName = removeDot(is.Name.Name)
436437
} else {
437-
_, last := path.Split(importPath)
438-
pkg = strings.SplitN(last, ".", 2)[0]
438+
pkg, err := build.Import(importPath, "", 0)
439+
if err != nil {
440+
// Fallback to import path suffix. Note that this is uncertain.
441+
log.Printf("failed to import package by path %s: %s - fallback to import path suffix", importPath, err.Error())
442+
_, last := path.Split(importPath)
443+
// If the last path component has dots, the first dot-delimited
444+
// field is used as the name.
445+
pkgName = strings.SplitN(last, ".", 2)[0]
446+
}
447+
pkgName = pkg.Name
439448
}
440-
if _, ok := m[pkg]; ok {
441-
log.Fatalf("imported package collision: %q imported twice", pkg)
449+
450+
if _, ok := m[pkgName]; ok {
451+
log.Fatalf("imported package collision: %q imported twice", pkgName)
442452
}
443-
m[pkg] = importPath
453+
m[pkgName] = importPath
444454
}
445455
return m
446456
}

mockgen/parse_test.go

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package main
2+
3+
import (
4+
"go/ast"
5+
"go/parser"
6+
"go/token"
7+
"testing"
8+
)
9+
10+
func TestFileParser_ParseFile(t *testing.T) {
11+
fs := token.NewFileSet()
12+
file, err := parser.ParseFile(fs, "tests/custom_package_name/greeter/greeter.go", nil, 0)
13+
if err != nil {
14+
t.Fatalf("Unexpected error: %v", err)
15+
}
16+
17+
p := fileParser{
18+
fileSet: fs,
19+
imports: make(map[string]string),
20+
importedInterfaces: make(map[string]map[string]*ast.InterfaceType),
21+
}
22+
23+
pkg, err := p.parseFile(file)
24+
if err != nil {
25+
t.Fatalf("Unexpected error: %v", err)
26+
}
27+
28+
checkGreeterImports(t, p.imports)
29+
30+
expectedName := "greeter"
31+
if pkg.Name != expectedName {
32+
t.Fatalf("Expected name to be %v but got %v", expectedName, pkg.Name)
33+
}
34+
35+
expectedInterfaceName := "InputMaker"
36+
if pkg.Interfaces[0].Name != expectedInterfaceName {
37+
t.Fatalf("Expected interface name to be %v but got %v", expectedInterfaceName, pkg.Interfaces[0].Name)
38+
}
39+
}
40+
41+
func TestFileParser_ParsePackage(t *testing.T) {
42+
fs := token.NewFileSet()
43+
_, err := parser.ParseFile(fs, "tests/custom_package_name/greeter/greeter.go", nil, 0)
44+
if err != nil {
45+
t.Fatalf("Unexpected error: %v", err)
46+
}
47+
48+
p := fileParser{
49+
fileSet: fs,
50+
imports: make(map[string]string),
51+
importedInterfaces: make(map[string]map[string]*ast.InterfaceType),
52+
}
53+
54+
err = p.parsePackage("github.com/golang/mock/mockgen/tests/custom_package_name/greeter")
55+
if err != nil {
56+
t.Fatalf("Unexpected error: %v", err)
57+
}
58+
59+
checkGreeterImports(t, p.imports)
60+
}
61+
62+
func TestImportsOfFile(t *testing.T) {
63+
fs := token.NewFileSet()
64+
file, err := parser.ParseFile(fs, "tests/custom_package_name/greeter/greeter.go", nil, 0)
65+
if err != nil {
66+
t.Fatalf("Unexpected error: %v", err)
67+
}
68+
69+
imports := importsOfFile(file)
70+
checkGreeterImports(t, imports)
71+
}
72+
73+
func checkGreeterImports(t *testing.T, imports map[string]string) {
74+
// check that imports have stdlib package "fmt"
75+
if fmtPackage, ok := imports["fmt"]; !ok {
76+
t.Errorf("Expected imports to have key \"fmt\"")
77+
} else {
78+
expectedFmtPackage := "fmt"
79+
if fmtPackage != expectedFmtPackage {
80+
t.Errorf("Expected fmt key to have value %s but got %s", expectedFmtPackage, fmtPackage)
81+
}
82+
}
83+
84+
// check that imports have package named "validator"
85+
if validatorPackage, ok := imports["validator"]; !ok {
86+
t.Errorf("Expected imports to have key \"fmt\"")
87+
} else {
88+
expectedValidatorPackage := "github.com/golang/mock/mockgen/tests/custom_package_name/validator"
89+
if validatorPackage != expectedValidatorPackage {
90+
t.Errorf("Expected validator key to have value %s but got %s", expectedValidatorPackage, validatorPackage)
91+
}
92+
}
93+
94+
// check that imports have package named "client"
95+
if clientPackage, ok := imports["client"]; !ok {
96+
t.Errorf("Expected imports to have key \"client\"")
97+
} else {
98+
expectedClientPackage := "github.com/golang/mock/mockgen/tests/custom_package_name/client/v1"
99+
if clientPackage != expectedClientPackage {
100+
t.Errorf("Expected client key to have value %s but got %s", expectedClientPackage, clientPackage)
101+
}
102+
}
103+
104+
// check that imports don't have package named "v1"
105+
if _, ok := imports["v1"]; ok {
106+
t.Errorf("Expected import not to have key \"v1\"")
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Tests for custom package names
2+
3+
This directory contains test for mockgen generating mocks when imported package
4+
name does not match import path suffix. For example, package with name "client"
5+
is located under import path "github.com/golang/mock/mockgen/tests/custom_package_name/client/v1".
6+
7+
Prior to this patch:
8+
9+
$ go generate greeter/greeter.go
10+
2018/03/05 22:44:52 Loading input failed: greeter.go:17:11: failed parsing returns: greeter.go:17:14: unknown package "client"
11+
greeter/greeter.go:1: running "mockgen": exit status 1
12+
13+
This can be fixed by manually providing `-imports` flag, like `-imports client=github.com/golang/mock/mockgen/tests/custom_package_name/client/v1`.
14+
But, mockgen should be able to automatically resolve package names in such situations.
15+
16+
With this patch applied:
17+
18+
$ go generate greeter/greeter.go
19+
$ echo $?
20+
0
21+
22+
Mockgen runs successfully, produced output is equal to [greeter_mock_test.go](greeter/greeter_mock_test.go) content.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package client
2+
3+
import "fmt"
4+
5+
type Client struct{}
6+
7+
func (c *Client) Greet(in GreetInput) string {
8+
return fmt.Sprintf("Hello, %s!", in.Name)
9+
}
10+
11+
type GreetInput struct {
12+
Name string
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//go:generate mockgen -source greeter.go -destination greeter_mock_test.go -package greeter
2+
3+
package greeter
4+
5+
import (
6+
// stdlib import
7+
"fmt"
8+
9+
// non-matching import suffix and package name
10+
"github.com/golang/mock/mockgen/tests/custom_package_name/client/v1"
11+
12+
// matching import suffix and package name
13+
"github.com/golang/mock/mockgen/tests/custom_package_name/validator"
14+
)
15+
16+
type InputMaker interface {
17+
MakeInput() client.GreetInput
18+
}
19+
20+
type Greeter struct {
21+
InputMaker InputMaker
22+
Client *client.Client
23+
}
24+
25+
func (g *Greeter) Greet() (string, error) {
26+
in := g.InputMaker.MakeInput()
27+
if err := validator.Validate(in.Name); err != nil {
28+
return "", fmt.Errorf("validation failed: %v", err)
29+
}
30+
return g.Client.Greet(in), nil
31+
}

mockgen/tests/custom_package_name/greeter/greeter_mock_test.go

+46
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package greeter
2+
3+
import (
4+
"testing"
5+
6+
"github.com/golang/mock/gomock"
7+
"github.com/golang/mock/mockgen/tests/custom_package_name/client/v1"
8+
)
9+
10+
func TestGreeter_Greet(t *testing.T) {
11+
ctrl := gomock.NewController(t)
12+
defer ctrl.Finish()
13+
14+
input := client.GreetInput{
15+
Name: "Foo",
16+
}
17+
18+
inputMaker := NewMockInputMaker(ctrl)
19+
inputMaker.EXPECT().
20+
MakeInput().
21+
Return(input)
22+
23+
g := &Greeter{
24+
InputMaker: inputMaker,
25+
Client: &client.Client{},
26+
}
27+
28+
greeting, err := g.Greet()
29+
if err != nil {
30+
t.Fatalf("Unexpected error: %v", err)
31+
}
32+
33+
expected := "Hello, Foo!"
34+
if greeting != expected {
35+
t.Fatalf("Expected greeting to be %v but got %v", expected, greeting)
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package validator
2+
3+
func Validate(s string) error {
4+
return nil
5+
}

0 commit comments

Comments
 (0)