Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bazel/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module(name = "bazel_gomock")

bazel_dep(name = "bazel_skylib", version = "1.8.2")
bazel_dep(name = "rules_go", version = "0.57.0", repo_name = "io_bazel_rules_go")
bazel_dep(name = "rules_go", version = "0.59.0", repo_name = "io_bazel_rules_go")
bazel_dep(name = "gazelle", version = "0.45.0")

go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
Expand Down
9 changes: 9 additions & 0 deletions mockgen/internal/tests/no_interfaces/input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package no_interfaces

type MyStruct struct {
Name string
}

func MyFunction() string {
return "hello"
}
26 changes: 19 additions & 7 deletions mockgen/mockgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,17 @@ func main() {
case *source != "": // source mode
pkg, err = sourceMode(*source)
case *archive != "": // archive mode
checkArgs()
Comment thread
lverma14 marked this conversation as resolved.
checkArgsArchive()
packageName = flag.Arg(0)
Comment thread
lverma14 marked this conversation as resolved.
interfaces := strings.Split(flag.Arg(1), ",")
var interfaces []string
Comment thread
lverma14 marked this conversation as resolved.
if flag.NArg() > 1 {
interfaces = strings.Split(flag.Arg(1), ",")
}
// If no interfaces specified, parseExportFile will discover all interfaces
pkg, err = parseExportFile(packageName, interfaces, *archive)

default: // package mode
checkArgs()
checkArgsPackage()
packageName = flag.Arg(0)
interfaces := strings.Split(flag.Arg(1), ",")

Expand Down Expand Up @@ -241,13 +245,20 @@ func parseExcludeInterfaces(names string) map[string]struct{} {
return namesSet
}

func checkArgs() {
func checkArgsPackage() {
if flag.NArg() != 2 {
usage()
log.Fatal("Expected exactly two arguments")
}
}

func checkArgsArchive() {
if flag.NArg() < 1 {
usage()
log.Fatal("Expected at least one argument")
}
}

func usage() {
_, _ = io.WriteString(os.Stderr, usageText)
flag.PrintDefaults()
Expand All @@ -270,11 +281,12 @@ Example:
mockgen . SomeInterface

Archive mode generates mock interfaces from a package archive
file (.a). It is enabled by using the -archive flag and two
non-flag arguments: an import path, and a comma-separated
list of symbols.
file (.a). It is enabled by using the -archive flag with an import path
and an optional comma-separated list of symbols. If no symbols are
specified, mocks will be generated for all discovered interfaces.
Example:
mockgen -archive=pkg.a database/sql/driver Conn,Driver
mockgen -archive=pkg.a database/sql/driver

`

Expand Down
46 changes: 45 additions & 1 deletion mockgen/package_mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,14 @@ func (p *packageModeParser) loadPackage(packageName string) (*packages.Package,
}

func extractInterfacesFromPackageTypes(pkgTypes *types.Package, ifaces []string) ([]*model.Interface, error) {
// If no interfaces specified, discover all interfaces in the package
if len(ifaces) == 0 {
return getAllInterfacesFromPackageTypes(pkgTypes)
}
scope := pkgTypes.Scope()
interfaces := make([]*model.Interface, len(ifaces))
for i, iface := range ifaces {
obj := pkgTypes.Scope().Lookup(iface)
obj := scope.Lookup(iface)
if obj == nil {
return nil, fmt.Errorf("interface %s does not exist", iface)
}
Expand All @@ -81,6 +86,45 @@ func extractInterfacesFromPackageTypes(pkgTypes *types.Package, ifaces []string)
return interfaces, nil
}

// getAllInterfacesFromPackageTypes discovers and returns all exported interfaces in the package.
func getAllInterfacesFromPackageTypes(pkgTypes *types.Package) ([]*model.Interface, error) {
scope := pkgTypes.Scope()
names := scope.Names()
var interfaces []*model.Interface
for _, name := range names {
obj := scope.Lookup(name)
if obj == nil {
continue
}
// Skip unexported types
if !obj.Exported() {
continue
}
named, ok := types.Unalias(obj.Type()).(*types.Named)
if !ok {
continue
}
// Check if the underlying type is an interface
iface, ok := named.Underlying().(*types.Interface)
if !ok {
continue
}
// Skip constraint interfaces (those with type constraints)
if isConstraint(iface) {
continue
}
modelIface, err := parseInterface(obj)
if err != nil {
return nil, newParseTypeError("parse interface", obj.Name(), err)
}
interfaces = append(interfaces, modelIface)
}
if len(interfaces) == 0 {
return nil, fmt.Errorf("no interfaces found in package %s", pkgTypes.Path())
}
return interfaces, nil
}

func parseInterface(obj types.Object) (*model.Interface, error) {
named, ok := types.Unalias(obj.Type()).(*types.Named)
if !ok {
Expand Down
39 changes: 37 additions & 2 deletions mockgen/package_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,15 @@ func Test_packageModeParser_parsePackage(t *testing.T) {
},
},
},
{
name: "error: no interfaces found when discovering all interfaces",
args: args{
packageName: "go.uber.org/mock/mockgen/internal/tests/no_interfaces",
ifaces: []string{},
},
expectedErr: "no interfaces found in package go.uber.org/mock/mockgen/internal/tests/no_interfaces",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parser := packageModeParser{}
Expand All @@ -360,10 +367,38 @@ func Test_packageModeParser_parsePackage(t *testing.T) {
} else {
assert.NoError(t, err)
}

assert.Equal(t, tt.expected, actual)
})
}
t.Run("success: discover all interfaces when no interfaces specified", func(t *testing.T) {
expected := &model.Package{
Name: "package_mode",
PkgPath: "go.uber.org/mock/mockgen/internal/tests/package_mode",
Interfaces: []*model.Interface{
{Name: "Animal"},
{Name: "Car"},
{Name: "Driver"},
{Name: "Earth"},
{Name: "Eater"},
{Name: "Farmer"},
{Name: "Food"},
{Name: "Human"},
{Name: "Primate"},
{Name: "UrbanResident"},
},
}

parser := packageModeParser{}
actual, err := parser.parsePackage(expected.PkgPath, []string{})
require.NoError(t, err)
require.NotNil(t, actual)
assert.Equal(t, expected.Name, actual.Name)
assert.Equal(t, expected.PkgPath, actual.PkgPath)
require.Len(t, actual.Interfaces, len(expected.Interfaces))
for i, expectedIface := range expected.Interfaces {
assert.Equal(t, expectedIface.Name, actual.Interfaces[i].Name)
}
})
}

// This tests the alias replacement behavior of package mode.
Expand Down