457 lines
11 KiB
Go
457 lines
11 KiB
Go
|
// Copyright 2017 Google Inc. All rights reserved.
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package python
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"reflect"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
|
||
|
"android/soong/android"
|
||
|
|
||
|
"github.com/google/blueprint"
|
||
|
)
|
||
|
|
||
|
type pyBinary struct {
|
||
|
name string
|
||
|
actualVersion string
|
||
|
pyRunfiles []string
|
||
|
depsPyRunfiles []string
|
||
|
parSpec string
|
||
|
depsParSpecs []string
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
buildNamePrefix = "soong_python_test"
|
||
|
moduleVariantErrTemplate = "%s: module %q variant %q: "
|
||
|
pkgPathErrTemplate = moduleVariantErrTemplate +
|
||
|
"pkg_path: %q is not a valid format."
|
||
|
badIdentifierErrTemplate = moduleVariantErrTemplate +
|
||
|
"srcs: the path %q contains invalid token %q."
|
||
|
dupRunfileErrTemplate = moduleVariantErrTemplate +
|
||
|
"found two files to be placed at the same runfiles location %q." +
|
||
|
" First file: in module %s at path %q." +
|
||
|
" Second file: in module %s at path %q."
|
||
|
noSrcFileErr = moduleVariantErrTemplate + "doesn't have any source files!"
|
||
|
badSrcFileExtErr = moduleVariantErrTemplate + "srcs: found non (.py) file: %q!"
|
||
|
badDataFileExtErr = moduleVariantErrTemplate + "data: found (.py) file: %q!"
|
||
|
bpFile = "Blueprints"
|
||
|
|
||
|
data = []struct {
|
||
|
desc string
|
||
|
mockFiles map[string][]byte
|
||
|
|
||
|
errors []string
|
||
|
expectedBinaries []pyBinary
|
||
|
}{
|
||
|
{
|
||
|
desc: "module without any src files",
|
||
|
mockFiles: map[string][]byte{
|
||
|
bpFile: []byte(`subdirs = ["dir"]`),
|
||
|
filepath.Join("dir", bpFile): []byte(
|
||
|
`python_library_host {
|
||
|
name: "lib1",
|
||
|
}`,
|
||
|
),
|
||
|
},
|
||
|
errors: []string{
|
||
|
fmt.Sprintf(noSrcFileErr,
|
||
|
"dir/Blueprints:1:1", "lib1", "PY3"),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
desc: "module with bad src file ext",
|
||
|
mockFiles: map[string][]byte{
|
||
|
bpFile: []byte(`subdirs = ["dir"]`),
|
||
|
filepath.Join("dir", bpFile): []byte(
|
||
|
`python_library_host {
|
||
|
name: "lib1",
|
||
|
srcs: [
|
||
|
"file1.exe",
|
||
|
],
|
||
|
}`,
|
||
|
),
|
||
|
"dir/file1.exe": nil,
|
||
|
},
|
||
|
errors: []string{
|
||
|
fmt.Sprintf(badSrcFileExtErr,
|
||
|
"dir/Blueprints:3:11", "lib1", "PY3", "dir/file1.exe"),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
desc: "module with bad data file ext",
|
||
|
mockFiles: map[string][]byte{
|
||
|
bpFile: []byte(`subdirs = ["dir"]`),
|
||
|
filepath.Join("dir", bpFile): []byte(
|
||
|
`python_library_host {
|
||
|
name: "lib1",
|
||
|
srcs: [
|
||
|
"file1.py",
|
||
|
],
|
||
|
data: [
|
||
|
"file2.py",
|
||
|
],
|
||
|
}`,
|
||
|
),
|
||
|
"dir/file1.py": nil,
|
||
|
"dir/file2.py": nil,
|
||
|
},
|
||
|
errors: []string{
|
||
|
fmt.Sprintf(badDataFileExtErr,
|
||
|
"dir/Blueprints:6:11", "lib1", "PY3", "dir/file2.py"),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
desc: "module with bad pkg_path format",
|
||
|
mockFiles: map[string][]byte{
|
||
|
bpFile: []byte(`subdirs = ["dir"]`),
|
||
|
filepath.Join("dir", bpFile): []byte(
|
||
|
`python_library_host {
|
||
|
name: "lib1",
|
||
|
pkg_path: "a/c/../../",
|
||
|
srcs: [
|
||
|
"file1.py",
|
||
|
],
|
||
|
}
|
||
|
|
||
|
python_library_host {
|
||
|
name: "lib2",
|
||
|
pkg_path: "a/c/../../../",
|
||
|
srcs: [
|
||
|
"file1.py",
|
||
|
],
|
||
|
}
|
||
|
|
||
|
python_library_host {
|
||
|
name: "lib3",
|
||
|
pkg_path: "/a/c/../../",
|
||
|
srcs: [
|
||
|
"file1.py",
|
||
|
],
|
||
|
}`,
|
||
|
),
|
||
|
"dir/file1.py": nil,
|
||
|
},
|
||
|
errors: []string{
|
||
|
fmt.Sprintf(pkgPathErrTemplate,
|
||
|
"dir/Blueprints:11:15", "lib2", "PY3", "a/c/../../../"),
|
||
|
fmt.Sprintf(pkgPathErrTemplate,
|
||
|
"dir/Blueprints:19:15", "lib3", "PY3", "/a/c/../../"),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
desc: "module with bad runfile src path format",
|
||
|
mockFiles: map[string][]byte{
|
||
|
bpFile: []byte(`subdirs = ["dir"]`),
|
||
|
filepath.Join("dir", bpFile): []byte(
|
||
|
`python_library_host {
|
||
|
name: "lib1",
|
||
|
pkg_path: "a/b/c/",
|
||
|
srcs: [
|
||
|
".file1.py",
|
||
|
"123/file1.py",
|
||
|
"-e/f/file1.py",
|
||
|
],
|
||
|
}`,
|
||
|
),
|
||
|
"dir/.file1.py": nil,
|
||
|
"dir/123/file1.py": nil,
|
||
|
"dir/-e/f/file1.py": nil,
|
||
|
},
|
||
|
errors: []string{
|
||
|
fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
|
||
|
"lib1", "PY3", "runfiles/a/b/c/-e/f/file1.py", "-e"),
|
||
|
fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
|
||
|
"lib1", "PY3", "runfiles/a/b/c/.file1.py", ".file1"),
|
||
|
fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
|
||
|
"lib1", "PY3", "runfiles/a/b/c/123/file1.py", "123"),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
desc: "module with duplicate runfile path",
|
||
|
mockFiles: map[string][]byte{
|
||
|
bpFile: []byte(`subdirs = ["dir"]`),
|
||
|
filepath.Join("dir", bpFile): []byte(
|
||
|
`python_library_host {
|
||
|
name: "lib1",
|
||
|
pkg_path: "a/b/",
|
||
|
srcs: [
|
||
|
"c/file1.py",
|
||
|
],
|
||
|
}
|
||
|
|
||
|
python_library_host {
|
||
|
name: "lib2",
|
||
|
pkg_path: "a/b/c/",
|
||
|
srcs: [
|
||
|
"file1.py",
|
||
|
],
|
||
|
libs: [
|
||
|
"lib1",
|
||
|
],
|
||
|
}
|
||
|
`,
|
||
|
),
|
||
|
"dir/c/file1.py": nil,
|
||
|
"dir/file1.py": nil,
|
||
|
},
|
||
|
errors: []string{
|
||
|
fmt.Sprintf(dupRunfileErrTemplate, "dir/Blueprints:9:6",
|
||
|
"lib2", "PY3", "runfiles/a/b/c/file1.py", "lib2", "dir/file1.py",
|
||
|
"lib1", "dir/c/file1.py"),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
desc: "module for testing dependencies",
|
||
|
mockFiles: map[string][]byte{
|
||
|
bpFile: []byte(`subdirs = ["dir"]`),
|
||
|
filepath.Join("dir", bpFile): []byte(
|
||
|
`python_library_host {
|
||
|
name: "lib5",
|
||
|
pkg_path: "a/b/",
|
||
|
srcs: [
|
||
|
"file1.py",
|
||
|
],
|
||
|
version: {
|
||
|
py2: {
|
||
|
enabled: true,
|
||
|
},
|
||
|
py3: {
|
||
|
enabled: true,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
python_library_host {
|
||
|
name: "lib6",
|
||
|
pkg_path: "c/d/",
|
||
|
srcs: [
|
||
|
"file2.py",
|
||
|
],
|
||
|
libs: [
|
||
|
"lib5",
|
||
|
],
|
||
|
}
|
||
|
|
||
|
python_binary_host {
|
||
|
name: "bin",
|
||
|
pkg_path: "e/",
|
||
|
srcs: [
|
||
|
"bin.py",
|
||
|
],
|
||
|
libs: [
|
||
|
"lib5",
|
||
|
],
|
||
|
version: {
|
||
|
py3: {
|
||
|
enabled: true,
|
||
|
srcs: [
|
||
|
"file4.py",
|
||
|
],
|
||
|
libs: [
|
||
|
"lib6",
|
||
|
],
|
||
|
},
|
||
|
},
|
||
|
}`,
|
||
|
),
|
||
|
filepath.Join("dir", "file1.py"): nil,
|
||
|
filepath.Join("dir", "file2.py"): nil,
|
||
|
filepath.Join("dir", "bin.py"): nil,
|
||
|
filepath.Join("dir", "file4.py"): nil,
|
||
|
stubTemplateHost: []byte(`PYTHON_BINARY = '%interpreter%'
|
||
|
MAIN_FILE = '%main%'`),
|
||
|
},
|
||
|
expectedBinaries: []pyBinary{
|
||
|
{
|
||
|
name: "bin",
|
||
|
actualVersion: "PY3",
|
||
|
pyRunfiles: []string{
|
||
|
"runfiles/e/bin.py",
|
||
|
"runfiles/e/file4.py",
|
||
|
},
|
||
|
depsPyRunfiles: []string{
|
||
|
"runfiles/a/b/file1.py",
|
||
|
"runfiles/c/d/file2.py",
|
||
|
},
|
||
|
parSpec: "-P runfiles/e -C dir/ -l @prefix@/.intermediates/dir/bin/PY3/dir_.list",
|
||
|
depsParSpecs: []string{
|
||
|
"-P runfiles/a/b -C dir/ -l @prefix@/.intermediates/dir/lib5/PY3/dir_.list",
|
||
|
"-P runfiles/c/d -C dir/ -l @prefix@/.intermediates/dir/lib6/PY3/dir_.list",
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
)
|
||
|
|
||
|
func TestPythonModule(t *testing.T) {
|
||
|
config, buildDir := setupBuildEnv(t)
|
||
|
defer tearDownBuildEnv()
|
||
|
android.TestPreDepsMutators(func(ctx android.RegisterMutatorsContext) {
|
||
|
ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
|
||
|
})
|
||
|
for _, d := range data {
|
||
|
t.Run(d.desc, func(t *testing.T) {
|
||
|
ctx := blueprint.NewContext()
|
||
|
android.RegisterTestMutators(ctx)
|
||
|
ctx.RegisterModuleType("python_library_host", PythonLibraryHostFactory)
|
||
|
ctx.RegisterModuleType("python_binary_host", PythonBinaryHostFactory)
|
||
|
ctx.MockFileSystem(d.mockFiles)
|
||
|
_, testErrs := ctx.ParseBlueprintsFiles(bpFile)
|
||
|
fail(t, testErrs)
|
||
|
_, actErrs := ctx.PrepareBuildActions(config)
|
||
|
if len(actErrs) > 0 {
|
||
|
testErrs = append(testErrs, expectErrors(t, actErrs, d.errors)...)
|
||
|
} else {
|
||
|
for _, e := range d.expectedBinaries {
|
||
|
testErrs = append(testErrs,
|
||
|
expectModule(t, ctx, buildDir, e.name,
|
||
|
e.actualVersion,
|
||
|
e.pyRunfiles, e.depsPyRunfiles,
|
||
|
e.parSpec, e.depsParSpecs)...)
|
||
|
}
|
||
|
}
|
||
|
fail(t, testErrs)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func expectErrors(t *testing.T, actErrs []error, expErrs []string) (testErrs []error) {
|
||
|
actErrStrs := []string{}
|
||
|
for _, v := range actErrs {
|
||
|
actErrStrs = append(actErrStrs, v.Error())
|
||
|
}
|
||
|
sort.Strings(actErrStrs)
|
||
|
if len(actErrStrs) != len(expErrs) {
|
||
|
t.Errorf("got (%d) errors, expected (%d) errors!", len(actErrStrs), len(expErrs))
|
||
|
for _, v := range actErrStrs {
|
||
|
testErrs = append(testErrs, errors.New(v))
|
||
|
}
|
||
|
} else {
|
||
|
sort.Strings(expErrs)
|
||
|
for i, v := range actErrStrs {
|
||
|
if v != expErrs[i] {
|
||
|
testErrs = append(testErrs, errors.New(v))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func expectModule(t *testing.T, ctx *blueprint.Context, buildDir, name, variant string,
|
||
|
expPyRunfiles, expDepsPyRunfiles []string,
|
||
|
expParSpec string, expDepsParSpecs []string) (testErrs []error) {
|
||
|
module := findModule(ctx, name, variant)
|
||
|
if module == nil {
|
||
|
t.Fatalf("failed to find module %s!", name)
|
||
|
}
|
||
|
|
||
|
base, baseOk := module.(*pythonBaseModule)
|
||
|
if !baseOk {
|
||
|
t.Fatalf("%s is not Python module!", name)
|
||
|
}
|
||
|
sub, subOk := base.subModule.(*PythonBinary)
|
||
|
if !subOk {
|
||
|
t.Fatalf("%s is not Python binary!", name)
|
||
|
}
|
||
|
|
||
|
actPyRunfiles := []string{}
|
||
|
for _, path := range base.srcsPathMappings {
|
||
|
actPyRunfiles = append(actPyRunfiles, path.dest)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(actPyRunfiles, expPyRunfiles) {
|
||
|
testErrs = append(testErrs, errors.New(fmt.Sprintf(
|
||
|
`binary "%s" variant "%s" has unexpected pyRunfiles: %q!`,
|
||
|
base.Name(),
|
||
|
base.properties.ActualVersion,
|
||
|
actPyRunfiles)))
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(sub.depsPyRunfiles, expDepsPyRunfiles) {
|
||
|
testErrs = append(testErrs, errors.New(fmt.Sprintf(
|
||
|
`binary "%s" variant "%s" has unexpected depsPyRunfiles: %q!`,
|
||
|
base.Name(),
|
||
|
base.properties.ActualVersion,
|
||
|
sub.depsPyRunfiles)))
|
||
|
}
|
||
|
|
||
|
if base.parSpec.soongParArgs() != strings.Replace(expParSpec, "@prefix@", buildDir, 1) {
|
||
|
testErrs = append(testErrs, errors.New(fmt.Sprintf(
|
||
|
`binary "%s" variant "%s" has unexpected parSpec: %q!`,
|
||
|
base.Name(),
|
||
|
base.properties.ActualVersion,
|
||
|
base.parSpec.soongParArgs())))
|
||
|
}
|
||
|
|
||
|
actDepsParSpecs := []string{}
|
||
|
for i, p := range sub.depsParSpecs {
|
||
|
actDepsParSpecs = append(actDepsParSpecs, p.soongParArgs())
|
||
|
expDepsParSpecs[i] = strings.Replace(expDepsParSpecs[i], "@prefix@", buildDir, 1)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(actDepsParSpecs, expDepsParSpecs) {
|
||
|
testErrs = append(testErrs, errors.New(fmt.Sprintf(
|
||
|
`binary "%s" variant "%s" has unexpected depsParSpecs: %q!`,
|
||
|
base.Name(),
|
||
|
base.properties.ActualVersion,
|
||
|
actDepsParSpecs)))
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func setupBuildEnv(t *testing.T) (config android.Config, buildDir string) {
|
||
|
buildDir, err := ioutil.TempDir("", buildNamePrefix)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
config = android.TestConfig(buildDir)
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func tearDownBuildEnv() {
|
||
|
os.RemoveAll(buildNamePrefix)
|
||
|
}
|
||
|
|
||
|
func findModule(ctx *blueprint.Context, name, variant string) blueprint.Module {
|
||
|
var ret blueprint.Module
|
||
|
ctx.VisitAllModules(func(m blueprint.Module) {
|
||
|
if ctx.ModuleName(m) == name && ctx.ModuleSubDir(m) == variant {
|
||
|
ret = m
|
||
|
}
|
||
|
})
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func fail(t *testing.T, errs []error) {
|
||
|
if len(errs) > 0 {
|
||
|
for _, err := range errs {
|
||
|
t.Error(err)
|
||
|
}
|
||
|
t.FailNow()
|
||
|
}
|
||
|
}
|