go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/vpython/spec/load_test.go (about)

     1  // Copyright 2017 The LUCI Authors.
     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 spec
    16  
    17  import (
    18  	"context"
    19  	"path/filepath"
    20  	"runtime"
    21  	"strings"
    22  	"testing"
    23  
    24  	"google.golang.org/protobuf/encoding/prototext"
    25  
    26  	"go.chromium.org/luci/common/testing/testfs"
    27  
    28  	"go.chromium.org/luci/vpython/api/vpython"
    29  
    30  	. "github.com/smartystreets/goconvey/convey"
    31  	. "go.chromium.org/luci/common/testing/assertions"
    32  )
    33  
    34  func TestLoadForScript(t *testing.T) {
    35  	t.Parallel()
    36  
    37  	goodSpec := &vpython.Spec{
    38  		PythonVersion: "3.4.0",
    39  		Wheel: []*vpython.Spec_Package{
    40  			{Name: "foo/bar", Version: "1"},
    41  			{Name: "baz/qux", Version: "2"},
    42  		},
    43  	}
    44  	goodSpecData := prototext.Format(goodSpec)
    45  	badSpecData := "foo: bar"
    46  
    47  	l := Loader{
    48  		CommonFilesystemBarriers: []string{"BARRIER"},
    49  	}
    50  
    51  	Convey(`Test LoadForScript`, t, func() {
    52  		tdir := t.TempDir()
    53  		c := context.Background()
    54  
    55  		// On OSX and Linux the temp dir may be in a symlinked directory (and on OSX
    56  		// it most often is). LoadForScript expands symlinks, so we need to expand
    57  		// them here too to make path string comparisons below pass.
    58  		if runtime.GOOS != "windows" {
    59  			var err error
    60  			if tdir, err = filepath.EvalSymlinks(tdir); err != nil {
    61  				panic(err)
    62  			}
    63  		}
    64  
    65  		makePath := func(path string) string {
    66  			return filepath.Join(tdir, filepath.FromSlash(path))
    67  		}
    68  		mustBuild := func(layout map[string]string) {
    69  			if err := testfs.Build(tdir, layout); err != nil {
    70  				panic(err)
    71  			}
    72  		}
    73  
    74  		Convey(`Layout: module with a good spec file`, func() {
    75  			mustBuild(map[string]string{
    76  				"foo/bar/baz/__main__.py": "main",
    77  				"foo/bar/baz/__init__.py": "",
    78  				"foo/bar/__init__.py":     "",
    79  				"foo/bar.vpython":         goodSpecData,
    80  			})
    81  			spec, path, err := l.LoadForScript(c, makePath("foo/bar/baz"), true)
    82  			So(err, ShouldBeNil)
    83  			So(spec, ShouldResembleProto, goodSpec)
    84  			So(path, ShouldEqual, makePath("foo/bar.vpython"))
    85  		})
    86  
    87  		Convey(`Layout: module with a bad spec file`, func() {
    88  			mustBuild(map[string]string{
    89  				"foo/bar/baz/__main__.py": "main",
    90  				"foo/bar/baz/__init__.py": "",
    91  				"foo/bar/__init__.py":     "",
    92  				"foo/bar.vpython":         badSpecData,
    93  			})
    94  			_, _, err := l.LoadForScript(c, makePath("foo/bar/baz"), true)
    95  			So(err, ShouldErrLike, "failed to unmarshal vpython.Spec")
    96  		})
    97  
    98  		Convey(`Layout: module with no spec file`, func() {
    99  			mustBuild(map[string]string{
   100  				"foo/bar/baz/__main__.py": "main",
   101  				"foo/bar/baz/__init__.py": "",
   102  				"foo/bar/__init__.py":     "",
   103  				"foo/__init__.py":         "",
   104  			})
   105  			spec, path, err := l.LoadForScript(c, makePath("foo/bar/baz"), true)
   106  			So(err, ShouldBeNil)
   107  			So(spec, ShouldBeNil)
   108  			So(path, ShouldEqual, "")
   109  		})
   110  
   111  		Convey(`Layout: individual file with a good spec file`, func() {
   112  			mustBuild(map[string]string{
   113  				"pants.py":         "PANTS!",
   114  				"pants.py.vpython": goodSpecData,
   115  			})
   116  			spec, path, err := l.LoadForScript(c, makePath("pants.py"), false)
   117  			So(err, ShouldBeNil)
   118  			So(spec, ShouldResembleProto, goodSpec)
   119  			So(path, ShouldEqual, makePath("pants.py.vpython"))
   120  		})
   121  
   122  		Convey(`Layout: individual file with a bad spec file`, func() {
   123  			mustBuild(map[string]string{
   124  				"pants.py":         "PANTS!",
   125  				"pants.py.vpython": badSpecData,
   126  			})
   127  			_, _, err := l.LoadForScript(c, makePath("pants.py"), false)
   128  			So(err, ShouldErrLike, "failed to unmarshal vpython.Spec")
   129  		})
   130  
   131  		Convey(`Layout: individual file with no spec (inline or file)`, func() {
   132  			mustBuild(map[string]string{
   133  				"pants.py": "PANTS!",
   134  			})
   135  			spec, path, err := l.LoadForScript(c, makePath("pants.py"), false)
   136  			So(err, ShouldBeNil)
   137  			So(spec, ShouldBeNil)
   138  			So(path, ShouldBeEmpty)
   139  		})
   140  
   141  		Convey(`Layout: module with good inline spec`, func() {
   142  			mustBuild(map[string]string{
   143  				"foo/bar/baz/__main__.py": strings.Join([]string{
   144  					"#!/usr/bin/env vpython",
   145  					"",
   146  					"# Test file",
   147  					"",
   148  					`"""Docstring`,
   149  					"[VPYTHON:BEGIN]",
   150  					goodSpecData,
   151  					"[VPYTHON:END]",
   152  					`"""`,
   153  					"",
   154  					"# Additional content...",
   155  				}, "\n"),
   156  			})
   157  			spec, path, err := l.LoadForScript(c, makePath("foo/bar/baz"), true)
   158  			So(err, ShouldBeNil)
   159  			So(spec, ShouldResembleProto, goodSpec)
   160  			So(path, ShouldEqual, makePath("foo/bar/baz/__main__.py"))
   161  		})
   162  
   163  		Convey(`Layout: individual file with good inline spec`, func() {
   164  			mustBuild(map[string]string{
   165  				"pants.py": strings.Join([]string{
   166  					"#!/usr/bin/env vpython",
   167  					"",
   168  					"# Test file",
   169  					"",
   170  					`"""Docstring`,
   171  					"[VPYTHON:BEGIN]",
   172  					goodSpecData,
   173  					"[VPYTHON:END]",
   174  					`"""`,
   175  					"",
   176  					"# Additional content...",
   177  				}, "\n"),
   178  			})
   179  			spec, path, err := l.LoadForScript(c, makePath("pants.py"), false)
   180  			So(err, ShouldBeNil)
   181  			So(spec, ShouldResembleProto, goodSpec)
   182  			So(path, ShouldEqual, makePath("pants.py"))
   183  		})
   184  
   185  		Convey(`Layout: individual file with good inline spec with a prefix`, func() {
   186  			specParts := strings.Split(goodSpecData, "\n")
   187  			for i, line := range specParts {
   188  				specParts[i] = strings.TrimSpace("# " + line)
   189  			}
   190  
   191  			mustBuild(map[string]string{
   192  				"pants.py": strings.Join([]string{
   193  					"#!/usr/bin/env vpython",
   194  					"",
   195  					"# Test file",
   196  					"#",
   197  					"# [VPYTHON:BEGIN]",
   198  					strings.Join(specParts, "\n"),
   199  					"# [VPYTHON:END]",
   200  					"",
   201  					"# Additional content...",
   202  				}, "\n"),
   203  			})
   204  
   205  			spec, path, err := l.LoadForScript(c, makePath("pants.py"), false)
   206  			So(err, ShouldBeNil)
   207  			So(spec, ShouldResembleProto, goodSpec)
   208  			So(path, ShouldEqual, makePath("pants.py"))
   209  		})
   210  
   211  		Convey(`Layout: individual file with bad inline spec`, func() {
   212  			mustBuild(map[string]string{
   213  				"pants.py": strings.Join([]string{
   214  					"#!/usr/bin/env vpython",
   215  					"",
   216  					"# Test file",
   217  					"",
   218  					`"""Docstring`,
   219  					"[VPYTHON:BEGIN]",
   220  					badSpecData,
   221  					"[VPYTHON:END]",
   222  					`"""`,
   223  					"",
   224  					"# Additional content...",
   225  				}, "\n"),
   226  			})
   227  
   228  			_, _, err := l.LoadForScript(c, makePath("pants.py"), false)
   229  			So(err, ShouldErrLike, "failed to unmarshal vpython.Spec")
   230  		})
   231  
   232  		Convey(`Layout: individual file with inline spec, missing end barrier`, func() {
   233  			mustBuild(map[string]string{
   234  				"pants.py": strings.Join([]string{
   235  					"#!/usr/bin/env vpython",
   236  					"",
   237  					"# Test file",
   238  					"",
   239  					`"""Docstring`,
   240  					"[VPYTHON:BEGIN]",
   241  					goodSpecData,
   242  					`"""`,
   243  					"",
   244  					"# Additional content...",
   245  				}, "\n"),
   246  			})
   247  
   248  			_, _, err := l.LoadForScript(c, makePath("pants.py"), false)
   249  			So(err, ShouldErrLike, "unterminated inline spec file")
   250  		})
   251  
   252  		Convey(`Layout: individual file with a common spec`, func() {
   253  			mustBuild(map[string]string{
   254  				"foo/bar/baz.py":      "main",
   255  				"foo/bar/__init__.py": "",
   256  				"common.vpython":      goodSpecData,
   257  			})
   258  
   259  			spec, path, err := l.LoadForScript(c, makePath("foo/bar/baz.py"), false)
   260  			So(err, ShouldBeNil)
   261  			So(spec, ShouldResembleProto, goodSpec)
   262  			So(path, ShouldEqual, makePath("common.vpython"))
   263  		})
   264  
   265  		Convey(`Layout: individual file with a custom common spec`, func() {
   266  			l.CommonSpecNames = []string{"ohaithere.friend", "common.vpython"}
   267  
   268  			mustBuild(map[string]string{
   269  				"foo/bar/baz.py":      "main",
   270  				"foo/bar/__init__.py": "",
   271  				"common.vpython":      badSpecData,
   272  				"ohaithere.friend":    goodSpecData,
   273  			})
   274  
   275  			spec, path, err := l.LoadForScript(c, makePath("foo/bar/baz.py"), false)
   276  			So(err, ShouldBeNil)
   277  			So(spec, ShouldResembleProto, goodSpec)
   278  			So(path, ShouldEqual, makePath("ohaithere.friend"))
   279  		})
   280  
   281  		Convey(`Layout: individual file with a common spec behind a barrier`, func() {
   282  			mustBuild(map[string]string{
   283  				"foo/bar/baz.py":      "main",
   284  				"foo/bar/__init__.py": "",
   285  				"foo/BARRIER":         "",
   286  				"common.vpython":      goodSpecData,
   287  			})
   288  
   289  			spec, path, err := l.LoadForScript(c, makePath("foo/bar/baz.py"), false)
   290  			So(err, ShouldBeNil)
   291  			So(spec, ShouldBeNil)
   292  			So(path, ShouldBeEmpty)
   293  		})
   294  
   295  		Convey(`Layout: individual file with a spec and barrier sharing a folder`, func() {
   296  			mustBuild(map[string]string{
   297  				"foo/bar/baz.py":      "main",
   298  				"foo/bar/__init__.py": "",
   299  				"BARRIER":             "",
   300  				"common.vpython":      goodSpecData,
   301  			})
   302  
   303  			spec, path, err := l.LoadForScript(c, makePath("foo/bar/baz.py"), false)
   304  			So(err, ShouldBeNil)
   305  			So(spec, ShouldResembleProto, goodSpec)
   306  			So(path, ShouldEqual, makePath("common.vpython"))
   307  		})
   308  
   309  		Convey(`Layout: module with a common spec`, func() {
   310  			mustBuild(map[string]string{
   311  				"foo/bar/baz/__main__.py": "main",
   312  				"foo/bar/baz/__init__.py": "",
   313  				"foo/bar/__init__.py":     "",
   314  				"common.vpython":          goodSpecData,
   315  			})
   316  
   317  			spec, path, err := l.LoadForScript(c, makePath("foo/bar/baz"), true)
   318  			So(err, ShouldBeNil)
   319  			So(spec, ShouldResembleProto, goodSpec)
   320  			So(path, ShouldEqual, makePath("common.vpython"))
   321  		})
   322  
   323  		Convey(`Layout: individual file with a bad common spec`, func() {
   324  			mustBuild(map[string]string{
   325  				"foo/bar/baz.py":      "main",
   326  				"foo/bar/__init__.py": "",
   327  				"common.vpython":      badSpecData,
   328  			})
   329  
   330  			_, _, err := l.LoadForScript(c, makePath("foo/bar/baz.py"), false)
   331  			So(err, ShouldErrLike, "failed to unmarshal vpython.Spec")
   332  		})
   333  	})
   334  }