func TestErrorMetadata(t *testing.T) { var checkParseErr = func(path string, expErr string) { sc := scanner.Scanner{ Position: scanner.Position{ Filename: path, }, } f, err := util.Open(path) if err != nil { t.Errorf("Couldn't open %s", path) } _, err = parse(*sc.Init(f)) if err.Error() != expErr { t.Errorf("Expected \"%s\"\ngot \"%s\"", expErr, err) return } } var checkEvalErr = func(path string, expErr string) { sc := scanner.Scanner{ Position: scanner.Position{ Filename: path, }, } f, err := util.Open(path) if err != nil { t.Errorf("Couldn't open %s", path) } parsed, err := parse(*sc.Init(f)) if err != nil { t.Errorf("Unexpected parse error: %s", parsed) } _, _, err = eval(astRoot(parsed)) if err.Error() != expErr { t.Errorf("Expected \"%s\"\ngot \"%s\"", expErr, err) return } } util.AppFs = afero.NewMemMapFs() util.WriteFile("paren.spec", []byte(` // This is a comment. (define Test "abc"`), 0644) checkParseErr("paren.spec", "paren.spec:3: unbalanced Parenthesis") util.WriteFile("undefined.spec", []byte(` (+ 1 b)`), 0644) checkEvalErr("undefined.spec", "undefined.spec:2: unassigned variable: b") util.WriteFile("bad_type.spec", []byte(` (define a "1") (+ 1 a)`), 0644) checkEvalErr("bad_type.spec", `bad_type.spec:3: bad arithmetic argument: "1"`) // Test that functions which evaluate generated S-expressions still have proper // error messages. util.WriteFile("generated_sexp.spec", []byte(`(apply + (list 1 "1"))`), 0644) checkEvalErr("generated_sexp.spec", `generated_sexp.spec:1: bad arithmetic argument: "1"`) }
func testCheckSpec(t *testing.T) { initVCSFunc() os.Setenv("QUILT_PATH", ".") util.AppFs = afero.NewMemMapFs() util.AppFs.Mkdir("test", 777) util.WriteFile("test/noDownload.spec", []byte(`(import "nextimport/nextimport")`), 0644) util.AppFs.Mkdir("nextimport", 777) util.WriteFile("nextimport/nextimport.spec", []byte("(define dummy 1)"), 0644) if err := checkSpec("test/noDownload.spec", nil, nil); err != nil { t.Error(err) } if len(created) != 0 { t.Errorf("should not have downloaded, but downloaded %s", created) } // Verify that call is made to GetSpec util.WriteFile("test/toDownload.spec", []byte(`(import "github.com/NetSys/quilt/specs/example")`), 0644) expected := "unable to open import github.com/NetSys/quilt/specs/example" if err := checkSpec("test/toDownload.spec", nil, nil); err.Error() != expected { t.Errorf("expected %s \n but got %s", expected, err.Error()) } if len(created) == 0 { t.Error("did not download dependency!") } expected = "github.com/NetSys/quilt" if created[0] != expected { t.Errorf("expected to download %s \n but got %s", expected, created[0]) } }
func TestAutoDownload(t *testing.T) { util.AppFs = afero.NewMemMapFs() repoName := "autodownload" importPath := filepath.Join(repoName, "foo") util.WriteFile("test.js", []byte(fmt.Sprintf("require(%q);", importPath)), 0644) logger := newRepoLogger() getter := ImportGetter{ Path: ".", repoFactory: logger.newRepoFactory(nil), } expErr := "StitchError: unable to open import autodownload/foo: no loadable file" err := getter.checkSpec("test.js", nil, nil) if err == nil || err.Error() != expErr { t.Errorf("Wrong error, expected %q, got %v", expErr, err) return } assert.Equal(t, map[string][]string{ importPath: {repoName}, }, logger.created, "Should autodownload the repo") assert.Empty(t, logger.updated, "Shouldn't update any repos") }
func TestPromptsUser(t *testing.T) { oldConfirm := confirm defer func() { confirm = oldConfirm }() util.AppFs = afero.NewMemMapFs() for _, confirmResp := range []bool{true, false} { confirm = func(in io.Reader, prompt string) (bool, error) { return confirmResp, nil } mockGetter := new(testutils.Getter) c := &clientMock.Client{ ClusterReturn: []db.Cluster{ { Spec: `{"old":"spec"}`, }, }, } mockGetter.On("Client", mock.Anything).Return(c, nil) util.WriteFile("test.js", []byte(""), 0644) runCmd := NewRunCommand() runCmd.clientGetter = mockGetter runCmd.stitch = "test.js" runCmd.Run() assert.Equal(t, confirmResp, c.DeployArg != "") } }
func TestImportExists(t *testing.T) { util.AppFs = afero.NewMemMapFs() util.WriteFile("test.js", []byte(`require("existingImport")`), 0644) util.WriteFile("existingImport.js", []byte(""), 0644) logger := newRepoLogger() getter := ImportGetter{ Path: ".", repoFactory: logger.newRepoFactory(nil), } if err := getter.checkSpec("test.js", nil, nil); err != nil { t.Error(err) return } assert.Empty(t, logger.created, "Shouldn't create any repos") assert.Empty(t, logger.updated, "Shouldn't update any repos") }
func (mr *mockRepo) create(dir string) error { for _, toCreate := range mr.toCreate { util.WriteFile( filepath.Join(dir, toCreate.name), []byte(toCreate.contents), 0644) } mr.logger.created[mr.repoName] = append(mr.logger.created[mr.repoName], dir) return nil }
func TestMain(t *testing.T) { util.AppFs = afero.NewMemMapFs() util.WriteFile("test.js", []byte(testStitch), 0644) exitCode := Main([]string{"test.js", "graphviz"}) assert.Zero(t, exitCode) res, err := util.ReadFile("test.dot") assert.Nil(t, err) assert.True(t, isGraphEqual(expGraph, res)) }
func initMachine(cloudConfig string, size string, id string) error { vdir, err := vagrantDir() if err != nil { return err } if _, err := os.Stat(vdir); os.IsNotExist(err) { os.Mkdir(vdir, os.ModeDir|os.ModePerm) } path := vdir + id os.Mkdir(path, os.ModeDir|os.ModePerm) _, stderr, err := shell(id, `vagrant --machine-readable init coreos-beta`) if err != nil { log.Errorf("Failed to initialize Vagrant environment: %s", stderr) destroy(id) return errors.New("unable to init machine") } err = util.WriteFile(path+"/user-data", []byte(cloudConfig), 0644) if err != nil { destroy(id) return err } err = util.WriteFile(path+"/vagrantFile", []byte(vagrantFile), 0644) if err != nil { destroy(id) return err } err = util.WriteFile(path+"/size", []byte(size), 0644) if err != nil { destroy(id) return err } return nil }
func (api vagrantAPI) Init(cloudConfig string, size string, id string) error { vdir, err := api.VagrantDir() if err != nil { return err } if _, err := os.Stat(vdir); os.IsNotExist(err) { os.Mkdir(vdir, os.ModeDir|os.ModePerm) } path := vdir + id os.Mkdir(path, os.ModeDir|os.ModePerm) _, err = api.Shell(id, `vagrant --machine-readable init coreos-beta`) if err != nil { api.Destroy(id) return err } err = util.WriteFile(path+"/user-data", []byte(cloudConfig), 0644) if err != nil { api.Destroy(id) return err } vagrant := vagrantFile() err = util.WriteFile(path+"/vagrantFile", []byte(vagrant), 0644) if err != nil { api.Destroy(id) return err } err = util.WriteFile(path+"/size", []byte(size), 0644) if err != nil { api.Destroy(id) return err } return nil }
func TestSyncKeys(t *testing.T) { tests := []keyTest{ { dbKeys: "key1\nkey2", expKeyFile: "key1\nkey2", }, { dbKeys: "key1\nkey2", keyFile: "key1", expKeyFile: "key1\nkey2", }, { dbKeys: "key1\nkey2", keyFile: "key1\nkey2", expKeyFile: "key1\nkey2", }, { keyFile: "key1\nkey2", expKeyFile: "", }, } for _, test := range tests { util.AppFs = afero.NewMemMapFs() if test.keyFile != "" { err := util.WriteFile( authorizedKeysFile, []byte(test.keyFile), 0644) assert.NoError(t, err) } conn := db.New() conn.Transact(func(view db.Database) error { m := view.InsertMinion() m.Self = true m.AuthorizedKeys = test.dbKeys view.Commit(m) return nil }) err := runOnce(conn) assert.NoError(t, err) actual, err := util.ReadFile(authorizedKeysFile) assert.NoError(t, err) assert.Equal(t, test.expKeyFile, actual) } }
func runOnce(conn db.Conn) error { if _, err := util.AppFs.Stat(authorizedKeysFile); os.IsNotExist(err) { util.AppFs.Create(authorizedKeysFile) } currKeys, err := util.ReadFile(authorizedKeysFile) if err != nil { return err } m, err := conn.MinionSelf() if err != nil { return err } if m.AuthorizedKeys == currKeys { return nil } return util.WriteFile(authorizedKeysFile, []byte(m.AuthorizedKeys), 0644) }
func TestRequire(t *testing.T) { squarer := `exports.square = function(x) { return x*x; };` tests := []requireTest{ // Import returning a primitive. { files: []file{ { name: "/quilt_path/math.js", contents: squarer, }, }, quiltPath: "/quilt_path", mainFile: `require("math").square(5);`, expVal: float64(25), }, // Import returning a type. { files: []file{ { name: "/quilt_path/testImport.js", contents: `exports.getService = function() { return new Service("foo", []); };`, }, }, quiltPath: "/quilt_path", mainFile: `require("testImport").getService().hostname();`, expVal: "foo.q", }, // Import with an import. { files: []file{ { name: "/quilt_path/square.js", contents: squarer, }, { name: "/quilt_path/cube.js", contents: `var square = require("square"); exports.cube = function(x) { return x * square.square(x); };`, }, }, quiltPath: "/quilt_path", mainFile: `require('cube').cube(5);`, expVal: float64(125), }, // Directly assigned exports. { files: []file{ { name: "/quilt_path/square.js", contents: `module.exports = function(x) { return x*x }`, }, }, quiltPath: "/quilt_path", mainFile: `require('square')(5);`, expVal: float64(25), }, // Test self import cycle. { files: []file{ { name: "/quilt_path/A.js", contents: `require("A");`, }, }, quiltPath: "/quilt_path", mainFile: `require("A");`, expErr: `StitchError: import cycle: [A A]`, }, // Test transitive import cycle. { files: []file{ { name: "/quilt_path/A.js", contents: `require("B");`, }, { name: "/quilt_path/B.js", contents: `require("A");`, }, }, quiltPath: "/quilt_path", mainFile: `require('A');`, expErr: `StitchError: import cycle: [A B A]`, }, // No error if there's a path between two imports, but no cycle. { files: []file{ { name: "/quilt_path/A.js", contents: `require("B");`, }, { name: "/quilt_path/B.js", }, }, quiltPath: "/quilt_path", mainFile: `require('B'); require('A'); "end"`, expVal: "end", }, // Absolute import. { files: []file{ { name: "/abs/import.js", contents: squarer, }, }, quiltPath: "/quilt_path", mainFile: `require('/abs/import').square(5);`, expVal: float64(25), }, // Relative import. { files: []file{ { name: "/quilt_path/rel/square.js", contents: squarer, }, { name: "/quilt_path/cube.js", contents: `var square = require("./rel/square"); exports.cube = function(x) { return x * square.square(x); };`, }, }, quiltPath: "/quilt_path", mainFile: `require('cube').cube(5);`, expVal: float64(125), }, // JSON import. { files: []file{ { name: "/quilt_path/static.json", contents: `{ "key": "val" }`, }, }, quiltPath: "/quilt_path", mainFile: `require('static')['key'];`, expVal: "val", }, // JSON import of improperly formatted file. { files: []file{ { name: "/quilt_path/static.json", contents: `{ key: "val" }`, }, }, quiltPath: "/quilt_path", mainFile: `require('static');`, expErr: "StitchError: unable to open import static: " + "invalid character 'k' looking for beginning of " + "object key string", }, // Directory import with index.js. { files: []file{ { name: "/quilt_path/square/index.js", contents: squarer, }, }, quiltPath: "/quilt_path", mainFile: `require('square').square(5);`, expVal: float64(25), }, // Directory import with package.json. { files: []file{ { name: "/quilt_path/square/package.json", contents: `{ "main": "./foo.js" } `, }, { name: "/quilt_path/square/foo.js", contents: squarer, }, }, quiltPath: "/quilt_path", mainFile: `require('square').square(5);`, expVal: float64(25), }, // package.json formatting errors. { files: []file{ { name: "/quilt_path/pkg-json/package.json", }, }, quiltPath: "/quilt_path", mainFile: `require('pkg-json')`, expErr: "StitchError: unable to open import pkg-json: " + "unexpected end of JSON input", }, { files: []file{ { name: "/quilt_path/pkg-json/package.json", contents: `{"main": 2}`, }, }, quiltPath: "/quilt_path", mainFile: `require('pkg-json')`, expErr: "StitchError: unable to open import pkg-json: " + "bad package.json format", }, // Missing files errors. { files: []file{ { name: "/quilt_path/pkg-json/package.json", contents: `{"main": "nonexistent"}`, }, }, quiltPath: "/quilt_path", mainFile: `require('pkg-json')`, expErr: "StitchError: unable to open import pkg-json: " + "no loadable file", }, { mainFile: `require('missing')`, expErr: "StitchError: unable to open import missing: " + "no loadable file", }, } for _, test := range tests { util.AppFs = afero.NewMemMapFs() for _, f := range test.files { util.WriteFile(f.name, []byte(f.contents), 0644) } testVM, _ := newVM(ImportGetter{ Path: test.quiltPath, }) res, err := run(testVM, "main.js", test.mainFile) if err != nil || test.expErr != "" { assert.EqualError(t, err, test.expErr) } else { resIntf, _ := res.Export() assert.Equal(t, test.expVal, resIntf) } } }
func TestImport(t *testing.T) { // Test module keyword code := `(module "math" (define Square (lambda (x) (* x x)))) (math.Square 2)` parseTest(t, code, `(module "math" (list)) 4`) // Test module with multiple-statement body parseTest(t, `(module "math" (define three 3) (define Triple (lambda (x) (* three x)))) (math.Triple 2)`, `(module "math" (list) (list)) 6`) // Test importing from disk testFs := afero.NewMemMapFs() util.AppFs = testFs util.WriteFile("math.spec", []byte("(define Square (lambda (x) (* x x)))"), 0644) util.AppFs = testFs parseTestImport(t, `(import "math") (math.Square 2)`, `(module "math" (list)) 4`, ".") // Test two imports in separate directories testFs = afero.NewMemMapFs() util.AppFs = testFs testFs.Mkdir("square", 777) util.WriteFile("square/square.spec", []byte("(define Square (lambda (x) (* x x)))"), 0644) testFs.Mkdir("cube", 777) util.WriteFile("cube/cube.spec", []byte("(define Cube (lambda (x) (* x x x)))"), 0644) parseTestImport(t, `(import "square/square") (import "cube/cube") (square.Square 2) (cube.Cube 2)`, `(module "square" (list)) (module "cube" (list)) 4 8`, ".") // Test import with an import testFs = afero.NewMemMapFs() util.AppFs = testFs util.WriteFile("square.spec", []byte("(define Square (lambda (x) (* x x)))"), 0644) util.WriteFile("cube.spec", []byte(`(import "square") (define Cube (lambda (x) (* x (square.Square x))))`), 0644) parseTestImport(t, `(import "cube") (cube.Cube 2)`, `(module "cube" (module "square" (list)) (list)) 8`, ".") // Test error in an imported module testFs = afero.NewMemMapFs() util.AppFs = testFs util.WriteFile("bad.spec", []byte(`(define BadFunc (lambda () (+ 1 "1")))`), 0644) runtimeErrImport(t, `(import "bad") (bad.BadFunc)`, `bad.spec:1: bad arithmetic argument: "1"`, ".") testFs = afero.NewMemMapFs() util.AppFs = testFs util.WriteFile("A.spec", []byte(`(import "A")`), 0644) importErr(t, `(import "A")`, `import cycle: [A A]`, ".") testFs = afero.NewMemMapFs() util.AppFs = testFs util.WriteFile("A.spec", []byte(`(import "B")`), 0644) util.WriteFile("B.spec", []byte(`(import "A")`), 0644) importErr(t, `(import "A")`, `import cycle: [A B A]`, ".") testFs = afero.NewMemMapFs() util.AppFs = testFs util.WriteFile("A.spec", []byte(`(import "B") (import "C") (define AddTwo (lambda (x) (+ (B.AddOne x) C.One)))`), 0644) util.WriteFile("B.spec", []byte(`(import "C") (define AddOne (lambda (x) (+ x C.One)))`), 0644) util.WriteFile("C.spec", []byte(`(define One 1)`), 0644) parseTestImport(t, `(import "A") (A.AddTwo 1)`, `(module "A" (module "B" (module "C" (list)) (list)) (module "C" (list)) (list)) 3`, ".") // Test that non-capitalized binds are not exported runtimeErr(t, `(module "A" (define addOne (lambda (x) (+ x 1)))) (A.addOne 1)`, `2: unknown function: A.addOne`) }
func TestRunSpec(t *testing.T) { os.Setenv("QUILT_PATH", "/quilt_path") stitch.DefaultImportGetter.Path = "/quilt_path" exJavascript := `deployment.deploy(new Machine({}));` exJSON := `{"Containers":[],"Labels":[],"Connections":[],"Placements":[],` + `"Machines":[{"Provider":"","Role":"","Size":"",` + `"CPU":{"Min":0,"Max":0},"RAM":{"Min":0,"Max":0},"DiskSize":0,` + `"Region":"","SSHKeys":[]}],"AdminACL":[],"MaxPrice":0,` + `"Namespace":"default-namespace","Invariants":[]}` tests := []runTest{ { files: []file{ { path: "test.js", contents: exJavascript, }, }, path: "test.js", expExitCode: 0, expDeployArg: exJSON, }, { path: "dne.js", expExitCode: 1, expEntries: []log.Entry{ { Message: "open /quilt_path/dne.js: " + "file does not exist", Level: log.ErrorLevel, }, }, }, { path: "/dne.js", expExitCode: 1, expEntries: []log.Entry{ { Message: "open /dne.js: file does not exist", Level: log.ErrorLevel, }, }, }, { files: []file{ { path: "/quilt_path/in_quilt_path.js", contents: exJavascript, }, }, path: "in_quilt_path", expDeployArg: exJSON, }, // Ensure we print a stacktrace when available. { files: []file{ { path: "/quilt_path/A.js", contents: `require("B").foo();`, }, { path: "/quilt_path/B.js", contents: `module.exports.foo = function() { throw new Error("bar"); }`, }, }, path: "/quilt_path/A.js", expExitCode: 1, expEntries: []log.Entry{ { Message: "Error: bar\n" + " at /quilt_path/B.js:2:17\n" + " at /quilt_path/A.js:1:67\n", Level: log.ErrorLevel, }, }, }, } for _, test := range tests { util.AppFs = afero.NewMemMapFs() mockGetter := new(testutils.Getter) c := &clientMock.Client{} mockGetter.On("Client", mock.Anything).Return(c, nil) logHook := logrusTestHook.NewGlobal() for _, f := range test.files { util.WriteFile(f.path, []byte(f.contents), 0644) } runCmd := NewRunCommand() runCmd.clientGetter = mockGetter runCmd.stitch = test.path exitCode := runCmd.Run() assert.Equal(t, test.expExitCode, exitCode) assert.Equal(t, test.expDeployArg, c.DeployArg) assert.Equal(t, len(test.expEntries), len(logHook.Entries)) for i, entry := range logHook.Entries { assert.Equal(t, test.expEntries[i].Message, entry.Message) assert.Equal(t, test.expEntries[i].Level, entry.Level) } } }