• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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