diff options
author | josh.macdonald <jmacd@users.noreply.github.com> | 2008-07-10 04:10:18 +0000 |
---|---|---|
committer | josh.macdonald <jmacd@users.noreply.github.com> | 2008-07-10 04:10:18 +0000 |
commit | cac3ff51853873ef6aa0d5b7e6d9d720218f38f6 (patch) | |
tree | c2a47a05a6acbde359d7971e63e863ee46a7d74c /xdelta3 | |
parent | bd594c150533173d0340df8204824c9d6f00966b (diff) |
Adds a test for the fix for issue 70. The new regression test
framework's ability to craft specific inputs is very handy.
Diffstat (limited to 'xdelta3')
-rwxr-xr-x | xdelta3/testing/Makefile | 4 | ||||
-rw-r--r-- | xdelta3/testing/file.h | 12 | ||||
-rw-r--r-- | xdelta3/testing/modify.h | 27 | ||||
-rw-r--r-- | xdelta3/testing/regtest.cc | 115 | ||||
-rw-r--r-- | xdelta3/xdelta3.c | 22 |
5 files changed, 123 insertions, 57 deletions
diff --git a/xdelta3/testing/Makefile b/xdelta3/testing/Makefile index 814f477..df9854d 100755 --- a/xdelta3/testing/Makefile +++ b/xdelta3/testing/Makefile | |||
@@ -1,5 +1,5 @@ | |||
1 | #CFLAGS = -g -Wall -I.. -DXD3_DEBUG=1 -DNDEBUG=0 | 1 | CFLAGS = -g -Wall -I.. -DXD3_DEBUG=1 -DNDEBUG=0 |
2 | CFLAGS = -g -Wall -I.. -DXD3_DEBUG=2 -DNDEBUG=0 | 2 | #CFLAGS = -g -Wall -I.. -DXD3_DEBUG=2 -DNDEBUG=0 |
3 | #CFLAGS = -O2 -Wall -I.. -DXD3_DEBUG=0 -DNDEBUG=1 | 3 | #CFLAGS = -O2 -Wall -I.. -DXD3_DEBUG=0 -DNDEBUG=1 |
4 | 4 | ||
5 | DEPS = ../*.h ../*.c *.cc *.h | 5 | DEPS = ../*.h ../*.c *.cc *.h |
diff --git a/xdelta3/testing/file.h b/xdelta3/testing/file.h index d38a720..4153e5f 100644 --- a/xdelta3/testing/file.h +++ b/xdelta3/testing/file.h | |||
@@ -53,6 +53,18 @@ class FileSpec { | |||
53 | FileSpec *modify) const { | 53 | FileSpec *modify) const { |
54 | modify->Reset(); | 54 | modify->Reset(); |
55 | mutator.Mutate(&modify->table_, &table_, rand_); | 55 | mutator.Mutate(&modify->table_, &table_, rand_); |
56 | modify->CheckSegments(); | ||
57 | } | ||
58 | |||
59 | void CheckSegments() const { | ||
60 | for (SegmentMap::const_iterator iter(table_.begin()); | ||
61 | iter != table_.end(); ) { | ||
62 | SegmentMap::const_iterator iter0(iter++); | ||
63 | if (iter == table_.end()) { | ||
64 | break; | ||
65 | } | ||
66 | CHECK_EQ(iter0->first + iter0->second.Size(), iter->first); | ||
67 | } | ||
56 | } | 68 | } |
57 | 69 | ||
58 | void Reset() { | 70 | void Reset() { |
diff --git a/xdelta3/testing/modify.h b/xdelta3/testing/modify.h index 8b81baf..67cccd9 100644 --- a/xdelta3/testing/modify.h +++ b/xdelta3/testing/modify.h | |||
@@ -17,6 +17,7 @@ public: | |||
17 | DELETE = 3, | 17 | DELETE = 3, |
18 | MOVE = 4, | 18 | MOVE = 4, |
19 | COPY = 5, | 19 | COPY = 5, |
20 | OVERWRITE = 6, | ||
20 | }; | 21 | }; |
21 | 22 | ||
22 | // Constructor for modify, add, delete. | 23 | // Constructor for modify, add, delete. |
@@ -25,7 +26,7 @@ public: | |||
25 | size(size), | 26 | size(size), |
26 | addr1(addr1), | 27 | addr1(addr1), |
27 | insert(NULL) { | 28 | insert(NULL) { |
28 | CHECK(kind != MOVE && kind != COPY); | 29 | CHECK(kind != MOVE && kind != COPY && kind != OVERWRITE); |
29 | } | 30 | } |
30 | 31 | ||
31 | // Constructor for modify, add w/ provided data. | 32 | // Constructor for modify, add w/ provided data. |
@@ -34,7 +35,7 @@ public: | |||
34 | size(size), | 35 | size(size), |
35 | addr1(addr1), | 36 | addr1(addr1), |
36 | insert(insert) { | 37 | insert(insert) { |
37 | CHECK(kind != MOVE && kind != COPY); | 38 | CHECK(kind != MOVE && kind != COPY && kind != OVERWRITE); |
38 | } | 39 | } |
39 | 40 | ||
40 | // Constructor for move | 41 | // Constructor for move |
@@ -44,7 +45,7 @@ public: | |||
44 | addr1(addr1), | 45 | addr1(addr1), |
45 | addr2(addr2), | 46 | addr2(addr2), |
46 | insert(NULL) { | 47 | insert(NULL) { |
47 | CHECK(kind == MOVE || kind == COPY); | 48 | CHECK(kind == MOVE || kind == COPY || kind == OVERWRITE); |
48 | } | 49 | } |
49 | 50 | ||
50 | Kind kind; | 51 | Kind kind; |
@@ -92,6 +93,11 @@ public: | |||
92 | const SegmentMap *source_table, | 93 | const SegmentMap *source_table, |
93 | MTRandom *rand); | 94 | MTRandom *rand); |
94 | 95 | ||
96 | static void OverwriteChange(const Change &ch, | ||
97 | SegmentMap *table, | ||
98 | const SegmentMap *source_table, | ||
99 | MTRandom *rand); | ||
100 | |||
95 | static void CopyChange(const Change &ch, | 101 | static void CopyChange(const Change &ch, |
96 | SegmentMap *table, | 102 | SegmentMap *table, |
97 | const SegmentMap *source_table, | 103 | const SegmentMap *source_table, |
@@ -124,6 +130,7 @@ void ChangeListMutator::Mutate(SegmentMap *table, | |||
124 | 130 | ||
125 | for (ChangeList::const_iterator iter(cl_.begin()); iter != cl_.end(); ++iter) { | 131 | for (ChangeList::const_iterator iter(cl_.begin()); iter != cl_.end(); ++iter) { |
126 | const Change &ch = *iter; | 132 | const Change &ch = *iter; |
133 | tmp.clear(); | ||
127 | Mutate(ch, &tmp, source_table, rand); | 134 | Mutate(ch, &tmp, source_table, rand); |
128 | tmp.swap(*table); | 135 | tmp.swap(*table); |
129 | source_table = table; | 136 | source_table = table; |
@@ -150,6 +157,9 @@ void ChangeListMutator::Mutate(const Change &ch, | |||
150 | case Change::MOVE: | 157 | case Change::MOVE: |
151 | MoveChange(ch, table, source_table, rand); | 158 | MoveChange(ch, table, source_table, rand); |
152 | break; | 159 | break; |
160 | case Change::OVERWRITE: | ||
161 | OverwriteChange(ch, table, source_table, rand); | ||
162 | break; | ||
153 | } | 163 | } |
154 | } | 164 | } |
155 | 165 | ||
@@ -311,6 +321,17 @@ void ChangeListMutator::MoveChange(const Change &ch, | |||
311 | DeleteChange(d, table, &tmp, rand); | 321 | DeleteChange(d, table, &tmp, rand); |
312 | } | 322 | } |
313 | 323 | ||
324 | void ChangeListMutator::OverwriteChange(const Change &ch, | ||
325 | SegmentMap *table, | ||
326 | const SegmentMap *source_table, | ||
327 | MTRandom *rand) { | ||
328 | SegmentMap tmp; | ||
329 | CHECK_NE(ch.addr1, ch.addr2); | ||
330 | CopyChange(ch, &tmp, source_table, rand); | ||
331 | Change d(Change::DELETE, ch.size, ch.addr2 + ch.size); | ||
332 | DeleteChange(d, table, &tmp, rand); | ||
333 | } | ||
334 | |||
314 | void ChangeListMutator::CopyChange(const Change &ch, | 335 | void ChangeListMutator::CopyChange(const Change &ch, |
315 | SegmentMap *table, | 336 | SegmentMap *table, |
316 | const SegmentMap *source_table, | 337 | const SegmentMap *source_table, |
diff --git a/xdelta3/testing/regtest.cc b/xdelta3/testing/regtest.cc index fe4983b..d615196 100644 --- a/xdelta3/testing/regtest.cc +++ b/xdelta3/testing/regtest.cc | |||
@@ -29,6 +29,7 @@ struct TestOptions { | |||
29 | size_t large_cksum_size; | 29 | size_t large_cksum_size; |
30 | }; | 30 | }; |
31 | 31 | ||
32 | // TODO! the smatcher setup isn't working, | ||
32 | void InMemoryEncodeDecode(const TestOptions &options, | 33 | void InMemoryEncodeDecode(const TestOptions &options, |
33 | FileSpec &source_file, | 34 | FileSpec &source_file, |
34 | FileSpec &target_file, | 35 | FileSpec &target_file, |
@@ -173,7 +174,9 @@ void InMemoryEncodeDecode(const TestOptions &options, | |||
173 | } | 174 | } |
174 | } | 175 | } |
175 | 176 | ||
176 | xd3_close_stream(&encode_stream); | 177 | CHECK_EQ(0, xd3_close_stream(&decode_stream)); |
178 | CHECK_EQ(0, xd3_close_stream(&encode_stream)); | ||
179 | xd3_free_stream(&encode_stream); | ||
177 | xd3_free_stream(&decode_stream); | 180 | xd3_free_stream(&decode_stream); |
178 | } | 181 | } |
179 | 182 | ||
@@ -477,6 +480,7 @@ void TestMoveMutator() { | |||
477 | } | 480 | } |
478 | 481 | ||
479 | void FindCksumCollision() { | 482 | void FindCksumCollision() { |
483 | // TODO! This is not being used. | ||
480 | if (golden_cksum_bytes[0] != 0) { | 484 | if (golden_cksum_bytes[0] != 0) { |
481 | CHECK(memcmp(golden_cksum_bytes, collision_cksum_bytes, CKSUM_SIZE) != 0); | 485 | CHECK(memcmp(golden_cksum_bytes, collision_cksum_bytes, CKSUM_SIZE) != 0); |
482 | CHECK_EQ(xd3_lcksum(golden_cksum_bytes, CKSUM_SIZE), | 486 | CHECK_EQ(xd3_lcksum(golden_cksum_bytes, CKSUM_SIZE), |
@@ -516,56 +520,80 @@ void FindCksumCollision() { | |||
516 | b2.Print(); | 520 | b2.Print(); |
517 | } | 521 | } |
518 | 522 | ||
519 | void TestNonBlockingCollisionProgress() { | 523 | void TestOverwriteMutator() { |
520 | MTRandom rand; | 524 | MTRandom rand; |
521 | FileSpec spec0(&rand); | 525 | FileSpec spec0(&rand); |
522 | FileSpec spec1(&rand); | 526 | FileSpec spec1(&rand); |
523 | FileSpec spec2(&rand); | ||
524 | TestOptions options; | 527 | TestOptions options; |
525 | 528 | ||
526 | FindCksumCollision(); | 529 | spec0.GenerateFixedSize(Constants::BLOCK_SIZE); |
527 | 530 | ||
528 | spec0.GenerateFixedSize(Constants::BLOCK_SIZE * 2); | 531 | ChangeList cl1; |
532 | cl1.push_back(Change(Change::OVERWRITE, 10, 0, 20)); | ||
533 | spec0.ModifyTo(ChangeListMutator(cl1), &spec1); | ||
534 | CHECK_EQ(spec0.Size(), spec1.Size()); | ||
529 | 535 | ||
530 | Segment gcksum(CKSUM_SIZE, golden_cksum_bytes); | 536 | Block b0, b1; |
531 | Segment ccksum(CKSUM_SIZE, collision_cksum_bytes); | 537 | BlockIterator(spec0).Get(&b0); |
538 | BlockIterator(spec1).Get(&b1); | ||
539 | |||
540 | CHECK(memcmp(b0.Data(), b1.Data() + 20, 10) == 0); | ||
541 | CHECK(memcmp(b0.Data(), b1.Data(), 20) == 0); | ||
542 | CHECK(memcmp(b0.Data() + 30, b1.Data() + 30, Constants::BLOCK_SIZE - 30) == 0); | ||
532 | 543 | ||
533 | uint32_t v1 = xd3_lcksum(golden_cksum_bytes, CKSUM_SIZE); | 544 | cl1.clear(); |
534 | uint32_t v2 = xd3_lcksum(collision_cksum_bytes, CKSUM_SIZE); | 545 | cl1.push_back(Change(Change::OVERWRITE, 10, 20, (xoff_t)0)); |
535 | CHECK_EQ(v1, v2); | 546 | spec0.ModifyTo(ChangeListMutator(cl1), &spec1); |
547 | CHECK_EQ(spec0.Size(), spec1.Size()); | ||
536 | 548 | ||
537 | // TODO! the smatcher setup isn't working, the default is 9/3 | 549 | BlockIterator(spec0).Get(&b0); |
550 | BlockIterator(spec1).Get(&b1); | ||
551 | |||
552 | CHECK(memcmp(b0.Data() + 20, b1.Data(), 10) == 0); | ||
553 | CHECK(memcmp(b0.Data() + 10, b1.Data() + 10, Constants::BLOCK_SIZE - 10) == 0); | ||
554 | } | ||
538 | 555 | ||
539 | // Place the collision just before the 1st block end, place the | 556 | void TestNonBlockingProgress() { |
540 | // matching bytes at the start of the next block. Note that | 557 | MTRandom rand; |
541 | // source checksums are computed in reverse from the end of the | 558 | FileSpec spec0(&rand); |
542 | // block, so placing the collision at the end of the block works. | 559 | FileSpec spec1(&rand); |
560 | FileSpec spec2(&rand); | ||
561 | TestOptions options; | ||
543 | 562 | ||
544 | xoff_t false_offset = Constants::BLOCK_SIZE - CKSUM_SIZE; | 563 | // TODO! this test assumes block_size == 128 |
545 | xoff_t modify_offset = Constants::BLOCK_SIZE + 20; | ||
546 | 564 | ||
547 | // Fixed content for a backward match prior to the collision, which | 565 | spec0.GenerateFixedSize(Constants::BLOCK_SIZE * 2); |
548 | // makes the total match length long enough for the min_match. | ||
549 | Segment preamble(20, 0xabcd); | ||
550 | 566 | ||
551 | Change c1(Change::MODIFY, CKSUM_SIZE, false_offset, &gcksum); | 567 | // This is a lazy target match |
552 | Change c2(Change::MODIFY, CKSUM_SIZE, modify_offset, &ccksum); | 568 | Change ct(Change::OVERWRITE, 22, |
569 | Constants::BLOCK_SIZE + 50, | ||
570 | Constants::BLOCK_SIZE + 20); | ||
553 | 571 | ||
554 | Change cp1(Change::MODIFY, 20, false_offset - 20, &preamble); | 572 | // This is a source match just after the block boundary, shorter |
555 | Change cp2(Change::MODIFY, 20, modify_offset - 20, &preamble); | 573 | // than the lazy target match. |
574 | Change cs1(Change::OVERWRITE, 16, | ||
575 | Constants::BLOCK_SIZE + 51, | ||
576 | Constants::BLOCK_SIZE - 1); | ||
556 | 577 | ||
557 | ChangeList cl1; | 578 | // This overwrites the original source bytes. |
558 | ChangeList cl2; | 579 | Change cs2(Change::MODIFY, 108, |
559 | cl1.push_back(c1); | 580 | Constants::BLOCK_SIZE + 20); |
560 | cl1.push_back(cp1); | ||
561 | cl2.push_back(c2); | ||
562 | cl2.push_back(cp2); | ||
563 | 581 | ||
564 | spec0.ModifyTo(ChangeListMutator(cl1), &spec1); | 582 | // This changes the first blocks |
565 | spec0.ModifyTo(ChangeListMutator(cl2), &spec2); | 583 | Change c1st(Change::MODIFY, Constants::BLOCK_SIZE - 2, 0); |
584 | |||
585 | ChangeList csl; | ||
586 | csl.push_back(cs1); | ||
587 | csl.push_back(cs2); | ||
588 | csl.push_back(c1st); | ||
589 | |||
590 | spec0.ModifyTo(ChangeListMutator(csl), &spec1); | ||
591 | |||
592 | ChangeList ctl; | ||
593 | ctl.push_back(ct); | ||
594 | ctl.push_back(c1st); | ||
566 | 595 | ||
567 | spec1.Print(); | 596 | spec0.ModifyTo(ChangeListMutator(ctl), &spec2); |
568 | spec2.Print(); | ||
569 | 597 | ||
570 | InMemoryEncodeDecode(options, spec1, spec2, NULL); | 598 | InMemoryEncodeDecode(options, spec1, spec2, NULL); |
571 | } | 599 | } |
@@ -574,15 +602,16 @@ void TestNonBlockingCollisionProgress() { | |||
574 | 602 | ||
575 | int main(int argc, char **argv) { | 603 | int main(int argc, char **argv) { |
576 | #define TEST(x) cerr << #x << "..." << endl; x() | 604 | #define TEST(x) cerr << #x << "..." << endl; x() |
577 | // TEST(TestRandomNumbers); | 605 | TEST(TestRandomNumbers); |
578 | // TEST(TestRandomFile); | 606 | TEST(TestRandomFile); |
579 | // TEST(TestFirstByte); | 607 | TEST(TestFirstByte); |
580 | // TEST(TestModifyMutator); | 608 | TEST(TestModifyMutator); |
581 | // TEST(TestAddMutator); | 609 | TEST(TestAddMutator); |
582 | // TEST(TestDeleteMutator); | 610 | TEST(TestDeleteMutator); |
583 | // TEST(TestCopyMutator); | 611 | TEST(TestCopyMutator); |
584 | // TEST(TestMoveMutator); | 612 | TEST(TestMoveMutator); |
585 | TEST(TestNonBlockingCollisionProgress); | 613 | TEST(TestOverwriteMutator); |
614 | TEST(TestNonBlockingProgress); | ||
586 | return 0; | 615 | return 0; |
587 | } | 616 | } |
588 | 617 | ||
diff --git a/xdelta3/xdelta3.c b/xdelta3/xdelta3.c index c2061a0..bdf02af 100644 --- a/xdelta3/xdelta3.c +++ b/xdelta3/xdelta3.c | |||
@@ -2103,7 +2103,7 @@ xd3_alloc (xd3_stream *stream, | |||
2103 | if (a != NULL) | 2103 | if (a != NULL) |
2104 | { | 2104 | { |
2105 | IF_DEBUG (stream->alloc_cnt += 1); | 2105 | IF_DEBUG (stream->alloc_cnt += 1); |
2106 | IF_DEBUG1 (DP(RINT "[stream %p malloc] size %u ptr %p\n", | 2106 | IF_DEBUG2 (DP(RINT "[stream %p malloc] size %u ptr %p\n", |
2107 | stream, elts * size, a)); | 2107 | stream, elts * size, a)); |
2108 | } | 2108 | } |
2109 | else | 2109 | else |
@@ -2122,7 +2122,7 @@ xd3_free (xd3_stream *stream, | |||
2122 | { | 2122 | { |
2123 | IF_DEBUG (stream->free_cnt += 1); | 2123 | IF_DEBUG (stream->free_cnt += 1); |
2124 | XD3_ASSERT (stream->free_cnt <= stream->alloc_cnt); | 2124 | XD3_ASSERT (stream->free_cnt <= stream->alloc_cnt); |
2125 | IF_DEBUG1 (DP(RINT "[stream %p free] %p\n", | 2125 | IF_DEBUG2 (DP(RINT "[stream %p free] %p\n", |
2126 | stream, ptr)); | 2126 | stream, ptr)); |
2127 | stream->free (stream->opaque, ptr); | 2127 | stream->free (stream->opaque, ptr); |
2128 | } | 2128 | } |
@@ -3228,7 +3228,7 @@ xd3_emit_single (xd3_stream *stream, xd3_rinst *single, usize_t code) | |||
3228 | 3228 | ||
3229 | IF_DEBUG1 (DP(RINT "[emit1] %u %s (%u) code %u\n", | 3229 | IF_DEBUG1 (DP(RINT "[emit1] %u %s (%u) code %u\n", |
3230 | single->pos, | 3230 | single->pos, |
3231 | xd3_rtype_to_string (single->type, 0), | 3231 | xd3_rtype_to_string ((xd3_rtype) single->type, 0), |
3232 | single->size, | 3232 | single->size, |
3233 | code)); | 3233 | code)); |
3234 | 3234 | ||
@@ -3266,9 +3266,9 @@ xd3_emit_double (xd3_stream *stream, xd3_rinst *first, | |||
3266 | 3266 | ||
3267 | IF_DEBUG1 (DP(RINT "[emit2]: %u %s (%u) %s (%u) code %u\n", | 3267 | IF_DEBUG1 (DP(RINT "[emit2]: %u %s (%u) %s (%u) code %u\n", |
3268 | first->pos, | 3268 | first->pos, |
3269 | xd3_rtype_to_string (first->type, 0), | 3269 | xd3_rtype_to_string ((xd3_rtype) first->type, 0), |
3270 | first->size, | 3270 | first->size, |
3271 | xd3_rtype_to_string (second->type, 0), | 3271 | xd3_rtype_to_string ((xd3_rtype) second->type, 0), |
3272 | second->size, | 3272 | second->size, |
3273 | code)); | 3273 | code)); |
3274 | 3274 | ||
@@ -4329,10 +4329,14 @@ xd3_source_match_setup (xd3_stream *stream, xoff_t srcpos) | |||
4329 | stream->match_back = 0; | 4329 | stream->match_back = 0; |
4330 | stream->match_fwd = 0; | 4330 | stream->match_fwd = 0; |
4331 | 4331 | ||
4332 | /* This avoids a loop. TODO: if ever duplicates are added to the | 4332 | /* This avoids a non-blocking endless loop caused by scanning |
4333 | * source hash table, this logic won't suffice to avoid loops. | 4333 | * backwards across a block boundary, only to find not enough |
4334 | * TODO: how can you test this in a unittest? Need to search for | 4334 | * matching bytes to beat the current min_match due to a better lazy |
4335 | * hash collisions, I suppose. */ | 4335 | * target match: the re-entry to xd3_string_match() repeats the same |
4336 | * long match because the input position hasn't changed. TODO: if | ||
4337 | * ever duplicates are added to the source hash table, this logic | ||
4338 | * won't suffice to avoid loops. See testing/regtest.cc's | ||
4339 | * TestNonBlockingProgress test! */ | ||
4336 | if (srcpos != 0 && srcpos == stream->match_last_srcpos) | 4340 | if (srcpos != 0 && srcpos == stream->match_last_srcpos) |
4337 | { | 4341 | { |
4338 | goto bad; | 4342 | goto bad; |