source: branches/help/SL/XFERSET/xferset.cxx

Last change on this file was 18186, checked in by westram, 5 years ago
File size: 70.2 KB
Line 
1// ========================================================= //
2//                                                           //
3//   File      : xferset.c                                   //
4//   Purpose   : field transfer sets                         //
5//                                                           //
6//   Coded by Ralf Westram (coder@reallysoft.de) in Mar 19   //
7//   http://www.arb-home.de/                                 //
8//                                                           //
9// ========================================================= //
10
11#include "xferset.h"
12
13#include <ConfigMapping.h>
14#include <BufferedFileReader.h>
15#include <arbdbt.h>
16#include <arb_str.h>
17#include <arb_stdstr.h>
18
19#include <set>
20#include <gb_aci.h>
21
22using namespace std;
23
24namespace FieldTransfer {
25
26    typedef set<string, NoCaseCmp> StrSet;
27
28    static void StrSet2StrArray(const StrSet& src, StrArray& dst) {
29        for (StrSet::const_iterator i = src.begin(); i != src.end(); ++i) {
30            dst.put(strdup(i->c_str()));
31        }
32    }
33    static void StrArray2StrSet(const StrArray& src, StrSet& dst) {
34        for (unsigned i = 0; i<src.size(); ++i) {
35            dst.insert(src[i]);
36        }
37    }
38
39    void RuleSet::extractUsedFields(StrArray& input, StrArray& output) const { // @@@ want flavor just filling 2 StrSets
40        StrSet in, out;
41        for (unsigned i = 0; i<size(); ++i) {
42            const Rule&   rule      = get(i);
43            const string& srcFields = rule.getSourceFields();
44            if (rule.multiple_source_fields()) {
45                ConstStrArray ifield;
46                GBT_split_string(ifield, srcFields.c_str(), ';');
47                for (unsigned f = 0; f<ifield.size(); ++f) {
48                    const char *source = ifield[f];
49                    if (source[0]) in.insert(source);
50                }
51            }
52            else {
53                if (!srcFields.empty()) in.insert(srcFields);
54            }
55            const string& target = rule.targetField();
56            if (!target.empty()) out.insert(target);
57        }
58        StrSet2StrArray(in,  input);
59        StrSet2StrArray(out, output);
60    }
61
62    TransportedData ReadRule::readTypedFromField(GB_TYPES readAsType, GBDATA *gb_field) const {
63        xf_assert(GB_read_type(gb_field) != GB_DB); // fails e.g. if rule defined for a name used by a container
64
65        switch (readAsType) {
66            case GB_INT: {
67                int asInt = GB_read_int(gb_field);
68                return TransportedData(asInt);
69            }
70            case GB_FLOAT: {
71                float asFloat = GB_read_float(gb_field);
72                return TransportedData(asFloat);
73            }
74            case GB_STRING: {
75                char   *asStr = GB_read_as_string(gb_field);
76                string  data(asStr);
77                free(asStr);
78                return TransportedData(data);
79            }
80            default: xf_assert(0); break; // invalid type
81        }
82        xf_assert(0); // should be never reached.
83        return TransportedData::none();
84    }
85
86    TransportedData ReadRule::aciAppliedTo(const string& toStr, GBDATA *gb_main, GBDATA *gb_dest_item) const {
87        // We can not generally provide a meaningful item for ACI here.
88        // Currently it always uses the destination item, but this item may be some dummy item,
89        // e.g. a species clone used only during transfer.
90
91        GBL_env      env(gb_main, NULp);
92        GBL_call_env callEnv(gb_dest_item, env);
93
94        char *result = GB_command_interpreter_in_env(toStr.c_str(), aci.c_str(), callEnv);
95        if (result) {
96            string converted(result);
97            free(result);
98            return TransportedData(converted);
99        }
100        return TransportedData::makeError(GB_await_error());
101    }
102
103    inline TransportedData cannotReadContainer(const char *containerName) {
104        return TransportedData::makeError(GBS_global_string("cannot read as data ('%s' is a container)", containerName));
105    }
106
107    TransportedData ReadRule::readFrom(GBDATA *gb_item, GBDATA *gb_dest_item) const {
108        // 'gb_dest_item' only used for ACI
109
110        if (!gb_item) { // got no item -> can't read
111            return TransportedData::makeError("lacking item to readFrom");
112        }
113
114        if (fields.empty()) {
115            return TransportedData::makeError("no source field(s) specified");
116        }
117
118        if (multiple_source_fields()) {
119            ConstStrArray field;
120            GBT_split_string(field, fields.c_str(), ';');
121
122            string concat;
123            bool   gotData = false; // at least one input field found?
124            for (size_t f = 0; f<field.size(); ++f) {
125                GBDATA *gb_field = GB_search(gb_item, field[f], GB_FIND);
126                if (gb_field) {
127                    GB_TYPES sourceType = GB_read_type(gb_field);
128                    if (sourceType == GB_DB) {
129                        return cannotReadContainer(field[f]);
130                    }
131
132                    TransportedData plain = readTypedFromField(GB_STRING, gb_field); // ignores sourceType
133                    if (plain.failed()) return plain;
134
135                    xf_assert(plain.exists());
136                    if (!concat.empty()) concat += separator;
137                    concat                      += plain.getString();
138
139                    gotData = true;
140                }
141                else if (GB_have_error()) {
142                    return TransportedData::makeError(GB_await_error());
143                }
144            }
145
146            if (gotData) {
147                if (!aci.empty()) {
148                    return aciAppliedTo(concat, GB_get_root(gb_item), gb_dest_item);
149                }
150                return TransportedData(concat);
151            }
152            // otherwise: do not transport if all source fields are missing
153        }
154        else {
155            GBDATA *gb_field = GB_search(gb_item, fields.c_str(), GB_FIND);
156            if (gb_field) {
157                GB_TYPES sourceType = GB_read_type(gb_field);
158                if (sourceType == GB_DB) {
159                    return cannotReadContainer(fields.c_str());
160                }
161                if (!aci.empty()) {
162                    TransportedData plain = readTypedFromField(GB_STRING, gb_field); // ignores sourceType
163                    // @@@ store sourceType if no dest-type deduced?
164                    return aciAppliedTo(plain.getString(), GB_get_root(gb_item), gb_dest_item);
165                }
166                return readTypedFromField(sourceType, gb_field);
167            }
168            else if (GB_have_error()) {
169                return TransportedData::makeError(GB_await_error());
170            }
171            // otherwise: do not transport if source field is missing
172        }
173        // if field does not exist -> report "no type"
174        return TransportedData::none();
175    }
176
177    static GB_ERROR unconvertedWrite(const TransportedData& data, GBDATA *gb_field) {
178        GB_ERROR error = NULp;
179        switch (data.getType()) {
180            case GB_STRING: {
181                const char *str = data.getString().c_str();
182                error           = GB_write_string(gb_field, str);
183                break;
184            }
185            case GB_INT: {
186                int num = data.getInt();
187                error   = GB_write_int(gb_field, num);
188                break;
189            }
190            case GB_FLOAT: {
191                float fnum = data.getFloat();
192                error      = GB_write_float(gb_field, fnum);
193                break;
194            }
195            default: { // unhandled type
196                xf_assert(0);
197                break;
198            }
199        }
200        return error;
201    }
202    static GB_ERROR convertAndWrite(const TransportedData& data, GBDATA *gb_field, GB_TYPES wantedTargetType, bool acceptLossyConversion) {
203        // perform conversion to 'wantedTargetType' and write to 'gb_field'
204        GB_ERROR error = NULp;
205
206        switch (data.getType()) {
207            case GB_INT:
208                if (wantedTargetType == GB_FLOAT) {
209                    // convert int -> float
210                    float   f = data.getInt();
211                    int32_t i = int(f+.5); // round (just in case some underflow happened, causing sth like 4711.9999999)
212
213                    if (i != data.getInt() && !acceptLossyConversion) {
214                        error = GBS_global_string("lossy int->float type conversion (%i->%i)", data.getInt(), i);
215                    }
216                    else {
217                        error = GB_write_float(gb_field, f);
218                    }
219                }
220                else {
221                    error = GB_write_lossless_int(gb_field, data.getInt());
222                }
223                break;
224
225            case GB_FLOAT:
226                if (wantedTargetType == GB_INT) {
227                    // convert float -> int
228                    double d  = data.getFloat();
229                    int    i  = d>0 ? (int)(d+0.5) : (int)(d-0.5);
230                    // @@@ increment a round-counter in RuleSet?
231                    double d2 = i;
232
233                    if (d != d2 && !acceptLossyConversion) { // precision loss
234                        error = GBS_global_string("lossy float->int type conversion (%e->%e)", d, d2);
235                    }
236                    else {
237                        error = GB_write_int(gb_field, i);
238                    }
239                }
240                else {
241                    error = GB_write_lossless_float(gb_field, data.getFloat());
242                }
243                break;
244
245            case GB_STRING:
246                error = GB_write_autoconv_string(gb_field, data.getString().c_str()); // @@@ avoid silent data loss
247                // @@@ use GBT_write_float_converted / GBT_write_int_converted here!
248                break;
249
250            default:
251                xf_assert(0); // unhandled type
252                break;
253        }
254
255        return error;
256    }
257
258    GB_ERROR WriteRule::writeTo(const TransportedData& data, GBDATA *gb_item, bool acceptLossyConversion) const {
259        if (!gb_item) return "lacking item to writeTo";
260
261        // @@@ overwrite existing target field? should it be allowed or denied? optional?
262        // @@@ try GBT_searchOrCreate_itemfield_according_to_changekey to create a field
263        xf_assert(data.exists());
264
265        GB_TYPES usedTargetType = forcesType() ? getTargetType() : data.getType();
266
267        GB_ERROR error = check_hkey();
268        if (!error) {
269            GBDATA *gb_field = GB_search(gb_item, name.c_str(), usedTargetType); // Note: works with hierarchical keys
270            if (!gb_field) {
271                error = GB_await_error(); // field not created -> report why
272            }
273            else {
274                if (data.getType() == usedTargetType) { // data and target have same type -> no conversion needed
275                    error = unconvertedWrite(data, gb_field);
276                }
277                else { // type differs -> perform conversion (act like field converter reachable from info-window)
278                    error = convertAndWrite(data, gb_field, usedTargetType, acceptLossyConversion);
279                }
280            }
281        }
282        return error;
283    }
284
285    GB_ERROR Rule::transferBy(GBDATA *gb_source, GBDATA *gb_dest) const {
286        /*! apply one rule (as part of transfer). */
287
288        // @@@ detect target field type. has to be done before starting transfer (do only once for each rule!)
289        // @@@ pass target field type to reader (to select best read method)!
290
291        GB_ERROR        error = NULp;
292        TransportedData tdata = readFrom(gb_source, gb_dest);
293        if (tdata.failed()) {
294            error = tdata.getError();
295        }
296        else if (tdata.exists()) {
297            error = writeTo(tdata, gb_dest, precisionLossPermitted());
298        }
299        // else source missing -> do nothing.
300        // Note: if target field exists and source field is missing -> target field remains intact.
301
302        xf_assert(!GB_have_error()); // invalid to export an error (should get returned)
303        return error;
304    }
305
306    GB_ERROR RuleSet::transferBy(GBDATA *gb_source, GBDATA *gb_dest) const {
307        /*! transfer field data by applying all rules. */
308
309        GB_ERROR error = NULp;
310        size_t r;
311        for (r = 0; r<size() && !error; ++r) {
312            const Rule& rule = get(r);
313            error            = rule.transferBy(gb_source, gb_dest);
314        }
315        if (error) {
316            error = GBS_global_string("%s (in rule #%zu)", error, r);
317        }
318
319        xf_assert(!GB_have_error()); // invalid to export an error (should get returned)
320        return error;
321    }
322
323    GB_ERROR RuleSet::saveTo(const char *filename) const {
324        GB_ERROR  error = NULp;
325        FILE     *out   = fopen(filename, "wt");
326        if (!out) {
327            error = GB_IO_error("saving", filename);
328        }
329        else {
330            // print header:
331            fputs("# arb field transfer set; version 1.0\n", out);
332            fputc('\n', out);
333
334            // print global RuleSet data:
335            {
336                ConstStrArray clines;
337                GBT_split_string(clines, comment.c_str(), '\n');
338
339                for (int c = 0; clines[c]; ++c) {
340                    fprintf(out, "desc:%s\n", clines[c]);
341                }
342                fputc('\n', out);
343            }
344            fprintf(out, "transferUndef:%i\n", int(transferUndefFields));
345
346            // print rules:
347            for (size_t r = 0; r<size(); ++r) {
348                const Rule& rule = get(r);
349                string      cfg  = rule.getConfig();
350                fprintf(out, "rule:%s\n", cfg.c_str());
351            }
352            fputc('\n', out);
353
354            fclose(out);
355        }
356        return error;
357    }
358
359    inline bool isCommentLine(const string& line) {
360        size_t leadingSpaces        = line.find_first_not_of(" \t");
361        return line[leadingSpaces] == '#';
362    }
363    inline bool shallIgnore(const string& line) {
364        // decide whether to ignore a line loaded from .fts file.
365        return line.empty() || isCommentLine(line);
366    }
367
368    ErrorOrRuleSetPtr RuleSet::loadFrom(const char *filename) {
369        ARB_ERROR  error;
370        RuleSetPtr ruleset;
371
372        FILE *in = fopen(filename, "rt");
373        if (!in) {
374            error = GB_IO_error("loading", filename);
375        }
376        else {
377            ruleset = new RuleSet();
378            BufferedFileReader reader(filename, in);
379
380            string line;
381            while (!error && reader.getLine(line)) {
382                if (shallIgnore(line)) continue;
383
384                size_t pos = line.find(':');
385                if (pos == string::npos) {
386                    error = GBS_global_string("expected ':' while parsing line '%s'", line.c_str());
387                }
388                else {
389                    string tag     = line.substr(0, pos);
390                    string content = line.substr(pos+1);
391
392                    if (tag == "rule") {
393                        ErrorOrRulePtr rule = Rule::makeFromConfig(content.c_str());
394                        if (rule.hasError()) {
395                            error = GBS_global_string("while reading rule from '%s': %s",
396                                                      content.c_str(),
397                                                      rule.getError().deliver());
398                        }
399                        else {
400                            ruleset->add(rule.getValue());
401                        }
402                    }
403                    else if (tag == "desc") {
404                        const string& existing = ruleset->getComment();
405                        ruleset->setComment(existing.empty() ? content : existing+'\n'+content);
406                    }
407                    else if (tag == "transferUndef") {
408                        ruleset->set_transferUndefFields(bool(atoi(content.c_str())));
409                    }
410                    else {
411                        error = GBS_global_string("unknown tag '%s' while parsing line '%s'",
412                                                  tag.c_str(),
413                                                  line.c_str());
414                    }
415                }
416            }
417
418            if (error) ruleset.setNull();
419        }
420
421        return ErrorOrRuleSetPtr(error, ruleset);
422    }
423
424    // --------------------------------
425    //      configuration of rules
426
427#define SOURCE "source"
428#define ACI    "aci"
429#define TARGET "target"
430#define SEP    "sep"
431#define TYPE   "type"
432#define LOSS   "loss"
433
434#define PERMITTED "permitted"
435
436    inline const char *type2str(GB_TYPES type) {
437        const char *str = NULp;
438        switch (type) {
439            case GB_STRING: str = "text";  break;
440            case GB_INT:    str = "int";   break;
441            case GB_FLOAT:  str = "float"; break;
442            case GB_BITS:   str = "bits";  break;
443            case GB_NONE:   str = "auto";  break;
444            default: break;
445        }
446        return str;
447    }
448    inline GB_TYPES str2type(const char *str) {
449        GB_TYPES type = GB_TYPE_MAX; // invalid
450        switch (str[0]) {
451            case 't': if (strcmp(str, "text")  == 0) type = GB_STRING; break;
452            case 'i': if (strcmp(str, "int")   == 0) type = GB_INT;    break;
453            case 'f': if (strcmp(str, "float") == 0) type = GB_FLOAT;  break;
454            case 'b': if (strcmp(str, "bits")  == 0) type = GB_BITS;   break;
455            case 'a': if (strcmp(str, "auto")  == 0) type = GB_NONE;   break;
456        }
457        return type;
458    }
459
460    void ReadRule::saveReadConfig(ConfigMapping& cfgmap) const {
461        cfgmap.set_entry(SOURCE, fields);
462        if (separator != NOSEP) cfgmap.set_entry(SEP, separator);
463        if (!aci.empty()) cfgmap.set_entry(ACI, aci);
464    }
465
466    void WriteRule::saveWriteConfig(ConfigMapping& cfgmap) const {
467        cfgmap.set_entry(TARGET, name);
468        if (forcesType()) {
469            cfgmap.set_entry(TYPE, type2str(getTargetType()));
470        }
471    }
472    string Rule::getConfig() const {
473        ConfigMapping cfgmap;
474
475        saveReadConfig(cfgmap);
476        saveWriteConfig(cfgmap);
477
478        if (precisionLossPermitted()) {
479            cfgmap.set_entry(LOSS, PERMITTED);
480        }
481
482        return cfgmap.config_string();
483    }
484
485    ErrorOrRulePtr Rule::makeFromConfig(const char *config) {
486        RulePtr       rule;
487        ConfigMapping cfgmap;
488        GB_ERROR      error = cfgmap.parseFrom(config);
489
490        if (!error) {
491            const char *source = cfgmap.get_entry(SOURCE);
492            const char *target = cfgmap.get_entry(TARGET);
493            const char *sep    = cfgmap.get_entry(SEP);
494
495            if (!source) error = "missing " SOURCE " entry";
496            if (!target) error = "missing " TARGET " entry";
497
498            if (!sep) sep = NOSEP; // default to 'no separator'
499
500            if (!error) {
501                const char *aci = cfgmap.get_entry(ACI);
502                if (aci) {
503                    rule = makeAciConverter(source, sep, aci, target);
504                }
505                else {
506                    rule = makeSimple(source, sep, target);
507                }
508
509                const char *typeID = cfgmap.get_entry(TYPE);
510                if (typeID) {
511                    GB_TYPES type = str2type(typeID);
512                    if (type == GB_TYPE_MAX) { // = unknown type ID
513                        error = GBS_global_string("invalid type id '%s'", typeID);
514                        rule.setNull();
515                    }
516                    else {
517                        xf_assert(GB_TYPE_readable_as_string(type));
518                        rule->setTargetType(type);
519                    }
520                }
521
522                if (!error) {
523                    const char *loss = cfgmap.get_entry(LOSS);
524                    if (loss && strcmp(loss, PERMITTED) == 0) {
525                        rule->permitPrecisionLoss();
526                    }
527                }
528            }
529        }
530
531        return ErrorOrRulePtr(error, rule);
532    }
533
534
535    // ------------------------
536    //      describe rules
537    string ReadRule::describe() const {
538        if (aci.empty()) return fields;
539        return fields+"|ACI";
540    }
541    string WriteRule::describe() const {
542        return name;
543    }
544    string Rule::getShortDescription() const {
545        return ReadRule::describe() + " -> " + WriteRule::describe();
546    }
547
548    // -----------------------------
549    //      ItemClonedByRuleSet
550    string ItemClonedByRuleSet::lastReportedError;
551
552    GB_ERROR ItemClonedByRuleSet::overlayOrCloneSub(const char *subName, GBDATA *gb_sub) {
553        GBDATA   *gb_existing = GB_entry(gb_clone, subName);
554        GB_ERROR  error;
555        if (gb_existing) {                                // if target entry exists ..
556            error = GB_copy_overlay(gb_existing, gb_sub); // .. overwrite its content.
557        }
558        else {                                                      // otherwise ..
559            error = GB_incur_error_if(!GB_clone(gb_clone, gb_sub)); // .. clone source entry
560        }
561        return error;
562    }
563
564    GB_ERROR ItemClonedByRuleSet::cloneMissingSub(const char *subName, GBDATA *gb_sub) {
565        GBDATA   *gb_existing = GB_entry(gb_clone, subName);
566        GB_ERROR  error;
567        if (gb_existing) { // if target entry exists ..
568            error = NULp;  // .. keep it
569        }
570        else {                                                      // otherwise ..
571            error = GB_incur_error_if(!GB_clone(gb_clone, gb_sub)); // .. clone source entry
572        }
573        return error;
574    }
575
576    GB_ERROR ItemClonedByRuleSet::copySubIfMissing(const char *subName) {
577        // copy sub-field (or -container) if it doesn't exist in target
578        GB_ERROR  error  = NULp;
579        GBDATA   *gb_sub = GB_entry(gb_source, subName);
580        if (!gb_sub) {
581            error = GBS_global_string("no such entry '%s' (in source)", subName);
582            UNCOVERED();
583        }
584        else {
585            error = cloneMissingSub(subName, gb_sub); // sub = passed field or container
586        }
587        return error;
588    }
589
590    GB_ERROR ItemClonedByRuleSet::copyAlignments() {
591        GB_ERROR error = NULp;
592        for (GBDATA *gb_ali = GB_child(gb_source); gb_ali; gb_ali = GB_nextChild(gb_ali)) {
593            if (GB_is_container(gb_ali)) {
594                const char *aliname = GB_read_key_pntr(gb_ali);
595                if (ARB_strBeginsWith(aliname, "ali_")) {
596                    GBDATA *gb_data = GB_entry(gb_ali, "data");
597                    if (gb_data) {
598                        bool dataIsSTRING = GB_read_type(gb_data) == GB_STRING;
599                        xf_assert(dataIsSTRING);
600                        if (dataIsSTRING) {
601                            error = overlayOrCloneSub(aliname, gb_ali); // sub = whole alignment container
602                        }
603                    }
604                    else {
605                        error = GB_incur_error();
606                        UNCOVERED();
607                    }
608                }
609            }
610        }
611        return error;
612    }
613
614    const char *ItemClonedByRuleSet::get_id_field() const {
615        const char *field = NULp;
616        switch (itemtype) {
617            case CLONE_ITEM_SPECIES: field = "name"; break;
618            default: xf_assert(0); break;
619        }
620        return field;
621    }
622
623    ItemClonedByRuleSet::ItemClonedByRuleSet(GBDATA*& gb_item, ClonableItemType itemtype_, RuleSetPtr ruleset, ItemCloneType type_, GBDATA *gb_refItem, const AlignmentTransporter *aliTransporter) :
624        itemtype(itemtype_),
625        gb_source(gb_item),
626        type(type_)
627    {
628        /*! clone or update item using ruleset.
629         *
630         * @param gb_item the source item (will be set to NULp if type_ is REPLACE_ITEM_BY_CLONE).
631         * @param itemtype_ currently always CLONE_ITEM_SPECIES.
632         * @param ruleset ruleset used to transfer fields from source item to cloned item
633         * @param type_ type of clone (see ItemCloneType for details).
634         * @param gb_refItem CLONE_INTO_EXISTING: target species, REAL_CLONE: target item container, otherwise: NULp
635         * @param aliTransporter allows to overide how alignment gets copied (default: copy all alignment sub-containers)
636         */
637
638        // @@@ method is far too long -> split
639
640        GB_ERROR       error = NULp;
641        GB_transaction ta(gb_source);
642
643#if defined(ASSERTION_USED)
644        checked4error    = false;
645        userCallbackUsed = false;
646#endif
647
648        if (type == CLONE_INTO_EXISTING) {
649            if (gb_refItem) {
650                gb_clone = gb_refItem; // use passed clone as target
651            }
652            else {
653                error = "no target species specified (logic error)";
654                UNCOVERED();
655            }
656        }
657        else {
658            GBDATA *gb_item_container;
659            {
660                GBDATA *gb_src_item_container = GB_get_father(gb_source);
661                if (type == REAL_CLONE) {
662                    gb_item_container = gb_refItem;
663                    if (!gb_item_container) {
664                        error = "no target item container specified (logic error)";
665                    }
666                    else if (gb_item_container == gb_src_item_container) {
667                        error = "source and target item containers need to differ (logic error)";
668                    }
669                }
670                else {
671                    xf_assert(!gb_refItem); // passed value is ignored (please pass NULp)
672                    gb_item_container = gb_src_item_container;
673                }
674            }
675
676            if (!error) {
677                xf_assert(itemtype_ == CLONE_ITEM_SPECIES);                   // next command only works for species
678                gb_clone = GB_create_container(gb_item_container, "species"); // create separate species
679                if (!gb_clone) {
680                    error = GB_await_error();
681                    UNCOVERED();
682                }
683            }
684        }
685
686        if (!error) {
687            // apply ruleset:
688            error = ruleset->transferBy(gb_source, gb_clone);
689
690            // perform some standard transfers:
691            const char *IDFIELD = get_id_field();
692            if (!error) error   = copySubIfMissing(IDFIELD); // transfer IDFIELD for any itemtype
693
694            switch (itemtype) {
695                case CLONE_ITEM_SPECIES:
696                    if (!error) error = copySubIfMissing("acc");
697                    if (!error) {
698                        if (aliTransporter) { // use user callback if given
699                            if (aliTransporter->shallCopyBefore()) {
700                                error = copyAlignments();
701                            }
702                            if (!error) {
703                                error = aliTransporter->transport(gb_source, gb_clone); // e.g. used to adapt alignment in mergetool
704                            }
705#if defined(ASSERTION_USED)
706                            userCallbackUsed = true;
707#endif
708                        }
709                        else {
710                            error = copyAlignments();
711                        }
712                    }
713                    break;
714                default: xf_assert(0); break;
715            }
716
717            if (!error && ruleset->shallTransferUndefFields()) {
718
719                StrSet defined;
720                // extract used fields:
721                {
722                    StrArray in, out;
723                    ruleset->extractUsedFields(in, out);
724                    // @@@ do extraction only once (not for each item transfer)
725
726                    StrArray2StrSet(in, defined);
727                    StrArray2StrSet(out, defined);
728                }
729                {
730                    // exclude parent containers:
731                    StrSet parents;
732                    for (StrSet::const_iterator field = defined.begin(); field != defined.end(); ++field) {
733                        size_t slashpos = field->find_first_of('/');
734                        if (slashpos != string::npos) { // fieldname contains a slash
735                            string parentname = field->substr(0, slashpos); // name of top-level parent container inside species
736                            parents.insert(parentname);
737                        }
738                    }
739                    defined.insert(parents.begin(), parents.end());
740                }
741
742                // transfer rest of fields (i.e. those neighter used by ruleset nor as standard field):
743                for (GBDATA *gb_field = GB_child(gb_source); gb_field && !error;  gb_field = GB_nextChild(gb_field)) {
744                    const char *key     = GB_read_key_pntr(gb_field);
745                    bool        keyUsed = defined.find(key) != defined.end(); // key was read or written by ruleset
746
747                    if (!keyUsed) {
748                        error = copySubIfMissing(key);
749                    }
750                }
751            }
752
753            // @@@ do we need to preserve security etc of cloned species? (security of sub-fields is preserved; e.g. see r17967)
754
755            if (!error) {
756                xf_assert(correlated(aliTransporter, userCallbackUsed)); // custom transporter was not used (logic error?)
757
758                switch (type) {
759                    case REPLACE_ITEM_BY_CLONE:
760                        error = GB_delete(gb_source);   // will be replaced by clone
761                        if (!error) {
762                            gb_item   = NULp;
763                            gb_source = NULp;
764                        }
765                        break;
766
767                    case RENAME_ITEM_WHILE_TEMP_CLONE_EXISTS: {
768                        GBDATA *gb_id = GB_entry(gb_source, IDFIELD);
769                        if (!gb_id) {
770                            error = GBS_global_string("expected field '%s' not found", IDFIELD);
771                        }
772                        else {
773                            const char *name = GB_read_char_pntr(gb_id);
774                            xf_assert(name);
775                            orgName = name; // store name (written back in dtor)
776
777                            error = GB_write_string(gb_id, "fake"); // change name // @@@ use different name
778                        }
779                        break;
780                    }
781                    case CLONE_INTO_EXISTING:
782                    case REAL_CLONE:
783                        // nothing to do here
784                        break;
785                }
786            }
787        }
788
789        error = ta.close(error);
790        if (error) {
791            errorCopy = error; // store copy of error in string
792            gb_clone  = NULp;
793        }
794    }
795
796    ItemClonedByRuleSet::~ItemClonedByRuleSet() {
797        if (!has_error()) { // if error occurred during construction -> TA was aborted -> nothing to undo
798            if (type == RENAME_ITEM_WHILE_TEMP_CLONE_EXISTS) {
799                GB_transaction ta(gb_source);
800                GB_ERROR       error = NULp;
801
802                GBDATA *gb_id = GB_entry(gb_source, get_id_field());
803                xf_assert(gb_id);
804
805                if (gb_id) {
806                    xf_assert(!orgName.empty());
807                    error = GB_write_string(gb_id, orgName.c_str());
808                    if (error) {
809                        fprintf(stderr, "Failed to rename original item after temp. clone (Reason: %s)", error);
810                        xf_assert(0); // should not happen
811                    }
812                }
813
814                // delete temp clone:
815                if (!error) {
816                    error = GB_delete(gb_clone);
817                    if (error) {
818                        fprintf(stderr, "Failed to delete temp. clone (Reason: %s)", error);
819                        xf_assert(0); // should not happen
820                    }
821                }
822            }
823        }
824    }
825
826};
827
828// --------------------------------------------------------------------------------
829
830#ifdef UNIT_TESTS
831
832#include <arb_diff.h>
833#include <arb_file.h>
834#include <arb_defs.h>
835
836#ifndef TEST_UNIT_H
837#include <test_unit.h>
838#endif
839
840void TEST_type2id() {
841    using namespace FieldTransfer;
842
843    for (GB_TYPES t = GB_NONE; t<=GB_TYPE_MAX; t = GB_TYPES(t+1)) {
844        const char *id = type2str(t);
845        if (id) {
846            TEST_ANNOTATE(id);
847            TEST_EXPECT_EQUAL(str2type(id), t);
848        }
849    }
850}
851void TEST_transportedData() {
852    using namespace FieldTransfer;
853
854    GB_ERROR error;
855    {
856        TransportedData noData    = TransportedData::none();
857        TransportedData errorData = TransportedData::makeError("the error msg");
858
859        TEST_REJECT(noData.failed());
860        TEST_REJECT(noData.exists());
861
862        TEST_EXPECT(errorData.failed());
863        error = errorData.getError();
864        TEST_EXPECT_EQUAL(error, "the error msg");
865    }
866    TEST_EXPECT_EQUAL(error, "the error msg"); // error has to survive destruction of TransportedData
867
868    TransportedData greet("hello");
869    TransportedData num(4711);
870    TransportedData fnum(0.815f);
871
872    TEST_REJECT(greet.failed());
873    TEST_EXPECT(greet.exists());
874    TEST_EXPECT_EQUAL(greet.getString(), "hello");
875
876    TEST_REJECT(num.failed());
877    TEST_EXPECT(num.exists());
878    TEST_EXPECT_EQUAL(num.getInt(), 4711);
879
880    TEST_REJECT(fnum.failed());
881    TEST_EXPECT(fnum.exists());
882    TEST_EXPECT_SIMILAR(fnum.getFloat(), 0.815, 0.000001);
883}
884
885void TEST_xferset() {
886    FieldTransfer::RuleSet fts;
887
888    TEST_EXPECT_ZERO(fts.size());
889    TEST_EXPECT(fts.empty());
890    TEST_EXPECT_EQUAL(fts.size(), 0);
891
892    // --------------------
893    //      add rules:
894    fts.add(new FieldTransfer::Rule(FieldTransfer::ReadRule("location", NOSEP), // add a simple rule (one source, no ACI)
895                                    FieldTransfer::WriteRule("geolocation")));
896    TEST_EXPECT(!fts.empty());
897    TEST_EXPECT_EQUAL(fts.size(), 1);
898
899    fts.add(FieldTransfer::Rule::permitPrecisionLoss(new FieldTransfer::Rule(FieldTransfer::ReadRule("isolation", NOSEP, "upper"), // add an ACI rule (one source)
900                                                                             FieldTransfer::WriteRule("isolation_source", GB_INT)))); // force int type
901    TEST_EXPECT_EQUAL(fts.size(), 2);
902
903    // @@@ add multisource rules (with and w/o ACI)!
904
905    // ---------------------
906    //      query rules
907    for (size_t r = 0; r<fts.size(); ++r) {
908        TEST_ANNOTATE(GBS_global_string("r=%zu", r));
909
910        const FieldTransfer::Rule& rule = fts.get(r);
911        switch (r) {
912            // @@@ add tests for source field(s)
913
914            case 0: // simple rule
915
916                TEST_EXPECT_EQUAL(rule.targetField(), "geolocation");
917                TEST_REJECT(rule.forcesType());
918                TEST_REJECT(rule.precisionLossPermitted());
919                break;
920
921            case 1: // basic ACI rule
922
923                TEST_EXPECT_EQUAL(rule.targetField(), "isolation_source");
924                TEST_EXPECT(rule.forcesType());                  // type is forced ..
925                TEST_EXPECT_EQUAL(rule.getTargetType(), GB_INT); // .. to int
926                TEST_EXPECT(rule.precisionLossPermitted());
927                break;
928
929            default:
930                xf_assert(0); // untested rule
931                break;
932        }
933    }
934    TEST_ANNOTATE(NULp);
935    TEST_EXPECT_EQUAL(fts.size(), 2);
936
937    // -------------------------------------------
938    //      test rule replacement and removal
939    {
940        // order=01
941        string cfg0 = fts.get(0).getConfig();
942        string cfg1 = fts.get(1).getConfig();
943
944        TEST_EXPECT_DIFFERENT(cfg0, cfg1); // otherwise test below does not test replacement
945
946        {
947            // swap 2 rules
948            FieldTransfer::RulePtr tmp = fts.getPtr(0);
949            fts.replace(0, fts.getPtr(1));
950            fts.replace(1, tmp);
951            // order=10
952        }
953
954        string newcfg0 = fts.get(0).getConfig();
955        string newcfg1 = fts.get(1).getConfig();
956
957        TEST_EXPECT_EQUAL(newcfg0, cfg1);
958        TEST_EXPECT_EQUAL(newcfg1, cfg0);
959
960        {
961            int insertedAt;
962
963            insertedAt = fts.insertBefore(0, fts.getPtr(1)); // insert before first -> order = 010
964            TEST_EXPECT_EQUAL(fts.size(), 3);
965            TEST_EXPECT_EQUAL(insertedAt, 0);
966            TEST_EXPECT_EQUAL(fts.get(insertedAt).getConfig(), cfg0);
967
968            insertedAt = fts.insertBefore(2, fts.getPtr(1)); // insert before last -> order = 0110
969            TEST_EXPECT_EQUAL(fts.size(), 4);
970            TEST_EXPECT_EQUAL(insertedAt, 2);
971            TEST_EXPECT_EQUAL(fts.get(insertedAt).getConfig(), cfg1);
972
973            insertedAt = fts.insertBefore(7, fts.getPtr(1)); // insert before invalid position = append -> order = 01101
974            TEST_EXPECT_EQUAL(fts.size(), 5);
975            TEST_EXPECT_EQUAL(insertedAt, 4);
976            TEST_EXPECT_EQUAL(fts.get(insertedAt).getConfig(), cfg1);
977
978            // "undo" inserts
979            fts.erase(1); // -> order = 0101
980            fts.erase(3); // erase at end -> order = 010
981            fts.erase(0); // -> order = 10
982        }
983
984        fts.erase(0); // erase 1st rule -> order = 0
985        TEST_EXPECT_EQUAL(fts.size(), 1);
986        string finalcfg = fts.get(0).getConfig();
987        TEST_EXPECT_EQUAL(finalcfg, cfg0);
988    }
989}
990
991class FailingRule: public FieldTransfer::Rule {
992    string partOfFailReason;
993public:
994    FailingRule(const Rule& failing, string part) : Rule(failing), partOfFailReason(part) {}
995    FailingRule(FieldTransfer::RulePtr failing, string part) : Rule(*failing), partOfFailReason(part) {}
996    const char *expectedPartOfFailure() const { return partOfFailReason.c_str(); }
997};
998
999
1000struct XferEnv : virtual Noncopyable { // provides test environment for transfer tests
1001    GB_shell shell;
1002
1003    const char *target_ascii;
1004
1005    GBDATA *gb_src;
1006    GBDATA *gb_dest;
1007
1008    XferEnv() :
1009        target_ascii("TEST_fields_xferred.arb")
1010    {
1011        gb_src  = GB_open("TEST_fields_ascii.arb", "r"); // ../../UNIT_TESTER/run/TEST_fields_ascii.arb
1012        gb_dest = GB_open(target_ascii, "wc");
1013    }
1014    ~XferEnv() {
1015        TEST_EXPECT_ZERO_OR_SHOW_ERRNO(GB_unlink(target_ascii));
1016
1017        GB_close(gb_dest);
1018        GB_close(gb_src);
1019    }
1020
1021    void transferAllSpeciesBy(const FieldTransfer::RuleSet& ruleset) { // transfer all species according to Ruleset
1022        // @@@ transferAllSpeciesBy is quite similar to what has to happen in merge-tool
1023        GB_transaction tas(gb_src);
1024        GB_transaction tad(gb_dest);
1025
1026        for (GBDATA *gb_src_species = GBT_first_species(gb_src);
1027             gb_src_species;
1028             gb_src_species = GBT_next_species(gb_src_species))
1029        {
1030            const char *name = GBT_get_name(gb_src_species);
1031            TEST_REJECT_NULL(name);
1032
1033            GBDATA *gb_dest_species = GBT_find_or_create_species(gb_dest, name, false);
1034            TEST_REJECT_NULL(gb_dest_species);
1035
1036            // @@@ transferAllSpeciesBy could allow overwrites (keep existing fields; allow overwrite of fields):
1037            //     -> try to use ItemClonedByRuleSet with CLONE_INTO_EXISTING instead of direct call to transferBy below!
1038
1039            TEST_EXPECT_ZERO(GB_read_flag(gb_dest_species)); // (was previously done by GBT_find_or_create_species)
1040            TEST_EXPECT_NO_ERROR(ruleset.transferBy(gb_src_species, gb_dest_species));
1041        }
1042    }
1043
1044    // ----------------------------------------------------------------------------
1045
1046    void copyAllSpecies() {
1047        GB_transaction tas(gb_src);
1048        GB_transaction tad(gb_dest);
1049
1050        GBDATA *gb_dest_species_data = GBT_get_species_data(gb_dest);
1051        TEST_REJECT_NULL(gb_dest_species_data);
1052
1053        for (GBDATA *gb_src_species = GBT_first_species(gb_src);
1054             gb_src_species;
1055             gb_src_species = GBT_next_species(gb_src_species))
1056        {
1057            const char *name = GBT_get_name(gb_src_species);
1058            TEST_REJECT_NULL(name);
1059
1060            GBDATA *gb_dest_exists = GBT_find_species(gb_dest, name);
1061            TEST_EXPECT_NULL(gb_dest_exists); // this method cannot handle overwrites
1062
1063            GBDATA *gb_dest_species = GB_create_container(gb_dest_species_data, "species");
1064            TEST_REJECT_NULL(gb_dest_species);
1065
1066            TEST_EXPECT_NO_ERROR(GB_copy_dropProtectMarksAndTempstate(gb_dest_species, gb_src_species));
1067            TEST_EXPECT_ZERO(GB_read_flag(gb_dest_species));
1068        }
1069    }
1070
1071    // ----------------------------------------------------------------------------
1072    //      write transferred data to ascii db + compare with expected result:
1073    void save() {
1074        TEST_EXPECT_NO_ERROR(GB_save_as(gb_dest, target_ascii, "a"));
1075    }
1076    void saveAndCompare(const char *expected_ascii, bool allowAutoUpdate) {
1077        save();
1078        if (allowAutoUpdate) {
1079// #define TEST_AUTO_UPDATE // uncomment to update expected result
1080#if defined(TEST_AUTO_UPDATE)
1081            TEST_COPY_FILE(target_ascii, expected_ascii);
1082#endif
1083        }
1084        TEST_EXPECT_TEXTFILE_DIFFLINES(target_ascii, expected_ascii, 0);
1085    }
1086};
1087
1088static const char *expRuleConfig[] = {
1089    "source='lat_lon';target='geolocation'",
1090    "source='seq_quality_slv';target='seq/slv_quality'",
1091    "source='homop_slv';target='slv_homop'",
1092
1093    "source='no1';target='notTransferred'",
1094
1095    "source='pubmed_id';target='str2int';type='int'",
1096    "source='pubmed_id';target='str2flt';type='float'",
1097    "source='stop';target='int2flt';type='float'",
1098    "source='stop';target='int2str';type='text'",
1099    "source='align_ident_slv';target='flt2str';type='text'",
1100    "loss='permitted';source='align_ident_slv';target='flt2int';type='int'",
1101
1102    "aci='|lower|contains(partial)|isAbove(0)';source='description';target='describedAsPartial'",
1103
1104    "aci='|fdiv(2.0)';source='align_bp_score_slv';target='halfBPscoreStr'",
1105    "aci='|fdiv(2.0)';source='align_bp_score_slv';target='halfBPscore';type='int'",
1106    "aci='|fdiv(2.0)';source='align_bp_score_slv';target='halfBPscoreFlt';type='float'",
1107
1108    "aci='|fmult(3.5)';source='homop_slv';target='multiHomopStr'",
1109    "aci='|fmult(3.5)';source='homop_slv';target='multiHomopInt';type='int'",
1110    "aci='|fmult(3.5)';source='homop_slv';target='multiHomop';type='float'",
1111
1112    "sep='/';source='embl_class;embl_division';target='embl_class_division'",
1113    "sep='-';source='align_startpos_slv;align_stoppos_slv';target='align_range_slv'",
1114    "sep='\\'';source='no1;align_bp_score_slv;no2;rel_ltp;no3';target='missing'",
1115
1116    "sep=';';source='NO1;no2;no3';target='skipped'",
1117
1118    "aci='|upper';sep=':';source='embl_class;embl_division';target='emblClassDivision'",
1119    "aci='|\"<\";dd;\">\"';sep=';';source='no1;no2;no3';target='skipped2'",
1120};
1121
1122static const char *EXPECTED_ASCII        = "TEST_fields_xferred_expected.arb"; // ../../UNIT_TESTER/run/TEST_fields_xferred_expected.arb
1123static const char *EXPECTED_ASCII_CLONED = "TEST_fields_cloned_expected.arb"; // ../../UNIT_TESTER/run/TEST_fields_cloned_expected.arb
1124
1125void TEST_xferBySet() {
1126    // tests data transfer between items using RuleSet|s
1127    using namespace FieldTransfer;
1128    XferEnv env;
1129
1130    // --------------------------------------------------------------
1131    //      create rules and transfer item data using RuleSet|s:
1132
1133    typedef std::vector<FailingRule> FailingRuleCont;
1134
1135    RuleSet         ruleset;
1136    FailingRuleCont failing; // failing rules should go here
1137
1138#define FAILING_add(rule,msg) failing.push_back(FailingRule(rule, msg))
1139
1140    ruleset.add(Rule::makeSimple("lat_lon",         NOSEP, "geolocation"));     // STRING->STRING
1141    ruleset.add(Rule::makeSimple("seq_quality_slv", NOSEP, "seq/slv_quality")); // INT   ->INT     (generate hierarchical target key)
1142    ruleset.add(Rule::makeSimple("homop_slv",       NOSEP, "slv_homop"));       // FLOAT ->FLOAT
1143
1144    ruleset.add(Rule::makeSimple("no1", NOSEP, "notTransferred")); // missing fields are skipped
1145
1146    // force target types
1147    ruleset.add(Rule::forceTargetType(GB_INT,    Rule::makeSimple("pubmed_id",       NOSEP, "str2int"))); // STRING->INT
1148    ruleset.add(Rule::forceTargetType(GB_FLOAT,  Rule::makeSimple("pubmed_id",       NOSEP, "str2flt"))); // STRING->FLOAT
1149    ruleset.add(Rule::forceTargetType(GB_FLOAT,  Rule::makeSimple("stop",            NOSEP, "int2flt"))); // INT->FLOAT
1150    ruleset.add(Rule::forceTargetType(GB_STRING, Rule::makeSimple("stop",            NOSEP, "int2str"))); // INT->STRING
1151    ruleset.add(Rule::forceTargetType(GB_STRING, Rule::makeSimple("align_ident_slv", NOSEP, "flt2str"))); // FLOAT->STRING
1152    FAILING_add(Rule::forceTargetType(GB_INT,    Rule::makeSimple("align_ident_slv", NOSEP, "dummy")), "lossy float->int type conversion (9.484605e+01->9.500000e+01)"); // FLOAT->INT
1153    ruleset.add(Rule::permitPrecisionLoss(Rule::forceTargetType(GB_INT, Rule::makeSimple("align_ident_slv", NOSEP, "flt2int"))));    // FLOAT->INT
1154    // @@@ test forcedTargetType(GB_BITS)
1155
1156    // @@@ test transfer with existing target keys (and mismatching i.e. not default types)
1157    // -> shall force type conversion (e.g. int->float, float->string, string->int)
1158
1159    ruleset.add(Rule::makeAciConverter("description", NOSEP, "|lower|contains(partial)|isAbove(0)", "describedAsPartial")); // transports STRING through ACI to STRING
1160
1161    // INT | ACI -> STRING|INT|FLOAT:
1162    ruleset.add(Rule::makeAciConverter("align_bp_score_slv", NOSEP, "|fdiv(2.0)", "halfBPscoreStr"));                                  // transports INT through ACI to STRING
1163    ruleset.add(Rule::forceTargetType(GB_INT, Rule::makeAciConverter("align_bp_score_slv", NOSEP, "|fdiv(2.0)", "halfBPscore")));      // transports INT through ACI to INT // @@@ why does this not complain about conversion loss? (e.g. PurGergo 58.5 -> 58). examine!!!
1164    ruleset.add(Rule::forceTargetType(GB_FLOAT, Rule::makeAciConverter("align_bp_score_slv", NOSEP, "|fdiv(2.0)", "halfBPscoreFlt"))); // transports INT through ACI to FLOAT
1165
1166    // FLOAT | ACI -> STRING:
1167    ruleset.add(Rule::makeAciConverter("homop_slv", NOSEP, "|fmult(3.5)", "multiHomopStr"));                                // transports FLOAT through ACI to STRING
1168    ruleset.add(Rule::forceTargetType(GB_INT, Rule::makeAciConverter("homop_slv", NOSEP, "|fmult(3.5)", "multiHomopInt"))); // transports FLOAT through ACI to INT // @@@ conversion loss happens (for all species?) // @@@ examine!
1169    ruleset.add(Rule::forceTargetType(GB_FLOAT, Rule::makeAciConverter("homop_slv", NOSEP, "|fmult(3.5)", "multiHomop")));  // transports FLOAT through ACI to FLOAT
1170
1171    // @@@ test ACIs containing the following chars: ['"\\ = ]
1172
1173    // test concatenating rules:
1174    ruleset.add(Rule::makeSimple("embl_class;embl_division",               "/", "embl_class_division")); // concat 2 STRINGs
1175    ruleset.add(Rule::makeSimple("align_startpos_slv;align_stoppos_slv",   "-", "align_range_slv"));     // concat 2 INTs
1176    ruleset.add(Rule::makeSimple("no1;align_bp_score_slv;no2;rel_ltp;no3", "'", "missing"));             // concat INT + STRING (plus 3 non-existing fields)
1177
1178    ruleset.add(Rule::makeSimple("NO1;no2;no3", ";", "skipped")); // concat 3 non-existing fields -> field 'skipped' is NOT written to result DB
1179
1180    // test concatenation + ACI:
1181    ruleset.add(Rule::makeAciConverter("embl_class;embl_division", ":", "|upper",          "emblClassDivision")); // concat 2 STRINGs
1182    ruleset.add(Rule::makeAciConverter("no1;no2;no3",              ";", "|\"<\";dd;\">\"", "skipped2"));          // concat 3 non-existing fields and apply ACI -> field 'skipped2' is NOT written to result DB + ACI not applied
1183
1184    // ----------------------------------------------------------------------------
1185    // please do not change 'ruleset' below this point
1186    // ----------------------------------------------------------------------------
1187
1188    // test input/output field extraction
1189    {
1190        StrArray input;
1191        StrArray output;
1192
1193        ruleset.extractUsedFields(input, output);
1194
1195        char *inJoined  = GBT_join_strings(input,  ';');
1196        char *outJoined = GBT_join_strings(output, ';');
1197
1198        TEST_EXPECT_EQUAL(inJoined,  "align_bp_score_slv;align_ident_slv;align_startpos_slv;align_stoppos_slv;description;embl_class;embl_division;homop_slv;lat_lon;no1;no2;no3;pubmed_id;rel_ltp;seq_quality_slv;stop");
1199        TEST_EXPECT_EQUAL(outJoined, "align_range_slv;describedAsPartial;emblClassDivision;embl_class_division;flt2int;flt2str;geolocation;halfBPscore;halfBPscoreFlt;halfBPscoreStr;int2flt;int2str;missing;multiHomop;multiHomopInt;multiHomopStr;notTransferred;seq/slv_quality;skipped;skipped2;slv_homop;str2flt;str2int");
1200
1201        free(outJoined);
1202        free(inJoined);
1203    }
1204
1205    // convert all rules in 'ruleset' into string and test versus expRuleConfig:
1206    const size_t cfgs = ARRAY_ELEMS(expRuleConfig);
1207    const size_t rulz = ruleset.size();
1208    {
1209        const size_t testableRepr = min(cfgs, rulz);
1210        for (size_t r = 0; r<testableRepr; ++r) {
1211            TEST_ANNOTATE(GBS_global_string("r=%zu", r));
1212            const Rule& rule = ruleset.get(r);
1213            string      rep  = rule.getConfig();
1214            TEST_EXPECT_EQUAL(expRuleConfig[r], rep.c_str());
1215        }
1216    }
1217
1218    TEST_EXPECT_EQUAL(cfgs, rulz);
1219
1220    // test no 2 rules have equal config:
1221    for (size_t r1 = 0; r1<rulz; ++r1) {
1222        for (size_t r2 = r1+1; r2<rulz; ++r2) {
1223            TEST_ANNOTATE(GBS_global_string("r1/r2=%zu/%zu", r1, r2));
1224            TEST_EXPECT_DIFFERENT(expRuleConfig[r1], expRuleConfig[r2]);
1225        }
1226    }
1227    TEST_ANNOTATE(NULp);
1228
1229    env.transferAllSpeciesBy(ruleset);
1230
1231    // -------------------------------------------
1232    //      test missing source-/target-item:
1233    {
1234        GB_transaction tas(env.gb_src);
1235        GB_transaction tad(env.gb_dest);
1236
1237        GBDATA *gb_src_species = GBT_first_species(env.gb_src);
1238        TEST_REJECT_NULL(gb_src_species);
1239
1240        const char *name = GBT_get_name(gb_src_species);
1241        TEST_REJECT_NULL(name);
1242
1243        GBDATA *gb_dest_species = GBT_find_species(env.gb_dest, name);     // already has been created by 'transferAllSpeciesBy' above
1244        TEST_REJECT_NULL(gb_dest_species);
1245
1246        TEST_EXPECT_ERROR_CONTAINS(ruleset.transferBy(NULp,           gb_dest_species), "lacking item to readFrom");
1247        TEST_EXPECT_ERROR_CONTAINS(ruleset.transferBy(gb_src_species, NULp),            "lacking item to writeTo");
1248    }
1249
1250    // ---------------------------------------------
1251    //      test rules failing during transfer:
1252    FAILING_add(Rule::forceTargetType(GB_FLOAT, Rule::makeSimple("nuc_region", NOSEP, "str2flt")), "cannot convert '1..1494' to float"); // test conversion errors (e.g. non-numeric string -> int or float)
1253    FAILING_add(Rule::makeAciConverter("homop_slv", NOSEP, "|fmult(3.5, ooo)", "dummy"), "Unknown command '3.5'");
1254    FAILING_add(Rule::makeSimple("stop", NOSEP, "xx*xx"), "Invalid character '*' in 'xx*xx'");
1255    FAILING_add(Rule::makeSimple("ali_16s", NOSEP, "whatever"), "cannot read as data ('ali_16s' is a container)");
1256
1257    for (FailingRuleCont::const_iterator failRule = failing.begin(); failRule != failing.end(); ++failRule) {
1258        const FailingRule& testableRule = *failRule;
1259        RuleSet            separated;
1260        separated.add(new Rule(testableRule));
1261
1262        // apply rule:
1263        {
1264            GB_transaction tas(env.gb_src);
1265            GB_transaction tad(env.gb_dest);
1266
1267            GB_ERROR error = NULp;
1268
1269            for (GBDATA *gb_src_species = GBT_first_species(env.gb_src);
1270                 gb_src_species && !error;
1271                 gb_src_species = GBT_next_species(gb_src_species))
1272            {
1273                const char *name = GBT_get_name(gb_src_species);
1274                if (!name) {
1275                    error = "cannot search for unnamed species";
1276                }
1277                else {
1278                    GBDATA *gb_dest_species = GBT_find_species(env.gb_dest, name);     // already has been created by 'transferBy' above
1279                    error = separated.transferBy(gb_src_species, gb_dest_species);
1280                }
1281            }
1282            tad.close(error); // aborts transaction (if error occurs, which is expected here)
1283            TEST_EXPECT_ERROR_CONTAINS(error, testableRule.expectedPartOfFailure());
1284        }
1285    }
1286
1287    // ----------------------------------------------------------------
1288    //      test type of each field is same across all items of DB
1289    {
1290        GB_transaction tad(env.gb_dest);
1291        GBDATA *gb_fake_species_data = GB_create_container(env.gb_dest, "tmp"); // necessary because GBT_scan_db never scans DIRECT childs
1292
1293        typedef map<string,GB_TYPES> TypedField;
1294        TypedField seen;
1295
1296        GB_ERROR error = NULp;
1297        for (GBDATA *gb_dest_species = GBT_first_species(env.gb_dest);
1298             gb_dest_species && !error;
1299             gb_dest_species = GBT_next_species(gb_dest_species))
1300        {
1301            TEST_ANNOTATE(GBS_global_string("name=%s", GBT_get_name_or_description(gb_dest_species)));
1302
1303            GBDATA *gb_specCopy = GB_create_container(gb_fake_species_data, "tmp");
1304            error               = GB_copy_dropProtectMarksAndTempstate(gb_specCopy, gb_dest_species);
1305
1306            if (error) break;
1307
1308            StrArray curr;
1309            GBT_scan_db(curr, gb_fake_species_data, NULp);
1310            TEST_REJECT_ZERO(curr.size()); // expect fields
1311
1312            for (int i = 0; curr[i]; ++i) {
1313                const char *scanned = curr[i]; // 1st char is type
1314                const char *field   = scanned+1;
1315                GB_TYPES    type    = GB_TYPES(scanned[0]);
1316
1317                TypedField::iterator found = seen.find(field);
1318                if (found != seen.end()) {
1319                    if (type != found->second) {
1320                        TEST_ANNOTATE(field);
1321                        TEST_EXPECT_EQUAL(type, found->second); // existing field has to have same type (in all species)
1322                    }
1323                }
1324                else {
1325                    fprintf(stderr, "field='%s' type='%i'\n", field, type);
1326                    seen[field] = type; // insert new field
1327                }
1328            }
1329
1330            if (!error) error = GB_delete(gb_specCopy);
1331        }
1332        if (!error) error = GB_delete(gb_fake_species_data);
1333        TEST_EXPECT_NO_ERROR(error);
1334    }
1335
1336    // ----------------------------------------------------------------------------
1337    xf_assert(rulz == ruleset.size()); // please do not change 'ruleset' after 'rulz' has been set!
1338
1339    env.saveAndCompare(EXPECTED_ASCII, true);
1340}
1341
1342void TEST_LATE_ruleConfigsReadable() {
1343    // run this test later than TEST_xferBySet
1344
1345    using namespace FieldTransfer;
1346
1347    {
1348        // test failing Rule configs:
1349        struct InvalidConfig {
1350            const char *config;
1351            GB_ERROR    failure;
1352        };
1353        InvalidConfig invalidCfg[] = {
1354            { TARGET "='xxx'",                                 "missing source entry" },
1355            { SOURCE "='xxx'",                                 "missing target entry" },
1356            { "tag='halfquot;",                                "could not find matching quote" },
1357            { TARGET "='xxx';" SOURCE "='xxx';type='bizarre'", "invalid type id 'bizarre'" },
1358        };
1359
1360        for (size_t i = 0; i<ARRAY_ELEMS(invalidCfg); ++i) {
1361            InvalidConfig& CFG = invalidCfg[i];
1362            TEST_ANNOTATE(GBS_global_string("invalidCfg='%s'", CFG.config));
1363
1364            ErrorOrRulePtr result = Rule::makeFromConfig(CFG.config);
1365            TEST_EXPECT(result.hasError());
1366            TEST_EXPECT_ERROR_CONTAINS(result.getError(), CFG.failure);
1367        }
1368        TEST_ANNOTATE(NULp);
1369    }
1370
1371    const size_t cfgs = ARRAY_ELEMS(expRuleConfig);
1372    RuleSet      ruleset;
1373
1374    // convert config->Rule + Rule->config + compare configs:
1375    for (size_t r = 0; r<cfgs; ++r) {
1376        const char *config = expRuleConfig[r];
1377
1378        ErrorOrRulePtr result = Rule::makeFromConfig(config);
1379        if (result.hasError()) {
1380            TEST_EXPECT_NO_ERROR(result.getError());
1381        }
1382        else {
1383            RulePtr rule           = result.getValue();
1384            string  reloadedConfig = rule->getConfig();
1385            TEST_EXPECT_EQUAL(reloadedConfig, config);
1386
1387            ruleset.add(rule);
1388        }
1389    }
1390
1391    // test RuleSet comment:
1392    const char *COMMENT = "A multi-\nline-\ntest-\ncomment.";
1393    ruleset.setComment(COMMENT);
1394    TEST_EXPECT_EQUAL(ruleset.getComment(), COMMENT);
1395
1396    ruleset.set_transferUndefFields(true);
1397    TEST_EXPECT(ruleset.shallTransferUndefFields());
1398
1399    // save RuleSet + reload it + compare:
1400    RuleSet reloaded_ruleset;
1401    {
1402        const char *rulesetSaved    = "impexp/rulesetCurr.fts";
1403        const char *rulesetExpected = "impexp/ruleset.fts";
1404
1405        TEST_EXPECT_NO_ERROR(ruleset.saveTo(rulesetSaved));
1406// #define TEST_AUTO_UPDATE_RS // uncomment to update expected result
1407#if defined(TEST_AUTO_UPDATE_RS)
1408        TEST_COPY_FILE(rulesetSaved, rulesetExpected);
1409#endif
1410        TEST_EXPECT_TEXTFILE_DIFFLINES(rulesetSaved, rulesetExpected, 0);
1411        TEST_EXPECT_ZERO_OR_SHOW_ERRNO(GB_unlink(rulesetSaved));
1412
1413        // reload RuleSet:
1414        {
1415            ErrorOrRuleSetPtr loaded = RuleSet::loadFrom(rulesetExpected);
1416            if (loaded.hasError()) TEST_EXPECT_NO_ERROR(loaded.getError()); // if error -> dump+fail
1417
1418            const RuleSet& loadedSet = *loaded.getValue();
1419            TEST_EXPECT_EQUAL(loadedSet.size(), ruleset.size());
1420
1421            // compare reloaded rules configs vs. array of expected configs.
1422            // tests:
1423            // - save+load is correct and complete
1424            // - Rule order is stable
1425            for (size_t r = 0; r<loadedSet.size(); ++r) {
1426                const Rule& rule = loadedSet.get(r);
1427                string cfg = rule.getConfig();
1428                TEST_EXPECT_EQUAL(cfg.c_str(), expRuleConfig[r]);
1429            }
1430
1431            // test comment survives reload:
1432            TEST_EXPECT_EQUAL(loadedSet.getComment(), COMMENT);
1433
1434            // test transferUndefFields survives save/load:
1435            TEST_EXPECT(loadedSet.shallTransferUndefFields());
1436
1437            // use reloaded Ruleset for tests below:
1438            reloaded_ruleset = loadedSet; // also tests RuleSet-copy-ctor works.
1439
1440            // test comment gets copied:
1441            TEST_EXPECT_EQUAL(reloaded_ruleset.getComment(), loadedSet.getComment());
1442        }
1443    }
1444
1445    // test RuleSet load/save errors:
1446    {
1447        const char        *noSuchFile = "nosuch.fts";
1448        ErrorOrRuleSetPtr  loaded     = RuleSet::loadFrom(noSuchFile);
1449
1450        TEST_EXPECT(loaded.hasError());
1451        TEST_EXPECT_ERROR_CONTAINS(loaded.getError(), "No such file or directory");
1452
1453        const char *unsavable = "noSuchDir/whatever.fts";
1454        TEST_EXPECT_ERROR_CONTAINS(ruleset.saveTo(unsavable), "No such file or directory");
1455    }
1456
1457    // load empty file -> empty RuleSet
1458    {
1459        const char *emptyFile = "general/empty.input";
1460
1461        ErrorOrRuleSetPtr empty = RuleSet::loadFrom(emptyFile);
1462        TEST_REJECT(empty.hasError());
1463
1464        const RuleSet& emptySet = *empty.getValue();
1465        TEST_EXPECT_ZERO(emptySet.size());            // test emptySet has no rules
1466        TEST_EXPECT_EQUAL(emptySet.getComment(), ""); // test emptySet has no comment
1467    }
1468
1469    // use 'reloaded_ruleset' to modify same DB (as above in TEST_xferBySet):
1470    {
1471        XferEnv env;
1472        env.transferAllSpeciesBy(reloaded_ruleset);
1473        env.saveAndCompare(EXPECTED_ASCII, false); // if this fails -> saving/reloading config looses Rule information
1474    }
1475}
1476
1477#define CUSTOM_ALI_TRANSPORT_ERROR "custom ali transport error"
1478
1479struct TestAlignmentTransporter FINAL_TYPE : public FieldTransfer::AlignmentTransporter {
1480    int mode;
1481    TestAlignmentTransporter(int mode_) : mode(mode_) {}
1482    bool shallCopyBefore() const OVERRIDE {
1483        return false; // do not call copyAlignments() b4 calling transport()
1484    }
1485    GB_ERROR transport(GBDATA*gb_src_item, GBDATA *gb_dst_item) const OVERRIDE {
1486        GB_ERROR error = NULp;
1487        switch (mode) {
1488            case 1: // custom error
1489                error = CUSTOM_ALI_TRANSPORT_ERROR;
1490                break;
1491
1492            case 2: // do nothing -> sequence still has old value (or is not copied)
1493                break;
1494
1495            case 3: { // write reverse sequence data
1496                GBDATA *gb_src_data = GBT_find_sequence(gb_src_item, "ali_16s");
1497                GBDATA *gb_dst_data = GBT_find_sequence(gb_dst_item, "ali_16s");
1498
1499                if (!gb_dst_data) { // destination has no 'ali_16s' -> clone whole container
1500                    GBDATA *gb_src_ali = GB_get_father(gb_src_data);
1501
1502                    error = GB_incur_error_if(!GB_clone(gb_dst_item, gb_src_ali));
1503                    if (!error) {
1504                        gb_dst_data = GBT_find_sequence(gb_dst_item, "ali_16s");
1505                        xf_assert(gb_dst_data);
1506                    }
1507                }
1508
1509                if (!error) {
1510                    const char *seq = GB_read_char_pntr(gb_src_data);
1511                    char       *rev = GBT_reverseNucSequence(seq, strlen(seq));
1512
1513                    error = GB_write_string(gb_dst_data, rev);
1514                    free(rev);
1515                }
1516                break;
1517            }
1518            default: xf_assert(0); break; // unsupported mode
1519        }
1520        return error;
1521    }
1522};
1523
1524void TEST_clone_by_ruleset() {
1525    using namespace FieldTransfer;
1526
1527    RuleSetPtr ruleset;
1528    {
1529        const char *rulesetExpected = "impexp/ruleset.fts"; // same ruleset as used in tests above
1530
1531        ErrorOrRuleSetPtr loaded = RuleSet::loadFrom(rulesetExpected);
1532        if (loaded.hasError()) TEST_EXPECT_NO_ERROR(loaded.getError()); // if RuleSet load error -> dump+fail. see .@loadFrom
1533
1534        ruleset = loaded.getValue();
1535    }
1536
1537    // use 'ruleset' to modify same DB (but use ItemClonedByRuleSet here)
1538    {
1539        XferEnv env;
1540        env.copyAllSpecies(); // copy species of input DB -> output DB
1541
1542        GBDATA *gb_overwritten_species = NULp;
1543        char   *overwrittenName        = NULp;
1544
1545        // clone some species (inside output db):
1546        {
1547            GB_transaction ta(env.gb_dest);
1548
1549            GBDATA *gb_next_species = NULp;
1550            GBDATA *gb_first_clone  = NULp;
1551            int     count           = 0;
1552
1553            for (GBDATA *gb_species = GBT_first_species(env.gb_dest);
1554                 gb_species && gb_species != gb_first_clone;
1555                 gb_species                = gb_next_species, ++count)
1556            {
1557                gb_next_species = GBT_next_species(gb_species);
1558
1559                TEST_EXPECT_EQUAL(GB_countEntries(gb_species, "name"), 1); // safety-belt (had problems with duplicate name-entries)
1560
1561                char *orgName = nulldup(GBT_get_name(gb_species));
1562                TEST_REJECT_NULL(orgName);
1563
1564                GBDATA        *gb_clone = NULp;
1565                ItemCloneType  cloneHow = (count == 3 || count == 7) ? RENAME_ITEM_WHILE_TEMP_CLONE_EXISTS : REPLACE_ITEM_BY_CLONE;
1566
1567                ruleset->set_transferUndefFields(count == 4); // test transfer of undefined fields for species #4
1568
1569                {
1570                    ItemClonedByRuleSet clone(gb_species, CLONE_ITEM_SPECIES, ruleset, cloneHow, NULp, NULp);
1571
1572                    if (clone.has_error()) TEST_EXPECT_NO_ERROR(clone.get_error());
1573                    gb_clone = clone.get_clone();
1574                    if (!gb_first_clone) {
1575                        xf_assert(cloneHow == REPLACE_ITEM_BY_CLONE); // limit will not work otherwise
1576                        gb_first_clone = gb_clone;
1577                    }
1578
1579                    switch (cloneHow) {
1580                        case REPLACE_ITEM_BY_CLONE:
1581                            TEST_EXPECT_NULL(gb_species);
1582                            break;
1583                        case RENAME_ITEM_WHILE_TEMP_CLONE_EXISTS:
1584                            TEST_EXPECT_EQUAL(GB_countEntries(gb_species, "name"), 1);
1585                            TEST_EXPECT_EQUAL(GBT_get_name(gb_species), "fake"); // @@@ need a temporary name which cannot clash with existing names
1586                            break;
1587                        default:
1588                            xf_assert(0); // not tested here
1589                            break;
1590                    }
1591
1592                    TEST_EXPECT_EQUAL(GB_countEntries(gb_clone, "name"), 1);
1593                    TEST_EXPECT_EQUAL(GBT_get_name(gb_clone), orgName);
1594                }
1595                // 'clone' has been destroyed now!
1596
1597                if (cloneHow == RENAME_ITEM_WHILE_TEMP_CLONE_EXISTS) {
1598                    TEST_EXPECT_EQUAL(GBT_get_name(gb_species), orgName); // rename back worked (RENAME_ITEM_WHILE_TEMP_CLONE_EXISTS)
1599                }
1600                int orgNameCount = 0;
1601                for (GBDATA *gb_peek = GBT_first_species(env.gb_dest); gb_peek; gb_peek = GBT_next_species(gb_peek)) {
1602                    bool hasOrgName  = strcmp(GBT_get_name(gb_peek), orgName) == 0;
1603                    orgNameCount    += hasOrgName;
1604                    switch (cloneHow) {
1605                        case REPLACE_ITEM_BY_CLONE:
1606                            if (hasOrgName) TEST_EXPECT(gb_peek == gb_clone);   // orgName only used in persisting clone
1607                            TEST_EXPECT(gb_peek != gb_species);                 // species has been removed
1608                            break;
1609                        case RENAME_ITEM_WHILE_TEMP_CLONE_EXISTS:
1610                            if (hasOrgName) TEST_EXPECT(gb_peek == gb_species); // orgName only used in original species (i.e. temp. clone did vanish)
1611                            TEST_EXPECT(gb_peek != gb_clone);                   // clone has been removed
1612                            break;
1613                        default:
1614                            xf_assert(0); // not tested here
1615                            break;
1616                    }
1617                    // @@@ also test against "fake" names?
1618                }
1619                TEST_EXPECT_EQUAL(orgNameCount, 1); // species with duplicate names unwanted
1620
1621                if (count == 3) {
1622                    gb_overwritten_species = gb_species; // = copy of original
1623                    overwrittenName        = ARB_strdup(orgName);
1624                }
1625                free(orgName);
1626            }
1627        }
1628
1629        // test merging one species from source DB onto existing (cloned) species in dest DB:
1630        {
1631            GBDATA *gb_source_species;
1632
1633            {
1634                GB_transaction ta1(env.gb_src);
1635                GB_transaction ta2(env.gb_dest);
1636
1637                gb_source_species = GBT_find_species(env.gb_src, overwrittenName);
1638                TEST_REJECT_NULL(gb_source_species);      // (in gb_src)
1639                TEST_REJECT_NULL(gb_overwritten_species); // (in gb_dest)
1640
1641                {
1642                    GBDATA *gb_name = GB_entry(gb_overwritten_species, "name");
1643                    TEST_EXPECT_NO_ERROR(GB_write_string(gb_name, "notOverwritten")); // prepare to test overwrite by data.
1644
1645                    // modify "data" in "ali_16s" to prove overwrite later:
1646                    GBDATA *gb_seq = GBT_find_sequence(gb_overwritten_species, "ali_16s");
1647                    TEST_REJECT_NULL(gb_seq);
1648
1649                    const char *seq    = GB_read_char_pntr(gb_seq);
1650                    char       *seqMod = GBS_string_eval(seq, ":U=T");
1651
1652                    TEST_EXPECT_NO_ERROR(GB_write_string(gb_seq, seqMod));
1653                    free(seqMod);
1654                }
1655            }
1656
1657            SmartPtr<TestAlignmentTransporter> reverseAliTransporter;
1658
1659            // overwrite with result of ruleset (=> mix original and clone)
1660            for (int pass = 1; pass<=4; ++pass) {
1661                TEST_ANNOTATE(GBS_global_string("pass %i", pass));
1662
1663                GB_transaction ta1(env.gb_src);
1664                GB_transaction ta2(env.gb_dest);
1665
1666                SmartPtr<TestAlignmentTransporter> aliTransporter;
1667                if (pass<4) {
1668                    aliTransporter = new TestAlignmentTransporter(pass);
1669                    if (pass == 3) reverseAliTransporter = aliTransporter; // keep for later
1670                }
1671
1672                ItemClonedByRuleSet overclone(gb_source_species, CLONE_ITEM_SPECIES, ruleset, CLONE_INTO_EXISTING, gb_overwritten_species, aliTransporter.content());
1673
1674                if (pass == 1) {
1675                    TEST_EXPECT(overclone.has_error());
1676                    TEST_EXPECT_ERROR_CONTAINS(overclone.get_error(), CUSTOM_ALI_TRANSPORT_ERROR);
1677                    ta2.close(overclone.get_error());
1678                    ta1.close(overclone.get_error());
1679                }
1680                else {
1681                    if (overclone.has_error()) TEST_EXPECT_NO_ERROR(overclone.get_error()); // expect no error, but show message if expectation fails
1682                    TEST_EXPECT(overclone.get_clone() == gb_overwritten_species);           // test get_clone reports gb_overwritten_species here
1683                    TEST_EXPECT_EQUAL(GBT_get_name(gb_overwritten_species), "notOverwritten"); // test name of clone does not get overwritten
1684                    TEST_EXPECT_EQUAL(GB_countEntries(gb_overwritten_species, "name"), 1);
1685
1686                    {
1687                        GBDATA *gb_seq = GBT_find_sequence(gb_overwritten_species, "ali_16s");
1688                        TEST_REJECT_NULL(gb_seq);
1689
1690                        const char *seq = GB_read_char_pntr(gb_seq);
1691
1692                        switch (pass) {
1693                            case 2: TEST_EXPECT_CONTAINS(seq, "GAAGTAGCTTGCTACTTTGCCGGCGAGCGGCGGAC"); break; // custom transporter: do nothing
1694                            case 3: TEST_EXPECT_CONTAINS(seq, "CAGGCGGCGAGCGGCCGUUUCAUCGUUCGAUGAAG"); break; // custom transporter: writes reversed sequence data
1695                            case 4: TEST_EXPECT_CONTAINS(seq, "GAAGUAGCUUGCUACUUUGCCGGCGAGCGGCGGAC"); break; // default behavior (=copy sequence over)
1696                            default: xf_assert(0); break; // unexpected 'pass'
1697                        }
1698
1699
1700                        GBDATA *gb_ali = GB_get_father(gb_seq);
1701                        TEST_EXPECT_EQUAL(GB_countEntries(gb_ali, "data"), 1);
1702                    }
1703
1704                    TEST_EXPECT_EQUAL(GB_countEntries(gb_overwritten_species, "ali_16s"), 1);
1705                }
1706            }
1707
1708            // "test" REAL_CLONE mode
1709
1710            {
1711                GB_transaction ta1(env.gb_src);
1712                GB_transaction ta2(env.gb_dest);
1713
1714                ItemClonedByRuleSet realClone(gb_source_species, CLONE_ITEM_SPECIES, ruleset, REAL_CLONE, GBT_get_species_data(env.gb_dest), &*reverseAliTransporter);
1715
1716                if (realClone.has_error()) TEST_EXPECT_NO_ERROR(realClone.get_error()); // expect no error, but show message if expectation fails
1717
1718                GBDATA *gb_clone = realClone.get_clone();
1719
1720                TEST_REJECT_NULL(gb_clone);
1721                TEST_REJECT_NULL(gb_source_species);
1722                TEST_EXPECT(gb_clone != gb_source_species);
1723                TEST_EXPECT_EQUAL(GBT_get_name(gb_clone), GBT_get_name(gb_source_species));
1724
1725                TEST_REJECT(GB_get_father(gb_clone) == GB_get_father(gb_source_species));
1726
1727                {
1728                    GBDATA *gb_seq = GBT_find_sequence(gb_clone, "ali_16s");
1729                    TEST_REJECT_NULL(gb_seq);
1730
1731                    const char *seq = GB_read_char_pntr(gb_seq);
1732
1733                    // TEST_EXPECT_CONTAINS(seq, "GAAGUAGCUUGCUACUUUGCCGGCGAGCGGCGGAC"); // default behavior (=copy sequence over)
1734                    TEST_EXPECT_CONTAINS(seq, "CAGGCGGCGAGCGGCCGUUUCAUCGUUCGAUGAAG"); // custom transporter: writes reversed sequence data
1735
1736                    GBDATA *gb_ali = GB_get_father(gb_seq);
1737                    TEST_EXPECT_EQUAL(GB_countEntries(gb_ali, "data"), 1);
1738                }
1739            }
1740        }
1741
1742        env.saveAndCompare(EXPECTED_ASCII_CLONED, true);
1743        free(overwrittenName);
1744    }
1745}
1746
1747#endif // UNIT_TESTS
1748
1749// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.