Skip to content

Commit

Permalink
Setup JavaScript command line runner.
Browse files Browse the repository at this point in the history
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: http://svn.apache.org/repos/asf/couchdb/trunk@884675 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
davisp committed Nov 26, 2009
1 parent 448b387 commit 416c07c
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 282 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ src/mochiweb/Makefile
stamp-h1
test/.deps/
test/Makefile
test/javascript/run_js_tests.sh
var/Makefile

# for make
Expand Down Expand Up @@ -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
Expand All @@ -85,6 +87,8 @@ test/etap/run
# for make check

test/etap/temp.*
couchdb.stderr
couchdb.stdout

# for make cover

Expand Down
4 changes: 3 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions license.skip
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
^CHANGES
^config.*
^configure
^couchdb.stderr
^couchdb.stdout
^cover/.*\.coverdata
^cover/.*\.html
^erl_crash.dump
Expand Down
2 changes: 1 addition & 1 deletion share/www/script/couch.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,4 +496,4 @@ CouchDB.params = function(options) {
returnArray.push(key + "=" + value);
}
return returnArray.join("&");
}
};
7 changes: 6 additions & 1 deletion share/www/script/test/changes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion share/www/script/test/cookie_auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 38 additions & 20 deletions src/couchdb/priv/couch_js/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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);
Expand All @@ -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?
Expand All @@ -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);
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
12 changes: 10 additions & 2 deletions src/couchdb/priv/couch_js/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
11 changes: 9 additions & 2 deletions test/javascript/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 $@
52 changes: 52 additions & 0 deletions test/javascript/cli_runner.js
Original file line number Diff line number Diff line change
@@ -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());
}
57 changes: 57 additions & 0 deletions test/javascript/couch_http.js
Original file line number Diff line number Diff line change
@@ -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();
};
21 changes: 16 additions & 5 deletions test/javascript/runner.sh → test/javascript/run.tpl
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -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 -
Loading

0 comments on commit 416c07c

Please sign in to comment.