| 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 |
|---|