Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Side by Side Diff: vpython/venv/venv_test.go

Issue 2699063004: vpython: Add VirtualEnv creation package. (Closed)
Patch Set: remake binaries Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« vpython/venv/venv.go ('K') | « vpython/venv/venv.go ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2017 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package venv
6
7 import (
8 "archive/zip"
9 "encoding/json"
10 "fmt"
11 "io"
12 "io/ioutil"
13 "os"
14 "path/filepath"
15 "strings"
16 "testing"
17
18 "github.com/luci/luci-go/vpython/api/env"
19 "github.com/luci/luci-go/vpython/filesystem"
20 "github.com/luci/luci-go/vpython/filesystem/testfs"
21 "github.com/luci/luci-go/vpython/python"
22
23 "github.com/luci/luci-go/common/errors"
24
25 "golang.org/x/net/context"
26
27 . "github.com/luci/luci-go/common/testing/assertions"
28 . "github.com/smartystreets/goconvey/convey"
29 )
30
31 const testDataDir = "test_data"
32
33 type resolvedInterpreter struct {
34 i *python.Interpreter
35 version python.Version
36 }
37
38 func resolveFromPath(vers python.Version) *resolvedInterpreter {
39 c := context.Background()
40 i, err := python.Find(c, vers)
41 if err != nil {
42 return nil
43 }
44 if err := filesystem.AbsPath(&i.Python); err != nil {
45 panic(err)
46 }
47
48 ri := resolvedInterpreter{
49 i: i,
50 }
51 if ri.version, err = ri.i.GetVersion(c); err != nil {
52 panic(err)
53 }
54 return &ri
55 }
56
57 var (
58 pythonGeneric = resolveFromPath(python.Version{})
59 python27 = resolveFromPath(python.Version{2, 7, 0})
60 python3 = resolveFromPath(python.Version{3, 0, 0})
61 )
62
63 func TestResolvePythonInterpreter(t *testing.T) {
64 t.Parallel()
65
66 Convey(`Resolving a Python interpreter`, t, func() {
67 c := context.Background()
68 cfg := Config{
69 Spec: &env.Spec{},
70 }
71
72 // Tests to run if we have Python 2.7 installed.
73 if python27 != nil {
74 Convey(`When Python 2.7 is requested, it gets resolved.` , func() {
75 cfg.Spec.PythonVersion = "2.7"
76 So(cfg.resolvePythonInterpreter(c), ShouldBeNil)
77 So(cfg.Python, ShouldEqual, python27.i.Python)
78
79 vers, err := python.ParseVersion(cfg.Spec.Python Version)
80 So(err, ShouldBeNil)
81 So(vers.IsSatisfiedBy(python27.version), ShouldB eTrue)
82 })
83
84 Convey(`Fails when Python 9999 is requested, but a Pytho n 2 interpreter is forced.`, func() {
85 cfg.Python = python27.i.Python
86 cfg.Spec.PythonVersion = "9999"
87 So(cfg.resolvePythonInterpreter(c), ShouldErrLik e, "doesn't match specification")
88 })
89 }
90
91 // Tests to run if we have Python 2.7 and a generic Python insta lled.
92 if pythonGeneric != nil && python27 != nil {
93 // Our generic Python resolves to a known version, so we can proceed.
94 Convey(`When no Python version is specified, spec resolv es to generic.`, func() {
95 So(cfg.resolvePythonInterpreter(c), ShouldBeNil)
96 So(cfg.Python, ShouldEqual, pythonGeneric.i.Pyth on)
97
98 vers, err := python.ParseVersion(cfg.Spec.Python Version)
99 So(err, ShouldBeNil)
100 So(vers.IsSatisfiedBy(pythonGeneric.version), Sh ouldBeTrue)
101 })
102 }
103
104 // Tests to run if we have Python 3 installed.
105 if python3 != nil {
106 Convey(`When Python 3 is requested, it gets resolved.`, func() {
107 cfg.Spec.PythonVersion = "3"
108 So(cfg.resolvePythonInterpreter(c), ShouldBeNil)
109 So(cfg.Python, ShouldEqual, python3.i.Python)
110
111 vers, err := python.ParseVersion(cfg.Spec.Python Version)
112 So(err, ShouldBeNil)
113 So(vers.IsSatisfiedBy(python3.version), ShouldBe True)
114 })
115
116 Convey(`Fails when Python 9999 is requested, but a Pytho n 3 interpreter is forced.`, func() {
117 cfg.Python = python3.i.Python
118 cfg.Spec.PythonVersion = "9999"
119 So(cfg.resolvePythonInterpreter(c), ShouldErrLik e, "doesn't match specification")
120 })
121 }
122 })
123 }
124
125 // testingPackageLoader is a map of a CIPD package name to the root directory
126 // that it should be loaded from.
127 type testingPackageLoader map[string]string
128
129 func (pl testingPackageLoader) Resolve(c context.Context, root string, packages []*env.Spec_Package) error {
130 for _, pkg := range packages {
131 pkg.Version = "resolved"
132 }
133 return nil
134 }
135
136 func (pl testingPackageLoader) Ensure(c context.Context, root string, packages [ ]*env.Spec_Package) error {
137 for _, pkg := range packages {
138 if err := pl.installPackage(pkg.Path, root); err != nil {
139 return err
140 }
141 }
142 return nil
143 }
144
145 func (pl testingPackageLoader) installPackage(name, root string) error {
146 testName := pl[name]
147 if testName == "" {
148 return errors.Reason("could not resolve package for %(name)q").
149 D("name", name).
150 Err()
151 }
152 sourcePath := filepath.Join(testDataDir, testName)
153
154 switch st, err := os.Stat(sourcePath); {
155 case err != nil:
156 return errors.Annotate(err).Reason("could not stat source: %(sou rce)s").
157 D("source", sourcePath).
158 Err()
159
160 case st.IsDir():
161 if err := recursiveCopyDir(sourcePath, root); err != nil {
162 return errors.Annotate(err).Reason("failed to recursivel y copy").Err()
163 }
164
165 case strings.HasSuffix(sourcePath, ".zip"):
166 // If it's a file, it's a ZIP file. Unpack it into destination.
167 if err := unzip(sourcePath, root); err != nil {
168 return errors.Annotate(err).Reason("failed to un-zip arc hive").Err()
169 }
170
171 default:
172 return errors.Reason("don't know how to handle: %(path)s").
173 D("path", sourcePath).
174 Err()
175 }
176 return nil
177 }
178
179 func recursiveCopyDir(src, dst string) error {
180 // Recursively copy from sourcePath to root.
181 return filepath.Walk(src, func(path string, fi os.FileInfo, err error) e rror {
182 if err != nil || path == src {
183 return err
184 }
185 rel, err := filepath.Rel(src, path)
186 if err != nil {
187 return errors.Annotate(err).Reason("failed to get relati ve path").Err()
188 }
189
190 dst := filepath.Join(dst, rel)
191
192 opener := func() (io.ReadCloser, error) { return os.Open(path) }
193 if err := copyFileOrDir(opener, dst, fi); err != nil {
194 return errors.Annotate(err).Reason("failed to copy: [%(s rc)s] => [%(dst)s]").
195 D("src", path).
196 D("dst", dst).
197 Err()
198 }
199 return nil
200 })
201 }
202
203 func unzip(src, dst string) error {
204 fd, err := zip.OpenReader(src)
205 if err != nil {
206 return errors.Annotate(err).Reason("failed to open ZIP reader"). Err()
207 }
208 defer fd.Close()
209
210 for _, f := range fd.File {
211 if err := copyFileOrDir(f.Open, filepath.Join(dst, f.Name), f.Fi leInfo()); err != nil {
212 return errors.Annotate(err).Reason("failed to extract fi le: %(name)s").
213 D("name", f.Name).
214 Err()
215 }
216 }
217 return nil
218 }
219
220 // copyFile copies a source file and its mode to a destination.
221 func copyFileOrDir(opener func() (io.ReadCloser, error), dst string, fi os.FileI nfo) error {
222 if fi.IsDir() {
223 if err := os.MkdirAll(dst, 0755); err != nil {
224 return errors.Annotate(err).Reason("failed to mkdir").Er r()
225 }
226 return nil
227 }
228
229 srcFD, err := opener()
230 if err != nil {
231 return errors.Annotate(err).Reason("failed to create source").Er r()
232 }
233 defer srcFD.Close()
234
235 dstFD, err := os.Create(dst)
236 if err != nil {
237 return errors.Annotate(err).Reason("failed to create dest").Err( )
238 }
239 defer dstFD.Close()
240
241 if _, err := io.Copy(dstFD, srcFD); err != nil {
242 return errors.Annotate(err).Reason("failed to copy").Err()
243 }
244 if err := os.Chmod(dst, fi.Mode()); err != nil {
245 return errors.Annotate(err).Reason("failed to chmod").Err()
246 }
247 return nil
248 }
249
250 type setupCheckManifest struct {
251 Interpreter string `json:"interpreter"`
252 Pants string `json:"pants"`
253 Shirt string `json:"shirt"`
254 }
255
256 func TestVirtualEnv(t *testing.T) {
257 t.Parallel()
258
259 for _, tc := range []struct {
260 name string
261 ri *resolvedInterpreter
262 }{
263 {"python27", python27},
264 {"python3", python3},
265 } {
266 tc := tc
267 t.Run(fmt.Sprintf(`Testing Virtualenv for: %s`, tc.name), func(t *testing.T) {
268 t.Parallel()
269
270 conveyOp := Convey
271 if tc.ri == nil {
272 // No interpreter found, skip this test.
273 conveyOp = SkipConvey
274 }
275 conveyOp(`Testing Setup`, t, testfs.MustWithTempDir(t, " TestVirtualEnv", func(tdir string) {
276 c := context.Background()
277 config := Config{
278 BaseDir: tdir,
279 MaxHashLen: 4,
280 Package: env.Spec_Package{
281 Path: "foo/bar/virtualenv",
282 Version: "unresolved",
283 },
284 Python: tc.ri.i.Python,
285 Spec: &env.Spec{
286 Wheel: []*env.Spec_Package{
287 {Path: "foo/bar/shirt", Version: "unresolved"},
288 {Path: "foo/bar/pants", Version: "unresolved"},
289 },
290 },
291 Loader: testingPackageLoader{
292 "foo/bar/virtualenv": "virtualen v-15.1.0.zip",
293 "foo/bar/shirt": "shirt",
294 "foo/bar/pants": "pants",
295 },
296 }
297 v, err := config.Env(c)
298 So(err, ShouldBeNil)
299
300 // The setup should be successful.
301 So(v.Setup(c, false), ShouldBeNil)
302
303 testScriptPath := filepath.Join(testDataDir, "se tup_check.py")
304 checkOut := filepath.Join(tdir, "output.json")
305 i := v.InterpreterCommand()
306 So(i.Run(c, testScriptPath, "--json-output", che ckOut), ShouldBeNil)
307
308 var m setupCheckManifest
309 So(loadJSON(checkOut, &m), ShouldBeNil)
310 So(m.Interpreter, ShouldStartWith, v.Root)
311 So(m.Pants, ShouldStartWith, v.Root)
312 So(m.Shirt, ShouldStartWith, v.Root)
313
314 // We should be able to delete it.
315 So(v.Delete(c), ShouldBeNil)
316 }))
317 })
318 }
319 }
320
321 func loadJSON(path string, dst interface{}) error {
322 content, err := ioutil.ReadFile(path)
323 if err != nil {
324 return errors.Annotate(err).Reason("failed to open file").Err()
325 }
326 if err := json.Unmarshal(content, dst); err != nil {
327 return errors.Annotate(err).Reason("failed to unmarshal JSON").E rr()
328 }
329 return nil
330 }
OLDNEW
« vpython/venv/venv.go ('K') | « vpython/venv/venv.go ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698