blob: 20753e5867907914d3bceb6ce84c4171dd99c87e [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package compile_test
import (
"reflect"
"testing"
"v.io/v23/vdl"
"v.io/x/ref/lib/vdl/build"
"v.io/x/ref/lib/vdl/compile"
"v.io/x/ref/lib/vdl/internal/vdltestutil"
"v.io/x/ref/lib/vdl/parse"
)
func TestInterface(t *testing.T) {
for _, test := range ifaceTests {
env := compile.NewEnv(-1)
for _, tpkg := range test.Pkgs {
// Compile the package with a single file, adding the "package a" prefix
// to the source data automatically.
files := map[string]string{
tpkg.Name + ".vdl": "package " + tpkg.Name + "\n" + tpkg.Data,
}
pkgPath := "p.kg/" + tpkg.Name // use dots in pkgpath to test tricky cases
buildPkg := vdltestutil.FakeBuildPackage(tpkg.Name, pkgPath, files)
pkg := build.BuildPackage(buildPkg, env)
vdltestutil.ExpectResult(t, env.Errors, test.Name, tpkg.ErrRE)
if pkg == nil || tpkg.ErrRE != "" {
continue
}
matchIfaceRes(t, test.Name, tpkg, pkg.Files[0].Interfaces)
}
}
}
func matchIfaceRes(t *testing.T, tname string, tpkg ifacePkg, ifaces []*compile.Interface) {
if tpkg.Iface == nil {
return
}
// Look for an interface called "Res" to compare our expected results.
for _, iface := range ifaces {
if iface.Name == "Res" {
if got, want := normalizeIface(*iface), normalizeIface(*tpkg.Iface); !reflect.DeepEqual(got, want) {
t.Errorf("%s got %v, want %v", tname, got, want)
}
return
}
}
t.Errorf("%s couldn't find Res in package %s", tname, tpkg.Name)
}
func normalizeIface(x compile.Interface) compile.Interface {
// Don't compare uninteresting portions, to make tests more succinct.
x.Pos = parse.Pos{}
x.Exported = false
x.File = nil
embeds := x.Embeds
x.Embeds = nil
for _, embed := range embeds {
norm := normalizeIface(*embed)
x.Embeds = append(x.Embeds, &norm)
}
methods := x.Methods
x.Methods = nil
for _, method := range methods {
norm := normalizeMethod(*method)
x.Methods = append(x.Methods, &norm)
}
return x
}
func normalizeMethod(x compile.Method) compile.Method {
x.Pos = parse.Pos{}
x.InArgs = normalizeArgs(x.InArgs)
x.OutArgs = normalizeArgs(x.OutArgs)
x.Interface = nil
return x
}
func normalizeArgs(x []*compile.Field) (ret []*compile.Field) {
for _, arg := range x {
norm := normalizeArg(*arg)
ret = append(ret, &norm)
}
return
}
func normalizeArg(x compile.Field) compile.Field {
x.Pos = parse.Pos{}
return x
}
func np(name string) compile.NamePos {
return compile.NamePos{Name: name}
}
type ifaceTest struct {
Name string
Pkgs ip
}
type ip []ifacePkg
type ifacePkg struct {
Name string
Data string
Iface *compile.Interface
ErrRE string
}
var ifaceTests = []ifaceTest{
{"Empty", ip{{"a", `type Res interface{}`, &compile.Interface{NamePos: np("Res")}, ""}}},
{"NoArgs", ip{{"a", `type Res interface{NoArgs() error}`,
&compile.Interface{
NamePos: np("Res"),
Methods: []*compile.Method{{NamePos: np("NoArgs")}},
},
"",
}}},
{"HasArgs", ip{{"a", `type Res interface{HasArgs(x bool) (string | error)}`,
&compile.Interface{
NamePos: np("Res"),
Methods: []*compile.Method{{
NamePos: np("HasArgs"),
InArgs: []*compile.Field{{NamePos: np("x"), Type: vdl.BoolType}},
OutArgs: []*compile.Field{{Type: vdl.StringType}},
}},
},
"",
}}},
{"NamedOutArg", ip{{"a", `type Res interface{NamedOutArg() (s string | error)}`,
&compile.Interface{
NamePos: np("Res"),
Methods: []*compile.Method{{
NamePos: np("NamedOutArg"),
OutArgs: []*compile.Field{{NamePos: np("s"), Type: vdl.StringType}},
}},
},
"",
}}},
{"Embed", ip{{"a", `type A interface{};type Res interface{A}`,
&compile.Interface{
NamePos: np("Res"),
Embeds: []*compile.Interface{{NamePos: np("A")}},
},
"",
}}},
{"MultiEmbed", ip{{"a", `type A interface{};type B interface{};type Res interface{A;B}`,
&compile.Interface{
NamePos: np("Res"),
Embeds: []*compile.Interface{{NamePos: np("A")}, {NamePos: np("B")}},
},
"",
}}},
{"MultiPkgEmbed", ip{
{"a", `type Res interface{}`, &compile.Interface{NamePos: np("Res")}, ""},
{"b", `import "p.kg/a";type Res interface{a.Res}`,
&compile.Interface{
NamePos: np("Res"),
Embeds: []*compile.Interface{{NamePos: np("Res")}},
},
"",
},
}},
{"MultiPkgEmbedQualifiedPath", ip{
{"a", `type Res interface{}`, &compile.Interface{NamePos: np("Res")}, ""},
{"b", `import "p.kg/a";type Res interface{"p.kg/a".Res}`,
&compile.Interface{
NamePos: np("Res"),
Embeds: []*compile.Interface{{NamePos: np("Res")}},
},
"",
},
}},
{"UnmatchedEmbed", ip{{"a", `type A interface{};type Res interface{A.foobar}`, nil,
`\(\.foobar unmatched\)`,
}}},
{"NoErrorReturn", ip{{"a", `type Res interface{NoArgs()}`, nil,
`syntax error`,
}}},
{"UnnamedInArg", ip{{"a", `type Res interface{UnnamedInArg(string) error}`, nil,
`must name all in-args`,
}}},
{"UnnamedOutArgs", ip{{"a", `type Res interface{UnnamedOutArgs() (bool, string | error)}`, nil,
`must name all out-args if there are more than 1`,
}}},
{"TransitiveExportArg", ip{{"a", `type t bool; type Res interface{TransExportArg(a t) error}`, nil,
`transitively exported`,
}}},
{"DupMethod", ip{{"a", `type t bool; type Res interface{Foo() error;Foo() error}`, nil,
`Foo redefined`,
}}},
{"DupMethodEmbed", ip{{"a", `type t bool; type A interface{Foo() error}; type B interface{A;Foo() error}`, nil,
`Foo redefined`,
}}},
{"DupMethodBothEmbed", ip{{"a", `type t bool; type A interface{Foo() error}; type B interface{Foo() error}; type Res interface {A;B}`, nil,
`Foo redefined`,
}}},
{"DupEmbed", ip{{"a", `type t bool; type A interface{Foo() error}; type Res interface {A;A}`, nil,
`duplicate embedding`,
}}},
}