| // 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()) |
| } |