source: tags/arb-6.0/SL/TREE_READ/TreeRead.cxx

Last change on this file was 11488, checked in by westram, 10 years ago
  • reintegrates 'tree' into 'trunk'
    • implements #417 (multifurcate tree)
    • tree display
      • adds MULTIFURC MODE
      • reordered modes (synchronizes NTREE and PARSIMONY)
    • branch analysis
      • display number of multifurcations in 'mark long branches'
      • display "in-tree-distance" and "per-species-distance"
    • added function to toggle '100%' bootstraps
    • document bug in GBT_remove_leafs (#452)
  • adds:
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.0 KB
Line 
1// ============================================================ //
2//                                                              //
3//   File      : TreeRead.cxx                                   //
4//   Purpose   : load tree from file                            //
5//                                                              //
6//   Institute of Microbiology (Technical University Munich)    //
7//   www.arb-home.de                                            //
8//                                                              //
9// ============================================================ //
10
11#include "TreeRead.h"
12
13#include <arb_msg_fwd.h>
14#include <arb_strbuf.h>
15#include <arb_file.h>
16#include <arb_defs.h>
17#include <algorithm>
18
19#define tree_assert(cond) arb_assert(cond)
20
21/*!******************************************************************************************
22                    load a tree from file system
23********************************************************************************************/
24
25
26// --------------------
27//      TreeReader
28
29class TreeReader : virtual Noncopyable {
30    enum tr_lfmode { LF_UNKNOWN, LF_N, LF_R, LF_NR, LF_RN, };
31
32    int   unnamed_counter;
33    char *tree_file_name;
34    FILE *in;
35    int   last_character;          // may be EOF
36    int   line_cnt;
37
38    GBS_strstruct tree_comment;
39    GBT_LEN       max_found_branchlen;
40    double        max_found_bootstrap;
41    tr_lfmode     lfmode;
42
43    char *warnings;
44
45    const TreeNodeFactory& nodeMaker;
46
47    void setError(const char *message);
48    void setErrorAt(const char *message);
49    void setExpectedError(const char *expected);
50
51    int get_char();
52    int read_char();
53    int read_tree_char(); // extracts comments and ignores whitespace outside comments
54
55    char *content_ahead(size_t how_many, bool show_eof);
56
57    void drop_tree_char(char expected);
58
59    void setBranchName_acceptingBootstrap(GBT_TREE *node, char*& name);
60
61    // The eat-functions below assume that the "current" character
62    // has already been read into 'last_character':
63    void  eat_white();
64    __ATTR__USERESULT bool eat_number(GBT_LEN& result);
65    char *eat_quoted_string();
66    bool  eat_and_set_name_and_length(GBT_TREE *node, GBT_LEN& len);
67
68    char *unnamedNodeName() { return GBS_global_string_copy("unnamed%i", ++unnamed_counter); }
69
70    GBT_TREE *load_subtree(GBT_LEN& nodeLen);
71    GBT_TREE *load_named_node(GBT_LEN& nodeLen);
72
73public:
74
75    TreeReader(FILE *input, const char *file_name, const TreeNodeFactory& nodeMaker_);
76    ~TreeReader();
77
78    GBT_TREE *load() {
79        GBT_LEN   rootNodeLen = DEFAULT_BRANCH_LENGTH_MARKER; // ignored dummy
80        GBT_TREE *tree        = load_named_node(rootNodeLen);
81
82        if (!error) {
83            if (rootNodeLen != DEFAULT_BRANCH_LENGTH_MARKER && rootNodeLen != 0.0) {
84                add_warning("Length specified for root-node has been ignored");
85            }
86
87            // check for unexpected input
88            if (last_character == ';') read_tree_char(); // accepts ';'
89            if (last_character != EOF) {
90                char *unused_input = content_ahead(30, false);
91                add_warningf("Unexpected input-data after tree: '%s'", unused_input);
92                free(unused_input);
93            }
94            tree->announce_tree_constructed();
95        }
96        return tree;
97    }
98
99    GB_ERROR error;
100
101    void add_warning(const char *msg) {
102        if (warnings) freeset(warnings, GBS_global_string_copy("%s\n%s", warnings, msg));
103        else warnings = GBS_global_string_copy("Warning(s): %s", msg);
104    }
105    __ATTR__FORMAT(2) void add_warningf(const char *format, ...) { FORWARD_FORMATTED(add_warning, format); }
106
107    GB_ERROR get_warnings() const { return warnings; } // valid until TreeReader is destroyed
108
109    char *takeComment() {
110        // can only be called once (further calls will return NULL)
111        return tree_comment.release();
112    }
113
114    double get_max_found_bootstrap() const { return max_found_bootstrap; }
115    GBT_LEN get_max_found_branchlen() const { return max_found_branchlen; }
116};
117
118TreeReader::TreeReader(FILE *input, const char *file_name, const TreeNodeFactory& nodeMaker_)
119    : unnamed_counter(0),
120      tree_file_name(strdup(file_name)),
121      in(input),
122      last_character(0),
123      line_cnt(1),
124      tree_comment(2048),
125      max_found_branchlen(-1),
126      max_found_bootstrap(-1),
127      lfmode(LF_UNKNOWN),
128      warnings(NULL),
129      nodeMaker(nodeMaker_),
130      error(NULL)
131{
132    read_tree_char();
133}
134
135TreeReader::~TreeReader() {
136    free(warnings);
137    free(tree_file_name);
138}
139
140void TreeReader::setError(const char *message) {
141    tree_assert(!error);
142    error = GBS_global_string("Error reading %s:%i: %s",
143                              tree_file_name, line_cnt, message);
144}
145char *TreeReader::content_ahead(size_t how_many, bool show_eof) {
146    char show[how_many+1+4]; // 4 = oversize of '<EOF>'
147    size_t i;
148    for (i = 0; i<how_many; ++i) {
149        show[i] = last_character;
150        if (show[i] == EOF) {
151            if (show_eof) {
152                strcpy(show+i, "<EOF>");
153                i += 5;
154            }
155            break;
156        }
157        read_char();
158    }
159    show[i] = 0;
160    return strdup(show);
161}
162
163void TreeReader::setErrorAt(const char *message) {
164    if (last_character == EOF) {
165        setError(GBS_global_string("%s while end-of-file was reached", message));
166    }
167    else {
168        char *show = content_ahead(30, true);
169        setError(GBS_global_string("%s while looking at '%s'", message, show));
170        free(show);
171    }
172}
173
174void TreeReader::setExpectedError(const char *expected) {
175    setErrorAt(GBS_global_string("Expected %s", expected));
176}
177
178int TreeReader::get_char() {
179    // reads character from stream
180    // - converts linefeeds for DOS- and MAC-textfiles
181    // - increments line_cnt
182
183    int c   = getc(in);
184    int inc = 0;
185
186    if (c == '\n') {
187        switch (lfmode) {
188            case LF_UNKNOWN: lfmode = LF_N; inc = 1; break;
189            case LF_N:       inc = 1; break;
190            case LF_R:       lfmode = LF_RN; c = get_char(); break;
191            case LF_NR:      c = get_char(); break;
192            case LF_RN:      inc = 1; break;
193        }
194    }
195    else if (c == '\r') {
196        switch (lfmode) {
197            case LF_UNKNOWN: lfmode = LF_R; inc = 1; break;
198            case LF_R:       inc = 1; break;
199            case LF_N:       lfmode = LF_NR; c = get_char(); break;
200            case LF_RN:      c = get_char(); break;
201            case LF_NR:      inc = 1; break;
202        }
203        if (c == '\r') c = '\n';     // never report '\r'
204    }
205    if (inc) line_cnt++;
206
207    return c;
208}
209
210int TreeReader::read_tree_char() {
211    // reads over tree comment(s) and whitespace.
212    // tree comments are stored inside TreeReader
213
214    bool done = false;
215    int  c    = ' ';
216
217    while (!done && !error) {
218        c = get_char();
219        if (c == ' ' || c == '\t' || c == '\n') ; // skip
220        else if (c == '[') {    // collect tree comment(s)
221            int openBrackets = 1;
222            if (tree_comment.get_position()) {
223                tree_comment.put('\n'); // not first comment -> add new line
224            }
225
226            while (openBrackets && !error) {
227                c = get_char();
228                switch (c) {
229                    case EOF:
230                        setError("Reached end of file while reading comment");
231                        break;
232                    case ']':
233                        openBrackets--;
234                        if (openBrackets) tree_comment.put(c); // write all but last closing brackets
235                        break;
236                    case '[':
237                        openBrackets++;
238                        // fall-through
239                    default:
240                        tree_comment.put(c);
241                        break;
242                }
243            }
244        }
245        else done = true;
246    }
247
248    last_character = c;
249    return c;
250}
251
252int TreeReader::read_char() {
253    int c = get_char();
254    last_character = c;
255    return c;
256}
257
258void TreeReader::eat_white() {
259    int c = last_character;
260    while ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t')) {
261        c = read_char();
262    }
263}
264
265bool TreeReader::eat_number(GBT_LEN& result) {
266    char    strng[256];
267    char   *s = strng;
268    int     c = last_character;
269
270    while (((c<='9') && (c>='0')) || (c=='.') || (c=='-') ||  (c=='+') || (c=='e') || (c=='E')) {
271        *(s++) = c;
272        c = read_char();
273    }
274    *s     = 0;
275    result = GB_atof(strng);
276    eat_white();
277
278    bool consumed_some_length = strng[0];
279    return consumed_some_length;
280}
281
282char *TreeReader::eat_quoted_string() {
283    /*! Read in a quoted or unquoted string.
284     * in quoted strings double quotes ('') are replaced by (').
285     *
286     * @return
287     *     NULL in case of error,
288     *     "" if no string present,
289     *     otherwise the found string.
290     */
291
292    const int MAX_NAME_LEN = 1000;
293
294    char  buffer[MAX_NAME_LEN+2];
295    char *s = buffer;
296    int   c = last_character;
297
298#define NAME_TOO_LONG ((s-buffer)>MAX_NAME_LEN)
299
300    if (c == '\'' || c == '"') {
301        char found_quote = c;
302
303        c = read_char();
304        while (c!=EOF && c!=found_quote) {
305            *(s++) = c;
306            if (NAME_TOO_LONG) { c = 0; break; }
307            c = read_char();
308        }
309        if (c == found_quote) c = read_tree_char();
310    }
311    else {
312#if 0
313        // previous behavior: skip prefixes matching PRE '_* *'
314        // (reason unknown; behavior exists since [2])
315        // conflicts with replacement of problematic character done in ../TREE_WRITE/TreeWrite.cxx@replace_by_underscore
316        // -> disabled
317        while (c == '_') c = read_tree_char();
318        while (c == ' ') c = read_tree_char();
319#endif
320        while (c!=':' && c!=EOF && c!=',' && c!=';' && c != ')') {
321            *(s++) = c;
322            if (NAME_TOO_LONG) break;
323            c = read_tree_char();
324        }
325    }
326    *s = 0;
327    if (NAME_TOO_LONG) {
328        setError(GBS_global_string("Name '%s' is longer than %i bytes", buffer, MAX_NAME_LEN));
329        return NULL;
330    }
331    return strdup(buffer);
332}
333
334void TreeReader::setBranchName_acceptingBootstrap(GBT_TREE *node, char*& name) {
335    // store groupname and/or bootstrap value.
336    //
337    // ARBs extended newick format allows 3 kinds of node-names:
338    //     'groupname'
339    //     'bootstrap'
340    //     'bootstrap:groupname' (needs to be quoted)
341    //
342    // where
343    //     'bootstrap' is sth interpretable as double (optionally followed by '%')
344    //     'groupname' is sth not interpretable as double
345    //
346    // If a groupname is detected, it is stored in node->name
347    // If a bootstrap is detected, it is stored in node->remark_branch
348    //
349    // Bootstrap values will be scaled up by factor 100.
350    // Wrong scale-ups (to 10000) will be corrected by calling TREE_scale() after the whole tree has been loaded.
351
352    char *new_name = NULL;
353    {
354        char   *end       = 0;
355        double  bootstrap = strtod(name, &end);
356
357        bool is_bootstrap = (end != name);
358        if (is_bootstrap) {
359            if (end[0] == '%') {
360                ++end;
361                bootstrap = bootstrap/100.0; // percent -> [0..1]
362            }
363            is_bootstrap = end[0] == ':' || !end[0]; // followed by ':' or at EOS
364        }
365
366        if (is_bootstrap) {
367            bootstrap = bootstrap*100.0; // needed if bootstrap values are between 0.0 and 1.0 (downscaling is done later)
368            if (bootstrap > max_found_bootstrap) { max_found_bootstrap = bootstrap; }
369
370            if (node->get_remark()) {
371                error = "Invalid duplicated bootstrap specification detected";
372            }
373            else {
374                node->set_bootstrap(bootstrap);
375            }
376
377            if (end[0] != 0) {      // sth behind bootstrap value
378                arb_assert(end[0] == ':');
379                new_name = strdup(end+1);
380            }
381            free(name);
382        }
383        else {
384            new_name = name; // use whole input as groupname
385        }
386        name = NULL;
387    }
388    if (new_name) {
389        if (node->name) {
390            if (node->is_leaf) {
391                add_warningf("Dropped group name specified for a single-node-subtree ('%s')\n", new_name);
392                freenull(new_name);
393            }
394            else {
395                add_warningf("Duplicated group name specification detected: dropped inner ('%s'), kept outer group name ('%s')\n",
396                             node->name, new_name);
397                freeset(node->name, new_name);
398            }
399        }
400        else {
401            node->name = new_name;
402        }
403    }
404}
405
406void TreeReader::drop_tree_char(char expected) {
407    if (last_character != expected) {
408        setExpectedError(GBS_global_string("'%c'", expected));
409    }
410    read_tree_char();
411}
412
413bool TreeReader::eat_and_set_name_and_length(GBT_TREE *node, GBT_LEN& nodeLen) {
414    // reads optional branch-length and -name
415    //
416    // if 'nodeLen' contains DEFAULT_BRANCH_LENGTH_MARKER, it gets overwritten with any found length-specification
417    // otherwise found length is added to 'nodeLen'
418    //
419    // sets the branch-name of 'node', if a name is found (e.g. sth like "(...)'name':0.5")
420    //
421    // returns true if successful, false otherwise (TreeReader::error is set then)
422
423    bool done            = false;
424    bool length_consumed = false;
425
426    while (!done && !error) {
427        switch (last_character) {
428            case ';':
429            case ',':
430            case ')':
431                done = true;
432                break;
433            case ':':
434                if (!error && length_consumed) setErrorAt("Unexpected ':' (already read a branchlength)");
435                if (!error) drop_tree_char(':');
436                if (!error) {
437                    GBT_LEN foundlen;
438                    if (eat_number(foundlen)) {
439                        if (is_marked_as_default_len(nodeLen)) {
440                            nodeLen = foundlen;
441                        }
442                        else {
443                            tree_assert(node->is_leaf); // should only happen when a single leaf in parenthesis was read
444                            nodeLen += foundlen;        // sum leaf and node lengths
445                        }
446                        max_found_branchlen = std::max(max_found_branchlen, nodeLen);
447                    }
448                    else {
449                        setExpectedError("valid length");
450                    }
451                }
452                length_consumed = true;
453                break;
454
455            case EOF:
456                done = true;
457                break;
458
459            default: {
460                char *branchName = eat_quoted_string();
461                if (branchName) {
462                    if (branchName[0]) setBranchName_acceptingBootstrap(node, branchName);
463                }
464                else {
465                    UNCOVERED();
466                    setExpectedError("branch-name or one of ':;,)'");
467                }
468                break;
469            }
470        }
471    }
472
473    return !error;
474}
475
476static GBT_TREE *createLinkedTreeNode(const TreeNodeFactory& nodeMaker, GBT_TREE *left, GBT_LEN leftlen, GBT_TREE *right, GBT_LEN rightlen) { // @@@ move into class GBT_tree (as ctor) - or better move into TreeNodeFactory
477    GBT_TREE *node = nodeMaker.makeNode();
478
479    node->leftson  = left;
480    node->leftlen  = leftlen;
481    node->rightson = right;
482    node->rightlen = rightlen;
483
484    left->father  = node;
485    right->father = node;
486
487    return node;
488}
489
490GBT_TREE *TreeReader::load_named_node(GBT_LEN& nodeLen) {
491    // reads a node or subtree.
492    // a single node is expected to have a name (or will be auto-named)
493    // subtrees may have a name (groupname)
494    GBT_TREE *node = NULL;
495
496    if (last_character == '(') {
497        node = load_subtree(nodeLen);
498    }
499    else {                      // single node
500        eat_white();
501        char *name = eat_quoted_string();
502        if (name) {
503            if (!name[0]) freeset(name, unnamedNodeName());
504
505            node          = nodeMaker.makeNode();
506            node->is_leaf = true;
507            node->name    = name;
508        }
509        else {
510            UNCOVERED();
511            setExpectedError("(quoted) string");
512        }
513    }
514    if (node && !error) {
515        if (!eat_and_set_name_and_length(node, nodeLen)) {
516            delete node;
517            node = NULL;
518        }
519    }
520    tree_assert(contradicted(node, error));
521    tree_assert(!node || !node->is_leaf || node->name); // leafs need to be named here
522    return node;
523}
524
525
526GBT_TREE *TreeReader::load_subtree(GBT_LEN& nodeLen) {
527    // loads a subtree (i.e. expects parenthesis around one or several nodes)
528    //
529    // 'nodeLen' normally is set to DEFAULT_BRANCH_LENGTH_MARKER
530    //           or to length of single node (if parenthesis contain only one node)
531    //
532    // length and/or name behind '(...)' are not parsed (has to be done by caller).
533    //
534    // if subtree contains a single node (or a single other subtree), 'name'+'remark_branch' are
535    // already set, when load_subtree() returns - otherwise they are NULL.
536
537    GBT_TREE *node = NULL;
538
539    drop_tree_char('(');
540
541    GBT_LEN   leftLen = DEFAULT_BRANCH_LENGTH_MARKER;
542    GBT_TREE *left    = load_named_node(leftLen);
543
544    if (left) {
545        switch (last_character) {
546            case ')':               // single node
547                nodeLen = leftLen;
548                node    = left;
549                left    = 0;
550                break;
551
552            case ',': {
553                GBT_LEN   rightLen = DEFAULT_BRANCH_LENGTH_MARKER;
554                GBT_TREE *right    = NULL;
555
556                while (last_character == ',' && !error) {
557                    if (right) { // multi-branch
558                        GBT_TREE *pair = createLinkedTreeNode(nodeMaker, left, leftLen, right, rightLen);
559
560                        left  = pair; leftLen = 0;
561                        right = 0; rightLen = DEFAULT_BRANCH_LENGTH_MARKER;
562                    }
563
564                    drop_tree_char(',');
565                    if (!error) {
566                        right = load_named_node(rightLen);
567                    }
568                }
569
570                if (!error) {
571                    if (last_character == ')') {
572                        node    = createLinkedTreeNode(nodeMaker, left, leftLen, right, rightLen);
573                        nodeLen = DEFAULT_BRANCH_LENGTH_MARKER;
574
575                        left  = NULL;
576                        right = NULL;
577                    }
578                    else {
579                        setExpectedError("one of ',)'");
580                    }
581                }
582
583                delete right;
584                if (error) {
585                    delete node;
586                    node = NULL;
587                }
588
589                break;
590            }
591
592            default:
593                setExpectedError("one of ',)'");
594                break;
595        }
596        delete left;
597    }
598
599    if (!error) drop_tree_char(')');
600
601    tree_assert(contradicted(node, error));
602    return node;
603}
604
605GBT_TREE *TREE_load(const char *path, const TreeNodeFactory& nodeMaker, char **commentPtr, bool allow_length_scaling, char **warningPtr) {
606    /* Load a newick compatible tree from file 'path',
607       if commentPtr != NULL -> set it to a malloc copy of all concatenated comments found in tree file
608       if warningPtr != NULL -> set it to a malloc copy of any warnings occurring during tree-load (e.g. autoscale- or informational warnings)
609    */
610
611    GBT_TREE *tree  = NULL;
612    FILE     *input = fopen(path, "rt");
613    GB_ERROR  error = NULL;
614
615    if (!input) {
616        error = GBS_global_string("No such file: %s", path);
617    }
618    else {
619        const char *name_only = strrchr(path, '/');
620        if (name_only) ++name_only;
621        else        name_only = path;
622
623        TreeReader reader(input, name_only, nodeMaker);
624        if (!reader.error) tree = reader.load();
625        fclose(input);
626
627        if      (reader.error)          error = reader.error;
628        else if (tree && tree->is_leaf) error = "tree is too small (need at least 2 species)";
629
630        if (error) {
631            delete tree;
632            tree = NULL;
633        }
634
635        if (tree) {
636            double bootstrap_scale = 1.0;
637            double branchlen_scale = 1.0;
638
639            if (reader.get_max_found_bootstrap() >= 101.0) { // bootstrap values were given in percent
640                bootstrap_scale = 0.01;
641                reader.add_warningf("Auto-scaling bootstrap values by factor %.2f (max. found bootstrap was %5.2f)",
642                                    bootstrap_scale, reader.get_max_found_bootstrap());
643            }
644            if (reader.get_max_found_branchlen() >= 1.1) { // assume branchlengths have range [0;100]
645                if (allow_length_scaling) {
646                    branchlen_scale = 0.01;
647                    reader.add_warningf("Auto-scaling branchlengths by factor %.2f (max. found branchlength = %.2f)\n"
648                                        "(use ARB_NT/Tree/Modify branches/Scale branchlengths with factor %.2f to undo auto-scaling)",
649                                        branchlen_scale, reader.get_max_found_branchlen(), 1.0/branchlen_scale);
650                }
651            }
652
653            TREE_scale(tree, branchlen_scale, bootstrap_scale); // scale bootstraps and branchlengths
654
655            if (warningPtr) {
656                const char *wmsg      = reader.get_warnings();
657                if (wmsg) *warningPtr = strdup(wmsg);
658            }
659
660            if (commentPtr) {
661                char *comment = reader.takeComment();
662
663                const char *loaded_from = GBS_global_string("Loaded from %s", path);
664                freeset(comment, GBS_log_dated_action_to(comment, loaded_from));
665
666                tree_assert(*commentPtr == 0);
667                *commentPtr = comment;
668            }
669        }
670    }
671
672    tree_assert(tree||error);
673    if (error) {
674        GB_export_errorf("Import tree: %s", error);
675        tree_assert(!tree);
676    }
677
678    return tree;
679}
680
681// --------------------------------------------------------------------------------
682
683#ifdef UNIT_TESTS
684#ifndef TEST_UNIT_H
685#include <test_unit.h>
686#endif
687
688static GBT_TREE *loadFromFileContaining(const char *treeString, char **warningsPtr) {
689    const char *filename = "trees/tmp.tree";
690    FILE       *out      = fopen(filename, "wt");
691    GBT_TREE   *tree     = NULL;
692
693    if (out) {
694        fputs(treeString, out);
695        fclose(out);
696        tree = TREE_load(filename, GBT_TREE_NodeFactory(), NULL, false, warningsPtr);
697    }
698    else {
699        GB_export_IO_error("save tree", filename);
700    }
701
702    return tree;
703}
704
705static arb_test::match_expectation loading_tree_failed_with(GBT_TREE *tree, const char *errpart) {
706    using namespace   arb_test;
707    expectation_group expected;
708
709    expected.add(that(tree).is_equal_to_NULL());
710    expected.add(that(GB_have_error()).is_equal_to(true));
711    if (GB_have_error()) {
712        expected.add(that(GB_await_error()).does_contain(errpart));
713    }
714    return all().ofgroup(expected);
715}
716
717static arb_test::match_expectation loading_tree_succeeds(GBT_TREE *tree, const char *newick_expected) {
718    using namespace   arb_test;
719    expectation_group expected;
720
721    expected.add(that(tree).does_differ_from_NULL());
722    expected.add(that(GB_get_error()).is_equal_to_NULL());
723    if (!GB_have_error() && tree) {
724        char *newick = GBT_tree_2_newick(tree, nSIMPLE);
725        expected.add(that(newick).is_equal_to(newick_expected));
726        free(newick);
727    }
728    return all().ofgroup(expected);
729}
730
731#define TEST_EXPECT_TREELOAD_FAILED_WITH(tree,errpart)         TEST_EXPECTATION(loading_tree_failed_with(tree, errpart))
732#define TEST_EXPECT_TREELOAD_FAILED_WITH__BROKEN(tree,errpart) TEST_EXPECTATION__BROKEN(loading_tree_failed_with(tree, errpart))
733
734#define TEST_EXPECT_TREELOAD(tree,newick)         TEST_EXPECTATION(loading_tree_succeeds(tree,newick))
735#define TEST_EXPECT_TREELOAD__BROKEN(tree,newick) TEST_EXPECTATION__BROKEN(loading_tree_succeeds(tree,newick))
736
737#define TEST_EXPECT_TREEFILE_FAILS_WITH(name,errpart) do {                              \
738        GBT_TREE *tree = TREE_load(name, GBT_TREE_NodeFactory(), NULL, false, NULL);    \
739        TEST_EXPECT_TREELOAD_FAILED_WITH(tree, errpart);                                \
740    } while(0)
741
742#define TEST_EXPECT_TREESTRING_FAILS_WITH(treeString,errpart) do {      \
743        GBT_TREE *tree = loadFromFileContaining(treeString, NULL);      \
744        TEST_EXPECT_TREELOAD_FAILED_WITH(tree, errpart);                \
745    } while(0)
746
747// argument 'newick' is vs regression only!
748#define TEST_EXPECT_TREESTRING_FAILS_WITH__BROKEN(treeString,errpart,newick) do {       \
749        char     *warnings = NULL;                                                      \
750        GBT_TREE *tree     = loadFromFileContaining(treeString, &warnings);             \
751        TEST_EXPECT_TREELOAD_FAILED_WITH__BROKEN(tree, errpart);                        \
752        TEST_EXPECT_TREELOAD(tree, newick);                                             \
753        TEST_EXPECT_NULL(warnings);                                                     \
754        delete tree;                                                                    \
755        free(warnings);                                                                 \
756    } while(0)
757
758#define TEST_EXPECT_TREESTRING_OK(treeString,newick) do {                       \
759        char     *warnings = NULL;                                              \
760        GBT_TREE *tree     = loadFromFileContaining(treeString, &warnings);     \
761        TEST_EXPECT_TREELOAD(tree, newick);                                     \
762        TEST_EXPECT_NULL(warnings);                                             \
763        delete tree;                                                            \
764        free(warnings);                                                         \
765    } while(0)
766
767#define TEST_EXPECT_TREESTRING_OK_WITH_WARNING(treeString,newick,warnPart) do { \
768        char     *warnings = NULL;                                              \
769        GBT_TREE *tree     = loadFromFileContaining(treeString, &warnings);     \
770        TEST_EXPECT_TREELOAD(tree, newick);                                     \
771        TEST_REJECT_NULL(warnings);                                             \
772        TEST_EXPECT_CONTAINS(warnings, warnPart);                               \
773        delete tree;                                                            \
774        free(warnings);                                                         \
775    } while(0)
776
777#define TEST_EXPECT_TREESTRING_OK__BROKEN(treeString,newick) do {       \
778        GBT_TREE *tree = loadFromFileContaining(treeString, NULL);      \
779        TEST_EXPECT_TREELOAD__BROKEN(tree, newick);                     \
780    } while(0)
781
782void TEST_load_tree() {
783    // just are few tests covering most of this module.
784    // more load tests are in ../../TOOLS/arb_test.cxx@TEST_SLOW_arb_read_tree
785
786    // simple succeeding tree load
787    {
788        char     *comment = NULL;
789        GBT_TREE *tree    = TREE_load("trees/test.tree", GBT_TREE_NodeFactory(), &comment, false, NULL);
790        // -> ../../UNIT_TESTER/run/trees/test.tree
791
792        TEST_EXPECT_TREELOAD(tree, "(((s1,s2),(s3,s 4)),(s5,s-6));");
793        if (tree) {
794            TEST_REJECT_NULL(comment);
795            TEST_EXPECT_CONTAINS(comment,
796                                 // comment part from treefile:
797                                 "tree covering most of tree reader code\n"
798                                 "comment contains [extra brackets] inside comment\n");
799            TEST_EXPECT_CONTAINS(comment,
800                                 // comment as appended by load:
801                                 ": Loaded from trees/test.tree\n");
802        }
803        free(comment);
804        delete tree;
805    }
806
807    // detailed load tests (checking branchlengths and nodenames)
808    {
809        const char *treestring[] = {
810            "(node1,node2)rootgroup;",             // [0] tree with a named root
811            "(node1:0.00,(node2, node3:0.57)):0;", // [1] test tree lengths (esp. length zero)
812            "(((((a))single)), ((b, c)17%:0.2));", // [2] test single-node-subtree name-conflict
813
814            "((a,b)17,(c,d)33.3,(e,f)12.5:0.2);",             // [3] test bootstraps
815            "((a,b)G,(c,d)H,(e,f)I:0.2);",                    // [4] test groupnames w/o bootstraps
816            "((a,b)'17:G',(c,d)'33.3:H',(e,f)'12.5:I':0.2);", // [5] test groupnames with bootstraps
817            "((a,b)17G,(c,d)33.3H,(e,f)12.5I:0.2)",           // [6] test groupnames + bootstraps w/o separator
818
819            "((a,b)'17%:G',(c,d)'33.3%:H',(e,f)'12.5%:I':0.2);",  // [7] test bootstraps with percent spec
820            "((a,b)'0.17:G',(c,d)'0.333:H',(e,f)'0.125:I':0.2);", // [8] test bootstraps in range [0..1]
821        };
822
823        const char *expected_newick[] = {
824            "(node1,node2);",
825            "(node1,(node2,node3));",
826            "(a,(b,c));",
827
828            "(((a,b),(c,d)),(e,f));",
829            "(((a,b),(c,d)),(e,f));",
830            "(((a,b),(c,d)),(e,f));",
831            "(((a,b),(c,d)),(e,f));",
832
833            "(((a,b),(c,d)),(e,f));",
834            "(((a,b),(c,d)),(e,f));",
835        };
836        const char *expected_warnings[] = {
837            NULL,
838            NULL,
839            "Dropped group name specified for a single-node-subtree",
840
841            "Auto-scaling bootstrap values by factor 0.01",
842            NULL,
843            "Auto-scaling bootstrap values by factor 0.01",
844            NULL,
845            NULL, // no auto-scaling shall occur here (bootstraps are already specified as percent)
846            NULL, // no auto-scaling shall occur here (bootstraps are in [0..1])
847        };
848
849        STATIC_ASSERT(ARRAY_ELEMS(expected_newick) == ARRAY_ELEMS(treestring));
850        STATIC_ASSERT(ARRAY_ELEMS(expected_warnings) == ARRAY_ELEMS(treestring));
851
852        for (size_t i = 0; i<ARRAY_ELEMS(treestring); ++i) {
853            TEST_ANNOTATE(GBS_global_string("for tree #%zu = '%s'", i, treestring[i]));
854            char     *warnings = NULL;
855            GBT_TREE *tree     = loadFromFileContaining(treestring[i], &warnings);
856            TEST_EXPECT_TREELOAD(tree, expected_newick[i]);
857            switch (i) {
858                case 0:
859                    TEST_EXPECT_EQUAL(tree->name, "rootgroup");
860                    break;
861                case 1:
862                    TEST_EXPECT_EQUAL(tree->leftlen, 0);
863                    TEST_EXPECT_EQUAL(tree->rightlen, DEFAULT_BRANCH_LENGTH);
864                    TEST_EXPECT_EQUAL(tree->rightson->rightlen, 0.57);
865                    break;
866                case 2:
867                    // test bootstrap with percent-specification is parsed correctly
868                    TEST_EXPECT_NULL(tree->rightson->name);
869                    TEST_EXPECT_EQUAL(tree->rightson->get_remark(), "17%");
870                    TEST_EXPECT_EQUAL(tree->rightlen, 0.2);
871                    break;
872
873                case 3:
874                case 4:
875                case 5:
876                case 6:
877                case 7:
878                case 8:
879                    // check bootstraps
880                    TEST_EXPECT_NULL(tree->leftson->get_remark());
881                    switch (i) {
882                        case 4:
883                        case 6:
884                            TEST_EXPECT_NULL(tree->leftson->leftson->get_remark());
885                            TEST_EXPECT_NULL(tree->leftson->rightson->get_remark());
886                            TEST_EXPECT_NULL(tree->rightson->get_remark());
887                            break;
888                        case 3:
889                        case 5:
890                        case 7:
891                        case 8:
892                            TEST_EXPECT_EQUAL(tree->leftson->leftson->get_remark(),  "17%");
893                            TEST_EXPECT_EQUAL(tree->leftson->rightson->get_remark(), "33%");
894                            TEST_EXPECT_EQUAL(tree->rightson->get_remark(),          "13%");
895                            break;
896                        default:
897                            TEST_REJECT(true); // unhandled tree
898                            break;
899                    }
900
901                    // check node-names
902                    TEST_EXPECT_NULL(tree->name);
903                    TEST_EXPECT_NULL(tree->leftson->name);
904                    switch (i) {
905                        case 6:
906                            // check un-separated digits are treated as strange names
907                            // (previously these were accepted as bootstraps)
908                            TEST_EXPECT_EQUAL(tree->leftson->leftson->name,  "17G");
909                            TEST_EXPECT_EQUAL(tree->leftson->rightson->name, "33.3H");
910                            TEST_EXPECT_EQUAL(tree->rightson->name,          "12.5I");
911                            break;
912                        case 4:
913                        case 5:
914                        case 8:
915                        case 7:
916                            TEST_EXPECT_EQUAL(tree->leftson->leftson->name,  "G");
917                            TEST_EXPECT_EQUAL(tree->leftson->rightson->name, "H");
918                            TEST_EXPECT_EQUAL(tree->rightson->name,          "I");
919                            break;
920                        case 3:
921                            TEST_EXPECT_NULL(tree->leftson->leftson->name);
922                            TEST_EXPECT_NULL(tree->leftson->rightson->name);
923                            TEST_EXPECT_NULL(tree->rightson->name);
924                            break;
925                        default:
926                            TEST_REJECT(true); // unhandled tree
927                            break;
928                    }
929
930                    // expect_no_lengths:
931                    TEST_EXPECT_EQUAL(tree->leftlen,           0); // multifurcation
932                    TEST_EXPECT_EQUAL(tree->leftson->leftlen,  DEFAULT_BRANCH_LENGTH);
933                    TEST_EXPECT_EQUAL(tree->leftson->rightlen, DEFAULT_BRANCH_LENGTH);
934                    TEST_EXPECT_EQUAL(tree->rightlen,          0.2);
935                    break;
936
937                default:
938                    TEST_REJECT(true); // unhandled tree
939                    break;
940            }
941            if (expected_warnings[i]) {
942                TEST_REJECT_NULL(warnings);
943                TEST_EXPECT_CONTAINS(warnings, expected_warnings[i]);
944            }
945            else                      {
946                TEST_EXPECT_NULL(warnings);
947            }
948            free(warnings);
949            delete tree;
950        }
951
952        TEST_ANNOTATE(NULL);
953    }
954
955    // test valid trees with strange or wrong behavior
956    TEST_EXPECT_TREESTRING_OK("(,);",                       "(unnamed1,unnamed2);");   // tree with 2 unamed species (weird, but ok)
957    TEST_EXPECT_TREESTRING_OK("( a, (b,(c),d), (e,(f)) );", "((a,((b,c),d)),(e,f));");
958    TEST_EXPECT_TREESTRING_OK("(((((a)))), ((b, c)));",     "(a,(b,c));");
959
960    TEST_EXPECT_TREESTRING_OK_WITH_WARNING("( (a), (((b),(c),(d))group)dupgroup, ((e),(f)) );",
961                                           "((a,((b,c),d)),(e,f));",
962                                           "Duplicated group name specification detected");
963
964    // test unacceptable trees
965    {
966        const char *tooSmallTree[] = {
967            "();",
968            "()",
969            ";",
970            "",
971            "(one)",
972            "((((()))));",
973            "(((((one)))));",
974        };
975
976        for (size_t i = 0; i<ARRAY_ELEMS(tooSmallTree); ++i) {
977            TEST_ANNOTATE(GBS_global_string("for tree #%zu = '%s'", i, tooSmallTree[i]));
978            GBT_TREE *tree = loadFromFileContaining(tooSmallTree[i], NULL);
979            TEST_EXPECT_TREELOAD_FAILED_WITH(tree, "tree is too small");
980        }
981        TEST_ANNOTATE(NULL);
982    }
983    {
984        GBT_TREE *tree = loadFromFileContaining("((a, b)25)20;", NULL);
985        TEST_EXPECT_TREELOAD_FAILED_WITH(tree, "Invalid duplicated bootstrap specification detected");
986    }
987
988    // test invalid trees
989    TEST_EXPECT_TREESTRING_FAILS_WITH("(;);", "Expected one of ',)'");
990
991    TEST_EXPECT_TREESTRING_FAILS_WITH("(17",    "Expected one of ',)' while end-of-file was reached");
992    TEST_EXPECT_TREESTRING_FAILS_WITH("((((",   "Expected one of ',)' while end-of-file was reached");
993    TEST_EXPECT_TREESTRING_FAILS_WITH("(a, 'b", "Expected one of ',)' while end-of-file was reached");
994
995    TEST_EXPECT_TREESTRING_FAILS_WITH("(a, b:5::::",  "Unexpected ':' (already read a branchlength) while looking at '::::<EOF>'");
996    TEST_EXPECT_TREESTRING_FAILS_WITH("(a, b:5:c:d",  "Unexpected ':' (already read a branchlength) while looking at ':c:d<EOF>'");
997    TEST_EXPECT_TREESTRING_FAILS_WITH("(a, b:5:c:d)", "Unexpected ':' (already read a branchlength) while looking at ':c:d)<EOF>'");
998
999    TEST_EXPECT_TREESTRING_FAILS_WITH("[unclosed\ncomment",         "while reading comment");
1000    TEST_EXPECT_TREESTRING_FAILS_WITH("[unclosed\ncomment [ bla ]", "while reading comment");
1001
1002    TEST_EXPECT_TREESTRING_FAILS_WITH("(a, b:d)", "Expected valid length while looking at 'd)<EOF>'");
1003
1004    // questionable accepted trees / check warnings
1005    TEST_EXPECT_TREESTRING_OK_WITH_WARNING("(a,b):0.5", "(a,b);", "Length specified for root-node has been ignored");
1006    TEST_EXPECT_TREESTRING_OK_WITH_WARNING("(a, b))",   "(a,b);", "Unexpected input-data after tree: ')'");
1007
1008    TEST_EXPECT_TREESTRING_OK("(a*,b%);", "(a*,b%);"); // @@@ really accept such names?
1009    TEST_EXPECT_TREESTRING_OK("(a, b:5)", "(a,b);");
1010
1011    // check errors
1012    TEST_EXPECT_TREEFILE_FAILS_WITH("trees/nosuch.tree",    "No such file");
1013    TEST_EXPECT_TREEFILE_FAILS_WITH("trees/corrupted.tree", "Error reading");
1014
1015    TEST_EXPECT_ZERO_OR_SHOW_ERRNO(GB_unlink("trees/tmp.tree")); // cleanup
1016}
1017
1018#endif // UNIT_TESTS
1019
1020// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.