| // 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 ( | 
 | 	"fmt" | 
 | 	"os" | 
 | 	"path/filepath" | 
 | 	"strings" | 
 | 	"testing" | 
 |  | 
 | 	"android/soong/android" | 
 | 	"android/soong/cc" | 
 |  | 
 | 	"github.com/google/blueprint" | 
 | ) | 
 |  | 
 | type pyModule struct { | 
 | 	name          string | 
 | 	actualVersion string | 
 | 	pyRunfiles    []string | 
 | 	srcsZip       string | 
 | 	depsSrcsZips  []string | 
 | } | 
 |  | 
 | var ( | 
 | 	buildNamePrefix = "soong_python_test" | 
 | 	// We allow maching almost anything before the actual variant so that the os/arch variant | 
 | 	// is matched. | 
 | 	moduleVariantErrTemplate = `%s: module %q variant "[a-zA-Z0-9_]*%s": ` | 
 | 	pkgPathErrTemplate       = moduleVariantErrTemplate + | 
 | 		"pkg_path: %q must be a relative path contained in par file." | 
 | 	badIdentifierErrTemplate = moduleVariantErrTemplate + | 
 | 		"srcs: the path %q contains invalid subpath %q." | 
 | 	dupRunfileErrTemplate = moduleVariantErrTemplate + | 
 | 		"found two files to be placed at the same location within zip %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|.proto) file: %q!" | 
 | 	badDataFileExtErr = moduleVariantErrTemplate + "data: found (.py) file: %q!" | 
 | 	bpFile            = "Android.bp" | 
 |  | 
 | 	data = []struct { | 
 | 		desc      string | 
 | 		mockFiles android.MockFS | 
 |  | 
 | 		errors           []string | 
 | 		expectedBinaries []pyModule | 
 | 	}{ | 
 | 		{ | 
 | 			desc: "module without any src files", | 
 | 			mockFiles: map[string][]byte{ | 
 | 				filepath.Join("dir", bpFile): []byte( | 
 | 					`python_library_host { | 
 | 						name: "lib1", | 
 | 					}`, | 
 | 				), | 
 | 			}, | 
 | 			errors: []string{ | 
 | 				fmt.Sprintf(noSrcFileErr, | 
 | 					"dir/Android.bp:1:1", "lib1", "PY3"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "module with bad src file ext", | 
 | 			mockFiles: map[string][]byte{ | 
 | 				filepath.Join("dir", bpFile): []byte( | 
 | 					`python_library_host { | 
 | 						name: "lib1", | 
 | 						srcs: [ | 
 | 							"file1.exe", | 
 | 						], | 
 | 					}`, | 
 | 				), | 
 | 				"dir/file1.exe": nil, | 
 | 			}, | 
 | 			errors: []string{ | 
 | 				fmt.Sprintf(badSrcFileExtErr, | 
 | 					"dir/Android.bp:3:11", "lib1", "PY3", "dir/file1.exe"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "module with bad data file ext", | 
 | 			mockFiles: map[string][]byte{ | 
 | 				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/Android.bp:6:11", "lib1", "PY3", "dir/file2.py"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "module with bad pkg_path format", | 
 | 			mockFiles: map[string][]byte{ | 
 | 				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/Android.bp:11:15", "lib2", "PY3", "a/c/../../../"), | 
 | 				fmt.Sprintf(pkgPathErrTemplate, | 
 | 					"dir/Android.bp:19:15", "lib3", "PY3", "/a/c/../../"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "module with bad runfile src path format", | 
 | 			mockFiles: map[string][]byte{ | 
 | 				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/Android.bp:4:11", | 
 | 					"lib1", "PY3", "a/b/c/-e/f/file1.py", "-e"), | 
 | 				fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11", | 
 | 					"lib1", "PY3", "a/b/c/.file1.py", ".file1"), | 
 | 				fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11", | 
 | 					"lib1", "PY3", "a/b/c/123/file1.py", "123"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "module with duplicate runfile path", | 
 | 			mockFiles: map[string][]byte{ | 
 | 				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", | 
 | 						], | 
 | 					} | 
 |  | 
 | 					python_binary_host { | 
 | 						name: "bin", | 
 | 						pkg_path: "e/", | 
 | 						srcs: [ | 
 | 							"bin.py", | 
 | 						], | 
 | 						libs: [ | 
 | 							"lib2", | 
 | 						], | 
 | 					} | 
 | 					`, | 
 | 				), | 
 | 				"dir/c/file1.py": nil, | 
 | 				"dir/file1.py":   nil, | 
 | 				"dir/bin.py":     nil, | 
 | 			}, | 
 | 			errors: []string{ | 
 | 				fmt.Sprintf(dupRunfileErrTemplate, "dir/Android.bp:20:6", | 
 | 					"bin", "PY3", "a/b/c/file1.py", "bin", "dir/file1.py", | 
 | 					"lib1", "dir/c/file1.py"), | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			desc: "module for testing dependencies", | 
 | 			mockFiles: map[string][]byte{ | 
 | 				filepath.Join("dir", bpFile): []byte( | 
 | 					`python_defaults { | 
 | 						name: "default_lib", | 
 | 						srcs: [ | 
 | 							"default.py", | 
 | 						], | 
 | 						version: { | 
 | 							py2: { | 
 | 								enabled: true, | 
 | 								srcs: [ | 
 | 									"default_py2.py", | 
 | 								], | 
 | 							}, | 
 | 							py3: { | 
 | 								enabled: false, | 
 | 								srcs: [ | 
 | 									"default_py3.py", | 
 | 								], | 
 | 							}, | 
 | 						}, | 
 | 					} | 
 |  | 
 | 					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", | 
 | 						defaults: ["default_lib"], | 
 | 						pkg_path: "e/", | 
 | 						srcs: [ | 
 | 							"bin.py", | 
 | 						], | 
 | 						libs: [ | 
 | 							"lib5", | 
 | 						], | 
 | 						version: { | 
 | 							py3: { | 
 | 								enabled: true, | 
 | 								srcs: [ | 
 | 									"file4.py", | 
 | 								], | 
 | 								libs: [ | 
 | 									"lib6", | 
 | 								], | 
 | 							}, | 
 | 						}, | 
 | 					}`, | 
 | 				), | 
 | 				filepath.Join("dir", "default.py"):     nil, | 
 | 				filepath.Join("dir", "default_py2.py"): nil, | 
 | 				filepath.Join("dir", "default_py3.py"): nil, | 
 | 				filepath.Join("dir", "file1.py"):       nil, | 
 | 				filepath.Join("dir", "file2.py"):       nil, | 
 | 				filepath.Join("dir", "bin.py"):         nil, | 
 | 				filepath.Join("dir", "file4.py"):       nil, | 
 | 			}, | 
 | 			expectedBinaries: []pyModule{ | 
 | 				{ | 
 | 					name:          "bin", | 
 | 					actualVersion: "PY3", | 
 | 					pyRunfiles: []string{ | 
 | 						"e/default.py", | 
 | 						"e/bin.py", | 
 | 						"e/default_py3.py", | 
 | 						"e/file4.py", | 
 | 					}, | 
 | 					srcsZip: "out/soong/.intermediates/dir/bin/PY3/bin.py.srcszip", | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 	} | 
 | ) | 
 |  | 
 | func TestPythonModule(t *testing.T) { | 
 | 	for _, d := range data { | 
 | 		if d.desc != "module with duplicate runfile path" { | 
 | 			continue | 
 | 		} | 
 | 		d.mockFiles[filepath.Join("common", bpFile)] = []byte(` | 
 | python_library { | 
 |   name: "py3-stdlib", | 
 |   host_supported: true, | 
 | } | 
 | cc_binary { | 
 |   name: "py3-launcher", | 
 |   host_supported: true, | 
 | } | 
 | `) | 
 |  | 
 | 		t.Run(d.desc, func(t *testing.T) { | 
 | 			result := android.GroupFixturePreparers( | 
 | 				android.PrepareForTestWithDefaults, | 
 | 				android.PrepareForTestWithArchMutator, | 
 | 				android.PrepareForTestWithAllowMissingDependencies, | 
 | 				cc.PrepareForTestWithCcDefaultModules, | 
 | 				PrepareForTestWithPythonBuildComponents, | 
 | 				d.mockFiles.AddToFixture(), | 
 | 			).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(d.errors)). | 
 | 				RunTest(t) | 
 |  | 
 | 			if len(result.Errs) > 0 { | 
 | 				return | 
 | 			} | 
 |  | 
 | 			for _, e := range d.expectedBinaries { | 
 | 				t.Run(e.name, func(t *testing.T) { | 
 | 					expectModule(t, result.TestContext, e.name, e.actualVersion, e.srcsZip, e.pyRunfiles) | 
 | 				}) | 
 | 			} | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | func TestTestOnlyProvider(t *testing.T) { | 
 | 	t.Parallel() | 
 | 	ctx := android.GroupFixturePreparers( | 
 | 		PrepareForTestWithPythonBuildComponents, | 
 | 		android.PrepareForTestWithAllowMissingDependencies, | 
 | 	).RunTestWithBp(t, ` | 
 |                 // These should be test-only | 
 |                 python_library { name: "py-lib-test", test_only: true } | 
 |                 python_library { name: "py-lib-test-host", test_only: true, host_supported: true } | 
 |                 python_test {    name: "py-test", srcs: ["py-test.py"] } | 
 |                 python_test_host { name: "py-test-host", srcs: ["py-test-host.py"] } | 
 |                 python_binary_host { name: "py-bin-test", srcs: ["py-bin-test.py"] } | 
 |  | 
 |                 // These should not be. | 
 |                 python_library { name: "py-lib" } | 
 |                 python_binary_host { name: "py-bin", srcs: ["py-bin.py"] } | 
 | 	`) | 
 |  | 
 | 	// Visit all modules and ensure only the ones that should | 
 | 	// marked as test-only are marked as test-only. | 
 |  | 
 | 	actualTestOnly := []string{} | 
 | 	ctx.VisitAllModules(func(m blueprint.Module) { | 
 | 		if provider, ok := android.OtherModuleProvider(ctx.TestContext.OtherModuleProviderAdaptor(), m, android.TestOnlyProviderKey); ok { | 
 | 			if provider.TestOnly { | 
 | 				actualTestOnly = append(actualTestOnly, m.Name()) | 
 | 			} | 
 | 		} | 
 | 	}) | 
 | 	expectedTestOnlyModules := []string{ | 
 | 		"py-lib-test", | 
 | 		"py-lib-test-host", | 
 | 		"py-test", | 
 | 		"py-test-host", | 
 | 	} | 
 |  | 
 | 	notEqual, left, right := android.ListSetDifference(expectedTestOnlyModules, actualTestOnly) | 
 | 	if notEqual { | 
 | 		t.Errorf("test-only: Expected but not found: %v, Found but not expected: %v", left, right) | 
 | 	} | 
 | } | 
 |  | 
 | // Don't allow setting test-only on things that are always tests or never tests. | 
 | func TestInvalidTestOnlyTargets(t *testing.T) { | 
 | 	testCases := []string{ | 
 | 		` python_test { name: "py-test", test_only: true, srcs: ["py-test.py"] } `, | 
 | 		` python_test_host { name: "py-test-host", test_only: true, srcs: ["py-test-host.py"] } `, | 
 | 		` python_defaults { name: "py-defaults", test_only: true, srcs: ["foo.py"] } `, | 
 | 	} | 
 |  | 
 | 	for i, bp := range testCases { | 
 | 		ctx := android.GroupFixturePreparers( | 
 | 			PrepareForTestWithPythonBuildComponents, | 
 | 			android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) { | 
 |  | 
 | 				ctx.RegisterModuleType("python_defaults", DefaultsFactory) | 
 | 			}), | 
 | 			android.PrepareForTestWithAllowMissingDependencies). | 
 | 			ExtendWithErrorHandler(android.FixtureIgnoreErrors). | 
 | 			RunTestWithBp(t, bp) | 
 | 		if len(ctx.Errs) != 1 { | 
 | 			t.Errorf("Expected err setting test_only in testcase #%d: %d errs", i, len(ctx.Errs)) | 
 | 			continue | 
 | 		} | 
 | 		if !strings.Contains(ctx.Errs[0].Error(), "unrecognized property \"test_only\"") { | 
 | 			t.Errorf("ERR: %s bad bp: %s", ctx.Errs[0], bp) | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | func expectModule(t *testing.T, ctx *android.TestContext, name, variant, expectedSrcsZip string, expectedPyRunfiles []string) { | 
 | 	module := ctx.ModuleForTests(name, variant) | 
 |  | 
 | 	base, baseOk := module.Module().(*PythonLibraryModule) | 
 | 	if !baseOk { | 
 | 		t.Fatalf("%s is not Python module!", name) | 
 | 	} | 
 |  | 
 | 	actualPyRunfiles := []string{} | 
 | 	for _, path := range base.srcsPathMappings { | 
 | 		actualPyRunfiles = append(actualPyRunfiles, path.dest) | 
 | 	} | 
 |  | 
 | 	android.AssertDeepEquals(t, "pyRunfiles", expectedPyRunfiles, actualPyRunfiles) | 
 |  | 
 | 	android.AssertPathRelativeToTopEquals(t, "srcsZip", expectedSrcsZip, base.srcsZip) | 
 | } | 
 |  | 
 | func TestMain(m *testing.M) { | 
 | 	os.Exit(m.Run()) | 
 | } |