blob: 176c6eca0a5026d882214749857ca19dad3b9871 [file] [log] [blame]
Nan Zhangdb0b9a32017-02-27 10:12:13 -08001// 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"
Nan Zhangdb0b9a32017-02-27 10:12:13 -080029)
30
Nan Zhangd4e641b2017-07-12 12:55:28 -070031type pyModule struct {
Nan Zhangdb0b9a32017-02-27 10:12:13 -080032 name string
33 actualVersion string
34 pyRunfiles []string
35 depsPyRunfiles []string
36 parSpec string
37 depsParSpecs []string
38}
39
40var (
41 buildNamePrefix = "soong_python_test"
42 moduleVariantErrTemplate = "%s: module %q variant %q: "
43 pkgPathErrTemplate = moduleVariantErrTemplate +
Nan Zhangd4e641b2017-07-12 12:55:28 -070044 "pkg_path: %q must be a relative path contained in par file."
Nan Zhangdb0b9a32017-02-27 10:12:13 -080045 badIdentifierErrTemplate = moduleVariantErrTemplate +
46 "srcs: the path %q contains invalid token %q."
47 dupRunfileErrTemplate = moduleVariantErrTemplate +
48 "found two files to be placed at the same runfiles location %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) file: %q!"
53 badDataFileExtErr = moduleVariantErrTemplate + "data: found (.py) file: %q!"
54 bpFile = "Blueprints"
55
56 data = []struct {
57 desc string
58 mockFiles map[string][]byte
59
60 errors []string
Nan Zhangd4e641b2017-07-12 12:55:28 -070061 expectedBinaries []pyModule
Nan Zhangdb0b9a32017-02-27 10:12:13 -080062 }{
63 {
64 desc: "module without any src files",
65 mockFiles: map[string][]byte{
66 bpFile: []byte(`subdirs = ["dir"]`),
67 filepath.Join("dir", bpFile): []byte(
68 `python_library_host {
69 name: "lib1",
70 }`,
71 ),
72 },
73 errors: []string{
74 fmt.Sprintf(noSrcFileErr,
75 "dir/Blueprints:1:1", "lib1", "PY3"),
76 },
77 },
78 {
79 desc: "module with bad src file ext",
80 mockFiles: map[string][]byte{
81 bpFile: []byte(`subdirs = ["dir"]`),
82 filepath.Join("dir", bpFile): []byte(
83 `python_library_host {
84 name: "lib1",
85 srcs: [
86 "file1.exe",
87 ],
88 }`,
89 ),
90 "dir/file1.exe": nil,
91 },
92 errors: []string{
93 fmt.Sprintf(badSrcFileExtErr,
94 "dir/Blueprints:3:11", "lib1", "PY3", "dir/file1.exe"),
95 },
96 },
97 {
98 desc: "module with bad data file ext",
99 mockFiles: map[string][]byte{
100 bpFile: []byte(`subdirs = ["dir"]`),
101 filepath.Join("dir", bpFile): []byte(
102 `python_library_host {
103 name: "lib1",
104 srcs: [
105 "file1.py",
106 ],
107 data: [
108 "file2.py",
109 ],
110 }`,
111 ),
112 "dir/file1.py": nil,
113 "dir/file2.py": nil,
114 },
115 errors: []string{
116 fmt.Sprintf(badDataFileExtErr,
117 "dir/Blueprints:6:11", "lib1", "PY3", "dir/file2.py"),
118 },
119 },
120 {
121 desc: "module with bad pkg_path format",
122 mockFiles: map[string][]byte{
123 bpFile: []byte(`subdirs = ["dir"]`),
124 filepath.Join("dir", bpFile): []byte(
125 `python_library_host {
126 name: "lib1",
127 pkg_path: "a/c/../../",
128 srcs: [
129 "file1.py",
130 ],
131 }
132
133 python_library_host {
134 name: "lib2",
135 pkg_path: "a/c/../../../",
136 srcs: [
137 "file1.py",
138 ],
139 }
140
141 python_library_host {
142 name: "lib3",
143 pkg_path: "/a/c/../../",
144 srcs: [
145 "file1.py",
146 ],
147 }`,
148 ),
149 "dir/file1.py": nil,
150 },
151 errors: []string{
152 fmt.Sprintf(pkgPathErrTemplate,
153 "dir/Blueprints:11:15", "lib2", "PY3", "a/c/../../../"),
154 fmt.Sprintf(pkgPathErrTemplate,
155 "dir/Blueprints:19:15", "lib3", "PY3", "/a/c/../../"),
156 },
157 },
158 {
159 desc: "module with bad runfile src path format",
160 mockFiles: map[string][]byte{
161 bpFile: []byte(`subdirs = ["dir"]`),
162 filepath.Join("dir", bpFile): []byte(
163 `python_library_host {
164 name: "lib1",
165 pkg_path: "a/b/c/",
166 srcs: [
167 ".file1.py",
168 "123/file1.py",
169 "-e/f/file1.py",
170 ],
171 }`,
172 ),
173 "dir/.file1.py": nil,
174 "dir/123/file1.py": nil,
175 "dir/-e/f/file1.py": nil,
176 },
177 errors: []string{
178 fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
179 "lib1", "PY3", "runfiles/a/b/c/-e/f/file1.py", "-e"),
180 fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
181 "lib1", "PY3", "runfiles/a/b/c/.file1.py", ".file1"),
182 fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
183 "lib1", "PY3", "runfiles/a/b/c/123/file1.py", "123"),
184 },
185 },
186 {
187 desc: "module with duplicate runfile path",
188 mockFiles: map[string][]byte{
189 bpFile: []byte(`subdirs = ["dir"]`),
190 filepath.Join("dir", bpFile): []byte(
191 `python_library_host {
192 name: "lib1",
193 pkg_path: "a/b/",
194 srcs: [
195 "c/file1.py",
196 ],
197 }
198
199 python_library_host {
200 name: "lib2",
201 pkg_path: "a/b/c/",
202 srcs: [
203 "file1.py",
204 ],
205 libs: [
206 "lib1",
207 ],
208 }
209 `,
210 ),
211 "dir/c/file1.py": nil,
212 "dir/file1.py": nil,
213 },
214 errors: []string{
215 fmt.Sprintf(dupRunfileErrTemplate, "dir/Blueprints:9:6",
216 "lib2", "PY3", "runfiles/a/b/c/file1.py", "lib2", "dir/file1.py",
217 "lib1", "dir/c/file1.py"),
218 },
219 },
220 {
221 desc: "module for testing dependencies",
222 mockFiles: map[string][]byte{
223 bpFile: []byte(`subdirs = ["dir"]`),
224 filepath.Join("dir", bpFile): []byte(
Nan Zhanga3fc4ba2017-07-20 17:43:37 -0700225 `python_defaults {
226 name: "default_lib",
227 srcs: [
228 "default.py",
229 ],
230 version: {
231 py2: {
232 enabled: true,
233 srcs: [
234 "default_py2.py",
235 ],
236 },
237 py3: {
238 enabled: false,
239 srcs: [
240 "default_py3.py",
241 ],
242 },
243 },
244 }
245
246 python_library_host {
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800247 name: "lib5",
248 pkg_path: "a/b/",
249 srcs: [
250 "file1.py",
251 ],
252 version: {
253 py2: {
254 enabled: true,
255 },
256 py3: {
257 enabled: true,
258 },
259 },
260 }
261
262 python_library_host {
263 name: "lib6",
264 pkg_path: "c/d/",
265 srcs: [
266 "file2.py",
267 ],
268 libs: [
269 "lib5",
270 ],
271 }
272
273 python_binary_host {
274 name: "bin",
Nan Zhanga3fc4ba2017-07-20 17:43:37 -0700275 defaults: ["default_lib"],
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800276 pkg_path: "e/",
277 srcs: [
278 "bin.py",
279 ],
280 libs: [
281 "lib5",
282 ],
283 version: {
284 py3: {
285 enabled: true,
286 srcs: [
287 "file4.py",
288 ],
289 libs: [
290 "lib6",
291 ],
292 },
293 },
294 }`,
295 ),
Nan Zhanga3fc4ba2017-07-20 17:43:37 -0700296 filepath.Join("dir", "default.py"): nil,
297 filepath.Join("dir", "default_py2.py"): nil,
298 filepath.Join("dir", "default_py3.py"): nil,
299 filepath.Join("dir", "file1.py"): nil,
300 filepath.Join("dir", "file2.py"): nil,
301 filepath.Join("dir", "bin.py"): nil,
302 filepath.Join("dir", "file4.py"): nil,
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800303 stubTemplateHost: []byte(`PYTHON_BINARY = '%interpreter%'
304 MAIN_FILE = '%main%'`),
305 },
Nan Zhangd4e641b2017-07-12 12:55:28 -0700306 expectedBinaries: []pyModule{
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800307 {
308 name: "bin",
309 actualVersion: "PY3",
310 pyRunfiles: []string{
Nan Zhanga3fc4ba2017-07-20 17:43:37 -0700311 "runfiles/e/default.py",
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800312 "runfiles/e/bin.py",
Nan Zhanga3fc4ba2017-07-20 17:43:37 -0700313 "runfiles/e/default_py3.py",
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800314 "runfiles/e/file4.py",
315 },
316 depsPyRunfiles: []string{
317 "runfiles/a/b/file1.py",
318 "runfiles/c/d/file2.py",
319 },
320 parSpec: "-P runfiles/e -C dir/ -l @prefix@/.intermediates/dir/bin/PY3/dir_.list",
321 depsParSpecs: []string{
322 "-P runfiles/a/b -C dir/ -l @prefix@/.intermediates/dir/lib5/PY3/dir_.list",
323 "-P runfiles/c/d -C dir/ -l @prefix@/.intermediates/dir/lib6/PY3/dir_.list",
324 },
325 },
326 },
327 },
328 }
329)
330
331func TestPythonModule(t *testing.T) {
332 config, buildDir := setupBuildEnv(t)
Nan Zhangaac67d32017-06-12 10:49:42 -0700333 defer tearDownBuildEnv(buildDir)
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800334 for _, d := range data {
335 t.Run(d.desc, func(t *testing.T) {
Colin Crosscec81712017-07-13 14:43:27 -0700336 ctx := android.NewTestContext()
337 ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
338 ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
339 })
Colin Cross36242852017-06-23 15:06:31 -0700340 ctx.RegisterModuleType("python_library_host",
341 android.ModuleFactoryAdaptor(PythonLibraryHostFactory))
342 ctx.RegisterModuleType("python_binary_host",
343 android.ModuleFactoryAdaptor(PythonBinaryHostFactory))
Nan Zhanga3fc4ba2017-07-20 17:43:37 -0700344 ctx.RegisterModuleType("python_defaults",
345 android.ModuleFactoryAdaptor(defaultsFactory))
346 ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
Colin Crosscec81712017-07-13 14:43:27 -0700347 ctx.Register()
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800348 ctx.MockFileSystem(d.mockFiles)
349 _, testErrs := ctx.ParseBlueprintsFiles(bpFile)
350 fail(t, testErrs)
351 _, actErrs := ctx.PrepareBuildActions(config)
352 if len(actErrs) > 0 {
353 testErrs = append(testErrs, expectErrors(t, actErrs, d.errors)...)
354 } else {
355 for _, e := range d.expectedBinaries {
356 testErrs = append(testErrs,
357 expectModule(t, ctx, buildDir, e.name,
358 e.actualVersion,
359 e.pyRunfiles, e.depsPyRunfiles,
360 e.parSpec, e.depsParSpecs)...)
361 }
362 }
363 fail(t, testErrs)
364 })
365 }
366}
367
368func expectErrors(t *testing.T, actErrs []error, expErrs []string) (testErrs []error) {
369 actErrStrs := []string{}
370 for _, v := range actErrs {
371 actErrStrs = append(actErrStrs, v.Error())
372 }
373 sort.Strings(actErrStrs)
374 if len(actErrStrs) != len(expErrs) {
375 t.Errorf("got (%d) errors, expected (%d) errors!", len(actErrStrs), len(expErrs))
376 for _, v := range actErrStrs {
377 testErrs = append(testErrs, errors.New(v))
378 }
379 } else {
380 sort.Strings(expErrs)
381 for i, v := range actErrStrs {
382 if v != expErrs[i] {
383 testErrs = append(testErrs, errors.New(v))
384 }
385 }
386 }
387
388 return
389}
390
Colin Crosscec81712017-07-13 14:43:27 -0700391func expectModule(t *testing.T, ctx *android.TestContext, buildDir, name, variant string,
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800392 expPyRunfiles, expDepsPyRunfiles []string,
393 expParSpec string, expDepsParSpecs []string) (testErrs []error) {
Colin Crosscec81712017-07-13 14:43:27 -0700394 module := ctx.ModuleForTests(name, variant)
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800395
Nan Zhangd4e641b2017-07-12 12:55:28 -0700396 base, baseOk := module.Module().(*Module)
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800397 if !baseOk {
398 t.Fatalf("%s is not Python module!", name)
399 }
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800400
401 actPyRunfiles := []string{}
402 for _, path := range base.srcsPathMappings {
403 actPyRunfiles = append(actPyRunfiles, path.dest)
404 }
405
406 if !reflect.DeepEqual(actPyRunfiles, expPyRunfiles) {
407 testErrs = append(testErrs, errors.New(fmt.Sprintf(
408 `binary "%s" variant "%s" has unexpected pyRunfiles: %q!`,
409 base.Name(),
Nan Zhangd4e641b2017-07-12 12:55:28 -0700410 base.properties.Actual_version,
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800411 actPyRunfiles)))
412 }
413
Nan Zhangd4e641b2017-07-12 12:55:28 -0700414 if !reflect.DeepEqual(base.depsPyRunfiles, expDepsPyRunfiles) {
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800415 testErrs = append(testErrs, errors.New(fmt.Sprintf(
416 `binary "%s" variant "%s" has unexpected depsPyRunfiles: %q!`,
417 base.Name(),
Nan Zhangd4e641b2017-07-12 12:55:28 -0700418 base.properties.Actual_version,
419 base.depsPyRunfiles)))
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800420 }
421
422 if base.parSpec.soongParArgs() != strings.Replace(expParSpec, "@prefix@", buildDir, 1) {
423 testErrs = append(testErrs, errors.New(fmt.Sprintf(
424 `binary "%s" variant "%s" has unexpected parSpec: %q!`,
425 base.Name(),
Nan Zhangd4e641b2017-07-12 12:55:28 -0700426 base.properties.Actual_version,
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800427 base.parSpec.soongParArgs())))
428 }
429
430 actDepsParSpecs := []string{}
Nan Zhangd4e641b2017-07-12 12:55:28 -0700431 for i, p := range base.depsParSpecs {
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800432 actDepsParSpecs = append(actDepsParSpecs, p.soongParArgs())
433 expDepsParSpecs[i] = strings.Replace(expDepsParSpecs[i], "@prefix@", buildDir, 1)
434 }
435
436 if !reflect.DeepEqual(actDepsParSpecs, expDepsParSpecs) {
437 testErrs = append(testErrs, errors.New(fmt.Sprintf(
438 `binary "%s" variant "%s" has unexpected depsParSpecs: %q!`,
439 base.Name(),
Nan Zhangd4e641b2017-07-12 12:55:28 -0700440 base.properties.Actual_version,
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800441 actDepsParSpecs)))
442 }
443
444 return
445}
446
447func setupBuildEnv(t *testing.T) (config android.Config, buildDir string) {
448 buildDir, err := ioutil.TempDir("", buildNamePrefix)
449 if err != nil {
450 t.Fatal(err)
451 }
452
Colin Cross6ccbc912017-10-10 23:07:38 -0700453 config = android.TestConfig(buildDir, nil)
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800454
455 return
456}
457
Nan Zhangaac67d32017-06-12 10:49:42 -0700458func tearDownBuildEnv(buildDir string) {
459 os.RemoveAll(buildDir)
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800460}
461
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800462func fail(t *testing.T, errs []error) {
463 if len(errs) > 0 {
464 for _, err := range errs {
465 t.Error(err)
466 }
467 t.FailNow()
468 }
469}