/********************************************************************
 * utest-Transaction.c: GLib g_test test suite for Transaction.c.   *
 * Copyright 2012 John Ralls <jralls@ceridwen.us>		    *
 *                                                                  *
 * This program is free software; you can redistribute it and/or    *
 * modify it under the terms of the GNU General Public License as   *
 * published by the Free Software Foundation; either version 2 of   *
 * the License, or (at your option) any later version.              *
 *                                                                  *
 * This program is distributed in the hope that it will be useful,  *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 * GNU General Public License for more details.                     *
 *                                                                  *
 * You should have received a copy of the GNU General Public License*
 * along with this program; if not, you can retrieve it from        *
 * https://www.gnu.org/licenses/old-licenses/gpl-2.0.html            *
 * or contact:                                                      *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
 ********************************************************************/
#include <glib.h>

#include <config.h>
#include <string.h>
#include <unittest-support.h>
/* Add specific headers for this class */
#include "../Transaction.h"
#include "../TransactionP.hpp"
#include "../Split.h"
#include "../Account.h"
#include "../gnc-lot.h"
#include "../gnc-event.h"
#include <qof.h>

#if defined(__clang__)
#define USE_CLANG_FUNC_SIG 1
#endif

static const gchar *suitename = "/engine/Transaction";
extern "C" void test_suite_transaction ( void );

#include <qof-backend.hpp>
#include <kvp-frame.hpp>

/* Copied from Transaction.c. Changing these values will break
 * existing databases, which is a good reason to fail a test.
 */
static const char *trans_notes_str = "notes";
static const char *void_reason_str = "void-reason";
static const char *void_time_str = "void-time";
static const char *void_former_notes_str = "void-former-notes";
static const char *doclink_uri_str = "assoc_uri";
const char *trans_is_closing_str = "book_closing";
#define TRANS_DATE_DUE_KVP       "trans-date-due"
#define TRANS_TXN_TYPE_KVP       "trans-txn-type"
#define TRANS_READ_ONLY_REASON   "trans-read-only"
#define TRANS_REVERSED_BY        "reversed-by"

#define ISO_DATELENGTH 32 /* length of an iso 8601 date string. */


typedef struct
{
    Transaction *txn;
    Account *acc1;
    Account *acc2;
    gnc_commodity *curr;
    gnc_commodity *comm;
    TransTestFunctions *func;
    GSList *hdlrs;
} Fixture;

typedef struct
{
    Fixture base;
    Transaction *gains_txn;
    Account *gains_acc;
} GainsFixture;

class TransMockBackend : public QofBackend
{
public:
    TransMockBackend() : QofBackend(), m_last_call{"Constructor"},
                    m_result_err{ERR_BACKEND_NO_ERR} {}
    void session_begin(QofSession*, const char*, SessionOpenMode) override {
        m_last_call = "session_begin";
    }
    void session_end() override {
        m_last_call = "session_end";
    }
    void load(QofBook*, QofBackendLoadType) override {
        m_last_call = "load";
    }
    void sync(QofBook*) override {
        m_last_call = "sync";
    }
    void safe_sync(QofBook*) override {
        m_last_call = "safe_sync";
    }
    void rollback(QofInstance*) override {
        set_error(m_result_err);
        m_last_call = "rollback";
    }
    void inject_error(QofBackendError err) {
        m_result_err = err;
    }
    std::string m_last_call;
private:
    QofBackendError m_result_err;
};

static void
setup (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_book_new ();
    TransMockBackend *mbe = new TransMockBackend;
    Transaction *txn;
    time64 entered = gnc_dmy2time64 (20, 4, 2012);
    time64 posted = gnc_dmy2time64 (21, 4, 2012);
    auto frame = new KvpFrame ();

    qof_book_set_backend (book, mbe);
    auto split1 = xaccMallocSplit (book);
    auto split2 = xaccMallocSplit (book);
    txn = xaccMallocTransaction (book);
    fixture->txn = txn;
    fixture->curr = gnc_commodity_new (book, "Gnu Rand", "CURRENCY", "GNR", "", 240);
    fixture->comm = gnc_commodity_new (book, "Wildebeest Fund", "FUND", "WBFXX", "", 1000);
    fixture->acc1 = xaccMallocAccount (book);
    fixture->acc2 = xaccMallocAccount (book);
    xaccAccountSetCommodity (fixture->acc1, fixture->comm);
    xaccAccountSetCommodity (fixture->acc2, fixture->curr);
    txn->date_posted = posted;
    txn->date_entered = entered;
    split1->memo = CACHE_INSERT ("foo");
    split1->action = CACHE_INSERT ("bar");
    split1->amount = gnc_numeric_create (100000, 1000);
    split1->value = gnc_numeric_create (3200, 240);
    split2->amount = gnc_numeric_create (-3200, 240);
    split2->value = gnc_numeric_create (-3200, 240);
    split1->acc = fixture->acc1;
    split2->acc = fixture->acc2;
    txn->num = CACHE_INSERT ("123");
    txn->description = CACHE_INSERT ("Waldo Pepper");
    xaccTransBeginEdit (txn);
    {
        xaccTransSetCurrency (txn, fixture->curr);
        xaccSplitSetParent (split1, txn);
        xaccSplitSetParent (split2, txn);
        frame->set({trans_notes_str}, new KvpValue(g_strdup ("Salt pork sausage")));
        frame->set_path({"qux", "quux", "corge"}, new KvpValue(123.456));
        qof_instance_set_slots (QOF_INSTANCE (txn), frame);
    }
    xaccTransCommitEdit (txn);
    xaccAccountSortSplits(fixture->acc1, FALSE);
    xaccAccountSortSplits(fixture->acc2, FALSE);
    xaccAccountRecomputeBalance(fixture->acc1);
    xaccAccountRecomputeBalance(fixture->acc2);
    qof_instance_mark_clean (QOF_INSTANCE (split1));
    qof_instance_mark_clean (QOF_INSTANCE (split2));
    qof_instance_mark_clean (QOF_INSTANCE (txn));
    fixture->func = _utest_trans_fill_functions();
    fixture->hdlrs = NULL;
}

static void
setup_with_gains (GainsFixture *fixture, gconstpointer pData)
{
    QofBook *book;
    Fixture *base = &(fixture->base);

    setup (base, NULL);

    book = qof_instance_get_book (QOF_INSTANCE (base->txn));
    fixture->gains_txn = xaccMallocTransaction (book);
    fixture->gains_acc = xaccMallocAccount (book);
    xaccAccountSetCommodity (fixture->gains_acc, base->curr);
    auto gains_split1 = xaccMallocSplit (book);
    auto gains_split2 = xaccMallocSplit (book);
    gains_split1->acc = base->acc1;
    gains_split2->acc = fixture->gains_acc;
    gains_split1->amount = gnc_numeric_create (30, 240);
    gains_split1->value = gnc_numeric_create (30, 240);
    gains_split2->amount = gnc_numeric_create (-30, 240);
    gains_split2->value = gnc_numeric_create (-30, 240);
    xaccTransBeginEdit (fixture->gains_txn);
    {
        xaccTransSetCurrency (fixture->gains_txn, base->curr);
        xaccSplitSetParent (gains_split1, fixture->gains_txn);
        xaccSplitSetParent (gains_split2, fixture->gains_txn);
    }
    xaccTransCommitEdit (fixture->gains_txn);
    auto base_split = static_cast<Split*>(g_list_nth_data (base->txn->splits, 1));
    base_split->gains_split = gains_split1;
}


/* Add a log handler to the handlers list to be cleared at teardown */


static void
teardown (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
    auto mbe = static_cast<TransMockBackend*>(qof_book_get_backend (book));

    test_destroy (fixture->txn);
    test_destroy (fixture->acc1);
    test_destroy (fixture->acc2);
    test_destroy (fixture->curr);
    test_destroy (fixture->comm);
    delete mbe;
    qof_book_destroy(book);
    g_slist_free_full (fixture->hdlrs, test_free_log_handler);
    test_clear_error_list();
}

static void
teardown_with_gains (GainsFixture *fixture, gconstpointer pData)
{
    Fixture *base = &(fixture->base);
    test_destroy (fixture->gains_acc);
    teardown (base, NULL);
}

/* check_open
void check_open (const Transaction *trans)// Local: 1:0:0
*/
static void
test_check_open (Fixture *fixture, gconstpointer pData)
{
    auto msg = g_strdup_printf ("[check_open()] transaction %p not open for editing", fixture->txn);
    auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_CRITICAL |
                                                G_LOG_FLAG_FATAL);
    auto check = test_error_struct_new ("gnc.engine", loglevel, msg);
    g_free (msg);
    fixture->hdlrs = test_log_set_fatal_handler (fixture->hdlrs, check,
                     (GLogFunc)test_checked_handler);
    check_open (fixture->txn);
    g_assert_cmpint (check->hits, ==, 1);
    xaccTransBeginEdit (fixture->txn);
    check_open (fixture->txn);
    g_assert_cmpint (check->hits, ==, 1);
    /* Don't commit the edit, there's nothing to balance! */
    xaccTransRollbackEdit (fixture->txn);
}
/* xaccTransStillHasSplit
gboolean
xaccTransStillHasSplit(const Transaction *trans, const Split *s)// C: 8 in 3  Local: 7:0:0
*/
static void
test_xaccTransStillHasSplit (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
    auto split = xaccMallocSplit (book);
    g_assert_true (!xaccTransStillHasSplit (fixture->txn, split));
    xaccSplitSetParent (split, fixture->txn);
    g_assert_true (xaccTransStillHasSplit (fixture->txn, split));
    qof_instance_set_destroying (split, TRUE);
    g_assert_true (!xaccTransStillHasSplit (fixture->txn, split));
    qof_instance_set_destroying (split, FALSE);
    g_assert_true (xaccTransStillHasSplit (fixture->txn, split));
    xaccSplitSetParent (split, NULL);
    g_assert_true (!xaccTransStillHasSplit (fixture->txn, split));

    test_destroy (split);
}
// Make Static
/* mark_trans
void mark_trans (Transaction *trans)// Local: 3:0:0
*/
#define check_split_dirty(xsplit, test)                \
{                                                      \
    gboolean sort_dirty, balance_dirty;                \
    auto split = xsplit;                             \
    g_object_get (split->acc,                          \
		  "sort-dirty", &sort_dirty,           \
		  "balance-dirty", &balance_dirty,     \
		  NULL);                               \
    g_assert_cmpint (sort_dirty, ==, test);            \
    g_assert_cmpint (balance_dirty, ==, test);         \
}

static void
test_mark_trans (Fixture *fixture, gconstpointer pData)
{
    GList *splits = NULL;

    for (splits = (fixture->txn)->splits; splits; splits = splits->next)
    {
        if (!splits->data) continue;
        g_assert_true (!qof_instance_get_dirty_flag (splits->data));
        check_split_dirty (static_cast<Split*>(splits->data), FALSE);
    }
    fixture->func->mark_trans (fixture->txn);
    g_assert_true (!qof_instance_get_dirty_flag (fixture->txn));
    for (splits = (fixture->txn)->splits; splits; splits = splits->next)
    {
        if (!splits->data) continue;
        g_assert_true (!qof_instance_get_dirty_flag (splits->data));
        check_split_dirty (static_cast<Split*>(splits->data), TRUE);
    }
}
/* gen_event_trans
void gen_event_trans (Transaction *trans)// Local: 2:0:0
*/
static void
test_gen_event_trans (Fixture *fixture, gconstpointer pData)
{
    auto split = static_cast<Split*>(fixture->txn->splits->data);
    GNCLot *lot = gnc_lot_new (qof_instance_get_book (QOF_INSTANCE (fixture->txn)));
    TestSignal sig1 = test_signal_new (QOF_INSTANCE (fixture->acc1),
                                       GNC_EVENT_ITEM_CHANGED, split);
    TestSignal sig2 = test_signal_new (QOF_INSTANCE (lot),
                                       QOF_EVENT_MODIFY, NULL);
    gnc_lot_add_split (lot, split);
    test_signal_assert_hits (sig1, 1);
    test_signal_assert_hits (sig2, 3);
    fixture->func->gen_event_trans (fixture->txn);
    test_signal_assert_hits (sig1, 2);
    test_signal_assert_hits (sig2, 4);

    test_signal_free (sig1);
    test_signal_free (sig2);
    test_destroy (lot);
}
/* gnc_transaction_init
G_DEFINE_TYPE(Transaction, gnc_transaction, QOF_TYPE_INSTANCE)
static void
gnc_transaction_init(Transaction* trans)*/
static void
test_gnc_transaction_init ()
{
    auto txn = static_cast<Transaction*>(g_object_new (GNC_TYPE_TRANSACTION, NULL));
    g_assert_cmpstr (txn->num, ==, "");
    g_assert_cmpstr (txn->description, ==, "");
    g_assert_true (txn->common_currency == NULL);
    g_assert_true (txn->splits == NULL);
    g_assert_cmpint (txn->date_entered, ==, 0);
    g_assert_cmpint (txn->date_posted, ==, 0);
    g_assert_cmpint (txn->marker, ==, 0);
    g_assert_true (txn->orig == NULL);

    test_destroy (txn);
}
/* gnc_transaction_dispose
static void
gnc_transaction_dispose(GObject *txnp)*/
static void
test_gnc_transaction_dispose ()
{
    QofBook *book = qof_book_new ();
    auto txn = static_cast<Transaction*>(g_object_new (GNC_TYPE_TRANSACTION, "book", book, NULL));
    auto split = static_cast<Split*>(g_object_new (GNC_TYPE_SPLIT, "book", book, NULL));
    auto s_ref = split;
    gnc_commodity *curr = gnc_commodity_new (book, "Gnu Rand", "CURRENCY",
                          "GNR", "", 240);
    gnc_commodity *c_ref = curr;
    g_object_add_weak_pointer (G_OBJECT (split), (gpointer*) &s_ref);
    g_object_add_weak_pointer (G_OBJECT (curr), (gpointer*) &c_ref);
    txn->splits = g_list_append (txn->splits, split);
    txn->common_currency = curr;

    g_assert_true (txn->splits != NULL);
    g_assert_true (s_ref != NULL);
    g_assert_true (c_ref != NULL);

    g_object_run_dispose (G_OBJECT (txn));
    /* If gnc_transaction_dispose was written correctly, txn->splits and
     * txn->curr would be null and all of the splits would be destroyed,
     * so all of these would be equal instead of unequal.
     */
    g_assert_true (txn->splits != NULL);
    g_assert_true (txn->common_currency != NULL);
    g_assert_true (s_ref != NULL);
    g_assert_true (c_ref != NULL);
    /* And these would be unnecessary -- in fact, they would assert */
    test_destroy (split);
    test_destroy (curr);

    test_destroy (txn);
    qof_book_destroy (book);
}
/* gnc_transaction_finalize
static void
gnc_transaction_finalize(GObject* txnp)*/
static void
test_gnc_transaction_finalize ()
{
    auto txn = static_cast<Transaction*>(g_object_new (GNC_TYPE_TRANSACTION, NULL));
    test_destroy (txn);
}
/* gnc_transaction_get_property
 * gnc_transaction_set_property
static void
gnc_transaction_set_property(GObject* object,*/
static void
test_gnc_transaction_set_get_property (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_book_new ();
    auto txn = static_cast<Transaction*>(g_object_new (GNC_TYPE_TRANSACTION, "book", book, NULL));
    auto num = "42", desc = "The Answer";
    gchar *t_num = NULL, *t_desc = NULL;
    gnc_commodity *curr = gnc_commodity_new (book, "Gnu Rand", "CURRENCY",
                          "GNR", "", 240), *t_curr = NULL;
    time64 now = gnc_time(NULL);
    Time64 *t_entered, *t_posted;
    g_assert_cmpstr (txn->num, ==, "");
    g_assert_cmpstr (txn->description, ==, "");
    g_assert_true (txn->common_currency == NULL);
    g_assert_cmpint (txn->date_entered, ==, 0);
    g_assert_cmpint (txn->date_posted, ==, 0);
    /* Kick up the edit counter to keep from committing */
    xaccTransBeginEdit (txn);
    g_object_set (G_OBJECT (txn),
                  "num", num,
                  "description", desc,
                  "currency", curr,
                  "post-date", &now,
                  "enter-date", &now,
                  NULL);

    g_assert_cmpstr (txn->num, ==, num);
    g_assert_cmpstr (txn->description, ==, desc);
    g_assert_true (txn->common_currency == curr);
    g_assert_true (txn->date_entered == now);
    g_assert_true (txn->date_posted == now);

    g_object_get (G_OBJECT (txn),
                  "num", &t_num,
                  "description", &t_desc,
                  "currency", &t_curr,
                  "post-date", &t_posted,
                  "enter-date", &t_entered,
                  NULL);

    g_assert_cmpstr (t_num, ==, num);
    g_assert_cmpstr (t_desc, ==, desc);
    g_assert_true (t_curr == curr);
    g_assert_cmpint (t_entered->t, ==, now);
    g_assert_cmpint (t_posted->t, ==, now);
    xaccTransRollbackEdit (txn);
    test_destroy (txn);
    test_destroy (curr);
    qof_book_destroy (book);
}
/* gnc_transaction_class_init
 * xaccInitTransaction
No way to really test class_init directly -- though the above tests cover everything pretty well indirectly. xaccInitTransaction is a useless one-line function that sets the book in the parent QofInstance.
 */
static void
test_xaccMallocTransaction (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_book_new ();
    TestSignal sig1 = test_signal_new (NULL, QOF_EVENT_CREATE,NULL);
    Transaction *txn;
#ifdef USE_CLANG_FUNC_SIG
#define _func "Transaction *xaccMallocTransaction(QofBook *)"
#else
#define _func "Transaction* xaccMallocTransaction(QofBook*)"
#endif
    auto msg = _func ": assertion 'book' failed";
#undef _func
    auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL);
    auto check = test_error_struct_new ("gnc.engine", loglevel, msg);
    fixture->hdlrs = test_log_set_fatal_handler (fixture->hdlrs, check,
                     (GLogFunc)test_checked_handler);
    test_signal_assert_hits (sig1, 0);
    txn = xaccMallocTransaction (NULL);
    g_assert_true (txn == NULL);
    g_assert_cmpint (check->hits, ==, 1);
    test_signal_assert_hits (sig1, 0);

    txn = xaccMallocTransaction (book);
    g_assert_true (txn != NULL);
    g_assert_cmpint (check->hits, ==, 1);
    test_signal_assert_hits (sig1, 1);

    test_destroy (txn);
    qof_book_destroy (book);
    test_signal_free (sig1);
}
/* xaccTransSortSplits
void
xaccTransSortSplits (Transaction *trans)// Local: 1:0:0
*/
static void
test_xaccTransSortSplits (Fixture *fixture, gconstpointer pData)
{
    Transaction *txn = fixture->txn;
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (txn));
    auto split1 = static_cast<Split*>(txn->splits->data);
    auto split2 = static_cast<Split*>(txn->splits->next->data);
    Split *split[3];
    guint i;
    GList *node;
    gnc_numeric values[3];

    values[0] = gnc_numeric_create (100, 240);
    values[1] = gnc_numeric_create (75, 240);
    values[2] = gnc_numeric_create (-125, 240);
    /* Prevent xaccTransCommitEdit in xaccSplitSetParent from doing anything */
    xaccTransBeginEdit (txn);
    for (i = 0; i < G_N_ELEMENTS (split); i++)
    {
        split[i] = xaccMallocSplit (book);
        split[i]->value = values[i];
        split[i]->acc = fixture->acc1;
        xaccSplitSetParent (split[i], txn);
    }

    node = txn->splits;
    g_assert_true (node->data == split1);
    node = g_list_next (node);
    g_assert_true (node->data == split2);
    node = g_list_next (node);
    g_assert_true (node->data == split[0]);
    node = g_list_next (node);
    g_assert_true (node->data == split[1]);
    node = g_list_next (node);
    g_assert_true (node->data == split[2]);

    xaccTransSortSplits (txn);

    node = txn->splits;
    g_assert_true (node->data == split1);
    node = g_list_next (node);
    g_assert_true (node->data == split[0]);
    node = g_list_next (node);
    g_assert_true (node->data == split[1]);
    node = g_list_next (node);
    g_assert_true (node->data == split2);
    node = g_list_next (node);
    g_assert_true (node->data == split[2]);

    xaccTransCommitEdit (txn);
}
/* dupe_trans
static Transaction *
dupe_trans (const Transaction *from)// Local: 1:0:0
*/
static void
test_dupe_trans (Fixture *fixture, gconstpointer pData)
{
    time64 posted = gnc_dmy2time64 (12, 7, 2011);
    time64 entered = gnc_dmy2time64 (14, 7, 2011);
    Transaction *newtxn = NULL, *oldtxn = fixture->txn;
    QofBook *old_book = qof_instance_get_book (QOF_INSTANCE (oldtxn));
    GList *newnode, *oldnode = oldtxn->splits;

    oldtxn->date_posted = posted;
    oldtxn->date_entered = entered;
    oldtxn->inst.kvp_data->set({"foo", "bar", "baz"},
                               new KvpValue(g_strdup ("The Great Waldo Pepper")));

    newtxn = fixture->func->dupe_trans (oldtxn);

    g_assert_cmpstr (newtxn->num, ==, oldtxn->num);
    g_assert_cmpstr (newtxn->description, ==, oldtxn->description);
    for (newnode = newtxn->splits; newnode && oldnode;
            newnode = g_list_next (newnode))
    {
        g_assert_true (xaccSplitEqual (static_cast<Split*>(newnode->data),
                                  static_cast<Split*>(oldnode->data),
                                  TRUE, FALSE, TRUE));
        oldnode = g_list_next (oldnode);
    }
    g_assert_true (newnode == NULL);
    g_assert_true (oldnode == NULL);
    g_assert_true (newtxn->date_posted == posted);
    g_assert_true (newtxn->date_entered == entered);
    g_assert_true (qof_instance_version_cmp (QOF_INSTANCE (newtxn),
                                        QOF_INSTANCE (oldtxn)) == 0);
    g_assert_true (newtxn->orig == NULL);
    g_assert_true (newtxn->common_currency == fixture->curr);
    g_assert_true (newtxn->inst.e_type == NULL);
    g_assert_true (guid_equal (qof_instance_get_guid (QOF_INSTANCE (newtxn)),
                          guid_null ()));
    g_assert_true (qof_instance_get_book (QOF_INSTANCE (newtxn)) == old_book);
    g_assert_true (compare (oldtxn->inst.kvp_data, newtxn->inst.kvp_data) == 0);

    test_destroy (newtxn);
}
/* xaccTransClone
Transaction *
xaccTransClone (const Transaction *from)// C: 1  Local: 1:0:0
*/
static void
test_xaccTransClone (Fixture *fixture, gconstpointer pData)
{
    time64 posted = gnc_dmy2time64 (12, 7, 2011);
    time64 entered = gnc_dmy2time64 (14, 7, 2011);
    Transaction *newtxn = NULL, *oldtxn = fixture->txn;
    QofBook *old_book = qof_instance_get_book (QOF_INSTANCE (oldtxn));
    GList *newnode, *oldnode;

    oldtxn->date_posted = posted;
    oldtxn->date_entered = entered;
    newtxn = xaccTransClone (oldtxn);

    g_assert_cmpstr (newtxn->num, ==, oldtxn->num);
    g_assert_cmpstr (newtxn->description, ==, oldtxn->description);

    g_assert_cmpint (xaccTransCountSplits (oldtxn), ==,
                     xaccTransCountSplits (newtxn));

    xaccTransSortSplits (newtxn);
    xaccTransSortSplits (oldtxn);

    oldnode = oldtxn->splits;
    for (newnode = newtxn->splits; newnode && oldnode;
            newnode = g_list_next (newnode))
    {
        g_assert_true (xaccSplitEqual (static_cast<Split*>(newnode->data),
                                  static_cast<Split*>(oldnode->data),
                                  FALSE, FALSE, FALSE));
        oldnode = g_list_next (oldnode);
    }
    g_assert_true (newnode == NULL);
    g_assert_true (oldnode == NULL);
    g_assert_true (newtxn->date_posted == posted);
    g_assert_true (newtxn->date_entered == entered);
    g_assert_true (qof_instance_version_cmp (QOF_INSTANCE (newtxn),
                                        QOF_INSTANCE (oldtxn)) == 0);
    g_assert_cmpint (qof_instance_get_version_check (newtxn), ==,
                     qof_instance_get_version_check (oldtxn));
    g_assert_true (newtxn->orig == NULL);
    g_assert_true (newtxn->common_currency == fixture->curr);

    g_assert_true (qof_instance_get_book (QOF_INSTANCE (newtxn)) == old_book);
    g_assert_true (compare (oldtxn->inst.kvp_data, newtxn->inst.kvp_data) == 0);

    test_destroy (newtxn);
}

/* xaccTransCopyOnto
void
xaccTransCopyOnto (const Transaction *from_trans, Transaction *to_trans)//Register 2
convenience function for xaccTransCopyFromClipboard (from_trans, to_trans, NULL, NULL, TRUE)
*/
/* xaccTransCopyFromClipboard
void
xaccTransCopyFromClipboard (const Transaction *from_trans,
			    Transaction *to_trans,
			    const Account *from_acc,
			    Account *to_acc, gboolean no_date) // Register 2
*/
static void
test_xaccTransCopyFromClipBoard (Fixture *fixture, gconstpointer pData)
{
    Transaction *txn = fixture->txn;
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (txn));
    Account *acc1 = xaccMallocAccount (book);
    Transaction *to_txn = xaccMallocTransaction (book);
    time64 now = gnc_time (nullptr);

    xaccAccountSetCommodity (acc1, fixture->comm);
    xaccTransCopyFromClipBoard (txn, to_txn, fixture->acc1, acc1, FALSE);
    g_assert_true (gnc_commodity_equal (txn->common_currency,
                                   to_txn->common_currency));
    g_assert_true (to_txn->date_entered == now);
    g_assert_true (to_txn->date_posted == txn->date_posted);
    g_assert_cmpstr (txn->num, ==, to_txn->num);
    /* Notes also tests that KVP is copied */
    g_assert_cmpstr (xaccTransGetNotes (txn), ==, xaccTransGetNotes (to_txn));
    g_assert_cmpstr (xaccTransGetDescription (txn), ==,
                     xaccTransGetDescription (to_txn));
    g_assert_cmpstr (xaccTransGetNotes (txn), ==, xaccTransGetNotes (to_txn));
    g_assert_cmpint (xaccTransCountSplits (txn), ==,
                     xaccTransCountSplits (to_txn));
}

static void
test_xaccTransCopyFromClipBoard_no_start (Fixture *fixture, gconstpointer pData)
{
    Transaction *txn = fixture->txn;
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (txn));
    Account *acc1 = xaccMallocAccount (book);
    Transaction *to_txn = xaccMallocTransaction (book);
    time64 now = gnc_time (nullptr);
    time64 never = 0;

    xaccAccountSetCommodity (acc1, fixture->comm);
    xaccTransCopyFromClipBoard (txn, to_txn, fixture->acc1, acc1, TRUE);
    g_assert_true (gnc_commodity_equal (txn->common_currency,
                                   to_txn->common_currency));
    g_assert_true (to_txn->date_entered == now);
    g_assert_true (to_txn->date_posted == never);
    g_assert_cmpstr (to_txn->num, ==, txn->num);
    /* Notes also tests that KVP is copied */
    g_assert_cmpstr (xaccTransGetNotes (txn), ==, xaccTransGetNotes (to_txn));
    g_assert_cmpstr (xaccTransGetDescription (txn), ==,
                     xaccTransGetDescription (to_txn));
    g_assert_cmpstr (xaccTransGetNotes (txn), ==, xaccTransGetNotes (to_txn));
    g_assert_cmpint (xaccTransCountSplits (txn), ==,
                     xaccTransCountSplits (to_txn));
}

/* xaccFreeTransaction
static void
xaccFreeTransaction (Transaction *trans)// Local: 4:0:0
*/
static void
test_xaccFreeTransaction (Fixture *fixture, gconstpointer pData)
{
    Transaction *txn = fixture->txn;
    Transaction *orig = xaccMallocTransaction (qof_instance_get_book (QOF_INSTANCE (txn)));
    auto split = static_cast<Split*>(txn->splits->data);
    auto txn_num = "321";
    g_object_add_weak_pointer (G_OBJECT (txn->splits->data),
                               reinterpret_cast<void**>(&split));
    /* so the "free" doesn't, leaving the structure for us to test */
    g_object_ref (txn);
    g_object_ref (orig);
    orig->num = CACHE_INSERT (txn_num);
    txn->orig = orig;

    fixture->func->xaccFreeTransaction (txn);

    g_assert_true (split == NULL);
    g_assert_true (txn->splits == NULL);
    g_assert_cmpint (GPOINTER_TO_INT(txn->num), ==, 1);
    g_assert_true (txn->description == NULL);
    g_assert_cmpint (txn->date_entered, ==, 0);
    g_assert_cmpint (txn->date_posted, ==, 0);
    g_assert_cmpint (GPOINTER_TO_INT(orig->num), ==, 1);
    g_assert_true (txn->orig == NULL);
    test_destroy (orig);

    g_test_log_set_fatal_handler ((GTestLogFatalFunc) test_log_handler, NULL);

}
/* compare_split_guids
static gint
compare_split_guids (gconstpointer a, gconstpointer b)// Local: 0:1:0

Pass-through function, test with TransEqual
*/
/* xaccTransEqual
gboolean
xaccTransEqual(const Transaction *ta, const Transaction *tb,// C: 2 in 2  Local: 0:0:0
*/
#define DATE_BUF_SIZE 100
static void
test_xaccTransEqual (Fixture *fixture, gconstpointer pData)
{

    QofBook *book2 = qof_book_new ();
    Transaction *txn0 = fixture->txn;
    Transaction *clone = xaccTransClone (txn0);
    Transaction *txn1 = xaccTransClone (txn0);
    const GncGUID *guid_f_txn = qof_instance_get_guid (txn0);
    gchar entered[DATE_BUF_SIZE], posted[DATE_BUF_SIZE];
    auto msg1 = "[xaccTransEqual] one is nullptr";
    gchar *msg2 = NULL;
    auto cleanup_fmt = "[trans_cleanup_commit] get rid of rollback trans=%p";
    gchar split_guid0[GUID_ENCODING_LENGTH + 1];
    gchar split_guid1[GUID_ENCODING_LENGTH + 1];
    auto logdomain = "gnc.engine";
    auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_INFO);
    auto check = test_error_struct_new (logdomain, loglevel, msg1);
    auto check2 = test_error_struct_new(logdomain, loglevel, msg2);
    auto check3 = test_error_struct_new(logdomain, loglevel, "");
    auto cleanup = test_error_struct_new (logdomain, loglevel, "");
    auto split0 = xaccTransGetSplit (txn0, 0);
    qof_log_set_level (GNC_MOD_ENGINE, QOF_LOG_INFO);
    test_add_error (check);
    test_add_error (check2);
    test_add_error (cleanup);

    fixture->hdlrs = test_log_set_handler (fixture->hdlrs, check,
                                           (GLogFunc)test_list_handler);
    /* Booleans are check_guids, check_splits, check_balances, assume_ordered */
    g_assert_true (xaccTransEqual (NULL, NULL, TRUE, TRUE, TRUE, TRUE));
    g_assert_true (!xaccTransEqual (txn0, NULL, TRUE, TRUE, TRUE, TRUE));
    g_assert_true (!xaccTransEqual (NULL, txn0, TRUE, TRUE, TRUE, TRUE));
    g_assert_true (xaccTransEqual (txn0, txn0, TRUE, TRUE, TRUE, TRUE));

    qof_instance_set_book (txn1, book2);
    qof_instance_set_guid (txn1, guid_f_txn);
    g_assert_cmpint (check->hits, ==, 2);
    check->hits = 0;

    g_assert_cmpint (xaccTransCountSplits (txn0), ==,
                     xaccTransCountSplits (txn1));

    g_free (check->msg);
    check->msg = g_strdup ("[xaccTransEqual] GUIDs differ");
    g_assert_true (!xaccTransEqual (clone, txn0, TRUE, TRUE, TRUE, TRUE));
    qof_instance_set_guid (clone, guid_f_txn);
    g_assert_true (xaccTransEqual (clone, txn0, TRUE, FALSE, TRUE, TRUE));
    g_assert_cmpint (check->hits, ==, 1);
    xaccTransBeginEdit (clone);
    cleanup->msg = g_strdup_printf (cleanup_fmt, clone->orig);
    /* This changes the amount and value of the first split */
    xaccTransSetCurrency (clone, fixture->comm);
    xaccTransCommitEdit (clone);
    g_free (cleanup->msg);
    g_free (check->msg);
    check->msg = g_strdup_printf ("[xaccTransEqual] commodities differ %s vs %s", gnc_commodity_get_unique_name (fixture->comm), gnc_commodity_get_unique_name (fixture->curr));
    g_assert_true (!xaccTransEqual (clone, txn0, TRUE, FALSE, TRUE, TRUE));
    g_assert_cmpint (check->hits, ==, 2);

    gnc_time64_to_iso8601_buff (clone->date_posted, posted);
    gnc_time64_to_iso8601_buff (clone->date_entered, entered);
    xaccTransBeginEdit (clone);
    cleanup->msg = g_strdup_printf (cleanup_fmt, clone->orig);
    /* This puts the value of the first split back, but leaves the amount changed */
    xaccTransSetCurrency (clone, fixture->curr);
    clone->date_posted = txn0->date_entered;
    xaccTransCommitEdit (clone);
    g_free (cleanup->msg);
    g_free (check->msg);
    check->msg = g_strdup_printf ("[xaccTransEqual] date posted differs: '%s' vs '%s'", entered, posted);
    g_assert_true (!xaccTransEqual (clone, txn0, TRUE, FALSE, TRUE, TRUE));
    g_assert_cmpint (check->hits, ==, 3);

    xaccTransBeginEdit (clone);
    cleanup->msg = g_strdup_printf (cleanup_fmt, clone->orig);
    clone->date_posted = txn0->date_posted;
    clone->date_entered = txn0->date_posted;
    xaccTransCommitEdit (clone);
    g_free (cleanup->msg);
    g_free (check->msg);
    check->msg = g_strdup_printf ("[xaccTransEqual] date entered differs: '%s' vs '%s'", posted, entered);
    g_assert_true (!xaccTransEqual (clone, txn0, TRUE, FALSE, TRUE, TRUE));
    g_assert_cmpint (check->hits, ==, 4);

    xaccTransBeginEdit (clone);
    cleanup->msg = g_strdup_printf (cleanup_fmt, clone->orig);
    clone->date_entered = txn0->date_entered;
    clone->num = g_strdup("123");
    xaccTransCommitEdit (clone);
    g_free (cleanup->msg);
    g_free (check->msg);
    check->msg = g_strdup ("[xaccTransEqual] num differs: 123 vs 123");
    g_assert_true (!xaccTransEqual (clone, txn0, TRUE, FALSE, TRUE, TRUE));
    g_assert_cmpint (check->hits, ==, 5);
    g_assert_true (xaccTransEqual (txn1, clone, TRUE, FALSE, TRUE, TRUE));
    g_assert_cmpint (check->hits, ==, 5);

    txn1->num = CACHE_INSERT("321");
    g_free (check->msg);
    check->msg = g_strdup ("[xaccTransEqual] num differs: 321 vs 123");
    g_assert_true (!xaccTransEqual (txn1, txn0, TRUE, FALSE, TRUE, TRUE));
    g_assert_cmpint (check->hits, ==, 6);

    g_free ((char*)clone->num);
    clone->num = CACHE_INSERT("123");
    CACHE_REMOVE(txn1->num);
    txn1->num = g_strdup("123");
    clone->description = CACHE_INSERT("salt pork");
    g_free (check->msg);
    check->msg = g_strdup ("[xaccTransEqual] descriptions differ: salt pork vs Waldo Pepper");
    g_assert_true (!xaccTransEqual (clone, txn0, TRUE, FALSE, TRUE, TRUE));
    g_assert_cmpint (check->hits, ==, 7);
    g_assert_true (xaccTransEqual (txn1, txn0, TRUE, FALSE, TRUE, TRUE));
    g_assert_cmpint (check->hits, ==, 7);
    g_assert_true (!xaccTransEqual (clone, txn1, TRUE, FALSE, TRUE, TRUE));
    g_assert_cmpint (check->hits, ==, 8);

    xaccTransBeginEdit (clone);
    cleanup->msg = g_strdup_printf (cleanup_fmt, clone->orig);
    CACHE_REMOVE(clone->description);
    clone->description = CACHE_INSERT ("Waldo Pepper");
    auto frame = qof_instance_get_slots (QOF_INSTANCE (clone));
    frame->set({"qux", "quux", "corge"}, new KvpValue(654.321));
    xaccTransCommitEdit (clone);
    g_free (cleanup->msg);
    g_free (check->msg);
    check->msg = g_strdup ("[xaccTransEqual] kvp frames differ:\nnotes/Salt pork sausage (char *)\nqux/quux/corge/654.321 (double)\n\n\n\n\nvs\n\nnotes/Salt pork sausage (char *)\nqux/quux/corge/123.456 (double)\n\n\n");

    g_assert_true (!xaccTransEqual (clone, txn0, TRUE, FALSE, TRUE, TRUE));

    g_assert_cmpint (check->hits, ==, 9);
    xaccTransBeginEdit (clone);
    cleanup->msg = g_strdup_printf (cleanup_fmt, clone->orig);
    clone->description = CACHE_INSERT ("Waldo Pepper");
    frame->set({"qux", "quux", "corge"}, new KvpValue(123.456));
    xaccTransCommitEdit (clone);
    g_free (cleanup->msg);
    cleanup->msg = NULL;
    g_free (check->msg);
    check->msg = g_strdup ("[xaccSplitEqual] GUIDs differ");
    auto split1 = xaccTransGetSplit (clone, 0);
    guid_to_string_buff (qof_instance_get_guid (split0), split_guid0);
    guid_to_string_buff (qof_instance_get_guid (split1), split_guid1);
    check2->msg = g_strdup_printf (
                     "[xaccTransEqual] splits %s and %s differ", split_guid1, split_guid0);

    g_assert_true (!xaccTransEqual (clone, txn0, TRUE, TRUE, TRUE, TRUE));
    g_assert_true (xaccTransEqual (clone, txn0, FALSE, FALSE, FALSE, TRUE));
    g_assert_cmpint (check->hits, ==, 10);
    g_assert_cmpint (check2->hits, ==, 1);

    g_free (check2->msg);
    check2->msg = g_strdup_printf (
                     "[xaccTransEqual] splits %s and %s differ", split_guid0, split_guid0);
    qof_instance_set_guid (split1, qof_instance_get_guid (split0));
    g_assert_true (!xaccTransEqual (clone, txn0, TRUE, TRUE, TRUE, TRUE));
    g_assert_true (xaccTransEqual (clone, txn0, TRUE, FALSE, FALSE, TRUE));
    g_assert_cmpint (check->hits, ==, 10);
    g_assert_cmpint (check2->hits, ==, 2);

    qof_instance_set_guid (xaccTransGetSplit (txn1, 0),
                           qof_instance_get_guid (split0));
    qof_instance_set_guid (xaccTransGetSplit (txn1, 1),
                           qof_instance_get_guid (xaccTransGetSplit (txn0, 1)));
    {
        Split* split00 = xaccTransGetSplit (txn0, 0);
        Split* split01 = xaccTransGetSplit (txn0, 1);
        Split* split10 = xaccTransGetSplit (txn1, 0);
        Split* split11 = xaccTransGetSplit (txn1, 1);
        auto bal00 = gnc_numeric_to_string (split00->balance);
        auto bal01 = gnc_numeric_to_string (split01->balance);
        auto bal10 = gnc_numeric_to_string (split10->balance);
        auto bal11 = gnc_numeric_to_string (split11->balance);
        g_free (check->msg);
        check->msg = g_strdup_printf("[xaccSplitEqualCheckBal] balances differ: %s vs %s", bal10, bal00);
        check3->msg = g_strdup_printf("[xaccSplitEqualCheckBal] balances differ: %s vs %s", bal11, bal01);

        test_add_error (check3);
        g_assert_true (!xaccTransEqual (txn1, txn0, TRUE, TRUE, TRUE, TRUE));
        g_assert_true (xaccTransEqual (txn1, txn0, TRUE, TRUE, FALSE, TRUE));
        g_assert_cmpint (check->hits, ==, 11);
        g_assert_cmpint (check2->hits, ==, 3);
        g_assert_cmpint (check3->hits, ==, 0);

        split10->balance = split00->balance;
        split11->balance = split01->balance;
        split10->noclosing_balance = split00->noclosing_balance;
        split11->noclosing_balance = split01->noclosing_balance;
        g_assert_true (xaccTransEqual (txn1, txn0, TRUE, TRUE, TRUE, TRUE));
    }
    g_free (check3->msg);
    g_free (check2->msg);
}
/* xaccTransUseTradingAccounts
xaccTransUseTradingAccounts
Returns true if the transaction should include trading account splits if
it involves more than one commodity.
gboolean xaccTransUseTradingAccounts(const Transaction *trans)// C: 10 in 7  Local: 2:0:0
Pass-through, no need to test.
*/
/* xaccTransLookup
Transaction *
xaccTransLookup (const GncGUID *guid, QofBook *book)// C: 22 in 7  Local: 1:0:0
*/
static void
test_xaccTransLookup (Fixture *fixture, gconstpointer pData)
{
    Transaction *txn = fixture->txn;
    QofInstance *inst = QOF_INSTANCE (txn);
    g_assert_true (xaccTransLookup (qof_instance_get_guid (inst),
                               qof_instance_get_book (inst)) == txn);
}
/* xaccTransGetImbalanceValue
gnc_numeric
xaccTransGetImbalanceValue (const Transaction * trans)// C: 11 in 5  Local: 1:1:0
*/
static void
test_xaccTransGetImbalanceValue (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
    auto split1 = xaccMallocSplit (book);
    g_assert_true (gnc_numeric_equal (xaccTransGetImbalanceValue (fixture->txn),
                                 gnc_numeric_zero ()));
    split1->acc = fixture->acc1;
    split1->memo = CACHE_INSERT ("foo");
    split1->action = CACHE_INSERT ("bar");
    split1->amount = gnc_numeric_create (100000, 1000);
    split1->value = gnc_numeric_create (3200, 240);
    xaccTransBeginEdit (fixture->txn);
    xaccSplitSetParent (split1, fixture->txn);

    g_assert_true (gnc_numeric_equal (xaccTransGetImbalanceValue (fixture->txn),
                                 split1->value));
    xaccTransCommitEdit (fixture->txn);
}
/* xaccTransGetImbalance
MonetaryList *
xaccTransGetImbalance (const Transaction * trans)// C: 15 in 6  Local: 1:0:0
*/
static void
test_xaccTransGetImbalance (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
    auto split1 = xaccMallocSplit (book);
    MonetaryList *mlist;
    g_assert_true (xaccTransGetImbalance (NULL) == NULL);
    mlist = xaccTransGetImbalance (fixture->txn);
    g_assert_cmpint (g_list_length (mlist), ==, 0);

    split1->acc = fixture->acc1;
    split1->memo = CACHE_INSERT ("foo");
    split1->action = CACHE_INSERT ("bar");
    split1->amount = gnc_numeric_create (100000, 1000);
    split1->value = gnc_numeric_create (3200, 240);
    xaccTransBeginEdit (fixture->txn);
    xaccSplitSetParent (split1, fixture->txn);
    mlist = xaccTransGetImbalance (fixture->txn);
    g_assert_cmpint (g_list_length (mlist), ==, 1);
    xaccTransCommitEdit (fixture->txn);
    gnc_monetary_list_free (mlist);
}

static void
test_xaccTransGetImbalance_trading (Fixture *fixture,
                                    gconstpointer pData)
{
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
    auto split1 = xaccMallocSplit (book);
    auto split2 = xaccMallocSplit (book);
    Account *acc1 = xaccMallocAccount (book);
    Account *acc2 = xaccMallocAccount (book);
    MonetaryList *mlist;
    qof_book_begin_edit (book);
    qof_instance_set (QOF_INSTANCE (book),
		      "trading-accts", "t",
		      NULL);
    qof_book_commit_edit (book);

 /* Without trading splits, the list is unbalanced */
    mlist = xaccTransGetImbalance (fixture->txn);
    g_assert_cmpint (g_list_length (mlist), ==, 2);
    gnc_monetary_list_free (mlist);

    xaccAccountSetCommodity (acc1, fixture->comm);
    xaccAccountSetCommodity (acc2, fixture->curr);
    xaccAccountSetType (acc1, ACCT_TYPE_TRADING);
    xaccAccountSetType (acc2, ACCT_TYPE_TRADING);
    /* The setup transaction is unbalanced in a trading-accounts environment. */
    g_assert_true (!xaccTransIsBalanced (fixture->txn));
    /* Make it look like a proper trading accounts transactionm */
    split1->acc = acc1;
    split1->memo = CACHE_INSERT ("foo");
    split1->action = CACHE_INSERT ("bar");
    split1->amount = gnc_numeric_create (-10000, 100);
    split1->value = gnc_numeric_create (-3200, 240);
    split2->acc = acc2;
    split2->memo = CACHE_INSERT ("foo");
    split2->action = CACHE_INSERT ("bar");
    split2->amount = gnc_numeric_create (3000, 240);
    split2->value = gnc_numeric_create (3200, 240);
    xaccTransBeginEdit (fixture->txn);
    xaccSplitSetParent (split1, fixture->txn);
    mlist = xaccTransGetImbalance (fixture->txn);
    g_assert_cmpint (g_list_length (mlist), ==, 1);
    gnc_monetary_list_free (mlist);
    xaccSplitSetParent (split2, fixture->txn);
    mlist = xaccTransGetImbalance (fixture->txn);
    g_assert_cmpint (g_list_length (mlist), ==, 1);
    gnc_monetary_list_free (mlist);
    split2->amount = gnc_numeric_create (3000, 240);
    split2->value = gnc_numeric_create (3000, 240);
    mlist = xaccTransGetImbalance (fixture->txn);
    g_assert_cmpint (g_list_length (mlist), ==, 1);
    gnc_monetary_list_free (mlist);
    split2->amount = gnc_numeric_create (3200, 240);
    split2->value = gnc_numeric_create (3200, 240);
    mlist = xaccTransGetImbalance (fixture->txn);
    g_assert_cmpint (g_list_length (mlist), ==, 0);
    gnc_monetary_list_free (mlist);

    xaccTransCommitEdit (fixture->txn);
    test_destroy (acc1);
    test_destroy (acc2);
}


/* xaccTransIsBalanced
gboolean
xaccTransIsBalanced (const Transaction *trans)// C: 4 in 4  Local: 1:0:0
*/
static void
test_xaccTransIsBalanced (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
    auto split1 = xaccMallocSplit (book);
    g_assert_true (!xaccTransIsBalanced (NULL));
    g_assert_true (xaccTransIsBalanced (fixture->txn));

    split1->acc = fixture->acc1;
    split1->memo = CACHE_INSERT ("foo");
    split1->action = CACHE_INSERT ("bar");
    split1->amount = gnc_numeric_create (100000, 1000);
    split1->value = gnc_numeric_create (3200, 240);
    xaccTransBeginEdit (fixture->txn);
    xaccSplitSetParent (split1, fixture->txn);
    g_assert_true (! xaccTransIsBalanced (fixture->txn));
    xaccTransCommitEdit (fixture->txn);
}


static void
test_xaccTransIsBalanced_trading (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
    auto split1 = xaccMallocSplit (book);
    auto split2 = xaccMallocSplit (book);
    Account *acc1 = xaccMallocAccount (book);
    Account *acc2 = xaccMallocAccount (book);

    qof_book_begin_edit (book);
    qof_instance_set (QOF_INSTANCE (book),
		      "trading-accts", "t",
		      NULL);
    qof_book_commit_edit (book);

    xaccAccountSetCommodity (acc1, fixture->curr);
    xaccAccountSetCommodity (acc2, fixture->comm);
    xaccAccountSetType (acc1, ACCT_TYPE_TRADING);
    xaccAccountSetType (acc2, ACCT_TYPE_TRADING);
    /* The setup transaction is unbalanced in a trading-accounts environment. */
    g_assert_true (!xaccTransIsBalanced (fixture->txn));
    split1->acc = acc1;
    split1->memo = CACHE_INSERT ("foo");
    split1->action = CACHE_INSERT ("bar");
    split1->amount = gnc_numeric_create (3200, 240);
    split1->value = gnc_numeric_create (3200, 240);
    split2->acc = acc2;
    split2->memo = CACHE_INSERT ("foo");
    split2->action = CACHE_INSERT ("bar");
    split2->amount = gnc_numeric_create (-10000, 100);
    split2->value = gnc_numeric_create (-3000, 240);
    xaccTransBeginEdit (fixture->txn);
    xaccSplitSetParent (split1, fixture->txn);
    g_assert_true (!xaccTransIsBalanced (fixture->txn));
    xaccSplitSetParent (split2, fixture->txn);
    g_assert_true (!xaccTransIsBalanced (fixture->txn));
    split2->amount = gnc_numeric_create (-11000, 100);
    split2->value = gnc_numeric_create (-3200, 240);
    g_assert_true (!xaccTransIsBalanced (fixture->txn));
    split2->amount = gnc_numeric_create (-10000, 100);
    split2->value = gnc_numeric_create (-3200, 240);
    g_assert_true (xaccTransIsBalanced (fixture->txn));
    xaccTransRollbackEdit (fixture->txn);

    test_destroy (acc2);
    test_destroy (acc1);
}
/* xaccTransGetAccountValue
gnc_numeric
xaccTransGetAccountValue (const Transaction *trans,// SCM: 6 in 6 Local: 0:0:0
*/
static void
test_xaccTransGetAccountValue (Fixture *fixture, gconstpointer pData)
{
    gnc_numeric val1 = {3200, 240}, val2 = {-3200, 240};

    g_assert_true (gnc_numeric_zero_p (xaccTransGetAccountValue (fixture->txn, NULL)));
    g_assert_true (gnc_numeric_zero_p (xaccTransGetAccountValue (NULL, fixture->acc1)));
    g_assert_true (gnc_numeric_eq (xaccTransGetAccountValue (fixture->txn, fixture->acc1), val1));
    g_assert_true (gnc_numeric_eq (xaccTransGetAccountValue (fixture->txn, fixture->acc2), val2));

}
/* xaccTransGetAccountAmount
gnc_numeric
xaccTransGetAccountAmount (const Transaction *trans, const Account *acc)// C: 2 in 1  Local: 0:0:0
*/
static void
test_xaccTransGetAccountAmount (Fixture *fixture, gconstpointer pData)
{
    gnc_numeric amt1 = {100000, 1000}, amt2 = {-3200, 240};

    g_assert_true (gnc_numeric_zero_p (xaccTransGetAccountAmount (fixture->txn, NULL)));
    g_assert_true (gnc_numeric_zero_p (xaccTransGetAccountAmount (NULL, fixture->acc1)));
    g_assert_true (gnc_numeric_eq (xaccTransGetAccountAmount (fixture->txn, fixture->acc1), amt1));
    g_assert_true (gnc_numeric_eq (xaccTransGetAccountAmount (fixture->txn, fixture->acc2), amt2));

}

/* xaccTransGetAccountConvRate
gnc_numeric
xaccTransGetAccountConvRate(const Transaction *txn, const Account *acc)// C: 5 in 4  Local: 0:0:0
*/
static void
test_xaccTransGetAccountConvRate (Fixture *fixture, gconstpointer pData)
{
    auto msg1 = "[xaccTransGetAccountConvRate()] How can amount be nonzero and value be zero?";
    auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL);
    auto check = test_error_struct_new ("gnc.engine", loglevel, msg1);
    auto split1 = xaccTransFindSplitByAccount(fixture->txn, fixture->acc1);
    gnc_numeric rate;

    fixture->hdlrs = test_log_set_fatal_handler (fixture->hdlrs, check,
                     (GLogFunc)test_checked_handler);

    g_assert_true (gnc_numeric_equal (xaccTransGetAccountConvRate (fixture->txn,
                                 fixture->acc2),
                                 gnc_numeric_create (1, 1)));

    g_assert_true (gnc_numeric_equal (xaccTransGetAccountConvRate (fixture->txn,
                                 fixture->acc1),
                                 gnc_numeric_create (1800, 240)));
    g_assert_cmpint (check->hits, ==, 0);
    split1->value = gnc_numeric_zero();
    rate = xaccTransGetAccountConvRate (fixture->txn, fixture->acc1);
    g_assert_cmpint (gnc_numeric_check (rate), ==, GNC_ERROR_OVERFLOW);
    g_assert_cmpint (check->hits, ==, 1);
}
/* xaccTransGetAccountBalance
gnc_numeric
xaccTransGetAccountBalance (const Transaction *trans,// C: 1  Local: 0:0:0
*/
static void
test_xaccTransGetAccountBalance (Fixture *fixture, gconstpointer pData)
{
#ifdef USE_CLANG_FUNC_SIG
#define _func "gnc_numeric xaccTransGetAccountBalance(const Transaction *, const Account *)"
#else
#define _func "gnc_numeric xaccTransGetAccountBalance(const Transaction*, const Account*)"
#endif
    auto msg1 = _func ": assertion 'account && trans' failed";
#undef _func
    auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL);
    auto check = test_error_struct_new ("gnc.engine", loglevel, msg1);
    gnc_numeric rate;

    fixture->hdlrs = test_log_set_fatal_handler (fixture->hdlrs, check,
                     (GLogFunc)test_checked_handler);

    rate = xaccTransGetAccountBalance (NULL, fixture->acc1);
    g_assert_cmpint (gnc_numeric_check (rate), ==, GNC_ERROR_ARG);
    g_assert_cmpint (check->hits, ==, 1);

    rate = xaccTransGetAccountBalance (fixture->txn, NULL);
    g_assert_cmpint (gnc_numeric_check (rate), ==, GNC_ERROR_ARG);
    g_assert_cmpint (check->hits, ==, 2);

    rate = xaccTransGetAccountBalance (fixture->txn, fixture->acc1);
    g_assert_true (gnc_numeric_equal (rate, gnc_numeric_create (100000, 1000)));
    g_assert_cmpint (check->hits, ==, 2);

    rate = xaccTransGetAccountBalance (fixture->txn, fixture->acc2);
    g_assert_true (gnc_numeric_equal (rate, gnc_numeric_create (-3200, 240)));
    g_assert_cmpint (check->hits, ==, 2);

}
/* xaccTransGetCurrency
gnc_commodity *
xaccTransGetCurrency (const Transaction *trans)// C: 33 in 17 SCM: 34 in 26 Local: 2:0:0
Simple Getter. No need to test.
*/
/* xaccTransSetCurrency
void
xaccTransSetCurrency (Transaction *trans, gnc_commodity *curr)// C: 22 in 18 SCM: 3 in 3 Local: 1:0:0
*/
static void
test_xaccTransSetCurrency (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
    gnc_commodity *curr = gnc_commodity_new (book, "Japanese Yen", "CURRENCY", "JPY", "¥", 1);
    auto split1 = xaccTransFindSplitByAccount (fixture->txn, fixture->acc1);
    gnc_numeric old_val = xaccSplitGetValue (split1);
    /* Prevent commit in xaccTransSetCurrency() */
    xaccTransBeginEdit(fixture->txn);
    xaccTransSetCurrency (fixture->txn, curr);
    g_assert_true (fixture->txn->common_currency == curr);
    g_assert_cmpint (xaccSplitGetValue (split1).denom, ==,
                     gnc_commodity_get_fraction (curr));
    g_assert_cmpint (xaccSplitGetValue (split1).num, ==,
                     old_val.num / old_val.denom);
}
/* xaccTransBeginEdit
void
xaccTransBeginEdit (Transaction *trans)// C: 72 in 28 SCM: 5 in 5 Local: 16:0:0
*/
static void
test_xaccTransBeginEdit ()
{
    QofBook *book = qof_book_new ();
    Transaction *txn = xaccMallocTransaction (book);
    Transaction *dupe = NULL;
    auto msg1 = "[xaccOpenLog] Attempt to open disabled transaction log";
    auto msg2 = "[xaccTransWriteLog] Attempt to write disabled transaction log";
    auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_INFO);
    auto logdomain = "gnc.translog";
    auto check1 = test_error_struct_new (logdomain, loglevel, msg1);
    auto check2 = test_error_struct_new (logdomain, loglevel, msg2);
    guint hdlr = g_log_set_handler (logdomain, loglevel,
                                    (GLogFunc)test_list_handler, NULL);
    qof_log_set_level (logdomain, QOF_LOG_INFO);
    test_add_error (check1);
    test_add_error (check2);


    g_assert_cmpint (0, ==, qof_instance_get_editlevel (QOF_INSTANCE (txn)));
    g_assert_true (txn->orig == NULL);
    xaccTransBeginEdit (txn);
    g_assert_cmpint (1, ==, qof_instance_get_editlevel (QOF_INSTANCE (txn)));
    dupe = txn->orig;
    g_assert_true (txn->orig != NULL);
    g_assert_cmpint (1, ==, check1->hits);
    g_assert_cmpint (1, ==, check2->hits);
    xaccTransBeginEdit (txn);
    g_assert_cmpint (2, ==, qof_instance_get_editlevel (QOF_INSTANCE (txn)));
    g_assert_true (txn->orig == dupe);
    g_assert_cmpint (1, ==, check1->hits);
    g_assert_cmpint (1, ==, check2->hits);
    xaccTransRollbackEdit (txn);
    xaccTransRollbackEdit (txn);
    g_assert_cmpint (0, ==, qof_instance_get_editlevel (QOF_INSTANCE (txn)));
    g_assert_true (txn->orig == NULL);
    qof_book_mark_readonly (book);
    xaccTransBeginEdit (txn);
    dupe = txn->orig;
    g_assert_cmpint (1, ==, qof_instance_get_editlevel (QOF_INSTANCE (txn)));
    g_assert_true (txn->orig == dupe);
    g_assert_cmpint (1, ==, check1->hits);
    g_assert_cmpint (2, ==, check2->hits);
    xaccTransRollbackEdit (txn);
    g_assert_cmpint (0, ==, qof_instance_get_editlevel (QOF_INSTANCE (txn)));
    g_assert_true (txn->orig == NULL);

    g_log_remove_handler (logdomain, hdlr);
    test_clear_error_list ();
    test_error_struct_free (check1);
    test_error_struct_free (check2);
    xaccTransDestroy (txn);
    qof_book_destroy (book);
}
/* xaccTransDestroy
void
xaccTransDestroy (Transaction *trans)// C: 26 in 15 SCM: 4 in 4 Local: 3:0:0
*/
static void
test_xaccTransDestroy (Fixture *fixture, gconstpointer pData)
{
    Transaction *txn = fixture->txn;
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (txn));
    Transaction *dupe = xaccTransClone (txn);

    xaccTransBeginEdit (txn);
    g_assert_true (!qof_instance_get_destroying (QOF_INSTANCE (txn)));
    g_assert_true (xaccTransEqual (txn, dupe, FALSE, TRUE, FALSE, TRUE));
    xaccTransDestroy (txn);
    g_assert_true (qof_instance_get_destroying (QOF_INSTANCE (txn)));
    g_assert_true (xaccTransEqual (txn, dupe, FALSE, TRUE, FALSE, TRUE));
    xaccTransRollbackEdit (txn);
    qof_book_mark_readonly (book);
    xaccTransBeginEdit (txn);
    xaccTransDestroy (txn);
    g_assert_true (qof_instance_get_destroying (QOF_INSTANCE (txn)));
    g_assert_true (xaccTransEqual (txn, dupe, FALSE, TRUE, FALSE, TRUE));
    xaccTransRollbackEdit (txn);

    test_destroy (dupe);
}
/* destroy_gains
static void
destroy_gains (Transaction *trans)// Local: 1:0:0 -- from do_destroy
*/
static void
test_destroy_gains (GainsFixture *fixture, gconstpointer pData)
{
    /* Don't try to test with a NULL transaction, this is an internal
     * function that isn't protected.
     */
    Fixture *base = &(fixture->base);
    auto base_split = static_cast<Split*>(g_list_nth_data (base->txn->splits, 1));
    xaccTransBeginEdit (fixture->gains_txn); /* Protect it from being actually destroyed */
    base->func->destroy_gains (base->txn);
    g_assert_true (qof_instance_get_destroying (QOF_INSTANCE (fixture->gains_txn)));
    g_assert_true (base_split->gains_split == NULL);
    xaccTransCommitEdit (fixture->gains_txn);
}
/* do_destroy
static void
do_destroy (Transaction *trans)// Local: 1:1:0 callback passed to qof_commit_edit_part2 in XaccTransCommitEdit

NB: This function has a weird three-step process for destroying and freeing the splits, which isn't really testable.
*/
static void
test_do_destroy (GainsFixture *fixture, gconstpointer pData)
{
    Fixture *base = &(fixture->base);
    auto base_split =  static_cast<Split*>(g_list_nth_data (base->txn->splits, 1));
    TestSignal sig = test_signal_new (QOF_INSTANCE (base->txn),
                                      QOF_EVENT_DESTROY, NULL);
    g_object_add_weak_pointer (G_OBJECT (base->txn->splits->data),
                               reinterpret_cast<void**>(&base_split));
    g_object_ref (base->txn);
    g_object_ref (fixture->gains_txn);

   /* Protect against recursive calls to do_destroy from xaccTransCommitEdit */
    xaccTransBeginEdit(base->txn);

    base->func->do_destroy (QOF_INSTANCE(base->txn));
    g_assert_cmpint (test_signal_return_hits (sig), ==, 1);
    g_assert_true (base->txn->description == NULL);
    g_assert_cmpint (GPOINTER_TO_INT(base->txn->num), ==, 1);
    g_assert_true (qof_instance_get_destroying (QOF_INSTANCE (fixture->gains_txn)));
    g_assert_true (base_split == NULL);

    test_signal_free (sig);
}
/* xaccEnableDataScrubbing
 * xaccDisableDataScrubbing
 Trivial setters
*/
/* was_trans_emptied
static gboolean was_trans_emptied(Transaction *trans)// Local: 1:0:0 xaccTransCommitEdit
*/
static void
test_was_trans_emptied (Fixture *fixture, gconstpointer pData)
{
    GList *list = fixture->txn->splits;
    g_assert_true (!fixture->func->was_trans_emptied (fixture->txn));
    fixture->txn->splits = NULL;
    g_assert_true (fixture->func->was_trans_emptied (fixture->txn));
    /* Restore the list so teardown can free the splits */
    fixture->txn->splits = list;
}
/* trans_on_error
static void trans_on_error(Transaction *trans, QofBackendError errcode)// Local: 0:1:0 callback for qof_commit_edit_part2, xaccTransCommitEdit
*/

static QofBackendError errorvalue = ERR_BACKEND_NO_ERR;
static void
commit_error_cb (gpointer data, QofBackendError errcode)
{
    errorvalue = errcode;
}

static void
test_trans_on_error (Fixture *fixture, gconstpointer pData)
{
    QofBackendError errcode = ERR_BACKEND_MODIFIED;
    auto msg =
        "[trans_on_error()] Another user has modified this transaction\n"
        "\tjust a moment ago. Please look at their changes,\n"
        "\tand try again, if needed.\n";
    auto logdomain = "gnc.engine";
    auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL);
    auto check = test_error_struct_new (logdomain, loglevel, msg);
    fixture->hdlrs = test_log_set_fatal_handler (fixture->hdlrs, check,
                     (GLogFunc)test_checked_handler);
    gnc_engine_add_commit_error_callback ((EngineCommitErrorCallback)commit_error_cb, NULL);
    xaccTransBeginEdit (fixture->txn);
    g_assert_cmpint (qof_instance_get_editlevel (fixture->txn), ==, 1);
    fixture->func->trans_on_error (QOF_INSTANCE(fixture->txn), errcode);
    g_assert_cmpint (check->hits, ==, 1);
    g_assert_cmpint ((guint)errorvalue, ==, (guint)errcode);
    g_assert_cmpint (qof_instance_get_editlevel (fixture->txn), ==, 0);
    errorvalue = ERR_BACKEND_NO_ERR;
}
/* trans_cleanup_commit
static void trans_cleanup_commit(Transaction *trans)// Local: 0:1:0 callback for qof_commit_edit_part2, xaccTransCommitEdit
*/
static void
test_trans_cleanup_commit (Fixture *fixture, gconstpointer pData)
{
    QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
    auto destr_split = xaccMallocSplit (book);
    auto bogus_split = xaccMallocSplit (book);
    auto split0 = static_cast<Split*>(fixture->txn->splits->data);
    Account *acct0 = split0->acc;
    Transaction *orig = NULL;
    auto sig_d_remove = test_signal_new (QOF_INSTANCE (destr_split),
                               QOF_EVENT_REMOVE, NULL);
    auto sig_b_remove = test_signal_new (QOF_INSTANCE (bogus_split),
                               QOF_EVENT_REMOVE, NULL);
    auto sig_d_destroy = test_signal_new (QOF_INSTANCE (destr_split),
                                QOF_EVENT_DESTROY, NULL);
    auto sig_b_modify = test_signal_new (QOF_INSTANCE (bogus_split),
                               QOF_EVENT_MODIFY, NULL);
    auto sig_t_modify = test_signal_new (QOF_INSTANCE (fixture->txn),
                               QOF_EVENT_MODIFY, NULL);
    auto sig_a_changed = test_signal_new (QOF_INSTANCE (acct0),
                                GNC_EVENT_ITEM_CHANGED, NULL);

    xaccTransBeginEdit (fixture->txn);
    orig = fixture->txn->orig;
    g_object_ref (orig);
    /* Check the txn-isn't-the-parent path */
    fixture->txn->splits = g_list_prepend (fixture->txn->splits, destr_split);
    fixture->txn->splits = g_list_prepend (fixture->txn->splits, bogus_split);
    qof_instance_set_dirty (QOF_INSTANCE (destr_split));
    qof_instance_set_dirty (QOF_INSTANCE (bogus_split));
    qof_instance_set_destroying (QOF_INSTANCE (destr_split), TRUE);
    /*Reverse the splits list so we can check later that it got sorted */
    fixture->txn->splits = g_list_reverse (fixture->txn->splits);
    g_assert_true (fixture->txn->splits->data != split0);
    fixture->func->trans_cleanup_commit (QOF_INSTANCE(fixture->txn));

    g_assert_cmpint (test_signal_return_hits (sig_d_remove), ==, 1);
    g_assert_cmpint (test_signal_return_hits (sig_b_remove), ==, 1);
    g_assert_cmpint (test_signal_return_hits (sig_d_destroy), ==, 0);
    g_assert_cmpint (test_signal_return_hits (sig_b_modify), ==, 0);
    g_assert_cmpint (test_signal_return_hits (sig_t_modify), ==, 1);
    g_assert_cmpint (test_signal_return_hits (sig_a_changed), ==, 1);
    g_assert_cmpint (g_list_index (fixture->txn->splits, destr_split), ==, -1);
    g_assert_cmpint (g_list_index (fixture->txn->splits, bogus_split), ==, -1);
    g_assert_true (fixture->txn->orig == NULL);
    g_assert_true (fixture->txn->splits->data == split0);
    g_assert_true (qof_instance_get_destroying (destr_split));
    /* Note that the function itself aborts if qof_instance_editlevel != 0 */

    /* load things back up and test the txn-is-the-parent path */
    qof_instance_increase_editlevel (fixture->txn);
    destr_split->parent = fixture->txn;
    bogus_split->parent = fixture->txn;
    fixture->txn->splits = g_list_prepend (fixture->txn->splits, destr_split);
    fixture->txn->splits = g_list_prepend (fixture->txn->splits, bogus_split);

    fixture->txn->orig = orig;
    orig->num = fixture->txn->num;
    g_object_ref (orig);
    fixture->func->trans_cleanup_commit (QOF_INSTANCE(fixture->txn));

    g_assert_cmpint (test_signal_return_hits (sig_d_remove), ==, 2);
    g_assert_cmpint (test_signal_return_hits (sig_b_remove), ==, 1);
    g_assert_cmpint (test_signal_return_hits (sig_d_destroy), ==, 1);
    g_assert_cmpint (test_signal_return_hits (sig_b_modify), ==, 1);
    g_assert_cmpint (test_signal_return_hits (sig_t_modify), ==, 2);
    g_assert_cmpint (test_signal_return_hits (sig_a_changed), ==, 2);
    g_assert_cmpint (g_list_index (fixture->txn->splits, destr_split), ==, -1);
    g_assert_cmpint (g_list_index (fixture->txn->splits, bogus_split), ==, 0);
    g_assert_cmpint (GPOINTER_TO_INT(orig->num), ==, 1);
    test_destroy (orig);

}
/* xaccTransCommitEdit
void
xaccTransCommitEdit (Transaction *trans)// C: 88 in 28 SCM: 5 in 5 Local: 16:0:0
Setup has to run transCommitEdit, so we have to do our own setup for this function.
*/
static void
test_xaccTransCommitEdit (void)
{
    QofBook *book = qof_book_new ();
    auto split1 = xaccMallocSplit (book);
    auto split2 = xaccMallocSplit (book);
    Transaction *txn = xaccMallocTransaction (book);
    Account *acc1 = xaccMallocAccount (book);
    Account *acc2 = xaccMallocAccount (book);
    gnc_commodity *curr = gnc_commodity_new (book, "Gnu Rand",
                          "CURRENCY", "GNR", "", 240);
    gnc_commodity *comm = gnc_commodity_new (book, "Wildebeest Fund",
                          "FUND", "WBFXX", "", 1000);

    time64 posted = gnc_dmy2time64 (21, 4, 2012);

    auto sig_1_modify = test_signal_new (QOF_INSTANCE (split1),
                               QOF_EVENT_MODIFY, NULL);
    auto sig_2_modify = test_signal_new (QOF_INSTANCE (split2),
                               QOF_EVENT_MODIFY, NULL);
    auto sig_txn_destroy = test_signal_new (QOF_INSTANCE (txn),
                                  QOF_EVENT_DESTROY, NULL);


    xaccAccountSetCommodity (acc1, comm);
    xaccAccountSetCommodity (acc2, curr);
    txn->date_posted = posted;
    split1->memo = CACHE_INSERT ("foo");
    split1->action = CACHE_INSERT ("bar");
    split1->amount = gnc_numeric_create (100000, 1000);
    split1->value = gnc_numeric_create (3200, 240);
    /* Note, deliberately imblanced to force xaccTransScrubImbalance
     * to create a balance split, thus showing that it got called.
     */
    split2->amount = gnc_numeric_create (-3000, 240);
    split2->value = gnc_numeric_create (-3000, 240);
    split1->acc = acc1;
    split2->acc = acc2;
    txn->num = CACHE_INSERT ("123");
    txn->description = CACHE_INSERT ("Waldo Pepper");
    xaccTransBeginEdit (txn);
    {
        xaccTransSetCurrency (txn, curr);
        xaccSplitSetParent (split1, txn);
        xaccSplitSetParent (split2, txn);
    }
    /* Setup's done, now test: */
    xaccTransCommitEdit (txn);

    g_assert_cmpint (txn->date_entered, !=, 0);
    /* Signals make sure that trans_cleanup_commit got called */
    g_assert_cmpint (test_signal_return_hits (sig_1_modify), ==, 1);
    g_assert_cmpint (test_signal_return_hits (sig_2_modify), ==, 1);
    g_assert_cmpint (g_list_length (txn->splits), ==, 3);

    xaccTransBeginEdit (txn);
    g_list_free (txn->splits);
    txn->splits = NULL;
    xaccTransCommitEdit (txn);
    g_assert_cmpint (test_signal_return_hits (sig_txn_destroy), ==, 1);

    test_signal_free (sig_1_modify);
    test_signal_free (sig_2_modify);
    test_signal_free (sig_txn_destroy);
    test_destroy (split1);
    test_destroy (split2);
    test_destroy (acc1);
    test_destroy (acc2);
    test_destroy (curr);
    test_destroy (comm);
    qof_book_destroy (book);
}
/* xaccTransRollbackEdit
void
xaccTransRollbackEdit (Transaction *trans)// C: 2 in 2  Local: 1:0:0
*/
static void
test_xaccTransRollbackEdit (Fixture *fixture, gconstpointer pData)
{
    Transaction *txn = fixture->txn;
    Transaction *orig = NULL;
    QofBook *book = qof_instance_get_book (txn);
    time64 new_post = gnc_time (nullptr);
    time64 new_entered = time64CanonicalDayTime (new_post);
    time64 orig_post = txn->date_posted;
    time64 orig_entered = txn->date_entered;
    KvpFrame *base_frame = NULL;
    auto sig_account = test_signal_new (QOF_INSTANCE (fixture->acc1),
            GNC_EVENT_ITEM_CHANGED, NULL);
    auto mbe = static_cast<TransMockBackend*>(qof_book_get_backend (book));
    auto split_00 = static_cast<Split*>(txn->splits->data);
    auto split_01 = static_cast<Split*>(txn->splits->next->data);
    auto split_02 = xaccMallocSplit (book);

    xaccTransBeginEdit (txn);
    qof_instance_set_destroying (txn, TRUE);
    orig = txn->orig;
    base_frame = orig->inst.kvp_data; /* DupeTransaction copies the kvp_frame */
    g_object_ref (orig); /* Keep rollback from actually freeing it */
    txn->num = CACHE_INSERT("321");
    txn->description = CACHE_INSERT("salt peanuts");
    txn->common_currency = NULL;
    txn->inst.kvp_data = NULL;
    txn->date_entered = new_entered;
    txn->date_posted = new_post;
    txn->splits->data = split_01;
    txn->splits->next->data = split_00;
    qof_instance_set_dirty (QOF_INSTANCE (split_01));
    xaccSplitSetParent (split_02, txn);
    g_object_ref (split_02);
    auto split_10 = xaccDupeSplit(static_cast<Split*>(orig->splits->data));
    g_object_ref (split_10);
    auto split_11 = xaccDupeSplit(static_cast<Split*>(orig->splits->next->data));
    g_object_ref (split_11);
    qof_instance_increase_editlevel (QOF_INSTANCE (txn)); /* So it's 2 */
    xaccTransRollbackEdit (txn);
    g_assert_true (txn->orig == orig);
    qof_instance_reset_editlevel (QOF_INSTANCE (txn)); /* Now it's 0 */
    xaccTransRollbackEdit (txn);
    g_assert_true (txn->orig == orig);
    qof_instance_increase_editlevel (QOF_INSTANCE (txn)); /* And back to 1 */
    xaccTransRollbackEdit (txn);
    g_assert_true (txn->orig == NULL);
    g_assert_cmpstr (txn->num, ==, "123");
    g_assert_cmpint (GPOINTER_TO_INT(orig->num), ==, 1);
    g_assert_cmpstr (txn->description, ==, "Waldo Pepper");
    g_assert_true (txn->inst.kvp_data == base_frame);
    g_assert_true (txn->common_currency == fixture->curr);
    g_assert_true (txn->date_posted == orig_post);
    g_assert_true (txn->date_entered == orig_entered);
    g_assert_cmpuint (test_signal_return_hits (sig_account), ==, 1);
    g_assert_cmpuint (g_list_length (txn->splits), ==, 2);
    g_assert_cmpint (GPOINTER_TO_INT(split_02->memo), ==, 1);
    g_assert_true (xaccSplitEqual (static_cast<Split*>(txn->splits->data), split_10,
                              FALSE, FALSE, FALSE));
    g_assert_true (xaccSplitEqual (static_cast<Split*>(txn->splits->next->data),
                              split_10, FALSE, FALSE, FALSE));
    g_assert_cmpstr (mbe->m_last_call.c_str(), ==, "rollback");
    g_assert_cmpuint (qof_instance_get_editlevel (QOF_INSTANCE (txn)), ==, 0);
    g_assert_true (qof_instance_get_destroying (txn) == FALSE);
    test_signal_free (sig_account);
    g_object_unref (split_10);
    g_object_unref (split_11);
    g_object_unref (split_02);
    g_object_unref (orig);

}
/* A second xaccTransRollbackEdit test to check the backend error handling */
static void
test_xaccTransRollbackEdit_BackendErrors (Fixture *fixture, gconstpointer pData)
{
    auto mbe = static_cast<TransMockBackend*>(qof_book_get_backend (qof_instance_get_book (fixture->txn)));
    auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL);
    auto msg = "[xaccTransRollbackEdit()] Rollback Failed.  Ouch!";
    auto check = test_error_struct_new ("gnc.engine", loglevel, msg);
    fixture->hdlrs = test_log_set_fatal_handler (fixture->hdlrs, check,
                     (GLogFunc)test_checked_handler);
    g_object_ref (fixture->txn);
    xaccTransBeginEdit (fixture->txn);
    mbe->inject_error(ERR_BACKEND_MODIFIED);
    xaccTransRollbackEdit (fixture->txn);
    g_assert_cmpint (check->hits, ==, 1);
    g_assert_cmpstr (mbe->m_last_call.c_str(), ==, "rollback");
    mbe->m_last_call.clear();
    xaccTransBeginEdit (fixture->txn);
    mbe->inject_error (ERR_BACKEND_MOD_DESTROY);
    xaccTransRollbackEdit (fixture->txn);
    g_assert_cmpint (GPOINTER_TO_INT(fixture->txn->num), ==, 1);
    g_assert_cmpstr (mbe->m_last_call.c_str(), ==, "rollback");

}
/* xaccTransIsOpen C: 23 in 7 SCM: 1  Local: 0:0:0
 * xaccTransOrder C: 2 in 2 SCM: 12 in 12 Local: 0:1:0

 * Simple convenience functions. No test required.
 */
/* xaccTransOrder_num_action
int
xaccTransOrder_num_action (const Transaction *ta, const char *actna,
                           const Transaction *tb, const char *actnb)// C: 1 Local: 1:0:0
*/
static void
test_xaccTransOrder_num_action (Fixture *fixture, gconstpointer pData)
{
    Transaction *txnA = fixture->txn;
    Transaction *txnB = fixture->func->dupe_trans (txnA);

    g_assert_cmpint (xaccTransOrder_num_action (txnA, NULL, NULL, NULL), ==, -1);
    g_assert_cmpint (xaccTransOrder_num_action (NULL, NULL, txnA, NULL), ==, 1);
    g_assert_cmpint (xaccTransOrder_num_action (NULL, NULL, NULL, NULL), ==, 0);
    g_assert_cmpint (xaccTransOrder_num_action (txnA, NULL, txnB, NULL), ==,
                     qof_instance_guid_compare (txnA, txnB));
    txnB->description = CACHE_INSERT ("Salt Peanuts");
    g_assert_cmpint (xaccTransOrder_num_action (txnA, NULL, txnB, NULL), >=, 1);
    txnB->date_entered += 1;
    g_assert_cmpint (xaccTransOrder_num_action (txnA, NULL, txnB, NULL), ==, -1);
    txnB->num = CACHE_INSERT ("101");
    g_assert_cmpint (xaccTransOrder_num_action (txnA, NULL, txnB, NULL), ==, 1);
    txnA->num = CACHE_INSERT ("12a");
    g_assert_cmpint (xaccTransOrder_num_action (txnA, NULL, txnB, NULL), ==, -1);
    txnB->num = CACHE_INSERT ("12c");
    g_assert_cmpint (xaccTransOrder_num_action (txnA, NULL, txnB, NULL), ==, -1);
    txnB->num = CACHE_INSERT ("12");
    g_assert_cmpint (xaccTransOrder_num_action (txnA, NULL, txnB, NULL), ==, 1);
    txnB->num = CACHE_INSERT ("one-oh-one");
    g_assert_cmpint (xaccTransOrder_num_action (txnA, NULL, txnB, NULL), ==, -1);
    g_assert_cmpint (xaccTransOrder_num_action (txnA, "24", txnB, "42"), ==, -1);
    txnB->date_posted -= 1;
    g_assert_cmpint (xaccTransOrder_num_action (txnA, "24", txnB, "42"), ==, 1);

    fixture->func->xaccFreeTransaction (txnB);
}

static void
test_xaccTransGetReadOnly (Fixture *fixture, gconstpointer pData)
{
    auto txn = fixture->txn;
    auto frame = fixture->txn->inst.kvp_data;

    g_assert_cmpstr (xaccTransGetReadOnly (txn), ==, nullptr);
    g_assert_null (frame->get_slot({TRANS_READ_ONLY_REASON}));

    xaccTransSetReadOnly (txn, "RO");
    g_assert_cmpstr (xaccTransGetReadOnly (txn), ==, "RO");
    g_assert_cmpstr (frame->get_slot({TRANS_READ_ONLY_REASON})->get<const char*>(), ==, "RO");

    xaccTransSetReadOnly (txn, nullptr); // reason being nullptr is a NOP
    g_assert_cmpstr (xaccTransGetReadOnly (txn), ==, "RO");
    g_assert_cmpstr (frame->get_slot({TRANS_READ_ONLY_REASON})->get<const char*>(), ==, "RO");

    xaccTransClearReadOnly (txn);
    g_assert_cmpstr (xaccTransGetReadOnly (txn), ==, nullptr);
    g_assert_null (frame->get_slot({TRANS_READ_ONLY_REASON}));

    xaccTransSetReadOnly (txn, "");
    g_assert_cmpstr (xaccTransGetReadOnly (txn), ==, nullptr);
    g_assert_null (frame->get_slot({TRANS_READ_ONLY_REASON}));

    xaccTransClearReadOnly (txn);
    g_assert_null (frame->get_slot({TRANS_READ_ONLY_REASON}));
}


/* xaccTransSetDateInternal Local: 7:0:0
 * set_gains_date_dirty Local: 4:0:0
 * xaccTransSetDatePostedSecs C: 17 in 13  Local: 0:0:0
 * xaccTransSetDatePostedGDate C: 1  Local: 1:0:0
 * xaccTransSetDateEnteredSecs C: 10 in 9  Local: 0:0:0
 * xaccTransSetDate C: 43 in 23 SCM: 2 in 2 Local: 0:0:0
 * xaccTransSetTxnType C: 4 in 3  Local: 0:0:0
 * xaccTransClearReadOnly C: 4 in 2  Local: 1:0:0
 * xaccTransSetReadOnly C: 2 in 2  Local: 1:0:0
 * qofTransSetNum Local: 0:1:0
 * xaccTransSetNum C: 13 in 12 SCM: 3 in 3 Local: 2:0:0
 * qofTransSetDescription Local: 0:0:0
 * xaccTransSetDescription C: 20 in 18 SCM: 5 in 3 Local: 2:0:0
 * qofTransSetNotes Local: 0:0:0
 * xaccTransSetNotes C: 5 in 5 SCM: 3 in 3 Local: 1:0:0
 * xaccTransSetIsClosingTxn C: 1  Local: 0:0:0
 * xaccTransGetSplit C: 57 in 24 SCM: 30 in 21 Local: 0:0:0
 * xaccTransGetSplitIndex C: 7 in 2  Local: 0:0:0
 * xaccTransGetSplitList C: 23 in 15 SCM: 19 in 15 Local: 2:1:0
 * xaccTransCountSplits C: 17 in 9 SCM: 2 in 2 Local: 0:0:0
 * xaccTransGetNum C: 15 in 12 SCM: 13 in 13 Local: 0:1:0
 * xaccTransGetDescription C: 43 in 23 SCM: 9 in 9 Local: 0:2:0
 * xaccTransGetNotes C: 8 in 6 SCM: 7 in 7 Local: 0:1:0
 * xaccTransGetIsClosingTxn SCM: 1  Local: 0:1:0
 * xaccTransGetDate C: 42 in 19  Local: 0:0:0
 * xaccTransGetDatePostedTS C: 6 in 5  Local: 1:0:0
 * xaccTransGetDateEnteredTS C: 1  Local: 0:0:0
 * xaccTransRetDatePostedTS C: 10 in 6  Local: 1:1:0
 * xaccTransGetDatePostedGDate C: 1  Local: 1:0:0
 * xaccTransRetDateEnteredTS C: 1  Local: 0:1:0
 * xaccTransGetDateDueTS C: 1  Local: 1:0:0
 * xaccTransRetDateDueTS C: 1 SCM: 2 in 2 Local: 0:1:0
 * xaccTransGetTxnType C: 3 in 2 SCM: 12 in 6 Local: 0:1:0*/
static void
test_xaccTransGetTxnType (Fixture *fixture, gconstpointer pData)
{
    // note this will only test TXN_TYPE_NONE, because TxnType is derived
    // from split data. Testing for TXN_TYPE_INVOICE TXN_TYPE_PAYMENT
    // will require creating invoices, so, they are tested in
    // utest-Invoice.c
    auto txn = fixture->txn;
    g_assert_cmpint (TXN_TYPE_NONE, ==, xaccTransGetTxnType(txn));
}


static void
test_xaccTransGetDateDue (Fixture *fixture, gconstpointer pData)
{
    auto txn = fixture->txn;
    auto frame = txn->inst.kvp_data;

    // if there's no date_due kvp, return posted_date
    g_assert_cmpint (xaccTransRetDateDue(txn), ==, xaccTransRetDatePosted(txn));
    g_assert_null (frame->get_slot({TRANS_DATE_DUE_KVP}));

    xaccTransSetDateDue (txn, 100);
    g_assert_cmpint (xaccTransRetDateDue(txn), ==, 100);
    g_assert_cmpint (frame->get_slot({TRANS_DATE_DUE_KVP})->get_ptr<Time64>()->t, ==, 100);

    xaccTransSetDateDue (txn, 0);
    g_assert_cmpint (frame->get_slot({TRANS_DATE_DUE_KVP})->get_ptr<Time64>()->t, ==, 0);
    g_assert_cmpint (xaccTransRetDateDue(txn), ==, 0);
}

/* xaccTransGetReadOnly C: 7 in 5  Local: 1:0:0
 * xaccTransIsReadonlyByPostedDate C: 2 in 2  Local: 0:0:0
 * xaccTransHasReconciledSplitsByAccount Local: 1:0:0
 * xaccTransHasReconciledSplits C: 4 in 3  Local: 0:0:0
 * xaccTransHasSplitsInStateByAccount Local: 1:0:0
 * xaccTransHasSplitsInState C: 4 in 1  Local: 0:0:0
 * counter_thunk Local: 0:1:0
 * gnc_book_count_transactions C: 3 in 2  Local: 0:0:0
 * xaccTransGetVoidStatus C: 3 in 2 SCM: 1  Local: 0:1:0

 * An absurdly long list of trivial accessors which don't need to be tested.
 */

/* xaccTransVoid
void
xaccTransVoid(Transaction *trans, const char *reason)// C: 1 SCM: 2 in 2 Local: 0:0:0
* xaccTransUnvoid
void
xaccTransUnvoid (Transaction *trans)// C: 1  Local: 0:0:0
*/


static void
test_xaccTransSetNotes (Fixture *fixture, gconstpointer pData)
{
    auto trans = fixture->txn;
    auto frame = fixture->txn->inst.kvp_data;

    xaccTransSetNotes (trans, "set");
    g_assert_cmpstr (xaccTransGetNotes (trans), ==, "set");
    g_assert_cmpstr (frame->get_slot({trans_notes_str})->get<const char*>(), ==, "set");

    xaccTransSetNotes (trans, "");
    g_assert_cmpstr (xaccTransGetNotes (trans), ==, nullptr);
    g_assert_null (frame->get_slot({trans_notes_str}));

    xaccTransSetNotes (trans, "reset");
    g_assert_cmpstr (xaccTransGetNotes (trans), ==, "reset");
    g_assert_cmpstr (frame->get_slot({trans_notes_str})->get<const char*>(), ==, "reset");

    // calling xaccTransSetNotes with notes==null is currently NOP
    xaccTransSetNotes (trans, NULL);
    g_assert_cmpstr (xaccTransGetNotes (trans), ==, "reset");
    g_assert_cmpstr (frame->get_slot({trans_notes_str})->get<const char*>(), ==, "reset");
}

static void
test_xaccTransSetDocLink (Fixture *fixture, gconstpointer pData)
{
    auto trans = fixture->txn;
    auto frame = fixture->txn->inst.kvp_data;

    g_assert_cmpstr (xaccTransGetDocLink (trans), ==, NULL);
    g_assert_null (frame->get_slot({doclink_uri_str}));

    xaccTransSetDocLink (trans, "doclink");
    g_assert_cmpstr (xaccTransGetDocLink (trans), ==, "doclink");
    g_assert_cmpstr (frame->get_slot({doclink_uri_str})->get<const char*>(), ==, "doclink");

    xaccTransSetDocLink (trans, "unset");
    g_assert_cmpstr (xaccTransGetDocLink (trans), ==, "unset");
    g_assert_cmpstr (frame->get_slot({doclink_uri_str})->get<const char*>(), ==, "unset");

    xaccTransSetDocLink (trans, "");
    g_assert_cmpstr (xaccTransGetDocLink (trans), ==, NULL);
    g_assert_null (frame->get_slot({doclink_uri_str}));

    xaccTransSetDocLink (trans, "reset");
    g_assert_cmpstr (xaccTransGetDocLink (trans), ==, "reset");
    g_assert_cmpstr (frame->get_slot({doclink_uri_str})->get<const char*>(), ==, "reset");

    // calling xaccTransSetDocLink with doclink==null is currently NOP
    xaccTransSetDocLink (trans, NULL);
    g_assert_cmpstr (xaccTransGetDocLink (trans), ==, "reset");
    g_assert_cmpstr (frame->get_slot({doclink_uri_str})->get<const char*>(), ==, "reset");
}

static void
test_xaccTransVoid (Fixture *fixture, gconstpointer pData)
{
    /* Actual function variables start here. */
    auto frame = fixture->txn->inst.kvp_data;
    auto void_reason = "Voided for Unit Test";
    auto txn_notes = g_strdup (frame->get_slot({trans_notes_str})->get<const char*>());
    time64 now = gnc_time(NULL);
    char iso8601_str[ISO_DATELENGTH + 1] = "";
    GList *split = NULL;

    xaccTransVoid (fixture->txn, void_reason);
    g_assert_cmpstr (frame->get_slot({trans_notes_str})->get<const char*>(), ==,
                     "Voided transaction");
    g_assert_cmpstr (frame->get_slot({void_former_notes_str})->get<const char*>(),
                     ==, txn_notes);
    g_assert_cmpstr (frame->get_slot({void_reason_str})->get<const char*>(), ==,
                     void_reason);
    gnc_time64_to_iso8601_buff (now, iso8601_str);
    g_assert_cmpstr (frame->get_slot({void_time_str})->get<const char*>(), ==,
                     iso8601_str);
    g_assert_cmpstr (frame->get_slot({TRANS_READ_ONLY_REASON})->get<const char*>(),
                     ==, "Transaction Voided");
    for (split = fixture->txn->splits; split; split=g_list_next (split))
    {
        g_assert_true (gnc_numeric_zero_p (((Split*)(split->data))->value));
        g_assert_true (gnc_numeric_zero_p (((Split*)(split->data))->amount));
    }

    xaccTransUnvoid (fixture->txn);

    g_assert_cmpstr (frame->get_slot({trans_notes_str})->get<const char*>(), ==,
                     txn_notes);
    g_assert_true (frame->get_slot({void_former_notes_str}) == NULL);
    g_assert_true (frame->get_slot({void_reason_str}) == NULL);
    g_assert_true (frame->get_slot({void_time_str}) == NULL);
    g_assert_true (frame->get_slot({TRANS_READ_ONLY_REASON}) == NULL);
    for (split = fixture->txn->splits; split; split=g_list_next (split))
    {
        g_assert_true (!gnc_numeric_zero_p (((Split*)(split->data))->value));
        g_assert_true (!gnc_numeric_zero_p (((Split*)(split->data))->amount));
    }

    g_free (txn_notes);

}
/* xaccTransReverse
Transaction *
xaccTransReverse (Transaction *orig)// C: 2 in 2  Local: 0:0:0
*/
static void
test_xaccTransReverse (Fixture *fixture, gconstpointer pData)
{
    Transaction *rev = xaccTransReverse (fixture->txn);
    auto frame = fixture->txn->inst.kvp_data;
    GList *orig_splits = NULL, *rev_splits = NULL;

    g_assert_true (guid_equal (frame->get_slot({TRANS_REVERSED_BY})->get<GncGUID*>(),
                          xaccTransGetGUID (rev)));

    g_assert_true (!qof_instance_is_dirty (QOF_INSTANCE (rev))); //Cleared by commit
    g_assert_cmpint (g_list_length (fixture->txn->splits), ==,
                     g_list_length (rev->splits));
    for (orig_splits = fixture->txn->splits,
            rev_splits = g_list_reverse (rev->splits);
            orig_splits && rev_splits;
            orig_splits = g_list_next (orig_splits),
            rev_splits = g_list_next (rev_splits))
    {
        auto orig_split = static_cast<Split*>(orig_splits->data);
        auto rev_split = static_cast<Split*>(rev_splits->data);
        g_assert_true (gnc_numeric_equal (orig_split->amount,
                                     gnc_numeric_neg (rev_split->amount)));
        g_assert_true (gnc_numeric_equal (orig_split->value,
                                     gnc_numeric_neg (rev_split->value)));
        g_assert_cmpint (xaccSplitGetReconcile (rev_split), ==, NREC);
    }

    fixture->func->xaccFreeTransaction (rev);
}
/* xaccTransGetReversedBy C: 2 in 2  Local: 0:0:0
 * Trivial getter.
 */
/* xaccTransScrubSplits  C: 1  Local: 0:0:0
 * Trivial pass-through.
 */
/* xaccTransScrubGainsDate
static void
xaccTransScrubGainsDate (Transaction *trans)// Local: 1:0:0
*/
static void
test_xaccTransScrubGainsDate_no_dirty (GainsFixture *fixture,
                                       gconstpointer pData)
{
    auto base_split = static_cast<Split *>(g_list_nth_data (fixture->base.txn->splits, 1));
    auto gains_split = base_split->gains_split;

    base_split->gains = GAINS_STATUS_GAINS;
    gains_split->gains = GAINS_STATUS_GAINS;

    fixture->base.func->xaccTransScrubGainsDate (fixture->base.txn);

    g_assert_true (fixture->base.txn->date_posted != fixture->gains_txn->date_posted);
    g_assert_cmphex (base_split->gains & GAINS_STATUS_DATE_DIRTY, ==, 0);
    g_assert_cmphex (base_split->gains_split->gains & GAINS_STATUS_DATE_DIRTY,
                     ==, 0);
}

static void
test_xaccTransScrubGainsDate_base_dirty (GainsFixture *fixture,
        gconstpointer pData)
{
    auto base_split = static_cast<Split *>(g_list_nth_data (fixture->base.txn->splits, 1));
    auto gains_split = base_split->gains_split;

    base_split->gains = GAINS_STATUS_GAINS | GAINS_STATUS_DATE_DIRTY;
    gains_split->gains = GAINS_STATUS_GAINS;

    fixture->base.func->xaccTransScrubGainsDate (fixture->base.txn);

    g_assert_true (fixture->base.txn->date_posted == fixture->gains_txn->date_posted);
    g_assert_cmphex (base_split->gains & GAINS_STATUS_DATE_DIRTY, ==, 0);
    g_assert_cmphex (base_split->gains_split->gains & GAINS_STATUS_DATE_DIRTY,
                     ==, 0);
}

static void
test_xaccTransScrubGainsDate_gains_dirty (GainsFixture *fixture,
        gconstpointer pData)
{
    auto base_split = static_cast<Split*>(g_list_nth_data (fixture->base.txn->splits, 1));
    auto gains_split = base_split->gains_split;

    base_split->gains = GAINS_STATUS_GAINS;
    gains_split->gains = GAINS_STATUS_GAINS | GAINS_STATUS_DATE_DIRTY;

    fixture->base.func->xaccTransScrubGainsDate (fixture->base.txn);

    g_assert_true (fixture->base.txn->date_posted == fixture->gains_txn->date_posted);
    g_assert_cmphex (base_split->gains & GAINS_STATUS_DATE_DIRTY, ==, 0);
    g_assert_cmphex (base_split->gains_split->gains & GAINS_STATUS_DATE_DIRTY,
                     ==, 0);
}

/* xaccTransScrubGains Local: 1:0:0
 * Non-trivial, but it passes through selected splits to functions in
 * cap-gains.c and Scrub3.c that are beyond the scope of this test
 * program.
 */
/* xaccTransFindSplitByAccount C: 7 in 5  Local: 0:0:0
 * destroy_tx_on_book_close Local: 0:1:0
 * gnc_transaction_book_end Local: 0:1:0
 * trans_is_balanced_p Local: 0:1:0
 * Trivial pass-through.
 */


void
test_suite_transaction (void)
{
    GNC_TEST_ADD (suitename, "check open", Fixture, NULL, setup, test_check_open, teardown);
    GNC_TEST_ADD (suitename, "xaccTransStillHasSplit", Fixture, NULL, setup, test_xaccTransStillHasSplit, teardown);
    GNC_TEST_ADD (suitename, "mark trans", Fixture, NULL, setup, test_mark_trans, teardown);
    GNC_TEST_ADD (suitename, "gen event trans", Fixture, NULL, setup, test_gen_event_trans, teardown);
    GNC_TEST_ADD_FUNC (suitename, "gnc transaction init", test_gnc_transaction_init);
    GNC_TEST_ADD_FUNC (suitename, "gnc transaction dispose", test_gnc_transaction_dispose);
    GNC_TEST_ADD_FUNC (suitename, "gnc transaction finalize", test_gnc_transaction_finalize);
    GNC_TEST_ADD (suitename, "gnc transaction set/get property", Fixture, NULL, setup, test_gnc_transaction_set_get_property, teardown);
    GNC_TEST_ADD (suitename, "xaccMallocTransaction", Fixture, NULL, setup, test_xaccMallocTransaction, teardown);
    GNC_TEST_ADD (suitename, "xaccTransSortSplits", Fixture, NULL, setup, test_xaccTransSortSplits, teardown);
    GNC_TEST_ADD (suitename, "dupe_trans", Fixture, NULL, setup, test_dupe_trans, teardown);
    GNC_TEST_ADD (suitename, "xaccTransClone", Fixture, NULL, setup, test_xaccTransClone, teardown);
    GNC_TEST_ADD (suitename, "xaccTransCopyFromClipBoard", Fixture, NULL, setup, test_xaccTransCopyFromClipBoard, teardown);
    GNC_TEST_ADD (suitename, "xaccTransCopyFromClipBoard No-Start", Fixture, NULL, setup, test_xaccTransCopyFromClipBoard_no_start, teardown);
    GNC_TEST_ADD (suitename, "xaccFreeTransaction", Fixture, NULL, setup, test_xaccFreeTransaction, teardown);
// GNC_TEST_ADD (suitename, "compare split guids", Fixture, NULL, setup, test_compare_split_guids, teardown);
    GNC_TEST_ADD (suitename, "xaccTransEqual", Fixture, NULL, setup, test_xaccTransEqual, teardown);
    GNC_TEST_ADD (suitename, "xaccTransLookup", Fixture, NULL, setup, test_xaccTransLookup, teardown);
    GNC_TEST_ADD (suitename, "xaccTransGetImbalanceValue", Fixture, NULL, setup, test_xaccTransGetImbalanceValue, teardown);
    GNC_TEST_ADD (suitename, "xaccTransGetImbalance", Fixture, NULL, setup, test_xaccTransGetImbalance, teardown);
    GNC_TEST_ADD (suitename, "xaccTransGetImbalance Trading Accounts", Fixture, NULL, setup, test_xaccTransGetImbalance_trading, teardown);
    GNC_TEST_ADD (suitename, "xaccTransIsBalanced", Fixture, NULL, setup, test_xaccTransIsBalanced, teardown);
    GNC_TEST_ADD (suitename, "xaccTransIsBalanced Trading Accounts", Fixture, NULL, setup, test_xaccTransIsBalanced_trading, teardown);
    GNC_TEST_ADD (suitename, "xaccTransGetAccountValue", Fixture, NULL, setup, test_xaccTransGetAccountValue, teardown);
    GNC_TEST_ADD (suitename, "xaccTransGetAccountAmount", Fixture, NULL, setup, test_xaccTransGetAccountAmount, teardown);
    GNC_TEST_ADD (suitename, "xaccTransGetAccountConvRate", Fixture, NULL, setup, test_xaccTransGetAccountConvRate, teardown);
    GNC_TEST_ADD (suitename, "xaccTransGetAccountBalance", Fixture, NULL, setup, test_xaccTransGetAccountBalance, teardown);

    GNC_TEST_ADD (suitename, "xaccTransSetCurrency", Fixture, NULL, setup, test_xaccTransSetCurrency, teardown);
    GNC_TEST_ADD_FUNC (suitename, "xaccTransBeginEdit", test_xaccTransBeginEdit);
    GNC_TEST_ADD (suitename, "xaccTransDestroy", Fixture, NULL, setup, test_xaccTransDestroy, teardown);
    GNC_TEST_ADD (suitename, "destroy gains", GainsFixture, NULL, setup_with_gains, test_destroy_gains, teardown_with_gains);
    GNC_TEST_ADD (suitename, "do destroy", GainsFixture, NULL, setup_with_gains, test_do_destroy, teardown_with_gains);
    GNC_TEST_ADD (suitename, "was trans emptied", Fixture, NULL, setup, test_was_trans_emptied, teardown);
    GNC_TEST_ADD (suitename, "trans on error", Fixture, NULL, setup, test_trans_on_error, teardown);
    GNC_TEST_ADD (suitename, "trans cleanup commit", Fixture, NULL, setup, test_trans_cleanup_commit, teardown);
    GNC_TEST_ADD_FUNC (suitename, "xaccTransCommitEdit", test_xaccTransCommitEdit);
    GNC_TEST_ADD (suitename, "xaccTransRollbackEdit", Fixture, NULL, setup, test_xaccTransRollbackEdit, teardown);
    GNC_TEST_ADD (suitename, "xaccTransRollbackEdit - Backend Errors", Fixture, NULL, setup, test_xaccTransRollbackEdit_BackendErrors, teardown);
    GNC_TEST_ADD (suitename, "xaccTransOrder_num_action", Fixture, NULL, setup, test_xaccTransOrder_num_action, teardown);
    GNC_TEST_ADD (suitename, "xaccTransGetTxnType", Fixture, NULL, setup, test_xaccTransGetTxnType, teardown);
    GNC_TEST_ADD (suitename, "xaccTransGetDateDue", Fixture, NULL, setup, test_xaccTransGetDateDue, teardown);
    GNC_TEST_ADD (suitename, "xaccTransGetreadOnly", Fixture, NULL, setup, test_xaccTransGetReadOnly, teardown);
    GNC_TEST_ADD (suitename, "xaccTransSetDocLink", Fixture, NULL, setup, test_xaccTransSetDocLink, teardown);
    GNC_TEST_ADD (suitename, "xaccTransSetNotes", Fixture, NULL, setup, test_xaccTransSetNotes, teardown);
    GNC_TEST_ADD (suitename, "xaccTransVoid", Fixture, NULL, setup, test_xaccTransVoid, teardown);
    GNC_TEST_ADD (suitename, "xaccTransReverse", Fixture, NULL, setup, test_xaccTransReverse, teardown);
    GNC_TEST_ADD (suitename, "xaccTransScrubGainsDate_no_dirty", GainsFixture, NULL, setup_with_gains, test_xaccTransScrubGainsDate_no_dirty, teardown_with_gains);
    GNC_TEST_ADD (suitename, "xaccTransScrubGainsDate_base_dirty", GainsFixture, NULL, setup_with_gains, test_xaccTransScrubGainsDate_base_dirty, teardown_with_gains);
    GNC_TEST_ADD (suitename, "xaccTransScrubGainsDate_gains_dirty", GainsFixture, NULL, setup_with_gains, test_xaccTransScrubGainsDate_gains_dirty, teardown_with_gains);

}
