This repository was archived by the owner on Jun 27, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 608
Add archive mode #633
Open
dr-dime
wants to merge
4
commits into
golang:main
Choose a base branch
from
dr-dime:cl/gotypes
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add archive mode #633
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"go/token" | ||
"go/types" | ||
"log" | ||
"os" | ||
|
||
"github.com/golang/mock/mockgen/model" | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove empty line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this follows google's Golang style guide for grouping imports, i.e. std, local then third-party. |
||
"golang.org/x/tools/go/gcexportdata" | ||
) | ||
|
||
func archiveMode(importpath, archive string) (*model.Package, error) { | ||
f, err := os.Open(archive) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer f.Close() | ||
r, err := gcexportdata.NewReader(f) | ||
if err != nil { | ||
return nil, fmt.Errorf("read export data %q: %v", archive, err) | ||
} | ||
|
||
fset := token.NewFileSet() | ||
imports := make(map[string]*types.Package) | ||
tp, err := gcexportdata.Read(r, fset, imports, importpath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
pkg := &model.Package{ | ||
Name: tp.Name(), | ||
PkgPath: tp.Path(), | ||
} | ||
for _, name := range tp.Scope().Names() { | ||
m := tp.Scope().Lookup(name) | ||
tn, ok := m.(*types.TypeName) | ||
if !ok { | ||
continue | ||
} | ||
ti, ok := tn.Type().Underlying().(*types.Interface) | ||
if !ok { | ||
continue | ||
} | ||
it, err := model.InterfaceFromGoTypesType(ti) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
it.Name = m.Name() | ||
pkg.Interfaces = append(pkg.Interfaces, it) | ||
} | ||
return pkg, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package model | ||
|
||
import ( | ||
"fmt" | ||
"go/types" | ||
) | ||
|
||
// InterfaceFromGoTypesType returns a pointer to an interface for the | ||
// given reflection interface type. | ||
func InterfaceFromGoTypesType(it *types.Interface) (*Interface, error) { | ||
intf := &Interface{} | ||
|
||
for i := 0; i < it.NumMethods(); i++ { | ||
mt := it.Method(i) | ||
// TODO: need to skip unexported methods? or just raise an error? | ||
m := &Method{ | ||
Name: mt.Name(), | ||
} | ||
|
||
var err error | ||
m.In, m.Variadic, m.Out, err = funcArgsFromGoTypesType(mt.Type().(*types.Signature)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
intf.AddMethod(m) | ||
} | ||
|
||
return intf, nil | ||
} | ||
|
||
func funcArgsFromGoTypesType(t *types.Signature) (in []*Parameter, variadic *Parameter, out []*Parameter, err error) { | ||
nin := t.Params().Len() | ||
if t.Variadic() { | ||
nin-- | ||
} | ||
var p *Parameter | ||
for i := 0; i < nin; i++ { | ||
p, err = parameterFromGoTypesType(t.Params().At(i), false) | ||
if err != nil { | ||
return | ||
} | ||
in = append(in, p) | ||
} | ||
if t.Variadic() { | ||
p, err = parameterFromGoTypesType(t.Params().At(nin), true) | ||
if err != nil { | ||
return | ||
} | ||
variadic = p | ||
} | ||
for i := 0; i < t.Results().Len(); i++ { | ||
p, err = parameterFromGoTypesType(t.Results().At(i), false) | ||
if err != nil { | ||
return | ||
} | ||
out = append(out, p) | ||
} | ||
return | ||
} | ||
|
||
func parameterFromGoTypesType(v *types.Var, variadic bool) (*Parameter, error) { | ||
t := v.Type() | ||
if variadic { | ||
t = t.(*types.Slice).Elem() | ||
} | ||
tt, err := typeFromGoTypesType(t) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Parameter{Name: v.Name(), Type: tt}, nil | ||
} | ||
|
||
func typeFromGoTypesType(t types.Type) (Type, error) { | ||
// Hack workaround for https://golang.org/issue/3853. | ||
// This explicit check should not be necessary. | ||
// if t == byteType { | ||
// return PredeclaredType("byte"), nil | ||
// } | ||
|
||
if t, ok := t.(*types.Named); ok { | ||
tn := t.Obj() | ||
if tn.Pkg() == nil { | ||
return PredeclaredType(tn.Name()), nil | ||
} | ||
return &NamedType{ | ||
Package: tn.Pkg().Path(), | ||
Type: tn.Name(), | ||
}, nil | ||
} | ||
|
||
// only unnamed or predeclared types after here | ||
|
||
// Lots of types have element types. Let's do the parsing and error checking for all of them. | ||
var elemType Type | ||
if t, ok := t.(interface{ Elem() types.Type }); ok { | ||
var err error | ||
elemType, err = typeFromGoTypesType(t.Elem()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
switch t := t.(type) { | ||
case *types.Array: | ||
return &ArrayType{ | ||
Len: int(t.Len()), | ||
Type: elemType, | ||
}, nil | ||
case *types.Basic: | ||
return PredeclaredType(t.String()), nil | ||
case *types.Chan: | ||
var dir ChanDir | ||
switch t.Dir() { | ||
case types.RecvOnly: | ||
dir = RecvDir | ||
case types.SendOnly: | ||
dir = SendDir | ||
} | ||
return &ChanType{ | ||
Dir: dir, | ||
Type: elemType, | ||
}, nil | ||
case *types.Signature: | ||
in, variadic, out, err := funcArgsFromGoTypesType(t) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &FuncType{ | ||
In: in, | ||
Out: out, | ||
Variadic: variadic, | ||
}, nil | ||
case *types.Interface: | ||
if t.NumMethods() == 0 { | ||
return PredeclaredType("interface{}"), nil | ||
} | ||
case *types.Map: | ||
kt, err := typeFromGoTypesType(t.Key()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &MapType{ | ||
Key: kt, | ||
Value: elemType, | ||
}, nil | ||
case *types.Pointer: | ||
return &PointerType{ | ||
Type: elemType, | ||
}, nil | ||
case *types.Slice: | ||
return &ArrayType{ | ||
Len: -1, | ||
Type: elemType, | ||
}, nil | ||
case *types.Struct: | ||
if t.NumFields() == 0 { | ||
return PredeclaredType("struct{}"), nil | ||
} | ||
} | ||
|
||
// TODO: Struct, UnsafePointer | ||
return nil, fmt.Errorf("can't yet turn %v (%T) into a model.Type", t.String(), t) | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can archive mode optionally take a list of interfaces, just like reflect mode? Mocking extra interfaces may introduce extra dependencies to the mock code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be, but it also means this mode will derive from the source mode. We will need to manually match the specified interfaces to the resolved types. IMHO, the generated code will not introduce dependencies that are not already transitively depended on (in theory), so it should be just fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Saying we have:
When we only mock
IBar
, then the mock won't depend on packagefoo
, so it can be compiled into a different packagefoomock
and imported by the tests in packagefoo
. However, when we mock bothIBar
andIBaz
, if the mock is still compiled intofoomock
, then the mock would have to import packagefoo
because a method inIBaz
returnsfoo.SFoo
. Now the tests infoo
cannot importfoomock
, because of circular dependencies.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, in this case, I'll just put the generated mocks in foo, say mock_foo_test.go.
I'm never a big fan of exporting mocks for unit tests in external packages. The obvious reason is that you are actually testing against an imaginary counterpart, a passing test doesn't reflect the actual usage of the package being mocked.
If you need to provide something for external packages to test against, create a fake sharing the same logic.
This also follows https://github.com/golang/go/wiki/CodeReviewComments#interfaces.