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