source: tags/ms_r16q2/CORE/arb_diff.cxx

Last change on this file was 14798, checked in by westram, 8 years ago
  • fix Ubuntu 16.04 build
    • add gcc 5.3.1
      • fix isnan/isinf use (only in testcode)
    • fix libdepends in install-scripts and -READMEs
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.3 KB
Line 
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
31class 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
72public:
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
117private:
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    }
139public:
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
148static GB_ERROR            static_error               = NULL;
149difflineMode::GBS_regexPtr difflineMode::contains_May = GBS_compile_regexpr("May", GB_IGNORE_CASE, &static_error);
150
151static void cutEOL(char *s) {
152    char *lf      = strchr(s, '\n');
153    if (lf) lf[0] = 0;
154}
155
156static 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
179class 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
198public:
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
254bool 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
341size_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
374bool 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
425void 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
475void 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
520void 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
Note: See TracBrowser for help on using the repository browser.