mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Return a gnc_numeric instead of allocations that every caller has to free. This makes it easier to fix the use after free in the unit test function equals_node_val_vs_split_internal() where the expression in the return statement wants to use the allocated gnc_numeric.
537 lines
16 KiB
C++
537 lines
16 KiB
C++
/***************************************************************************
|
|
* test-xml-transaction.c
|
|
*
|
|
* Fri Oct 7 21:26:59 2005
|
|
* Copyright 2005 Neil Williams
|
|
* linux@codehelp.co.uk
|
|
****************************************************************************/
|
|
/*
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*/
|
|
#include <glib.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <gnc-engine.h>
|
|
#include <cashobjects.h>
|
|
#include <TransLog.h>
|
|
|
|
#include <test-engine-stuff.h>
|
|
#include <unittest-support.h>
|
|
|
|
#include <AccountP.h>
|
|
#include <Transaction.h>
|
|
#include <TransactionP.h>
|
|
|
|
#include "../gnc-xml-helper.h"
|
|
#include "../gnc-xml.h"
|
|
#include "../sixtp-parsers.h"
|
|
#include "../sixtp-dom-parsers.h"
|
|
#include "../io-gncxml-gen.h"
|
|
#include "test-file-stuff.h"
|
|
#include <test-stuff.h>
|
|
static QofBook* book;
|
|
|
|
extern gboolean gnc_transaction_xml_v2_testing;
|
|
|
|
static xmlNodePtr
|
|
find_appropriate_node (xmlNodePtr node, Split* spl)
|
|
{
|
|
xmlNodePtr mark;
|
|
|
|
for (mark = node->xmlChildrenNode; mark; mark = mark->next)
|
|
{
|
|
gboolean account_guid_good = FALSE;
|
|
gboolean amount_good = FALSE;
|
|
xmlNodePtr mark2;
|
|
|
|
for (mark2 = mark->xmlChildrenNode; mark2; mark2 = mark2->next)
|
|
{
|
|
if (g_strcmp0 ((char*)mark2->name, "split:value") == 0)
|
|
{
|
|
gnc_numeric num = dom_tree_to_gnc_numeric (mark2);
|
|
|
|
if (gnc_numeric_equal (num, xaccSplitGetValue (spl)))
|
|
{
|
|
amount_good = TRUE;
|
|
}
|
|
}
|
|
else if (g_strcmp0 ((char*)mark2->name, "split:account") == 0)
|
|
{
|
|
GncGUID* accid = dom_tree_to_guid (mark2);
|
|
Account* account = xaccSplitGetAccount (spl);
|
|
|
|
if (guid_equal (accid, xaccAccountGetGUID (account)))
|
|
{
|
|
account_guid_good = TRUE;
|
|
}
|
|
guid_free (accid);
|
|
}
|
|
|
|
if (account_guid_good && amount_good)
|
|
{
|
|
return mark;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char*
|
|
equals_node_val_vs_split_internal (xmlNodePtr node, Split* spl)
|
|
{
|
|
xmlNodePtr mark;
|
|
|
|
for (mark = node->children; mark != NULL; mark = mark->next)
|
|
{
|
|
if (g_strcmp0 ((char*)mark->name, "split:id") == 0)
|
|
{
|
|
GncGUID* id = dom_tree_to_guid (mark);
|
|
|
|
if (!guid_equal (id, xaccSplitGetGUID (spl)))
|
|
{
|
|
guid_free (id);
|
|
return "ids differ";
|
|
}
|
|
guid_free (id);
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "split:memo") == 0)
|
|
{
|
|
char* memo = dom_tree_to_text (mark);
|
|
|
|
if (g_strcmp0 (memo, xaccSplitGetMemo (spl)) != 0)
|
|
{
|
|
g_free (memo);
|
|
return "memos differ";
|
|
}
|
|
g_free (memo);
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "split:reconciled-state") == 0)
|
|
{
|
|
char* rs = dom_tree_to_text (mark);
|
|
|
|
if (rs[0] != xaccSplitGetReconcile (spl))
|
|
{
|
|
g_free (rs);
|
|
return "states differ";
|
|
}
|
|
g_free (rs);
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "split:value") == 0)
|
|
{
|
|
gnc_numeric num = dom_tree_to_gnc_numeric (mark);
|
|
gnc_numeric val = xaccSplitGetValue (spl);
|
|
|
|
if (!gnc_numeric_equal (num, val))
|
|
{
|
|
return g_strdup_printf ("values differ: %" G_GINT64_FORMAT "/%"
|
|
G_GINT64_FORMAT " v %" G_GINT64_FORMAT
|
|
"/%" G_GINT64_FORMAT,
|
|
num.num, num.denom,
|
|
val.num, val.denom);
|
|
}
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "split:quantity") == 0)
|
|
{
|
|
gnc_numeric num = dom_tree_to_gnc_numeric (mark);
|
|
gnc_numeric val = xaccSplitGetAmount (spl);
|
|
|
|
if (!gnc_numeric_equal (num, val))
|
|
{
|
|
return g_strdup_printf ("quantities differ under _equal: %"
|
|
G_GINT64_FORMAT "/%" G_GINT64_FORMAT
|
|
" v %" G_GINT64_FORMAT "/%"
|
|
G_GINT64_FORMAT,
|
|
num.num, num.denom,
|
|
val.num, val.denom);
|
|
}
|
|
if (!gnc_numeric_equal (num, val))
|
|
{
|
|
return g_strdup_printf ("quantities differ: %" G_GINT64_FORMAT
|
|
"/%" G_GINT64_FORMAT " v %"
|
|
G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
|
|
num.num, num.denom,
|
|
val.num, val.denom);
|
|
}
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "split:account") == 0)
|
|
{
|
|
GncGUID* id = dom_tree_to_guid (mark);
|
|
Account* account = xaccSplitGetAccount (spl);
|
|
|
|
if (!guid_equal (id, xaccAccountGetGUID (account)))
|
|
{
|
|
guid_free (id);
|
|
return "accounts differ";
|
|
}
|
|
guid_free (id);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const char*
|
|
equals_node_val_vs_splits (xmlNodePtr node, const Transaction* trn)
|
|
{
|
|
xmlNodePtr spl_node;
|
|
Split* spl_mark;
|
|
int i;
|
|
|
|
g_return_val_if_fail (node, FALSE);
|
|
g_return_val_if_fail (node->xmlChildrenNode, FALSE);
|
|
|
|
for (i = 0, spl_mark = xaccTransGetSplit ((Transaction*)trn, i);
|
|
spl_mark;
|
|
i++, spl_mark = xaccTransGetSplit ((Transaction*)trn, i))
|
|
{
|
|
spl_node = find_appropriate_node (node, spl_mark);
|
|
|
|
if (!spl_node)
|
|
{
|
|
gchar guidstr[GUID_ENCODING_LENGTH + 1];
|
|
guid_to_string_buff (xaccSplitGetGUID (spl_mark), guidstr);
|
|
g_print ("Split GUID %s", guidstr);
|
|
return "no matching split found";
|
|
}
|
|
|
|
const char* msg = equals_node_val_vs_split_internal (spl_node, spl_mark);
|
|
if (msg != NULL)
|
|
{
|
|
return msg;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char*
|
|
node_and_transaction_equal (xmlNodePtr node, Transaction* trn)
|
|
{
|
|
xmlNodePtr mark;
|
|
|
|
while (g_strcmp0 ((char*)node->name, "text") == 0)
|
|
node = node->next;
|
|
|
|
if (!check_dom_tree_version (node, "2.0.0"))
|
|
{
|
|
return "version wrong. Not 2.0.0 or not there";
|
|
}
|
|
|
|
if (!node->name || g_strcmp0 ((char*)node->name, "gnc:transaction"))
|
|
{
|
|
return "Name of toplevel node is bad";
|
|
}
|
|
|
|
for (mark = node->xmlChildrenNode; mark; mark = mark->next)
|
|
{
|
|
if (g_strcmp0 ((char*)mark->name, "text") == 0)
|
|
{
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "trn:id") == 0)
|
|
{
|
|
if (!equals_node_val_vs_guid (mark, xaccTransGetGUID (trn)))
|
|
{
|
|
return "ids differ";
|
|
}
|
|
}
|
|
|
|
/* This test will fail for many splits where the transaction has
|
|
* splits in different commodities -- eg, buying or selling a
|
|
* stock. jralls 2010-11-02 */
|
|
else if (g_strcmp0 ((char*)mark->name, "trn:currency") == 0)
|
|
{
|
|
#if 0
|
|
if (!equals_node_val_vs_commodity (
|
|
mark, xaccTransGetCurrency (trn), xaccTransGetBook (trn)))
|
|
{
|
|
return g_strdup ("currencies differ");
|
|
}
|
|
#endif
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "trn:num") == 0)
|
|
{
|
|
if (!equals_node_val_vs_string (mark, xaccTransGetNum (trn)))
|
|
{
|
|
return "nums differ";
|
|
}
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "trn:date-posted") == 0)
|
|
{
|
|
if (!equals_node_val_vs_date (mark, xaccTransRetDatePosted (trn)))
|
|
{
|
|
return "posted dates differ";
|
|
}
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "trn:date-entered") == 0)
|
|
{
|
|
if (!equals_node_val_vs_date (mark, xaccTransRetDateEntered (trn)))
|
|
{
|
|
return "entered dates differ";
|
|
}
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "trn:description") == 0)
|
|
{
|
|
if (!equals_node_val_vs_string (mark, xaccTransGetDescription (trn)))
|
|
{
|
|
return "descriptions differ";
|
|
}
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "trn:slots") == 0)
|
|
{
|
|
if (!equals_node_val_vs_kvp_frame (mark,
|
|
qof_instance_get_slots (QOF_INSTANCE (trn))))
|
|
{
|
|
return "slots differ";
|
|
}
|
|
}
|
|
else if (g_strcmp0 ((char*)mark->name, "trn:splits") == 0)
|
|
{
|
|
const char* msg = equals_node_val_vs_splits (mark, trn);
|
|
if (msg != NULL)
|
|
{
|
|
return msg;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return "unknown node";
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
really_get_rid_of_transaction (Transaction* trn)
|
|
{
|
|
xaccTransBeginEdit (trn);
|
|
xaccTransDestroy (trn);
|
|
xaccTransCommitEdit (trn);
|
|
}
|
|
|
|
struct tran_data_struct
|
|
{
|
|
Transaction* trn;
|
|
Transaction* new_trn;
|
|
gnc_commodity* com;
|
|
int value;
|
|
};
|
|
typedef struct tran_data_struct tran_data;
|
|
|
|
static gboolean
|
|
test_add_transaction (const char* tag, gpointer globaldata, gpointer data)
|
|
{
|
|
Transaction* trans = static_cast<decltype (trans)> (data);
|
|
tran_data* gdata = static_cast<decltype (gdata)> (globaldata);
|
|
gboolean retval = TRUE;
|
|
|
|
xaccTransBeginEdit (trans);
|
|
xaccTransSetCurrency (trans, gdata->com);
|
|
xaccTransCommitEdit (trans);
|
|
|
|
if (!do_test_args (xaccTransEqual (gdata->trn, trans, TRUE, TRUE, TRUE, FALSE),
|
|
"gnc_transaction_sixtp_parser_create",
|
|
__FILE__, __LINE__,
|
|
"%d", gdata->value))
|
|
retval = FALSE;
|
|
|
|
gdata->new_trn = trans;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
test_transaction (void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 50; i++)
|
|
{
|
|
Transaction* ran_trn;
|
|
xmlNodePtr test_node;
|
|
gnc_commodity* com, *new_com;
|
|
gchar* filename1;
|
|
int fd;
|
|
|
|
/* The next line exists for its side effect of creating the
|
|
* account tree. */
|
|
get_random_account_tree (book);
|
|
ran_trn = get_random_transaction (book);
|
|
new_com = get_random_commodity (book);
|
|
if (!ran_trn)
|
|
{
|
|
failure_args ("transaction_xml", __FILE__, __LINE__,
|
|
"get_random_transaction returned NULL");
|
|
return;
|
|
}
|
|
|
|
{
|
|
/* xaccAccountInsertSplit can reorder the splits. */
|
|
GList* list = g_list_copy (xaccTransGetSplitList (ran_trn));
|
|
GList* node = list;
|
|
for (; node; node = node->next)
|
|
{
|
|
Split* s = static_cast<decltype (s)> (node->data);
|
|
Account* a = xaccMallocAccount (book);
|
|
|
|
xaccAccountBeginEdit (a);
|
|
xaccAccountSetCommodity (a, new_com);
|
|
xaccAccountSetCommoditySCU (a, xaccSplitGetAmount (s).denom);
|
|
xaccAccountInsertSplit (a, s);
|
|
xaccAccountCommitEdit (a);
|
|
}
|
|
g_list_free (list);
|
|
}
|
|
|
|
com = xaccTransGetCurrency (ran_trn);
|
|
|
|
test_node = gnc_transaction_dom_tree_create (ran_trn);
|
|
if (!test_node)
|
|
{
|
|
failure_args ("transaction_xml", __FILE__, __LINE__,
|
|
"gnc_transaction_dom_tree_create returned NULL");
|
|
really_get_rid_of_transaction (ran_trn);
|
|
continue;
|
|
}
|
|
auto compare_msg = node_and_transaction_equal (test_node, ran_trn);
|
|
if (compare_msg != nullptr)
|
|
{
|
|
failure_args ("transaction_xml", __FILE__, __LINE__,
|
|
"node and transaction were not equal: %s",
|
|
compare_msg);
|
|
xmlElemDump (stdout, NULL, test_node);
|
|
printf ("\n");
|
|
fflush (stdout);
|
|
xmlFreeNode (test_node);
|
|
really_get_rid_of_transaction (ran_trn);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
success_args ("transaction_xml", __FILE__, __LINE__, "%d", i);
|
|
}
|
|
|
|
filename1 = g_strdup_printf ("test_file_XXXXXX");
|
|
|
|
fd = g_mkstemp (filename1);
|
|
|
|
write_dom_node_to_file (test_node, fd);
|
|
|
|
close (fd);
|
|
|
|
{
|
|
GList* node = xaccTransGetSplitList (ran_trn);
|
|
for (; node; node = node->next)
|
|
{
|
|
Split* s = static_cast<decltype (s)> (node->data);
|
|
Account* a1 = xaccSplitGetAccount (s);
|
|
Account* a2 = xaccMallocAccount (book);
|
|
|
|
xaccAccountBeginEdit (a2);
|
|
xaccAccountSetCommoditySCU (a2, xaccAccountGetCommoditySCU (a1));
|
|
xaccAccountSetGUID (a2, xaccAccountGetGUID (a1));
|
|
xaccAccountCommitEdit (a2);
|
|
}
|
|
}
|
|
|
|
{
|
|
sixtp* parser;
|
|
tran_data data;
|
|
|
|
const char* msg =
|
|
"[xaccAccountScrubCommodity()] Account \"\" does not have a commodity!";
|
|
const char* logdomain = "gnc.engine.scrub";
|
|
GLogLevelFlags loglevel = static_cast<decltype (loglevel)>
|
|
(G_LOG_LEVEL_CRITICAL);
|
|
TestErrorStruct check = { loglevel, const_cast<char*> (logdomain),
|
|
const_cast<char*> (msg)
|
|
};
|
|
g_log_set_handler (logdomain, loglevel,
|
|
(GLogFunc)test_checked_handler, &check);
|
|
data.trn = ran_trn;
|
|
data.com = com;
|
|
data.value = i;
|
|
parser = gnc_transaction_sixtp_parser_create ();
|
|
|
|
if (!gnc_xml_parse_file (parser, filename1, test_add_transaction,
|
|
(gpointer)&data, book))
|
|
{
|
|
failure_args ("gnc_xml_parse_file returned FALSE",
|
|
__FILE__, __LINE__, "%d", i);
|
|
}
|
|
else
|
|
really_get_rid_of_transaction (data.new_trn);
|
|
}
|
|
/* no handling of circular data structures. We'll do that later */
|
|
/* sixtp_destroy(parser); */
|
|
|
|
|
|
g_unlink (filename1);
|
|
g_free (filename1);
|
|
really_get_rid_of_transaction (ran_trn);
|
|
xmlFreeNode (test_node);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
test_real_transaction (const char* tag, gpointer global_data, gpointer data)
|
|
{
|
|
const char* msg;
|
|
|
|
msg = node_and_transaction_equal ((xmlNodePtr)global_data,
|
|
(Transaction*)data);
|
|
do_test_args (msg == NULL, "test_real_transaction",
|
|
__FILE__, __LINE__, msg);
|
|
really_get_rid_of_transaction ((Transaction*)data);
|
|
return TRUE;
|
|
}
|
|
|
|
int
|
|
main (int argc, char** argv)
|
|
{
|
|
qof_init ();
|
|
cashobjects_register ();
|
|
xaccLogDisable ();
|
|
|
|
gnc_transaction_xml_v2_testing = TRUE;
|
|
|
|
book = qof_book_new ();
|
|
|
|
if (argc > 1)
|
|
{
|
|
test_files_in_dir (argc, argv, test_real_transaction,
|
|
gnc_transaction_sixtp_parser_create (),
|
|
"gnc:transaction", book);
|
|
}
|
|
else
|
|
{
|
|
test_transaction ();
|
|
}
|
|
|
|
print_test_results ();
|
|
qof_close ();
|
|
exit (get_rv ());
|
|
}
|