Merge "Add jstests.go, a unit test harness for JavaScript files."

This commit is contained in:
Aaron Boodman 2014-01-07 04:26:16 +00:00 committed by Gerrit Code Review
commit 8a970d23f0
5 changed files with 263 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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))
}
}

View File

@ -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;
}
});
});
});

View File

@ -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)
}