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