From 5b2760e14a60e7447f04b58879fcdbc0c45c04df Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Thu, 9 Oct 2008 22:04:46 +0000 Subject: make check now runs the JavaScript test suite git-svn-id: https://svn.apache.org/repos/asf/incubator/couchdb/trunk@703276 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/couch_tests.js | 7 +- src/couchdb/Makefile.am | 3 +- src/couchdb/couch_js.c | 796 +++++++++++++++++++++++++++++++++++++++- src/couchdb/curlhelper.c | 261 +++++++++++++ src/couchdb/curlhelper.h | 49 +++ test/runner.sh | 2 + test/test.js | 252 +++++++++++++ 7 files changed, 1367 insertions(+), 3 deletions(-) create mode 100644 src/couchdb/curlhelper.c create mode 100644 src/couchdb/curlhelper.h create mode 100644 test/test.js diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index 46e42464..e88c68d1 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -10,6 +10,11 @@ // License for the specific language governing permissions and limitations under // the License. +// Used by replication test +CouchDB.host = (typeof window == 'undefined' || !window) ? + "127.0.0.1" : window.location.host; +CouchDB.port = 5984; + var tests = { // Do some basic tests. @@ -1556,7 +1561,7 @@ var tests = { replication: function(debug) { if (debug) debugger; - var host = window.location.host; + var host = CouchDB.host; var dbPairs = [ {source:"test_suite_db_a", target:"test_suite_db_b"}, diff --git a/src/couchdb/Makefile.am b/src/couchdb/Makefile.am index 83220a57..28fd418f 100644 --- a/src/couchdb/Makefile.am +++ b/src/couchdb/Makefile.am @@ -24,7 +24,8 @@ couch_erl_driver_la_CFLAGS = $(ICU_LOCAL_FLAGS) couch_erl_driver_la_LIBADD = -licuuc -licudata -licui18n locallibbin_PROGRAMS = couchjs -couchjs_SOURCES = couch_js.c +couchjs_SOURCES = couch_js.c curlhelper.c +couchjs_LDADD = -lcurl -lgssapi_krb5 couchinclude_DATA = couch_db.hrl diff --git a/src/couchdb/couch_js.c b/src/couchdb/couch_js.c index 8481bc50..21faac1c 100644 --- a/src/couchdb/couch_js.c +++ b/src/couchdb/couch_js.c @@ -13,8 +13,16 @@ specific language governing permissions and limitations under the License. */ +#include #include +#include +#include "curlhelper.h" #include +#include + +#ifndef CURLOPT_COPYPOSTFIELDS + #define CURLOPT_COPYPOSTFIELDS 10165 +#endif int gExitCode = 0; int gStackChunkSize = 8L * 1024L; @@ -401,6 +409,785 @@ PrintError(JSContext *context, const char *message, JSErrorReport *report) { fprintf(stderr, "%s\n", message); } +JSBool ThrowError(JSContext *cx, const char *message) +{ + void *mark; + jsval *args; + jsval exc; + + printf("%s\n",message); + + args = JS_PushArguments(cx, &mark, "s", message); + if (args) { + if (JS_CallFunctionName(cx, JS_GetGlobalObject(cx), + "Error", 1, args, &exc)) + JS_SetPendingException(cx, exc); + JS_PopArguments(cx, mark); + } + + return JS_FALSE; +} + +typedef struct buffer_counter { + Buffer buffer; + int pos; +}* BufferCount; + +size_t curl_read(void *ptr, size_t size, size_t nmemb, void *stream) { + if( size == 0 || nmemb == 0) { + return 0; + } + + char* databuffer = (char*)ptr; + Buffer b = ((BufferCount)stream)->buffer; + int* pos = &(((BufferCount)stream)->pos); + + if((b->count - *pos) == 0) { + return 0; + } + + int readlength = size*nmemb; + int spaceleft = b->count - *pos; + int i; + + if(readlength < spaceleft) { + copy_Buffer(b,databuffer,*pos,readlength); + *(pos) += readlength; + return readlength; + } else { + copy_Buffer(b,databuffer,*pos,spaceleft); + *(pos) += spaceleft; + return spaceleft; + } +} + +size_t curl_write(void *ptr, size_t size, size_t nmemb, void *stream) { + if( size == 0 || nmemb == 0 ) + return 0; + + char *data, *tmp; + Buffer b; + + data = (char *)ptr; + b = (Buffer)stream; + + append_Buffer(b,data,size*nmemb); + + return size*nmemb; +} + +// This uses MALLOC dont forget to free +char* JSValToChar(JSContext* context, jsval* arg) { + if(!JSVAL_IS_STRING(*arg)) { + return NULL; + } + + char *c, *tmp; + JSString *jsmsg; + size_t len; + + jsmsg = JS_ValueToString(context,*arg); + len = JS_GetStringLength(jsmsg); + tmp = JS_GetStringBytes(jsmsg); + + c = (char*)malloc(len+1); + c[len] = '\0'; + + int i; + + for(i = 0;i < len;i++) { + c[i] = tmp[i]; + } + + return c; +} + +JSBool BufferToJSVal(JSContext *context, Buffer b, jsval *rval) { + char* c; + JSString *str; + + // Important for char* to be JS_malloced, otherwise js wont let you use it in the NewString method + c = JS_malloc(context, b->count * sizeof(char)); + copy_Buffer(b,c,0,b->count); + + + /* Initialize a JSString object */ + str = JS_NewString(context, c, b->count); + + if (!str) { + JS_free(context, c); + return JS_FALSE; + } + + // Set Return Value + *rval = STRING_TO_JSVAL(str); + if(rval == NULL) { + return JS_FALSE; + } + return JS_TRUE; +} + +struct curl_slist* generateCurlHeaders(JSContext* context,jsval* arg) { + // If arg is an object then we go the header-hash route else return NULL + + if(!JSVAL_IS_NULL(*arg)) { + + struct curl_slist *slist = NULL; + JSObject* header_obj; + + // If we fail to convert arg2 to an object. Error! + if(!JS_ValueToObject(context,*arg,&header_obj)) { + return NULL; + } + + JSObject* iterator = JS_NewPropertyIterator(context,header_obj); + + jsval *jsProperty = JS_malloc(context,sizeof(jsval)); + jsval *jsValue = JS_malloc(context,sizeof(jsval)); + jsid *jsId = JS_malloc(context,sizeof(jsid)); + + while(JS_NextProperty(context,iterator,jsId) == JS_TRUE) { + + if(*jsId == JSVAL_VOID) { + break; + } + + // TODO: Refactor this maybe make a JSValAppendBuffer method b/c that is what you really want to do. + + Buffer bTmp = init_Buffer(); + JS_IdToValue(context,*jsId,jsProperty); + char* jsPropertyName = JSValToChar(context,jsProperty); + + // TODO: Remove strlen =/ + append_Buffer(bTmp,jsPropertyName,strlen(jsPropertyName)); + append_Buffer(bTmp,": ",2); + + JS_GetProperty(context,header_obj,jsPropertyName,jsValue); + char* jsPropertyValue = JSValToChar(context,jsValue); + // TODO: Remove strlen =/ + append_Buffer(bTmp,jsPropertyValue,strlen(jsPropertyValue)); + append_Buffer(bTmp,"",1); + + slist = curl_slist_append(slist,bTmp->data); + + free_Buffer(bTmp); + free(jsPropertyValue); + free(jsPropertyName); + } + + JS_free(context,jsProperty); + JS_free(context,jsValue); + JS_free(context,jsId); + + return slist; + + } else { + return NULL; + } +} + +static JSBool +GetHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + CURL* handle; + Buffer b; + char *url; + size_t charslen, readlen; + + // Run GC + JS_MaybeGC(context); + + // Init Curl + if((handle = curl_easy_init()) == NULL) { + return JS_FALSE; + } + + // Get URL + url = JSValToChar(context,argv); + if( url == NULL ) { + return ThrowError(context,"Unable to convert url (argument 0) to a string"); + } + + b = init_Buffer(); // Allocate buffer that will store the get resultant + + // Configuration + curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); + curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); + curl_easy_setopt(handle,CURLOPT_URL,url); + curl_easy_setopt(handle,CURLOPT_HTTPGET,1); + curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); + curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); + + struct curl_slist *slist = generateCurlHeaders(context,argv+1); + if(slist != NULL) { + curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); + } + + // Perform + int exitcode; + + if((exitcode = curl_easy_perform(handle)) != 0) { + if(slist != NULL) { + curl_slist_free_all(slist); + } + curl_easy_cleanup(handle); + free(url); + free_Buffer(b); + return JS_FALSE; + } + + free(url); + if(slist != NULL) { + curl_slist_free_all(slist); + } + + /* Treat the empty string specially */ + if (b->count == 0) { + free_Buffer(b); + *rval = JS_GetEmptyStringValue(context); + curl_easy_cleanup(handle); + return JS_TRUE; + } + + /* Shrink the buffer to the real size and store its value in rval */ + shrink_Buffer(b); + BufferToJSVal(context,b,rval); + + // Free Buffer + free_Buffer(b); + + if(rval == NULL) { + curl_easy_cleanup(handle); + return JS_FALSE; + } + + JS_MaybeGC(context); + + curl_easy_cleanup(handle); + + return JS_TRUE; +} + +static JSBool +HeadHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + CURL* handle; + Buffer b; + char *url; + size_t charslen, readlen; + + // Run GC + JS_MaybeGC(context); + + // Init Curl + if((handle = curl_easy_init()) == NULL) { + return JS_FALSE; + } + + // Get URL + url = JSValToChar(context,argv); + if( url == NULL ) { + return ThrowError(context,"Unable to convert url (argument 0) to a string"); + } + + b = init_Buffer(); // Allocate buffer that will store the get resultant + + // Configuration + // curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); + // curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); + curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); + curl_easy_setopt(handle,CURLOPT_URL,url); + curl_easy_setopt(handle,CURLOPT_HTTPGET,0); + curl_easy_setopt(handle,CURLOPT_NOBODY,1); + curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); + curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); + + struct curl_slist *slist = generateCurlHeaders(context,argv+1); + if(slist != NULL) { + curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); + } + + // fprintf(stderr, "about to run HEAD request\n"); + + // Perform + int exitcode; + + if((exitcode = curl_easy_perform(handle)) != 0) { + if(slist != NULL) { + curl_slist_free_all(slist); + } + curl_easy_cleanup(handle); + free(url); + free_Buffer(b); + return JS_FALSE; + } + // fprintf(stderr, "ran ok HEAD request\n"); + + free(url); + if(slist != NULL) { + curl_slist_free_all(slist); + } + + /* Treat the empty string specially */ + if (b->count == 0) { + free_Buffer(b); + *rval = JS_GetEmptyStringValue(context); + curl_easy_cleanup(handle); + return JS_TRUE; + } + + /* Shrink the buffer to the real size and store its value in rval */ + shrink_Buffer(b); + BufferToJSVal(context,b,rval); + + // Free Buffer + free_Buffer(b); + + if(rval == NULL) { + curl_easy_cleanup(handle); + return JS_FALSE; + } + + JS_MaybeGC(context); + + curl_easy_cleanup(handle); + + return JS_TRUE; +} + + +static JSBool +PostHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + CURL* handle; + Buffer b; + char *url, *body; + size_t charslen, readlen; + + // Run GC + JS_MaybeGC(context); + + // Init Curl + if((handle = curl_easy_init()) == NULL) { + return JS_FALSE; + } + + // Get URL + if((url = JSValToChar(context,argv)) == NULL) { + curl_easy_cleanup(handle); + return JS_FALSE; + } + + // Initialize buffer + b = init_Buffer(); + + curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); // function that recieves data + curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); // buffer to write the data to + curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); + curl_easy_setopt(handle,CURLOPT_URL,url); // url + curl_easy_setopt(handle,CURLOPT_HTTPPOST,1); // Set Op. to post + curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); // No Progress Meter + curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); // only ipv4 + + if((body = JSValToChar(context,argv+1)) == NULL) { // Convert arg1 to a string + free(url); + free_Buffer(b); + curl_easy_cleanup(handle); + return JS_FALSE; + } + + curl_easy_setopt(handle,CURLOPT_COPYPOSTFIELDS,body); // Curl wants '\0' terminated, we oblige + free(body); + + struct curl_slist *slist = generateCurlHeaders(context,argv+2); // Initialize Headers + if(slist != NULL) { + curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); + } + + int exitcode; + + if((exitcode = curl_easy_perform(handle)) != 0) { // Perform + curl_slist_free_all(slist); + free(url); + free_Buffer(b); + curl_easy_cleanup(handle); + return JS_FALSE; + } + + free(url); + curl_slist_free_all(slist); + + // Convert response back to javascript value and then clean + BufferToJSVal(context,b,rval); + free_Buffer(b); + curl_easy_cleanup(handle); + + JS_MaybeGC(context); + + if( rval == NULL ) { + return JS_FALSE; + } + + return JS_TRUE; +} + +#define CLEAN \ + free_Buffer(b); \ + free_Buffer(b_data->buffer); \ + free(b_data); \ + free(url) + +static JSBool +PutHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ + + Buffer b; + BufferCount b_data; + char *url, *data; + size_t charslen, readlen; + JSObject* header_obj; + + // Run GC + JS_MaybeGC(context); + + // Get URL + url = JSValToChar(context,argv); + + // Allocate buffer that will store the get resultant + b = init_Buffer(); + + // Allocate data buffer and move data into them + b_data = (BufferCount)malloc(sizeof(Buffer) + sizeof(int)); + b_data->buffer = init_Buffer(); + b_data->pos = 0; + + data = JSValToChar(context,(argv+1)); + // TODO: remove strlen + append_Buffer(b_data->buffer,data,strlen(data)); + + free(data); + + CURL* handle; + + // Init Curl + + if((handle = curl_easy_init()) == NULL) { + CLEAN; + return JS_FALSE; + } + + // Configuration + curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); + curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); + curl_easy_setopt(handle,CURLOPT_READFUNCTION,curl_read); + curl_easy_setopt(handle,CURLOPT_READDATA,b_data); + curl_easy_setopt(handle,CURLOPT_URL,url); + curl_easy_setopt(handle,CURLOPT_UPLOAD,1); + + // Curl structure + struct curl_slist *slist = generateCurlHeaders(context,argv+2); + if(slist != NULL) { + curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); + } + + // Little Things + // No progress meter + curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); + // Use only ipv4 + curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); + + // Perform + int exitcode; + + if((exitcode = curl_easy_perform(handle)) != 0) { + if(slist != NULL) + curl_slist_free_all(slist); + curl_easy_cleanup(handle); + CLEAN; + return JS_FALSE; + } + + if(slist != NULL) + curl_slist_free_all(slist); + free_Buffer(b_data->buffer); + free(b_data); + free(url); + + /* Treat the empty string specially */ + if (b->count == 0) { + *rval = JS_GetEmptyStringValue(context); + curl_easy_cleanup(handle); + free_Buffer(b); + return JS_TRUE; + } + + /* Shrink the buffer to the real size */ + shrink_Buffer(b); + + BufferToJSVal(context,b,rval); + + free_Buffer(b); + + if(rval == NULL) { + curl_easy_cleanup(handle); + return JS_FALSE; + } + + JS_MaybeGC(context); + + curl_easy_cleanup(handle); + + return JS_TRUE; +} + +static JSBool +DelHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Buffer b; + char *url; + size_t charslen, readlen; + char header_name[7]; + strcpy(header_name,"DELETE"); + + // Run GC + JS_MaybeGC(context); + + // Get URL + url = JSValToChar(context,argv); + + // Allocate buffer that will store the del resultant + b = init_Buffer(); + + CURL* handle; + + // Init Curl + if((handle = curl_easy_init()) == NULL) { + free_Buffer(b); + return JS_FALSE; + } + + // Configuration + curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); + curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); + curl_easy_setopt(handle,CURLOPT_URL,url); + curl_easy_setopt(handle,CURLOPT_CUSTOMREQUEST,header_name); + curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); + curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); + + // Curl structure + struct curl_slist *slist = NULL; + if((slist = generateCurlHeaders(context,argv+1)) != NULL) { + curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); + } + + // Perform + int exitcode; + + if((exitcode = curl_easy_perform(handle)) != 0) { + if(slist != NULL) + curl_slist_free_all(slist); + curl_easy_cleanup(handle); + free(url); + free_Buffer(b); + return JS_FALSE; + } + + if(slist != NULL) + curl_slist_free_all(slist); + free(url); + + /* Treat the empty string specially */ + if (b->count == 0) { + *rval = JS_GetEmptyStringValue(context); + curl_easy_cleanup(handle); + free_Buffer(b); + return JS_TRUE; + } + + /* Shrink the buffer to the real size */ + shrink_Buffer(b); + + BufferToJSVal(context,b,rval); + + if(rval == NULL) { + curl_easy_cleanup(handle); + return JS_FALSE; + } + + JS_MaybeGC(context); + + curl_easy_cleanup(handle); + + return JS_TRUE; +} + +static JSBool +CopyHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Buffer b; + char *url; + size_t charslen, readlen; + char header_name[5]; + strcpy(header_name,"COPY"); + + // Run GC + JS_MaybeGC(context); + + // Get URL + url = JSValToChar(context,argv); + + // Allocate buffer that will store the del resultant + b = init_Buffer(); + + CURL* handle; + + // Init Curl + if((handle = curl_easy_init()) == NULL) { + free_Buffer(b); + return JS_FALSE; + } + + // Configuration + curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); + curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); + curl_easy_setopt(handle,CURLOPT_URL,url); + curl_easy_setopt(handle,CURLOPT_CUSTOMREQUEST,header_name); + curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); + curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); + + // Curl structure + struct curl_slist *slist = NULL; + if((slist = generateCurlHeaders(context,argv+1)) != NULL) { + curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); + } + + // Perform + int exitcode; + + if((exitcode = curl_easy_perform(handle)) != 0) { + if(slist != NULL) + curl_slist_free_all(slist); + curl_easy_cleanup(handle); + free(url); + free_Buffer(b); + return JS_FALSE; + } + + if(slist != NULL) + curl_slist_free_all(slist); + free(url); + + /* Treat the empty string specially */ + if (b->count == 0) { + *rval = JS_GetEmptyStringValue(context); + curl_easy_cleanup(handle); + free_Buffer(b); + return JS_TRUE; + } + + /* Shrink the buffer to the real size */ + shrink_Buffer(b); + + BufferToJSVal(context,b,rval); + + if(rval == NULL) { + curl_easy_cleanup(handle); + return JS_FALSE; + } + + JS_MaybeGC(context); + + curl_easy_cleanup(handle); + + return JS_TRUE; +} + +static JSBool +MoveHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + Buffer b; + char *url; + size_t charslen, readlen; + char header_name[5]; + strcpy(header_name,"MOVE"); + + // Run GC + JS_MaybeGC(context); + + // Get URL + url = JSValToChar(context,argv); + + // Allocate buffer that will store the del resultant + b = init_Buffer(); + + CURL* handle; + + // Init Curl + if((handle = curl_easy_init()) == NULL) { + free_Buffer(b); + return JS_FALSE; + } + + // Configuration + curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); + curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); + curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); + curl_easy_setopt(handle,CURLOPT_URL,url); + curl_easy_setopt(handle,CURLOPT_CUSTOMREQUEST,header_name); + curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); + curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); + + // Curl structure + struct curl_slist *slist = NULL; + if((slist = generateCurlHeaders(context,argv+1)) != NULL) { + curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); + } + + // Perform + int exitcode; + + if((exitcode = curl_easy_perform(handle)) != 0) { + if(slist != NULL) + curl_slist_free_all(slist); + curl_easy_cleanup(handle); + free(url); + free_Buffer(b); + return JS_FALSE; + } + + if(slist != NULL) + curl_slist_free_all(slist); + free(url); + + /* Treat the empty string specially */ + if (b->count == 0) { + *rval = JS_GetEmptyStringValue(context); + curl_easy_cleanup(handle); + free_Buffer(b); + return JS_TRUE; + } + + /* Shrink the buffer to the real size */ + shrink_Buffer(b); + + BufferToJSVal(context,b,rval); + + if(rval == NULL) { + curl_easy_cleanup(handle); + return JS_FALSE; + } + + JS_MaybeGC(context); + + curl_easy_cleanup(handle); + + return JS_TRUE; +} + int main(int argc, const char * argv[]) { JSRuntime *runtime; @@ -428,7 +1215,14 @@ main(int argc, const char * argv[]) { || !JS_DefineFunction(context, global, "print", Print, 0, 0) || !JS_DefineFunction(context, global, "quit", Quit, 0, 0) || !JS_DefineFunction(context, global, "readline", ReadLine, 0, 0) - || !JS_DefineFunction(context, global, "seal", Seal, 0, 0)) + || !JS_DefineFunction(context, global, "seal", Seal, 0, 0) + || !JS_DefineFunction(context, global, "gethttp", GetHttp, 1, 0) + || !JS_DefineFunction(context, global, "headhttp", HeadHttp, 1, 0) + || !JS_DefineFunction(context, global, "posthttp", PostHttp, 2, 0) + || !JS_DefineFunction(context, global, "puthttp", PutHttp, 2, 0) + || !JS_DefineFunction(context, global, "delhttp", DelHttp, 1, 0) + || !JS_DefineFunction(context, global, "movehttp", MoveHttp, 1, 0) + || !JS_DefineFunction(context, global, "copyhttp", CopyHttp, 1, 0)) return 1; if (argc != 2) { diff --git a/src/couchdb/curlhelper.c b/src/couchdb/curlhelper.c new file mode 100644 index 00000000..99b2e6ab --- /dev/null +++ b/src/couchdb/curlhelper.c @@ -0,0 +1,261 @@ +/* + +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. + +*/ + +#include +#include +#include "curlhelper.h" + +#define TRUE 1 +#define FALSE 0 + +Buffer init_Buffer() { + Buffer b; + + if((b = (Buffer)malloc(sizeof(char*) + sizeof(int)*2)) == NULL) { + return NULL; + } + + b->count = 0; + b->capacity = 50; + + if(b->data = (char*)malloc(sizeof(char)*b->capacity)) { + return b; + } else { + return NULL; + } +} + +void free_Buffer(Buffer b) { + if(b == NULL) + return; + if(b->data != NULL) + free(b->data); + free(b); +} + +int append_Buffer(Buffer b, char* c, int length) { + int capacity_changed; + int new_count; + + capacity_changed = FALSE; + new_count = b->count + length; + + if(new_count > b->capacity) { + capacity_changed = TRUE; + b->capacity *= 2; + } + + while(new_count > b->capacity) { + b->capacity *= 2; + } + + if(capacity_changed) { + if((b->data = (char*)realloc(b->data,b->capacity)) == NULL) { + return FALSE; + } + } + + int i; + + for(i = 0;i < length;i++) { + *(b->data + b->count + i) = *(c + i); + } + + b->count = new_count; + + return TRUE; +} + +char* toString_Buffer(Buffer b) { + char* result; + + if((result = (char*)malloc(sizeof(char)*(b->count+1))) == NULL) { + return NULL; + } + + result[b->count] = '\0'; + + int i; + + for(i = 0;i < b->count;i++) { + result[i] = b->data[i]; + } + + return result; +} + +int print_Buffer(Buffer b) { + char* c; + + if((c = toString_Buffer(b)) == NULL) { + return FALSE; + } + + printf("%s\n",c); + + free(c); + + return TRUE; +} + + +int shrink_Buffer(Buffer b) { + b->capacity = b->count; + + if((b->data = realloc(b->data,sizeof(char)*b->capacity)) == NULL) { + return FALSE; + } else { + return TRUE; + } +} + +void copy_Buffer(Buffer b, char* c, int pos, int length) { + if((pos + length) > b->count) + return; + + int i; + + for(i = 0; i < length;i++) { + *(c + i) = *(b->data + pos + i); + } +} + + +List init_List(int capacity) { + if(capacity < 5) + capacity = 5; + + List l; + + if((l = (List)malloc(sizeof(void**)+sizeof(int)*2)) == NULL) { + return NULL; + } + + l->count = 0; + l->capacity = capacity; + + if((l->elements = (void**)malloc(sizeof(void*)*l->capacity)) == NULL) { + return NULL; + } + + return l; +} + +void free_List(List l) { + if(l == NULL) + return; + if(l->elements != NULL) + free(l->elements); + free(l); +} + +void* get_List(List l, int pos) { + if(pos > (l->count - 1)) { + return NULL; + } + return *(l->elements + pos); +} + +void* pull_List(List l) { + void* r = *(l->elements); + + int i; + + for(i = 1; i < (l->count-1);i++) { + l->elements[i] = l->elements[i+1]; + } + l->count -= 1; + return r; +} + +int set_List(List l, int pos, void* ptr) { + if(pos > (l->count - 1)) { + return FALSE; + } + + *(l->elements + pos) = ptr; + + return TRUE; +} + +int append_List(List l, void* ptr, int length) { + int capacity_changed; + int new_count; + + capacity_changed = FALSE; + new_count = l->count + length; + + if(new_count > l->capacity) { + capacity_changed = TRUE; + l->capacity *= 2; + } + + while(new_count > l->capacity) { + l->capacity *= 2; + } + + if(capacity_changed) { + if((l->elements = (void*)realloc(l->elements,l->capacity)) == NULL) { + return FALSE; + } + } + + int i; + + for(i = 0;i < length;i++) { + *(l->elements + l->count + i) = ptr + i; + } + + l->count = new_count; + + return TRUE; +} + +int push_List(List l, void* ptr, int length) { + int capacity_changed; + int new_count; + + capacity_changed = FALSE; + new_count = l->count + length; + + if(new_count > l->capacity) { + capacity_changed = TRUE; + l->capacity *= 2; + } + + while(new_count > l->capacity) { + l->capacity *= 2; + } + + if(capacity_changed) { + if((l->elements = (void*)realloc(l->elements,l->capacity)) == NULL) { + return FALSE; + } + } + + int i; + + for(i = 0;i < length;i++) { + *(l->elements + l->count + i) = *(l->elements + i); + } + + for(i = 0;i < length;i++) { + *(l->elements + i) = ptr+i; + } + + l->count = new_count; + + return TRUE; +} diff --git a/src/couchdb/curlhelper.h b/src/couchdb/curlhelper.h new file mode 100644 index 00000000..c04c2c92 --- /dev/null +++ b/src/couchdb/curlhelper.h @@ -0,0 +1,49 @@ +/* + +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. + +*/ + +#ifndef CURLHELPER_H +#define CURLHELPER_H + +typedef struct { + char* data; + int count; + int capacity; +}* Buffer; + +Buffer init_Buffer(); +void free_Buffer(Buffer b); +int append_Buffer(Buffer b,char* c,int length); +// WARNING USES MALLOC DONT FORGET TO FREE +char* toString_Buffer(Buffer b); +int print_Buffer(Buffer b); +int shrink_Buffer(Buffer b); +void copy_Buffer(Buffer b, char* c, int pos, int length); + + +typedef struct { + void** elements; + int count; + int capacity; +}* List; + +List init_List(int capacity); +void free_List(List l); +void* get_List(List l, int pos); +void* pull_List(List l); +int set_List(List l, int pos, void* ptr); +int append_List(List l, void* ptr, int length); +int push_List(List l, void* ptr, int length); + +#endif diff --git a/test/runner.sh b/test/runner.sh index ae0f1ef8..e071ad63 100755 --- a/test/runner.sh +++ b/test/runner.sh @@ -1,3 +1,5 @@ #!/bin/sh -e erl -noshell -pa ../src/couchdb -pa ../src/mochiweb -eval "runner:run()" + +cat ../share/www/script/couch.js ../share/www/script/couch_tests.js test.js | ../src/couchdb/couchjs - diff --git a/test/test.js b/test/test.js new file mode 100644 index 00000000..1d3e989b --- /dev/null +++ b/test/test.js @@ -0,0 +1,252 @@ +// 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); + value = value.slice(0, value.length-1); + return value; + } + } + 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" : window; +CouchDB.port = 5984; + +CouchDB.request = function(method, uri, options) { + var full_uri = "http://" + CouchDB.host + ":" + CouchDB.port + 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 tests) { + 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 = tests[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); +p("Port: "+CouchDB.port); + +try { + p("Version: "+CouchDB.getVersion()+"\n"); + runAllTestsConsole(); + // runTestConsole(tests.attachments); +} catch (e) { + p(e.toString()); +} + +p("\nFinished"); -- cgit v1.2.3