1 | // ================================================================= // |
---|
2 | // // |
---|
3 | // File : arb_diff.cxx // |
---|
4 | // Purpose : code to compare/diff files // |
---|
5 | // // |
---|
6 | // Coded by Ralf Westram (coder@reallysoft.de) in September 2013 // |
---|
7 | // Institute of Microbiology (Technical University Munich) // |
---|
8 | // http://www.arb-home.de/ // |
---|
9 | // // |
---|
10 | // ================================================================= // |
---|
11 | |
---|
12 | // AISC_MKPT_PROMOTE:#ifndef _GLIBCXX_CSTDLIB |
---|
13 | // AISC_MKPT_PROMOTE:#include <cstdlib> |
---|
14 | // AISC_MKPT_PROMOTE:#endif |
---|
15 | |
---|
16 | #include "arb_diff.h" |
---|
17 | #include "arb_match.h" |
---|
18 | #include "arb_string.h" |
---|
19 | #include "arb_msg.h" |
---|
20 | #include "arb_file.h" |
---|
21 | |
---|
22 | #include <arb_str.h> |
---|
23 | #include <arbtools.h> |
---|
24 | #include <smartptr.h> |
---|
25 | |
---|
26 | #include <list> |
---|
27 | #include <string> |
---|
28 | |
---|
29 | #define MAX_REGS 13 |
---|
30 | |
---|
31 | class difflineMode : virtual Noncopyable { |
---|
32 | bool ignoreDateTimeChanges; |
---|
33 | |
---|
34 | GBS_regex *reg[MAX_REGS]; |
---|
35 | bool wordsOnly[MAX_REGS]; // only match if regexpr hits a word |
---|
36 | const char *replace[MAX_REGS]; |
---|
37 | |
---|
38 | int count; |
---|
39 | mutable GB_ERROR error; |
---|
40 | |
---|
41 | void add(bool onlyWords, const char *regEx, GB_CASE case_flag, const char *replacement) { |
---|
42 | if (!error) { |
---|
43 | arb_assert(count<MAX_REGS); |
---|
44 | reg[count] = GBS_compile_regexpr(regEx, case_flag, &error); |
---|
45 | if (!error) { |
---|
46 | replace[count] = replacement; |
---|
47 | wordsOnly[count] = onlyWords; |
---|
48 | count++; |
---|
49 | } |
---|
50 | } |
---|
51 | } |
---|
52 | |
---|
53 | static bool is_word_char(char c) { return isalnum(c) || c == '_'; } // matches posix word def |
---|
54 | |
---|
55 | typedef SmartCustomPtr(GBS_regex, GBS_free_regexpr) GBS_regexPtr; |
---|
56 | |
---|
57 | mutable bool may_involved; |
---|
58 | static GBS_regexPtr contains_May; |
---|
59 | |
---|
60 | void avoid_may_problems(const char *str) const { |
---|
61 | if (!may_involved) { |
---|
62 | if (GBS_regmatch_compiled(str, &*contains_May, NULp)) { |
---|
63 | // 'May' does not differ between short/long monthname |
---|
64 | // -> use less accurate tests in May |
---|
65 | fprintf(stderr, "Loosening month comparison: 'May' involved in '%s'\n", str); |
---|
66 | const_cast<difflineMode*>(this)->add(false, "<Month>", GB_MIND_CASE, "<MON>"); |
---|
67 | may_involved = true; |
---|
68 | } |
---|
69 | } |
---|
70 | } |
---|
71 | |
---|
72 | public: |
---|
73 | difflineMode(bool ignoreDateTimeChanges_) : |
---|
74 | ignoreDateTimeChanges(ignoreDateTimeChanges_), |
---|
75 | count(0), |
---|
76 | error(NULp), |
---|
77 | may_involved(false) |
---|
78 | { |
---|
79 | memset(reg, 0, sizeof(reg)); |
---|
80 | if (ignoreDateTimeChanges) { |
---|
81 | add(false, "[0-9]{2}:[0-9]{2}:[0-9]{2}", GB_MIND_CASE, "<TIME>"); |
---|
82 | add(true, "(Mon|Tue|Wed|Thu|Fri|Sat|Sun)", GB_IGNORE_CASE, "<DOW>"); |
---|
83 | |
---|
84 | add(true, "(January|February|March|April|May|June|July|August|September|October|November|December)", GB_IGNORE_CASE, "<Month>"); |
---|
85 | add(true, "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)", GB_IGNORE_CASE, "<MON>"); |
---|
86 | |
---|
87 | add(false, "<MON>[ -][0-9]{4}", GB_IGNORE_CASE, "<MON> <YEAR>"); |
---|
88 | add(false, "<Month>[ -][0-9]{4}", GB_IGNORE_CASE, "<Month> <YEAR>"); |
---|
89 | |
---|
90 | add(false, "<TIME>[ -][0-9]{4}", GB_IGNORE_CASE, "<TIME> <YEAR>"); |
---|
91 | |
---|
92 | add(false, "<MON>[ -][0-9 ]?[0-9]", GB_IGNORE_CASE, "<MON> <DAY>"); |
---|
93 | add(false, "<Month>[ -][0-9 ]?[0-9]", GB_IGNORE_CASE, "<Month> <DAY>"); |
---|
94 | |
---|
95 | add(false, "[0-9]{2}[ -\\.]+<MON>", GB_IGNORE_CASE, "<DAY> <MON>"); |
---|
96 | add(false, "[0-9]{2}[ -\\.]+<Month>", GB_IGNORE_CASE, "<DAY> <Month>"); |
---|
97 | |
---|
98 | add(false, "<DAY>, [0-9]{4}", GB_IGNORE_CASE, "<DAY> <YEAR>"); |
---|
99 | } |
---|
100 | } |
---|
101 | ~difflineMode() { |
---|
102 | for (int i = 0; i<count; ++i) { |
---|
103 | GBS_free_regexpr(reg[i]); |
---|
104 | reg[i] = NULp; |
---|
105 | } |
---|
106 | } |
---|
107 | |
---|
108 | const char *get_error() const { return error; } |
---|
109 | int get_count() const { return count; } |
---|
110 | |
---|
111 | private: |
---|
112 | void replaceAll(char*& str) const { |
---|
113 | for (int i = 0; i<count; ++i) { |
---|
114 | size_t matchlen; |
---|
115 | const char *matched = GBS_regmatch_compiled(str, reg[i], &matchlen); |
---|
116 | |
---|
117 | if (matched) { |
---|
118 | char *prefix = ARB_strpartdup(str, matched-1); |
---|
119 | const char *suffix = matched+matchlen; |
---|
120 | |
---|
121 | bool do_repl = true; |
---|
122 | if (wordsOnly[i]) { |
---|
123 | if (prefix[0] != 0 && is_word_char(matched[-1])) do_repl = false; |
---|
124 | else if (suffix[0] != 0 && is_word_char(suffix[0])) do_repl = false; |
---|
125 | } |
---|
126 | |
---|
127 | if (do_repl) freeset(str, GBS_global_string_copy("%s%s%s", prefix, replace[i], suffix)); |
---|
128 | |
---|
129 | free(prefix); |
---|
130 | } |
---|
131 | } |
---|
132 | } |
---|
133 | public: |
---|
134 | void replaceAll(char*& str1, char*& str2) const { |
---|
135 | avoid_may_problems(str1); |
---|
136 | avoid_may_problems(str2); |
---|
137 | replaceAll(str1); |
---|
138 | replaceAll(str2); |
---|
139 | } |
---|
140 | }; |
---|
141 | |
---|
142 | static GB_ERROR static_error = NULp; |
---|
143 | difflineMode::GBS_regexPtr difflineMode::contains_May = GBS_compile_regexpr("May", GB_IGNORE_CASE, &static_error); |
---|
144 | |
---|
145 | static void cutEOL(char *s) { |
---|
146 | char *lf = strchr(s, '\n'); |
---|
147 | if (lf) lf[0] = 0; |
---|
148 | } |
---|
149 | |
---|
150 | static bool test_accept_diff_lines(const char *line1, const char *line2, const difflineMode& mode) { |
---|
151 | if (*line1++ != '-') return false; |
---|
152 | if (*line2++ != '+') return false; |
---|
153 | |
---|
154 | char *dup1 = ARB_strdup(line1); |
---|
155 | char *dup2 = ARB_strdup(line2); |
---|
156 | |
---|
157 | cutEOL(dup1); // side-effect: accepts missing trailing newline |
---|
158 | cutEOL(dup2); |
---|
159 | |
---|
160 | mode.replaceAll(dup1, dup2); |
---|
161 | |
---|
162 | bool equalNow = strcmp(dup1, dup2) == 0; |
---|
163 | #if defined(DEBUG) |
---|
164 | // printf("dup1='%s'\ndup2='%s'\n", dup1, dup2); // uncomment this line to trace replaces |
---|
165 | #endif // DEBUG |
---|
166 | |
---|
167 | free(dup2); |
---|
168 | free(dup1); |
---|
169 | |
---|
170 | return equalNow; |
---|
171 | } |
---|
172 | |
---|
173 | class DiffLines { |
---|
174 | typedef std::list<std::string> Lines; |
---|
175 | typedef Lines::iterator LinesIter; |
---|
176 | typedef Lines::const_iterator LinesCIter; |
---|
177 | |
---|
178 | Lines added_lines; |
---|
179 | Lines deleted_lines; |
---|
180 | |
---|
181 | LinesIter added_last_checked; |
---|
182 | LinesIter deleted_last_checked; |
---|
183 | |
---|
184 | static LinesIter next(LinesIter iter) { advance(iter, 1); return iter; } |
---|
185 | static LinesIter last(Lines& lines) { return (++lines.rbegin()).base(); } |
---|
186 | |
---|
187 | void set_checked() { |
---|
188 | added_last_checked = last(added_lines); |
---|
189 | deleted_last_checked = last(deleted_lines); |
---|
190 | } |
---|
191 | |
---|
192 | public: |
---|
193 | DiffLines() { set_checked(); } |
---|
194 | |
---|
195 | bool add(const char *diffline) { |
---|
196 | bool did_add = true; |
---|
197 | switch (diffline[0]) { |
---|
198 | case '-': deleted_lines.push_back(diffline); break; |
---|
199 | case '+': added_lines.push_back(diffline); break; |
---|
200 | default : did_add = false; break; |
---|
201 | } |
---|
202 | // fputs(diffline, stdout); // uncomment to show all difflines |
---|
203 | return did_add; |
---|
204 | } |
---|
205 | |
---|
206 | int added() const { return added_lines.size(); } |
---|
207 | int deleted() const { return deleted_lines.size(); } |
---|
208 | |
---|
209 | void remove_accepted_lines(const difflineMode& mode) { |
---|
210 | LinesIter d = next(deleted_last_checked); |
---|
211 | LinesIter dEnd = deleted_lines.end(); |
---|
212 | LinesIter a = next(added_last_checked); |
---|
213 | LinesIter aEnd = added_lines.end(); |
---|
214 | |
---|
215 | |
---|
216 | while (d != dEnd && a != aEnd) { |
---|
217 | if (test_accept_diff_lines(d->c_str(), a->c_str(), mode)) { |
---|
218 | d = deleted_lines.erase(d); |
---|
219 | a = added_lines.erase(a); |
---|
220 | } |
---|
221 | else { |
---|
222 | ++d; |
---|
223 | ++a; |
---|
224 | } |
---|
225 | } |
---|
226 | set_checked(); |
---|
227 | } |
---|
228 | |
---|
229 | void print_from(FILE *out, LinesCIter a, LinesCIter d) const { |
---|
230 | LinesCIter dEnd = deleted_lines.end(); |
---|
231 | LinesCIter aEnd = added_lines.end(); |
---|
232 | |
---|
233 | while (d != dEnd && a != aEnd) { |
---|
234 | fputs(d->c_str(), out); ++d; |
---|
235 | fputs(a->c_str(), out); ++a; |
---|
236 | } |
---|
237 | while (d != dEnd) { fputs(d->c_str(), out); ++d; } |
---|
238 | while (a != aEnd) { fputs(a->c_str(), out); ++a; } |
---|
239 | } |
---|
240 | |
---|
241 | void print(FILE *out) const { |
---|
242 | LinesCIter d = deleted_lines.begin(); |
---|
243 | LinesCIter a = added_lines.begin(); |
---|
244 | print_from(out, a, d); |
---|
245 | } |
---|
246 | }; |
---|
247 | |
---|
248 | // AISC_MKPT_PROMOTE:enum TextDiffMode { TDM_DIFF_LINECOUNT=0, TDM_IGNORE_TIMESTAMPS=1, TDM_NOT_DIFF_LINECOUNT=2 }; |
---|
249 | |
---|
250 | bool ARB_textfiles_have_difflines(const char *file1, const char *file2, int expected_difflines, TextDiffMode tdmode) { |
---|
251 | // tdmode & TDM_IGNORE_TIMESTAMPS -> accept date and time changes as equal |
---|
252 | // |
---|
253 | // Warning: also returns true if comparing two EQUAL binary files. |
---|
254 | // But it always fails if one file is binary and files differ |
---|
255 | // |
---|
256 | // Always fails if one or both files are missing. |
---|
257 | |
---|
258 | const char *error = NULp; |
---|
259 | |
---|
260 | if (!GB_is_regularfile(file1)) error = GBS_global_string("No such file '%s'", file1); |
---|
261 | else if (!GB_is_regularfile(file2)) error = GBS_global_string("No such file '%s'", file2); |
---|
262 | else { |
---|
263 | char *cmd = GBS_global_string_copy("/usr/bin/diff --unified %s %s", file1, file2); |
---|
264 | FILE *diffout = popen(cmd, "r"); |
---|
265 | |
---|
266 | if (diffout) { |
---|
267 | #define BUFSIZE 20000 |
---|
268 | char *buffer = ARB_alloc<char>(BUFSIZE); |
---|
269 | bool inHunk = false; |
---|
270 | DiffLines diff_lines; |
---|
271 | difflineMode mode(tdmode & TDM_IGNORE_TIMESTAMPS); |
---|
272 | |
---|
273 | // TEST_EXPECT_NO_ERROR(mode.get_error()); |
---|
274 | error = mode.get_error(); |
---|
275 | |
---|
276 | while (!error && !feof(diffout)) { |
---|
277 | char *line = fgets(buffer, BUFSIZE, diffout); |
---|
278 | if (!line) break; |
---|
279 | |
---|
280 | #if defined(ASSERTION_USED) |
---|
281 | size_t len = strlen(line); |
---|
282 | arb_assert(len<(BUFSIZE-1)); // increase BUFSIZE |
---|
283 | #endif |
---|
284 | |
---|
285 | if (ARB_strBeginsWith(line, "Binary files")) { |
---|
286 | if (strstr(line, "differ")) { |
---|
287 | error = "attempt to compare binary files"; |
---|
288 | } |
---|
289 | } |
---|
290 | else { |
---|
291 | bool remove_now = true; |
---|
292 | if (strncmp(line, "@@", 2) == 0) inHunk = true; |
---|
293 | else if (!inHunk && strncmp(line, "Index: ", 7) == 0) inHunk = false; |
---|
294 | else if (inHunk) { |
---|
295 | remove_now = !diff_lines.add(line); |
---|
296 | } |
---|
297 | |
---|
298 | if (remove_now) diff_lines.remove_accepted_lines(mode); |
---|
299 | } |
---|
300 | } |
---|
301 | |
---|
302 | if (!error) { |
---|
303 | diff_lines.remove_accepted_lines(mode); |
---|
304 | |
---|
305 | int added = diff_lines.added(); |
---|
306 | int deleted = diff_lines.deleted(); |
---|
307 | bool dump_diffs = false; |
---|
308 | |
---|
309 | if (added != deleted) { |
---|
310 | error = GBS_global_string("added lines (=%i) differ from deleted lines(=%i)", added, deleted); |
---|
311 | dump_diffs = true; |
---|
312 | } |
---|
313 | else { |
---|
314 | // here 'added' contains "changed lines" |
---|
315 | bool matches_expected = added == expected_difflines; |
---|
316 | bool want_expected = (tdmode & TDM_NOT_DIFF_LINECOUNT) == 0; |
---|
317 | |
---|
318 | if (matches_expected != want_expected) { // fail |
---|
319 | error = GBS_global_string("files differ in %i lines (expected: %s%i)", |
---|
320 | added, |
---|
321 | want_expected ? "" : "not ", |
---|
322 | expected_difflines); |
---|
323 | |
---|
324 | dump_diffs = added != 0; |
---|
325 | } |
---|
326 | } |
---|
327 | |
---|
328 | if (dump_diffs) { |
---|
329 | fputs("Different lines:\n", stdout); |
---|
330 | diff_lines.print(stdout); |
---|
331 | fputc('\n', stdout); |
---|
332 | } |
---|
333 | } |
---|
334 | free(buffer); |
---|
335 | IF_ASSERTION_USED(int err =) pclose(diffout); |
---|
336 | arb_assert(err != -1); |
---|
337 | #undef BUFSIZE |
---|
338 | } |
---|
339 | else { |
---|
340 | error = GBS_global_string("failed to run diff (%s)", cmd); |
---|
341 | } |
---|
342 | free(cmd); |
---|
343 | } |
---|
344 | // return result; |
---|
345 | if (error) printf("ARB_textfiles_have_difflines(%s, %s) fails: %s\n", file1, file2, error); |
---|
346 | return !error; |
---|
347 | } |
---|
348 | |
---|
349 | #ifdef UNIT_TESTS |
---|
350 | #include <test_unit.h> |
---|
351 | |
---|
352 | size_t ARB_test_mem_equal(const unsigned char *buf1, const unsigned char *buf2, size_t common, size_t blockStartAddress) { |
---|
353 | size_t equal_bytes; |
---|
354 | if (memcmp(buf1, buf2, common) == 0) { |
---|
355 | equal_bytes = common; |
---|
356 | } |
---|
357 | else { |
---|
358 | equal_bytes = 0; |
---|
359 | size_t x = 0; // position inside memory |
---|
360 | while (buf1[x] == buf2[x]) { |
---|
361 | x++; |
---|
362 | equal_bytes++; |
---|
363 | } |
---|
364 | |
---|
365 | const size_t DUMP = 7; |
---|
366 | size_t y1 = x >= DUMP ? x-DUMP : 0; |
---|
367 | size_t y2 = (x+2*DUMP)>common ? common : (x+2*DUMP); |
---|
368 | size_t blockstart = blockStartAddress+equal_bytes-x; |
---|
369 | |
---|
370 | for (size_t y = y1; y <= y2; y++) { |
---|
371 | fprintf(stderr, "[0x%04zx]", blockstart+y); |
---|
372 | arb_test::print_pair(buf1[y], buf2[y]); |
---|
373 | fputc(' ', stderr); |
---|
374 | arb_test::print_hex_pair(buf1[y], buf2[y]); |
---|
375 | if (buf1[y] != buf2[y]) fputs(" <- diff", stderr); |
---|
376 | fputc('\n', stderr); |
---|
377 | } |
---|
378 | if (y2 == common) { |
---|
379 | fputs("[end of block - truncated]\n", stderr); |
---|
380 | } |
---|
381 | } |
---|
382 | return equal_bytes; |
---|
383 | } |
---|
384 | |
---|
385 | static GB_ERROR compare_files(const char *file1, const char *file2, bool error_if_different) { |
---|
386 | const char *error = NULp; |
---|
387 | FILE *fp1 = fopen(file1, "rb"); |
---|
388 | |
---|
389 | if (!fp1) { |
---|
390 | printf("can't open '%s'\n", file1); |
---|
391 | error = "i/o error"; |
---|
392 | } |
---|
393 | else { |
---|
394 | FILE *fp2 = fopen(file2, "rb"); |
---|
395 | if (!fp2) { |
---|
396 | printf("can't open '%s'\n", file2); |
---|
397 | error = "i/o error"; |
---|
398 | } |
---|
399 | else { |
---|
400 | const int BLOCKSIZE = 4096; |
---|
401 | unsigned char *buf1 = ARB_alloc<unsigned char>(BLOCKSIZE); |
---|
402 | unsigned char *buf2 = ARB_alloc<unsigned char>(BLOCKSIZE); |
---|
403 | size_t equal_bytes = 0; |
---|
404 | |
---|
405 | while (!error) { |
---|
406 | int read1 = fread(buf1, 1, BLOCKSIZE, fp1); |
---|
407 | int read2 = fread(buf2, 1, BLOCKSIZE, fp2); |
---|
408 | size_t common = read1<read2 ? read1 : read2; |
---|
409 | |
---|
410 | if (!common) { |
---|
411 | if (read1 != read2) error = "filesize differs"; |
---|
412 | break; |
---|
413 | } |
---|
414 | |
---|
415 | size_t eqCount = ARB_test_mem_equal(buf1, buf2, common, equal_bytes); |
---|
416 | if (eqCount != common) { |
---|
417 | error = "content differs"; |
---|
418 | } |
---|
419 | equal_bytes += eqCount; |
---|
420 | } |
---|
421 | |
---|
422 | if (error) printf("compare_files: equal_bytes=%zu\n", equal_bytes); |
---|
423 | arb_assert(error || equal_bytes); // comparing empty files is nonsense |
---|
424 | |
---|
425 | free(buf2); |
---|
426 | free(buf1); |
---|
427 | fclose(fp2); |
---|
428 | |
---|
429 | if (!error_if_different) error = error ? NULp : "files are equal"; |
---|
430 | } |
---|
431 | fclose(fp1); |
---|
432 | } |
---|
433 | |
---|
434 | return error; |
---|
435 | } |
---|
436 | |
---|
437 | |
---|
438 | bool ARB_files_are_equal(const char *file1, const char *file2) { |
---|
439 | GB_ERROR error = compare_files(file1, file2, true); |
---|
440 | if (error) printf("ARB_files_are_equal(%s, %s) fails: %s\n", file1, file2, error); |
---|
441 | return !error; |
---|
442 | } |
---|
443 | bool ARB_files_differ(const char *file1, const char *file2) { // used via unittest macros (e.g. TEST_EXPECT_FILES_DIFFER) |
---|
444 | GB_ERROR error = compare_files(file1, file2, false); |
---|
445 | if (error) printf("ARB_files_differ(%s, %s) fails: %s\n", file1, file2, error); |
---|
446 | return !error; |
---|
447 | } |
---|
448 | |
---|
449 | void TEST_diff_files() { |
---|
450 | // files are in ../UNIT_TESTER/run/diff |
---|
451 | const char *file = "diff/base.input"; |
---|
452 | const char *file_swapped = "diff/swapped.input"; // same dates as 'base', but 2 other lines swapped |
---|
453 | const char *file_date_swapped = "diff/date_swapped.input"; // compared to 'base': 3 pairs of date-lines were swapped |
---|
454 | // compared to 'swapped': 3 pairs of date-lines were swapped + 2 other lines were swapped |
---|
455 | const char *file_date_changed = "diff/date_changed.input"; // compared to 'base' only dates were changed; |
---|
456 | // compared to 'swapped' dates were changed and 2 other lines were swapped |
---|
457 | // compared to 'date_swapped' dates were changed |
---|
458 | const char *file_missing = "diff/nosuch.input"; |
---|
459 | |
---|
460 | TEST_EXPECT(ARB_textfiles_have_difflines(file, file, 0, TDM_DIFF_LINECOUNT)); // check identity |
---|
461 | |
---|
462 | TEST_EXPECT(ARB_textfiles_have_difflines(file, file_swapped, 0, TDM_NOT_DIFF_LINECOUNT)); // check difference (will fail when files get equal) |
---|
463 | TEST_REJECT(ARB_textfiles_have_difflines(file, file, 0, TDM_NOT_DIFF_LINECOUNT)); // failing difference-test (reason: files are equal) |
---|
464 | TEST_REJECT(ARB_textfiles_have_difflines(file, file_missing, 0, TDM_NOT_DIFF_LINECOUNT)); // failing difference-test (reason: not all files exist) |
---|
465 | |
---|
466 | // check if swapped lines are detected properly |
---|
467 | TEST_EXPECT(ARB_textfiles_have_difflines(file, file_swapped, 1, TDM_DIFF_LINECOUNT)); |
---|
468 | TEST_EXPECT(ARB_textfiles_have_difflines(file, file_swapped, 1, TDM_IGNORE_TIMESTAMPS)); |
---|
469 | |
---|
470 | TEST_EXPECT(ARB_textfiles_have_difflines(file, file_date_swapped, 3, TDM_DIFF_LINECOUNT)); |
---|
471 | TEST_EXPECT(ARB_textfiles_have_difflines(file, file_date_swapped, 3, TDM_IGNORE_TIMESTAMPS)); |
---|
472 | |
---|
473 | TEST_EXPECT(ARB_textfiles_have_difflines(file, file_date_changed, 6, TDM_DIFF_LINECOUNT)); |
---|
474 | TEST_EXPECT(ARB_textfiles_have_difflines(file, file_date_changed, 0, TDM_IGNORE_TIMESTAMPS)); |
---|
475 | |
---|
476 | TEST_EXPECT(ARB_textfiles_have_difflines(file_swapped, file_date_swapped, 4, TDM_DIFF_LINECOUNT)); |
---|
477 | TEST_EXPECT(ARB_textfiles_have_difflines(file_swapped, file_date_swapped, 4, TDM_IGNORE_TIMESTAMPS)); |
---|
478 | |
---|
479 | TEST_EXPECT(ARB_textfiles_have_difflines(file_swapped, file_date_changed, 7, TDM_DIFF_LINECOUNT)); |
---|
480 | TEST_EXPECT(ARB_textfiles_have_difflines(file_swapped, file_date_changed, 1, TDM_IGNORE_TIMESTAMPS)); |
---|
481 | |
---|
482 | TEST_EXPECT(ARB_textfiles_have_difflines(file_date_swapped, file_date_changed, 6, TDM_DIFF_LINECOUNT)); |
---|
483 | TEST_EXPECT(ARB_textfiles_have_difflines(file_date_swapped, file_date_changed, 0, TDM_IGNORE_TIMESTAMPS)); |
---|
484 | |
---|
485 | // test combination of TDM_NOT_DIFF_LINECOUNT and TDM_IGNORE_TIMESTAMPS: |
---|
486 | TEST_EXPECT(ARB_textfiles_have_difflines(file_swapped, file_date_changed, 0, TextDiffMode(TDM_NOT_DIFF_LINECOUNT|TDM_IGNORE_TIMESTAMPS))); // check difference beyond date-lines |
---|
487 | TEST_REJECT(ARB_textfiles_have_difflines(file, file_date_changed, 0, TextDiffMode(TDM_NOT_DIFF_LINECOUNT|TDM_IGNORE_TIMESTAMPS))); // failing difference-test (reason: only dates differ) |
---|
488 | |
---|
489 | const char *binary = "TEST_gpt.arb.expected"; // a binary arb DB |
---|
490 | const char *binary2 = "TEST_gpt.arb.pt.expected"; // a ptserver index |
---|
491 | const char *text = file; |
---|
492 | |
---|
493 | // diff between text and binary should fail |
---|
494 | TEST_REJECT(ARB_textfiles_have_difflines(text, binary, 0, TDM_DIFF_LINECOUNT)); |
---|
495 | TEST_REJECT(ARB_textfiles_have_difflines(binary, text, 0, TDM_DIFF_LINECOUNT)); |
---|
496 | TEST_REJECT(ARB_textfiles_have_difflines(binary2, text, 0, TDM_DIFF_LINECOUNT)); |
---|
497 | TEST_REJECT(ARB_textfiles_have_difflines(text, binary2, 0, TDM_DIFF_LINECOUNT)); |
---|
498 | |
---|
499 | // diff between two binaries shall fails as well .. |
---|
500 | TEST_REJECT(ARB_textfiles_have_difflines(binary, binary2, 0, TDM_DIFF_LINECOUNT)); |
---|
501 | TEST_REJECT(ARB_textfiles_have_difflines(binary2, binary, 0, TDM_DIFF_LINECOUNT)); |
---|
502 | |
---|
503 | // when files do not differ, ARB_textfiles_have_difflines always |
---|
504 | // returns true - even if the files are binary |
---|
505 | // (unwanted but accepted behavior) |
---|
506 | TEST_EXPECT(ARB_textfiles_have_difflines(binary, binary, 0, TDM_DIFF_LINECOUNT)); |
---|
507 | TEST_EXPECT(ARB_textfiles_have_difflines(binary2, binary2, 0, TDM_DIFF_LINECOUNT)); |
---|
508 | TEST_EXPECT(ARB_textfiles_have_difflines(binary2, binary2, 0, TDM_DIFF_LINECOUNT)); |
---|
509 | TEST_EXPECT(ARB_textfiles_have_difflines(text, text, 0, TDM_DIFF_LINECOUNT)); |
---|
510 | } |
---|
511 | |
---|
512 | // -------------------------------------------------------------------------------- |
---|
513 | // tests for global code included from central ARB headers (located in $ARBHOME/TEMPLATES) |
---|
514 | |
---|
515 | // gcc reports: "warning: logical 'or' of collectively exhaustive tests is always true" |
---|
516 | // for 'implicated(any, any)'. True, obviously. Nevertheless annoying. |
---|
517 | #pragma GCC diagnostic ignored "-Wlogical-op" |
---|
518 | |
---|
519 | void TEST_logic() { |
---|
520 | #define FOR_ANY_BOOL(name) for (int name = 0; name<2; ++name) |
---|
521 | |
---|
522 | TEST_EXPECT(implicated(true, true)); |
---|
523 | // for (int any = 0; any<2; ++any) { |
---|
524 | FOR_ANY_BOOL(any) { |
---|
525 | TEST_EXPECT(implicated(false, any)); // "..aus Falschem folgt Beliebiges.." |
---|
526 | TEST_EXPECT(implicated(any, any)); |
---|
527 | |
---|
528 | TEST_EXPECT(correlated(any, any)); |
---|
529 | TEST_REJECT(correlated(any, !any)); |
---|
530 | TEST_REJECT(contradicted(any, any)); |
---|
531 | TEST_EXPECT(contradicted(any, !any)); |
---|
532 | } |
---|
533 | |
---|
534 | TEST_EXPECT(correlated(false, false)); |
---|
535 | |
---|
536 | TEST_EXPECT(contradicted(true, false)); |
---|
537 | TEST_EXPECT(contradicted(false, true)); |
---|
538 | |
---|
539 | FOR_ANY_BOOL(kermitIsFrog) { |
---|
540 | FOR_ANY_BOOL(kermitIsGreen) { |
---|
541 | bool allFrogsAreGreen = implicated(kermitIsFrog, kermitIsGreen); |
---|
542 | bool onlyFrogsAreGreen = implicated(kermitIsGreen, kermitIsFrog); |
---|
543 | |
---|
544 | TEST_EXPECT(implicated( kermitIsFrog && allFrogsAreGreen, kermitIsGreen)); |
---|
545 | TEST_EXPECT(implicated(!kermitIsGreen && allFrogsAreGreen, !kermitIsFrog)); |
---|
546 | |
---|
547 | TEST_EXPECT(implicated( kermitIsGreen && onlyFrogsAreGreen, kermitIsFrog)); |
---|
548 | TEST_EXPECT(implicated(!kermitIsFrog && onlyFrogsAreGreen, !kermitIsGreen)); |
---|
549 | |
---|
550 | TEST_EXPECT(implicated(kermitIsFrog && !kermitIsGreen, !allFrogsAreGreen)); |
---|
551 | TEST_EXPECT(implicated(kermitIsGreen && !kermitIsFrog, !onlyFrogsAreGreen)); |
---|
552 | |
---|
553 | TEST_EXPECT(correlated( |
---|
554 | correlated(kermitIsGreen, kermitIsFrog), |
---|
555 | allFrogsAreGreen && onlyFrogsAreGreen |
---|
556 | )); |
---|
557 | } |
---|
558 | } |
---|
559 | } |
---|
560 | |
---|
561 | #include <cmath> |
---|
562 | // #include <math.h> |
---|
563 | |
---|
564 | void TEST_naninf() { |
---|
565 | double num = 3.1415; |
---|
566 | double zero = num-num; |
---|
567 | double inf = num/zero; |
---|
568 | double ninf = -inf; |
---|
569 | double nan = 0*inf; |
---|
570 | |
---|
571 | TEST_EXPECT_DIFFERENT(inf, ninf); |
---|
572 | TEST_EXPECT_EQUAL(ninf, -1.0/zero); |
---|
573 | |
---|
574 | // decribe how isnan, isinf and isnormal shall behave: |
---|
575 | #define TEST_ISINF(isinf_fun) \ |
---|
576 | TEST_EXPECT(isinf_fun(inf)); \ |
---|
577 | TEST_EXPECT(!!isinf_fun(ninf)); \ |
---|
578 | TEST_EXPECT(isinf_fun(INFINITY)); \ |
---|
579 | TEST_EXPECT(!isinf_fun(nan)); \ |
---|
580 | TEST_EXPECT(!isinf_fun(NAN)); \ |
---|
581 | TEST_EXPECT(!isinf_fun(num)); \ |
---|
582 | TEST_EXPECT(!isinf_fun(zero)) |
---|
583 | |
---|
584 | #define TEST_ISNAN(isnan_fun) \ |
---|
585 | TEST_EXPECT(isnan_fun(nan)); \ |
---|
586 | TEST_EXPECT(isnan_fun(NAN)); \ |
---|
587 | TEST_EXPECT(!isnan_fun(inf)); \ |
---|
588 | TEST_EXPECT(!isnan_fun(ninf)); \ |
---|
589 | TEST_EXPECT(!isnan_fun(INFINITY)); \ |
---|
590 | TEST_EXPECT(!isnan_fun(zero)); \ |
---|
591 | TEST_EXPECT(!isnan_fun(num)) |
---|
592 | |
---|
593 | #define TEST_ISNORMAL(isnormal_fun) \ |
---|
594 | TEST_EXPECT(isnormal_fun(num)); \ |
---|
595 | TEST_EXPECT(!isnormal_fun(zero)); \ |
---|
596 | TEST_EXPECT(!isnormal_fun(inf)); \ |
---|
597 | TEST_EXPECT(!isnormal_fun(ninf)); \ |
---|
598 | TEST_EXPECT(!isnormal_fun(nan)); \ |
---|
599 | TEST_EXPECT(!isnormal_fun(INFINITY)); \ |
---|
600 | TEST_EXPECT(!isnormal_fun(NAN)) |
---|
601 | |
---|
602 | // check behavior of arb templates: |
---|
603 | TEST_ISNAN(is_nan); |
---|
604 | TEST_ISINF(is_inf); |
---|
605 | TEST_ISNORMAL(is_normal); |
---|
606 | |
---|
607 | #if !defined(DARWIN) |
---|
608 | // TEST_DISABLED_OSX: section fails compilation under OSX |
---|
609 | # if (GCC_PATCHLEVEL_CODE < 50301) |
---|
610 | // (section leads to compile error with gcc 5.3.1 - suggesting std::isnan and std::isinf) |
---|
611 | // document behavior of math.h macros: |
---|
612 | TEST_ISNAN(isnan); |
---|
613 | # if (GCC_PATCHLEVEL_CODE >= 40403 && GCC_PATCHLEVEL_CODE <= 40407 && defined(DEBUG)) |
---|
614 | // TEST_ISINF(isinf); // isinf macro is broken (gcc 4.4.3 up to gcc 4.4.7, if compiled with DEBUG) |
---|
615 | TEST_EXPECT__BROKEN(isinf(ninf)); // broken; contradicts http://www.cplusplus.com/reference/cmath/isinf/ |
---|
616 | # else |
---|
617 | TEST_ISINF(isinf); |
---|
618 | # endif |
---|
619 | // TEST_ISNORMAL(isnormal); // ok if <math.h> included, compile error if <cmath> included |
---|
620 | # endif |
---|
621 | #endif // DARWIN |
---|
622 | |
---|
623 | // check behavior of std-versions: |
---|
624 | TEST_ISNAN(std::isnan); |
---|
625 | TEST_ISINF(std::isinf); |
---|
626 | TEST_ISNORMAL(std::isnormal); |
---|
627 | } |
---|
628 | |
---|
629 | #endif // UNIT_TESTS |
---|