blob: 57aaa344df8179bb009ad3cd173f57642053c4a7 [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)
Nan Zhangaac67d32017-06-12 10:49:42 -0700308 defer tearDownBuildEnv(buildDir)
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800309 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)
Colin Cross36242852017-06-23 15:06:31 -0700316 ctx.RegisterModuleType("python_library_host",
317 android.ModuleFactoryAdaptor(PythonLibraryHostFactory))
318 ctx.RegisterModuleType("python_binary_host",
319 android.ModuleFactoryAdaptor(PythonBinaryHostFactory))
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800320 ctx.MockFileSystem(d.mockFiles)
321 _, testErrs := ctx.ParseBlueprintsFiles(bpFile)
322 fail(t, testErrs)
323 _, actErrs := ctx.PrepareBuildActions(config)
324 if len(actErrs) > 0 {
325 testErrs = append(testErrs, expectErrors(t, actErrs, d.errors)...)
326 } else {
327 for _, e := range d.expectedBinaries {
328 testErrs = append(testErrs,
329 expectModule(t, ctx, buildDir, e.name,
330 e.actualVersion,
331 e.pyRunfiles, e.depsPyRunfiles,
332 e.parSpec, e.depsParSpecs)...)
333 }
334 }
335 fail(t, testErrs)
336 })
337 }
338}
339
340func expectErrors(t *testing.T, actErrs []error, expErrs []string) (testErrs []error) {
341 actErrStrs := []string{}
342 for _, v := range actErrs {
343 actErrStrs = append(actErrStrs, v.Error())
344 }
345 sort.Strings(actErrStrs)
346 if len(actErrStrs) != len(expErrs) {
347 t.Errorf("got (%d) errors, expected (%d) errors!", len(actErrStrs), len(expErrs))
348 for _, v := range actErrStrs {
349 testErrs = append(testErrs, errors.New(v))
350 }
351 } else {
352 sort.Strings(expErrs)
353 for i, v := range actErrStrs {
354 if v != expErrs[i] {
355 testErrs = append(testErrs, errors.New(v))
356 }
357 }
358 }
359
360 return
361}
362
363func expectModule(t *testing.T, ctx *blueprint.Context, buildDir, name, variant string,
364 expPyRunfiles, expDepsPyRunfiles []string,
365 expParSpec string, expDepsParSpecs []string) (testErrs []error) {
366 module := findModule(ctx, name, variant)
367 if module == nil {
368 t.Fatalf("failed to find module %s!", name)
369 }
370
371 base, baseOk := module.(*pythonBaseModule)
372 if !baseOk {
373 t.Fatalf("%s is not Python module!", name)
374 }
Nan Zhang5323f8e2017-05-10 13:37:54 -0700375 sub, subOk := base.subModule.(*pythonBinaryBase)
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800376 if !subOk {
377 t.Fatalf("%s is not Python binary!", name)
378 }
379
380 actPyRunfiles := []string{}
381 for _, path := range base.srcsPathMappings {
382 actPyRunfiles = append(actPyRunfiles, path.dest)
383 }
384
385 if !reflect.DeepEqual(actPyRunfiles, expPyRunfiles) {
386 testErrs = append(testErrs, errors.New(fmt.Sprintf(
387 `binary "%s" variant "%s" has unexpected pyRunfiles: %q!`,
388 base.Name(),
389 base.properties.ActualVersion,
390 actPyRunfiles)))
391 }
392
393 if !reflect.DeepEqual(sub.depsPyRunfiles, expDepsPyRunfiles) {
394 testErrs = append(testErrs, errors.New(fmt.Sprintf(
395 `binary "%s" variant "%s" has unexpected depsPyRunfiles: %q!`,
396 base.Name(),
397 base.properties.ActualVersion,
398 sub.depsPyRunfiles)))
399 }
400
401 if base.parSpec.soongParArgs() != strings.Replace(expParSpec, "@prefix@", buildDir, 1) {
402 testErrs = append(testErrs, errors.New(fmt.Sprintf(
403 `binary "%s" variant "%s" has unexpected parSpec: %q!`,
404 base.Name(),
405 base.properties.ActualVersion,
406 base.parSpec.soongParArgs())))
407 }
408
409 actDepsParSpecs := []string{}
410 for i, p := range sub.depsParSpecs {
411 actDepsParSpecs = append(actDepsParSpecs, p.soongParArgs())
412 expDepsParSpecs[i] = strings.Replace(expDepsParSpecs[i], "@prefix@", buildDir, 1)
413 }
414
415 if !reflect.DeepEqual(actDepsParSpecs, expDepsParSpecs) {
416 testErrs = append(testErrs, errors.New(fmt.Sprintf(
417 `binary "%s" variant "%s" has unexpected depsParSpecs: %q!`,
418 base.Name(),
419 base.properties.ActualVersion,
420 actDepsParSpecs)))
421 }
422
423 return
424}
425
426func setupBuildEnv(t *testing.T) (config android.Config, buildDir string) {
427 buildDir, err := ioutil.TempDir("", buildNamePrefix)
428 if err != nil {
429 t.Fatal(err)
430 }
431
432 config = android.TestConfig(buildDir)
433
434 return
435}
436
Nan Zhangaac67d32017-06-12 10:49:42 -0700437func tearDownBuildEnv(buildDir string) {
438 os.RemoveAll(buildDir)
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800439}
440
441func findModule(ctx *blueprint.Context, name, variant string) blueprint.Module {
442 var ret blueprint.Module
443 ctx.VisitAllModules(func(m blueprint.Module) {
444 if ctx.ModuleName(m) == name && ctx.ModuleSubDir(m) == variant {
445 ret = m
446 }
447 })
448 return ret
449}
450
451func fail(t *testing.T, errs []error) {
452 if len(errs) > 0 {
453 for _, err := range errs {
454 t.Error(err)
455 }
456 t.FailNow()
457 }
458}