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 // We allow maching almost anything before the actual variant so that the os/arch variant 41 // is matched. 42 moduleVariantErrTemplate = `%s: module %q variant "[a-zA-Z0-9_]*%s": ` 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 subpath %q." 47 dupRunfileErrTemplate = moduleVariantErrTemplate + 48 "found two files to be placed at the same location within zip %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|.proto) file: %q!" 53 badDataFileExtErr = moduleVariantErrTemplate + "data: found (.py) file: %q!" 54 bpFile = "Android.bp" 55 56 data = []struct { 57 desc string 58 mockFiles android.MockFS 59 60 errors []string 61 expectedBinaries []pyModule 62 }{ 63 { 64 desc: "module without any src files", 65 mockFiles: map[string][]byte{ 66 filepath.Join("dir", bpFile): []byte( 67 `python_library_host { 68 name: "lib1", 69 }`, 70 ), 71 }, 72 errors: []string{ 73 fmt.Sprintf(noSrcFileErr, 74 "dir/Android.bp:1:1", "lib1", "PY3"), 75 }, 76 }, 77 { 78 desc: "module with bad src file ext", 79 mockFiles: map[string][]byte{ 80 filepath.Join("dir", bpFile): []byte( 81 `python_library_host { 82 name: "lib1", 83 srcs: [ 84 "file1.exe", 85 ], 86 }`, 87 ), 88 "dir/file1.exe": nil, 89 }, 90 errors: []string{ 91 fmt.Sprintf(badSrcFileExtErr, 92 "dir/Android.bp:3:11", "lib1", "PY3", "dir/file1.exe"), 93 }, 94 }, 95 { 96 desc: "module with bad data file ext", 97 mockFiles: map[string][]byte{ 98 filepath.Join("dir", bpFile): []byte( 99 `python_library_host { 100 name: "lib1", 101 srcs: [ 102 "file1.py", 103 ], 104 data: [ 105 "file2.py", 106 ], 107 }`, 108 ), 109 "dir/file1.py": nil, 110 "dir/file2.py": nil, 111 }, 112 errors: []string{ 113 fmt.Sprintf(badDataFileExtErr, 114 "dir/Android.bp:6:11", "lib1", "PY3", "dir/file2.py"), 115 }, 116 }, 117 { 118 desc: "module with bad pkg_path format", 119 mockFiles: map[string][]byte{ 120 filepath.Join("dir", bpFile): []byte( 121 `python_library_host { 122 name: "lib1", 123 pkg_path: "a/c/../../", 124 srcs: [ 125 "file1.py", 126 ], 127 } 128 129 python_library_host { 130 name: "lib2", 131 pkg_path: "a/c/../../../", 132 srcs: [ 133 "file1.py", 134 ], 135 } 136 137 python_library_host { 138 name: "lib3", 139 pkg_path: "/a/c/../../", 140 srcs: [ 141 "file1.py", 142 ], 143 }`, 144 ), 145 "dir/file1.py": nil, 146 }, 147 errors: []string{ 148 fmt.Sprintf(pkgPathErrTemplate, 149 "dir/Android.bp:11:15", "lib2", "PY3", "a/c/../../../"), 150 fmt.Sprintf(pkgPathErrTemplate, 151 "dir/Android.bp:19:15", "lib3", "PY3", "/a/c/../../"), 152 }, 153 }, 154 { 155 desc: "module with bad runfile src path format", 156 mockFiles: map[string][]byte{ 157 filepath.Join("dir", bpFile): []byte( 158 `python_library_host { 159 name: "lib1", 160 pkg_path: "a/b/c/", 161 srcs: [ 162 ".file1.py", 163 "123/file1.py", 164 "-e/f/file1.py", 165 ], 166 }`, 167 ), 168 "dir/.file1.py": nil, 169 "dir/123/file1.py": nil, 170 "dir/-e/f/file1.py": nil, 171 }, 172 errors: []string{ 173 fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11", 174 "lib1", "PY3", "a/b/c/-e/f/file1.py", "-e"), 175 fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11", 176 "lib1", "PY3", "a/b/c/.file1.py", ".file1"), 177 fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11", 178 "lib1", "PY3", "a/b/c/123/file1.py", "123"), 179 }, 180 }, 181 { 182 desc: "module with duplicate runfile path", 183 mockFiles: map[string][]byte{ 184 filepath.Join("dir", bpFile): []byte( 185 `python_library_host { 186 name: "lib1", 187 pkg_path: "a/b/", 188 srcs: [ 189 "c/file1.py", 190 ], 191 } 192 193 python_library_host { 194 name: "lib2", 195 pkg_path: "a/b/c/", 196 srcs: [ 197 "file1.py", 198 ], 199 libs: [ 200 "lib1", 201 ], 202 } 203 204 python_binary_host { 205 name: "bin", 206 pkg_path: "e/", 207 srcs: [ 208 "bin.py", 209 ], 210 libs: [ 211 "lib2", 212 ], 213 } 214 `, 215 ), 216 "dir/c/file1.py": nil, 217 "dir/file1.py": nil, 218 "dir/bin.py": nil, 219 }, 220 errors: []string{ 221 fmt.Sprintf(dupRunfileErrTemplate, "dir/Android.bp:20:6", 222 "bin", "PY3", "a/b/c/file1.py", "bin", "dir/file1.py", 223 "lib1", "dir/c/file1.py"), 224 }, 225 }, 226 { 227 desc: "module for testing dependencies", 228 mockFiles: map[string][]byte{ 229 filepath.Join("dir", bpFile): []byte( 230 `python_defaults { 231 name: "default_lib", 232 srcs: [ 233 "default.py", 234 ], 235 version: { 236 py2: { 237 enabled: true, 238 srcs: [ 239 "default_py2.py", 240 ], 241 }, 242 py3: { 243 enabled: false, 244 srcs: [ 245 "default_py3.py", 246 ], 247 }, 248 }, 249 } 250 251 python_library_host { 252 name: "lib5", 253 pkg_path: "a/b/", 254 srcs: [ 255 "file1.py", 256 ], 257 version: { 258 py2: { 259 enabled: true, 260 }, 261 py3: { 262 enabled: true, 263 }, 264 }, 265 } 266 267 python_library_host { 268 name: "lib6", 269 pkg_path: "c/d/", 270 srcs: [ 271 "file2.py", 272 ], 273 libs: [ 274 "lib5", 275 ], 276 } 277 278 python_binary_host { 279 name: "bin", 280 defaults: ["default_lib"], 281 pkg_path: "e/", 282 srcs: [ 283 "bin.py", 284 ], 285 libs: [ 286 "lib5", 287 ], 288 version: { 289 py3: { 290 enabled: true, 291 srcs: [ 292 "file4.py", 293 ], 294 libs: [ 295 "lib6", 296 ], 297 }, 298 }, 299 }`, 300 ), 301 filepath.Join("dir", "default.py"): nil, 302 filepath.Join("dir", "default_py2.py"): nil, 303 filepath.Join("dir", "default_py3.py"): nil, 304 filepath.Join("dir", "file1.py"): nil, 305 filepath.Join("dir", "file2.py"): nil, 306 filepath.Join("dir", "bin.py"): nil, 307 filepath.Join("dir", "file4.py"): nil, 308 }, 309 expectedBinaries: []pyModule{ 310 { 311 name: "bin", 312 actualVersion: "PY3", 313 pyRunfiles: []string{ 314 "e/default.py", 315 "e/bin.py", 316 "e/default_py3.py", 317 "e/file4.py", 318 }, 319 srcsZip: "out/soong/.intermediates/dir/bin/PY3/bin.py.srcszip", 320 }, 321 }, 322 }, 323 } 324) 325 326func TestPythonModule(t *testing.T) { 327 for _, d := range data { 328 if d.desc != "module with duplicate runfile path" { 329 continue 330 } 331 d.mockFiles[filepath.Join("common", bpFile)] = []byte(` 332python_library { 333 name: "py3-stdlib", 334 host_supported: true, 335} 336cc_binary { 337 name: "py3-launcher", 338 host_supported: true, 339} 340`) 341 342 t.Run(d.desc, func(t *testing.T) { 343 result := android.GroupFixturePreparers( 344 android.PrepareForTestWithDefaults, 345 android.PrepareForTestWithArchMutator, 346 android.PrepareForTestWithAllowMissingDependencies, 347 cc.PrepareForTestWithCcDefaultModules, 348 PrepareForTestWithPythonBuildComponents, 349 d.mockFiles.AddToFixture(), 350 ).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(d.errors)). 351 RunTest(t) 352 353 if len(result.Errs) > 0 { 354 return 355 } 356 357 for _, e := range d.expectedBinaries { 358 t.Run(e.name, func(t *testing.T) { 359 expectModule(t, result.TestContext, e.name, e.actualVersion, e.srcsZip, e.pyRunfiles) 360 }) 361 } 362 }) 363 } 364} 365 366func TestTestOnlyProvider(t *testing.T) { 367 t.Parallel() 368 ctx := android.GroupFixturePreparers( 369 PrepareForTestWithPythonBuildComponents, 370 android.PrepareForTestWithAllowMissingDependencies, 371 ).RunTestWithBp(t, ` 372 // These should be test-only 373 python_library { name: "py-lib-test", test_only: true } 374 python_library { name: "py-lib-test-host", test_only: true, host_supported: true } 375 python_test { name: "py-test", srcs: ["py-test.py"] } 376 python_test_host { name: "py-test-host", srcs: ["py-test-host.py"] } 377 python_binary_host { name: "py-bin-test", srcs: ["py-bin-test.py"] } 378 379 // These should not be. 380 python_library { name: "py-lib" } 381 python_binary_host { name: "py-bin", srcs: ["py-bin.py"] } 382 `) 383 384 // Visit all modules and ensure only the ones that should 385 // marked as test-only are marked as test-only. 386 387 actualTestOnly := []string{} 388 ctx.VisitAllModules(func(m blueprint.Module) { 389 if provider, ok := android.OtherModuleProvider(ctx.TestContext.OtherModuleProviderAdaptor(), m, android.TestOnlyProviderKey); ok { 390 if provider.TestOnly { 391 actualTestOnly = append(actualTestOnly, m.Name()) 392 } 393 } 394 }) 395 expectedTestOnlyModules := []string{ 396 "py-lib-test", 397 "py-lib-test-host", 398 "py-test", 399 "py-test-host", 400 } 401 402 notEqual, left, right := android.ListSetDifference(expectedTestOnlyModules, actualTestOnly) 403 if notEqual { 404 t.Errorf("test-only: Expected but not found: %v, Found but not expected: %v", left, right) 405 } 406} 407 408// Don't allow setting test-only on things that are always tests or never tests. 409func TestInvalidTestOnlyTargets(t *testing.T) { 410 testCases := []string{ 411 ` python_test { name: "py-test", test_only: true, srcs: ["py-test.py"] } `, 412 ` python_test_host { name: "py-test-host", test_only: true, srcs: ["py-test-host.py"] } `, 413 ` python_defaults { name: "py-defaults", test_only: true, srcs: ["foo.py"] } `, 414 } 415 416 for i, bp := range testCases { 417 ctx := android.GroupFixturePreparers( 418 PrepareForTestWithPythonBuildComponents, 419 android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) { 420 421 ctx.RegisterModuleType("python_defaults", DefaultsFactory) 422 }), 423 android.PrepareForTestWithAllowMissingDependencies). 424 ExtendWithErrorHandler(android.FixtureIgnoreErrors). 425 RunTestWithBp(t, bp) 426 if len(ctx.Errs) != 1 { 427 t.Errorf("Expected err setting test_only in testcase #%d: %d errs", i, len(ctx.Errs)) 428 continue 429 } 430 if !strings.Contains(ctx.Errs[0].Error(), "unrecognized property \"test_only\"") { 431 t.Errorf("ERR: %s bad bp: %s", ctx.Errs[0], bp) 432 } 433 } 434} 435 436func expectModule(t *testing.T, ctx *android.TestContext, name, variant, expectedSrcsZip string, expectedPyRunfiles []string) { 437 module := ctx.ModuleForTests(name, variant) 438 439 base, baseOk := module.Module().(*PythonLibraryModule) 440 if !baseOk { 441 t.Fatalf("%s is not Python module!", name) 442 } 443 444 actualPyRunfiles := []string{} 445 for _, path := range base.srcsPathMappings { 446 actualPyRunfiles = append(actualPyRunfiles, path.dest) 447 } 448 449 android.AssertDeepEquals(t, "pyRunfiles", expectedPyRunfiles, actualPyRunfiles) 450 451 android.AssertPathRelativeToTopEquals(t, "srcsZip", expectedSrcsZip, base.srcsZip) 452} 453 454func TestMain(m *testing.M) { 455 os.Exit(m.Run()) 456} 457