blob: f4475cf0fd77ac30e1165e2ddcb181b12edaeea3 [file] [log] [blame]
package analysis
import (
"go/ast"
"go/types"
"testing"
"github.com/gopherjs/gopherjs/compiler/internal/typeparams"
"github.com/gopherjs/gopherjs/internal/srctesting"
)
func TestBlocking_Simple(t *testing.T) {
bt := newBlockingTest(t,
`package test
func notBlocking() {
println("hi")
}`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_Recursive(t *testing.T) {
bt := newBlockingTest(t,
`package test
func notBlocking(i int) {
if i > 0 {
println(i)
notBlocking(i - 1)
}
}`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_AlternatingRecursive(t *testing.T) {
bt := newBlockingTest(t,
`package test
func near(i int) {
if i > 0 {
println(i)
far(i)
}
}
func far(i int) {
near(i - 1)
}`)
bt.assertNotBlocking(`near`)
bt.assertNotBlocking(`far`)
}
func TestBlocking_Channels(t *testing.T) {
bt := newBlockingTest(t,
`package test
func readFromChannel(c chan bool) {
<-c
}
func readFromChannelAssign(c chan bool) {
v := <-c
println(v)
}
func readFromChannelAsArg(c chan bool) {
println(<-c)
}
func sendToChannel(c chan bool) {
c <- true
}
func rangeOnChannel(c chan bool) {
for v := range c {
println(v)
}
}
func rangeOnSlice(c []bool) {
for v := range c {
println(v)
}
}`)
bt.assertBlocking(`readFromChannel`)
bt.assertBlocking(`sendToChannel`)
bt.assertBlocking(`rangeOnChannel`)
bt.assertBlocking(`readFromChannelAssign`)
bt.assertBlocking(`readFromChannelAsArg`)
bt.assertNotBlocking(`rangeOnSlice`)
}
func TestBlocking_Selects(t *testing.T) {
bt := newBlockingTest(t,
`package test
func selectReadWithoutDefault(a, b chan bool) {
select {
case <-a:
println("a")
case v := <-b:
println("b", v)
}
}
func selectReadWithDefault(a, b chan bool) {
select {
case <-a:
println("a")
case v := <-b:
println("b", v)
default:
println("nothing")
}
}
func selectSendWithoutDefault(a, b chan bool) {
select {
case a <- true:
println("a")
case b <- false:
println("b")
}
}
func selectSendWithDefault(a, b chan bool) {
select {
case a <- true:
println("a")
case b <- false:
println("b")
default:
println("nothing")
}
}`)
bt.assertBlocking(`selectReadWithoutDefault`)
bt.assertBlocking(`selectSendWithoutDefault`)
bt.assertNotBlocking(`selectReadWithDefault`)
bt.assertNotBlocking(`selectSendWithDefault`)
}
func TestBlocking_GoRoutines_WithFuncLiterals(t *testing.T) {
bt := newBlockingTest(t,
`package test
func notBlocking(c chan bool) {
go func(c chan bool) { // line 4
println(<-c)
}(c)
}
func blocking(c chan bool) {
go func(v bool) { // line 10
println(v)
}(<-c)
}`)
bt.assertNotBlocking(`notBlocking`)
bt.assertBlockingLit(4)
bt.assertBlocking(`blocking`)
bt.assertNotBlockingLit(10)
}
func TestBlocking_GoRoutines_WithNamedFuncs(t *testing.T) {
bt := newBlockingTest(t,
`package test
func blockingRoutine(c chan bool) {
println(<-c)
}
func nonBlockingRoutine(v bool) {
println(v)
}
func notBlocking(c chan bool) {
go blockingRoutine(c)
}
func blocking(c chan bool) {
go nonBlockingRoutine(<-c)
}`)
bt.assertBlocking(`blockingRoutine`)
bt.assertNotBlocking(`nonBlockingRoutine`)
bt.assertNotBlocking(`notBlocking`)
bt.assertBlocking(`blocking`)
}
func TestBlocking_Defers_WithoutReturns_WithFuncLiterals(t *testing.T) {
bt := newBlockingTest(t,
`package test
func blockingBody(c chan bool) {
defer func(c chan bool) { // line 4
println(<-c)
}(c)
}
func blockingArg(c chan bool) {
defer func(v bool) { // line 10
println(v)
}(<-c)
}
func notBlocking(c chan bool) {
defer func(v bool) { // line 16
println(v)
}(true)
}`)
bt.assertBlocking(`blockingBody`)
bt.assertBlockingLit(4)
bt.assertBlocking(`blockingArg`)
bt.assertNotBlockingLit(10)
bt.assertNotBlocking(`notBlocking`)
bt.assertNotBlockingLit(16)
}
func TestBlocking_Defers_WithoutReturns_WithNamedFuncs(t *testing.T) {
bt := newBlockingTest(t,
`package test
func blockingPrint(c chan bool) {
println(<-c)
}
func nonBlockingPrint(v bool) {
println(v)
}
func blockingBody(c chan bool) {
defer blockingPrint(c)
}
func blockingArg(c chan bool) {
defer nonBlockingPrint(<-c)
}
func notBlocking(c chan bool) {
defer nonBlockingPrint(true)
}`)
bt.assertBlocking(`blockingPrint`)
bt.assertNotBlocking(`nonBlockingPrint`)
bt.assertBlocking(`blockingBody`)
bt.assertBlocking(`blockingArg`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_Defers_WithReturns_WithFuncLiterals(t *testing.T) {
bt := newBlockingTest(t,
`package test
func blockingBody(c chan bool) int {
defer func(c chan bool) { // line 4
println(<-c)
}(c)
return 42
}
func blockingArg(c chan bool) int {
defer func(v bool) { // line 11
println(v)
}(<-c)
return 42
}
func notBlocking(c chan bool) int {
defer func(v bool) { // line 18
println(v)
}(true)
return 42
}`)
bt.assertBlocking(`blockingBody`)
bt.assertBlockingLit(4)
bt.assertBlocking(`blockingArg`)
bt.assertNotBlockingLit(11)
// TODO: The following is blocking because currently any defer with a return
// is assumed to be blocking. This limitation should be fixed in the future.
bt.assertBlocking(`notBlocking`)
bt.assertNotBlockingLit(18)
}
func TestBlocking_Defers_WithReturns_WithNamedFuncs(t *testing.T) {
bt := newBlockingTest(t,
`package test
func blockingPrint(c chan bool) {
println(<-c)
}
func nonBlockingPrint(v bool) {
println(v)
}
func blockingBody(c chan bool) int {
defer blockingPrint(c)
return 42
}
func blockingArg(c chan bool) int {
defer nonBlockingPrint(<-c)
return 42
}
func notBlocking(c chan bool) int {
defer nonBlockingPrint(true)
return 42
}`)
bt.assertBlocking(`blockingPrint`)
bt.assertNotBlocking(`nonBlockingPrint`)
bt.assertBlocking(`blockingBody`)
bt.assertBlocking(`blockingArg`)
// TODO: The following is blocking because currently any defer with a return
// is assumed to be blocking. This limitation should be fixed in the future.
bt.assertBlocking(`notBlocking`)
}
func TestBlocking_Returns_WithoutDefers(t *testing.T) {
bt := newBlockingTest(t,
`package test
func blocking(c chan bool) bool {
return <-c
}
func indirectlyBlocking(c chan bool) bool {
return blocking(c)
}
func notBlocking(c chan bool) bool {
return true
}`)
bt.assertBlocking(`blocking`)
bt.assertBlocking(`indirectlyBlocking`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_FunctionLiteral(t *testing.T) {
// See: https://github.com/gopherjs/gopherjs/issues/955.
bt := newBlockingTest(t,
`package test
func blocking() {
c := make(chan bool)
<-c
}
func indirectlyBlocking() {
func() { blocking() }() // line 9
}
func directlyBlocking() {
func() { // line 13
c := make(chan bool)
<-c
}()
}
func notBlocking() {
func() { println() } () // line 20
}`)
bt.assertBlocking(`blocking`)
bt.assertBlocking(`indirectlyBlocking`)
bt.assertBlockingLit(9)
bt.assertBlocking(`directlyBlocking`)
bt.assertBlockingLit(13)
bt.assertNotBlocking(`notBlocking`)
bt.assertNotBlockingLit(20)
}
func TestBlocking_LinkedFunction(t *testing.T) {
bt := newBlockingTest(t,
`package test
// linked to some other function
func blocking()
func indirectlyBlocking() {
blocking()
}`)
bt.assertBlocking(`blocking`)
bt.assertBlocking(`indirectlyBlocking`)
}
func TestBlocking_Instances_WithSingleTypeArg(t *testing.T) {
bt := newBlockingTest(t,
`package test
func blocking[T any]() {
c := make(chan T)
<-c
}
func notBlocking[T any]() {
var v T
println(v)
}
func bInt() {
blocking[int]()
}
func nbUint() {
notBlocking[uint]()
}`)
bt.assertFuncInstCount(4)
// blocking and notBlocking as generics do not have FuncInfo,
// only non-generic and instances have FuncInfo.
bt.assertBlockingInst(`test.blocking[int]`)
bt.assertBlocking(`bInt`)
bt.assertNotBlockingInst(`test.notBlocking[uint]`)
bt.assertNotBlocking(`nbUint`)
}
func TestBlocking_Instances_WithMultipleTypeArgs(t *testing.T) {
bt := newBlockingTest(t,
`package test
func blocking[K comparable, V any, M ~map[K]V]() {
c := make(chan M)
<-c
}
func notBlocking[K comparable, V any, M ~map[K]V]() {
var m M
println(m)
}
func bInt() {
blocking[string, int, map[string]int]()
}
func nbUint() {
notBlocking[string, uint, map[string]uint]()
}`)
bt.assertFuncInstCount(4)
// blocking and notBlocking as generics do not have FuncInfo,
// only non-generic and instances have FuncInfo.
bt.assertBlockingInst(`test.blocking[string, int, map[string]int]`)
bt.assertBlocking(`bInt`)
bt.assertNotBlockingInst(`test.notBlocking[string, uint, map[string]uint]`)
bt.assertNotBlocking(`nbUint`)
}
func TestBlocking_Indexed_FunctionSlice(t *testing.T) {
// This calls notBlocking but since the function pointers
// are in the slice they will both be considered as blocking.
bt := newBlockingTest(t,
`package test
func blocking() {
c := make(chan int)
<-c
}
func notBlocking() {
println()
}
var funcs = []func() { blocking, notBlocking }
func indexer(i int) {
funcs[i]()
}`)
bt.assertBlocking(`blocking`)
bt.assertBlocking(`indexer`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_Indexed_FunctionMap(t *testing.T) {
// This calls notBlocking but since the function pointers
// are in the map they will both be considered as blocking.
bt := newBlockingTest(t,
`package test
func blocking() {
c := make(chan int)
<-c
}
func notBlocking() {
println()
}
var funcs = map[string]func() {
"b": blocking,
"nb": notBlocking,
}
func indexer(key string) {
funcs[key]()
}`)
bt.assertBlocking(`blocking`)
bt.assertBlocking(`indexer`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_Indexed_FunctionArray(t *testing.T) {
// This calls notBlocking but since the function pointers
// are in the array they will both be considered as blocking.
bt := newBlockingTest(t,
`package test
func blocking() {
c := make(chan int)
<-c
}
func notBlocking() {
println()
}
var funcs = [2]func() { blocking, notBlocking }
func indexer(i int) {
funcs[i]()
}`)
bt.assertBlocking(`blocking`)
bt.assertBlocking(`indexer`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_Casting_InterfaceInstanceWithSingleTypeParam(t *testing.T) {
// This checks that casting to an instance type with a single type parameter
// is treated as a cast and not accidentally treated as a function call.
bt := newBlockingTest(t,
`package test
type Foo[T any] interface {
Baz() T
}
type Bar struct {
name string
}
func (b Bar) Baz() string {
return b.name
}
func caster() Foo[string] {
b := Bar{name: "foo"}
return Foo[string](b)
}`)
bt.assertNotBlocking(`caster`)
}
func TestBlocking_Casting_InterfaceInstanceWithMultipleTypeParams(t *testing.T) {
// This checks that casting to an instance type with multiple type parameters
// is treated as a cast and not accidentally treated as a function call.
bt := newBlockingTest(t,
`package test
type Foo[K comparable, V any] interface {
Baz(K) V
}
type Bar struct {
dat map[string]int
}
func (b Bar) Baz(key string) int {
return b.dat[key]
}
func caster() Foo[string, int] {
b := Bar{ dat: map[string]int{ "foo": 2 }}
return Foo[string, int](b)
}`)
bt.assertNotBlocking(`caster`)
}
func TestBlocking_Casting_Interface(t *testing.T) {
// This checks that non-generic casting of type is treated as a
// cast and not accidentally treated as a function call.
bt := newBlockingTest(t,
`package test
type Foo interface {
Baz() string
}
type Bar struct {
name string
}
func (b Bar) Baz() string {
return b.name
}
func caster() Foo {
b := Bar{"foo"}
return Foo(b)
}`)
bt.assertNotBlocking(`caster`)
}
func TestBlocking_ComplexCasting(t *testing.T) {
// This checks a complex casting to a type is treated as a
// cast and not accidentally treated as a function call.
bt := newBlockingTest(t,
`package test
type Foo interface {
Bar() string
}
func doNothing(f Foo) Foo {
return interface{ Bar() string }(f)
}`)
bt.assertNotBlocking(`doNothing`)
}
func TestBlocking_ComplexCall(t *testing.T) {
// This checks a complex call of a function is defaulted to blocking.
bt := newBlockingTest(t,
`package test
type Foo func() string
func bar(f any) string {
return f.(Foo)()
}`)
bt.assertBlocking(`bar`)
}
func TestBlocking_CallWithNamedInterfaceReceiver(t *testing.T) {
// This checks that calling a named interface function is defaulted to blocking.
bt := newBlockingTest(t,
`package test
type Foo interface {
Baz()
}
func bar(f Foo) {
f.Baz()
}`)
bt.assertBlocking(`bar`)
}
func TestBlocking_CallWithUnnamedInterfaceReceiver(t *testing.T) {
// This checks that calling an unnamed interface function is defaulted to blocking.
bt := newBlockingTest(t,
`package test
func bar(f interface { Baz() }) {
f.Baz()
}`)
bt.assertBlocking(`bar`)
}
func TestBlocking_VarFunctionCall(t *testing.T) {
// This checks that calling a function in a var is defaulted to blocking.
bt := newBlockingTest(t,
`package test
var foo = func() { // line 3
println("hi")
}
func bar() {
foo()
}`)
bt.assertNotBlockingLit(3)
bt.assertBlocking(`bar`)
}
func TestBlocking_FieldFunctionCallOnNamed(t *testing.T) {
// This checks that calling a function in a field is defaulted to blocking.
// This should be the same as the previous test but with a field since
// all function pointers are treated as blocking.
bt := newBlockingTest(t,
`package test
type foo struct {
Baz func()
}
func bar(f foo) {
f.Baz()
}`)
bt.assertBlocking(`bar`)
}
func TestBlocking_FieldFunctionCallOnUnnamed(t *testing.T) {
// Same as previous test but with an unnamed struct.
bt := newBlockingTest(t,
`package test
func bar(f struct { Baz func() }) {
f.Baz()
}`)
bt.assertBlocking(`bar`)
}
func TestBlocking_ParamFunctionCall(t *testing.T) {
// Same as previous test but with an unnamed function parameter.
bt := newBlockingTest(t,
`package test
func bar(baz func()) {
baz()
}`)
bt.assertBlocking(`bar`)
}
func TestBlocking_FunctionUnwrapping(t *testing.T) {
// Test that calling a function that calls a function etc.
// is defaulted to blocking.
bt := newBlockingTest(t,
`package test
func bar(baz func()func()func()) {
baz()()()
}`)
bt.assertBlocking(`bar`)
}
func TestBlocking_MethodCall_NonPointer(t *testing.T) {
// Test that calling a method on a non-pointer receiver.
bt := newBlockingTest(t,
`package test
type Foo struct {}
func (f Foo) blocking() {
ch := make(chan bool)
<-ch
}
func (f Foo) notBlocking() {
println("hi")
}
func blocking(f Foo) {
f.blocking()
}
func notBlocking(f Foo) {
f.notBlocking()
}`)
bt.assertBlocking(`Foo.blocking`)
bt.assertNotBlocking(`Foo.notBlocking`)
bt.assertBlocking(`blocking`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_MethodCall_Pointer(t *testing.T) {
// Test that calling a method on a pointer receiver.
bt := newBlockingTest(t,
`package test
type Foo struct {}
func (f *Foo) blocking() {
ch := make(chan bool)
<-ch
}
func (f *Foo) notBlocking() {
println("hi")
}
func blocking(f *Foo) {
f.blocking()
}
func notBlocking(f *Foo) {
f.notBlocking()
}`)
bt.assertBlocking(`Foo.blocking`)
bt.assertNotBlocking(`Foo.notBlocking`)
bt.assertBlocking(`blocking`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_InstantiationBlocking(t *testing.T) {
// This checks that the instantiation of a generic function is
// being used when checking for blocking not the type argument interface.
bt := newBlockingTest(t,
`package test
type BazBlocker struct {
c chan bool
}
func (bb BazBlocker) Baz() {
println(<-bb.c)
}
type BazNotBlocker struct {}
func (bnb BazNotBlocker) Baz() {
println("hi")
}
type Foo interface { Baz() }
func FooBaz[T Foo](foo T) {
foo.Baz()
}
func blockingViaExplicit() {
FooBaz[BazBlocker](BazBlocker{c: make(chan bool)})
}
func notBlockingViaExplicit() {
FooBaz[BazNotBlocker](BazNotBlocker{})
}
func blockingViaImplicit() {
FooBaz(BazBlocker{c: make(chan bool)})
}
func notBlockingViaImplicit() {
FooBaz(BazNotBlocker{})
}`)
bt.assertFuncInstCount(8)
// `FooBaz` as a generic function does not have FuncInfo for it,
// only non-generic or instantiations of a generic functions have FuncInfo.
bt.assertBlocking(`BazBlocker.Baz`)
bt.assertBlocking(`blockingViaExplicit`)
bt.assertBlocking(`blockingViaImplicit`)
bt.assertBlockingInst(`test.FooBaz[pkg/test.BazBlocker]`)
bt.assertNotBlocking(`BazNotBlocker.Baz`)
bt.assertNotBlocking(`notBlockingViaExplicit`)
bt.assertNotBlocking(`notBlockingViaImplicit`)
bt.assertNotBlockingInst(`test.FooBaz[pkg/test.BazNotBlocker]`)
}
func TestBlocking_NestedInstantiations(t *testing.T) {
// Checking that the type parameters are being propagated down into calls.
bt := newBlockingTest(t,
`package test
func Foo[T any](t T) {
println(t)
}
func Bar[K comparable, V any, M ~map[K]V](m M) {
Foo(m)
}
func Baz[T any, S ~[]T](s S) {
m:= map[int]T{}
for i, v := range s {
m[i] = v
}
Bar(m)
}
func bazInt() {
Baz([]int{1, 2, 3})
}
func bazString() {
Baz([]string{"one", "two", "three"})
}`)
bt.assertFuncInstCount(8)
bt.assertNotBlocking(`bazInt`)
bt.assertNotBlocking(`bazString`)
bt.assertNotBlockingInst(`test.Foo[map[int]int]`)
bt.assertNotBlockingInst(`test.Foo[map[int]string]`)
bt.assertNotBlockingInst(`test.Bar[int, int, map[int]int]`)
bt.assertNotBlockingInst(`test.Bar[int, string, map[int]string]`)
bt.assertNotBlockingInst(`test.Baz[int, []int]`)
bt.assertNotBlockingInst(`test.Baz[string, []string]`)
}
func TestBlocking_MethodSelection(t *testing.T) {
// This tests method selection using method expression (receiver as the first
// argument) selecting on type and method call selecting on a variable.
// This tests in both generic (FooBaz[T]) and non-generic contexts.
bt := newBlockingTest(t,
`package test
type Foo interface { Baz() }
type BazBlocker struct {
c chan bool
}
func (bb BazBlocker) Baz() {
println(<-bb.c)
}
type BazNotBlocker struct {}
func (bnb BazNotBlocker) Baz() {
println("hi")
}
type FooBaz[T Foo] struct {}
func (fb FooBaz[T]) ByMethodExpression() {
var foo T
T.Baz(foo)
}
func (fb FooBaz[T]) ByInstance() {
var foo T
foo.Baz()
}
func blocking() {
fb := FooBaz[BazBlocker]{}
FooBaz[BazBlocker].ByMethodExpression(fb)
FooBaz[BazBlocker].ByInstance(fb)
fb.ByMethodExpression()
fb.ByInstance()
}
func notBlocking() {
fb := FooBaz[BazNotBlocker]{}
FooBaz[BazNotBlocker].ByMethodExpression(fb)
FooBaz[BazNotBlocker].ByInstance(fb)
fb.ByMethodExpression()
fb.ByInstance()
}`)
bt.assertFuncInstCount(8)
bt.assertBlocking(`BazBlocker.Baz`)
bt.assertBlockingInst(`test.ByMethodExpression[pkg/test.BazBlocker]`)
bt.assertBlockingInst(`test.ByInstance[pkg/test.BazBlocker]`)
bt.assertBlocking(`blocking`)
bt.assertNotBlocking(`BazNotBlocker.Baz`)
bt.assertNotBlockingInst(`test.ByMethodExpression[pkg/test.BazNotBlocker]`)
bt.assertNotBlockingInst(`test.ByInstance[pkg/test.BazNotBlocker]`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_IsImportBlocking_Simple(t *testing.T) {
otherSrc := `package other
func Blocking() {
ch := make(chan bool)
<-ch
}
func NotBlocking() {
println("hi")
}`
testSrc := `package test
import "pkg/other"
func blocking() {
other.Blocking()
}
func notBlocking() {
other.NotBlocking()
}`
bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc)
bt.assertBlocking(`blocking`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_IsImportBlocking_ForwardInstances(t *testing.T) {
otherSrc := `package other
type BazBlocker struct {
c chan bool
}
func (bb BazBlocker) Baz() {
println(<-bb.c)
}
type BazNotBlocker struct {}
func (bnb BazNotBlocker) Baz() {
println("hi")
}`
testSrc := `package test
import "pkg/other"
type Foo interface { Baz() }
func FooBaz[T Foo](f T) {
f.Baz()
}
func blocking() {
FooBaz(other.BazBlocker{})
}
func notBlocking() {
FooBaz(other.BazNotBlocker{})
}`
bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc)
bt.assertBlocking(`blocking`)
bt.assertNotBlocking(`notBlocking`)
}
func TestBlocking_IsImportBlocking_BackwardInstances(t *testing.T) {
t.Skip(`isImportedBlocking doesn't fully handle instances yet`)
// TODO(grantnelson-wf): This test is currently failing because the info
// for the test package is need while creating the instances for FooBaz
// while analyzing the other package. However the other package is analyzed
// first since the test package is dependent on it. One possible fix is that
// we add some mechanism similar to the localInstCallees but for remote
// instances then perform the blocking propagation steps for all packages
// including the localInstCallees propagation at the same time. After all the
// propagation of the calls then the flow control statements can be marked.
otherSrc := `package other
type Foo interface { Baz() }
func FooBaz[T Foo](f T) {
f.Baz()
}`
testSrc := `package test
import "pkg/other"
type BazBlocker struct {
c chan bool
}
func (bb BazBlocker) Baz() {
println(<-bb.c)
}
type BazNotBlocker struct {}
func (bnb BazNotBlocker) Baz() {
println("hi")
}
func blocking() {
other.FooBaz(BazBlocker{})
}
func notBlocking() {
other.FooBaz(BazNotBlocker{})
}`
bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc)
bt.assertBlocking(`blocking`)
bt.assertNotBlocking(`notBlocking`)
}
type blockingTest struct {
f *srctesting.Fixture
file *ast.File
pkgInfo *Info
}
func newBlockingTest(t *testing.T, src string) *blockingTest {
f := srctesting.New(t)
tc := typeparams.Collector{
TContext: types.NewContext(),
Info: f.Info,
Instances: &typeparams.PackageInstanceSets{},
}
file := f.Parse(`test.go`, src)
testInfo, testPkg := f.Check(`pkg/test`, file)
tc.Scan(testPkg, file)
isImportBlocking := func(i typeparams.Instance) bool {
t.Fatalf(`isImportBlocking should not be called in this test, called with %v`, i)
return true
}
pkgInfo := AnalyzePkg([]*ast.File{file}, f.FileSet, testInfo, types.NewContext(), testPkg, tc.Instances, isImportBlocking)
return &blockingTest{
f: f,
file: file,
pkgInfo: pkgInfo,
}
}
func newBlockingTestWithOtherPackage(t *testing.T, testSrc string, otherSrc string) *blockingTest {
f := srctesting.New(t)
tc := typeparams.Collector{
TContext: types.NewContext(),
Info: f.Info,
Instances: &typeparams.PackageInstanceSets{},
}
pkgInfo := map[*types.Package]*Info{}
isImportBlocking := func(i typeparams.Instance) bool {
if info, ok := pkgInfo[i.Object.Pkg()]; ok {
return info.IsBlocking(i)
}
t.Fatalf(`unexpected package in isImportBlocking for %v`, i)
return true
}
otherFile := f.Parse(`other.go`, otherSrc)
_, otherPkg := f.Check(`pkg/other`, otherFile)
tc.Scan(otherPkg, otherFile)
testFile := f.Parse(`test.go`, testSrc)
_, testPkg := f.Check(`pkg/test`, testFile)
tc.Scan(testPkg, testFile)
otherPkgInfo := AnalyzePkg([]*ast.File{otherFile}, f.FileSet, f.Info, types.NewContext(), otherPkg, tc.Instances, isImportBlocking)
pkgInfo[otherPkg] = otherPkgInfo
testPkgInfo := AnalyzePkg([]*ast.File{testFile}, f.FileSet, f.Info, types.NewContext(), testPkg, tc.Instances, isImportBlocking)
pkgInfo[testPkg] = testPkgInfo
return &blockingTest{
f: f,
file: testFile,
pkgInfo: testPkgInfo,
}
}
func (bt *blockingTest) assertFuncInstCount(expCount int) {
if got := bt.pkgInfo.funcInstInfos.Len(); got != expCount {
bt.f.T.Errorf(`Got %d function infos but expected %d.`, got, expCount)
for i, inst := range bt.pkgInfo.funcInstInfos.Keys() {
bt.f.T.Logf(` %d. %q`, i+1, inst.TypeString())
}
}
}
func (bt *blockingTest) assertBlocking(funcName string) {
if !bt.isTypesFuncBlocking(funcName) {
bt.f.T.Errorf(`Got %q as not blocking but expected it to be blocking.`, funcName)
}
}
func (bt *blockingTest) assertNotBlocking(funcName string) {
if bt.isTypesFuncBlocking(funcName) {
bt.f.T.Errorf(`Got %q as blocking but expected it to be not blocking.`, funcName)
}
}
func getFuncDeclName(fd *ast.FuncDecl) string {
name := fd.Name.Name
if fd.Recv != nil && len(fd.Recv.List) == 1 && fd.Recv.List[0].Type != nil {
typ := fd.Recv.List[0].Type
if p, ok := typ.(*ast.StarExpr); ok {
typ = p.X
}
if id, ok := typ.(*ast.Ident); ok {
name = id.Name + `.` + name
}
}
return name
}
func (bt *blockingTest) isTypesFuncBlocking(funcName string) bool {
var decl *ast.FuncDecl
ast.Inspect(bt.file, func(n ast.Node) bool {
if f, ok := n.(*ast.FuncDecl); ok && getFuncDeclName(f) == funcName {
decl = f
return false
}
return decl == nil
})
if decl == nil {
bt.f.T.Fatalf(`Declaration of %q is not found in the AST.`, funcName)
}
blockingType, ok := bt.pkgInfo.Defs[decl.Name]
if !ok {
bt.f.T.Fatalf(`No function declaration found for %q.`, decl.Name)
}
inst := typeparams.Instance{Object: blockingType.(*types.Func)}
return bt.pkgInfo.IsBlocking(inst)
}
func (bt *blockingTest) assertBlockingLit(lineNo int) {
if !bt.isFuncLitBlocking(lineNo) {
bt.f.T.Errorf(`Got FuncLit at line %d as not blocking but expected it to be blocking.`, lineNo)
}
}
func (bt *blockingTest) assertNotBlockingLit(lineNo int) {
if bt.isFuncLitBlocking(lineNo) {
bt.f.T.Errorf(`Got FuncLit at line %d as blocking but expected it to be not blocking.`, lineNo)
}
}
func (bt *blockingTest) getFuncLitLineNo(fl *ast.FuncLit) int {
return bt.f.FileSet.Position(fl.Pos()).Line
}
func (bt *blockingTest) isFuncLitBlocking(lineNo int) bool {
var fnLit *ast.FuncLit
ast.Inspect(bt.file, func(n ast.Node) bool {
if fl, ok := n.(*ast.FuncLit); ok && bt.getFuncLitLineNo(fl) == lineNo {
fnLit = fl
return false
}
return fnLit == nil
})
if fnLit == nil {
bt.f.T.Fatalf(`FuncLit found on line %d not found in the AST.`, lineNo)
}
return bt.pkgInfo.FuncLitInfo(fnLit).HasBlocking()
}
func (bt *blockingTest) assertBlockingInst(instanceStr string) {
if !bt.isFuncInstBlocking(instanceStr) {
bt.f.T.Errorf(`Got function instance of %q as not blocking but expected it to be blocking.`, instanceStr)
}
}
func (bt *blockingTest) assertNotBlockingInst(instanceStr string) {
if bt.isFuncInstBlocking(instanceStr) {
bt.f.T.Errorf(`Got function instance of %q as blocking but expected it to be not blocking.`, instanceStr)
}
}
func (bt *blockingTest) isFuncInstBlocking(instanceStr string) bool {
instances := bt.pkgInfo.funcInstInfos.Keys()
for _, inst := range instances {
if inst.TypeString() == instanceStr {
return bt.pkgInfo.FuncInfo(inst).HasBlocking()
}
}
bt.f.T.Logf(`Function instances found in package info:`)
for i, inst := range instances {
bt.f.T.Logf(` %d. %s`, i+1, inst.TypeString())
}
bt.f.T.Fatalf(`No function instance found for %q in package info.`, instanceStr)
return false
}