github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/soong/python/python_test.go (about)

     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  
    15  package python
    16  
    17  import (
    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  
    31  type pyModule struct {
    32  	name           string
    33  	actualVersion  string
    34  	pyRunfiles     []string
    35  	depsPyRunfiles []string
    36  	parSpec        string
    37  	depsParSpecs   []string
    38  }
    39  
    40  var (
    41  	buildNamePrefix          = "soong_python_test"
    42  	moduleVariantErrTemplate = "%s: module %q variant %q: "
    43  	pkgPathErrTemplate       = moduleVariantErrTemplate +
    44  		"pkg_path: %q must be a relative path contained in par file."
    45  	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
    61  		expectedBinaries []pyModule
    62  	}{
    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(
   225  					`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 {
   247  						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",
   275  						defaults: ["default_lib"],
   276  						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  				),
   296  				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,
   303  				stubTemplateHost: []byte(`PYTHON_BINARY = '%interpreter%'
   304  				MAIN_FILE = '%main%'`),
   305  			},
   306  			expectedBinaries: []pyModule{
   307  				{
   308  					name:          "bin",
   309  					actualVersion: "PY3",
   310  					pyRunfiles: []string{
   311  						"runfiles/e/default.py",
   312  						"runfiles/e/bin.py",
   313  						"runfiles/e/default_py3.py",
   314  						"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  
   331  func TestPythonModule(t *testing.T) {
   332  	config, buildDir := setupBuildEnv(t)
   333  	defer tearDownBuildEnv(buildDir)
   334  	for _, d := range data {
   335  		t.Run(d.desc, func(t *testing.T) {
   336  			ctx := android.NewTestContext()
   337  			ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
   338  				ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
   339  			})
   340  			ctx.RegisterModuleType("python_library_host",
   341  				android.ModuleFactoryAdaptor(PythonLibraryHostFactory))
   342  			ctx.RegisterModuleType("python_binary_host",
   343  				android.ModuleFactoryAdaptor(PythonBinaryHostFactory))
   344  			ctx.RegisterModuleType("python_defaults",
   345  				android.ModuleFactoryAdaptor(defaultsFactory))
   346  			ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
   347  			ctx.Register()
   348  			ctx.MockFileSystem(d.mockFiles)
   349  			_, testErrs := ctx.ParseBlueprintsFiles(bpFile)
   350  			android.FailIfErrored(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  			android.FailIfErrored(t, testErrs)
   364  		})
   365  	}
   366  }
   367  
   368  func 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  
   391  func expectModule(t *testing.T, ctx *android.TestContext, buildDir, name, variant string,
   392  	expPyRunfiles, expDepsPyRunfiles []string,
   393  	expParSpec string, expDepsParSpecs []string) (testErrs []error) {
   394  	module := ctx.ModuleForTests(name, variant)
   395  
   396  	base, baseOk := module.Module().(*Module)
   397  	if !baseOk {
   398  		t.Fatalf("%s is not Python module!", name)
   399  	}
   400  
   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(),
   410  			base.properties.Actual_version,
   411  			actPyRunfiles)))
   412  	}
   413  
   414  	if !reflect.DeepEqual(base.depsPyRunfiles, expDepsPyRunfiles) {
   415  		testErrs = append(testErrs, errors.New(fmt.Sprintf(
   416  			`binary "%s" variant "%s" has unexpected depsPyRunfiles: %q!`,
   417  			base.Name(),
   418  			base.properties.Actual_version,
   419  			base.depsPyRunfiles)))
   420  	}
   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(),
   426  			base.properties.Actual_version,
   427  			base.parSpec.soongParArgs())))
   428  	}
   429  
   430  	actDepsParSpecs := []string{}
   431  	for i, p := range base.depsParSpecs {
   432  		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(),
   440  			base.properties.Actual_version,
   441  			actDepsParSpecs)))
   442  	}
   443  
   444  	return
   445  }
   446  
   447  func 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  
   453  	config = android.TestConfig(buildDir, nil)
   454  
   455  	return
   456  }
   457  
   458  func tearDownBuildEnv(buildDir string) {
   459  	os.RemoveAll(buildDir)
   460  }