From e29a1924afe9e6051369f7bcbf44ccdf53de536a Mon Sep 17 00:00:00 2001 From: Paul Joseph Davis Date: Mon, 9 Nov 2009 00:39:16 +0000 Subject: Fixes 'make distcheck' to run the test suite. Quite a few changes to the build system to handle VPATH builds appropriately as well as make the test suite know about them. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@833951 13f79535-47bb-0310-9956-ffa450edef68 --- Makefile.am | 6 +- bin/Makefile.am | 2 +- configure.ac | 4 + etc/couchdb/Makefile.am | 13 +- etc/couchdb/default.ini.tpl.in | 2 +- license.skip | 6 + share/Makefile.am | 1 + src/couchdb/priv/Makefile.am | 9 +- src/couchdb/priv/stat_descriptions.cfg | 37 -- src/couchdb/priv/stat_descriptions.cfg.in | 37 ++ src/erlang-oauth/Makefile.am | 15 +- src/erlang-oauth/oauth.app | 20 - src/erlang-oauth/oauth.app.in | 20 + src/ibrowse/Makefile.am | 9 +- src/ibrowse/ibrowse.app | 13 - src/ibrowse/ibrowse.app.in | 13 + src/mochiweb/Makefile.am | 11 +- src/mochiweb/mochiweb.app | 32 -- src/mochiweb/mochiweb.app.in | 32 ++ test/Makefile.am | 34 +- test/etap/001-load.t | 4 +- test/etap/002-erl-driver.t | 3 +- test/etap/010-file-basics.t | 5 +- test/etap/011-file-headers.t | 5 +- test/etap/020-btree-basics.t | 5 +- test/etap/021-btree-reductions.t | 3 +- test/etap/030-doc-from-json.t | 3 +- test/etap/031-doc-to-json.t | 3 +- test/etap/040-util.t | 3 +- test/etap/041-uuid-gen.t | 9 +- test/etap/050-stream.t | 3 +- test/etap/060-kt-merging.t | 3 +- test/etap/061-kt-missing-leaves.t | 3 +- test/etap/062-kt-remove-leaves.t | 3 +- test/etap/063-kt-get-leaves.t | 3 +- test/etap/064-kt-counting.t | 3 +- test/etap/065-kt-stemming.t | 3 +- test/etap/070-couch-db.t | 4 +- test/etap/080-config-get-set.t | 5 +- test/etap/081-config-override.t | 11 +- test/etap/082-config-register.t | 5 +- test/etap/083-config-no-files.t | 5 +- test/etap/090-task-status.t | 3 +- test/etap/100-ref-counter.t | 3 +- test/etap/110-replication-httpc.t | 16 +- test/etap/111-replication-changes-feed.t | 17 +- test/etap/112-replication-missing-revs.t | 16 +- test/etap/120-stats-collect.t | 3 +- test/etap/121-stats-aggregates.t | 13 +- test/etap/Makefile.am | 64 +++ test/etap/run.tpl | 27 ++ test/etap/test_util.erl.in | 35 ++ test/javascript/Makefile.am | 15 + test/javascript/runner.sh | 19 + test/javascript/test.js | 249 +++++++++++ test/query_server_spec.rb | 695 ------------------------------ test/run_native_process.es | 55 --- test/runner.sh | 19 - test/test.js | 249 ----------- test/view_server/Makefile.am | 15 + test/view_server/query_server_spec.rb | 695 ++++++++++++++++++++++++++++++ test/view_server/run_native_process.es | 55 +++ utils/Makefile.am | 19 +- 63 files changed, 1411 insertions(+), 1281 deletions(-) delete mode 100644 src/couchdb/priv/stat_descriptions.cfg create mode 100644 src/couchdb/priv/stat_descriptions.cfg.in delete mode 100644 src/erlang-oauth/oauth.app create mode 100644 src/erlang-oauth/oauth.app.in delete mode 100644 src/ibrowse/ibrowse.app create mode 100644 src/ibrowse/ibrowse.app.in delete mode 100644 src/mochiweb/mochiweb.app create mode 100644 src/mochiweb/mochiweb.app.in create mode 100644 test/etap/Makefile.am create mode 100644 test/etap/run.tpl create mode 100644 test/etap/test_util.erl.in create mode 100644 test/javascript/Makefile.am create mode 100755 test/javascript/runner.sh create mode 100644 test/javascript/test.js delete mode 100644 test/query_server_spec.rb delete mode 100755 test/run_native_process.es delete mode 100755 test/runner.sh delete mode 100644 test/test.js create mode 100644 test/view_server/Makefile.am create mode 100644 test/view_server/query_server_spec.rb create mode 100755 test/view_server/run_native_process.es diff --git a/Makefile.am b/Makefile.am index 8e858457..cb963ec6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,7 +37,7 @@ THANKS.gz: $(top_srcdir)/THANKS -gzip -9 < $< > $@ check: dev - prove test/etap/*.t + $(top_builddir)/test/etap/run cover: dev rm -f cover/*.coverdata @@ -57,6 +57,9 @@ dev: all mkdir -p $(top_builddir)/tmp/log mkdir -p $(top_builddir)/tmp/run +distclean-local: + rm -fr $(top_builddir)/tmp + .PHONY: local-clean local-clean: maintainer-clean @echo "This command is intended for maintainers to use;" @@ -69,7 +72,6 @@ local-clean: maintainer-clean rm -f $(top_srcdir)/test/etap/temp.* rm -f $(top_srcdir)/*.tar.gz rm -f $(top_srcdir)/*.tar.gz.* - rm -fr $(top_srcdir)/tmp find $(top_srcdir) -name Makefile.in -exec rm {} \; distcheck-hook: diff --git a/bin/Makefile.am b/bin/Makefile.am index f7eb2697..fc84b1a8 100644 --- a/bin/Makefile.am +++ b/bin/Makefile.am @@ -60,7 +60,7 @@ couchjs: couchjs.tpl chmod +x $@ couchjs_dev: couchjs.tpl - sed -e "s|%locallibbindir%|$(abs_top_srcdir)/src/couchdb|g" \ + sed -e "s|%locallibbindir%|$(abs_top_builddir)/src/couchdb|g" \ -e "s|%bug_uri%|@bug_uri@|g" \ -e "s|%package_author_address%|@package_author_address@|g" \ -e "s|%package_author_name%|@package_author_name@|g" \ diff --git a/configure.ac b/configure.ac index f48b8677..24667bcc 100644 --- a/configure.ac +++ b/configure.ac @@ -403,6 +403,10 @@ AC_CONFIG_FILES([src/etap/Makefile]) AC_CONFIG_FILES([src/ibrowse/Makefile]) AC_CONFIG_FILES([src/mochiweb/Makefile]) AC_CONFIG_FILES([test/Makefile]) +AC_CONFIG_FILES([test/etap/Makefile]) +AC_CONFIG_FILES([test/etap/test_util.erl]) +AC_CONFIG_FILES([test/javascript/Makefile]) +AC_CONFIG_FILES([test/view_server/Makefile]) AC_CONFIG_FILES([utils/Makefile]) AC_CONFIG_FILES([var/Makefile]) diff --git a/etc/couchdb/Makefile.am b/etc/couchdb/Makefile.am index 805274c4..b5d8aaf0 100644 --- a/etc/couchdb/Makefile.am +++ b/etc/couchdb/Makefile.am @@ -11,7 +11,7 @@ ## the License. couchprivlibdir = $(localerlanglibdir)/couch-$(version)/priv/lib -devcouchprivlibdir = $(abs_top_srcdir)/src/couchdb/.libs +devcouchprivlibdir = $(abs_top_builddir)/src/couchdb/.libs localconf_DATA = default.ini noinst_DATA = default_dev.ini local_dev.ini @@ -29,6 +29,7 @@ default.ini: default.ini.tpl sed -e "s|%bindir%|.|g" \ -e "s|%localconfdir%|$(localconfdir)|g" \ -e "s|%localdatadir%|../share/couchdb|g" \ + -e "s|%localbuilddatadir%|../share/couchdb|g" \ -e "s|%localstatelibdir%|../var/lib/couchdb|g" \ -e "s|%localstatelogdir%|../var/log/couchdb|g" \ -e "s|%couchprivlibdir%|../lib/couch-$(version)/priv/lib|g" \ @@ -39,6 +40,7 @@ default.ini: default.ini.tpl sed -e "s|%bindir%|$(bindir)|g" \ -e "s|%localconfdir%|$(localconfdir)|g" \ -e "s|%localdatadir%|$(localdatadir)|g" \ + -e "s|%localbuilddatadir%|$(localdatadir)|g" \ -e "s|%localstatelibdir%|$(localstatelibdir)|g" \ -e "s|%localstatelogdir%|$(localstatelogdir)|g" \ -e "s|%couchprivlibdir%|$(couchprivlibdir)|g" \ @@ -47,11 +49,12 @@ default.ini: default.ini.tpl endif default_dev.ini: default.ini.tpl - sed -e "s|%bindir%|$(abs_top_srcdir)/bin|g" \ - -e "s|%localconfdir%|$(abs_top_srcdir)/etc/couchdb|g" \ + sed -e "s|%bindir%|$(abs_top_builddir)/bin|g" \ + -e "s|%localconfdir%|$(abs_top_builddir)/etc/couchdb|g" \ -e "s|%localdatadir%|$(abs_top_srcdir)/share|g" \ - -e "s|%localstatelibdir%|$(abs_top_srcdir)/tmp/lib|g" \ - -e "s|%localstatelogdir%|$(abs_top_srcdir)/tmp/log|g" \ + -e "s|%localbuilddatadir%|$(abs_top_builddir)/share|g" \ + -e "s|%localstatelibdir%|$(abs_top_builddir)/tmp/lib|g" \ + -e "s|%localstatelogdir%|$(abs_top_builddir)/tmp/log|g" \ -e "s|%couchprivlibdir%|$(devcouchprivlibdir)|g" \ -e "s|%couchjs_command_name%|$(couchjs_dev_command_name)|g" \ < $< > $@ diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in index 33385207..422292ff 100644 --- a/etc/couchdb/default.ini.tpl.in +++ b/etc/couchdb/default.ini.tpl.in @@ -31,7 +31,7 @@ secret = replace this with a real secret in your local.ini file require_valid_user = false [query_servers] -javascript = %bindir%/%couchjs_command_name% %localdatadir%/server/main.js +javascript = %bindir%/%couchjs_command_name% %localbuilddatadir%/server/main.js ; Changing reduce_limit to false will disable reduce_limit. ; If you think you're hitting reduce_limit with a "good" reduce function, diff --git a/license.skip b/license.skip index c7cfa20b..635a6d05 100644 --- a/license.skip +++ b/license.skip @@ -74,6 +74,12 @@ ^test/local.ini ^test/Makefile ^test/Makefile.in +^test/etap/Makefile +^test/etap/Makefile.in +^test/javascript/Makefile +^test/javascript/Makefile.in +^test/view_server/Makefile +^test/view_server/Makefile.in ^tmp/* ^THANKS ^utils/Makefile diff --git a/share/Makefile.am b/share/Makefile.am index 86ab8349..4adce5db 100644 --- a/share/Makefile.am +++ b/share/Makefile.am @@ -24,6 +24,7 @@ JS_FILE_COMPONENTS = \ JS_FILE_COMPONENTS_LAST = server/loop.js $(JS_FILE): $(JS_FILE_COMPONENTS) $(JS_FILE_COMPONENTS_LAST) + mkdir -p `dirname $(JS_FILE)` echo "// DO NOT EDIT THIS FILE BY HAND" > $@ echo >> $@ cat $^ >> $@ diff --git a/src/couchdb/priv/Makefile.am b/src/couchdb/priv/Makefile.am index 8c504dbc..fecaa64b 100644 --- a/src/couchdb/priv/Makefile.am +++ b/src/couchdb/priv/Makefile.am @@ -13,11 +13,18 @@ couchlibdir = $(localerlanglibdir)/couch-$(version) couchprivdir = $(couchlibdir)/priv -EXTRA_DIST = couchspawnkillable.sh stat_descriptions.cfg +EXTRA_DIST = \ + couchspawnkillable.sh \ + stat_descriptions.cfg.in + +CLEANFILES = stat_descriptions.cfg couchpriv_DATA = stat_descriptions.cfg couchpriv_PROGRAMS = couchspawnkillable +%.cfg: %.cfg.in + cp $< $@ + if WINDOWS couchspawnkillable_SOURCES = couchspawnkillable_win.c endif diff --git a/src/couchdb/priv/stat_descriptions.cfg b/src/couchdb/priv/stat_descriptions.cfg deleted file mode 100644 index 91a1540f..00000000 --- a/src/couchdb/priv/stat_descriptions.cfg +++ /dev/null @@ -1,37 +0,0 @@ -% Style guide for descriptions: Start with a lowercase letter & do not add -% a trailing full-stop / period -% Please keep this in alphabetical order - -{couchdb, database_writes, "number of times a database was change"}. -{couchdb, database_reads, "number of times a document was read from a databas"}. -{couchdb, open_databases, "number of open database"}. -{couchdb, open_os_files, "number of file descriptors CouchDB has ope"}. -{couchdb, request_time, "length of a request inside CouchDB without MochiWe"}. - -{httpd, bulk_requests, "number of bulk request"}. -{httpd, requests, "number of HTTP request"}. -{httpd, temporary_view_reads, "number of temporary view read"}. -{httpd, view_reads, "number of view read"}. -{httpd, clients_requesting_changes, "number of clients for continuous _change"}. - -{httpd_request_methods, 'COPY', "number of HTTP COPY request"}. -{httpd_request_methods, 'DELETE', "number of HTTP DELETE request"}. -{httpd_request_methods, 'GET', "number of HTTP GET request"}. -{httpd_request_methods, 'HEAD', "number of HTTP HEAD request"}. -{httpd_request_methods, 'MOVE', "number of HTTP MOVE request"}. -{httpd_request_methods, 'POST', "number of HTTP POST request"}. -{httpd_request_methods, 'PUT', "number of HTTP PUT request"}. - -{httpd_status_codes, '200', "number of HTTP 200 OK response"}. -{httpd_status_codes, '201', "number of HTTP 201 Created response"}. -{httpd_status_codes, '202', "number of HTTP 202 Accepted response"}. -{httpd_status_codes, '301', "number of HTTP 301 Moved Permanently response"}. -{httpd_status_codes, '304', "number of HTTP 304 Not Modified response"}. -{httpd_status_codes, '400', "number of HTTP 400 Bad Request response"}. -{httpd_status_codes, '401', "number of HTTP 401 Unauthorized response"}. -{httpd_status_codes, '403', "number of HTTP 403 Forbidden response"}. -{httpd_status_codes, '404', "number of HTTP 404 Not Found response"}. -{httpd_status_codes, '405', "number of HTTP 405 Method Not Allowed response"}. -{httpd_status_codes, '409', "number of HTTP 409 Conflict response"}. -{httpd_status_codes, '412', "number of HTTP 412 Precondition Failed response"}. -{httpd_status_codes, '500', "number of HTTP 500 Internal Server Error response"}. diff --git a/src/couchdb/priv/stat_descriptions.cfg.in b/src/couchdb/priv/stat_descriptions.cfg.in new file mode 100644 index 00000000..91a1540f --- /dev/null +++ b/src/couchdb/priv/stat_descriptions.cfg.in @@ -0,0 +1,37 @@ +% Style guide for descriptions: Start with a lowercase letter & do not add +% a trailing full-stop / period +% Please keep this in alphabetical order + +{couchdb, database_writes, "number of times a database was change"}. +{couchdb, database_reads, "number of times a document was read from a databas"}. +{couchdb, open_databases, "number of open database"}. +{couchdb, open_os_files, "number of file descriptors CouchDB has ope"}. +{couchdb, request_time, "length of a request inside CouchDB without MochiWe"}. + +{httpd, bulk_requests, "number of bulk request"}. +{httpd, requests, "number of HTTP request"}. +{httpd, temporary_view_reads, "number of temporary view read"}. +{httpd, view_reads, "number of view read"}. +{httpd, clients_requesting_changes, "number of clients for continuous _change"}. + +{httpd_request_methods, 'COPY', "number of HTTP COPY request"}. +{httpd_request_methods, 'DELETE', "number of HTTP DELETE request"}. +{httpd_request_methods, 'GET', "number of HTTP GET request"}. +{httpd_request_methods, 'HEAD', "number of HTTP HEAD request"}. +{httpd_request_methods, 'MOVE', "number of HTTP MOVE request"}. +{httpd_request_methods, 'POST', "number of HTTP POST request"}. +{httpd_request_methods, 'PUT', "number of HTTP PUT request"}. + +{httpd_status_codes, '200', "number of HTTP 200 OK response"}. +{httpd_status_codes, '201', "number of HTTP 201 Created response"}. +{httpd_status_codes, '202', "number of HTTP 202 Accepted response"}. +{httpd_status_codes, '301', "number of HTTP 301 Moved Permanently response"}. +{httpd_status_codes, '304', "number of HTTP 304 Not Modified response"}. +{httpd_status_codes, '400', "number of HTTP 400 Bad Request response"}. +{httpd_status_codes, '401', "number of HTTP 401 Unauthorized response"}. +{httpd_status_codes, '403', "number of HTTP 403 Forbidden response"}. +{httpd_status_codes, '404', "number of HTTP 404 Not Found response"}. +{httpd_status_codes, '405', "number of HTTP 405 Method Not Allowed response"}. +{httpd_status_codes, '409', "number of HTTP 409 Conflict response"}. +{httpd_status_codes, '412', "number of HTTP 412 Precondition Failed response"}. +{httpd_status_codes, '500', "number of HTTP 500 Internal Server Error response"}. diff --git a/src/erlang-oauth/Makefile.am b/src/erlang-oauth/Makefile.am index c87d7f68..1d123396 100644 --- a/src/erlang-oauth/Makefile.am +++ b/src/erlang-oauth/Makefile.am @@ -16,6 +16,7 @@ oauthebindir = $(localerlanglibdir)/erlang-oauth/ebin # we add a ./configure option to enable it. oauth_file_collection = \ + oauth.app.in \ oauth.erl \ oauth_hmac_sha1.erl \ oauth_http.erl \ @@ -23,9 +24,8 @@ oauth_file_collection = \ oauth_unix.erl \ oauth_uri.erl -oauthebin_static_file = oauth.app - oauthebin_make_generated_file_list = \ + oauth.app \ oauth.beam \ oauth_hmac_sha1.beam \ oauth_http.beam \ @@ -34,15 +34,16 @@ oauthebin_make_generated_file_list = \ oauth_uri.beam oauthebin_DATA = \ - $(oauthebin_static_file) \ - $(oauthebin_make_generated_file_list) + $(oauthebin_make_generated_file_list) -EXTRA_DIST = \ - $(oauth_file_collection) \ - $(oauthebin_static_file) +EXTRA_DIST = \ + $(oauth_file_collection) CLEANFILES = \ $(oauthebin_make_generated_file_list) +%.app: %.app.in + cp $< $@ + %.beam: %.erl $(ERLC) $(ERLC_FLAGS) $< diff --git a/src/erlang-oauth/oauth.app b/src/erlang-oauth/oauth.app deleted file mode 100644 index 6357b9b0..00000000 --- a/src/erlang-oauth/oauth.app +++ /dev/null @@ -1,20 +0,0 @@ -{application, oauth, [ - {description, "Erlang OAuth implementation"}, - {vsn, "dev"}, - {modules, [ - oauth, - oauth_hmac_sha1, - oauth_http, - oauth_plaintext, - oauth_rsa_sha1, - oauth_unix, - oauth_uri - ]}, - {registered, []}, - {applications, [ - kernel, - stdlib, - crypto, - inets - ]} -]}. diff --git a/src/erlang-oauth/oauth.app.in b/src/erlang-oauth/oauth.app.in new file mode 100644 index 00000000..6357b9b0 --- /dev/null +++ b/src/erlang-oauth/oauth.app.in @@ -0,0 +1,20 @@ +{application, oauth, [ + {description, "Erlang OAuth implementation"}, + {vsn, "dev"}, + {modules, [ + oauth, + oauth_hmac_sha1, + oauth_http, + oauth_plaintext, + oauth_rsa_sha1, + oauth_unix, + oauth_uri + ]}, + {registered, []}, + {applications, [ + kernel, + stdlib, + crypto, + inets + ]} +]}. diff --git a/src/ibrowse/Makefile.am b/src/ibrowse/Makefile.am index 76262a6e..510f36a9 100644 --- a/src/ibrowse/Makefile.am +++ b/src/ibrowse/Makefile.am @@ -13,6 +13,7 @@ ibrowseebindir = $(localerlanglibdir)/ibrowse-1.5.2/ebin ibrowse_file_collection = \ + ibrowse.app.in \ ibrowse.erl \ ibrowse_app.erl \ ibrowse_http_client.erl \ @@ -21,9 +22,8 @@ ibrowse_file_collection = \ ibrowse_sup.erl \ ibrowse_test.erl -ibrowseebin_static_file = ibrowse.app - ibrowseebin_make_generated_file_list = \ + ibrowse.app \ ibrowse.beam \ ibrowse_app.beam \ ibrowse_http_client.beam \ @@ -33,16 +33,17 @@ ibrowseebin_make_generated_file_list = \ ibrowse_test.beam ibrowseebin_DATA = \ - $(ibrowseebin_static_file) \ $(ibrowseebin_make_generated_file_list) EXTRA_DIST = \ $(ibrowse_file_collection) \ - $(ibrowseebin_static_file) \ ibrowse.hrl CLEANFILES = \ $(ibrowseebin_make_generated_file_list) +%.app: %.app.in + cp $< $@ + %.beam: %.erl $(ERLC) $(ERLC_FLAGS) $< diff --git a/src/ibrowse/ibrowse.app b/src/ibrowse/ibrowse.app deleted file mode 100644 index 4f43dd92..00000000 --- a/src/ibrowse/ibrowse.app +++ /dev/null @@ -1,13 +0,0 @@ -{application, ibrowse, - [{description, "HTTP client application"}, - {vsn, "1.5.1"}, - {modules, [ ibrowse, - ibrowse_http_client, - ibrowse_app, - ibrowse_sup, - ibrowse_lib, - ibrowse_lb ]}, - {registered, []}, - {applications, [kernel,stdlib,sasl]}, - {env, []}, - {mod, {ibrowse_app, []}}]}. diff --git a/src/ibrowse/ibrowse.app.in b/src/ibrowse/ibrowse.app.in new file mode 100644 index 00000000..4f43dd92 --- /dev/null +++ b/src/ibrowse/ibrowse.app.in @@ -0,0 +1,13 @@ +{application, ibrowse, + [{description, "HTTP client application"}, + {vsn, "1.5.1"}, + {modules, [ ibrowse, + ibrowse_http_client, + ibrowse_app, + ibrowse_sup, + ibrowse_lib, + ibrowse_lb ]}, + {registered, []}, + {applications, [kernel,stdlib,sasl]}, + {env, []}, + {mod, {ibrowse_app, []}}]}. diff --git a/src/mochiweb/Makefile.am b/src/mochiweb/Makefile.am index db46ace3..608d4dcd 100644 --- a/src/mochiweb/Makefile.am +++ b/src/mochiweb/Makefile.am @@ -20,6 +20,7 @@ mochiweb_file_collection = \ mochijson.erl \ mochijson2.erl \ mochinum.erl \ + mochiweb.app.in \ mochiweb.erl \ mochiweb_app.erl \ mochiweb_charref.erl \ @@ -37,8 +38,6 @@ mochiweb_file_collection = \ mochiweb_util.erl \ reloader.erl -mochiwebebin_static_file = mochiweb.app - mochiwebebin_make_generated_file_list = \ mochifmt.beam \ mochifmt_records.beam \ @@ -47,6 +46,7 @@ mochiwebebin_make_generated_file_list = \ mochijson.beam \ mochijson2.beam \ mochinum.beam \ + mochiweb.app \ mochiweb.beam \ mochiweb_app.beam \ mochiweb_charref.beam \ @@ -65,15 +65,16 @@ mochiwebebin_make_generated_file_list = \ reloader.beam mochiwebebin_DATA = \ - $(mochiwebebin_static_file) \ $(mochiwebebin_make_generated_file_list) EXTRA_DIST = \ - $(mochiweb_file_collection) \ - $(mochiwebebin_static_file) + $(mochiweb_file_collection) CLEANFILES = \ $(mochiwebebin_make_generated_file_list) +%.app: %.app.in + cp $< $@ + %.beam: %.erl $(ERLC) $(ERLC_FLAGS) $< diff --git a/src/mochiweb/mochiweb.app b/src/mochiweb/mochiweb.app deleted file mode 100644 index cd8dbb25..00000000 --- a/src/mochiweb/mochiweb.app +++ /dev/null @@ -1,32 +0,0 @@ -{application, mochiweb, - [{description, "MochiMedia Web Server"}, - {vsn, "0.01"}, - {modules, [ - mochihex, - mochijson, - mochijson2, - mochinum, - mochiweb, - mochiweb_app, - mochiweb_charref, - mochiweb_cookies, - mochiweb_echo, - mochiweb_headers, - mochiweb_html, - mochiweb_http, - mochiweb_multipart, - mochiweb_request, - mochiweb_response, - mochiweb_skel, - mochiweb_socket_server, - mochiweb_sup, - mochiweb_util, - reloader, - mochifmt, - mochifmt_std, - mochifmt_records - ]}, - {registered, []}, - {mod, {mochiweb_app, []}}, - {env, []}, - {applications, [kernel, stdlib]}]}. diff --git a/src/mochiweb/mochiweb.app.in b/src/mochiweb/mochiweb.app.in new file mode 100644 index 00000000..cd8dbb25 --- /dev/null +++ b/src/mochiweb/mochiweb.app.in @@ -0,0 +1,32 @@ +{application, mochiweb, + [{description, "MochiMedia Web Server"}, + {vsn, "0.01"}, + {modules, [ + mochihex, + mochijson, + mochijson2, + mochinum, + mochiweb, + mochiweb_app, + mochiweb_charref, + mochiweb_cookies, + mochiweb_echo, + mochiweb_headers, + mochiweb_html, + mochiweb_http, + mochiweb_multipart, + mochiweb_request, + mochiweb_response, + mochiweb_skel, + mochiweb_socket_server, + mochiweb_sup, + mochiweb_util, + reloader, + mochifmt, + mochifmt_std, + mochifmt_records + ]}, + {registered, []}, + {mod, {mochiweb_app, []}}, + {env, []}, + {applications, [kernel, stdlib]}]}. diff --git a/test/Makefile.am b/test/Makefile.am index c2e705bb..4c4e54ea 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -10,37 +10,5 @@ ## License for the specific language governing permissions and limitations under ## the License. -dist_TESTS = runner.sh +SUBDIRS = etap javascript view_server -EXTRA_DIST = \ - etap/030-doc-from-json.t \ - etap/080-config-get-set.t \ - etap/064-kt-counting.t \ - etap/081-config-override.1.ini \ - etap/100-ref-counter.t \ - etap/083-config-no-files.t \ - etap/070-couch-db.t \ - etap/010-file-basics.t \ - etap/060-kt-merging.t \ - etap/040-util.t \ - etap/110-replication-httpc.t \ - etap/062-kt-remove-leaves.t \ - etap/001-load.t \ - etap/111-replication-changes-feed.t \ - etap/063-kt-get-leaves.t \ - etap/090-task-status.t \ - etap/021-btree-reductions.t \ - etap/081-config-override.2.ini \ - etap/065-kt-stemming.t \ - etap/020-btree-basics.t \ - etap/081-config-override.t \ - etap/082-config-register.t \ - etap/112-replication-missing-revs.t \ - etap/011-file-headers.t \ - etap/061-kt-missing-leaves.t \ - etap/050-stream.t \ - etap/031-doc-to-json.t \ - query_server_spec.rb \ - run_native_process.es \ - runner.sh \ - test.js diff --git a/test/etap/001-load.t b/test/etap/001-load.t index f42ab3db..6f49e1ba 100755 --- a/test/etap/001-load.t +++ b/test/etap/001-load.t @@ -1,6 +1,5 @@ #!/usr/bin/env escript %% -*- erlang -*- -%%! -pa src/couchdb -sasl errlog_type error -boot start_sasl -noshell % 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 @@ -17,8 +16,7 @@ % Test that we can load each module. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(37), Modules = [ couch_btree, diff --git a/test/etap/002-erl-driver.t b/test/etap/002-erl-driver.t index 4a2a40da..f353eda4 100644 --- a/test/etap/002-erl-driver.t +++ b/test/etap/002-erl-driver.t @@ -13,8 +13,7 @@ main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(3), etap:is( couch_util:start_driver("src/couchdb/.libs"), diff --git a/test/etap/010-file-basics.t b/test/etap/010-file-basics.t index b6c95a6f..09b2f2b1 100755 --- a/test/etap/010-file-basics.t +++ b/test/etap/010-file-basics.t @@ -12,11 +12,10 @@ % License for the specific language governing permissions and limitations under % the License. -filename() -> "./test/etap/temp.010". +filename() -> test_util:build_file("test/etap/temp.010"). main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(16), case (catch test()) of ok -> diff --git a/test/etap/011-file-headers.t b/test/etap/011-file-headers.t index d26629c0..4705f629 100755 --- a/test/etap/011-file-headers.t +++ b/test/etap/011-file-headers.t @@ -14,12 +14,11 @@ % License for the specific language governing permissions and limitations under % the License. -filename() -> "./test/etap/temp.011". +filename() -> test_util:build_file("test/etap/temp.011"). sizeblock() -> 4096. % Need to keep this in sync with couch_file.erl main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), {S1, S2, S3} = now(), random:seed(S1, S2, S3), diff --git a/test/etap/020-btree-basics.t b/test/etap/020-btree-basics.t index 93a139fb..18c4a836 100755 --- a/test/etap/020-btree-basics.t +++ b/test/etap/020-btree-basics.t @@ -14,14 +14,13 @@ % License for the specific language governing permissions and limitations under % the License. -filename() -> "./test/etap/temp.020". +filename() -> test_util:build_file("test/etap/temp.020"). rows() -> 250. -record(btree, {fd, root, extract_kv, assemble_kv, less, reduce}). main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(48), case (catch test()) of ok -> diff --git a/test/etap/021-btree-reductions.t b/test/etap/021-btree-reductions.t index 64d390ed..3e19c767 100755 --- a/test/etap/021-btree-reductions.t +++ b/test/etap/021-btree-reductions.t @@ -18,8 +18,7 @@ filename() -> "./test/etap/temp.021". rows() -> 1000. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(8), case (catch test()) of ok -> diff --git a/test/etap/030-doc-from-json.t b/test/etap/030-doc-from-json.t index 45763173..dc3327aa 100755 --- a/test/etap/030-doc-from-json.t +++ b/test/etap/030-doc-from-json.t @@ -20,8 +20,7 @@ -record(att, {name, type, len, md5= <<>>, revpos=0, data}). main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(26), case (catch test()) of ok -> diff --git a/test/etap/031-doc-to-json.t b/test/etap/031-doc-to-json.t index 00440abe..4e0c3f74 100755 --- a/test/etap/031-doc-to-json.t +++ b/test/etap/031-doc-to-json.t @@ -20,8 +20,7 @@ -record(att, {name, type, len, md5= <<>>, revpos=0, data}). main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(12), case (catch test()) of ok -> diff --git a/test/etap/040-util.t b/test/etap/040-util.t index c4dffc4d..6d6da2c1 100755 --- a/test/etap/040-util.t +++ b/test/etap/040-util.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), application:start(crypto), etap:plan(11), diff --git a/test/etap/041-uuid-gen.t b/test/etap/041-uuid-gen.t index b61141fc..1e6aa9ee 100755 --- a/test/etap/041-uuid-gen.t +++ b/test/etap/041-uuid-gen.t @@ -14,13 +14,13 @@ % the License. default_config() -> - "etc/couchdb/default_dev.ini". + test_util:build_file("etc/couchdb/default_dev.ini"). seq_alg_config() -> - "test/etap/041-uuid-gen-seq.ini". + test_util:source_file("test/etap/041-uuid-gen-seq.ini"). utc_alg_config() -> - "test/etap/041-uuid-gen-utc.ini". + test_util:source_file("test/etap/041-uuid-gen-utc.ini"). % Run tests and wait for the gen_servers to shutdown run_test(IniFiles, Test) -> @@ -38,8 +38,7 @@ run_test(IniFiles, Test) -> end. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), application:start(crypto), etap:plan(6), diff --git a/test/etap/050-stream.t b/test/etap/050-stream.t index b3bce45f..9324916c 100755 --- a/test/etap/050-stream.t +++ b/test/etap/050-stream.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(13), case (catch test()) of ok -> diff --git a/test/etap/060-kt-merging.t b/test/etap/060-kt-merging.t index 17f77e3d..d6b13d6d 100755 --- a/test/etap/060-kt-merging.t +++ b/test/etap/060-kt-merging.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(16), case (catch test()) of ok -> diff --git a/test/etap/061-kt-missing-leaves.t b/test/etap/061-kt-missing-leaves.t index d1dc3fd7..d60b4db8 100755 --- a/test/etap/061-kt-missing-leaves.t +++ b/test/etap/061-kt-missing-leaves.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(4), case (catch test()) of ok -> diff --git a/test/etap/062-kt-remove-leaves.t b/test/etap/062-kt-remove-leaves.t index 9c33475b..745a00be 100755 --- a/test/etap/062-kt-remove-leaves.t +++ b/test/etap/062-kt-remove-leaves.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(6), case (catch test()) of ok -> diff --git a/test/etap/063-kt-get-leaves.t b/test/etap/063-kt-get-leaves.t index a8fa3e94..6d4e8007 100755 --- a/test/etap/063-kt-get-leaves.t +++ b/test/etap/063-kt-get-leaves.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(11), case (catch test()) of ok -> diff --git a/test/etap/064-kt-counting.t b/test/etap/064-kt-counting.t index b68215b5..f182d287 100755 --- a/test/etap/064-kt-counting.t +++ b/test/etap/064-kt-counting.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(4), case (catch test()) of ok -> diff --git a/test/etap/065-kt-stemming.t b/test/etap/065-kt-stemming.t index 6dfe9380..6e781c1d 100755 --- a/test/etap/065-kt-stemming.t +++ b/test/etap/065-kt-stemming.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(3), case (catch test()) of ok -> diff --git a/test/etap/070-couch-db.t b/test/etap/070-couch-db.t index ba381277..bf20dc0a 100755 --- a/test/etap/070-couch-db.t +++ b/test/etap/070-couch-db.t @@ -14,9 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), - code:add_pathz("src/mochiweb"), + test_util:init_code_path(), etap:plan(4), case (catch test()) of diff --git a/test/etap/080-config-get-set.t b/test/etap/080-config-get-set.t index 7702fb07..a4a8577a 100755 --- a/test/etap/080-config-get-set.t +++ b/test/etap/080-config-get-set.t @@ -14,11 +14,10 @@ % the License. default_config() -> - "etc/couchdb/default_dev.ini". + test_util:build_file("etc/couchdb/default_dev.ini"). main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(12), case (catch test()) of ok -> diff --git a/test/etap/081-config-override.t b/test/etap/081-config-override.t index e96e3c23..01f8b4c2 100755 --- a/test/etap/081-config-override.t +++ b/test/etap/081-config-override.t @@ -14,16 +14,16 @@ % the License. default_config() -> - "etc/couchdb/default_dev.ini". + test_util:build_file("etc/couchdb/default_dev.ini"). local_config_1() -> - "test/etap/081-config-override.1.ini". + test_util:source_file("test/etap/081-config-override.1.ini"). local_config_2() -> - "test/etap/081-config-override.2.ini". + test_util:source_file("test/etap/081-config-override.2.ini"). local_config_write() -> - "test/etap/temp.081". + test_util:build_file("test/etap/temp.081"). % Run tests and wait for the config gen_server to shutdown. run_tests(IniFiles, Tests) -> @@ -39,8 +39,7 @@ run_tests(IniFiles, Tests) -> end. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(17), case (catch test()) of diff --git a/test/etap/082-config-register.t b/test/etap/082-config-register.t index d484db11..191ba8f8 100755 --- a/test/etap/082-config-register.t +++ b/test/etap/082-config-register.t @@ -14,11 +14,10 @@ % the License. default_config() -> - "etc/couchdb/default_dev.ini". + test_util:build_file("etc/couchdb/default_dev.ini"). main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(5), case (catch test()) of ok -> diff --git a/test/etap/083-config-no-files.t b/test/etap/083-config-no-files.t index 0d513cef..675feb59 100755 --- a/test/etap/083-config-no-files.t +++ b/test/etap/083-config-no-files.t @@ -14,11 +14,10 @@ % the License. default_config() -> - "etc/couchdb/default_dev.ini". + test_util:build_file("etc/couchdb/default_dev.ini"). main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(3), case (catch test()) of ok -> diff --git a/test/etap/090-task-status.t b/test/etap/090-task-status.t index 26cff33a..b6ebbe4c 100755 --- a/test/etap/090-task-status.t +++ b/test/etap/090-task-status.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(16), case (catch test()) of ok -> diff --git a/test/etap/100-ref-counter.t b/test/etap/100-ref-counter.t index c2c233e1..6f18d828 100755 --- a/test/etap/100-ref-counter.t +++ b/test/etap/100-ref-counter.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(8), case (catch test()) of ok -> diff --git a/test/etap/110-replication-httpc.t b/test/etap/110-replication-httpc.t index a84491f1..492732bc 100755 --- a/test/etap/110-replication-httpc.t +++ b/test/etap/110-replication-httpc.t @@ -38,12 +38,14 @@ server() -> "http://127.0.0.1:5984/". dbname() -> "etap-test-db". +config_files() -> + lists:map(fun test_util:build_file/1, [ + "etc/couchdb/default_dev.ini", + "etc/couchdb/local_dev.ini" + ]). + main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), - code:add_pathz("src/ibrowse"), - code:add_pathz("src/mochiweb"), - code:add_pathz("src/erlang-oauth"), + test_util:init_code_path(), etap:plan(6), case (catch test()) of @@ -56,9 +58,7 @@ main(_) -> ok. test() -> - couch_server_sup:start_link( - ["etc/couchdb/default_dev.ini", "etc/couchdb/local_dev.ini"] - ), + couch_server_sup:start_link(config_files()), ibrowse:start(), crypto:start(), diff --git a/test/etap/111-replication-changes-feed.t b/test/etap/111-replication-changes-feed.t index 10cea201..b03c1ac7 100755 --- a/test/etap/111-replication-changes-feed.t +++ b/test/etap/111-replication-changes-feed.t @@ -37,12 +37,15 @@ pause = 1, conn = nil }). + +config_files() -> + lists:map(fun test_util:build_file/1, [ + "etc/couchdb/default_dev.ini", + "etc/couchdb/local_dev.ini" + ]). + main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), - code:add_pathz("src/ibrowse"), - code:add_pathz("src/mochiweb"), - code:add_pathz("src/erlang-oauth"), + test_util:init_code_path(), etap:plan(13), case (catch test()) of @@ -55,9 +58,7 @@ main(_) -> ok. test() -> - couch_server_sup:start_link( - ["etc/couchdb/default_dev.ini", "etc/couchdb/local_dev.ini"] - ), + couch_server_sup:start_link(config_files()), ibrowse:start(), crypto:start(), diff --git a/test/etap/112-replication-missing-revs.t b/test/etap/112-replication-missing-revs.t index cfb11f35..8aabfd37 100755 --- a/test/etap/112-replication-missing-revs.t +++ b/test/etap/112-replication-missing-revs.t @@ -39,12 +39,14 @@ conn = nil }). +config_files() -> + lists:map(fun test_util:build_file/1, [ + "etc/couchdb/default_dev.ini", + "etc/couchdb/local_dev.ini" + ]). + main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), - code:add_pathz("src/ibrowse"), - code:add_pathz("src/mochiweb"), - code:add_pathz("src/erlang-oauth"), + test_util:init_code_path(), etap:plan(12), case (catch test()) of @@ -57,9 +59,7 @@ main(_) -> ok. test() -> - couch_server_sup:start_link( - ["etc/couchdb/default_dev.ini", "etc/couchdb/local_dev.ini"] - ), + couch_server_sup:start_link(config_files()), ibrowse:start(), crypto:start(), diff --git a/test/etap/120-stats-collect.t b/test/etap/120-stats-collect.t index a870bfef..dee88765 100755 --- a/test/etap/120-stats-collect.t +++ b/test/etap/120-stats-collect.t @@ -14,8 +14,7 @@ % the License. main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(11), case (catch test()) of ok -> diff --git a/test/etap/121-stats-aggregates.t b/test/etap/121-stats-aggregates.t index 9caa36ff..cd6b1430 100755 --- a/test/etap/121-stats-aggregates.t +++ b/test/etap/121-stats-aggregates.t @@ -13,9 +13,14 @@ % License for the specific language governing permissions and limitations under % the License. +ini_file() -> + test_util:source_file("test/etap/121-stats-aggregates.ini"). + +cfg_file() -> + test_util:source_file("test/etap/121-stats-aggregates.cfg"). + main(_) -> - code:add_patha("src/etap"), - code:add_pathz("src/couchdb"), + test_util:init_code_path(), etap:plan(17), case (catch test()) of ok -> @@ -27,9 +32,9 @@ main(_) -> ok. test() -> - couch_config:start_link(["test/etap/121-stats-aggregates.ini"]), + couch_config:start_link([ini_file()]), couch_stats_collector:start(), - couch_stats_aggregator:start("test/etap/121-stats-aggregates.cfg"), + couch_stats_aggregator:start(cfg_file()), ok = test_all_empty(), ok = test_get_empty(), ok = test_count_stats(), diff --git a/test/etap/Makefile.am b/test/etap/Makefile.am new file mode 100644 index 00000000..03685d0e --- /dev/null +++ b/test/etap/Makefile.am @@ -0,0 +1,64 @@ +## 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. + +check_SCRIPTS = run +noinst_DATA = test_util.beam + +%.beam: %.erl + erlc $< + +run: run.tpl + sed -e "s|%abs_top_srcdir%|@abs_top_srcdir@|g" \ + -e "s|%abs_top_builddir%|@abs_top_builddir@|g" > \ + $@ < $< + chmod +x $@ + +CLEANFILES = run *.beam +DISTCLEANFILES = temp.* + +EXTRA_DIST = \ + run.tpl \ + 001-load.t \ + 002-erl-driver.t \ + 010-file-basics.t \ + 011-file-headers.t \ + 020-btree-basics.t \ + 021-btree-reductions.t \ + 030-doc-from-json.t \ + 031-doc-to-json.t \ + 040-util.t \ + 041-uuid-gen-seq.ini \ + 041-uuid-gen-utc.ini \ + 041-uuid-gen.t \ + 050-stream.t \ + 060-kt-merging.t \ + 061-kt-missing-leaves.t \ + 062-kt-remove-leaves.t \ + 063-kt-get-leaves.t \ + 064-kt-counting.t \ + 065-kt-stemming.t \ + 070-couch-db.t \ + 080-config-get-set.t \ + 081-config-override.1.ini \ + 081-config-override.2.ini \ + 081-config-override.t \ + 082-config-register.t \ + 083-config-no-files.t \ + 090-task-status.t \ + 100-ref-counter.t \ + 110-replication-httpc.t \ + 111-replication-changes-feed.t \ + 112-replication-missing-revs.t \ + 120-stats-collect.t \ + 121-stats-aggregates.cfg \ + 121-stats-aggregates.ini \ + 121-stats-aggregates.t diff --git a/test/etap/run.tpl b/test/etap/run.tpl new file mode 100644 index 00000000..faf0f456 --- /dev/null +++ b/test/etap/run.tpl @@ -0,0 +1,27 @@ +#!/bin/sh -e + +# 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. + +SRCDIR="%abs_top_srcdir%" +BUILDIR="%abs_top_builddir%" + +export ERL_FLAGS="$ERL_FLAGS -pa $BUILDIR/test/etap/" + +if test $# -gt 0; then + while [ $# -gt 0 ]; do + $1 + shift + done +else + prove $SRCDIR/test/etap/*.t +fi diff --git a/test/etap/test_util.erl.in b/test/etap/test_util.erl.in new file mode 100644 index 00000000..4c42edb1 --- /dev/null +++ b/test/etap/test_util.erl.in @@ -0,0 +1,35 @@ +% 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. + +-module(test_util). + +-export([init_code_path/0]). +-export([source_file/1, build_file/1]). + +srcdir() -> + "@abs_top_srcdir@". + +builddir() -> + "@abs_top_builddir@". + +init_code_path() -> + Paths = ["etap", "couchdb", "erlang-oauth", "ibrowse", "mochiweb"], + lists:foreach(fun(Name) -> + code:add_pathz(filename:join([builddir(), "src", Name])) + end, Paths). + +source_file(Name) -> + filename:join([srcdir(), Name]). + +build_file(Name) -> + filename:join([builddir(), Name]). + diff --git a/test/javascript/Makefile.am b/test/javascript/Makefile.am new file mode 100644 index 00000000..faaca829 --- /dev/null +++ b/test/javascript/Makefile.am @@ -0,0 +1,15 @@ +## 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. + +EXTRA_DIST = \ + runner.sh \ + test.js diff --git a/test/javascript/runner.sh b/test/javascript/runner.sh new file mode 100755 index 00000000..1f48c390 --- /dev/null +++ b/test/javascript/runner.sh @@ -0,0 +1,19 @@ +#!/bin/sh -e + +# 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. + +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 - diff --git a/test/javascript/test.js b/test/javascript/test.js new file mode 100644 index 00000000..7f8a0787 --- /dev/null +++ b/test/javascript/test.js @@ -0,0 +1,249 @@ +// 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"); diff --git a/test/query_server_spec.rb b/test/query_server_spec.rb deleted file mode 100644 index 8fc2ab43..00000000 --- a/test/query_server_spec.rb +++ /dev/null @@ -1,695 +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. - -# to run (requires ruby and rspec): -# spec test/query_server_spec.rb -f specdoc --color - -COUCH_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(COUCH_ROOT) -LANGUAGE = ENV["QS_LANG"] || "js" - -puts "Running query server specs for #{LANGUAGE} query server" - -require 'spec' -require 'json' - -class OSProcessRunner - def self.run - trace = ENV["QS_TRACE"] || false - puts "launching #{run_command}" if trace - if block_given? - IO.popen(run_command, "r+") do |io| - qs = QueryServerRunner.new(io, trace) - yield qs - end - else - io = IO.popen(run_command, "r+") - QueryServerRunner.new(io, trace) - end - end - def initialize io, trace = false - @qsio = io - @trace = trace - end - def close - @qsio.close - end - def reset! - run(["reset"]) - end - def add_fun(fun) - run(["add_fun", fun]) - end - def get_chunks - resp = jsgets - raise "not a chunk" unless resp.first == "chunks" - return resp[1] - end - def run json - rrun json - jsgets - end - def rrun json - line = json.to_json - puts "run: #{line}" if @trace - @qsio.puts line - end - def rgets - resp = @qsio.gets - puts "got: #{resp}" if @trace - resp - end - def jsgets - resp = rgets - # err = @qserr.gets - # puts "err: #{err}" if err - if resp - begin - rj = JSON.parse("[#{resp.chomp}]")[0] - rescue JSON::ParserError - puts "JSON ERROR (dump under trace mode)" - # puts resp.chomp - while resp = rgets - # puts resp.chomp - end - end - if rj.respond_to?(:[]) && rj.is_a?(Array) - if rj[0] == "log" - log = rj[1] - puts "log: #{log}" if @trace - rj = jsgets - end - end - rj - else - raise "no response" - end - end -end - -class QueryServerRunner < OSProcessRunner - - COMMANDS = { - "js" => "#{COUCH_ROOT}/src/couchdb/couchjs #{COUCH_ROOT}/share/server/main.js", - "erlang" => "#{COUCH_ROOT}/test/run_native_process.es" - } - - def self.run_command - COMMANDS[LANGUAGE] - end -end - -class ExternalRunner < OSProcessRunner - def self.run_command - "#{COUCH_ROOT}/src/couchdb/couchjs #{COUCH_ROOT}/share/server/echo.js" - end -end - - -functions = { - "emit-twice" => { - "js" => %{function(doc){emit("foo",doc.a); emit("bar",doc.a)}}, - "erlang" => <<-ERLANG - fun({Doc}) -> - A = proplists:get_value(<<"a">>, Doc, null), - Emit(<<"foo">>, A), - Emit(<<"bar">>, A) - end. - ERLANG - }, - "emit-once" => { - "js" => %{function(doc){emit("baz",doc.a)}}, - "erlang" => <<-ERLANG - fun({Doc}) -> - A = proplists:get_value(<<"a">>, Doc, null), - Emit(<<"baz">>, A) - end. - ERLANG - }, - "reduce-values-length" => { - "js" => %{function(keys, values, rereduce) { return values.length; }}, - "erlang" => %{fun(Keys, Values, ReReduce) -> length(Values) end.} - }, - "reduce-values-sum" => { - "js" => %{function(keys, values, rereduce) { return sum(values); }}, - "erlang" => %{fun(Keys, Values, ReReduce) -> lists:sum(Values) end.} - }, - "validate-forbidden" => { - "js" => <<-JS, - function(newDoc, oldDoc, userCtx) { - if(newDoc.bad) - throw({forbidden:"bad doc"}); "foo bar"; - } - JS - "erlang" => <<-ERLANG - fun({NewDoc}, _OldDoc, _UserCtx) -> - case proplists:get_value(<<"bad">>, NewDoc) of - undefined -> 1; - _ -> {[{forbidden, <<"bad doc">>}]} - end - end. - ERLANG - }, - "show-simple" => { - "js" => <<-JS, - function(doc, req) { - log("ok"); - return [doc.title, doc.body].join(' - '); - } - JS - "erlang" => <<-ERLANG - fun({Doc}, Req) -> - Title = proplists:get_value(<<"title">>, Doc), - Body = proplists:get_value(<<"body">>, Doc), - Resp = <>, - {[{<<"body">>, Resp}]} - end. - ERLANG - }, - "show-headers" => { - "js" => <<-JS, - function(doc, req) { - var resp = {"code":200, "headers":{"X-Plankton":"Rusty"}}; - resp.body = [doc.title, doc.body].join(' - '); - return resp; - } - JS - "erlang" => <<-ERLANG - fun({Doc}, Req) -> - Title = proplists:get_value(<<"title">>, Doc), - Body = proplists:get_value(<<"body">>, Doc), - Resp = <<Title/binary, " - ", Body/binary>>, - {[ - {<<"code">>, 200}, - {<<"headers">>, {[{<<"X-Plankton">>, <<"Rusty">>}]}}, - {<<"body">>, Resp} - ]} - end. - ERLANG - }, - "show-sends" => { - "js" => <<-JS, - function(head, req) { - start({headers:{"Content-Type" : "text/plain"}}); - send("first chunk"); - send('second "chunk"'); - return "tail"; - }; - JS - "erlang" => <<-ERLANG - fun(Head, Req) -> - Resp = {[ - {<<"headers">>, {[{<<"Content-Type">>, <<"text/plain">>}]}} - ]}, - Start(Resp), - Send(<<"first chunk">>), - Send(<<"second \\\"chunk\\\"">>), - <<"tail">> - end. - ERLANG - }, - "show-while-get-rows" => { - "js" => <<-JS, - function(head, req) { - send("first chunk"); - send(req.q); - var row; - log("about to getRow " + typeof(getRow)); - while(row = getRow()) { - send(row.key); - }; - return "tail"; - }; - JS - "erlang" => <<-ERLANG, - fun(Head, {Req}) -> - Send(<<"first chunk">>), - Send(proplists:get_value(<<"q">>, Req)), - Fun = fun({Row}, _) -> - Send(proplists:get_value(<<"key">>, Row)), - {ok, nil} - end, - {ok, _} = FoldRows(Fun, nil), - <<"tail">> - end. - ERLANG - }, - "show-while-get-rows-multi-send" => { - "js" => <<-JS, - function(head, req) { - send("bacon"); - var row; - log("about to getRow " + typeof(getRow)); - while(row = getRow()) { - send(row.key); - send("eggs"); - }; - return "tail"; - }; - JS - "erlang" => <<-ERLANG, - fun(Head, Req) -> - Send(<<"bacon">>), - Fun = fun({Row}, _) -> - Send(proplists:get_value(<<"key">>, Row)), - Send(<<"eggs">>), - {ok, nil} - end, - FoldRows(Fun, nil), - <<"tail">> - end. - ERLANG - }, - "list-simple" => { - "js" => <<-JS, - function(head, req) { - send("first chunk"); - send(req.q); - var row; - while(row = getRow()) { - send(row.key); - }; - return "early"; - }; - JS - "erlang" => <<-ERLANG, - fun(Head, {Req}) -> - Send(<<"first chunk">>), - Send(proplists:get_value(<<"q">>, Req)), - Fun = fun({Row}, _) -> - Send(proplists:get_value(<<"key">>, Row)), - {ok, nil} - end, - FoldRows(Fun, nil), - <<"early">> - end. - ERLANG - }, - "list-chunky" => { - "js" => <<-JS, - function(head, req) { - send("first chunk"); - send(req.q); - var row, i=0; - while(row = getRow()) { - send(row.key); - i += 1; - if (i > 2) { - return('early tail'); - } - }; - }; - JS - "erlang" => <<-ERLANG, - fun(Head, {Req}) -> - Send(<<"first chunk">>), - Send(proplists:get_value(<<"q">>, Req)), - Fun = fun - ({Row}, Count) when Count < 2 -> - Send(proplists:get_value(<<"key">>, Row)), - {ok, Count+1}; - ({Row}, Count) when Count == 2 -> - Send(proplists:get_value(<<"key">>, Row)), - {stop, <<"early tail">>} - end, - {ok, Tail} = FoldRows(Fun, 0), - Tail - end. - ERLANG - }, - "list-old-style" => { - "js" => <<-JS, - function(head, req, foo, bar) { - return "stuff"; - } - JS - "erlang" => <<-ERLANG, - fun(Head, Req, Foo, Bar) -> - <<"stuff">> - end. - ERLANG - }, - "list-capped" => { - "js" => <<-JS, - function(head, req) { - send("bacon") - var row, i = 0; - while(row = getRow()) { - send(row.key); - i += 1; - if (i > 2) { - return('early'); - } - }; - } - JS - "erlang" => <<-ERLANG, - fun(Head, Req) -> - Send(<<"bacon">>), - Fun = fun - ({Row}, Count) when Count < 2 -> - Send(proplists:get_value(<<"key">>, Row)), - {ok, Count+1}; - ({Row}, Count) when Count == 2 -> - Send(proplists:get_value(<<"key">>, Row)), - {stop, <<"early">>} - end, - {ok, Tail} = FoldRows(Fun, 0), - Tail - end. - ERLANG - }, - "list-raw" => { - "js" => <<-JS, - function(head, req) { - send("first chunk"); - send(req.q); - var row; - while(row = getRow()) { - send(row.key); - }; - return "tail"; - }; - JS - "erlang" => <<-ERLANG, - fun(Head, {Req}) -> - Send(<<"first chunk">>), - Send(proplists:get_value(<<"q">>, Req)), - Fun = fun({Row}, _) -> - Send(proplists:get_value(<<"key">>, Row)), - {ok, nil} - end, - FoldRows(Fun, nil), - <<"tail">> - end. - ERLANG - }, - "filter-basic" => { - "js" => <<-JS, - function(doc, req) { - if (doc.good) { - return true; - } - } - JS - "erlang" => <<-ERLANG, - fun({Doc}, Req) -> - proplists:get_value(<<"good">>, Doc) - end. - ERLANG - }, - "update-basic" => { - "js" => <<-JS, - function(doc, req) { - doc.world = "hello"; - var resp = [doc, "hello doc"]; - return resp; - } - JS - "erlang" => <<-ERLANG, - fun({Doc}, Req) -> - Doc2 = [{<<"world">>, <<"hello">>}|Doc], - [{Doc2}, {[{<<"body">>, <<"hello doc">>}]}] - end. - ERLANG - } -} - -describe "query server normal case" do - before(:all) do - `cd #{COUCH_ROOT} && make` - @qs = QueryServerRunner.run - end - after(:all) do - @qs.close - end - it "should reset" do - @qs.run(["reset"]).should == true - end - it "should run map funs" do - @qs.reset! - @qs.run(["add_fun", functions["emit-twice"][LANGUAGE]]).should == true - @qs.run(["add_fun", functions["emit-once"][LANGUAGE]]).should == true - rows = @qs.run(["map_doc", {:a => "b"}]) - rows[0][0].should == ["foo", "b"] - rows[0][1].should == ["bar", "b"] - rows[1][0].should == ["baz", "b"] - end - describe "reduce" do - before(:all) do - @fun = functions["reduce-values-length"][LANGUAGE] - @qs.reset! - end - it "should reduce" do - kvs = (0...10).collect{|i|[i,i*2]} - @qs.run(["reduce", [@fun], kvs]).should == [true, [10]] - end - end - describe "rereduce" do - before(:all) do - @fun = functions["reduce-values-sum"][LANGUAGE] - @qs.reset! - end - it "should rereduce" do - vs = (0...10).collect{|i|i} - @qs.run(["rereduce", [@fun], vs]).should == [true, [45]] - end - end - - # it "should validate" - describe "validation" do - before(:all) do - @fun = functions["validate-forbidden"][LANGUAGE] - @qs.reset! - end - it "should allow good updates" do - @qs.run(["validate", @fun, {"good" => true}, {}, {}]).should == 1 - end - it "should reject invalid updates" do - @qs.run(["validate", @fun, {"bad" => true}, {}, {}]).should == {"forbidden"=>"bad doc"} - end - end - - describe "show" do - before(:all) do - @fun = functions["show-simple"][LANGUAGE] - @qs.reset! - end - it "should show" do - @qs.rrun(["show", @fun, - {:title => "Best ever", :body => "Doc body"}, {}]) - @qs.jsgets.should == ["resp", {"body" => "Best ever - Doc body"}] - end - end - - describe "show with headers" do - before(:all) do - @fun = functions["show-headers"][LANGUAGE] - @qs.reset! - end - it "should show headers" do - @qs.rrun(["show", @fun, - {:title => "Best ever", :body => "Doc body"}, {}]) - @qs.jsgets.should == ["resp", {"code"=>200,"headers" => {"X-Plankton"=>"Rusty"}, "body" => "Best ever - Doc body"}] - end - end - -# end -# LIST TESTS -# __END__ - - describe "raw list with headers" do - before(:each) do - @fun = functions["show-sends"][LANGUAGE] - @qs.reset! - @qs.add_fun(@fun).should == true - end - it "should do headers proper" do - @qs.rrun(["list", {"total_rows"=>1000}, {"q" => "ok"}]) - @qs.jsgets.should == ["start", ["first chunk", 'second "chunk"'], {"headers"=>{"Content-Type"=>"text/plain"}}] - @qs.rrun(["list_end"]) - @qs.jsgets.should == ["end", ["tail"]] - end - end - - describe "list with rows" do - before(:each) do - @fun = functions["show-while-get-rows"][LANGUAGE] - @qs.run(["reset"]).should == true - @qs.add_fun(@fun).should == true - end - it "should list em" do - @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}]) - @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}] - @qs.rrun(["list_row", {"key"=>"baz"}]) - @qs.get_chunks.should == ["baz"] - @qs.rrun(["list_row", {"key"=>"bam"}]) - @qs.get_chunks.should == ["bam"] - @qs.rrun(["list_end"]) - @qs.jsgets.should == ["end", ["tail"]] - end - it "should work with zero rows" do - @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}]) - @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}] - @qs.rrun(["list_end"]) - @qs.jsgets.should == ["end", ["tail"]] - end - end - - describe "should buffer multiple chunks sent for a single row." do - before(:all) do - @fun = functions["show-while-get-rows-multi-send"][LANGUAGE] - @qs.reset! - @qs.add_fun(@fun).should == true - end - it "should should buffer em" do - @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}]) - @qs.jsgets.should == ["start", ["bacon"], {"headers"=>{}}] - @qs.rrun(["list_row", {"key"=>"baz"}]) - @qs.get_chunks.should == ["baz", "eggs"] - @qs.rrun(["list_row", {"key"=>"bam"}]) - @qs.get_chunks.should == ["bam", "eggs"] - @qs.rrun(["list_end"]) - @qs.jsgets.should == ["end", ["tail"]] - end - end - - describe "example list" do - before(:all) do - @fun = functions["list-simple"][LANGUAGE] - @qs.reset! - @qs.add_fun(@fun).should == true - end - it "should run normal" do - @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]).should == ["start", ["first chunk", "ok"], {"headers"=>{}}] - @qs.run(["list_row", {"key"=>"baz"}]).should == ["chunks", ["baz"]] - @qs.run(["list_row", {"key"=>"bam"}]).should == ["chunks", ["bam"]] - @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]] - @qs.run(["list_row", {"key"=>"fooz"}]).should == ["chunks", ["fooz"]] - @qs.run(["list_row", {"key"=>"foox"}]).should == ["chunks", ["foox"]] - @qs.run(["list_end"]).should == ["end" , ["early"]] - end - end - - describe "only goes to 2 list" do - before(:all) do - @fun = functions["list-chunky"][LANGUAGE] - @qs.reset! - @qs.add_fun(@fun).should == true - end - it "should end early" do - @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]). - should == ["start", ["first chunk", "ok"], {"headers"=>{}}] - @qs.run(["list_row", {"key"=>"baz"}]). - should == ["chunks", ["baz"]] - - @qs.run(["list_row", {"key"=>"bam"}]). - should == ["chunks", ["bam"]] - - @qs.run(["list_row", {"key"=>"foom"}]). - should == ["end", ["foom", "early tail"]] - # here's where js has to discard quit properly - @qs.run(["reset"]). - should == true - end - end - - describe "changes filter" do - before(:all) do - @fun = functions["filter-basic"][LANGUAGE] - @qs.reset! - @qs.add_fun(@fun).should == true - end - it "should only return true for good docs" do - @qs.run(["filter", [{"key"=>"bam", "good" => true}, {"foo" => "bar"}, {"good" => true}], {"req" => "foo"}]). - should == [true, [true, false, true]] - end - end - - describe "update" do - before(:all) do - @fun = functions["update-basic"][LANGUAGE] - @qs.reset! - end - it "should return a doc and a resp body" do - up, doc, resp = @qs.run(["update", @fun, {"foo" => "gnarly"}, {"verb" => "POST"}]) - up.should == "up" - doc.should == {"foo" => "gnarly", "world" => "hello"} - resp["body"].should == "hello doc" - end - end -end - -def should_have_exited qs - begin - qs.run(["reset"]) - "raise before this".should == true - rescue RuntimeError => e - e.message.should == "no response" - rescue Errno::EPIPE - true.should == true - end -end - -describe "query server that exits" do - before(:each) do - @qs = QueryServerRunner.run - end - after(:each) do - @qs.close - end - - if LANGUAGE == "js" - describe "old style list" do - before(:each) do - @fun = functions["list-old-style"][LANGUAGE] - @qs.reset! - @qs.add_fun(@fun).should == true - end - it "should get a warning" do - resp = @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]) - resp["error"].should == "render_error" - resp["reason"].should include("the list API has changed") - end - end - end - - describe "only goes to 2 list" do - before(:each) do - @fun = functions["list-capped"][LANGUAGE] - @qs.reset! - @qs.add_fun(@fun).should == true - end - it "should exit if erlang sends too many rows" do - @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]).should == ["start", ["bacon"], {"headers"=>{}}] - @qs.run(["list_row", {"key"=>"baz"}]).should == ["chunks", ["baz"]] - @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]] - @qs.run(["list_row", {"key"=>"fooz"}]).should == ["end", ["fooz", "early"]] - @qs.rrun(["list_row", {"key"=>"foox"}]) - @qs.jsgets["error"].should == "query_server_error" - should_have_exited @qs - end - end - - describe "raw list" do - before(:each) do - @fun = functions["list-raw"][LANGUAGE] - @qs.run(["reset"]).should == true - @qs.add_fun(@fun).should == true - end - it "should exit if it gets a non-row in the middle" do - @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}]) - @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}] - @qs.run(["reset"])["error"].should == "query_server_error" - should_have_exited @qs - end - end -end diff --git a/test/run_native_process.es b/test/run_native_process.es deleted file mode 100755 index dfdc423e..00000000 --- a/test/run_native_process.es +++ /dev/null @@ -1,55 +0,0 @@ -#! /usr/bin/env escript - -% 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. - -read() -> - case io:get_line('') of - eof -> stop; - Data -> mochijson2:decode(Data) - end. - -send(Data) when is_binary(Data) -> - send(binary_to_list(Data)); -send(Data) when is_list(Data) -> - io:format(Data ++ "\n", []). - -write(Data) -> - case (catch mochijson2:encode(Data)) of - {json_encode, Error} -> write({[{<<"error">>, Error}]}); - Json -> send(Json) - end. - -%log(Mesg) -> -% log(Mesg, []). -%log(Mesg, Params) -> -% io:format(standard_error, Mesg, Params). - -loop(Pid) -> - case read() of - stop -> ok; - Json -> - case (catch couch_native_process:prompt(Pid, Json)) of - {error, Reason} -> - ok = write({[{error, Reason}]}); - Resp -> - ok = write(Resp), - loop(Pid) - end - end. - -main([]) -> - code:add_pathz("src/couchdb"), - code:add_pathz("src/mochiweb"), - {ok, Pid} = couch_native_process:start_link(), - loop(Pid). - diff --git a/test/runner.sh b/test/runner.sh deleted file mode 100755 index 1f48c390..00000000 --- a/test/runner.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -e - -# 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. - -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 - diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 7f8a0787..00000000 --- a/test/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"); diff --git a/test/view_server/Makefile.am b/test/view_server/Makefile.am new file mode 100644 index 00000000..11e7feb4 --- /dev/null +++ b/test/view_server/Makefile.am @@ -0,0 +1,15 @@ +## 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. + +EXTRA_DIST = \ + query_server_spec.rb \ + run_native_process.es diff --git a/test/view_server/query_server_spec.rb b/test/view_server/query_server_spec.rb new file mode 100644 index 00000000..8fc2ab43 --- /dev/null +++ b/test/view_server/query_server_spec.rb @@ -0,0 +1,695 @@ +# 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. + +# to run (requires ruby and rspec): +# spec test/query_server_spec.rb -f specdoc --color + +COUCH_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(COUCH_ROOT) +LANGUAGE = ENV["QS_LANG"] || "js" + +puts "Running query server specs for #{LANGUAGE} query server" + +require 'spec' +require 'json' + +class OSProcessRunner + def self.run + trace = ENV["QS_TRACE"] || false + puts "launching #{run_command}" if trace + if block_given? + IO.popen(run_command, "r+") do |io| + qs = QueryServerRunner.new(io, trace) + yield qs + end + else + io = IO.popen(run_command, "r+") + QueryServerRunner.new(io, trace) + end + end + def initialize io, trace = false + @qsio = io + @trace = trace + end + def close + @qsio.close + end + def reset! + run(["reset"]) + end + def add_fun(fun) + run(["add_fun", fun]) + end + def get_chunks + resp = jsgets + raise "not a chunk" unless resp.first == "chunks" + return resp[1] + end + def run json + rrun json + jsgets + end + def rrun json + line = json.to_json + puts "run: #{line}" if @trace + @qsio.puts line + end + def rgets + resp = @qsio.gets + puts "got: #{resp}" if @trace + resp + end + def jsgets + resp = rgets + # err = @qserr.gets + # puts "err: #{err}" if err + if resp + begin + rj = JSON.parse("[#{resp.chomp}]")[0] + rescue JSON::ParserError + puts "JSON ERROR (dump under trace mode)" + # puts resp.chomp + while resp = rgets + # puts resp.chomp + end + end + if rj.respond_to?(:[]) && rj.is_a?(Array) + if rj[0] == "log" + log = rj[1] + puts "log: #{log}" if @trace + rj = jsgets + end + end + rj + else + raise "no response" + end + end +end + +class QueryServerRunner < OSProcessRunner + + COMMANDS = { + "js" => "#{COUCH_ROOT}/src/couchdb/couchjs #{COUCH_ROOT}/share/server/main.js", + "erlang" => "#{COUCH_ROOT}/test/run_native_process.es" + } + + def self.run_command + COMMANDS[LANGUAGE] + end +end + +class ExternalRunner < OSProcessRunner + def self.run_command + "#{COUCH_ROOT}/src/couchdb/couchjs #{COUCH_ROOT}/share/server/echo.js" + end +end + + +functions = { + "emit-twice" => { + "js" => %{function(doc){emit("foo",doc.a); emit("bar",doc.a)}}, + "erlang" => <<-ERLANG + fun({Doc}) -> + A = proplists:get_value(<<"a">>, Doc, null), + Emit(<<"foo">>, A), + Emit(<<"bar">>, A) + end. + ERLANG + }, + "emit-once" => { + "js" => %{function(doc){emit("baz",doc.a)}}, + "erlang" => <<-ERLANG + fun({Doc}) -> + A = proplists:get_value(<<"a">>, Doc, null), + Emit(<<"baz">>, A) + end. + ERLANG + }, + "reduce-values-length" => { + "js" => %{function(keys, values, rereduce) { return values.length; }}, + "erlang" => %{fun(Keys, Values, ReReduce) -> length(Values) end.} + }, + "reduce-values-sum" => { + "js" => %{function(keys, values, rereduce) { return sum(values); }}, + "erlang" => %{fun(Keys, Values, ReReduce) -> lists:sum(Values) end.} + }, + "validate-forbidden" => { + "js" => <<-JS, + function(newDoc, oldDoc, userCtx) { + if(newDoc.bad) + throw({forbidden:"bad doc"}); "foo bar"; + } + JS + "erlang" => <<-ERLANG + fun({NewDoc}, _OldDoc, _UserCtx) -> + case proplists:get_value(<<"bad">>, NewDoc) of + undefined -> 1; + _ -> {[{forbidden, <<"bad doc">>}]} + end + end. + ERLANG + }, + "show-simple" => { + "js" => <<-JS, + function(doc, req) { + log("ok"); + return [doc.title, doc.body].join(' - '); + } + JS + "erlang" => <<-ERLANG + fun({Doc}, Req) -> + Title = proplists:get_value(<<"title">>, Doc), + Body = proplists:get_value(<<"body">>, Doc), + Resp = <<Title/binary, " - ", Body/binary>>, + {[{<<"body">>, Resp}]} + end. + ERLANG + }, + "show-headers" => { + "js" => <<-JS, + function(doc, req) { + var resp = {"code":200, "headers":{"X-Plankton":"Rusty"}}; + resp.body = [doc.title, doc.body].join(' - '); + return resp; + } + JS + "erlang" => <<-ERLANG + fun({Doc}, Req) -> + Title = proplists:get_value(<<"title">>, Doc), + Body = proplists:get_value(<<"body">>, Doc), + Resp = <<Title/binary, " - ", Body/binary>>, + {[ + {<<"code">>, 200}, + {<<"headers">>, {[{<<"X-Plankton">>, <<"Rusty">>}]}}, + {<<"body">>, Resp} + ]} + end. + ERLANG + }, + "show-sends" => { + "js" => <<-JS, + function(head, req) { + start({headers:{"Content-Type" : "text/plain"}}); + send("first chunk"); + send('second "chunk"'); + return "tail"; + }; + JS + "erlang" => <<-ERLANG + fun(Head, Req) -> + Resp = {[ + {<<"headers">>, {[{<<"Content-Type">>, <<"text/plain">>}]}} + ]}, + Start(Resp), + Send(<<"first chunk">>), + Send(<<"second \\\"chunk\\\"">>), + <<"tail">> + end. + ERLANG + }, + "show-while-get-rows" => { + "js" => <<-JS, + function(head, req) { + send("first chunk"); + send(req.q); + var row; + log("about to getRow " + typeof(getRow)); + while(row = getRow()) { + send(row.key); + }; + return "tail"; + }; + JS + "erlang" => <<-ERLANG, + fun(Head, {Req}) -> + Send(<<"first chunk">>), + Send(proplists:get_value(<<"q">>, Req)), + Fun = fun({Row}, _) -> + Send(proplists:get_value(<<"key">>, Row)), + {ok, nil} + end, + {ok, _} = FoldRows(Fun, nil), + <<"tail">> + end. + ERLANG + }, + "show-while-get-rows-multi-send" => { + "js" => <<-JS, + function(head, req) { + send("bacon"); + var row; + log("about to getRow " + typeof(getRow)); + while(row = getRow()) { + send(row.key); + send("eggs"); + }; + return "tail"; + }; + JS + "erlang" => <<-ERLANG, + fun(Head, Req) -> + Send(<<"bacon">>), + Fun = fun({Row}, _) -> + Send(proplists:get_value(<<"key">>, Row)), + Send(<<"eggs">>), + {ok, nil} + end, + FoldRows(Fun, nil), + <<"tail">> + end. + ERLANG + }, + "list-simple" => { + "js" => <<-JS, + function(head, req) { + send("first chunk"); + send(req.q); + var row; + while(row = getRow()) { + send(row.key); + }; + return "early"; + }; + JS + "erlang" => <<-ERLANG, + fun(Head, {Req}) -> + Send(<<"first chunk">>), + Send(proplists:get_value(<<"q">>, Req)), + Fun = fun({Row}, _) -> + Send(proplists:get_value(<<"key">>, Row)), + {ok, nil} + end, + FoldRows(Fun, nil), + <<"early">> + end. + ERLANG + }, + "list-chunky" => { + "js" => <<-JS, + function(head, req) { + send("first chunk"); + send(req.q); + var row, i=0; + while(row = getRow()) { + send(row.key); + i += 1; + if (i > 2) { + return('early tail'); + } + }; + }; + JS + "erlang" => <<-ERLANG, + fun(Head, {Req}) -> + Send(<<"first chunk">>), + Send(proplists:get_value(<<"q">>, Req)), + Fun = fun + ({Row}, Count) when Count < 2 -> + Send(proplists:get_value(<<"key">>, Row)), + {ok, Count+1}; + ({Row}, Count) when Count == 2 -> + Send(proplists:get_value(<<"key">>, Row)), + {stop, <<"early tail">>} + end, + {ok, Tail} = FoldRows(Fun, 0), + Tail + end. + ERLANG + }, + "list-old-style" => { + "js" => <<-JS, + function(head, req, foo, bar) { + return "stuff"; + } + JS + "erlang" => <<-ERLANG, + fun(Head, Req, Foo, Bar) -> + <<"stuff">> + end. + ERLANG + }, + "list-capped" => { + "js" => <<-JS, + function(head, req) { + send("bacon") + var row, i = 0; + while(row = getRow()) { + send(row.key); + i += 1; + if (i > 2) { + return('early'); + } + }; + } + JS + "erlang" => <<-ERLANG, + fun(Head, Req) -> + Send(<<"bacon">>), + Fun = fun + ({Row}, Count) when Count < 2 -> + Send(proplists:get_value(<<"key">>, Row)), + {ok, Count+1}; + ({Row}, Count) when Count == 2 -> + Send(proplists:get_value(<<"key">>, Row)), + {stop, <<"early">>} + end, + {ok, Tail} = FoldRows(Fun, 0), + Tail + end. + ERLANG + }, + "list-raw" => { + "js" => <<-JS, + function(head, req) { + send("first chunk"); + send(req.q); + var row; + while(row = getRow()) { + send(row.key); + }; + return "tail"; + }; + JS + "erlang" => <<-ERLANG, + fun(Head, {Req}) -> + Send(<<"first chunk">>), + Send(proplists:get_value(<<"q">>, Req)), + Fun = fun({Row}, _) -> + Send(proplists:get_value(<<"key">>, Row)), + {ok, nil} + end, + FoldRows(Fun, nil), + <<"tail">> + end. + ERLANG + }, + "filter-basic" => { + "js" => <<-JS, + function(doc, req) { + if (doc.good) { + return true; + } + } + JS + "erlang" => <<-ERLANG, + fun({Doc}, Req) -> + proplists:get_value(<<"good">>, Doc) + end. + ERLANG + }, + "update-basic" => { + "js" => <<-JS, + function(doc, req) { + doc.world = "hello"; + var resp = [doc, "hello doc"]; + return resp; + } + JS + "erlang" => <<-ERLANG, + fun({Doc}, Req) -> + Doc2 = [{<<"world">>, <<"hello">>}|Doc], + [{Doc2}, {[{<<"body">>, <<"hello doc">>}]}] + end. + ERLANG + } +} + +describe "query server normal case" do + before(:all) do + `cd #{COUCH_ROOT} && make` + @qs = QueryServerRunner.run + end + after(:all) do + @qs.close + end + it "should reset" do + @qs.run(["reset"]).should == true + end + it "should run map funs" do + @qs.reset! + @qs.run(["add_fun", functions["emit-twice"][LANGUAGE]]).should == true + @qs.run(["add_fun", functions["emit-once"][LANGUAGE]]).should == true + rows = @qs.run(["map_doc", {:a => "b"}]) + rows[0][0].should == ["foo", "b"] + rows[0][1].should == ["bar", "b"] + rows[1][0].should == ["baz", "b"] + end + describe "reduce" do + before(:all) do + @fun = functions["reduce-values-length"][LANGUAGE] + @qs.reset! + end + it "should reduce" do + kvs = (0...10).collect{|i|[i,i*2]} + @qs.run(["reduce", [@fun], kvs]).should == [true, [10]] + end + end + describe "rereduce" do + before(:all) do + @fun = functions["reduce-values-sum"][LANGUAGE] + @qs.reset! + end + it "should rereduce" do + vs = (0...10).collect{|i|i} + @qs.run(["rereduce", [@fun], vs]).should == [true, [45]] + end + end + + # it "should validate" + describe "validation" do + before(:all) do + @fun = functions["validate-forbidden"][LANGUAGE] + @qs.reset! + end + it "should allow good updates" do + @qs.run(["validate", @fun, {"good" => true}, {}, {}]).should == 1 + end + it "should reject invalid updates" do + @qs.run(["validate", @fun, {"bad" => true}, {}, {}]).should == {"forbidden"=>"bad doc"} + end + end + + describe "show" do + before(:all) do + @fun = functions["show-simple"][LANGUAGE] + @qs.reset! + end + it "should show" do + @qs.rrun(["show", @fun, + {:title => "Best ever", :body => "Doc body"}, {}]) + @qs.jsgets.should == ["resp", {"body" => "Best ever - Doc body"}] + end + end + + describe "show with headers" do + before(:all) do + @fun = functions["show-headers"][LANGUAGE] + @qs.reset! + end + it "should show headers" do + @qs.rrun(["show", @fun, + {:title => "Best ever", :body => "Doc body"}, {}]) + @qs.jsgets.should == ["resp", {"code"=>200,"headers" => {"X-Plankton"=>"Rusty"}, "body" => "Best ever - Doc body"}] + end + end + +# end +# LIST TESTS +# __END__ + + describe "raw list with headers" do + before(:each) do + @fun = functions["show-sends"][LANGUAGE] + @qs.reset! + @qs.add_fun(@fun).should == true + end + it "should do headers proper" do + @qs.rrun(["list", {"total_rows"=>1000}, {"q" => "ok"}]) + @qs.jsgets.should == ["start", ["first chunk", 'second "chunk"'], {"headers"=>{"Content-Type"=>"text/plain"}}] + @qs.rrun(["list_end"]) + @qs.jsgets.should == ["end", ["tail"]] + end + end + + describe "list with rows" do + before(:each) do + @fun = functions["show-while-get-rows"][LANGUAGE] + @qs.run(["reset"]).should == true + @qs.add_fun(@fun).should == true + end + it "should list em" do + @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}]) + @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}] + @qs.rrun(["list_row", {"key"=>"baz"}]) + @qs.get_chunks.should == ["baz"] + @qs.rrun(["list_row", {"key"=>"bam"}]) + @qs.get_chunks.should == ["bam"] + @qs.rrun(["list_end"]) + @qs.jsgets.should == ["end", ["tail"]] + end + it "should work with zero rows" do + @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}]) + @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}] + @qs.rrun(["list_end"]) + @qs.jsgets.should == ["end", ["tail"]] + end + end + + describe "should buffer multiple chunks sent for a single row." do + before(:all) do + @fun = functions["show-while-get-rows-multi-send"][LANGUAGE] + @qs.reset! + @qs.add_fun(@fun).should == true + end + it "should should buffer em" do + @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}]) + @qs.jsgets.should == ["start", ["bacon"], {"headers"=>{}}] + @qs.rrun(["list_row", {"key"=>"baz"}]) + @qs.get_chunks.should == ["baz", "eggs"] + @qs.rrun(["list_row", {"key"=>"bam"}]) + @qs.get_chunks.should == ["bam", "eggs"] + @qs.rrun(["list_end"]) + @qs.jsgets.should == ["end", ["tail"]] + end + end + + describe "example list" do + before(:all) do + @fun = functions["list-simple"][LANGUAGE] + @qs.reset! + @qs.add_fun(@fun).should == true + end + it "should run normal" do + @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]).should == ["start", ["first chunk", "ok"], {"headers"=>{}}] + @qs.run(["list_row", {"key"=>"baz"}]).should == ["chunks", ["baz"]] + @qs.run(["list_row", {"key"=>"bam"}]).should == ["chunks", ["bam"]] + @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]] + @qs.run(["list_row", {"key"=>"fooz"}]).should == ["chunks", ["fooz"]] + @qs.run(["list_row", {"key"=>"foox"}]).should == ["chunks", ["foox"]] + @qs.run(["list_end"]).should == ["end" , ["early"]] + end + end + + describe "only goes to 2 list" do + before(:all) do + @fun = functions["list-chunky"][LANGUAGE] + @qs.reset! + @qs.add_fun(@fun).should == true + end + it "should end early" do + @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]). + should == ["start", ["first chunk", "ok"], {"headers"=>{}}] + @qs.run(["list_row", {"key"=>"baz"}]). + should == ["chunks", ["baz"]] + + @qs.run(["list_row", {"key"=>"bam"}]). + should == ["chunks", ["bam"]] + + @qs.run(["list_row", {"key"=>"foom"}]). + should == ["end", ["foom", "early tail"]] + # here's where js has to discard quit properly + @qs.run(["reset"]). + should == true + end + end + + describe "changes filter" do + before(:all) do + @fun = functions["filter-basic"][LANGUAGE] + @qs.reset! + @qs.add_fun(@fun).should == true + end + it "should only return true for good docs" do + @qs.run(["filter", [{"key"=>"bam", "good" => true}, {"foo" => "bar"}, {"good" => true}], {"req" => "foo"}]). + should == [true, [true, false, true]] + end + end + + describe "update" do + before(:all) do + @fun = functions["update-basic"][LANGUAGE] + @qs.reset! + end + it "should return a doc and a resp body" do + up, doc, resp = @qs.run(["update", @fun, {"foo" => "gnarly"}, {"verb" => "POST"}]) + up.should == "up" + doc.should == {"foo" => "gnarly", "world" => "hello"} + resp["body"].should == "hello doc" + end + end +end + +def should_have_exited qs + begin + qs.run(["reset"]) + "raise before this".should == true + rescue RuntimeError => e + e.message.should == "no response" + rescue Errno::EPIPE + true.should == true + end +end + +describe "query server that exits" do + before(:each) do + @qs = QueryServerRunner.run + end + after(:each) do + @qs.close + end + + if LANGUAGE == "js" + describe "old style list" do + before(:each) do + @fun = functions["list-old-style"][LANGUAGE] + @qs.reset! + @qs.add_fun(@fun).should == true + end + it "should get a warning" do + resp = @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]) + resp["error"].should == "render_error" + resp["reason"].should include("the list API has changed") + end + end + end + + describe "only goes to 2 list" do + before(:each) do + @fun = functions["list-capped"][LANGUAGE] + @qs.reset! + @qs.add_fun(@fun).should == true + end + it "should exit if erlang sends too many rows" do + @qs.run(["list", {"foo"=>"bar"}, {"q" => "ok"}]).should == ["start", ["bacon"], {"headers"=>{}}] + @qs.run(["list_row", {"key"=>"baz"}]).should == ["chunks", ["baz"]] + @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]] + @qs.run(["list_row", {"key"=>"fooz"}]).should == ["end", ["fooz", "early"]] + @qs.rrun(["list_row", {"key"=>"foox"}]) + @qs.jsgets["error"].should == "query_server_error" + should_have_exited @qs + end + end + + describe "raw list" do + before(:each) do + @fun = functions["list-raw"][LANGUAGE] + @qs.run(["reset"]).should == true + @qs.add_fun(@fun).should == true + end + it "should exit if it gets a non-row in the middle" do + @qs.rrun(["list", {"foo"=>"bar"}, {"q" => "ok"}]) + @qs.jsgets.should == ["start", ["first chunk", "ok"], {"headers"=>{}}] + @qs.run(["reset"])["error"].should == "query_server_error" + should_have_exited @qs + end + end +end diff --git a/test/view_server/run_native_process.es b/test/view_server/run_native_process.es new file mode 100755 index 00000000..dfdc423e --- /dev/null +++ b/test/view_server/run_native_process.es @@ -0,0 +1,55 @@ +#! /usr/bin/env escript + +% 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. + +read() -> + case io:get_line('') of + eof -> stop; + Data -> mochijson2:decode(Data) + end. + +send(Data) when is_binary(Data) -> + send(binary_to_list(Data)); +send(Data) when is_list(Data) -> + io:format(Data ++ "\n", []). + +write(Data) -> + case (catch mochijson2:encode(Data)) of + {json_encode, Error} -> write({[{<<"error">>, Error}]}); + Json -> send(Json) + end. + +%log(Mesg) -> +% log(Mesg, []). +%log(Mesg, Params) -> +% io:format(standard_error, Mesg, Params). + +loop(Pid) -> + case read() of + stop -> ok; + Json -> + case (catch couch_native_process:prompt(Pid, Json)) of + {error, Reason} -> + ok = write({[{error, Reason}]}); + Resp -> + ok = write(Resp), + loop(Pid) + end + end. + +main([]) -> + code:add_pathz("src/couchdb"), + code:add_pathz("src/mochiweb"), + {ok, Pid} = couch_native_process:start_link(), + loop(Pid). + diff --git a/utils/Makefile.am b/utils/Makefile.am index 65fd1a81..379c1eb0 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -20,17 +20,18 @@ couchdb_command_name = `echo couchdb | sed '$(transform)'` run: ../bin/couchdb.tpl sed -e "s|%ERL%|$(ERL)|g" \ -e "s|%ICU_CONFIG%|$(ICU_CONFIG)|g" \ - -e "s|%bindir%|$(abs_top_srcdir)/bin|g" \ + -e "s|%bindir%|$(abs_top_builddir)/bin|g" \ -e "s|%defaultini%|default_dev.ini|g" \ -e "s|%localini%|local_dev.ini|g" \ - -e "s|%localerlanglibdir%|foo -pa $(abs_top_srcdir)\/src\/couchdb \ - -pa $(abs_top_srcdir)\/src\/erlang-oauth \ - -pa $(abs_top_srcdir)\/src\/ibrowse \ - -pa $(abs_top_srcdir)\/src\/mochiweb|g" \ - -e "s|%localconfdir%|$(abs_top_srcdir)/etc/couchdb|g" \ - -e "s|%localstatelogdir%|$(abs_top_srcdir)/tmp/log|g" \ - -e "s|%localstatelibdir%|$(abs_top_srcdir)/tmp/lib|g" \ - -e "s|%localstatedir%|$(abs_top_srcdir)/tmp|g" \ + -e "s|%localerlanglibdir%|foo \ + -pa $(abs_top_builddir)\/src\/couchdb \ + -pa $(abs_top_builddir)\/src\/erlang-oauth \ + -pa $(abs_top_builddir)\/src\/ibrowse \ + -pa $(abs_top_builddir)\/src\/mochiweb|g" \ + -e "s|%localconfdir%|$(abs_top_builddir)/etc/couchdb|g" \ + -e "s|%localstatelogdir%|$(abs_top_builddir)/tmp/log|g" \ + -e "s|%localstatelibdir%|$(abs_top_builddir)/tmp/lib|g" \ + -e "s|%localstatedir%|$(abs_top_builddir)/tmp|g" \ -e "s|%bug_uri%|@bug_uri@|g" \ -e "s|%package_author_address%|@package_author_address@|g" \ -e "s|%package_author_name%|@package_author_name@|g" \ -- cgit v1.2.3