summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Joseph Davis <davisp@apache.org>2009-11-26 19:32:08 +0000
committerPaul Joseph Davis <davisp@apache.org>2009-11-26 19:32:08 +0000
commit627b7d2d90c7cfcec2c8bb6e5b5e2b53ea60d217 (patch)
treec8b09655791ba3e5bc8a34344709ded81f8dc73e
parent95ee619df135a4c8b3ecefe65503c6d1cc7c36da (diff)
Setup JavaScript command line runner.
All JS tests can now be run from the command line using the cURL adapter. In the future I would like to rework this and provide better TAP output. For now, each test is a single TAP assert. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@884675 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--.gitignore4
-rw-r--r--Makefile.am4
-rw-r--r--license.skip2
-rw-r--r--share/www/script/couch.js2
-rw-r--r--share/www/script/test/changes.js7
-rw-r--r--share/www/script/test/cookie_auth.js6
-rw-r--r--src/couchdb/priv/couch_js/http.c58
-rw-r--r--src/couchdb/priv/couch_js/main.c12
-rw-r--r--test/javascript/Makefile.am11
-rw-r--r--test/javascript/cli_runner.js52
-rw-r--r--test/javascript/couch_http.js57
-rw-r--r--[-rwxr-xr-x]test/javascript/run.tpl (renamed from test/javascript/runner.sh)21
-rw-r--r--test/javascript/test.js249
13 files changed, 203 insertions, 282 deletions
diff --git a/.gitignore b/.gitignore
index be37e00a..19085e70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@ src/mochiweb/Makefile
stamp-h1
test/.deps/
test/Makefile
+test/javascript/run_js_tests.sh
var/Makefile
# for make
@@ -65,6 +66,7 @@ src/mochiweb/mochiweb.app
test/local.ini
test/etap/run
test/etap/test_util.erl
+test/javascript/run
share/server/main.js
# for make dev
@@ -85,6 +87,8 @@ test/etap/run
# for make check
test/etap/temp.*
+couchdb.stderr
+couchdb.stdout
# for make cover
diff --git a/Makefile.am b/Makefile.am
index ae6ab9fd..fff5648b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -53,9 +53,11 @@ dev: all
@echo "it creates development ini files as well as a"
@echo "$(top_builddir)/tmp structure for development runtime files."
@echo "Use ./utils/run to launch CouchDB from the source tree."
+ mkdir -p $(top_builddir)/etc/couchdb/default.d
+ mkdir -p $(top_builddir)/etc/couchdb/local.d
mkdir -p $(top_builddir)/tmp/lib
mkdir -p $(top_builddir)/tmp/log
- mkdir -p $(top_builddir)/tmp/run
+ mkdir -p $(top_builddir)/tmp/run/couchdb
distclean-local:
rm -fr $(top_builddir)/tmp
diff --git a/license.skip b/license.skip
index f33902ee..3c7d83a5 100644
--- a/license.skip
+++ b/license.skip
@@ -12,6 +12,8 @@
^CHANGES
^config.*
^configure
+^couchdb.stderr
+^couchdb.stdout
^cover/.*\.coverdata
^cover/.*\.html
^erl_crash.dump
diff --git a/share/www/script/couch.js b/share/www/script/couch.js
index 9f0995ff..f7d7d43d 100644
--- a/share/www/script/couch.js
+++ b/share/www/script/couch.js
@@ -496,4 +496,4 @@ CouchDB.params = function(options) {
returnArray.push(key + "=" + value);
}
return returnArray.join("&");
-}
+};
diff --git a/share/www/script/test/changes.js b/share/www/script/test/changes.js
index f348aed8..bf4a1617 100644
--- a/share/www/script/test/changes.js
+++ b/share/www/script/test/changes.js
@@ -57,7 +57,12 @@ couchTests.changes = function(debug) {
}
// poor man's browser detection
- var is_safari = navigator.userAgent.match(/AppleWebKit/);
+ var is_safari = false;
+ if(typeof(navigator) == "undefined") {
+ is_safari = true; // For CouchHTTP based runners
+ } else if(navigator.userAgent.match(/AppleWebKit/)) {
+ is_safari = true;
+ };
if (!is_safari && xhr) {
// Only test the continuous stuff if we have a real XHR object
// with real async support.
diff --git a/share/www/script/test/cookie_auth.js b/share/www/script/test/cookie_auth.js
index d82d302a..0a42b4a9 100644
--- a/share/www/script/test/cookie_auth.js
+++ b/share/www/script/test/cookie_auth.js
@@ -94,7 +94,11 @@ couchTests.cookie_auth = function(debug) {
body: "username=Jason%20Davies&password="+encodeURIComponent(password)
});
// should this be a redirect code instead of 200?
- T(xhr.status == 200);
+ // The cURL adapter is returning the expected 302 here.
+ // I imagine this has to do with whether the client is willing
+ // to follow the redirect, ie, the browser follows and does a
+ // GET on the returned Location
+ T(xhr.status == 200 || xhr.status == 302);
usersDb.deleteDb();
// test user creation
diff --git a/src/couchdb/priv/couch_js/http.c b/src/couchdb/priv/couch_js/http.c
index 6ee337af..703ab97b 100644
--- a/src/couchdb/priv/couch_js/http.c
+++ b/src/couchdb/priv/couch_js/http.c
@@ -38,6 +38,9 @@ char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", NULL};
static JSBool
go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t blen);
+static JSString*
+str_from_binary(JSContext* cx, char* data, size_t length);
+
static JSBool
constructor(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval)
{
@@ -406,13 +409,13 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
if(!HANDLE)
{
JS_ReportError(cx, "Failed to initialize cURL handle.");
- goto error;
+ goto done;
}
if(http->method < 0 || http->method > COPY)
{
JS_ReportError(cx, "INTERNAL: Unknown method.");
- goto error;
+ goto done;
}
curl_easy_setopt(HANDLE, CURLOPT_CUSTOMREQUEST, METHODS[http->method]);
@@ -452,13 +455,13 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
if(curl_easy_perform(HANDLE) != 0)
{
JS_ReportError(cx, "Failed to execute HTTP request: %s", ERRBUF);
- goto error;
+ goto done;
}
if(!state.resp_headers)
{
JS_ReportError(cx, "Failed to recieve HTTP headers.");
- goto error;
+ goto done;
}
tmp = OBJECT_TO_JSVAL(state.resp_headers);
@@ -473,7 +476,7 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
))
{
JS_ReportError(cx, "INTERNAL: Failed to set response headers.");
- goto error;
+ goto done;
}
if(state.recvbuf) // Is good enough?
@@ -482,16 +485,15 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
jsbody = dec_string(cx, state.recvbuf, state.read+1);
if(!jsbody)
{
- // This is so dirty its not even almost funny. I'm ignoring
- // all sorts of content-types and character sets and just falling
- // back to doing a chop job when something doesn't decode as UTF-8
- // which is pretty sad. But, if you hate me for it, then feel free
- // to write a patch that does the proper content-type parsing and
- // actually respects charsets as returned in headers.
- jsbody = JS_NewString(cx, state.recvbuf, state.read);
+ // If we can't decode the body as UTF-8 we forcefully
+ // convert it to a string by just forcing each byte
+ // to a jschar.
+ jsbody = str_from_binary(cx, state.recvbuf, state.read);
if(!jsbody) {
- JS_ReportError(cx, "INTERNAL: Failed to decode body.");
- goto error;
+ if(!JS_IsExceptionPending(cx)) {
+ JS_ReportError(cx, "INTERNAL: Failed to decode body.");
+ }
+ goto done;
}
}
tmp = STRING_TO_JSVAL(jsbody);
@@ -512,16 +514,13 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
))
{
JS_ReportError(cx, "INTERNAL: Failed to set responseText.");
- goto error;
+ goto done;
}
ret = JS_TRUE;
- goto success;
-error:
+done:
if(state.recvbuf) JS_free(cx, state.recvbuf);
-
-success:
return ret;
}
@@ -644,4 +643,23 @@ recv_body(void *ptr, size_t size, size_t nmem, void *data)
state->read += length;
return length;
}
-
+
+JSString*
+str_from_binary(JSContext* cx, char* data, size_t length)
+{
+ jschar* conv = (jschar*) JS_malloc(cx, length * sizeof(jschar));
+ JSString* ret = NULL;
+ size_t i;
+
+ if(!conv) return NULL;
+
+ for(i = 0; i < length; i++)
+ {
+ conv[i] = (jschar) data[i];
+ }
+
+ ret = JS_NewUCString(cx, conv, length);
+ if(!ret) JS_free(cx, conv);
+
+ return ret;
+}
diff --git a/src/couchdb/priv/couch_js/main.c b/src/couchdb/priv/couch_js/main.c
index ee0e42c9..f8578375 100644
--- a/src/couchdb/priv/couch_js/main.c
+++ b/src/couchdb/priv/couch_js/main.c
@@ -306,13 +306,21 @@ main(int argc, const char * argv[])
JS_SetGlobalObject(cx, global);
- if(argc != 2) {
+ if(argc > 2)
+ {
fprintf(stderr, "incorrect number of arguments\n\n");
fprintf(stderr, "usage: %s <scriptfile>\n", argv[0]);
return 2;
}
- execute_script(cx, global, argv[1]);
+ if(argc == 0)
+ {
+ execute_script(cx, global, NULL);
+ }
+ else
+ {
+ execute_script(cx, global, argv[1]);
+ }
FINISH_REQUEST(cx);
diff --git a/test/javascript/Makefile.am b/test/javascript/Makefile.am
index faaca829..81aec36b 100644
--- a/test/javascript/Makefile.am
+++ b/test/javascript/Makefile.am
@@ -11,5 +11,12 @@
## the License.
EXTRA_DIST = \
- runner.sh \
- test.js
+ cli_runner.js \
+ couch_http.js \
+ run.tpl
+
+run: run.tpl
+ sed -e "s|%abs_top_srcdir%|$(abs_top_srcdir)|" \
+ -e "s|%abs_top_builddir%|$(abs_top_builddir)|" \
+ < $< > $@
+ chmod +x $@
diff --git a/test/javascript/cli_runner.js b/test/javascript/cli_runner.js
new file mode 100644
index 00000000..cdbe2e73
--- /dev/null
+++ b/test/javascript/cli_runner.js
@@ -0,0 +1,52 @@
+// 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.
+
+var console = {
+ log: function(arg) {
+ var msg = (arg.toString()).replace(/\n/g, "\n ");
+ print("# " + msg);
+ }
+};
+
+function T(arg1, arg2) {
+ if(!arg1) {
+ throw((arg2 ? arg2 : arg1).toString());
+ }
+}
+
+function runTestConsole(num, name, func) {
+ try {
+ func();
+ print("ok " + num + " " + name);
+ } catch(e) {
+ msg = e.toString();
+ msg = msg.replace(/\n/g, "\n ");
+ print("not ok " + num + " " + name + " " + msg);
+ }
+}
+
+function runAllTestsConsole() {
+ var numTests = 0;
+ for(var t in couchTests) { numTests += 1; }
+ print("1.." + numTests);
+ var testId = 0;
+ for(var t in couchTests) {
+ testId += 1;
+ runTestConsole(testId, t, couchTests[t]);
+ }
+};
+
+try {
+ runAllTestsConsole();
+} catch (e) {
+ p("# " + e.toString());
+}
diff --git a/test/javascript/couch_http.js b/test/javascript/couch_http.js
new file mode 100644
index 00000000..f92cf119
--- /dev/null
+++ b/test/javascript/couch_http.js
@@ -0,0 +1,57 @@
+// 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.
+
+(function() {
+ CouchHTTP.prototype.base_url = "http://127.0.0.1:5984"
+
+ if(typeof(CouchHTTP) != "undefined") {
+ CouchHTTP.prototype.open = function(method, url, async) {
+ if(/^\s*http:\/\//.test(url)) {
+ return this._open(method, url, async);
+ } else {
+ return this._open(method, this.base_url + url, async);
+ }
+ };
+
+ CouchHTTP.prototype.setRequestHeader = function(name, value) {
+ // Drop content-length headers because cURL will set it for us
+ // based on body length
+ if(name.toLowerCase().replace(/^\s+|\s+$/g, '') != "content-length") {
+ this._setRequestHeader(name, value);
+ }
+ }
+
+ CouchHTTP.prototype.send = function(body) {
+ this._send(body || "");
+ var headers = {};
+ this._headers.forEach(function(hdr) {
+ var pair = hdr.split(":");
+ var name = pair.shift();
+ headers[name] = pair.join(":").replace(/^\s+|\s+$/g, "");
+ });
+ this.headers = headers;
+ };
+
+ CouchHTTP.prototype.getResponseHeader = function(name) {
+ for(var hdr in this.headers) {
+ if(hdr.toLowerCase() == name.toLowerCase()) {
+ return this.headers[hdr];
+ }
+ }
+ return null;
+ };
+ }
+})();
+
+CouchDB.newXhr = function() {
+ return new CouchHTTP();
+};
diff --git a/test/javascript/runner.sh b/test/javascript/run.tpl
index 1f48c390..c5abe6e7 100755..100644
--- a/test/javascript/runner.sh
+++ b/test/javascript/run.tpl
@@ -12,8 +12,19 @@
# License for the specific language governing permissions and limitations under
# the License.
-cat ../share/www/script/couch.js \
- ../share/www/script/couch_test_runner.js \
- ../share/www/script/couch_tests.js \
- ../share/www/script/test/*.js test.js \
- | ../src/couchdb/couchjs -
+SRC_DIR=%abs_top_srcdir%
+SCRIPT_DIR=$SRC_DIR/share/www/script
+JS_TEST_DIR=$SRC_DIR/test/javascript
+
+COUCHJS=%abs_top_builddir%/src/couchdb/priv/couchjs
+
+cat $SCRIPT_DIR/json2.js \
+ $SCRIPT_DIR/sha1.js \
+ $SCRIPT_DIR/oauth.js \
+ $SCRIPT_DIR/couch.js \
+ $SCRIPT_DIR/couch_test_runner.js \
+ $SCRIPT_DIR/couch_tests.js \
+ $SCRIPT_DIR/test/*.js \
+ $JS_TEST_DIR/couch_http.js \
+ $JS_TEST_DIR/cli_runner.js \
+ | $COUCHJS -
diff --git a/test/javascript/test.js b/test/javascript/test.js
deleted file mode 100644
index 7f8a0787..00000000
--- a/test/javascript/test.js
+++ /dev/null
@@ -1,249 +0,0 @@
-// 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.
-
-// couch.js, with modifications
-
-// 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.
-
-// some monkeypatches
-var JSON = {
- parse : function(string) {
- return eval('('+string+')');
- },
- stringify : function(obj) {
- return toJSON(obj||null);
- }
-};
-
-RegExp.escape = function(text) {
- if (!arguments.callee.sRE) {
- var specials = [
- '/', '.', '*', '+', '?', '|',
- '(', ')', '[', ']', '{', '}', '\\'
- ];
- arguments.callee.sRE = new RegExp(
- '(\\' + specials.join('|\\') + ')', 'g'
- );
- }
- return text.replace(arguments.callee.sRE, '\\$1');
-}
-
-// This is a JS wrapper for the curl function made available in couch_js.c,
-// it should be used in other JavaScripts that would like to make HTTP calls.
-
-var HTTP = (function() {
- function parseCurl(string) {
- var parts = string.split(/\r\n\r\n/);
- var body = parts.pop();
- var header = parts.pop();
- var headers = header.split(/\n/);
-
- var status = /HTTP\/1.\d (\d*)/.exec(header)[1];
- return {
- responseText: body,
- status: parseInt(status),
- getResponseHeader: function(key) {
- var keymatcher = new RegExp(RegExp.escape(key), "i");
- for (var i in headers) {
- var h = headers[i];
- if (keymatcher.test(h)) {
- var value = h.substr(key.length+2);
- return value.replace(/^\s+|\s+$/g,"");
- }
- }
- return "";
- }
- }
- };
- return {
- GET : function(url, body, headers) {
- var st, urx = url, hx = (headers || null);
- st = gethttp(urx, hx);
- return parseCurl(st);
- },
- HEAD : function(url, body, headers) {
- var st, urx = url, hx = (headers || null);
- st = headhttp(urx, hx);
- return parseCurl(st);
- },
- DELETE : function(url, body, headers) {
- var st, urx = url, hx = (headers || null);
- st = delhttp(urx, hx);
- return parseCurl(st);
- },
- MOVE : function(url, body, headers) {
- var st, urx = url, hx = (headers || null);
- st = movehttp(urx, hx);
- return parseCurl(st);
- },
- COPY : function(url, body, headers) {
- var st, urx = url, hx = (headers || null);
- st = copyhttp(urx, hx);
- return parseCurl(st);
- },
- POST : function(url, body, headers) {
- var st, urx = url, bx = (body || ""), hx = (headers || {});
- hx['Content-Type'] = hx['Content-Type'] || "application/json";
- st = posthttp(urx, bx, hx);
- return parseCurl(st);
- },
- PUT : function(url, body, headers) {
- var st, urx = url, bx = (body || ""), hx = (headers || {});
- hx['Content-Type'] = hx['Content-Type'] || "application/json";
- st = puthttp(urx, bx, hx);
- return parseCurl(st);
- }
- };
-})();
-
-// Monkeypatches to CouchDB client for use of curl.
-
-CouchDB.host = (typeof window == 'undefined' || !window) ? "127.0.0.1:5984" : window;
-
-CouchDB.request = function(method, uri, options) {
- var full_uri = "http://" + CouchDB.host + uri;
- options = options || {};
- var response = HTTP[method](full_uri, options.body, options.headers);
- return response;
-}
-
-
-function toJSON(val) {
- if (typeof(val) == "undefined") {
- throw {error:"bad_value", reason:"Cannot encode 'undefined' value as JSON"};
- }
- var subs = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f',
- '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
- if (typeof(val) == "xml") { // E4X support
- val = val.toXMLString();
- }
- return {
- "Array": function(v) {
- var buf = [];
- for (var i = 0; i < v.length; i++) {
- buf.push(toJSON(v[i]));
- }
- return "[" + buf.join(",") + "]";
- },
- "Boolean": function(v) {
- return v.toString();
- },
- "Date": function(v) {
- var f = function(n) { return n < 10 ? '0' + n : n }
- return '"' + v.getUTCFullYear() + '-' +
- f(v.getUTCMonth() + 1) + '-' +
- f(v.getUTCDate()) + 'T' +
- f(v.getUTCHours()) + ':' +
- f(v.getUTCMinutes()) + ':' +
- f(v.getUTCSeconds()) + 'Z"';
- },
- "Number": function(v) {
- return isFinite(v) ? v.toString() : "null";
- },
- "Object": function(v) {
- if (v === null) return "null";
- var buf = [];
- for (var k in v) {
- if (!v.hasOwnProperty(k) || typeof(k) !== "string" || v[k] === undefined) {
- continue;
- }
- buf.push(toJSON(k, val) + ": " + toJSON(v[k]));
- }
- return "{" + buf.join(",") + "}";
- },
- "String": function(v) {
- if (/["\\\x00-\x1f]/.test(v)) {
- v = v.replace(/([\x00-\x1f\\"])/g, function(a, b) {
- var c = subs[b];
- if (c) return c;
- c = b.charCodeAt();
- return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
- });
- }
- return '"' + v + '"';
- }
- }[val != null ? val.constructor.name : "Object"](val);
-}
-
-
-
-// *************** Test Framework Console Adapter ****************** //
-
-var p = print;
-var numFailures = 0;
-
-function runAllTestsConsole() {
- var numTests = 0;
- var debug = false;
- for (var t in couchTests) {
- p(t);
- if (t == "utf8") {
- p("We skip the utf8 test because it fails due to problems in couch_js.c");
- p("Run the in-browser tests to verify utf8.\n");
- } else {
- numTests += 1;
- var testFun = couchTests[t];
- runTestConsole(testFun, debug);
- }
- }
- p("Results: "+numFailures.toString() + " failures in "+numTests+" tests.")
-};
-
-function runTestConsole(testFun, debug) {
- var start = new Date().getTime();
- try {
- if (!debug) testFun = patchTest(testFun) || testFun;
- testFun();
- p("PASS");
- } catch(e) {
- p("ERROR");
- p("Exception raised: "+e.toString());
- p("Backtrace: "+e.stack);
- }
- var duration = new Date().getTime() - start;
- p(duration+"ms\n");
-};
-
-
-// Use T to perform a test that returns false on failure and if the test fails,
-// display the line that failed.
-// Example:
-// T(MyValue==1);
-function T(arg1, arg2) {
- if (!arg1) {
- p("Assertion failed: "+(arg2 != null ? arg2 : arg1).toString());
- numFailures += 1
- }
-}
-
-p("Running CouchDB Test Suite\n");
-p("Host: "+CouchDB.host);
-
-try {
- p("Version: "+CouchDB.getVersion()+"\n");
- runAllTestsConsole();
- // runTestConsole(tests.attachments);
-} catch (e) {
- p(e.toString());
-}
-
-p("\nFinished");