from rust
This commit is contained in:
parent
32203fadc0
commit
1f00cff032
8 changed files with 598 additions and 323 deletions
246
source/xdiff_unittests.d
Normal file
246
source/xdiff_unittests.d
Normal file
|
@ -0,0 +1,246 @@
|
|||
module xdiff_unittests;
|
||||
|
||||
import std.algorithm : equal;
|
||||
import std.array : appender;
|
||||
import std.exception : assertThrown;
|
||||
import std.range : iota, cycle, take;
|
||||
import xdiff_mmfile : MMFile;
|
||||
import xdiff_mmblocks : MMBlocks;
|
||||
|
||||
// -------------------- Вспомогалки --------------------
|
||||
|
||||
/// Сгенерировать массив длиной n, цикляя значения 0..239
|
||||
ubyte[] genCycled(size_t n)
|
||||
{
|
||||
auto acc = appender!(ubyte[])();
|
||||
foreach (v; iota(0, 240).cycle.take(n))
|
||||
acc.put(cast(ubyte)v);
|
||||
return acc.data;
|
||||
}
|
||||
|
||||
/// Вытянуть контент MMBlocks в байты (не разрушая оригинал)
|
||||
ubyte[] blocksToBytes(const MMBlocks b)
|
||||
{
|
||||
auto tmp = b.clone(); // глубокая копия
|
||||
auto mf = MMFile.fromBlocksMoved(tmp);
|
||||
return (cast(const(ubyte)[]) mf.asSlice()).dup;
|
||||
}
|
||||
|
||||
/// Сравнить содержимое двух MMFile по байтам
|
||||
bool sameBytes(const MMFile a, const MMFile b)
|
||||
{
|
||||
return a.asSlice().equal(b.asSlice());
|
||||
}
|
||||
|
||||
// -------------------- ТЕСТЫ --------------------
|
||||
|
||||
unittest // new_empty
|
||||
{
|
||||
auto f = new MMFile;
|
||||
assert(f.size() == 0);
|
||||
assert(f.isCompact());
|
||||
}
|
||||
|
||||
unittest // new_from_bytes
|
||||
{
|
||||
auto data = cast(ubyte[])"hello world";
|
||||
auto f = MMFile.fromBytes(data);
|
||||
assert(f.size() == data.length);
|
||||
assert(f.isCompact());
|
||||
assert(f.asSlice().equal(data));
|
||||
}
|
||||
|
||||
unittest // large_from_bytes
|
||||
{
|
||||
auto data = genCycled(15_000);
|
||||
auto f = MMFile.fromBytes(data);
|
||||
assert(f.size() == data.length);
|
||||
assert(f.isCompact());
|
||||
assert(f.asSlice().equal(data));
|
||||
}
|
||||
|
||||
unittest // "clone" для MMFile: через байты
|
||||
{
|
||||
auto data = genCycled(15_000);
|
||||
auto f = MMFile.fromBytes(data);
|
||||
auto f2 = MMFile.fromBytes(f.asSlice().dup);
|
||||
assert(sameBytes(f, f2));
|
||||
}
|
||||
|
||||
unittest // clone_blocks
|
||||
{
|
||||
auto data = genCycled(15_000);
|
||||
auto b = MMBlocks.fromBytes(data);
|
||||
auto b2 = b.clone(); // глубокий
|
||||
auto bytes = blocksToBytes(b);
|
||||
auto bytes2 = blocksToBytes(b2);
|
||||
assert(bytes.equal(bytes2));
|
||||
}
|
||||
|
||||
unittest // eq для MMFile (по байтам)
|
||||
{
|
||||
auto data = genCycled(15_000);
|
||||
auto f = MMFile.fromBytes(data);
|
||||
auto f2 = MMFile.fromBytes(data.dup);
|
||||
assert(sameBytes(f, f2));
|
||||
}
|
||||
|
||||
unittest // as_slice
|
||||
{
|
||||
assert((new MMFile).asSlice().length == 0);
|
||||
|
||||
auto data = genCycled(15_000);
|
||||
auto f = MMFile.fromBytes(data);
|
||||
assert(f.asSlice().equal(data));
|
||||
}
|
||||
|
||||
unittest // as_slice_mut
|
||||
{
|
||||
auto empty = new MMFile;
|
||||
assert(empty.asSlice().length == 0);
|
||||
|
||||
auto data = genCycled(15_000);
|
||||
auto f = MMFile.fromBytes(data);
|
||||
assert(f.asSlice()[0] == data[0]);
|
||||
|
||||
auto mut = f.asSliceMut();
|
||||
mut[0] = cast(ubyte) (mut[0] + 1);
|
||||
assert(f.asSlice()[0] == data[0] + 1);
|
||||
}
|
||||
|
||||
unittest // diff_simple → проверяем applyPatch
|
||||
{
|
||||
auto data = cast(ubyte[])"hello world\n";
|
||||
auto data2 = cast(ubyte[])"hello world!\n";
|
||||
auto f = MMFile.fromBytes(data);
|
||||
auto f2 = MMFile.fromBytes(data2);
|
||||
|
||||
auto patch = f.computePatch(f2);
|
||||
auto r = f.applyPatch(patch);
|
||||
assert(r.success);
|
||||
assert(r.patched.asSlice().equal(data2));
|
||||
assert(r.rejected.asSlice().length == 0);
|
||||
}
|
||||
|
||||
unittest // diff_with_mutation
|
||||
{
|
||||
auto f = MMFile.fromBytes(cast(ubyte[])"hello world\n");
|
||||
auto f2 = MMFile.fromBytes(cast(ubyte[])"hello world!\n");
|
||||
|
||||
// 1-й прогон
|
||||
auto p1 = f.computePatch(f2);
|
||||
auto r1 = f.applyPatch(p1);
|
||||
assert(r1.success);
|
||||
assert(r1.patched.asSlice().equal(f2.asSlice()));
|
||||
|
||||
// Меняем первую букву во втором файле
|
||||
auto m = f2.asSliceMut();
|
||||
m[0] = cast(ubyte)'j';
|
||||
|
||||
// 2-й прогон
|
||||
auto p2 = f.computePatch(f2);
|
||||
auto r2 = f.applyPatch(p2);
|
||||
assert(r2.success);
|
||||
assert(r2.patched.asSlice().equal(f2.asSlice()));
|
||||
}
|
||||
|
||||
unittest // merge3_simple (без конфликтов)
|
||||
{
|
||||
auto base = MMFile.fromBytes(cast(ubyte[])"header\nline2\nline3\nline4\nhello world\n");
|
||||
auto a = MMFile.fromBytes(cast(ubyte[])"header\nline2\nline3\nline4\nhello world changed\n");
|
||||
auto b = MMFile.fromBytes(cast(ubyte[])"header_changed\nline2\nline3\nline4\nhello world\n");
|
||||
|
||||
auto lines = appender!(string[])();
|
||||
auto rej = appender!(string[])();
|
||||
|
||||
int acceptCb(const(ubyte)[] s) nothrow
|
||||
{
|
||||
lines.put(cast(string) s.idup);
|
||||
return 0;
|
||||
}
|
||||
int rejectCb(const(ubyte)[] s) nothrow
|
||||
{
|
||||
rej.put(cast(string) s.idup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
base.merge3Raw(a, b, &acceptCb, &rejectCb);
|
||||
|
||||
auto expected = [
|
||||
"header_changed\n",
|
||||
"line2\n",
|
||||
"line3\n",
|
||||
"line4\n",
|
||||
"hello world changed\n",
|
||||
];
|
||||
assert(lines.data.equal(expected));
|
||||
assert(rej.data.length == 0);
|
||||
}
|
||||
|
||||
unittest // merge3_conflicts (есть rejected)
|
||||
{
|
||||
auto base = MMFile.fromBytes(cast(ubyte[])"header\nline2\nline3\nline4\nhello world\n");
|
||||
auto a = MMFile.fromBytes(cast(ubyte[])"header\nline2\nline3\nline4\nhello world changed\n");
|
||||
auto b = MMFile.fromBytes(cast(ubyte[])"header\nline2\nline3\nline4\nhello world also changed\n");
|
||||
|
||||
auto lines = appender!(string[])();
|
||||
auto rej = appender!(string[])();
|
||||
|
||||
int acceptCb(const(ubyte)[] s) nothrow { lines.put(cast(string)s.idup); return 0; }
|
||||
int rejectCb(const(ubyte)[] s) nothrow { rej.put(cast(string)s.idup); return 0; }
|
||||
|
||||
base.merge3Raw(a, b, &acceptCb, &rejectCb);
|
||||
|
||||
assert(lines.data.length > 0);
|
||||
assert(rej.data.length > 0);
|
||||
}
|
||||
|
||||
unittest // merge3_panic_* в Rust → у нас error-ветка через nothrow-коды: вернём -1
|
||||
{
|
||||
auto base = MMFile.fromBytes(cast(ubyte[])"header\nline2\nline3\nline4\nhello world\n");
|
||||
auto a = MMFile.fromBytes(cast(ubyte[])"header\nline2\nline3\nline4\nhello world changed\n");
|
||||
auto b = MMFile.fromBytes(cast(ubyte[])"header_changed\nline2\nline3\nline4\nhello world\n");
|
||||
|
||||
auto lines = appender!(string[])();
|
||||
|
||||
int acceptCb(const(ubyte)[] s) nothrow
|
||||
{
|
||||
static size_t cnt = 0;
|
||||
++cnt;
|
||||
if (cnt > 3) return -1; // сигнализируем об ошибке
|
||||
lines.put(cast(string)s.idup);
|
||||
return 0;
|
||||
}
|
||||
int rejectCb(const(ubyte)[] s) nothrow { return 0; }
|
||||
|
||||
assertThrown!Exception( base.merge3Raw(a, b, &acceptCb, &rejectCb) );
|
||||
}
|
||||
|
||||
unittest // patch_simple (computePatch→applyPatch)
|
||||
{
|
||||
auto base = MMFile.fromBytes(cast(ubyte[])"header\nline2\nline3\nline4\nhello world\n");
|
||||
auto want = MMFile.fromBytes(cast(ubyte[])"header\nline2\nline3\nline4\nhello world changed\n");
|
||||
|
||||
auto patch = base.computePatch(want);
|
||||
auto res = base.applyPatch(patch);
|
||||
|
||||
assert(res.success);
|
||||
assert(sameBytes(res.patched, want));
|
||||
assert(res.rejected.asSlice().length == 0);
|
||||
}
|
||||
|
||||
unittest // patch_reject (искажаем базу перед apply)
|
||||
{
|
||||
auto base = MMFile.fromBytes(cast(ubyte[])"header\nline2\nline3\nline4\nhello world\n");
|
||||
auto want = MMFile.fromBytes(cast(ubyte[])"header changed\nline2\nline3\nline4\nhello world changed\n");
|
||||
|
||||
auto patch = base.computePatch(want);
|
||||
|
||||
auto m = base.asSliceMut();
|
||||
m[0] = cast(ubyte)'b';
|
||||
|
||||
auto res = base.applyPatch(patch);
|
||||
|
||||
assert(!res.success);
|
||||
assert(res.rejected.asSlice().length > 0);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue