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