123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- var _ = require('lodash');
- var seedrandom = require('seedrandom');
- var diff = require('./diff.js');
-
- var ITERATIONS = 10000;
- var ALPHABET = 'GATTACA';
- var LENGTH = 100;
- var EMOJI_MAX_LENGTH = 50;
-
- var seed = Math.floor(Math.random() * 10000);
- var random = seedrandom(seed);
-
- console.log('Running regression tests...');
- [
- ['GAATAAAAAAAGATTAACAT', 'AAAAACTTGTAATTAACAAC'],
- ['๐๐ค๐๐', '๐๐ค๐ค__๐ค๐ค๐ค๐ค๐๐ค๐'],
- ['๐๐ค๐ค__๐ค๐ค๐ค๐ค๐๐ค๐', '๐ค๐ค๐'],
- ['๐ค๐ค๐๐_๐๐๐ค๐ค๐ค__๐๐ค', '๐ค๐๐ค๐๐ค๐ค๐๐ค๐ค๐๐'],
- ['๐ค๐ค๐ค๐๐ค๐๐ค_๐ค๐๐ค๐ค_๐ค๐๐ค๐ค๐๐ค๐ค๐ค๐๐ค๐๐๐๐ค_๐ค๐๐ค๐ค๐๐ค๐ค๐ค๐ค',
- '_๐ค๐ค_๐ค๐ค๐๐ค๐๐ค_๐๐ค๐๐๐๐ค๐๐ค๐ค๐๐๐๐ค๐_๐ค๐ค๐ค๐ค__๐ค_๐๐ค๐ค_๐๐ค๐'],
- ['๐๐ค๐ค๐๐๐ค', '๐ค๐ค๐ค๐ค๐๐๐'],
- ['๐_๐๐๐๐ค๐', '๐ค๐ค๐๐ค_๐ค๐_'],
- ].forEach(function (data) {
- var result = diff(data[0], data[1]);
- applyDiff(result, data[0], data[1]);
- });
-
- console.log('Running computing ' + ITERATIONS + ' diffs with seed ' + seed + '...');
-
- console.log('Generating strings...');
- var strings = [];
- for (var i = 0; i <= ITERATIONS; ++i) {
- var chars = [];
- for (var l = 0; l < LENGTH; ++l) {
- var letter = ALPHABET.substr(Math.floor(random() * ALPHABET.length), 1);
- chars.push(letter);
- }
- strings.push(chars.join(''));
- }
-
- console.log('Running fuzz tests *without* cursor information...');
- for (var i = 0; i < ITERATIONS; ++i) {
- var result = diff(strings[i], strings[i + 1]);
- applyDiff(result, strings[i], strings[i + 1]);
- }
-
- console.log('Running fuzz tests *with* cursor information');
- for (var i = 0; i < ITERATIONS; ++i) {
- var cursor_pos = Math.floor(random() * strings[i].length + 1);
- var diffs = diff(strings[i], strings[i + 1], cursor_pos);
- applyDiff(diffs, strings[i], strings[i + 1]);
- }
-
- function parseDiff(str) {
- if (!str) {
- return [];
- }
- return str.split(/(?=[+\-=])/).map(function (piece) {
- var symbol = piece.charAt(0);
- var text = piece.slice(1);
- return [
- symbol === '+' ? diff.INSERT : symbol === '-' ? diff.DELETE : diff.EQUAL,
- text
- ]
- });
- }
-
- console.log('Running cursor tests');
- [
- ['', 0, '', null, ''],
-
- ['', 0, 'a', null, '+a'],
- ['a', 0, 'aa', null, '+a=a'],
- ['a', 1, 'aa', null, '=a+a'],
- ['aa', 0, 'aaa', null, '+a=aa'],
- ['aa', 1, 'aaa', null, '=a+a=a'],
- ['aa', 2, 'aaa', null, '=aa+a'],
- ['aaa', 0, 'aaaa', null, '+a=aaa'],
- ['aaa', 1, 'aaaa', null, '=a+a=aa'],
- ['aaa', 2, 'aaaa', null, '=aa+a=a'],
- ['aaa', 3, 'aaaa', null, '=aaa+a'],
-
- ['a', 0, '', null, '-a'],
- ['a', 1, '', null, '-a'],
- ['aa', 0, 'a', null, '-a=a'],
- ['aa', 1, 'a', null, '-a=a'],
- ['aa', 2, 'a', null, '=a-a'],
- ['aaa', 0, 'aa', null, '-a=aa'],
- ['aaa', 1, 'aa', null, '-a=aa'],
- ['aaa', 2, 'aa', null, '=a-a=a'],
- ['aaa', 3, 'aa', null, '=aa-a'],
-
- ['', 0, '', 0, ''],
-
- ['', 0, 'a', 1, '+a'],
- ['a', 0, 'aa', 1, '+a=a'],
- ['a', 1, 'aa', 2, '=a+a'],
- ['aa', 0, 'aaa', 1, '+a=aa'],
- ['aa', 1, 'aaa', 2, '=a+a=a'],
- ['aa', 2, 'aaa', 3, '=aa+a'],
- ['aaa', 0, 'aaaa', 1, '+a=aaa'],
- ['aaa', 1, 'aaaa', 2, '=a+a=aa'],
- ['aaa', 2, 'aaaa', 3, '=aa+a=a'],
- ['aaa', 3, 'aaaa', 4, '=aaa+a'],
-
- ['a', 1, '', 0, '-a'],
- ['aa', 1, 'a', 0, '-a=a'],
- ['aa', 2, 'a', 1, '=a-a'],
- ['aaa', 1, 'aa', 0, '-a=aa'],
- ['aaa', 2, 'aa', 1, '=a-a=a'],
- ['aaa', 3, 'aa', 2, '=aa-a'],
-
- ['a', 1, '', 0, '-a'],
- ['aa', 1, 'a', 0, '-a=a'],
- ['aa', 2, 'a', 1, '=a-a'],
- ['aaa', 1, 'aa', 0, '-a=aa'],
- ['aaa', 2, 'aa', 1, '=a-a=a'],
- ['aaa', 3, 'aa', 2, '=aa-a'],
-
- // forward-delete
- ['a', 0, '', 0, '-a'],
- ['aa', 0, 'a', 0, '-a=a'],
- ['aa', 1, 'a', 1, '=a-a'],
- ['aaa', 0, 'aa', 0, '-a=aa'],
- ['aaa', 1, 'aa', 1, '=a-a=a'],
- ['aaa', 2, 'aa', 2, '=aa-a'],
-
- ['bob', 0, 'bobob', null, '+bo=bob'],
- ['bob', 1, 'bobob', null, '=b+ob=ob'],
- ['bob', 2, 'bobob', null, '=bo+bo=b'],
- ['bob', 3, 'bobob', null, '=bob+ob'],
- ['bob', 0, 'bobob', 2, '+bo=bob'],
- ['bob', 1, 'bobob', 3, '=b+ob=ob'],
- ['bob', 2, 'bobob', 4, '=bo+bo=b'],
- ['bob', 3, 'bobob', 5, '=bob+ob'],
- ['bobob', 2, 'bob', null, '-bo=bob'],
- ['bobob', 3, 'bob', null, '=b-ob=ob'],
- ['bobob', 4, 'bob', null, '=bo-bo=b'],
- ['bobob', 5, 'bob', null, '=bob-ob'],
- ['bobob', 2, 'bob', 0, '-bo=bob'],
- ['bobob', 3, 'bob', 1, '=b-ob=ob'],
- ['bobob', 4, 'bob', 2, '=bo-bo=b'],
- ['bobob', 5, 'bob', 3, '=bob-ob'],
-
- ['bob', 1, 'b', null, '=b-ob'],
-
- ['hello', [0, 5], 'h', 1, '-hello+h'],
- ['yay', [0, 3], 'y', 1, '-yay+y'],
- ['bobob', [1, 4], 'bob', 2, '=b-obo+o=b'],
-
- ].forEach(function (data) {
- var oldText = data[0];
- var newText = data[2];
- var oldRange = typeof data[1] === 'number' ?
- { index: data[1], length: 0 } :
- { index: data[1][0], length: data[1][1] - data[1][0] };
- var newRange = typeof data[3] === 'number' ?
- { index: data[3], length: 0 } :
- data[3] === null ? null : { index: data[3][0], length: data[3][1] - data[3][0] };
- var expected = parseDiff(data[4]);
- if (newRange === null && typeof data[1] !== 'number') {
- throw new Error('invalid test case');
- }
- var cursorInfo = newRange === null ? data[1] : {
- oldRange: oldRange,
- newRange: newRange,
- };
- doCursorTest(oldText, newText, cursorInfo, expected);
- doCursorTest('x' + oldText, 'x' + newText, shiftCursorInfo(cursorInfo, 1), diffPrepend(expected, 'x'));
- doCursorTest(oldText + 'x', newText + 'x', cursorInfo, diffAppend(expected, 'x'));
- });
-
- function diffPrepend(tuples, text) {
- if (tuples.length > 0 && tuples[0][0] === diff.EQUAL) {
- return [[diff.EQUAL, text + tuples[0][1]]].concat(tuples.slice(1));
- } else {
- return [[diff.EQUAL, text]].concat(tuples);
- }
- }
-
- function diffAppend(tuples, text) {
- var lastTuple = tuples[tuples.length - 1];
- if (lastTuple && lastTuple[0] === diff.EQUAL) {
- return tuples.slice(0, -1).concat([[diff.EQUAL, lastTuple[1] + text]]);
- } else {
- return tuples.concat([[diff.EQUAL, text]]);
- }
- }
-
- function shiftCursorInfo(cursorInfo, amount) {
- if (typeof cursorInfo === 'number') {
- return cursorInfo + amount;
- } else {
- return {
- oldRange: {
- index: cursorInfo.oldRange.index + amount,
- length: cursorInfo.oldRange.length,
- },
- newRange: {
- index: cursorInfo.newRange.index + amount,
- length: cursorInfo.newRange.length,
- },
- }
- }
- }
-
- function doCursorTest(oldText, newText, cursorInfo, expected) {
- var result = diff(oldText, newText, cursorInfo);
- if (!_.isEqual(result, expected)) {
- console.log([oldText, newText, cursorInfo]);
- console.log(result, '!==', expected);
- throw new Error('cursor test failed');
- }
- }
-
- console.log('Running emoji tests');
- [
- ['๐ถ', '๐ฏ', '-๐ถ+๐ฏ'],
- ['๐จ๐ฝ', '๐ฉ๐ฝ', '-๐จ+๐ฉ=๐ฝ'],
- ['๐ฉ๐ผ', '๐ฉ๐ฝ', '=๐ฉ-๐ผ+๐ฝ'],
-
- ['๐๐', '๐', '-๐=๐'],
- ['๐', '๐๐', '+๐=๐'],
-
- ].forEach(function (data) {
- var oldText = data[0];
- var newText = data[1];
- var expected = parseDiff(data[2]);
- doEmojiTest(oldText, newText, expected);
- doEmojiTest('x' + oldText, 'x' + newText, diffPrepend(expected, 'x'));
- doEmojiTest(oldText + 'x', newText + 'x', diffAppend(expected, 'x'));
- });
-
- function doEmojiTest(oldText, newText, expected) {
- var result = diff(oldText, newText);
- if (!_.isEqual(result, expected)) {
- console.log(oldText, newText, expected);
- console.log(result, '!==', expected);
- throw new Error('Emoji simple test case failed');
- }
- }
-
- // emojis chosen to share high and low surrogates!
- var EMOJI_ALPHABET = ['_', '๐ค', '๐', '๐ค', '๐'];
-
- console.log('Generating emoji strings...');
- var emoji_strings = [];
- for (var i = 0; i <= ITERATIONS; ++i) {
- var letters = [];
- var len = Math.floor(random() * EMOJI_MAX_LENGTH);
- for (var l = 0; l < len; ++l) {
- var letter = EMOJI_ALPHABET[Math.floor(random() * EMOJI_ALPHABET.length)];
- letters.push(letter);
- }
- emoji_strings.push(letters.join(''));
- }
-
- console.log('Running emoji fuzz tests...');
- for (var i = 0; i < ITERATIONS; ++i) {
- var oldText = emoji_strings[i];
- var newText = emoji_strings[i + 1];
- var result = diff(oldText, newText);
- applyDiff(result, oldText, newText);
- }
-
- // Applies a diff to text, throwing an error if diff is invalid or incorrect
- function applyDiff(diffs, text, expectedResult) {
- var pos = 0;
- function throwError(message) {
- console.log(diffs, text, expectedResult);
- throw new Error(message);
- }
- function expect(expected) {
- var found = text.substr(pos, expected.length);
- if (found !== expected) {
- throwError('Expected "' + expected + '", found "' + found + '"');
- }
- }
- var result = '';
- var inserts_since_last_equality = 0;
- var deletes_since_last_equality = 0;
- for (var i = 0; i < diffs.length; i++) {
- var d = diffs[i];
- if (!d[1]) {
- throwError('Empty tuple in diff')
- }
- var firstCharCode = d[1].charCodeAt(0);
- var lastCharCode = d[1].slice(-1).charCodeAt(0);
- if (firstCharCode >= 0xDC00 && firstCharCode <= 0xDFFF ||
- lastCharCode >= 0xD800 && lastCharCode <= 0xDBFF) {
- throwError('Bad unicode diff tuple')
- }
- switch (d[0]) {
- case diff.EQUAL:
- if (i !== 0 && !inserts_since_last_equality && !deletes_since_last_equality) {
- throwError('two consecutive equalities in diff');
- }
- inserts_since_last_equality = 0;
- deletes_since_last_equality = 0;
- expect(d[1]);
- result += d[1];
- pos += d[1].length;
- break;
- case diff.DELETE:
- if (deletes_since_last_equality) {
- throwError('multiple deletes between equalities')
- }
- if (inserts_since_last_equality) {
- throwError('delete following insert in diff')
- }
- deletes_since_last_equality++;
- expect(d[1]);
- pos += d[1].length;
- break
- case diff.INSERT:
- if (inserts_since_last_equality) {
- throwError('multiple inserts between equalities')
- }
- inserts_since_last_equality++;
- result += d[1];
- break;
- }
- }
- if (pos !== text.length) {
- throwError('Diff did not consume entire input text');
- }
- if (result !== expectedResult) {
- console.log(diffs, text, expectedResult, result);
- throw new Error('Diff not correct')
- }
- return result;
- }
-
- console.log("Success!");
|