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 15package python 16 17import ( 18 "fmt" 19 "os" 20 "path/filepath" 21 "strings" 22 "testing" 23 24 "android/soong/android" 25 "android/soong/cc" 26 27 "github.com/google/blueprint" 28) 29 30type pyModule struct { 31 name string 32 actualVersion string 33 pyRunfiles []string 34 srcsZip string 35 depsSrcsZips []string 36} 37 38var ( 39 buildNamePrefix = "soong_python_test" 40 moduleVariantErrTemplate = `%s: module %q variant "[a-zA-Z0-9_]*": ` 41 pkgPathErrTemplate = moduleVariantErrTemplate + 42 "pkg_path: %q must be a relative path contained in par file." 43 badIdentifierErrTemplate = moduleVariantErrTemplate + 44 "srcs: the path %q contains invalid subpath %q." 45 dupRunfileErrTemplate = moduleVariantErrTemplate + 46 "found two files to be placed at the same location within zip %q." + 47 " First file: in module %s at path %q." + 48 " Second file: in module %s at path %q." 49 badSrcFileExtErr = moduleVariantErrTemplate + `srcs: found non \(.py\|.proto\) file: %q!` 50 badDataFileExtErr = moduleVariantErrTemplate + `data: found \(.py\) file: %q!` 51 bpFile = "Android.bp" 52 53 data = []struct { 54 desc string 55 mockFiles android.MockFS 56 57 errors []string 58 expectedBinaries []pyModule 59 }{ 60 { 61 desc: "module with bad src file ext", 62 mockFiles: map[string][]byte{ 63 filepath.Join("dir", bpFile): []byte( 64 `python_library_host { 65 name: "lib1", 66 srcs: [ 67 "file1.exe", 68 ], 69 }`, 70 ), 71 "dir/file1.exe": nil, 72 }, 73 errors: []string{ 74 fmt.Sprintf(badSrcFileExtErr, 75 "dir/Android.bp:3:11", "lib1", "dir/file1.exe"), 76 }, 77 }, 78 { 79 desc: "module with bad data file ext", 80 mockFiles: map[string][]byte{ 81 filepath.Join("dir", bpFile): []byte( 82 `python_library_host { 83 name: "lib1", 84 srcs: [ 85 "file1.py", 86 ], 87 data: [ 88 "file2.py", 89 ], 90 }`, 91 ), 92 "dir/file1.py": nil, 93 "dir/file2.py": nil, 94 }, 95 errors: []string{ 96 fmt.Sprintf(badDataFileExtErr, 97 "dir/Android.bp:6:11", "lib1", "dir/file2.py"), 98 }, 99 }, 100 { 101 desc: "module with bad pkg_path format", 102 mockFiles: map[string][]byte{ 103 filepath.Join("dir", bpFile): []byte( 104 `python_library_host { 105 name: "lib1", 106 pkg_path: "a/c/../../", 107 srcs: [ 108 "file1.py", 109 ], 110 } 111 112 python_library_host { 113 name: "lib2", 114 pkg_path: "a/c/../../../", 115 srcs: [ 116 "file1.py", 117 ], 118 } 119 120 python_library_host { 121 name: "lib3", 122 pkg_path: "/a/c/../../", 123 srcs: [ 124 "file1.py", 125 ], 126 }`, 127 ), 128 "dir/file1.py": nil, 129 }, 130 errors: []string{ 131 fmt.Sprintf(pkgPathErrTemplate, 132 "dir/Android.bp:11:15", "lib2", "a/c/../../../"), 133 fmt.Sprintf(pkgPathErrTemplate, 134 "dir/Android.bp:19:15", "lib3", "/a/c/../../"), 135 }, 136 }, 137 { 138 desc: "module with bad runfile src path format", 139 mockFiles: map[string][]byte{ 140 filepath.Join("dir", bpFile): []byte( 141 `python_library_host { 142 name: "lib1", 143 pkg_path: "a/b/c/", 144 srcs: [ 145 ".file1.py", 146 "123/file1.py", 147 "-e/f/file1.py", 148 ], 149 }`, 150 ), 151 "dir/.file1.py": nil, 152 "dir/123/file1.py": nil, 153 "dir/-e/f/file1.py": nil, 154 }, 155 errors: []string{ 156 fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11", 157 "lib1", "a/b/c/-e/f/file1.py", "-e"), 158 fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11", 159 "lib1", "a/b/c/.file1.py", ".file1"), 160 fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11", 161 "lib1", "a/b/c/123/file1.py", "123"), 162 }, 163 }, 164 { 165 desc: "module with duplicate runfile path", 166 mockFiles: map[string][]byte{ 167 filepath.Join("dir", bpFile): []byte( 168 `python_library_host { 169 name: "lib1", 170 pkg_path: "a/b/", 171 srcs: [ 172 "c/file1.py", 173 ], 174 } 175 176 python_library_host { 177 name: "lib2", 178 pkg_path: "a/b/c/", 179 srcs: [ 180 "file1.py", 181 ], 182 libs: [ 183 "lib1", 184 ], 185 } 186 187 python_binary_host { 188 name: "bin", 189 pkg_path: "e/", 190 srcs: [ 191 "bin.py", 192 ], 193 libs: [ 194 "lib2", 195 ], 196 } 197 `, 198 ), 199 "dir/c/file1.py": nil, 200 "dir/file1.py": nil, 201 "dir/bin.py": nil, 202 }, 203 errors: []string{ 204 fmt.Sprintf(dupRunfileErrTemplate, "dir/Android.bp:20:6", 205 "bin", "a/b/c/file1.py", "bin", "dir/file1.py", 206 "lib1", "dir/c/file1.py"), 207 }, 208 }, 209 } 210) 211 212func TestPythonModule(t *testing.T) { 213 for _, d := range data { 214 d.mockFiles[filepath.Join("common", bpFile)] = []byte(` 215python_library { 216 name: "py3-stdlib", 217 host_supported: true, 218} 219cc_binary { 220 name: "py3-launcher", 221 host_supported: true, 222} 223`) 224 225 t.Run(d.desc, func(t *testing.T) { 226 result := android.GroupFixturePreparers( 227 android.PrepareForTestWithDefaults, 228 android.PrepareForTestWithArchMutator, 229 android.PrepareForTestWithAllowMissingDependencies, 230 cc.PrepareForTestWithCcDefaultModules, 231 PrepareForTestWithPythonBuildComponents, 232 d.mockFiles.AddToFixture(), 233 ).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(d.errors)). 234 RunTest(t) 235 236 if len(result.Errs) > 0 { 237 return 238 } 239 240 for _, e := range d.expectedBinaries { 241 t.Run(e.name, func(t *testing.T) { 242 expectModule(t, result.TestContext, e.name, e.actualVersion, e.srcsZip, e.pyRunfiles) 243 }) 244 } 245 }) 246 } 247} 248 249func TestTestOnlyProvider(t *testing.T) { 250 t.Parallel() 251 ctx := android.GroupFixturePreparers( 252 PrepareForTestWithPythonBuildComponents, 253 android.PrepareForTestWithAllowMissingDependencies, 254 ).RunTestWithBp(t, ` 255 // These should be test-only 256 python_library { name: "py-lib-test", test_only: true } 257 python_library { name: "py-lib-test-host", test_only: true, host_supported: true } 258 python_test { name: "py-test", srcs: ["py-test.py"] } 259 python_test_host { name: "py-test-host", srcs: ["py-test-host.py"] } 260 python_binary_host { name: "py-bin-test", srcs: ["py-bin-test.py"] } 261 262 // These should not be. 263 python_library { name: "py-lib" } 264 python_binary_host { name: "py-bin", srcs: ["py-bin.py"] } 265 `) 266 267 // Visit all modules and ensure only the ones that should 268 // marked as test-only are marked as test-only. 269 270 actualTestOnly := []string{} 271 ctx.VisitAllModules(func(m blueprint.Module) { 272 if provider, ok := android.OtherModuleProvider(ctx.TestContext.OtherModuleProviderAdaptor(), m, android.TestOnlyProviderKey); ok { 273 if provider.TestOnly { 274 actualTestOnly = append(actualTestOnly, m.Name()) 275 } 276 } 277 }) 278 expectedTestOnlyModules := []string{ 279 "py-lib-test", 280 "py-lib-test-host", 281 "py-test", 282 "py-test-host", 283 } 284 285 notEqual, left, right := android.ListSetDifference(expectedTestOnlyModules, actualTestOnly) 286 if notEqual { 287 t.Errorf("test-only: Expected but not found: %v, Found but not expected: %v", left, right) 288 } 289} 290 291// Don't allow setting test-only on things that are always tests or never tests. 292func TestInvalidTestOnlyTargets(t *testing.T) { 293 testCases := []string{ 294 ` python_test { name: "py-test", test_only: true, srcs: ["py-test.py"] } `, 295 ` python_test_host { name: "py-test-host", test_only: true, srcs: ["py-test-host.py"] } `, 296 ` python_defaults { name: "py-defaults", test_only: true, srcs: ["foo.py"] } `, 297 } 298 299 for i, bp := range testCases { 300 ctx := android.GroupFixturePreparers( 301 PrepareForTestWithPythonBuildComponents, 302 android.PrepareForTestWithAllowMissingDependencies). 303 ExtendWithErrorHandler(android.FixtureIgnoreErrors). 304 RunTestWithBp(t, bp) 305 if len(ctx.Errs) != 1 { 306 t.Errorf("Expected err setting test_only in testcase #%d: %d errs", i, len(ctx.Errs)) 307 continue 308 } 309 if !strings.Contains(ctx.Errs[0].Error(), "unrecognized property \"test_only\"") { 310 t.Errorf("ERR: %s bad bp: %s", ctx.Errs[0], bp) 311 } 312 } 313} 314 315func expectModule(t *testing.T, ctx *android.TestContext, name, variant, expectedSrcsZip string, expectedPyRunfiles []string) { 316 module := ctx.ModuleForTests(t, name, variant) 317 318 base, baseOk := module.Module().(*PythonLibraryModule) 319 if !baseOk { 320 t.Fatalf("%s is not Python module!", name) 321 } 322 323 actualPyRunfiles := []string{} 324 for _, path := range base.srcsPathMappings { 325 actualPyRunfiles = append(actualPyRunfiles, path.dest) 326 } 327 328 android.AssertDeepEquals(t, "pyRunfiles", expectedPyRunfiles, actualPyRunfiles) 329 330 android.AssertPathRelativeToTopEquals(t, "srcsZip", expectedSrcsZip, base.srcsZip) 331} 332 333func TestMain(m *testing.M) { 334 os.Exit(m.Run()) 335} 336