From 544a38dd45f6a58d34296c6c768afd086eb2ac70 Mon Sep 17 00:00:00 2001 From: Christopher Lenz Date: Fri, 28 Mar 2008 23:32:19 +0000 Subject: Imported trunk. git-svn-id: https://svn.apache.org/repos/asf/incubator/couchdb/trunk@642432 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/couch_tests.js | 1027 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1027 insertions(+) create mode 100644 share/www/script/couch_tests.js (limited to 'share/www/script/couch_tests.js') diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js new file mode 100644 index 00000000..bd159310 --- /dev/null +++ b/share/www/script/couch_tests.js @@ -0,0 +1,1027 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +var tests = { + + // Do some basic tests. + basics: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // Get the database info, check the doc_count + T(db.info().doc_count == 0); + + // create a document and save it to the database + var doc = {_id:"0",a:1,b:1}; + var result = db.save(doc); + + T(result.ok==true); // return object has an ok member with a value true + T(result.id); // the _id of the document is set. + T(result.rev); // the revision id of the document is set. + + // Verify the input doc is now set with the doc id and rev + // (for caller convenience). + T(doc._id == result.id && doc._rev == result.rev); + + var id = result.id; // save off the id for later + + // Create some more documents. + // Notice the use of the ok member on the return result. + T(db.save({_id:"1",a:2,b:4}).ok); + T(db.save({_id:"2",a:3,b:9}).ok); + T(db.save({_id:"3",a:4,b:16}).ok); + + // Check the database doc count + T(db.info().doc_count == 4); + + // Check the all docs + var results = db.allDocs(); + var rows = results.rows; + + for(var i=0; i < rows.length; i++) { + T(rows[i].id >= "0" && rows[i].id <= "4"); + } + + // Test a simple map functions + + // create a map function that selects all documents whose "a" member + // has a value of 4, and then returns the document's b value. + var mapFunction = function(doc){ + if(doc.a==4) + map(null, doc.b); + }; + + results = db.query(mapFunction); + + // verify only one document found and the result value (doc.b). + T(results.total_rows == 1 && results.rows[0].value == 16); + + // reopen document we saved earlier + existingDoc = db.open(id); + + T(existingDoc.a==1); + + //modify and save + existingDoc.a=4; + db.save(existingDoc); + + // redo the map query + results = db.query(mapFunction); + + // the modified document should now be in the results. + T(results.total_rows == 2); + + // write 2 more documents + T(db.save({a:3,b:9}).ok); + T(db.save({a:4,b:16}).ok); + + results = db.query(mapFunction); + + // 1 more document should now be in the result. + T(results.total_rows == 3); + T(db.info().doc_count == 6); + + // delete a document + T(db.deleteDoc(existingDoc).ok); + + // make sure we can't open the doc + T(db.open(existingDoc._id) == null); + + results = db.query(mapFunction); + + // 1 less document should now be in the results. + T(results.total_rows == 2); + T(db.info().doc_count == 5); + }, + + // Do some edit conflict detection tests + conflicts: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // create a doc and save + var doc = {_id:"foo",a:1,b:1}; + T(db.save(doc).ok); + + // reopen + var doc2 = db.open(doc._id); + + // ensure the revisions are the same + T(doc._id == doc2._id && doc._rev == doc2._rev); + + // edit the documents. + doc.a = 2; + doc2.a = 3; + + // save one document + T(db.save(doc).ok); + + // save the other document + try { + db.save(doc2); // this should generate a conflict exception + T("no save conflict 1" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + // Now clear out the _rev member and save. This indicates this document is + // new, not based on an existing revision. + doc2._rev = undefined; + try { + db.save(doc2); // this should generate a conflict exception + T("no save conflict 2" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + // Now delete the document from the database + T(db.deleteDoc(doc).ok); + + T(db.save(doc2).ok); // we can save a new document over a deletion without + // knowing the deletion rev. + }, + + recreate_doc: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // First create a new document with the ID "foo", and delete it again + var doc = {_id: "foo", a: "bar", b: 42}; + T(db.save(doc).ok); + T(db.deleteDoc(doc).ok); + + // Now create a new document with the same ID, save it, and then modify it + // This should work fine, but currently results in a conflict error, at + // least "sometimes" + for (var i = 0; i < 10; i++) { + doc = {_id: "foo"}; + T(db.save(doc).ok); + doc = db.open("foo"); + doc.a = "baz"; + try { + T(db.save(doc).ok); + } finally { + // And now, we can't even delete the document anymore :/ + T(db.deleteDoc(doc).rev != undefined); + } + } + }, + + // test saving a semi-large quanitity of documents and do some view queries. + lots_of_docs: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // keep number lowish for now to keep tests fasts. Crank up manually to + // to really test. + var numDocsToCreate = 500; + + var docs = makeDocs(numDocsToCreate); + T(db.bulkSave(docs).ok); + + // query all documents, and return the doc.integer member as a key. + results = db.query(function(doc){ map(doc.integer, null) }); + + T(results.total_rows == numDocsToCreate); + + // validate the keys are ordered ascending + for(var i=0; i= 0; i -= 10) { + var queryResults = db.query(queryFun, {startkey:i, startkey_docid:i, + count:-10}) + T(queryResults.rows.length == 10) + T(queryResults.total_rows == docs.length) + T(queryResults.offset == i - 9) + var j; + for (j = 0; j < 10;j++) { + T(queryResults.rows[j].key == i - 9 + j); + } + } + + // page through the view descending and going forward + for (i = docs.length - 1; i >= 0; i -= 10) { + var queryResults = db.query(queryFun, {startkey:i, startkey_docid:i, + descending:true, count:10}) + T(queryResults.rows.length == 10) + T(queryResults.total_rows == docs.length) + T(queryResults.offset == docs.length - i - 1) + var j; + for (j = 0; j < 10; j++) { + T(queryResults.rows[j].key == i - j); + } + } + + // page through the view descending and going backward + for (i = 0; i < docs.length; i += 10) { + var queryResults = db.query(queryFun, {startkey:i, startkey_docid:i, + descending:true, count:-10}); + T(queryResults.rows.length == 10) + T(queryResults.total_rows == docs.length) + T(queryResults.offset == docs.length - i - 10) + var j; + for (j = 0; j < 10; j++) { + T(queryResults.rows[j].key == i + 9 - j); + } + } + }, + + view_sandboxing: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var docs = makeDocs(2); + T(db.bulkSave(docs).ok); + + // make sure that attempting to change the document throws an error + var results = db.query(function(doc) { + doc._id = "foo"; + map(null, doc); + }); + T(results.total_rows == 0); + + // make sure that a view cannot invoke interpreter internals such as the + // garbage collector + var results = db.query(function(doc) { + gc(); + map(null, doc); + }); + T(results.total_rows == 0); + + // make sure that a view cannot access the map_funs array defined used by + // the view server + var results = db.query(function(doc) { map_funs.push(1); map(null, doc) }); + T(results.total_rows == 0); + + // make sure that a view cannot access the map_results array defined used by + // the view server + var results = db.query(function(doc) { map_results.push(1); map(null, doc) }); + T(results.total_rows == 0); + }, + + view_xml: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + db.save({content: "Testing XML"}); + db.save({content: "Testing E4X"}); + + var results = db.query( + "function(doc) {\n" + + " var xml = new XML(doc.content);\n" + + " map(xml.title.text(), null);\n" + + "}"); + T(results.total_rows == 2); + T(results.rows[0].key == "Testing E4X"); + T(results.rows[1].key == "Testing XML"); + + var results = db.query( + "function(doc) {\n" + + " var xml = new XML(doc.content);\n" + + " map(xml.title.@id, null);\n" + + "}"); + T(results.total_rows == 2); + T(results.rows[0].key == "e4x"); + T(results.rows[1].key == "xml"); + }, + + replication: function(debug) { + if (debug) debugger; + var dbPairs = [ + {source:"test_suite_db_a", + target:"test_suite_db_b"}, + {source:"test_suite_db_a", + target:"http://localhost:5984/test_suite_db_b"}, + {source:"http://localhost:5984/test_suite_db_a", + target:"test_suite_db_b"}, + {source:"http://localhost:5984/test_suite_db_a", + target:"http://localhost:5984/test_suite_db_b"} + ] + var dbA = new CouchDB("test_suite_db_a"); + var dbB = new CouchDB("test_suite_db_b"); + var numDocs = 10; + var xhr; + for (var testPair = 0; testPair < dbPairs.length; testPair++) { + var A = dbPairs[testPair].source + var B = dbPairs[testPair].target + + dbA.deleteDb(); + dbA.createDb(); + dbB.deleteDb(); + dbB.createDb(); + + var docs = makeDocs(numDocs); + T(dbA.bulkSave(docs).ok); + + T(CouchDB.replicate(A, B).ok); + + for (var j = 0; j < numDocs; j++) { + docA = dbA.open("" + j); + docB = dbB.open("" + j); + T(docA._rev == docB._rev); + } + + // now check binary attachments + var binDoc = { + _id:"bin_doc", + _attachments:{ + "foo.txt": { + "type":"base64", + "data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + } + } + + dbA.save(binDoc); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + xhr = CouchDB.request("GET", "/test_suite_db_a/bin_doc/foo.txt"); + T(xhr.responseText == "This is a base64 encoded text") + + xhr = CouchDB.request("GET", "/test_suite_db_b/bin_doc/foo.txt"); + T(xhr.responseText == "This is a base64 encoded text") + + dbA.save({_id:"foo1",value:"a"}); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + docA = dbA.open("foo1"); + docB = dbB.open("foo1"); + T(docA._rev == docB._rev); + + dbA.deleteDoc(docA); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + T(dbA.open("foo1") == null); + T(dbB.open("foo1") == null); + + dbA.save({_id:"foo",value:"a"}); + dbB.save({_id:"foo",value:"b"}); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + // open documents and include the conflict meta data + docA = dbA.open("foo", {conflicts: true}); + docB = dbB.open("foo", {conflicts: true}); + + // make sure the same rev is in each db + T(docA._rev === docB._rev); + + // make sure the conflicts are the same in each db + T(docA._conflicts[0] === docB._conflicts[0]); + + // delete a conflict. + dbA.deleteDoc({_id:"foo", _rev:docA._conflicts[0]}); + + // replicate the change + T(CouchDB.replicate(A, B).ok); + + // open documents and include the conflict meta data + docA = dbA.open("foo", {conflicts: true}); + docB = dbB.open("foo", {conflicts: true}); + + // We should have no conflicts this time + T(docA._conflicts === undefined) + T(docB._conflicts === undefined); + } + }, + + etags_head: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var xhr; + + // create a new doc + xhr = CouchDB.request("PUT", "/test_suite_db/1", { + body: "{}" + }); + T(xhr.status == 201); + + // extract the ETag header values + var etag = xhr.getResponseHeader("etag") + + // get the doc and verify the headers match + xhr = CouchDB.request("GET", "/test_suite_db/1"); + T(etag == xhr.getResponseHeader("etag")); + + // 'head' the doc and verify the headers match + xhr = CouchDB.request("HEAD", "/test_suite_db/1", { + headers: {"if-none-match": "s"} + }); + T(etag == xhr.getResponseHeader("etag")); + + // replace a doc + xhr = CouchDB.request("PUT", "/test_suite_db/1", { + body: "{}", + headers: {"if-match": etag} + }); + T(xhr.status == 201); + + // extract the new ETag value + var etagOld= etag; + etag = xhr.getResponseHeader("etag") + + // fail to replace a doc + xhr = CouchDB.request("PUT", "/test_suite_db/1", { + body: "{}" + }); + T(xhr.status == 412) + + // verify get w/Etag + xhr = CouchDB.request("GET", "/test_suite_db/1", { + headers: {"if-none-match": etagOld} + }); + T(xhr.status == 200); + xhr = CouchDB.request("GET", "/test_suite_db/1", { + headers: {"if-none-match": etag} + }); + T(xhr.status == 304); + + // fail to delete a doc + xhr = CouchDB.request("DELETE", "/test_suite_db/1", { + headers: {"if-match": etagOld} + }); + T(xhr.status == 412); + + //now do it for real + xhr = CouchDB.request("DELETE", "/test_suite_db/1", { + headers: {"if-match": etag} + }); + T(xhr.status == 202) + } + +}; + +function makeDocs(n, templateDoc) { + var templateDocSrc = templateDoc ? templateDoc.toSource() : "{}" + var docs = [] + for(var i=0; i 0 ? "failure" : "success"; + } catch (e) { + var status = "error"; + if ($("td.details ol", row).length == 0) { + $("
    ").appendTo($("td.details", row)); + } + $("
  1. Exception raised:
  2. ") + .find("code").text(JSON.stringify(e)).end() + .appendTo($("td.details ol", row)); + if (debug) { + currentRow = null; + throw e; + } + } + if ($("td.details ol", row).length) { + $("Run with debugger").click(function() { + runTest(this, undefined, true); + }).prependTo($("td.details ol", row)); + } + var duration = new Date().getTime() - start; + $("td.status", row).removeClass("running").addClass(status).text(status); + $("td.duration", row).text(duration + "ms"); + updateTestsFooter(); + currentRow = null; + if (callback) callback(); + } + $("td.status", row).addClass("running").text("running…"); + setTimeout(run, 100); +} + +function showSource(cell) { + var name = $(cell).text(); + var win = window.open("", name, "width=700,height=500,resizable=yes,scrollbars=yes"); + win.document.title = name; + $("
    ").text(tests[name].toString()).appendTo(win.document.body).fadeIn();
    +}
    +
    +function updateTestsListing() {
    +  for (var name in tests) {
    +    if (!tests.hasOwnProperty(name)) continue;
    +    var testFunction = tests[name];
    +    var row = $("")
    +      .find("th").text(name).attr("title", "Show source").click(function() {
    +        showSource(this);
    +      }).end()
    +      .find("td:nth(0)").addClass("status").text("not run").end()
    +      .find("td:nth(1)").addClass("duration").html(" ").end()
    +      .find("td:nth(2)").addClass("details").html(" ").end();
    +    $("").click(function() {
    +      this.blur();
    +      runTest(this);
    +      return false;
    +    }).prependTo(row.find("th"));
    +    row.attr("id", name).appendTo("#tests tbody.content");
    +  }
    +  $("#tests tr").removeClass("odd").filter(":odd").addClass("odd");
    +  updateTestsFooter();
    +}
    +
    +function updateTestsFooter() {
    +  var tests = $("#tests tbody.content tr td.status");
    +  var testsRun = tests.not(":contains('not run'))");
    +  var testsFailed = testsRun.not(".success");
    +  $("#tests tbody.footer td").text(testsRun.length + " of " + tests.length +
    +    " test(s) run, " + testsFailed.length + " failures");
    +}
    +
    +// 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) {
    +    if (currentRow) {
    +      if ($("td.details ol", currentRow).length == 0) {
    +        $("
      ").appendTo($("td.details", currentRow)); + } + $("
    1. Assertion failed:
    2. ") + .find("code").text((arg2 != null ? arg2 : arg1).toString()).end() + .appendTo($("td.details ol", currentRow)); + } + numFailures += 1 + } +} + +function equals(a,b) { + if (a === b) return true; + try { + return repr(a) === repr(b); + } catch (e) { + return false; + } +} + +function repr(val) { + if (val === undefined) { + return null; + } else if (val === null) { + return "null"; + } else { + return JSON.stringify(val); + } +} + +function restartServer() { + var xhr = CouchDB.request("POST", "/_restart"); + do { + xhr = CouchDB.request("GET", "/"); + } while(xhr.status != 200); +} -- cgit v1.2.3