From 87f45e73df3e37fbb631bcb14871c621ee77489b Mon Sep 17 00:00:00 2001 From: "Damien F. Katz" Date: Fri, 9 Jan 2009 22:20:48 +0000 Subject: Added support so clients can detect if a server has potentially lost commits after multiple updates, like during bulk imports and so the replicator can detect lost commits on remote replications. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@733174 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/couch_tests.js | 305 +++++++++++++++++++++++++++++----------- 1 file changed, 222 insertions(+), 83 deletions(-) (limited to 'share/www') diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index 20d8733f..3263c532 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -136,6 +136,89 @@ var tests = { // make sure we can still open T(db.open(existingDoc._id, {rev: existingDoc._rev}) != null); }, + + delayed_commits: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // By default, couchdb doesn't fully commit documents to disk right away, + // it waits about a second to batch the full commit flush along with any + // other updates. If it crashes or is restarted you may lose the most + // recent commits. + + T(db.save({_id:"1",a:2,b:4}).ok); + T(db.open("1") != null); + + restartServer(); + + T(db.open("1") == null); // lost the update. + // note if we waited > 1 sec before the restart, the doc would likely + // commit. + + + // Retry the same thing but with full commits on. + + var db2 = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"}); + + T(db2.save({_id:"1",a:2,b:4}).ok); + T(db2.open("1") != null); + + restartServer(); + + T(db2.open("1") != null); + + // You can update but without committing immediately, and then ensure + // everything is commited in the last step. + + T(db.save({_id:"2",a:2,b:4}).ok); + T(db.open("2") != null); + T(db.ensureFullCommit().ok); + restartServer(); + + T(db.open("2") != null); + + // However, it's possible even when flushed, that the server crashed between + // the update and the commit, and you don't want to check to make sure + // every doc you updated actually made it to disk. So record the instance + // start time of the database before the updates and then check it again + // after the flush (the instance start time is returned by the flush + // operation). if they are the same, we know everything was updated + // safely. + + // First try it with a crash. + + var instanceStartTime = db.info().instance_start_time; + + T(db.save({_id:"3",a:2,b:4}).ok); + T(db.open("3") != null); + + restartServer(); + + var commitResult = db.ensureFullCommit(); + T(commitResult.ok && commitResult.instance_start_time != instanceStartTime); + // start times don't match, meaning the server lost our change + + T(db.open("3") == null); // yup lost it + + // retry with no server restart + + var instanceStartTime = db.info().instance_start_time; + + T(db.save({_id:"4",a:2,b:4}).ok); + T(db.open("4") != null); + + var commitResult = db.ensureFullCommit(); + T(commitResult.ok && commitResult.instance_start_time == instanceStartTime); + // Successful commit, start times match! + + restartServer(); + + T(db.open("4") != null); + + }, + all_docs: function(debug) { var db = new CouchDB("test_suite_db"); db.deleteDb(); @@ -1881,97 +1964,153 @@ var tests = { dbA.createDb(); dbB.deleteDb(); dbB.createDb(); - - var docs = makeDocs(0, 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); - } - - // check documents with a '/' in the ID - // need to re-encode the slash when replicating from a remote source - dbA.save({ _id:"abc/def", val:"one" }); - T(CouchDB.replicate(A, B).ok); - T(CouchDB.replicate(B, A).ok); + var repTests = { + // copy and paste and put your code in. delete unused steps. + test_template: new function () { + this.init = function(dbA, dbB) { + // before anything has happened + } + this.afterAB1 = function(dbA, dbB) { + // called after replicating src=A tgt=B first time. + }; + this.afterBA1 = function(dbA, dbB) { + // called after replicating src=B tgt=A first time. + }; + this.afterAB2 = function(dbA, dbB) { + // called after replicating src=A tgt=B second time. + }; + this.afterBA2 = function(dbA, dbB) { + // etc... + }; + }, + + simple_test: new function () { + this.init = function(dbA, dbB) { + var docs = makeDocs(0, numDocs); + T(dbA.bulkSave(docs).ok); + }; + + this.afterAB1 = function(dbA, dbB) { + for (var j = 0; j < numDocs; j++) { + var docA = dbA.open("" + j); + var docB = dbB.open("" + j); + T(docA._rev == docB._rev); + } + }; + }, - docA = dbA.open("abc/def"); - docB = dbB.open("abc/def"); - T(docA._rev == docB._rev); + deletes_test: new function () { + this.init = function(dbA, dbB) { + T(dbA.save({_id:"foo1",value:"a"}).ok); + }; + + this.afterAB1 = function(dbA, dbB) { + var docA = dbA.open("foo1"); + var docB = dbB.open("foo1"); + T(docA._rev == docB._rev); + + dbA.deleteDoc(docA); + }; + + this.afterAB2 = function(dbA, dbB) { + T(dbA.open("foo1") == null); + T(dbB.open("foo1") == null); + }; + }, + + slashes_in_ids_test: new function () { + // make sure docs with slashes in id replicate properly + this.init = function(dbA, dbB) { + dbA.save({ _id:"abc/def", val:"one" }); + }; + + this.afterAB1 = function(dbA, dbB) { + var docA = dbA.open("abc/def"); + var docB = dbB.open("abc/def"); + T(docA._rev == docB._rev); + }; + }, - // now check binary attachments - var binDoc = { - _id:"bin_doc", - _attachments:{ - "foo.txt": { - "type":"base64", - "data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" - } + attachments_test: new function () { + // Test attachments + this.init = function(dbA, dbB) { + dbA.save({ + _id:"bin_doc", + _attachments:{ + "foo.txt": { + "type":"base64", + "data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + } + }); + }; + + this.afterAB1 = function(dbA, dbB) { + var 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") + }; + }, + + conflicts_test: new function () { + // test conflicts + this.init = function(dbA, dbB) { + dbA.save({_id:"foo",value:"a"}); + dbB.save({_id:"foo",value:"b"}); + }; + + this.afterBA1 = function(dbA, dbB) { + var docA = dbA.open("foo", {conflicts: true}); + var 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]}); + }; + + this.afterBA2 = function(dbA, dbB) { + // open documents and include the conflict meta data + var docA = dbA.open("foo", {conflicts: true}); + var docB = dbB.open("foo", {conflicts: true}); + + // We should have no conflicts this time + T(docA._conflicts === undefined) + T(docB._conflicts === undefined); + }; } - } - - 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); - + }; + var test; + for(test in repTests) + if(repTests[test].init) repTests[test].init(dbA, dbB); + T(CouchDB.replicate(A, B).ok); + + for(test in repTests) + if(repTests[test].afterAB1) repTests[test].afterAB1(dbA, dbB); + 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"}); - + + for(test in repTests) + if(repTests[test].afterBA1) repTests[test].afterBA1(dbA, dbB); + T(CouchDB.replicate(A, B).ok); + + for(test in repTests) + if(repTests[test].afterAB2) repTests[test].afterAB2(dbA, dbB); + 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); + + for(test in repTests) + if(repTests[test].afterBA2) repTests[test].afterBA2(dbA, dbB); + } }, -- cgit v1.2.3