diff --git a/Makefile b/Makefile index 46d169fdc..e71a74dc8 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ forcefull: go install -a --tags=with_sqlite ./pkg/... ./server/... ./cmd/... ./third_party/... ./dev/... presubmit: - SKIP_DEP_TESTS=1 go test `pkg-config --libs sqlite3 1>/dev/null 2>/dev/null && echo "--tags=with_sqlite"` -short ./pkg/... ./server/camlistored ./server/appengine ./cmd/... ./dev/... && echo PASS + SKIP_DEP_TESTS=1 go test `pkg-config --libs sqlite3 1>/dev/null 2>/dev/null && echo "--tags=with_sqlite"` -short ./pkg/... ./server/camlistored/... ./server/appengine ./cmd/... ./dev/... && echo PASS embeds: go install ./pkg/fileembed/genfileembed/ && genfileembed ./server/camlistored/ui && genfileembed ./pkg/server diff --git a/pkg/misc/closure/gendeps.go b/pkg/misc/closure/gendeps.go index 8b3624dcf..c4165560b 100644 --- a/pkg/misc/closure/gendeps.go +++ b/pkg/misc/closure/gendeps.go @@ -33,12 +33,18 @@ import ( "time" ) -// GenDeps returns the namespace dependencies between the -// closure javascript files in root. It does not descend -// in directories. +// GenDeps returns the namespace dependencies between the closure javascript files in root. It does not descend in directories. +// Each of the files listed in the output is prepended with the path "../../", which is assumed to be the location where these files can be found, relative to Closure's base.js. +// // The format for each relevant javascript file is: // goog.addDependency("filepath", ["namespace provided"], ["required namespace 1", "required namespace 2", ...]); func GenDeps(root http.FileSystem) ([]byte, error) { + // In the typical configuration, Closure is served at 'closure/goog/...'' + return GenDepsWithPath("../../", root) +} + +// GenDepsWithPath is like GenDeps, but you can specify a path where the files are to be found at runtime relative to Closure's base.js. +func GenDepsWithPath(pathPrefix string, root http.FileSystem) ([]byte, error) { d, err := root.Open("/") if err != nil { return nil, fmt.Errorf("Failed to open root of %v: %v", root, err) @@ -70,7 +76,7 @@ func GenDeps(root http.FileSystem) ([]byte, error) { return nil, fmt.Errorf("Could not parse deps for %v: %v", name, err) } if len(prov) > 0 { - fmt.Fprintf(&buf, "goog.addDependency(%q, %v, %v);\n", "../../"+name, jsList(prov), jsList(req)) + fmt.Fprintf(&buf, "goog.addDependency(%q, %v, %v);\n", pathPrefix+name, jsList(prov), jsList(req)) } } return buf.Bytes(), nil diff --git a/pkg/misc/closure/jstest/jstest.go b/pkg/misc/closure/jstest/jstest.go new file mode 100644 index 000000000..83e106925 --- /dev/null +++ b/pkg/misc/closure/jstest/jstest.go @@ -0,0 +1,121 @@ +/* +Copyright 2014 The Camlistore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package jstest uses the Go testing package to test JavaScript code using Node and Mocha. +package jstest + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "camlistore.org/pkg/misc/closure" +) + +// checkSystemRequirements checks whether system dependencies such as node and npm are present. +func checkSystemRequirements() error { + binaries := []string{"mocha", "node", "npm"} + for _, b := range binaries { + if _, err := exec.LookPath(b); err != nil { + return fmt.Errorf("Required dependency %q not present", b) + } + } + + c := exec.Command("npm", "list", "--depth=0") + b, _ := c.Output() + s := string(b) + modules := []string{"mocha", "assert"} + for _, m := range modules { + if !strings.Contains(s, fmt.Sprintf(" %s@", m)) { + return fmt.Errorf("Required npm module %v not present", m) + } + } + return nil +} + +func getRepoRoot(target string) (string, error) { + dir, err := filepath.Abs(filepath.Dir(target)) + if err != nil { + return "", fmt.Errorf("Could not get working directory: %v", err) + } + for ; dir != "" && filepath.Base(dir) != "camlistore.org"; dir = filepath.Dir(dir) { + } + if dir == "" { + return "", fmt.Errorf("Could not find Camlistore repo in ancestors of %q", target) + } + return dir, nil +} + +// writeDeps runs closure.GenDeps() on targetDir and writes the resulting dependencies to a temporary file which will be used during the test run. The entries in the deps files are generated with paths relative to baseJS, which should be Closure's base.js file. +func writeDeps(baseJS, targetDir string) (string, error) { + closureBaseDir := filepath.Dir(baseJS) + depPrefix, err := filepath.Rel(closureBaseDir, targetDir) + if err != nil { + return "", fmt.Errorf("Could not compute relative path from %q to %q: %v", baseJS, targetDir, err) + } + + depPrefix += string(os.PathSeparator) + b, err := closure.GenDepsWithPath(depPrefix, http.Dir(targetDir)) + if err != nil { + return "", fmt.Errorf("GenDepsWithPath failed: %v", err) + } + depsFile, err := ioutil.TempFile("", "camlistore_closure_test_runner") + if err != nil { + return "", fmt.Errorf("Could not create temp js deps file: %v", err) + } + err = ioutil.WriteFile(depsFile.Name(), b, 0644) + if err != nil { + return "", fmt.Errorf("Could not write js deps file: %v", err) + } + return depsFile.Name(), nil +} + +// TestCwd runs all the tests in the current working directory. +func TestCwd(t *testing.T) { + err := checkSystemRequirements() + if err != nil { + t.Logf("WARNING: JavaScript unit tests could not be run due to a missing system dependency: %v.\nIf you are doing something that might affect JavaScript, you might want to fix this.", err) + t.Log(err) + t.Skip() + } + + path, err := os.Getwd() + if err != nil { + t.Fatalf("Could not determine current directory: %v.", err) + } + + repoRoot, err := getRepoRoot(path) + if err != nil { + t.Fatalf("Could not find repository root: %v", err) + } + baseJS := filepath.Join(repoRoot, "third_party", "closure", "lib", "closure", "goog", "base.js") + bootstrap := filepath.Join(filepath.Dir(baseJS), "bootstrap", "nodejs.js") + depsFile, err := writeDeps(baseJS, path) + if err != nil { + t.Fatal(err) + } + + c := exec.Command("mocha", "-r", bootstrap, "-r", depsFile, filepath.Join(path, "*test.js")) + b, err := c.CombinedOutput() + if err != nil { + t.Fatalf(string(b)) + } +} diff --git a/server/camlistored/ui/blob_test.js b/server/camlistored/ui/blob_test.js new file mode 100644 index 000000000..66295dd25 --- /dev/null +++ b/server/camlistored/ui/blob_test.js @@ -0,0 +1,104 @@ +/* +Copyright 2014 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +goog.require('goog.crypt.Hash'); +goog.require('goog.crypt.Sha1'); +goog.require('goog.string'); +var assert = require('assert'); +var fs = require('fs'); + +goog.require('camlistore.blob'); + + +var MockDOMBlob = function(buffer, start, end) { + this.buffer_ = buffer; + this.start_ = start; + this.size = end - start; +}; + +MockDOMBlob.fromSize = function(size, chr) { + var arr = new Uint8Array(size); + for (var i = 0; i < arr.length; i++) { + arr[i] = chr.charCodeAt(0); + } + return new MockDOMBlob(arr.buffer, 0, arr.length); +}; + +MockDOMBlob.prototype.slice = function(start, end) { + if (start < 0 || start >= this.size) { + throw new Error(goog.strings.subs("start '%s' out of range [0,%s)", start, this.size)); + } + if (end < this.start_ || end > this.size) { + throw new Error(goog.string.subs("end '%s' out of range [0,%s)", end, this.size)); + } + if (end < start) { + throw new Error(goog.string.subs("end '%s' is less than start '%s'", start, end)); + } + + return new MockDOMBlob(this.buffer_, this.start_ + start, this.start_ + end); +}; + +MockDOMBlob.prototype.getArrayBuffer = function() { + return new Uint8Array(this.buffer_, this.start_, this.size); +}; + + +var MockFileReaderSync = function() { +}; + +MockFileReaderSync.prototype.readAsArrayBuffer = function(blob) { + return blob.getArrayBuffer(); +}; + + +describe('camlistore.blob', function() { + describe('#refFromHash', function() { + it('should calculate the right hash', function() { + var hash = new goog.crypt.Sha1(); + assert.equal(camlistore.blob.refFromHash(hash), 'sha1-da39a3ee5e6b4b0d3255bfef95601890afd80709'); + + hash.reset(); + hash.update('The quick brown fox jumps over the lazy dog'); + assert.equal(camlistore.blob.refFromHash(hash), 'sha1-2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'); + }); + + it('should complain about wrong hash function', function() { + function FooHash() {}; + goog.inherits(FooHash, goog.crypt.Hash); + assert.throws(camlistore.blob.refFromHash.bind(null, new FooHash()), /Unsupported hash function type/); + }); + }); + + describe('#refFromString', function() { + it('should calculate the right hash', function() { + assert.equal(camlistore.blob.refFromString(''), 'sha1-da39a3ee5e6b4b0d3255bfef95601890afd80709'); + assert.equal(camlistore.blob.refFromString('The quick brown fox jumps over the lazy dog'), 'sha1-2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'); + }); + }); + + describe('#refFromDOMBlob', function() { + it('should calculate the right hash', function() { + blob = MockDOMBlob.fromSize(1000001, 'a'); + goog.global.FileReaderSync = MockFileReaderSync; + try { + // Verified with openssl. + assert.equal(camlistore.blob.refFromDOMBlob(blob), 'sha1-432e7e01de7086c5246b6ac57f5f435b58f13752'); + } finally { + delete goog.global.FileReaderSync; + } + }); + }); +}); diff --git a/server/camlistored/ui/ui_test.go b/server/camlistored/ui/ui_test.go new file mode 100644 index 000000000..2c721d5cd --- /dev/null +++ b/server/camlistored/ui/ui_test.go @@ -0,0 +1,27 @@ +/* +Copyright 2014 The Camlistore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ui + +import ( + "testing" + + "camlistore.org/pkg/misc/closure/jstest" +) + +func TestJS(t *testing.T) { + jstest.TestCwd(t) +}