source: branches/stable/CORE/arb_diff.cxx

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