blob: 4cea37d02dfe0064ff9fd11fdfbf55da9c1772ac [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 swift implements Swift code generation from compiled VDL packages.
package swift
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"v.io/x/lib/gosh"
"v.io/x/ref/lib/vdl/build"
"v.io/x/ref/lib/vdl/compile"
"v.io/x/ref/lib/vdl/internal/vdltestutil"
)
const (
pkgAFile1 = `package a
const ABool = true
type StructA struct {
X bool
}`
pkgBFile1 = `package b
const BBool = true
type StructB struct {
X bool
}`
pkgCFile1 = `package c
import "v.io/v23/a"
const CBool = a.ABool
type (
StructC struct {
X bool
Y a.StructA
}
SelfContainedStructC struct {
X bool
}
UnionC union {
X bool
Y a.StructA
}
MapCKey map[a.StructA]string
MapCValue map[string]a.StructA
ArrayC [5]a.StructA
ListC []a.StructA
SetC set[a.StructA]
)
`
)
type moduleConfig struct {
Name string
Path string
}
type pkgConfig struct {
Name string
Path string
Files map[string]string
}
func createTmpVdlPath(t *testing.T, modules []moduleConfig, pkgs []pkgConfig) (string, func()) {
oldVdlPath := os.Getenv("VDLPATH")
sh := gosh.NewShell(nil)
tempDir := sh.MakeTempDir()
os.Setenv("VDLPATH", tempDir)
for _, module := range modules {
if strings.HasPrefix(module.Path, "/") {
sh.Cleanup()
t.Fatalf("module.Path must be relative")
}
sh.Cmd("mkdir", "-p", filepath.Join(tempDir, module.Path)).Run()
moduleConfigPath := filepath.Join(tempDir, module.Path, "swiftmodule")
err := ioutil.WriteFile(moduleConfigPath, []byte(module.Name), 0644)
if err != nil {
sh.Cleanup()
t.Fatalf("Unable to create temp vdl.config file: %v", err)
}
}
for _, pkg := range pkgs {
if strings.HasPrefix(pkg.Path, "/") {
sh.Cleanup()
t.Errorf("pkg.Path must be relative")
}
sh.Cmd("mkdir", "-p", filepath.Join(tempDir, pkg.Path)).Run()
for file, contents := range pkg.Files {
vdlPath := filepath.Join(tempDir, pkg.Path, file)
err := ioutil.WriteFile(vdlPath, []byte(contents), 0644)
if err != nil {
sh.Cleanup()
t.Fatalf("Unable to create temp vdl file at %v: %v", vdlPath, err)
}
}
}
return tempDir, func() {
sh.Cleanup()
os.Setenv("VDLPATH", oldVdlPath)
}
}
func createPackagesAndSwiftContext(t *testing.T, modules []moduleConfig, pkgs []pkgConfig, ctxPath string) ([]*compile.Package, *swiftContext, func()) {
vdlPath, cleanup := createTmpVdlPath(t, modules, pkgs)
built := make([]*compile.Package, len(pkgs))
i := 0
env := compile.NewEnv(-1)
genPathToDir := map[string]string{}
for _, pkg := range pkgs {
buildPkg := vdltestutil.FakeBuildPackage(pkg.Name, pkg.Path, pkg.Files)
built[i] = build.BuildPackage(buildPkg, env)
built[i].GenPath = built[i].Path
genPathToDir[built[i].GenPath] = filepath.Join(vdlPath, pkg.Path)
i++
}
// Find ctxPkgPath
var ctxPkg *compile.Package = nil
for _, pkg := range built {
if pkg.Path == ctxPath {
ctxPkg = pkg
break
}
}
if ctxPkg == nil {
cleanup()
t.Fatalf("Could not find test context pkg %v", ctxPath)
}
ctx := swiftContext{
env,
ctxPkg,
genPathToDir,
map[string]bool{vdlPath: true},
map[*compile.TypeDef]string{},
}
return built, &ctx, cleanup
}
func TestPkgIsSameModule(t *testing.T) {
tests := []struct {
name string
modules []moduleConfig
pkgs []pkgConfig
pkgPath string
ctxPath string
expect bool
}{
{"only one module",
[]moduleConfig{moduleConfig{V23SwiftFrameworkName, "v.io/v23"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"b", "v.io/v23/b", map[string]string{"file1.vdl": pkgBFile1}}},
"v.io/v23/a",
"v.io/v23/b",
true},
{"two modules, in same module",
[]moduleConfig{moduleConfig{V23SwiftFrameworkName, "v.io/v23"}, moduleConfig{"VanadiumServices", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"b", "v.io/v23/b", map[string]string{"file1.vdl": pkgBFile1}}},
"v.io/v23/a",
"v.io/v23/b",
true},
{"in diff module",
[]moduleConfig{moduleConfig{V23SwiftFrameworkName, "v.io/v23"}, moduleConfig{"VanadiumServices", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"b", "v.io/v23/b", map[string]string{"file1.vdl": pkgBFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/b",
"v.io/v23/services/c",
false},
}
for _, test := range tests {
pkgs, ctx, cleanup := createPackagesAndSwiftContext(t, test.modules, test.pkgs, test.ctxPath)
// Find test pkgPath
var testPkg *compile.Package = nil
for _, pkg := range pkgs {
if pkg.Path == test.pkgPath {
testPkg = pkg
break
}
}
if testPkg == nil {
cleanup()
t.Errorf("Could not find test pkg")
return
}
if ctxModule, pkgModule := ctx.swiftModule(ctx.pkg), ctx.swiftModule(testPkg); ctxModule == "" || pkgModule == "" {
cleanup()
t.Errorf("%s\n ctx or pkg module is nil", test.name)
return
}
if got, want := ctx.pkgIsSameModule(testPkg), test.expect; got != want {
t.Errorf("%s\n GOT %v\nWANT %v", test.name, got, want)
}
cleanup()
}
}
func TestSwiftModule(t *testing.T) {
tests := []struct {
name string
modules []moduleConfig
pkgs []pkgConfig
ctxPath string
expect string
}{
{"no modules",
[]moduleConfig{},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"b", "v.io/v23/b", map[string]string{"file1.vdl": pkgBFile1}}},
"v.io/v23/a",
""},
{"only one module",
[]moduleConfig{moduleConfig{V23SwiftFrameworkName, "v.io/v23"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"b", "v.io/v23/b", map[string]string{"file1.vdl": pkgBFile1}}},
"v.io/v23/a",
V23SwiftFrameworkName},
{"two modules (VanadiumCore)",
[]moduleConfig{moduleConfig{V23SwiftFrameworkName, "v.io/v23"}, moduleConfig{"VanadiumServices", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/a",
V23SwiftFrameworkName},
{"two modules (VanadiumServices)",
[]moduleConfig{moduleConfig{V23SwiftFrameworkName, "v.io/v23"}, moduleConfig{"VanadiumServices", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/services/c",
"VanadiumServices"},
{"not in path",
[]moduleConfig{moduleConfig{V23SwiftFrameworkName, "v.io/v23/a"}, moduleConfig{"VanadiumServices", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/b", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/b",
""},
}
for _, test := range tests {
_, ctx, cleanup := createPackagesAndSwiftContext(t, test.modules, test.pkgs, test.ctxPath)
if got, want := ctx.swiftModule(ctx.pkg), test.expect; got != want {
t.Errorf("%s\n GOT %v\nWANT %v", test.name, got, want)
}
cleanup()
}
}
func TestPackageName(t *testing.T) {
tests := []struct {
name string
modules []moduleConfig
pkgs []pkgConfig
ctxPath string
pkgPath string
expect string
}{
{"no modules",
[]moduleConfig{},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"b", "v.io/v23/b", map[string]string{"file1.vdl": pkgBFile1}}},
"v.io/v23/a",
"v.io/v23/a",
"VioV23A"},
{"removes module path",
[]moduleConfig{moduleConfig{V23SwiftFrameworkName, "v.io/v23"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"b", "v.io/v23/b", map[string]string{"file1.vdl": pkgBFile1}}},
"v.io/v23/a",
"v.io/v23/a",
"A"},
{"root is empty name",
[]moduleConfig{moduleConfig{V23SwiftFrameworkName, "v.io/v23/a"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"b", "v.io/v23/b", map[string]string{"file1.vdl": pkgBFile1}}},
"v.io/v23/a",
"v.io/v23/a",
""},
{"path not in module",
[]moduleConfig{moduleConfig{"VanadiumServices", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/a",
"v.io/v23/a",
"VioV23A"},
{"path in module",
[]moduleConfig{moduleConfig{"VanadiumServices", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/a",
"v.io/v23/services/c",
"C"},
{"two modules (VanadiumServices)",
[]moduleConfig{moduleConfig{V23SwiftFrameworkName, "v.io/v23"}, moduleConfig{"VanadiumServices", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/a",
"v.io/v23/services/c",
"C"},
}
for _, test := range tests {
pkgs, ctx, cleanup := createPackagesAndSwiftContext(t, test.modules, test.pkgs, test.ctxPath)
// Find test pkgPath
var testPkg *compile.Package = nil
for _, pkg := range pkgs {
if pkg.Path == test.pkgPath {
testPkg = pkg
break
}
}
if testPkg == nil {
cleanup()
t.Errorf("Could not find test pkg")
return
}
if got, want := ctx.swiftPackageName(testPkg), test.expect; got != want {
t.Errorf("%s\n GOT %v\nWANT %v", test.name, got, want)
}
cleanup()
}
}
func TestImportedModules(t *testing.T) {
tests := []struct {
name string
modules []moduleConfig
pkgs []pkgConfig
ctxPath string
tdefQualifiedPath string
expect string
}{
{"no modules",
[]moduleConfig{},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"b", "v.io/v23/b", map[string]string{"file1.vdl": pkgBFile1}}},
"v.io/v23/a",
"v.io/v23/a.StructA",
""},
{"one module",
[]moduleConfig{moduleConfig{"VanadiumC", "v.io/v23"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"b", "v.io/v23/b", map[string]string{"file1.vdl": pkgBFile1}}},
"v.io/v23/a",
"v.io/v23/a.StructA",
""},
{"path not in module",
[]moduleConfig{moduleConfig{"VanadiumS", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/a",
"v.io/v23/a.StructA",
""},
{"path in module",
[]moduleConfig{moduleConfig{"VanadiumS", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/services/c",
"v.io/v23/services/c.SelfContainedStructC",
""},
{"cross module union field",
[]moduleConfig{moduleConfig{"VanadiumC", "v.io/v23"}, moduleConfig{"VanadiumS", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/services/c",
"v.io/v23/services/c.UnionC",
"VanadiumC"},
{"cross module struct field",
[]moduleConfig{moduleConfig{"VanadiumC", "v.io/v23"}, moduleConfig{"VanadiumS", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/services/c",
"v.io/v23/services/c.StructC",
"VanadiumC"},
{"cross module map key",
[]moduleConfig{moduleConfig{"VanadiumC", "v.io/v23"}, moduleConfig{"VanadiumS", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/services/c",
"v.io/v23/services/c.MapCKey",
"VanadiumC"},
{"cross module map value",
[]moduleConfig{moduleConfig{"VanadiumC", "v.io/v23"}, moduleConfig{"VanadiumS", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/services/c",
"v.io/v23/services/c.MapCValue",
"VanadiumC"},
{"cross module array",
[]moduleConfig{moduleConfig{"VanadiumC", "v.io/v23"}, moduleConfig{"VanadiumS", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/services/c",
"v.io/v23/services/c.ArrayC",
"VanadiumC"},
{"cross module list",
[]moduleConfig{moduleConfig{"VanadiumC", "v.io/v23"}, moduleConfig{"VanadiumS", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/services/c",
"v.io/v23/services/c.ListC",
"VanadiumC"},
{"cross module set",
[]moduleConfig{moduleConfig{"VanadiumC", "v.io/v23"}, moduleConfig{"VanadiumS", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/services/c",
"v.io/v23/services/c.SetC",
"VanadiumC"},
{"non-cross module",
[]moduleConfig{moduleConfig{"VanadiumC", "v.io/v23"}, moduleConfig{"VanadiumS", "v.io/v23/services"}},
[]pkgConfig{pkgConfig{"a", "v.io/v23/a", map[string]string{"file1.vdl": pkgAFile1}},
pkgConfig{"c", "v.io/v23/services/c", map[string]string{"file1.vdl": pkgCFile1}}},
"v.io/v23/services/c",
"v.io/v23/services/c.SelfContainedStructC",
"VanadiumC"},
}
for _, test := range tests {
pkgs, ctx, cleanup := createPackagesAndSwiftContext(t, test.modules, test.pkgs, test.ctxPath)
// Find test tdef
var testTdef *compile.TypeDef = nil
for _, pkg := range pkgs {
if strings.HasPrefix(test.tdefQualifiedPath, pkg.Path) {
for _, tdef := range pkg.TypeDefs() {
if strings.HasSuffix(test.tdefQualifiedPath, tdef.Name) {
testTdef = tdef
break
}
}
for _, cdef := range pkg.ConstDefs() {
tdef := ctx.env.FindTypeDef(cdef.Value.Type())
if strings.HasSuffix(test.tdefQualifiedPath, cdef.Name) {
testTdef = tdef
break
}
}
break
}
}
if testTdef == nil {
cleanup()
t.Errorf("Could not find tdef %v", test.tdefQualifiedPath)
return
}
got := ctx.importedModules([]*compile.TypeDef{testTdef})
// Remove VanadiumCore if we aren't expecting it as it's always imported
foundCore := false
gotModule := ""
for _, module := range got {
if module == V23SwiftFrameworkName {
foundCore = true
} else {
gotModule = module
}
}
if !foundCore {
t.Errorf("Couldn't find expected core import %v", V23SwiftFrameworkName)
}
if gotModule != test.expect {
t.Errorf("%s\n GOT %v\nWANT %v", test.name, gotModule, test.expect)
}
cleanup()
}
}