Commit d1a87961 authored by Joey Adams's avatar Joey Adams Committed by Rusty Russell

bdelta: new module for binary diff/patch

parent aa6b7489
../../licenses/BSD-MIT
\ No newline at end of file
#include <string.h>
#include "config.h"
/**
* bdelta - Generate and apply binary deltas
*
* This library takes two strings containing binary data, and produces a
* "patch" that can be applied to the first one to produce the second one.
* It can be used to save bandwidth and disk space when many texts differing
* by a small number of bytes need to be transmitted or stored.
*
* Patches produced by this version of the library can be applied using future
* versions, but not past versions.
*
* bdelta implements the algorithm described in
* An O(ND) Difference Algorithm and Its Variations by Eugene W. Myers.
* Because its memory usage and expected running time are O(N + D^2),
* it works well only when the strings differ by a small number of bytes.
* This implementation stops trying when the strings differ by more than
* 1000 bytes, and falls back to producing a patch that simply emits the new
* string.
*
* Thus, bdelta does not save any space when given two strings that differ by
* more than 1000 bytes. This may be improved in a future version of the
* library.
*
* Example:
* #include <ccan/bdelta/bdelta.h>
* #include <stdio.h>
* #include <stdlib.h>
* #include <string.h>
*
* static void gulp(const char *filename, void **data_out, size_t *size_out);
*
* static int usage(const char *prog)
* {
* fprintf(
* stderr,
* "Usage: %s diff <old> <new> > <patch>\n"
* " %s patch <old> <patch> > <new>\n",
* prog, prog
* );
* return 1;
* }
*
* int main(int argc, char *argv[])
* {
* void *old, *new_, *patch;
* size_t old_size, new_size, patch_size;
* BDELTAcode rc;
*
* if (argc != 4)
* return usage(argv[0]);
*
* if (strcmp(argv[1], "diff") == 0) {
* gulp(argv[2], &old, &old_size);
* gulp(argv[3], &new_, &new_size);
*
* rc = bdelta_diff(old, old_size, new_, new_size, &patch, &patch_size);
* if (rc != BDELTA_OK) {
* bdelta_perror("bdelta_diff", rc);
* return 1;
* }
*
* if (fwrite(patch, 1, patch_size, stdout) != patch_size) {
* perror("stdout");
* return 1;
* }
* } else if (strcmp(argv[1], "patch") == 0) {
* gulp(argv[2], &old, &old_size);
* gulp(argv[3], &patch, &patch_size);
*
* rc = bdelta_patch(old, old_size, patch, patch_size, &new_, &new_size);
* if (rc != BDELTA_OK) {
* bdelta_perror("bdelta_patch", rc);
* return 1;
* }
*
* if (fwrite(new_, 1, new_size, stdout) != new_size) {
* perror("stdout");
* return 1;
* }
* } else {
* return usage(argv[0]);
* }
*
* free(old);
* free(new_);
* free(patch);
* return 0;
* }
*
* static void gulp(const char *filename, void **data_out, size_t *size_out)
* {
* FILE *f = fopen(filename, "rb");
* size_t size = 0;
* size_t alloc = 16;
* char *data = malloc(alloc);
*
* if (f == NULL || data == NULL)
* goto error;
*
* for (;;) {
* size += fread(data + size, 1, alloc - size, f);
* if (size < alloc) {
* if (!feof(f))
* goto error;
* break;
* }
* data = realloc(data, alloc *= 2);
* if (data == NULL)
* goto error;
* }
*
* if (fclose(f) != 0)
* goto error;
*
* *data_out = data;
* *size_out = size;
* return;
*
* error:
* perror(filename);
* exit(EXIT_FAILURE);
* }
*
* Author: Joey Adams <joeyadams3.14159@gmail.com>
* License: MIT
* Version: 0.1.1
*/
int main(int argc, char *argv[])
{
/* Expect exactly one argument */
if (argc != 2)
return 1;
if (strcmp(argv[1], "depends") == 0) {
/* Nothing */
return 0;
}
return 1;
}
This diff is collapsed.
/*
* Copyright (C) 2011 Joseph Adams <joeyadams3.14159@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef CCAN_BDELTA_H
#define CCAN_BDELTA_H
#include <stddef.h>
typedef enum {
BDELTA_OK = 0, /* Operation succeeded. */
BDELTA_MEMORY = 1, /* Memory allocation failed. */
BDELTA_PATCH_INVALID = 2, /* Patch is malformed. */
BDELTA_PATCH_MISMATCH = 3, /* Patch applied to wrong original string. */
/* Internal error codes. These will never be returned by API functions. */
BDELTA_INTERNAL_DMAX_EXCEEDED = -10,
BDELTA_INTERNAL_INPUTS_TOO_LARGE = -11,
} BDELTAcode;
/*
* bdelta_diff - Given two byte strings, generate a "patch" (also a byte string)
* that describes how to transform the old string into the new string.
*
* On success, returns BDELTA_OK, and passes a malloc'd block
* and its size through *patch_out and *patch_size_out.
*
* On failure, returns an error code, and clears *patch_out and *patch_size_out.
*
* Example:
* const char *old = "abcabba";
* const char *new_ = "cbabac";
* void *patch;
* size_t patch_size;
* BDELTAcode rc;
*
* rc = bdelta_diff(old, strlen(old), new_, strlen(new_), &patch, &patch_size);
* if (rc != BDELTA_OK) {
* bdelta_perror("bdelta_diff", rc);
* return;
* }
* ...
* free(patch);
*/
BDELTAcode bdelta_diff(
const void *old, size_t old_size,
const void *new_, size_t new_size,
void **patch_out, size_t *patch_size_out
);
/*
* bdelta_patch - Apply a patch produced by bdelta_diff to the
* old string to recover the new string.
*
* On success, returns BDELTA_OK, and passes a malloc'd block
* and its size through *new_out and *new_size_out.
*
* On failure, returns an error code, and clears *new_out and *new_size_out.
*
* Example:
* const char *old = "abcabba";
* void *new_;
* size_t new_size;
* BDELTAcode rc;
*
* rc = bdelta_patch(old, strlen(old), patch, patch_size, &new_, &new_size);
* if (rc != BDELTA_OK) {
* bdelta_perror("bdelta_patch", rc);
* return;
* }
* fwrite(new_, 1, new_size, stdout);
* putchar('\n');
* free(new_);
*/
BDELTAcode bdelta_patch(
const void *old, size_t old_size,
const void *patch, size_t patch_size,
void **new_out, size_t *new_size_out
);
/*
* bdelta_strerror - Return a string describing a bdelta error code.
*/
const char *bdelta_strerror(BDELTAcode code);
/*
* bdelta_perror - Print a bdelta error message to stderr.
*
* This function handles @s the same way perror does.
*/
void bdelta_perror(const char *s, BDELTAcode code);
#endif
#include <ccan/bdelta/bdelta.h>
#include <ccan/bdelta/bdelta.c>
#include <ccan/tap/tap.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/*
* Finds a pseudorandom 32-bit number from 0 to 2^32-1 .
* Uses the BCPL linear congruential generator method.
*
* Used instead of system RNG to ensure tests are consistent.
*/
static uint32_t rand32(void)
{
#if 0
/*
* Tests should be run with a different random function
* from time to time. I've found that the method below
* sometimes behaves poorly for testing purposes.
* For example, rand32() % N might only return even numbers.
*/
assert(RAND_MAX == 2147483647);
return ((random() & 0xFFFF) << 16) | (random() & 0xFFFF);
#else
static uint32_t rand32_state = 0;
rand32_state *= (uint32_t)0x7FF8A3ED;
rand32_state += (uint32_t)0x2AA01D31;
return rand32_state;
#endif
}
#define RSTRING_OK 0 /* Operation succeeded */
#define RSTRING_MEMORY 1 /* Memory allocation failed */
#define RSTRING_ZERO_CARDINALITY 2 /* Cardinality of byte range is zero */
#define RSTRING_RANGE_OVERLAP 3 /* Byte range contains overlapping characters */
struct rstring_byte_range {
unsigned int cardinality;
unsigned int multiplier;
unsigned int offset;
};
static int check_range(const struct rstring_byte_range *range)
{
unsigned int i;
unsigned int c;
char set[256];
if (range == NULL)
return RSTRING_OK;
if (range->cardinality == 0)
return RSTRING_ZERO_CARDINALITY;
memset(set, 0, sizeof(set));
c = range->offset & 0xFF;
for (i = 0; i < range->cardinality; i++) {
if (set[c])
return RSTRING_RANGE_OVERLAP;
set[c] = 1;
c += range->multiplier;
c &= 0xFF;
}
return RSTRING_OK;
}
/*
* Generate a pseudorandom string of the given length,
* writing it into a user-supplied buffer.
*/
static uint8_t *random_string_into(uint8_t *str, uint32_t size, const struct rstring_byte_range *range)
{
uint32_t i;
uint32_t r;
if (range == NULL) {
for (i = 0; size - i >= 4; ) {
r = rand32();
str[i++] = r & 0xFF;
r >>= 8;
str[i++] = r & 0xFF;
r >>= 8;
str[i++] = r & 0xFF;
r >>= 8;
str[i++] = r & 0xFF;
}
if (i < size) {
r = rand32();
do {
str[i++] = r & 0xFF;
r >>= 8;
} while (i < size);
}
} else {
for (i = 0; i < size; )
str[i++] = ((rand32() % range->cardinality) * range->multiplier + range->offset) & 0xFF;
}
return str;
}
/*
* Generate a pseudorandom string of the given length.
*/
static uint8_t *random_string(uint32_t size, const struct rstring_byte_range *range)
{
uint8_t *str;
if (check_range(range) != RSTRING_OK) {
fprintf(stderr, "Invalid byte range for random string generation\n");
exit(EXIT_FAILURE);
}
str = malloc(size);
if (str == NULL)
return NULL;
return random_string_into(str, size, range);
}
/*
* Generate two byte strings:
*
* An "old" string, of length @old_size
* A "new" string, differing from "old" by at most @diff_size bytes
* (where modifying a character is considered one change).
*
* Returns RSTRING_OK on success, RSTRING_MEMORY if memory allocation fails.
*/
static int random_string_pair(
uint32_t old_size, uint32_t diff_size, const struct rstring_byte_range *range,
uint8_t **old_out, uint8_t **new_out, uint32_t *new_size_out)
{
uint8_t *old;
uint8_t *new_;
uint8_t *nptr;
uint32_t new_size;
uint32_t i;
uint32_t j;
/* insertions[i] is the number of characters to insert before position i. */
uint32_t *insertions;
/*
* changes[i] indicates what to do to the character at position i:
*
* 0: Leave it as-is.
* 1: Delete it.
* 2: Change it to another byte (possibly the same byte).
*/
char *changes;
/* String of new bytes to use for insertions and changes. */
uint8_t *new_bytes;
uint8_t *new_bytes_ptr;
uint32_t new_bytes_size;
old = random_string(old_size, range);
if (old == NULL)
goto oom0;
insertions = calloc(old_size + 1, sizeof(*insertions));
if (insertions == NULL)
goto oom1;
changes = calloc(old_size, sizeof(*changes));
if (changes == NULL)
goto oom2;
/*
* Generate a collection of edits, which will be
* applied to the old string "simultaneously".
*/
new_size = old_size;
new_bytes_size = 0;
for (i = 0; i < diff_size; i++) {
uint32_t pos;
switch (rand32() % 3) {
case 0: /* Insert a byte. */
pos = rand32() % (old_size + 1);
insertions[pos]++;
new_size++;
new_bytes_size++;
break;
case 1: /* Delete a byte. */
pos = rand32() % old_size;
if (changes[pos]) {
/*
* This character is already deleted or changed.
* Do something else instead.
*
* For the observative: i will overflow if it's 0.
* However, overflow of unsigned integers is well-defined
* by the C standard. i will wrap around to UINT32_MAX,
* then the for-loop increment above will wrap it back to 0.
*/
i--;
} else {
changes[pos] = 1;
new_size--;
}
break;
default: /* Change a byte. */
pos = rand32() % old_size;
if (changes[pos]) {
/*
* This character is already deleted or changed.
* Do something else instead.
*/
i--;
} else {
changes[pos] = 2;
new_bytes_size++;
}
break;
}
}
new_bytes = malloc(new_bytes_size);
if (new_bytes == NULL)
goto oom3;
random_string_into(new_bytes, new_bytes_size, range);
new_ = malloc(new_size);
if (new_ == NULL)
goto oom4;
/* Apply the insertions and changes generated above. */
nptr = new_;
new_bytes_ptr = new_bytes;
for (i = 0; i < old_size; i++) {
for (j = 0; j < insertions[i]; j++)
*nptr++ = *new_bytes_ptr++;
switch (changes[i]) {
case 0: /* No change */
*nptr++ = old[i];
break;
case 1: /* Delete */
break;
default: /* Change value */
*nptr++ = *new_bytes_ptr++;
break;
}
}
for (j = 0; j < insertions[i]; j++)
*nptr++ = *new_bytes_ptr++;
assert((size_t)(nptr - new_) == new_size);
assert((size_t)(new_bytes_ptr - new_bytes) == new_bytes_size);
free(new_bytes);
free(changes);
free(insertions);
if (old_out)
*old_out = old;
else
free(old);
if (new_out)
*new_out = new_;
else
free(new_);
if (new_size_out)
*new_size_out = new_size;
return RSTRING_OK;
oom4:
free(new_bytes);
oom3:
free(changes);
oom2:
free(insertions);
oom1:
free(old);
oom0:
if (old_out)
*old_out = NULL;
if (new_out)
*new_out = NULL;
if (new_size_out)
*new_size_out = 0;
return RSTRING_MEMORY;
}
#include "common.h"
/*
* Note that bdelta_diff verifies the patch before returning it (except for
* when it returns a PT_LITERAL patch, as its correctness is easy to prove).
* Only the trivial tests check the result explicitly using bdiff_patch.
*/
static int test_random(
uint32_t old_size, uint32_t diff_size,
unsigned int cardinality, unsigned int multiplier, unsigned int offset)
{
struct rstring_byte_range range;
uint8_t *old;
uint8_t *new_;
uint32_t new_size;
BDELTAcode rc;
range.cardinality = cardinality;
range.multiplier = multiplier;
range.offset = offset;
if (random_string_pair(old_size, diff_size, cardinality == 0 ? NULL : &range,
&old, &new_, &new_size) != RSTRING_OK)
{
fprintf(stderr, "Error generating random string pair\n");
exit(EXIT_FAILURE);
}
rc = bdelta_diff(old, old_size, new_, new_size, NULL, NULL);
if (rc != BDELTA_OK) {
bdelta_perror("bdelta_diff", rc);
return 0;
}
free(new_);
free(old);
return 1;
}
int main(void)
{
int i;
int count = 25;
plan_tests(count * 14);
for (i = 0; i < count; i++)
ok1(test_random(100, 10, 0, 0, 0));
for (i = 0; i < count; i++)
ok1(test_random(100, rand32() % 200, 0, 0, 0));
for (i = 0; i < count; i++)
ok1(test_random(1000, rand32() % 200, 0, 0, 0));
for (i = 0; i < count; i++)
ok1(test_random(1000, rand32() % 2000, 0, 0, 0));
for (i = 0; i < count; i++)
ok1(test_random(10000, rand32() % 200, 0, 0, 0));
for (i = 0; i < count; i++)
ok1(test_random(10000, rand32() % 2000, 0, 0, 0));
for (i = 0; i < count; i++)
ok1(test_random(rand32() % 20000, rand32() % 20000, 0, 0, 0));
/* Low-cardinality tests */
for (i = 0; i < count; i++)
ok1(test_random(100, 10, rand32() % 20 + 1, 1, i));
for (i = 0; i < count; i++)
ok1(test_random(100, rand32() % 200, rand32() % 20 + 1, 1, i));
for (i = 0; i < count; i++)
ok1(test_random(1000, rand32() % 200, rand32() % 20 + 1, 1, i));
for (i = 0; i < count; i++)
ok1(test_random(1000, rand32() % 2000, rand32() % 20 + 1, 1, i));
for (i = 0; i < count; i++)
ok1(test_random(10000, rand32() % 200, rand32() % 20 + 1, 1, i));
for (i = 0; i < count; i++)
ok1(test_random(10000, rand32() % 2000, rand32() % 20 + 1, 1, i));
for (i = 0; i < count; i++)
ok1(test_random(rand32() % 20000, rand32() % 20000, rand32() % 20 + 1, 1, i));
return exit_status();
}
#include "common.h"
#define is_digit(c) ((c) >= '0' && (c) <= '9')
static uint32_t parse_int(const char **sp)
{
const char *s = *sp;
uint32_t n = 0;
while (is_digit(*s)) {
n *= 10;
n += *s++ - '0';
}
*sp = s;
return n;
}
static const char *op_name(int op)
{
switch (op) {
case OP_COPY:
return "copy";
case OP_SKIP:
return "skip";
case OP_INSERT:
return "insert";
default:
return "<invalid opcode>";
}
}
static const char *verify_csi32(
const unsigned char *patch_start, const unsigned char *patch_end,
const char *expected)
{
const unsigned char *p = patch_start;
if (p >= patch_end)
return "Patch type byte missing";
if (*p++ != PT_CSI32)
return "Patch type byte is not PT_CSI32";
for (;;) {
int patch_op;
uint32_t patch_size;
int expected_op;
uint32_t expected_size;
while (*expected == ' ')
expected++;
if (*expected == '\0')
break;
if (!csi32_parse_op(&p, patch_end, &patch_op, &patch_size)) {
if (p == patch_end)
return "Patch shorter than expected.";
else
return "Patch contains invalid CSI-32";
}
switch (*expected) {
case 'c':
expected_op = OP_COPY;
break;
case 's':
expected_op = OP_SKIP;
break;
case 'i':
expected_op = OP_INSERT;
break;
default:
diag("verify_csi32: Unexpected character %c", *expected);
return "Syntax error in expected difference";
}
expected++;
while (*expected == ' ')
expected++;
if (patch_op != expected_op) {
diag("verify_csi32: Expected %s, but next op is %s %lu",
op_name(expected_op), op_name(patch_op), (unsigned long)patch_size);
return "Operation mismatch";
}
if (expected_op == OP_COPY || expected_op == OP_SKIP) {
if (!is_digit(*expected)) {
diag("verify_csi32: Expected size after %s instruction",
op_name(expected_op));
return "Syntax error in expected difference";
}
expected_size = parse_int(&expected);
if (patch_size != expected_size) {
diag("verify_csi32: Expected %s %lu, but patch says %s %lu",
op_name(expected_op), (unsigned long)expected_size,
op_name(expected_op), (unsigned long)patch_size);
return "Operation size mismatch";
}
} else {
if (*expected++ != '\'') {
diag("verify_csi32: Expected single-quoted string after insert instruction");
return "Syntax error in expected difference";
}
for (expected_size = 0; ; expected_size++) {
unsigned char c;
if (*expected == '\'' && *(expected + 1) == '\'') {
c = '\'';
expected += 2;
} else if (*expected == '\'') {
expected++;
break;
} else if (*expected == '\0') {
diag("verify_csi32: Missing end quote");
return "Syntax error in expected difference";
} else {
c = *expected++;
}
if (!(p < patch_end && *p++ == c))
return "Insertion string mismatch";
}
if (patch_size != expected_size)
return "Insertion string mismatch";
}
}
if (p != patch_end)
return "Patch longer than expected.";
return NULL;
}
static void test_myers(const char *old, const char *new_, const char *expected_difference)
{
SB patch;
BDELTAcode rc;
const char *verify_msg;
if (sb_init(&patch) != 0) {
fail("Out of memory");
return;
}
rc = diff_myers(old, strlen(old), new_, strlen(new_), &patch);
if (rc != BDELTA_OK) {
fail("test_myers(%s, %s, %s): diff_myers failed: %s", old, new_, expected_difference, bdelta_strerror(rc));
sb_discard(&patch, NULL, NULL);
return;
}
verify_msg = verify_csi32(patch.start, patch.cur, expected_difference);
sb_discard(&patch, NULL, NULL);
if (verify_msg != NULL) {
fail("test_myers(%s, %s, %s): %s", old, new_, expected_difference, verify_msg);
return;
}
pass("test_myers(%s, %s, %s)", old, new_, expected_difference);
}
int main(void)
{
(void) random_string_pair;
plan_tests(17);
test_myers("abcabba", "cbabac", "s2 c1 i'b' c2 s1 c1 i'c'");
test_myers("abcdefg", "abcdefg", "c7");
test_myers("abcdefg", "abcde", "c5");
test_myers("abcdefg", "abcdefga", "c7 i'a'");
test_myers("abbbbbb", "bbbbbb", "s1 c6");
test_myers("abbbbbb", "bbbbbbb", "s1 c6 i'b'");
test_myers("bbbbbb", "abbbbbb", "i'a' c6");
test_myers("bbbbbbb", "abbbbbb", "i'a' c6");
test_myers("bbbbbb", "abbbbbbb", "i'a' c6 i'b'");
test_myers("ac", "ba", "i'b' c1");
test_myers("ac", "bc", "s1 i'b' c1");
test_myers("abcd", "cabd", "i'c' c2 s1 c1");
test_myers("", "abc", "i'abc'");
test_myers("abc", "", "");
test_myers("abcd", "d", "s3 c1");
test_myers("", "", "");
/*
* In the event the strings have no characters in common, diff_myers will
* emit a skip of all the characters in the old string. Although it is
* unnecessary, it is tricky to get rid of. bdelta_diff will discard the
* patch anyway, because it's bigger than a literal.
*/
test_myers("aaaaaaa", "bbbbbbb", "s7 i'bbbbbbb'");
return exit_status();
}
#include <ccan/bdelta/bdelta.h>
#include <ccan/bdelta/bdelta.c>
#include <ccan/tap/tap.h>
static int test_trivial(const char *old, const char *new_)
{
void *patch;
size_t patch_size;
BDELTAcode rc;
void *new2;
size_t new2_size;
rc = bdelta_diff(old, strlen(old), new_, strlen(new_), &patch, &patch_size);
if (rc != BDELTA_OK) {
bdelta_perror("bdelta_diff", rc);
return 0;
}
if (patch_size > strlen(new_) + 1) {
fprintf(stderr, "bdelta_diff produced a patch larger than a simple literal emitting the new string.\n");
return 0;
}
rc = bdelta_patch(old, strlen(old), patch, patch_size, &new2, &new2_size);
if (rc != BDELTA_OK) {
bdelta_perror("bdelta_patch", rc);
return 0;
}
if (new2_size != strlen(new_) || strcmp(new2, new_) != 0) {
fprintf(stderr, "patch(old, diff(old, new)) != new\n");
return 0;
}
/* Make sure bdelta_diff properly discards unwanted return values. */
rc = bdelta_diff(old, strlen(old), new_, strlen(new_), NULL, NULL);
if (rc != BDELTA_OK) {
bdelta_perror("bdelta_diff (second time)", rc);
return 0;
}
free(new2);
free(patch);
return 1;
}
int main(void)
{
plan_tests(13);
ok1(test_trivial("abcabba", "cbabac"));
ok1(test_trivial("aaabbbcdaabcc", "aaabbcdaabeca"));
ok1(test_trivial("aaaaaaaa", "bbbbbbbb"));
ok1(test_trivial("aaaaaaaa", ""));
ok1(test_trivial("", "bbbbbbbb"));
ok1(test_trivial("", ""));
ok1(test_trivial("aaaaaaaa", "aaaaaaaabbbbbbbb"));
ok1(test_trivial("aaaaaaaa", "bbbbbbbbaaaaaaaa"));
ok1(test_trivial("aaaaaaaabbbbbbbb", "aaaaaaaa"));
ok1(test_trivial("aaaaaaaabbbbbbbb", "bbbbbbbb"));
ok1(test_trivial("aaaaaaaabbbbbbbb", "bbbbbbbb"));
ok1(test_trivial("abababababababab", "babababababababa"));
ok1(test_trivial("aababcabcdabcde", "aababcabcdabcde"));
return exit_status();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment