summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--xdelta3/testing/Makefile2
-rw-r--r--xdelta3/testing/file.h6
-rw-r--r--xdelta3/testing/modify.h12
-rw-r--r--xdelta3/testing/regtest.cc568
-rw-r--r--xdelta3/xdelta3-internal.h1
-rw-r--r--xdelta3/xdelta3-test.h22
-rw-r--r--xdelta3/xdelta3.c13
7 files changed, 382 insertions, 242 deletions
diff --git a/xdelta3/testing/Makefile b/xdelta3/testing/Makefile
new file mode 100644
index 0000000..77cc11d
--- /dev/null
+++ b/xdelta3/testing/Makefile
@@ -0,0 +1,2 @@
1all:
2 (cd .. && make all)
diff --git a/xdelta3/testing/file.h b/xdelta3/testing/file.h
index a204d74..2c13f63 100644
--- a/xdelta3/testing/file.h
+++ b/xdelta3/testing/file.h
@@ -342,6 +342,12 @@ public:
342 return true; 342 return true;
343 } 343 }
344 344
345 // Check whether a real file matches another
346 bool EqualsSpec(const ExtFile &other) const {
347 int ret = compare_files(Name(), other.Name());
348 return true;
349 }
350
345protected: 351protected:
346 string filename_; 352 string filename_;
347}; 353};
diff --git a/xdelta3/testing/modify.h b/xdelta3/testing/modify.h
index d4698e3..5c2e316 100644
--- a/xdelta3/testing/modify.h
+++ b/xdelta3/testing/modify.h
@@ -15,10 +15,10 @@ public:
15 DELETE = 3, // Delete a specified range of data 15 DELETE = 3, // Delete a specified range of data
16 COPY = 4, // Copy from one region, inserting elsewhere 16 COPY = 4, // Copy from one region, inserting elsewhere
17 MOVE = 5, // Copy then delete copied-from range 17 MOVE = 5, // Copy then delete copied-from range
18 OVERWRITE = 6 // Copy then delete copied-to range 18 COPYOVER = 6 // Copy then delete copied-to range
19 19
20 // ADD, DELETE, and COPY change the file size 20 // ADD, DELETE, and COPY change the file size
21 // MODIFY, MOVE, OVERWRITE preserve the file size 21 // MODIFY, MOVE, COPYOVER preserve the file size
22 }; 22 };
23 23
24 // Constructor for modify, add, delete. 24 // Constructor for modify, add, delete.
@@ -28,7 +28,7 @@ public:
28 addr1(addr1_0), 28 addr1(addr1_0),
29 addr2(0), 29 addr2(0),
30 insert(NULL) { 30 insert(NULL) {
31 CHECK(kind != MOVE && kind != COPY && kind != OVERWRITE); 31 CHECK(kind != MOVE && kind != COPY && kind != COPYOVER);
32 } 32 }
33 33
34 // Constructor for modify, add w/ provided data. 34 // Constructor for modify, add w/ provided data.
@@ -38,7 +38,7 @@ public:
38 addr1(addr1_0), 38 addr1(addr1_0),
39 addr2(0), 39 addr2(0),
40 insert(insert0) { 40 insert(insert0) {
41 CHECK(kind != MOVE && kind != COPY && kind != OVERWRITE); 41 CHECK(kind != MOVE && kind != COPY && kind != COPYOVER);
42 } 42 }
43 43
44 // Constructor for move, copy, overwrite 44 // Constructor for move, copy, overwrite
@@ -48,7 +48,7 @@ public:
48 addr1(addr1_0), 48 addr1(addr1_0),
49 addr2(addr2_0), 49 addr2(addr2_0),
50 insert(NULL) { 50 insert(NULL) {
51 CHECK(kind == MOVE || kind == COPY || kind == OVERWRITE); 51 CHECK(kind == MOVE || kind == COPY || kind == COPYOVER);
52 } 52 }
53 53
54 Kind kind; 54 Kind kind;
@@ -106,7 +106,7 @@ public:
106 case Change::MOVE: 106 case Change::MOVE:
107 MoveChange(ch, table, source_table, rand); 107 MoveChange(ch, table, source_table, rand);
108 break; 108 break;
109 case Change::OVERWRITE: 109 case Change::COPYOVER:
110 OverwriteChange(ch, table, source_table, rand); 110 OverwriteChange(ch, table, source_table, rand);
111 break; 111 break;
112 } 112 }
diff --git a/xdelta3/testing/regtest.cc b/xdelta3/testing/regtest.cc
index d5864d6..12b712e 100644
--- a/xdelta3/testing/regtest.cc
+++ b/xdelta3/testing/regtest.cc
@@ -9,7 +9,12 @@ public:
9 typedef typename Constants::Sizes Sizes; 9 typedef typename Constants::Sizes Sizes;
10 10
11 struct Options { 11 struct Options {
12 Options() : encode_srcwin_maxsz(1<<20),
13 block_size(Constants::BLOCK_SIZE),
14 size_known(false) { }
12 size_t encode_srcwin_maxsz; 15 size_t encode_srcwin_maxsz;
16 size_t block_size;
17 bool size_known;
13 }; 18 };
14 19
15#include "segment.h" 20#include "segment.h"
@@ -18,218 +23,265 @@ public:
18#include "cmp.h" 23#include "cmp.h"
19#include "delta.h" 24#include "delta.h"
20 25
21void InMemoryEncodeDecode(const FileSpec &source_file, 26 void InMemoryEncodeDecode(const FileSpec &source_file,
22 const FileSpec &target_file, 27 const FileSpec &target_file,
23 Block *coded_data, 28 Block *coded_data,
24 const Options &options = Options()) { 29 const Options &options = Options()) {
25 xd3_stream encode_stream; 30 xd3_stream encode_stream;
26 xd3_config encode_config; 31 xd3_config encode_config;
27 xd3_source encode_source; 32 xd3_source encode_source;
28 33
29 xd3_stream decode_stream; 34 xd3_stream decode_stream;
30 xd3_config decode_config; 35 xd3_config decode_config;
31 xd3_source decode_source; 36 xd3_source decode_source;
32 xoff_t verified_bytes = 0; 37 xoff_t verified_bytes = 0;
33 xoff_t encoded_bytes = 0; 38 xoff_t encoded_bytes = 0;
34 39
35 if (coded_data) { 40 if (coded_data) {
36 coded_data->Reset(); 41 coded_data->Reset();
37 } 42 }
38 43
39 memset(&encode_stream, 0, sizeof (encode_stream)); 44 memset(&encode_stream, 0, sizeof (encode_stream));
40 memset(&encode_source, 0, sizeof (encode_source)); 45 memset(&encode_source, 0, sizeof (encode_source));
41 46
42 memset(&decode_stream, 0, sizeof (decode_stream)); 47 memset(&decode_stream, 0, sizeof (decode_stream));
43 memset(&decode_source, 0, sizeof (decode_source)); 48 memset(&decode_source, 0, sizeof (decode_source));
44 49
45 xd3_init_config(&encode_config, XD3_ADLER32); 50 xd3_init_config(&encode_config, XD3_ADLER32);
46 xd3_init_config(&decode_config, XD3_ADLER32); 51 xd3_init_config(&decode_config, XD3_ADLER32);
47 52
48 encode_config.winsize = Constants::WINDOW_SIZE; 53 encode_config.winsize = Constants::WINDOW_SIZE;
49 54
50 // TODO! the smatcher setup isn't working, 55 // TODO! the smatcher setup isn't working,
51// if (options.large_cksum_step) { 56 // if (options.large_cksum_step) {
52// encode_config.smatch_cfg = XD3_SMATCH_SOFT; 57 // encode_config.smatch_cfg = XD3_SMATCH_SOFT;
53// encode_config.smatcher_soft.large_step = options.large_cksum_step; 58 // encode_config.smatcher_soft.large_step = options.large_cksum_step;
54// } 59 // }
55// if (options.large_cksum_size) { 60 // if (options.large_cksum_size) {
56// encode_config.smatch_cfg = XD3_SMATCH_SOFT; 61 // encode_config.smatch_cfg = XD3_SMATCH_SOFT;
57// encode_config.smatcher_soft.large_look = options.large_cksum_size; 62 // encode_config.smatcher_soft.large_look = options.large_cksum_size;
58// } 63 // }
59 64
60 CHECK_EQ(0, xd3_config_stream (&encode_stream, &encode_config)); 65 CHECK_EQ(0, xd3_config_stream (&encode_stream, &encode_config));
61 CHECK_EQ(0, xd3_config_stream (&decode_stream, &decode_config)); 66 CHECK_EQ(0, xd3_config_stream (&decode_stream, &decode_config));
62 67
63 encode_source.blksize = Constants::BLOCK_SIZE; 68 encode_source.blksize = options.block_size;
64 decode_source.blksize = Constants::BLOCK_SIZE; 69 decode_source.blksize = options.block_size;
65 70
66 encode_source.max_winsize = options.encode_srcwin_maxsz; 71 encode_source.max_winsize = options.encode_srcwin_maxsz;
72 decode_source.max_winsize = options.encode_srcwin_maxsz;
67 73
68 xd3_set_source (&encode_stream, &encode_source); 74 if (!options.size_known)
69 xd3_set_source (&decode_stream, &decode_source); 75 {
76 xd3_set_source (&encode_stream, &encode_source);
77 xd3_set_source (&decode_stream, &decode_source);
78 }
79 else
80 {
81 xd3_set_source_and_size (&encode_stream, &encode_source,
82 source_file.Size());
83 xd3_set_source_and_size (&decode_stream, &decode_source,
84 source_file.Size());
85 }
70 86
71 BlockIterator source_iterator(source_file, Constants::BLOCK_SIZE); 87 BlockIterator source_iterator(source_file, options.block_size);
72 BlockIterator target_iterator(target_file, Constants::READ_SIZE); 88 BlockIterator target_iterator(target_file, Constants::READ_SIZE);
73 Block encode_source_block, decode_source_block; 89 Block encode_source_block, decode_source_block;
74 Block decoded_block, target_block; 90 Block decoded_block, target_block;
75 bool encoding = true; 91 bool encoding = true;
76 bool done = false; 92 bool done = false;
77 bool done_after_input = false; 93 bool done_after_input = false;
78 94
79 IF_DEBUG1 (XPR(NTR "source %"Q"u[%"Q"u] target %"Q"u[%lu] winsize %lu\n", 95 IF_DEBUG1 (XPR(NTR "source %"Q"u[%"Q"u] target %"Q"u[%lu] winsize %lu\n",
80 source_file.Size(), Constants::BLOCK_SIZE, 96 source_file.Size(), options.block_size,
81 target_file.Size(), Constants::READ_SIZE, 97 target_file.Size(), Constants::READ_SIZE,
82 Constants::WINDOW_SIZE)); 98 Constants::WINDOW_SIZE));
83 99
84 while (!done) { 100 while (!done) {
85 target_iterator.Get(&target_block); 101 target_iterator.Get(&target_block);
86 102
87 xoff_t blks = target_iterator.Blocks(); 103 xoff_t blks = target_iterator.Blocks();
88 104
89 IF_DEBUG2(XPR(NTR "target in %s: %llu..%llu %"Q"u(%"Q"u) verified %"Q"u\n", 105 IF_DEBUG2(XPR(NTR "target in %s: %llu..%llu %"Q"u(%"Q"u) verified %"Q"u\n",
90 encoding ? "encoding" : "decoding", 106 encoding ? "encoding" : "decoding",
91 target_iterator.Offset(), 107 target_iterator.Offset(),
92 target_iterator.Offset() + target_block.Size(), 108 target_iterator.Offset() + target_block.Size(),
93 target_iterator.Blkno(), blks, verified_bytes)); 109 target_iterator.Blkno(), blks, verified_bytes));
94 110
95 if (blks == 0 || target_iterator.Blkno() == (blks - 1)) { 111 if (blks == 0 || target_iterator.Blkno() == (blks - 1)) {
96 xd3_set_flags(&encode_stream, XD3_FLUSH | encode_stream.flags); 112 xd3_set_flags(&encode_stream, XD3_FLUSH | encode_stream.flags);
97 } 113 }
98 114
99 xd3_avail_input(&encode_stream, target_block.Data(), target_block.Size()); 115 xd3_avail_input(&encode_stream, target_block.Data(), target_block.Size());
100 encoded_bytes += target_block.Size(); 116 encoded_bytes += target_block.Size();
101
102 process:
103 int ret;
104 const char *msg;
105 if (encoding) {
106 ret = xd3_encode_input(&encode_stream);
107 msg = encode_stream.msg;
108 } else {
109 ret = xd3_decode_input(&decode_stream);
110 msg = decode_stream.msg;
111 }
112 (void) msg;
113 117
114 switch (ret) { 118 process:
115 case XD3_OUTPUT: 119 int ret;
120 const char *msg;
116 if (encoding) { 121 if (encoding) {
117 if (coded_data != NULL) { 122 ret = xd3_encode_input(&encode_stream);
118 // Optional encoded-output to the caller 123 msg = encode_stream.msg;
119 coded_data->Append(encode_stream.next_out,
120 encode_stream.avail_out);
121 }
122 // Feed this data to the decoder.
123 xd3_avail_input(&decode_stream,
124 encode_stream.next_out,
125 encode_stream.avail_out);
126 xd3_consume_output(&encode_stream);
127 encoding = false;
128 } else { 124 } else {
129 decoded_block.Append(decode_stream.next_out, 125 ret = xd3_decode_input(&decode_stream);
130 decode_stream.avail_out); 126 msg = decode_stream.msg;
131 xd3_consume_output(&decode_stream);
132 }
133 goto process;
134
135 case XD3_GETSRCBLK: {
136 xd3_source *src = (encoding ? &encode_source : &decode_source);
137 Block *block = (encoding ? &encode_source_block : &decode_source_block);
138 if (encoding) {
139 IF_DEBUG1(XPR(NTR "[srcblock] %"Q"u last srcpos %"Q"u "
140 "encodepos %"Q"u\n",
141 encode_source.getblkno,
142 encode_stream.match_last_srcpos,
143 encode_stream.input_position + encode_stream.total_in));
144 } 127 }
145 128 (void) msg;
146 source_iterator.SetBlock(src->getblkno); 129
147 source_iterator.Get(block); 130 switch (ret) {
148 src->curblkno = src->getblkno; 131 case XD3_OUTPUT:
149 src->onblk = block->Size(); 132 if (encoding) {
150 src->curblk = block->Data(); 133 if (coded_data != NULL) {
151 134 // Optional encoded-output to the caller
152 goto process; 135 coded_data->Append(encode_stream.next_out,
153 } 136 encode_stream.avail_out);
154 137 }
155 case XD3_INPUT: 138 // Feed this data to the decoder.
156 if (!encoding) { 139 xd3_avail_input(&decode_stream,
157 encoding = true; 140 encode_stream.next_out,
158 goto process; 141 encode_stream.avail_out);
159 } else { 142 xd3_consume_output(&encode_stream);
160 if (done_after_input) {
161 done = true;
162 continue;
163 }
164
165 if (target_block.Size() < target_iterator.BlockSize()) {
166 encoding = false; 143 encoding = false;
167 } else { 144 } else {
168 target_iterator.Next(); 145 decoded_block.Append(decode_stream.next_out,
146 decode_stream.avail_out);
147 xd3_consume_output(&decode_stream);
169 } 148 }
170 continue; 149 goto process;
171 }
172 150
173 case XD3_WINFINISH: 151 case XD3_GETSRCBLK: {
174 if (encoding) { 152 xd3_source *src = (encoding ? &encode_source : &decode_source);
175 if (encode_stream.flags & XD3_FLUSH) { 153 Block *block = (encoding ? &encode_source_block : &decode_source_block);
176 done_after_input = true; 154 if (encoding) {
155 IF_DEBUG1(XPR(NTR "[srcblock] %"Q"u last srcpos %"Q"u "
156 "encodepos %"Q"u\n",
157 encode_source.getblkno,
158 encode_stream.match_last_srcpos,
159 encode_stream.input_position + encode_stream.total_in));
177 } 160 }
178 encoding = false; 161
179 } else { 162 source_iterator.SetBlock(src->getblkno);
180 CHECK_EQ(0, CmpDifferentBlockBytesAtOffset(decoded_block, 163 source_iterator.Get(block);
181 target_file, 164 src->curblkno = src->getblkno;
182 verified_bytes)); 165 src->onblk = block->Size();
183 verified_bytes += decoded_block.Size(); 166 src->curblk = block->Data();
184 decoded_block.Reset(); 167
185 encoding = true; 168 goto process;
186 } 169 }
187 goto process;
188 170
189 case XD3_WINSTART: 171 case XD3_INPUT:
190 case XD3_GOTHEADER: 172 if (!encoding) {
191 goto process; 173 encoding = true;
174 goto process;
175 } else {
176 if (done_after_input) {
177 done = true;
178 continue;
179 }
192 180
193 default: 181 if (target_block.Size() < target_iterator.BlockSize()) {
194 XPR(NTR "%s = %s %s\n", encoding ? "E " : " D", 182 encoding = false;
195 xd3_strerror(ret), 183 } else {
196 msg == NULL ? "" : msg); 184 target_iterator.Next();
185 }
186 continue;
187 }
197 188
198 CHECK_EQ(0, ret); 189 case XD3_WINFINISH:
199 CHECK_EQ(-1, ret); 190 if (encoding) {
200 } 191 if (encode_stream.flags & XD3_FLUSH) {
192 done_after_input = true;
193 }
194 encoding = false;
195 } else {
196 CHECK_EQ(0, CmpDifferentBlockBytesAtOffset(decoded_block,
197 target_file,
198 verified_bytes));
199 verified_bytes += decoded_block.Size();
200 decoded_block.Reset();
201 encoding = true;
202 }
203 goto process;
204
205 case XD3_WINSTART:
206 case XD3_GOTHEADER:
207 goto process;
208
209 default:
210 XPR(NTR "%s = %s %s\n", encoding ? "E " : " D",
211 xd3_strerror(ret),
212 msg == NULL ? "" : msg);
213
214 CHECK_EQ(0, ret);
215 CHECK_EQ(-1, ret);
216 }
217 }
218
219 CHECK_EQ(target_file.Size(), encoded_bytes);
220 CHECK_EQ(target_file.Size(), verified_bytes);
221 CHECK_EQ(0, xd3_close_stream(&decode_stream));
222 CHECK_EQ(0, xd3_close_stream(&encode_stream));
223 xd3_free_stream(&encode_stream);
224 xd3_free_stream(&decode_stream);
225 }
226
227 void MainEncodeDecode(const TmpFile &source_file,
228 const TmpFile &target_file,
229 ExtFile *coded_data,
230 const Options &options) {
231 vector<const char*> ecmd;
232 char buf[16];
233 snprintf(buf, sizeof(buf), "-B%"Q"u", options.encode_srcwin_maxsz);
234 ecmd.push_back("xdelta3");
235 ecmd.push_back(buf);
236 ecmd.push_back("-s");
237 ecmd.push_back(source_file.Name());
238 ecmd.push_back(target_file.Name());
239 ecmd.push_back(coded_data->Name());
240 ecmd.push_back(NULL);
241
242 CHECK_EQ(0, xd3_main_cmdline(ecmd.size() - 1,
243 const_cast<char**>(&ecmd[0])));
244
245 vector<const char*> dcmd;
246 ExtFile recon_file;
247 dcmd.push_back("xdelta3");
248 ecmd.push_back(buf);
249 dcmd.push_back("-d");
250 dcmd.push_back("-s");
251 dcmd.push_back(source_file.Name());
252 dcmd.push_back(coded_data->Name());
253 dcmd.push_back(recon_file.Name());
254 dcmd.push_back(NULL);
255
256 CHECK_EQ(0, xd3_main_cmdline(dcmd.size() - 1,
257 const_cast<char**>(&dcmd[0])));
258
259 CHECK_EQ(0, test_compare_files(recon_file.Name(),
260 target_file.Name()));
201 } 261 }
202 262
203 CHECK_EQ(target_file.Size(), encoded_bytes); 263 //////////////////////////////////////////////////////////////////////
204 CHECK_EQ(target_file.Size(), verified_bytes);
205 CHECK_EQ(0, xd3_close_stream(&decode_stream));
206 CHECK_EQ(0, xd3_close_stream(&encode_stream));
207 xd3_free_stream(&encode_stream);
208 xd3_free_stream(&decode_stream);
209}
210
211//////////////////////////////////////////////////////////////////////
212 264
213void TestRandomNumbers() { 265 void TestRandomNumbers() {
214 MTRandom rand; 266 MTRandom rand;
215 int rounds = 1<<20; 267 int rounds = 1<<20;
216 uint64_t usum = 0; 268 uint64_t usum = 0;
217 uint64_t esum = 0; 269 uint64_t esum = 0;
218 270
219 for (int i = 0; i < rounds; i++) { 271 for (int i = 0; i < rounds; i++) {
220 usum += rand.Rand32(); 272 usum += rand.Rand32();
221 esum += rand.ExpRand32(1024); 273 esum += rand.ExpRand32(1024);
222 } 274 }
223 275
224 double allowed_error = 0.01; 276 double allowed_error = 0.01;
225 277
226 uint32_t umean = usum / rounds; 278 uint32_t umean = usum / rounds;
227 uint32_t emean = esum / rounds; 279 uint32_t emean = esum / rounds;
228 280
229 uint32_t uexpect = UINT32_MAX / 2; 281 uint32_t uexpect = UINT32_MAX / 2;
230 uint32_t eexpect = 1024; 282 uint32_t eexpect = 1024;
231 283
232 if (umean < uexpect * (1.0 - allowed_error) || 284 if (umean < uexpect * (1.0 - allowed_error) ||
233 umean > uexpect * (1.0 + allowed_error)) { 285 umean > uexpect * (1.0 + allowed_error)) {
234 XPR(NT "uniform mean error: %u != %u\n", umean, uexpect); 286 XPR(NT "uniform mean error: %u != %u\n", umean, uexpect);
235 abort(); 287 abort();
@@ -516,7 +568,7 @@ void TestOverwriteMutator() {
516 spec0.GenerateFixedSize(Constants::BLOCK_SIZE); 568 spec0.GenerateFixedSize(Constants::BLOCK_SIZE);
517 569
518 ChangeList cl1; 570 ChangeList cl1;
519 cl1.push_back(Change(Change::OVERWRITE, 10, 0, 20)); 571 cl1.push_back(Change(Change::COPYOVER, 10, 0, 20));
520 spec0.ModifyTo(ChangeListMutator(cl1), &spec1); 572 spec0.ModifyTo(ChangeListMutator(cl1), &spec1);
521 CHECK_EQ(spec0.Size(), spec1.Size()); 573 CHECK_EQ(spec0.Size(), spec1.Size());
522 574
@@ -530,7 +582,7 @@ void TestOverwriteMutator() {
530 Constants::BLOCK_SIZE - 30) == 0); 582 Constants::BLOCK_SIZE - 30) == 0);
531 583
532 cl1.clear(); 584 cl1.clear();
533 cl1.push_back(Change(Change::OVERWRITE, 10, 20, (xoff_t)0)); 585 cl1.push_back(Change(Change::COPYOVER, 10, 20, (xoff_t)0));
534 spec0.ModifyTo(ChangeListMutator(cl1), &spec1); 586 spec0.ModifyTo(ChangeListMutator(cl1), &spec1);
535 CHECK_EQ(spec0.Size(), spec1.Size()); 587 CHECK_EQ(spec0.Size(), spec1.Size());
536 588
@@ -553,13 +605,13 @@ void TestNonBlockingProgress() {
553 spec0.GenerateFixedSize(Constants::BLOCK_SIZE * 3); 605 spec0.GenerateFixedSize(Constants::BLOCK_SIZE * 3);
554 606
555 // This is a lazy target match 607 // This is a lazy target match
556 Change ct(Change::OVERWRITE, 22, 608 Change ct(Change::COPYOVER, 22,
557 Constants::BLOCK_SIZE + 50, 609 Constants::BLOCK_SIZE + 50,
558 Constants::BLOCK_SIZE + 20); 610 Constants::BLOCK_SIZE + 20);
559 611
560 // This is a source match just after the block boundary, shorter 612 // This is a source match just after the block boundary, shorter
561 // than the lazy target match. 613 // than the lazy target match.
562 Change cs1(Change::OVERWRITE, 16, 614 Change cs1(Change::COPYOVER, 16,
563 Constants::BLOCK_SIZE + 51, 615 Constants::BLOCK_SIZE + 51,
564 Constants::BLOCK_SIZE - 1); 616 Constants::BLOCK_SIZE - 1);
565 617
@@ -617,64 +669,108 @@ void TestBlockInMemory() {
617 CHECK_EQ(spec1.Blocks(Constants::WINDOW_SIZE), delta.Windows()); 669 CHECK_EQ(spec1.Blocks(Constants::WINDOW_SIZE), delta.Windows());
618} 670}
619 671
620void TestFifoCopyDiscipline() { 672void TestHalfBlockCopy() {
621 MTRandom rand; 673 MTRandom rand;
622 FileSpec spec0(&rand); 674 FileSpec spec0(&rand);
623 FileSpec spec1(&rand); 675 FileSpec spec1(&rand);
624 676
625 spec0.GenerateFixedSize(Constants::BLOCK_SIZE * 4); 677 spec0.GenerateFixedSize(Constants::BLOCK_SIZE * 4);
626 678
627 // Create a half-block copy, 2.5 blocks apart. With 64-byte blocks, 679 // Create a half-block copy, 2.5 blocks apart, from the second half
628 // the file in spec0 copies @ 384 from spec1 @ 64. 680 // of the source version to the first half of the target version.
681 // 0 1 2 3
682 // spec0 [bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb][ccccc][bbbbb]
683 // spec1 [aaaaa][ccccc][aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]
629 ChangeList cl1; 684 ChangeList cl1;
630 cl1.push_back(Change(Change::MODIFY, 685 cl1.push_back(Change(Change::MODIFY,
631 Constants::BLOCK_SIZE / 2, 686 Constants::BLOCK_SIZE / 2, // size
632 0)); 687 0));
633 cl1.push_back(Change(Change::OVERWRITE, 688 cl1.push_back(Change(Change::COPYOVER,
634 Constants::BLOCK_SIZE / 2, 689 Constants::BLOCK_SIZE / 2, // size
635 Constants::BLOCK_SIZE * 3, 690 Constants::BLOCK_SIZE * 3, // offset
636 Constants::BLOCK_SIZE / 2)); 691 Constants::BLOCK_SIZE / 2));
637 cl1.push_back(Change(Change::MODIFY, 692 cl1.push_back(Change(Change::MODIFY,
638 Constants::BLOCK_SIZE * 3, 693 Constants::BLOCK_SIZE * 3,
639 Constants::BLOCK_SIZE)); 694 Constants::BLOCK_SIZE));
640 spec0.ModifyTo(ChangeListMutator(cl1), &spec1); 695 spec0.ModifyTo(ChangeListMutator(cl1), &spec1);
641 696
642 Options options1; 697 const int onecopy_adds =
643 options1.encode_srcwin_maxsz = Constants::BLOCK_SIZE * 4; 698 4 * Constants::BLOCK_SIZE - Constants::BLOCK_SIZE / 2;
699 const int nocopy_adds = 4 * Constants::BLOCK_SIZE;
700
701 // Note the case b=4 is contrived: the caller should use a single block
702 // containing the entire source, if possible.
703 for (int b = 1; b <= 4; b++)
704 {
705 Options options;
706 options.encode_srcwin_maxsz = Constants::BLOCK_SIZE * b;
707
708 Block block0;
709 Block block1;
710 InMemoryEncodeDecode(spec0, spec1, &block0, options);
711 InMemoryEncodeDecode(spec1, spec0, &block1, options);
712 Delta delta0(block0);
713 Delta delta1(block1);
714
715 // The first block never copies from the last source block, by
716 // design, because if the last source block is available when
717 // the first target block is ready, the caller is expected to
718 // use a single block.
719 CHECK_EQ(nocopy_adds, delta0.AddedBytes());
720 if (Constants::BLOCK_SIZE < 8192 || b > 2)
721 {
722 // For small-block inputs, the entire file is read into one
723 // block (the min source window size is 16kB).
724 //
725 // For large blocks, at least 3 blocks of source window are
726 // needed.
727 CHECK_EQ(onecopy_adds, delta1.AddedBytes());
728 }
729 else
730 {
731 // When there are fewer than 3 source blocks.
732 CHECK_EQ(nocopy_adds, delta1.AddedBytes());
733 }
734 // XPR(NT "0=%zu 1=%zu\n", delta0.AddedBytes(), delta1.AddedBytes());
735 }
736
737 Options options;
738 options.encode_srcwin_maxsz = Constants::BLOCK_SIZE * 4;
739 options.block_size = Constants::BLOCK_SIZE * 4;
740
741 // Test the whole-buffer case.
742 Block block0;
644 Block block1; 743 Block block1;
645 InMemoryEncodeDecode(spec1, spec0, &block1, options1); 744 InMemoryEncodeDecode(spec0, spec1, &block0, options);
745 InMemoryEncodeDecode(spec1, spec0, &block1, options);
746 Delta delta0(block0);
646 Delta delta1(block1); 747 Delta delta1(block1);
647 CHECK_EQ(4 * Constants::BLOCK_SIZE - 748 // This <= >= are only for blocksize = 512, which has irregular readsize.
648 Constants::BLOCK_SIZE / 2, delta1.AddedBytes()); 749 CHECK_LE(onecopy_adds, delta0.AddedBytes());
649 750 CHECK_GE(onecopy_adds + 1, delta0.AddedBytes());
650 Options options2; 751
651 options2.encode_srcwin_maxsz = Constants::BLOCK_SIZE * 3; 752 CHECK_EQ(onecopy_adds, delta1.AddedBytes());
652 Block block2; 753 // XPR(NT "0=%zu 1=%zu\n", delta0.AddedBytes(), delta1.AddedBytes());
653 InMemoryEncodeDecode(spec1, spec0, &block2, options2);
654 Delta delta2(block2);
655 CHECK_EQ(4 * Constants::BLOCK_SIZE, delta2.AddedBytes());
656} 754}
657 755
658void FourWayMergeTest(const FileSpec &spec0, 756void FourWayMergeTest(const FileSpec &spec0,
659 const FileSpec &spec1, 757 const FileSpec &spec1,
660 const FileSpec &spec2, 758 const FileSpec &spec2,
661 const FileSpec &spec3) { 759 const FileSpec &spec3) {
662 Block delta01, delta12, delta23; 760 TmpFile f0, f1, f2, f3;
663 761 ExtFile d01, d12, d23;
664 InMemoryEncodeDecode(spec0, spec1, &delta01); 762 Options options;
665 InMemoryEncodeDecode(spec1, spec2, &delta12); 763 options.encode_srcwin_maxsz =
666 InMemoryEncodeDecode(spec2, spec3, &delta23); 764 std::max(spec0.Size(), options.encode_srcwin_maxsz);
667
668 TmpFile f0, f1, f2, f3, d01, d12, d23;
669 765
670 spec0.WriteTmpFile(&f0); 766 spec0.WriteTmpFile(&f0);
671 spec1.WriteTmpFile(&f1); 767 spec1.WriteTmpFile(&f1);
672 spec2.WriteTmpFile(&f2); 768 spec2.WriteTmpFile(&f2);
673 spec2.WriteTmpFile(&f3); 769 spec3.WriteTmpFile(&f3);
674 770
675 delta01.WriteTmpFile(&d01); 771 MainEncodeDecode(f0, f1, &d01, options);
676 delta12.WriteTmpFile(&d12); 772 MainEncodeDecode(f1, f2, &d12, options);
677 delta23.WriteTmpFile(&d23); 773 MainEncodeDecode(f2, f3, &d23, options);
678 774
679 // Merge 2 775 // Merge 2
680 ExtFile out; 776 ExtFile out;
@@ -687,7 +783,7 @@ void FourWayMergeTest(const FileSpec &spec0,
687 mcmd.push_back(out.Name()); 783 mcmd.push_back(out.Name());
688 mcmd.push_back(NULL); 784 mcmd.push_back(NULL);
689 785
690 //XPR(NTR "Running one merge: %s\n", CommandToString(mcmd).c_str()); 786 // XPR(NTR "Running one merge: %s\n", CommandToString(mcmd).c_str());
691 CHECK_EQ(0, xd3_main_cmdline(mcmd.size() - 1, 787 CHECK_EQ(0, xd3_main_cmdline(mcmd.size() - 1,
692 const_cast<char**>(&mcmd[0]))); 788 const_cast<char**>(&mcmd[0])));
693 789
@@ -701,14 +797,46 @@ void FourWayMergeTest(const FileSpec &spec0,
701 tcmd.push_back(recon.Name()); 797 tcmd.push_back(recon.Name());
702 tcmd.push_back(NULL); 798 tcmd.push_back(NULL);
703 799
704 //XPR(NTR "Running one recon! %s\n", CommandToString(tcmd).c_str()); 800 // XPR(NTR "Running one recon! %s\n", CommandToString(tcmd).c_str());
705 CHECK_EQ(0, xd3_main_cmdline(tcmd.size() - 1, 801 CHECK_EQ(0, xd3_main_cmdline(tcmd.size() - 1,
706 const_cast<char**>(&tcmd[0]))); 802 const_cast<char**>(&tcmd[0])));
707 //XPR(NTR "Should equal! %s\n", f2.Name()); 803 // XPR(NTR "Should equal! %s\n", f2.Name());
708 804
709 CHECK(recon.EqualsSpec(spec2)); 805 CHECK(recon.EqualsSpec(spec2));
710 806
711 /* TODO: we've only done 3-way merges, try 4-way. */ 807 // Merge 3
808 ExtFile out3;
809 vector<const char*> mcmd3;
810 mcmd3.push_back("xdelta3");
811 mcmd3.push_back("merge");
812 mcmd3.push_back("-m");
813 mcmd3.push_back(d01.Name());
814 mcmd3.push_back("-m");
815 mcmd3.push_back(d12.Name());
816 mcmd3.push_back(d23.Name());
817 mcmd3.push_back(out3.Name());
818 mcmd3.push_back(NULL);
819
820 // XPR(NTR "Running one 3-merge: %s\n", CommandToString(mcmd3).c_str());
821 CHECK_EQ(0, xd3_main_cmdline(mcmd3.size() - 1,
822 const_cast<char**>(&mcmd3[0])));
823
824 ExtFile recon3;
825 vector<const char*> tcmd3;
826 tcmd3.push_back("xdelta3");
827 tcmd3.push_back("-d");
828 tcmd3.push_back("-s");
829 tcmd3.push_back(f0.Name());
830 tcmd3.push_back(out3.Name());
831 tcmd3.push_back(recon3.Name());
832 tcmd3.push_back(NULL);
833
834 // XPR(NTR "Running one 3-recon %s\n", CommandToString(tcmd3).c_str());
835 CHECK_EQ(0, xd3_main_cmdline(tcmd3.size() - 1,
836 const_cast<char**>(&tcmd3[0])));
837 // XPR(NTR "Should equal %s\n", f3.Name());
838
839 CHECK(recon3.EqualsSpec(spec3));
712} 840}
713 841
714void TestMergeCommand1() { 842void TestMergeCommand1() {
@@ -735,6 +863,7 @@ void TestMergeCommand1() {
735 863
736 // XPR(NTR "S0 = %lu\n", size0); 864 // XPR(NTR "S0 = %lu\n", size0);
737 // XPR(NTR "C1 = %lu\n", change1); 865 // XPR(NTR "C1 = %lu\n", change1);
866 // XPR(NTR ".");
738 867
739 size_t add1_pos = size0 ? rand.Rand32() % size0 : 0; 868 size_t add1_pos = size0 ? rand.Rand32() % size0 : 0;
740 size_t del2_pos = size0 ? rand.Rand32() % size0 : 0; 869 size_t del2_pos = size0 ? rand.Rand32() % size0 : 0;
@@ -802,6 +931,7 @@ void TestMergeCommand2() {
802 // XPR(NTR "S1 = %lu\n", size1); 931 // XPR(NTR "S1 = %lu\n", size1);
803 // XPR(NTR "S2 = %lu\n", size2); 932 // XPR(NTR "S2 = %lu\n", size2);
804 // XPR(NTR "S3 = %lu\n", size3); 933 // XPR(NTR "S3 = %lu\n", size3);
934 // XPR(NTR ".");
805 935
806 spec0.GenerateFixedSize(size0); 936 spec0.GenerateFixedSize(size0);
807 937
@@ -845,12 +975,13 @@ void UnitTest() {
845// These are Xdelta tests. 975// These are Xdelta tests.
846template <class T> 976template <class T>
847void MainTest() { 977void MainTest() {
848 XPR(NT "Blocksize: %"Q"u\n", T::BLOCK_SIZE); 978 XPR(NT "Blocksize %"Q"u readsize %"Q"u windowsize %"Q"u\n",
979 T::BLOCK_SIZE, T::READ_SIZE, T::WINDOW_SIZE);
849 Regtest<T> regtest; 980 Regtest<T> regtest;
850 TEST(TestEmptyInMemory); 981 TEST(TestEmptyInMemory);
851 TEST(TestBlockInMemory); 982 TEST(TestBlockInMemory);
852 TEST(TestNonBlockingProgress); 983 TEST(TestNonBlockingProgress);
853 TEST(TestFifoCopyDiscipline); 984 TEST(TestHalfBlockCopy);
854 TEST(TestMergeCommand1); 985 TEST(TestMergeCommand1);
855 TEST(TestMergeCommand2); 986 TEST(TestMergeCommand2);
856} 987}
@@ -870,14 +1001,15 @@ int main(int argc, char **argv)
870 mcmd.push_back("test"); 1001 mcmd.push_back("test");
871 mcmd.push_back(NULL); 1002 mcmd.push_back(NULL);
872 1003
873 CHECK_EQ(0, xd3_main_cmdline(mcmd.size() - 1,
874 const_cast<char**>(&mcmd[0])));
875
876 UnitTest<SmallBlock>(); 1004 UnitTest<SmallBlock>();
877 MainTest<SmallBlock>(); 1005 MainTest<SmallBlock>();
878 MainTest<MixedBlock>(); 1006 MainTest<MixedBlock>();
879 MainTest<PrimeBlock>(); 1007 MainTest<PrimeBlock>();
880 MainTest<OversizeBlock>(); 1008 MainTest<OversizeBlock>();
881 MainTest<LargeBlock>(); 1009 MainTest<LargeBlock>();
1010
1011 CHECK_EQ(0, xd3_main_cmdline(mcmd.size() - 1,
1012 const_cast<char**>(&mcmd[0])));
1013
882 return 0; 1014 return 0;
883} 1015}
diff --git a/xdelta3/xdelta3-internal.h b/xdelta3/xdelta3-internal.h
index 13f4a34..8e9e25b 100644
--- a/xdelta3/xdelta3-internal.h
+++ b/xdelta3/xdelta3-internal.h
@@ -40,6 +40,7 @@ int main_file_read (main_file *ifile,
40 const char *msg); 40 const char *msg);
41int main_file_write (main_file *ofile, uint8_t *buf, 41int main_file_write (main_file *ofile, uint8_t *buf,
42 usize_t size, const char *msg); 42 usize_t size, const char *msg);
43int test_compare_files (const char* f0, const char* f1);
43usize_t xd3_bytes_on_srcblk (xd3_source *src, xoff_t blkno); 44usize_t xd3_bytes_on_srcblk (xd3_source *src, xoff_t blkno);
44xoff_t xd3_source_eof(const xd3_source *src); 45xoff_t xd3_source_eof(const xd3_source *src);
45uint32_t xd3_large_cksum_update (uint32_t cksum, 46uint32_t xd3_large_cksum_update (uint32_t cksum,
diff --git a/xdelta3/xdelta3-test.h b/xdelta3/xdelta3-test.h
index a2ba64f..e9848b6 100644
--- a/xdelta3/xdelta3-test.h
+++ b/xdelta3/xdelta3-test.h
@@ -39,7 +39,7 @@ struct mtrand {
39 uint32_t mt_buffer_[MT_LEN]; 39 uint32_t mt_buffer_[MT_LEN];
40}; 40};
41 41
42int compare_files (const char* tgt, const char *rec); 42int test_compare_files (const char* tgt, const char *rec);
43void mt_init(mtrand *mt, uint32_t seed); 43void mt_init(mtrand *mt, uint32_t seed);
44uint32_t mt_random (mtrand *mt); 44uint32_t mt_random (mtrand *mt);
45int test_setup (void); 45int test_setup (void);
@@ -369,7 +369,7 @@ test_make_inputs (xd3_stream *stream, xoff_t *ss_out, xoff_t *ts_out)
369} 369}
370 370
371int 371int
372compare_files (const char* tgt, const char *rec) 372test_compare_files (const char* tgt, const char *rec)
373{ 373{
374 FILE *orig, *recons; 374 FILE *orig, *recons;
375 static uint8_t obuf[TESTBUFSIZE], rbuf[TESTBUFSIZE]; 375 static uint8_t obuf[TESTBUFSIZE], rbuf[TESTBUFSIZE];
@@ -1771,7 +1771,7 @@ test_command_line_arguments (xd3_stream *stream, int ignore)
1771 } 1771 }
1772 1772
1773 /* Compare the target file. */ 1773 /* Compare the target file. */
1774 if ((ret = compare_files (TEST_TARGET_FILE, TEST_RECON_FILE))) 1774 if ((ret = test_compare_files (TEST_TARGET_FILE, TEST_RECON_FILE)))
1775 { 1775 {
1776 return ret; 1776 return ret;
1777 } 1777 }
@@ -1802,12 +1802,12 @@ test_command_line_arguments (xd3_stream *stream, int ignore)
1802 return XD3_INTERNAL; 1802 return XD3_INTERNAL;
1803 } 1803 }
1804 1804
1805 /* Also check that compare_files works. The delta and original should 1805 /* Also check that test_compare_files works. The delta and original should
1806 * not be identical. */ 1806 * not be identical. */
1807 if ((ret = compare_files (TEST_DELTA_FILE, 1807 if ((ret = test_compare_files (TEST_DELTA_FILE,
1808 TEST_TARGET_FILE)) == 0) 1808 TEST_TARGET_FILE)) == 0)
1809 { 1809 {
1810 stream->msg = "broken compare_files"; 1810 stream->msg = "broken test_compare_files";
1811 return XD3_INTERNAL; 1811 return XD3_INTERNAL;
1812 } 1812 }
1813 1813
@@ -2004,7 +2004,7 @@ test_recode_command2 (xd3_stream *stream, int has_source,
2004 } 2004 }
2005 2005
2006 /* Now compare. */ 2006 /* Now compare. */
2007 if ((ret = compare_files (TEST_TARGET_FILE, TEST_RECON_FILE))) 2007 if ((ret = test_compare_files (TEST_TARGET_FILE, TEST_RECON_FILE)))
2008 { 2008 {
2009 return ret; 2009 return ret;
2010 } 2010 }
@@ -2089,7 +2089,7 @@ test_compressed_pipe (xd3_stream *stream, main_extcomp *ext, char* buf,
2089 return XD3_INTERNAL; 2089 return XD3_INTERNAL;
2090 } 2090 }
2091 2091
2092 if ((ret = compare_files (TEST_TARGET_FILE, TEST_RECON_FILE))) 2092 if ((ret = test_compare_files (TEST_TARGET_FILE, TEST_RECON_FILE)))
2093 { 2093 {
2094 return XD3_INTERNAL; 2094 return XD3_INTERNAL;
2095 } 2095 }
@@ -2219,7 +2219,7 @@ test_source_decompression (xd3_stream *stream, int ignore)
2219 snprintf_func (buf, TESTBUFSIZE, "%s -v -dq -R -s%s %s %s", program_name, 2219 snprintf_func (buf, TESTBUFSIZE, "%s -v -dq -R -s%s %s %s", program_name,
2220 TEST_SOURCE_FILE, TEST_DELTA_FILE, TEST_RECON_FILE); 2220 TEST_SOURCE_FILE, TEST_DELTA_FILE, TEST_RECON_FILE);
2221 if ((ret = do_cmd (stream, buf))) { return ret; } 2221 if ((ret = do_cmd (stream, buf))) { return ret; }
2222 if ((ret = compare_files (TEST_COPY_FILE, 2222 if ((ret = test_compare_files (TEST_COPY_FILE,
2223 TEST_RECON_FILE))) { return ret; } 2223 TEST_RECON_FILE))) { return ret; }
2224 2224
2225 /* Decode the delta file with recompression, should get a compressed file 2225 /* Decode the delta file with recompression, should get a compressed file
@@ -2230,7 +2230,7 @@ test_source_decompression (xd3_stream *stream, int ignore)
2230 snprintf_func (buf, TESTBUFSIZE, "%s %s < %s > %s", ext->decomp_cmdname, ext->decomp_options, 2230 snprintf_func (buf, TESTBUFSIZE, "%s %s < %s > %s", ext->decomp_cmdname, ext->decomp_options,
2231 TEST_RECON_FILE, TEST_RECON2_FILE); 2231 TEST_RECON_FILE, TEST_RECON2_FILE);
2232 if ((ret = do_cmd (stream, buf))) { return ret; } 2232 if ((ret = do_cmd (stream, buf))) { return ret; }
2233 if ((ret = compare_files (TEST_COPY_FILE, 2233 if ((ret = test_compare_files (TEST_COPY_FILE,
2234 TEST_RECON2_FILE))) { return ret; } 2234 TEST_RECON2_FILE))) { return ret; }
2235 2235
2236 /* Encode with decompression disabled */ 2236 /* Encode with decompression disabled */
@@ -2243,7 +2243,7 @@ test_source_decompression (xd3_stream *stream, int ignore)
2243 snprintf_func (buf, TESTBUFSIZE, "%s -d -D -vfq -s%s %s %s", program_name, 2243 snprintf_func (buf, TESTBUFSIZE, "%s -d -D -vfq -s%s %s %s", program_name,
2244 TEST_SOURCE_FILE, TEST_DELTA_FILE, TEST_RECON_FILE); 2244 TEST_SOURCE_FILE, TEST_DELTA_FILE, TEST_RECON_FILE);
2245 if ((ret = do_cmd (stream, buf))) { return ret; } 2245 if ((ret = do_cmd (stream, buf))) { return ret; }
2246 if ((ret = compare_files (TEST_TARGET_FILE, 2246 if ((ret = test_compare_files (TEST_TARGET_FILE,
2247 TEST_RECON_FILE))) { return ret; } 2247 TEST_RECON_FILE))) { return ret; }
2248 2248
2249 test_cleanup(); 2249 test_cleanup();
diff --git a/xdelta3/xdelta3.c b/xdelta3/xdelta3.c
index 608622a..3e1bc14 100644
--- a/xdelta3/xdelta3.c
+++ b/xdelta3/xdelta3.c
@@ -1127,7 +1127,8 @@ xd3_choose_instruction (const xd3_code_table_desc *desc, xd3_rinst *prev, xd3_ri
1127 * it cannot be an add. This check is more clear. */ 1127 * it cannot be an add. This check is more clear. */
1128 if (prev_mode >= 0 && inst->size <= desc->copyadd_add_max) 1128 if (prev_mode >= 0 && inst->size <= desc->copyadd_add_max)
1129 { 1129 {
1130 const xd3_code_table_sizes *sizes = & desc->copyadd_max_sizes[prev_mode]; 1130 const xd3_code_table_sizes *sizes =
1131 & desc->copyadd_max_sizes[prev_mode];
1131 1132
1132 /* This check and the inst->size-<= above are == in 1133 /* This check and the inst->size-<= above are == in
1133 the default table. */ 1134 the default table. */
@@ -5079,12 +5080,10 @@ xd3_srcwin_move_point (xd3_stream *stream, usize_t *next_move_point)
5079 * small inputs, especially when the content may have moved anywhere 5080 * small inputs, especially when the content may have moved anywhere
5080 * in the file (e.g., tar files). 5081 * in the file (e.g., tar files).
5081 * 5082 *
5082 * if (src->size > src->max_winsize), index at least one block (which 5083 * if (src->size > src->max_winsize), index at least one block ahead
5083 * the command-line sets to 1/32 of src->max_winsize) ahead of the 5084 * of the logical position. This is good for different reasons:
5084 * logical position. This is good for different reasons: when a 5085 * when a long match spanning several source blocks is encountered,
5085 * long match spanning several source blocks is encountered, this 5086 * this avoids computing checksums for those blocks.
5086 * avoids computing checksums for those blocks. If the data can
5087 * move anywhere, this is bad.
5088 */ 5087 */
5089 logical_input_cksum_pos += stream->src->blksize; 5088 logical_input_cksum_pos += stream->src->blksize;
5090 5089