libxdiff/source/libxdiff/unittests.d
2025-08-31 18:49:38 +03:00

253 lines
5.6 KiB
D

module libxdiff.unittests;
import std.algorithm : equal;
import std.array : appender;
import std.exception : assertThrown;
import std.range : iota, cycle, take;
import libxdiff.mmfile : MMFile;
import libxdiff.mmblocks : MMBlocks;
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;
}
ubyte[] blocksToBytes(const MMBlocks b)
{
auto tmp = b.clone();
auto mf = MMFile.fromBlocksMoved(tmp);
return (cast(const(ubyte)[]) mf.asSlice()).dup;
}
bool sameBytes(const MMFile a, const MMFile b)
{
return a.asSlice().equal(b.asSlice());
}
unittest
{
auto f = new MMFile;
assert(f.size() == 0);
assert(f.isCompact());
}
unittest
{
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
{
auto data = genCycled(15_000);
auto f = MMFile.fromBytes(data);
assert(f.size() == data.length);
assert(f.isCompact());
assert(f.asSlice().equal(data));
}
unittest
{
auto data = genCycled(15_000);
auto f = MMFile.fromBytes(data);
auto f2 = MMFile.fromBytes(f.asSlice().dup);
assert(sameBytes(f, f2));
}
unittest
{
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
{
auto data = genCycled(15_000);
auto f = MMFile.fromBytes(data);
auto f2 = MMFile.fromBytes(data.dup);
assert(sameBytes(f, f2));
}
unittest
{
assert((new MMFile).asSlice().length == 0);
auto data = genCycled(15_000);
auto f = MMFile.fromBytes(data);
assert(f.asSlice().equal(data));
}
unittest
{
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
{
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
{
auto f = MMFile.fromBytes(cast(ubyte[]) "hello world\n");
auto f2 = MMFile.fromBytes(cast(ubyte[]) "hello world!\n");
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';
auto p2 = f.computePatch(f2);
auto r2 = f.applyPatch(p2);
assert(r2.success);
assert(r2.patched.asSlice().equal(f2.asSlice()));
}
unittest
{
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
{
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
{
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
{
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
{
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);
}