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