blob: b610bb50470fa30052f60ff986d700183593ea9b [file] [log] [blame]
'use strict';
// Porting tests from Apache CouchDB
// https://github.com/davisp/couchdb/blob/local_doc_revs/test/
// etap/060-kt-merging.t
var PouchDB = require('../../packages/node_modules/pouchdb-for-coverage');
var merge = PouchDB.utils.merge;
var winningRev = PouchDB.utils.winningRev;
describe('test.merge.js', function () {
var simple;
var two0;
var two1;
var newleaf;
var withnewleaf;
var newbranch;
var newdeepbranch;
var stemmededit;
var stemmedconflicts;
var newbranchleaf;
var newbranchleafbranch;
var stemmed2;
var stemmed3;
var partialrecover;
/*
* Our merge() function actually mutates the input object, because it's
* more performant than deep cloning the object every time it's passed
* into merge(). So in order for these tests to pass, we need to redefine
* these objects every time.
*/
beforeEach(function () {
simple = {pos: 1, ids: ['1', {}, []]};
two0 = {pos: 1, ids: ['1', {}, [['2_0', {}, []]]]};
two1 = {pos: 1, ids: ['1', {}, [['2_1', {}, []]]]};
newleaf = {pos: 2, ids: ['2_0', {}, [['3', {}, []]]]};
withnewleaf = {pos: 1, ids: ['1', {}, [['2_0', {}, [['3', {}, []]]]]]};
newbranch = {pos: 1, ids: ['1', {}, [['2_0', {}, []], ['2_1', {}, []]]]};
newdeepbranch = {pos: 2, ids: ['2_0', {}, [['3_1', {}, []]]]};
stemmededit = {pos: 3, ids: ['3', {}, []]};
stemmedconflicts = [simple, stemmededit];
newbranchleaf = {
pos: 1,
ids: ['1', {}, [['2_0', {}, [['3', {}, []]]], ['2_1', {}, []]]]
};
newbranchleafbranch = {
pos: 1,
ids: ['1', {}, [
['2_0', {}, [['3', {}, []], ['3_1', {}, []]]], ['2_1', {}, []]
]]
};
stemmed2 = [
{pos: 1, ids: ['1', {}, [['2_1', {}, []]]]},
{pos: 2, ids: ['2_0', {}, [['3', {}, []], ['3_1', {}, []]]]}
];
stemmed3 = [
{pos: 2, ids: ['2_1', {}, []]},
{pos: 3, ids: ['3', {}, []]},
{pos: 3, ids: ['3_1', {}, []]}
];
partialrecover = [
{pos: 1, ids: ['1', {}, [['2_0', {}, [['3', {}, []]]]]]},
{pos: 2, ids: ['2_1', {}, []]},
{pos: 3, ids: ['3_1', {}, []]}
];
});
it('Merging a path into an empty tree is the path', function () {
merge([], simple, 10).should.deep.equal({
tree: [simple],
stemmedRevs: [],
conflicts: 'new_leaf'
});
});
it('Remerge path into path is reflexive', function () {
merge([simple], simple, 10).should.deep.equal({
tree: [simple],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
it('Merging a path with multiple entries is the path', function () {
merge([], two0, 10).should.deep.equal({
tree: [two0],
stemmedRevs: [],
conflicts: 'new_leaf'
});
});
it('Merging a path with multiple entries is reflexive', function () {
merge([two0], two0, 10).should.deep.equal({
tree: [two0],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
it('Merging a subpath into a path results in the path', function () {
merge([two0], simple, 10).should.deep.equal({
tree: [two0],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
it('Merging a new leaf gives us a new leaf', function () {
merge([two0], newleaf, 10).should.deep.equal({
tree: [withnewleaf],
stemmedRevs: [],
conflicts: 'new_leaf'
});
});
it('Merging a new branch returns a proper tree', function () {
merge([two0], two1, 10).should.deep.equal({
tree: [newbranch],
stemmedRevs: [],
conflicts: 'new_branch'
});
});
it('Order of merging does not affect the resulting tree', function () {
merge([two1], two0, 10).should.deep.equal({
tree: [newbranch],
stemmedRevs: [],
conflicts: 'new_branch'
});
});
it('Merging a new_leaf doesnt return new_branch when branches exist',
function () {
merge([newbranch], newleaf, 10).should.deep.equal({
tree: [newbranchleaf],
stemmedRevs: [],
conflicts: 'new_leaf'
});
});
it('Merging a deep branch with branches works', function () {
merge([newbranchleaf], newdeepbranch, 10).should.deep.equal({
tree: [newbranchleafbranch],
stemmedRevs: [],
conflicts: 'new_branch'
});
});
it('New information reconnects steming induced conflicts', function () {
merge(stemmedconflicts, withnewleaf, 10).should.deep.equal({
tree: [withnewleaf],
stemmedRevs: [],
conflicts: 'new_leaf'
});
});
it('Simple stemming works', function () {
merge([two0], newleaf, 2).should.deep.equal({
tree: [newleaf],
stemmedRevs: ['1-1'],
conflicts: 'new_leaf'
});
});
it('Merge with stemming works correctly for branches', function () {
merge([newbranchleafbranch], simple, 2).should.deep.equal({
tree: stemmed2,
stemmedRevs: [],
conflicts: 'internal_node'
});
});
it('Merge with stemming to leaves works fine', function () {
merge([newbranchleafbranch], simple, 1).should.deep.equal({
tree: stemmed3,
stemmedRevs: ['1-1', '2-2_0'],
conflicts: 'internal_node'
});
});
it('Merging unstemmed recovers as much as possible without losing info',
function () {
merge(stemmed3, withnewleaf, 10).should.deep.equal({
tree: partialrecover,
stemmedRevs: [],
conflicts: 'internal_node'
});
});
it('winningRev returns the longest leaf', function () {
var tree = [
{"pos": 1, "ids": [
"bfe70372c90ded1087239e5191984f76", {}, [
["44d71a718b90e4696c06a90e08912c8f", {}, []],
["56e657612d55ab1a402dcb281c874f2a", {}, [
["93c3db16462f656f7172ccabd3cf6cd6", {}, []]
]]
]
]}
];
winningRev({rev_tree: tree})
.should.equal("3-93c3db16462f656f7172ccabd3cf6cd6");
});
it('winningRev returns the longest leaf again', function () {
// this one is from issue #293
var tree = [
{"pos": 1, "ids": [
"203db1a1810a838895d561f67b224b5d", {}, [
["bf5e08a4f9fa6d33a53f4a00ae3ea399", {}, [
["28cd77a3ca30f79e1cfffcd6a41ca308", {}, []]
]]
]
]},
{"pos": 1, "ids": [
"c6d5cce35bcfbef90b20f140d723cbdb", {}, [
["1b8dfbb1267e213328920bae43f2f597", {}, []],
["59ed830b84b276ab776c3c51aaf93a16", {}, [
["64a9842c6aea50bf24660378e496e853", {}, []]
]]
]
]}
];
winningRev({rev_tree: tree})
.should.equal("3-64a9842c6aea50bf24660378e496e853");
});
// ///// These are tests from CouchDB's kt-merging.erl test suite
var one = {pos: 1, ids: ['1', {}, []]};
it('The empty tree is the identity for merge.', function () {
merge([], one, 10).should.deep.equal({
tree: [one],
stemmedRevs: [],
conflicts: 'new_leaf'
});
});
it('Merging is reflexive', function () {
merge([one], one, 10).should.deep.equal({
tree: [one],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
var two = {pos: 1, ids: ['2', {}, []]};
var twoSibs = [one, two];
it('Merging a prefix of a tree with the tree yields the tree.', function () {
merge(twoSibs, one, 10).should.deep.equal({
tree: twoSibs,
stemmedRevs: [],
conflicts: 'internal_node'
});
});
var three = {pos: 1, ids: ['3', {}, []]};
var threeSibs = [one, two, three];
it('Merging a third unrelated branch leads to a conflict.', function () {
merge(twoSibs, three, 10).should.deep.equal({
tree: threeSibs,
stemmedRevs: [],
conflicts: 'internal_node'
});
});
var twoChild = {pos: 1, ids: ['1', {}, [
['1a', {}, [
['1aa', {}, []]
]]
]]};
it('Merging two children is still reflexive.', function () {
merge([twoChild], twoChild, 10).should.deep.equal({
tree: [twoChild],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
var twoChildSibs = {pos: 1, ids: ['1', {}, [
['1a', {}, []],
['1b', {}, []]
]]};
it('Merging a tree to itself is itself.', function () {
merge([twoChildSibs], twoChildSibs, 10).should.deep.equal({
tree: [twoChildSibs],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
var twoChildPlusSibs = {pos: 1, ids: ['1', {}, [
['1a', {}, [
['1aa', {}, []]
]],
['1b', {}, []]
]]};
it('Merging tree of uneven length at node 2.', function () {
merge([twoChild], twoChildSibs, 10).should.deep.equal({
tree: [twoChildPlusSibs],
stemmedRevs: [],
conflicts: 'new_branch'
});
});
var stemmed1b = {pos: 2, ids: ['1a', {}, []]};
it('Merging a tree with a stem.', function () {
merge([twoChildSibs], stemmed1b, 10).should.deep.equal({
tree: [twoChildSibs],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
var twoChildPlusSibs2 = {pos: 1, ids: ['1', {}, [
['1a', {}, []],
['1b', {}, [
['1bb', {}, []]
]]
]]};
var stemmed1bb = {pos: 3, ids: ['1bb', {}, []]};
it('Merging a stem at a deeper level.', function () {
merge([twoChildPlusSibs2], stemmed1bb, 10).should.deep.equal({
tree: [twoChildPlusSibs2],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
var stemmedTwoChildSibs2 = [
{pos: 2, ids: ['1a', {}, []]},
{pos: 2, ids: ['1b', {}, [['1bb', {}, []]]]}
];
it('Merging a stem at a deeper level against paths at deeper levels.',
function () {
merge(stemmedTwoChildSibs2, stemmed1bb, 10).should.deep.equal({
tree: stemmedTwoChildSibs2,
stemmedRevs: [],
conflicts: 'internal_node'
});
});
var stemmed1aa = {pos: 3, ids: ['1aa', {}, []]};
it("Merging a single tree with a deeper stem.", function () {
merge([twoChild], stemmed1aa, 10).should.deep.equal({
tree: [twoChild],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
var stemmed1a = {pos: 2, ids: ['1a', {}, [['1aa', {}, []]]]};
it('Merging a larger stem.', function () {
merge([twoChild], stemmed1a, 10).should.deep.equal({
tree: [twoChild],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
it('More merging.', function () {
merge([stemmed1a], stemmed1aa, 10).should.deep.equal({
tree: [stemmed1a],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
var oneChild = {pos: 1, ids: ['1', {}, [['1a', {}, []]]]};
it('Merging should create conflicts.', function () {
merge([oneChild], stemmed1aa, 10).should.deep.equal({
tree: [oneChild, stemmed1aa],
stemmedRevs: [],
conflicts: 'internal_node'
});
});
it('Merging should have no conflicts.', function () {
merge([oneChild, stemmed1aa], twoChild, 10).should.deep.equal({
tree: [twoChild],
stemmedRevs: [],
conflicts: 'new_leaf'
});
});
var foo = {pos: 1, ids: ['foo', {}, [
['foo2', {}, []],
['foo3', {}, []]
]]};
var bar = {pos: 1, ids: ['foo', {}, [
['foo3', {}, [
['foo4', {}, []]
]]
]]};
var fooBar = {pos: 1, ids: ['foo', {}, [
['foo2', {}, []],
['foo3', {}, [
['foo4', {}, []]
]]
]]};
it('Merging trees with conflicts ought to behave.', function () {
merge([foo], bar, 10).should.deep.equal({
tree: [fooBar],
stemmedRevs: [],
conflicts: 'new_leaf'
});
});
});