blob: 03cab48039f9e839b1eab77d178797869234f63d [file] [log] [blame]
// Copyright 2016 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 main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"v.io/x/lib/gosh"
)
func TestMain(m *testing.M) {
gosh.InitMain()
os.Exit(m.Run())
}
func tempFilename(t *testing.T) string {
f, err := ioutil.TempFile("", "madb_test")
if err != nil {
t.Fatalf("could not open a temp file: %v", err)
}
f.Close()
return f.Name()
}
func TestParseDevicesOutput(t *testing.T) {
var output string
// Normal case
output = `List of devices attached
deviceid01 device usb:3-3.4.3 product:bullhead model:Nexus_5X device:bullhead
emulator-5554 device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic
`
got, err := parseDevicesOutput(output, nil)
if err != nil {
t.Fatalf("failed to parse the output: %v", err)
}
want := []device{
device{
Serial: "deviceid01",
Type: realDevice,
Qualifiers: []string{"usb:3-3.4.3", "product:bullhead", "model:Nexus_5X", "device:bullhead"},
Nickname: "",
Index: 1,
UserID: "",
},
device{
Serial: "emulator-5554",
Type: emulator,
Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
Nickname: "",
Index: 2,
UserID: "",
},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unmatched results: got %v, want %v", got, want)
}
// No devices at all
output = `List of devices attached
`
got, err = parseDevicesOutput(output, nil)
if err != nil {
t.Fatalf("failed to parse the output: %v", err)
}
if want = []device{}; !reflect.DeepEqual(got, want) {
t.Fatalf("unmatched results: got %v, want %v", got, want)
}
// Offline devices should be excluded
output = `List of devices attached
deviceid01 offline usb:3-3.4.3 product:bullhead model:Nexus_5X device:bullhead
deviceid02 device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic
`
got, err = parseDevicesOutput(output, nil)
if err != nil {
t.Fatalf("failed to parse the output: %v", err)
}
want = []device{
device{
Serial: "deviceid02",
Type: realDevice,
Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
Nickname: "",
Index: 2,
UserID: "",
},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unmatched results: got %v, want %v", got, want)
}
// In case some nicknames are defined.
output = `List of devices attached
deviceid01 device usb:3-3.4.3 product:bullhead model:Nexus_5X device:bullhead
emulator-5554 device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic
`
cfg := &config{
Names: map[string]string{
"MyPhone": "deviceid01",
"ARMv7": "model:sdk_phone_armv7",
},
UserIDs: map[string]string{
"deviceid01": "10",
},
}
got, err = parseDevicesOutput(output, cfg)
if err != nil {
t.Fatalf("failed to parse the output: %v", err)
}
want = []device{
device{
Serial: "deviceid01",
Type: realDevice,
Qualifiers: []string{"usb:3-3.4.3", "product:bullhead", "model:Nexus_5X", "device:bullhead"},
Nickname: "MyPhone",
Index: 1,
UserID: "10",
},
device{
Serial: "emulator-5554",
Type: emulator,
Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
Nickname: "ARMv7",
Index: 2,
UserID: "",
},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unmatched results: got %v, want %v", got, want)
}
}
func TestGetSpecifiedDevices(t *testing.T) {
// First, define some devices (three real devices, and two emulators).
d1 := device{
Serial: "deviceid01",
Type: realDevice,
Qualifiers: []string{"usb:3-3.4.3", "product:bullhead", "model:Nexus_5X", "device:bullhead"},
Nickname: "MyPhone",
Index: 1,
UserID: "",
}
d2 := device{
Serial: "deviceid02",
Type: realDevice,
Qualifiers: []string{"usb:3-3.4.1", "product:volantisg", "model:Nexus_9", "device:flounder_lte"},
Nickname: "",
Index: 2,
UserID: "",
}
e1 := device{
Serial: "emulator-5554",
Type: emulator,
Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
Nickname: "ARMv7",
Index: 3,
UserID: "",
}
d3 := device{
Serial: "deviceid03",
Type: realDevice,
Qualifiers: []string{"usb:3-3.3", "product:bullhead", "model:Nexus_5X", "device:bullhead"},
Nickname: "SecondPhone",
Index: 4,
UserID: "",
}
e2 := device{
Serial: "emulator-5555",
Type: emulator,
Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
Nickname: "",
Index: 5,
UserID: "",
}
allDevices := []device{d1, d2, e1, d3, e2}
type deviceFlags struct {
allDevices bool
allEmulators bool
devices string
}
tests := []struct {
flags deviceFlags
want []device
}{
{deviceFlags{false, false, ""}, allDevices}, // Nothing is specified
{deviceFlags{true, true, ""}, allDevices}, // Both -d and -e are specified
{deviceFlags{true, false, ""}, []device{d1, d2, d3}}, // Only -d is specified
{deviceFlags{false, true, ""}, []device{e1, e2}}, // Only -e is specified
{deviceFlags{false, false, "device:bullhead"}, []device{d1, d3}}, // Device qualifier
{deviceFlags{false, false, "ARMv7,SecondPhone"}, []device{e1, d3}}, // Nicknames
{deviceFlags{false, false, "@2,@4"}, []device{d2, d3}}, // Device Indices
{deviceFlags{false, false, "NormalGroup"}, []device{d1, d2, e1}}, // Normal group
{deviceFlags{false, false, "SelfRefGroup"}, []device{d2}}, // Self referencing group
{deviceFlags{false, false, "CyclicGroup1"}, []device{d1, d2, d3}}, // Cyclic group inclusion
{deviceFlags{true, false, "ARMv7"}, []device{d1, d2, e1, d3}}, // Combinations
{deviceFlags{false, true, "model:Nexus_9"}, []device{d2, e1, e2}}, // Combinations
{deviceFlags{false, false, "@1,SecondPhone"}, []device{d1, d3}}, // Combinations
{deviceFlags{false, false, "SecondPhone,NormalGroup,@1"}, []device{d1, d2, e1, d3}}, // Combinations
}
cfg := &config{
Groups: map[string][]string{
"NormalGroup": []string{"deviceid01", "deviceid02", "@3"},
"SelfRefGroup": []string{"deviceid02", "SelfRefGroup"},
"CyclicGroup1": []string{"CyclicGroup2", "@1"},
"CyclicGroup2": []string{"@2", "CyclicGroup3"},
"CyclicGroup3": []string{"deviceid03", "CyclicGroup1"},
},
}
for i, test := range tests {
tokens := strings.Split(test.flags.devices, ",")
got, err := filterSpecifiedDevices(allDevices, cfg, test.flags.allDevices, test.flags.allEmulators, tokens)
if err != nil {
t.Fatalf(err.Error())
}
if !reflect.DeepEqual(got, test.want) {
t.Fatalf("unmatched results for testCases[%v]: got %v, want %v", i, got, test.want)
}
}
}
func TestIsFlutterProject(t *testing.T) {
testCases := []struct {
projectDir string
want bool
}{
{"testMultiPlatform", false},
{"testMultiPlatform/android", false},
{"testMultiPlatform/android/app", false},
{"testMultiPlatform/flutter", true},
}
for i, testCase := range testCases {
dir := filepath.Join("testdata", "projects", testCase.projectDir)
if got := isFlutterProject(dir); got != testCase.want {
t.Fatalf("unmatched results for testCases[%v]: got %v, want %v", i, got, testCase.want)
}
}
}
func TestIsGradleProject(t *testing.T) {
testCases := []struct {
projectDir string
want bool
}{
{"testMultiPlatform", false},
{"testMultiPlatform/android", true},
{"testMultiPlatform/android/app", true},
{"testMultiPlatform/flutter", false},
}
for i, testCase := range testCases {
dir := filepath.Join("testdata", "projects", testCase.projectDir)
if got := isGradleProject(dir); got != testCase.want {
t.Fatalf("unmatched results for testCases[%v]: got %v, want %v", i, got, testCase.want)
}
}
}
func TestExtractPropertiesFromGradle(t *testing.T) {
tests := []struct {
key variantKey
want variantProperties
}{
{
variantKey{"testMultiPlatform/android", "", ""},
variantProperties{AppID: "io.v.testProjectId", Activity: "io.v.testProjectPackage.LauncherActivity"},
},
{
variantKey{"testMultiPlatform/android", "app", "debug"},
variantProperties{AppID: "io.v.testProjectId", Activity: "io.v.testProjectPackage.LauncherActivity"},
},
{
variantKey{"testMultiPlatform/android/app", "", ""},
variantProperties{AppID: "io.v.testProjectId", Activity: "io.v.testProjectPackage.LauncherActivity"},
},
{
variantKey{"testAndroidMultiFlavor", "", ""},
variantProperties{AppID: "io.v.testProjectId.lite.debug", Activity: "io.v.testProjectPackage.LauncherActivity"},
},
{
variantKey{"testAndroidMultiFlavor", "app", "liteDebug"},
variantProperties{AppID: "io.v.testProjectId.lite.debug", Activity: "io.v.testProjectPackage.LauncherActivity"},
},
{
variantKey{"testAndroidMultiFlavor/app", "", "proRelease"},
variantProperties{AppID: "io.v.testProjectId.pro", Activity: "io.v.testProjectPackage.LauncherActivity"},
},
{
variantKey{"testApplicationIdFallback", "", ""},
variantProperties{AppID: "io.v.testProjectPackage", Activity: "io.v.testProjectPackage.LauncherActivity"},
},
}
for i, test := range tests {
test.key.Dir = filepath.Join("testdata", "projects", test.key.Dir)
got, err := extractPropertiesFromGradle(test.key)
if err != nil {
t.Fatalf("error occurred while extracting properties for testCases[%v]: %v", i, err)
}
if got.AppID != test.want.AppID || got.Activity != test.want.Activity {
t.Fatalf("unmatched results for testCases[%v]: got %v, want %v", i, got, test.want)
}
}
}
func TestGetProjectProperties(t *testing.T) {
cacheFile := tempFilename(t)
defer os.Remove(cacheFile)
called := false
// See if it runs the extractor for the first time.
extractor := func(key variantKey) (variantProperties, error) {
called = true
return variantProperties{AppID: "testAppID", Activity: "Activity"}, nil
}
want := variantProperties{AppID: "testAppID", Activity: "Activity"}
got, err := getProjectProperties(extractor, variantKey{"testDir", "mod", "var"}, false, cacheFile)
if err != nil {
t.Fatalf(err.Error())
}
if !called {
t.Fatalf("extractor was not called when expected to be called.")
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unmatched results: got %v, want %v", got, want)
}
// The second run should not invoke the extractor.
called = false
got, err = getProjectProperties(extractor, variantKey{"testDir", "mod", "var"}, false, cacheFile)
if err != nil {
t.Fatalf(err.Error())
}
if called {
t.Fatalf("extracted was called when not expected.")
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unmatched results: got %v, want %v", got, want)
}
// Run with clear cache flag.
called = false
got, err = getProjectProperties(extractor, variantKey{"testDir", "mod", "var"}, true, cacheFile)
if err != nil {
t.Fatalf(err.Error())
}
if !called {
t.Fatalf("extractor was not called when expected to be called.")
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unmatched results: got %v, want %v", got, want)
}
}
// TestEmbeddedGradleScript tests whether the gradle script defined in embedded_gradle.go matches
// the madb_init.gradle file.
func TestEmbeddedGradleScript(t *testing.T) {
f, err := os.Open("madb_init.gradle")
if err != nil {
t.Fatal(err)
}
defer f.Close()
bytes, err := ioutil.ReadAll(f)
if err != nil {
t.Fatal(err)
}
if string(bytes) != gradleInitScript {
t.Fatalf(`The embedded Gradle script is out of date. Please run "jiri go generate" to regenerate the embedded script.`)
}
}
// TestExpandKeywords tests the "expandKeywords" function, which is used for expanding pre-defined
// keywords such as "{{serial}}" and "{{name}}".
func TestExpandKeywords(t *testing.T) {
// Sample devices.
d1 := device{
Serial: "0123456789",
Type: realDevice,
Qualifiers: nil,
Nickname: "Alice",
Index: 1,
UserID: "10",
}
d2 := device{
Serial: "emulator-1234",
Type: emulator,
Qualifiers: nil,
Nickname: "",
Index: 2,
UserID: "",
}
tests := []struct {
arg string
d device
want string
}{
{"{{name}}.txt", d1, "Alice.txt"},
{"{{serial}}.txt", d1, "0123456789.txt"},
{"{{index}}.txt", d1, "1.txt"},
{"Hello, {{name}}!", d2, "Hello, emulator-1234!"},
{"Hello, {{serial}}!", d2, "Hello, emulator-1234!"},
{"{{index}}.txt", d2, "2.txt"},
{"{{name}}-{{serial}}.txt", d1, "Alice-0123456789.txt"},
}
for i, test := range tests {
if got := expandKeywords(test.arg, test.d); got != test.want {
t.Fatalf("unmatched results for tests[%v]: got %v, want %v", i, got, test.want)
}
}
}
var helloFunc = gosh.RegisterFunc("helloFunc", func() {
fmt.Println("Hello, World!")
})
func TestOutputPrefix(t *testing.T) {
// Sample device.
d1 := device{
Serial: "deviceid01",
Type: realDevice,
Qualifiers: nil,
Nickname: "Alice",
Index: 1,
UserID: "",
}
d2 := device{
Serial: "deviceid02",
Type: realDevice,
Qualifiers: nil,
Nickname: "Bob",
Index: 2,
UserID: "10",
}
d3 := device{
Serial: "deviceid03",
Type: realDevice,
Qualifiers: nil,
Nickname: "",
Index: 3,
UserID: "",
}
sh := gosh.NewShell(nil)
defer sh.Cleanup()
tests := []struct {
prefixType string
d device
want string
}{
{"name", d1, "[Alice]\tHello, World!\n"},
{"name", d2, "[Bob:10]\tHello, World!\n"},
{"name", d3, "[deviceid03]\tHello, World!\n"},
{"serial", d1, "[deviceid01]\tHello, World!\n"},
{"serial", d2, "[deviceid02:10]\tHello, World!\n"},
{"serial", d3, "[deviceid03]\tHello, World!\n"},
{"none", d1, "Hello, World!\n"},
{"none", d2, "Hello, World!\n"},
{"none", d3, "Hello, World!\n"},
}
for i, test := range tests {
var b1, b2 bytes.Buffer
helloCmd := sh.FuncCmd(helloFunc)
prefixFlag = test.prefixType
if err := runGoshCommandForDeviceWithWriters(helloCmd, test.d, true, &b1, &b2); err != nil {
t.Fatalf("error occurred while running gosh command: %v", err)
}
if got, want := b1.String(), test.want; got != want {
t.Fatalf("unmatched results for tests[%v]: got %v, want %v", i, got, want)
}
if b2.String() != "" {
t.Fatalf("unexpected output to stderr for tests[%v]: %v", i, b2.String())
}
}
}
func TestIsValidSerial(t *testing.T) {
tests := []struct {
input string
want bool
}{
// The following strings should be accepted
{"HT4BVWV00023", true},
{"01023f5e2fd2accf", true},
{"usb:3-3.4.2", true},
{"product:volantisg", true},
{"model:Nexus_9", true},
{"device:flounder_lte", true},
{"@1", true},
{"@2", true},
// The following strings should not be accepted
{"have spaces", false},
{"@abcd", false},
{"#not_allowed_chars~", false},
}
for _, test := range tests {
if got := isValidSerial(test.input); got != test.want {
t.Fatalf("unmatched results for serial '%v': got %v, want %v", test.input, got, test.want)
}
}
}
func TestIsValidName(t *testing.T) {
tests := []struct {
input string
want bool
}{
// The following strings should be accepted
{"Nexus5X", true},
{"Nexus9", true},
{"P1", true},
{"P2", true},
{"Tablet", true},
// The following strings should not be accepted
{"have spaces", false},
{"@1", false},
{"@abcd", false},
{"#not_allowed_chars~", false},
}
for _, test := range tests {
if got := isValidName(test.input); got != test.want {
t.Fatalf("unmatched results for nickname '%v': got %v, want %v", test.input, got, test.want)
}
}
}
func TestConfigMigration(t *testing.T) {
tests := []struct {
configDir string
fileMap map[string]string
want config
}{
{
"testdata/configs/newFormat",
map[string]string{"config": "config"},
config{
Version: "v2.0.0",
Names: map[string]string{"nickname01": "serial01", "nickname02": "serial02"},
Groups: map[string][]string{},
UserIDs: map[string]string{"serial01": "10"},
},
},
{
"testdata/configs/oldFormatBoth",
map[string]string{"": "config", "nicknames": "nicknames.bak", "users": "users.bak"},
config{
Version: version,
Names: map[string]string{"nickname01": "serial01", "nickname02": "serial02"},
Groups: map[string][]string{},
UserIDs: map[string]string{"serial01": "10"},
},
},
{
"testdata/configs/oldFormatNicknamesOnly",
map[string]string{"": "config", "nicknames": "nicknames.bak"},
config{
Version: version,
Names: map[string]string{"nickname01": "serial01", "nickname02": "serial02"},
Groups: map[string][]string{},
UserIDs: map[string]string{},
},
},
{
"testdata/configs/oldFormatUsersOnly",
map[string]string{"": "config", "users": "users.bak"},
config{
Version: version,
Names: map[string]string{},
Groups: map[string][]string{},
UserIDs: map[string]string{"serial01": "10"},
},
},
}
for i, test := range tests {
// Copy the files in configDir to a temporary directory.
tempConfigDir, err := ioutil.TempDir("", "madbConfigTest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempConfigDir)
files, err := ioutil.ReadDir(test.configDir)
if err != nil {
t.Fatal(err)
}
for _, file := range files {
src, err := os.Open(filepath.Join(test.configDir, file.Name()))
if err != nil {
t.Fatal(err)
}
dst, err := os.Create(filepath.Join(tempConfigDir, file.Name()))
if err != nil {
t.Fatal(err)
}
_, err = io.Copy(dst, src)
if err != nil {
t.Fatal(err)
}
src.Close()
dst.Close()
}
// Run the migration.
migrateOldConfigFiles(tempConfigDir)
// Check the resulting config
cfg, err := readConfig(filepath.Join(tempConfigDir, "config"))
if err != nil {
t.Fatal(err)
}
if got, want := *cfg, test.want; !reflect.DeepEqual(got, want) {
t.Fatalf("unmatched results for tests[%v]: got %v, want %v", i, got, want)
}
// Check the file mapping.
for oldFile, newFile := range test.fileMap {
if oldFile == "" {
if _, err := os.Stat(filepath.Join(tempConfigDir, newFile)); os.IsNotExist(err) {
t.Fatalf("missing an expected config file %q", newFile)
}
continue
}
oldBytes, err := ioutil.ReadFile(filepath.Join(test.configDir, oldFile))
if err != nil {
t.Fatal(err)
}
newBytes, err := ioutil.ReadFile(filepath.Join(tempConfigDir, newFile))
if err != nil {
t.Fatal(err)
}
if bytes.Compare(oldBytes, newBytes) != 0 {
t.Fatalf("unmatched file contents between %q and %q.", oldFile, newFile)
}
}
}
}