Home | History | Annotate | Download | only in python
      1 // Copyright 2017 Google Inc. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package python
     16 
     17 import (
     18 	"errors"
     19 	"fmt"
     20 	"io/ioutil"
     21 	"os"
     22 	"path/filepath"
     23 	"reflect"
     24 	"sort"
     25 	"strings"
     26 	"testing"
     27 
     28 	"android/soong/android"
     29 )
     30 
     31 type pyModule struct {
     32 	name           string
     33 	actualVersion  string
     34 	pyRunfiles     []string
     35 	depsPyRunfiles []string
     36 	parSpec        string
     37 	depsParSpecs   []string
     38 }
     39 
     40 var (
     41 	buildNamePrefix          = "soong_python_test"
     42 	moduleVariantErrTemplate = "%s: module %q variant %q: "
     43 	pkgPathErrTemplate       = moduleVariantErrTemplate +
     44 		"pkg_path: %q must be a relative path contained in par file."
     45 	badIdentifierErrTemplate = moduleVariantErrTemplate +
     46 		"srcs: the path %q contains invalid token %q."
     47 	dupRunfileErrTemplate = moduleVariantErrTemplate +
     48 		"found two files to be placed at the same runfiles location %q." +
     49 		" First file: in module %s at path %q." +
     50 		" Second file: in module %s at path %q."
     51 	noSrcFileErr      = moduleVariantErrTemplate + "doesn't have any source files!"
     52 	badSrcFileExtErr  = moduleVariantErrTemplate + "srcs: found non (.py) file: %q!"
     53 	badDataFileExtErr = moduleVariantErrTemplate + "data: found (.py) file: %q!"
     54 	bpFile            = "Blueprints"
     55 
     56 	data = []struct {
     57 		desc      string
     58 		mockFiles map[string][]byte
     59 
     60 		errors           []string
     61 		expectedBinaries []pyModule
     62 	}{
     63 		{
     64 			desc: "module without any src files",
     65 			mockFiles: map[string][]byte{
     66 				bpFile: []byte(`subdirs = ["dir"]`),
     67 				filepath.Join("dir", bpFile): []byte(
     68 					`python_library_host {
     69 						name: "lib1",
     70 					}`,
     71 				),
     72 			},
     73 			errors: []string{
     74 				fmt.Sprintf(noSrcFileErr,
     75 					"dir/Blueprints:1:1", "lib1", "PY3"),
     76 			},
     77 		},
     78 		{
     79 			desc: "module with bad src file ext",
     80 			mockFiles: map[string][]byte{
     81 				bpFile: []byte(`subdirs = ["dir"]`),
     82 				filepath.Join("dir", bpFile): []byte(
     83 					`python_library_host {
     84 						name: "lib1",
     85 						srcs: [
     86 							"file1.exe",
     87 						],
     88 					}`,
     89 				),
     90 				"dir/file1.exe": nil,
     91 			},
     92 			errors: []string{
     93 				fmt.Sprintf(badSrcFileExtErr,
     94 					"dir/Blueprints:3:11", "lib1", "PY3", "dir/file1.exe"),
     95 			},
     96 		},
     97 		{
     98 			desc: "module with bad data file ext",
     99 			mockFiles: map[string][]byte{
    100 				bpFile: []byte(`subdirs = ["dir"]`),
    101 				filepath.Join("dir", bpFile): []byte(
    102 					`python_library_host {
    103 						name: "lib1",
    104 						srcs: [
    105 							"file1.py",
    106 						],
    107 						data: [
    108 							"file2.py",
    109 						],
    110 					}`,
    111 				),
    112 				"dir/file1.py": nil,
    113 				"dir/file2.py": nil,
    114 			},
    115 			errors: []string{
    116 				fmt.Sprintf(badDataFileExtErr,
    117 					"dir/Blueprints:6:11", "lib1", "PY3", "dir/file2.py"),
    118 			},
    119 		},
    120 		{
    121 			desc: "module with bad pkg_path format",
    122 			mockFiles: map[string][]byte{
    123 				bpFile: []byte(`subdirs = ["dir"]`),
    124 				filepath.Join("dir", bpFile): []byte(
    125 					`python_library_host {
    126 						name: "lib1",
    127 						pkg_path: "a/c/../../",
    128 						srcs: [
    129 							"file1.py",
    130 						],
    131 					}
    132 
    133 					python_library_host {
    134 						name: "lib2",
    135 						pkg_path: "a/c/../../../",
    136 						srcs: [
    137 							"file1.py",
    138 						],
    139 					}
    140 
    141 					python_library_host {
    142 						name: "lib3",
    143 						pkg_path: "/a/c/../../",
    144 						srcs: [
    145 							"file1.py",
    146 						],
    147 					}`,
    148 				),
    149 				"dir/file1.py": nil,
    150 			},
    151 			errors: []string{
    152 				fmt.Sprintf(pkgPathErrTemplate,
    153 					"dir/Blueprints:11:15", "lib2", "PY3", "a/c/../../../"),
    154 				fmt.Sprintf(pkgPathErrTemplate,
    155 					"dir/Blueprints:19:15", "lib3", "PY3", "/a/c/../../"),
    156 			},
    157 		},
    158 		{
    159 			desc: "module with bad runfile src path format",
    160 			mockFiles: map[string][]byte{
    161 				bpFile: []byte(`subdirs = ["dir"]`),
    162 				filepath.Join("dir", bpFile): []byte(
    163 					`python_library_host {
    164 						name: "lib1",
    165 						pkg_path: "a/b/c/",
    166 						srcs: [
    167 							".file1.py",
    168 							"123/file1.py",
    169 							"-e/f/file1.py",
    170 						],
    171 					}`,
    172 				),
    173 				"dir/.file1.py":     nil,
    174 				"dir/123/file1.py":  nil,
    175 				"dir/-e/f/file1.py": nil,
    176 			},
    177 			errors: []string{
    178 				fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
    179 					"lib1", "PY3", "runfiles/a/b/c/-e/f/file1.py", "-e"),
    180 				fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
    181 					"lib1", "PY3", "runfiles/a/b/c/.file1.py", ".file1"),
    182 				fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
    183 					"lib1", "PY3", "runfiles/a/b/c/123/file1.py", "123"),
    184 			},
    185 		},
    186 		{
    187 			desc: "module with duplicate runfile path",
    188 			mockFiles: map[string][]byte{
    189 				bpFile: []byte(`subdirs = ["dir"]`),
    190 				filepath.Join("dir", bpFile): []byte(
    191 					`python_library_host {
    192 						name: "lib1",
    193 						pkg_path: "a/b/",
    194 						srcs: [
    195 							"c/file1.py",
    196 						],
    197 					}
    198 
    199 					python_library_host {
    200 						name: "lib2",
    201 						pkg_path: "a/b/c/",
    202 						srcs: [
    203 							"file1.py",
    204 						],
    205 						libs: [
    206 							"lib1",
    207 						],
    208 					}
    209 					`,
    210 				),
    211 				"dir/c/file1.py": nil,
    212 				"dir/file1.py":   nil,
    213 			},
    214 			errors: []string{
    215 				fmt.Sprintf(dupRunfileErrTemplate, "dir/Blueprints:9:6",
    216 					"lib2", "PY3", "runfiles/a/b/c/file1.py", "lib2", "dir/file1.py",
    217 					"lib1", "dir/c/file1.py"),
    218 			},
    219 		},
    220 		{
    221 			desc: "module for testing dependencies",
    222 			mockFiles: map[string][]byte{
    223 				bpFile: []byte(`subdirs = ["dir"]`),
    224 				filepath.Join("dir", bpFile): []byte(
    225 					`python_defaults {
    226 						name: "default_lib",
    227 						srcs: [
    228 							"default.py",
    229 						],
    230 						version: {
    231 							py2: {
    232 								enabled: true,
    233 								srcs: [
    234 									"default_py2.py",
    235 								],
    236 							},
    237 							py3: {
    238 								enabled: false,
    239 								srcs: [
    240 									"default_py3.py",
    241 								],
    242 							},
    243 						},
    244 					}
    245 
    246 					python_library_host {
    247 						name: "lib5",
    248 						pkg_path: "a/b/",
    249 						srcs: [
    250 							"file1.py",
    251 						],
    252 						version: {
    253 							py2: {
    254 								enabled: true,
    255 							},
    256 							py3: {
    257 								enabled: true,
    258 							},
    259 						},
    260 					}
    261 
    262 					python_library_host {
    263 						name: "lib6",
    264 						pkg_path: "c/d/",
    265 						srcs: [
    266 							"file2.py",
    267 						],
    268 						libs: [
    269 							"lib5",
    270 						],
    271 					}
    272 
    273 					python_binary_host {
    274 						name: "bin",
    275 						defaults: ["default_lib"],
    276 						pkg_path: "e/",
    277 						srcs: [
    278 							"bin.py",
    279 						],
    280 						libs: [
    281 							"lib5",
    282 						],
    283 						version: {
    284 							py3: {
    285 								enabled: true,
    286 								srcs: [
    287 									"file4.py",
    288 								],
    289 								libs: [
    290 									"lib6",
    291 								],
    292 							},
    293 						},
    294 					}`,
    295 				),
    296 				filepath.Join("dir", "default.py"):     nil,
    297 				filepath.Join("dir", "default_py2.py"): nil,
    298 				filepath.Join("dir", "default_py3.py"): nil,
    299 				filepath.Join("dir", "file1.py"):       nil,
    300 				filepath.Join("dir", "file2.py"):       nil,
    301 				filepath.Join("dir", "bin.py"):         nil,
    302 				filepath.Join("dir", "file4.py"):       nil,
    303 				stubTemplateHost: []byte(`PYTHON_BINARY = '%interpreter%'
    304 				MAIN_FILE = '%main%'`),
    305 			},
    306 			expectedBinaries: []pyModule{
    307 				{
    308 					name:          "bin",
    309 					actualVersion: "PY3",
    310 					pyRunfiles: []string{
    311 						"runfiles/e/default.py",
    312 						"runfiles/e/bin.py",
    313 						"runfiles/e/default_py3.py",
    314 						"runfiles/e/file4.py",
    315 					},
    316 					depsPyRunfiles: []string{
    317 						"runfiles/a/b/file1.py",
    318 						"runfiles/c/d/file2.py",
    319 					},
    320 					parSpec: "-P runfiles/e -C dir/ -l @prefix@/.intermediates/dir/bin/PY3/dir_.list",
    321 					depsParSpecs: []string{
    322 						"-P runfiles/a/b -C dir/ -l @prefix@/.intermediates/dir/lib5/PY3/dir_.list",
    323 						"-P runfiles/c/d -C dir/ -l @prefix@/.intermediates/dir/lib6/PY3/dir_.list",
    324 					},
    325 				},
    326 			},
    327 		},
    328 	}
    329 )
    330 
    331 func TestPythonModule(t *testing.T) {
    332 	config, buildDir := setupBuildEnv(t)
    333 	defer tearDownBuildEnv(buildDir)
    334 	for _, d := range data {
    335 		t.Run(d.desc, func(t *testing.T) {
    336 			ctx := android.NewTestContext()
    337 			ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
    338 				ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
    339 			})
    340 			ctx.RegisterModuleType("python_library_host",
    341 				android.ModuleFactoryAdaptor(PythonLibraryHostFactory))
    342 			ctx.RegisterModuleType("python_binary_host",
    343 				android.ModuleFactoryAdaptor(PythonBinaryHostFactory))
    344 			ctx.RegisterModuleType("python_defaults",
    345 				android.ModuleFactoryAdaptor(defaultsFactory))
    346 			ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
    347 			ctx.Register()
    348 			ctx.MockFileSystem(d.mockFiles)
    349 			_, testErrs := ctx.ParseBlueprintsFiles(bpFile)
    350 			android.FailIfErrored(t, testErrs)
    351 			_, actErrs := ctx.PrepareBuildActions(config)
    352 			if len(actErrs) > 0 {
    353 				testErrs = append(testErrs, expectErrors(t, actErrs, d.errors)...)
    354 			} else {
    355 				for _, e := range d.expectedBinaries {
    356 					testErrs = append(testErrs,
    357 						expectModule(t, ctx, buildDir, e.name,
    358 							e.actualVersion,
    359 							e.pyRunfiles, e.depsPyRunfiles,
    360 							e.parSpec, e.depsParSpecs)...)
    361 				}
    362 			}
    363 			android.FailIfErrored(t, testErrs)
    364 		})
    365 	}
    366 }
    367 
    368 func expectErrors(t *testing.T, actErrs []error, expErrs []string) (testErrs []error) {
    369 	actErrStrs := []string{}
    370 	for _, v := range actErrs {
    371 		actErrStrs = append(actErrStrs, v.Error())
    372 	}
    373 	sort.Strings(actErrStrs)
    374 	if len(actErrStrs) != len(expErrs) {
    375 		t.Errorf("got (%d) errors, expected (%d) errors!", len(actErrStrs), len(expErrs))
    376 		for _, v := range actErrStrs {
    377 			testErrs = append(testErrs, errors.New(v))
    378 		}
    379 	} else {
    380 		sort.Strings(expErrs)
    381 		for i, v := range actErrStrs {
    382 			if v != expErrs[i] {
    383 				testErrs = append(testErrs, errors.New(v))
    384 			}
    385 		}
    386 	}
    387 
    388 	return
    389 }
    390 
    391 func expectModule(t *testing.T, ctx *android.TestContext, buildDir, name, variant string,
    392 	expPyRunfiles, expDepsPyRunfiles []string,
    393 	expParSpec string, expDepsParSpecs []string) (testErrs []error) {
    394 	module := ctx.ModuleForTests(name, variant)
    395 
    396 	base, baseOk := module.Module().(*Module)
    397 	if !baseOk {
    398 		t.Fatalf("%s is not Python module!", name)
    399 	}
    400 
    401 	actPyRunfiles := []string{}
    402 	for _, path := range base.srcsPathMappings {
    403 		actPyRunfiles = append(actPyRunfiles, path.dest)
    404 	}
    405 
    406 	if !reflect.DeepEqual(actPyRunfiles, expPyRunfiles) {
    407 		testErrs = append(testErrs, errors.New(fmt.Sprintf(
    408 			`binary "%s" variant "%s" has unexpected pyRunfiles: %q!`,
    409 			base.Name(),
    410 			base.properties.Actual_version,
    411 			actPyRunfiles)))
    412 	}
    413 
    414 	if !reflect.DeepEqual(base.depsPyRunfiles, expDepsPyRunfiles) {
    415 		testErrs = append(testErrs, errors.New(fmt.Sprintf(
    416 			`binary "%s" variant "%s" has unexpected depsPyRunfiles: %q!`,
    417 			base.Name(),
    418 			base.properties.Actual_version,
    419 			base.depsPyRunfiles)))
    420 	}
    421 
    422 	if base.parSpec.soongParArgs() != strings.Replace(expParSpec, "@prefix@", buildDir, 1) {
    423 		testErrs = append(testErrs, errors.New(fmt.Sprintf(
    424 			`binary "%s" variant "%s" has unexpected parSpec: %q!`,
    425 			base.Name(),
    426 			base.properties.Actual_version,
    427 			base.parSpec.soongParArgs())))
    428 	}
    429 
    430 	actDepsParSpecs := []string{}
    431 	for i, p := range base.depsParSpecs {
    432 		actDepsParSpecs = append(actDepsParSpecs, p.soongParArgs())
    433 		expDepsParSpecs[i] = strings.Replace(expDepsParSpecs[i], "@prefix@", buildDir, 1)
    434 	}
    435 
    436 	if !reflect.DeepEqual(actDepsParSpecs, expDepsParSpecs) {
    437 		testErrs = append(testErrs, errors.New(fmt.Sprintf(
    438 			`binary "%s" variant "%s" has unexpected depsParSpecs: %q!`,
    439 			base.Name(),
    440 			base.properties.Actual_version,
    441 			actDepsParSpecs)))
    442 	}
    443 
    444 	return
    445 }
    446 
    447 func setupBuildEnv(t *testing.T) (config android.Config, buildDir string) {
    448 	buildDir, err := ioutil.TempDir("", buildNamePrefix)
    449 	if err != nil {
    450 		t.Fatal(err)
    451 	}
    452 
    453 	config = android.TestConfig(buildDir, nil)
    454 
    455 	return
    456 }
    457 
    458 func tearDownBuildEnv(buildDir string) {
    459 	os.RemoveAll(buildDir)
    460 }
    461