1 /***************************************************************************
2  *            test-xml-transaction.c
3  *
4  *  Fri Oct  7 21:26:59 2005
5  *  Copyright  2005  Neil Williams
6  *  linux@codehelp.co.uk
7  ****************************************************************************/
8 /*
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or
12  *  (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  *  02110-1301, USA.
23  */
24 #include <glib.h>
25 #include <glib/gstdio.h>
26 
27 extern "C"
28 {
29 #include <config.h>
30 
31 #include <stdlib.h>
32 #include <unistd.h>
33 
34 #include <sys/types.h>
35 #include <dirent.h>
36 #include <sys/stat.h>
37 
38 #include <gnc-engine.h>
39 #include <cashobjects.h>
40 #include <TransLog.h>
41 
42 #include <test-engine-stuff.h>
43 #include <unittest-support.h>
44 
45 #include <AccountP.h>
46 #include <Transaction.h>
47 #include <TransactionP.h>
48 }
49 
50 #include "../gnc-xml-helper.h"
51 #include "../gnc-xml.h"
52 #include "../sixtp-parsers.h"
53 #include "../sixtp-dom-parsers.h"
54 #include "../io-gncxml-gen.h"
55 #include "test-file-stuff.h"
56 #include <test-stuff.h>
57 static QofBook* book;
58 
59 extern gboolean gnc_transaction_xml_v2_testing;
60 
61 static xmlNodePtr
find_appropriate_node(xmlNodePtr node,Split * spl)62 find_appropriate_node (xmlNodePtr node, Split* spl)
63 {
64     xmlNodePtr mark;
65 
66     for (mark = node->xmlChildrenNode; mark; mark = mark->next)
67     {
68         gboolean account_guid_good = FALSE;
69         gboolean amount_good = FALSE;
70         xmlNodePtr mark2;
71 
72         for (mark2 = mark->xmlChildrenNode; mark2; mark2 = mark2->next)
73         {
74             if (g_strcmp0 ((char*)mark2->name, "split:value") == 0)
75             {
76                 gnc_numeric* num = dom_tree_to_gnc_numeric (mark2);
77 
78                 if (gnc_numeric_equal (*num, xaccSplitGetValue (spl)))
79                 {
80                     amount_good = TRUE;
81                 }
82 
83                 g_free (num);
84             }
85             else if (g_strcmp0 ((char*)mark2->name, "split:account") == 0)
86             {
87                 GncGUID* accid = dom_tree_to_guid (mark2);
88                 Account* account = xaccSplitGetAccount (spl);
89 
90                 if (guid_equal (accid, xaccAccountGetGUID (account)))
91                 {
92                     account_guid_good = TRUE;
93                 }
94                 guid_free (accid);
95             }
96 
97             if (account_guid_good && amount_good)
98             {
99                 return mark;
100             }
101         }
102     }
103 
104     return NULL;
105 }
106 
107 static const char*
equals_node_val_vs_split_internal(xmlNodePtr node,Split * spl)108 equals_node_val_vs_split_internal (xmlNodePtr node, Split* spl)
109 {
110     xmlNodePtr mark;
111 
112     for (mark = node->children; mark != NULL; mark = mark->next)
113     {
114         if (g_strcmp0 ((char*)mark->name, "split:id") == 0)
115         {
116             GncGUID* id = dom_tree_to_guid (mark);
117 
118             if (!guid_equal (id, xaccSplitGetGUID (spl)))
119             {
120                 guid_free (id);
121                 return "ids differ";
122             }
123             guid_free (id);
124         }
125         else if (g_strcmp0 ((char*)mark->name, "split:memo") == 0)
126         {
127             char* memo = dom_tree_to_text (mark);
128 
129             if (g_strcmp0 (memo, xaccSplitGetMemo (spl)) != 0)
130             {
131                 g_free (memo);
132                 return "memos differ";
133             }
134             g_free (memo);
135         }
136         else if (g_strcmp0 ((char*)mark->name, "split:reconciled-state") == 0)
137         {
138             char* rs = dom_tree_to_text (mark);
139 
140             if (rs[0] != xaccSplitGetReconcile (spl))
141             {
142                 g_free (rs);
143                 return "states differ";
144             }
145             g_free (rs);
146         }
147         else if (g_strcmp0 ((char*)mark->name, "split:value") == 0)
148         {
149             gnc_numeric* num = dom_tree_to_gnc_numeric (mark);
150             gnc_numeric val = xaccSplitGetValue (spl);
151 
152             if (!gnc_numeric_equal (*num, val))
153             {
154                 g_free (num);
155                 return g_strdup_printf ("values differ: %" G_GINT64_FORMAT "/%"
156                                         G_GINT64_FORMAT " v %" G_GINT64_FORMAT
157                                         "/%" G_GINT64_FORMAT,
158                                         (*num).num, (*num).denom,
159                                         val.num, val.denom);
160             }
161             g_free (num);
162         }
163         else if (g_strcmp0 ((char*)mark->name, "split:quantity") == 0)
164         {
165             gnc_numeric* num = dom_tree_to_gnc_numeric (mark);
166             gnc_numeric val = xaccSplitGetAmount (spl);
167 
168             if (!gnc_numeric_equal (*num, val))
169             {
170                 return g_strdup_printf ("quantities differ under _equal: %"
171                                         G_GINT64_FORMAT "/%" G_GINT64_FORMAT
172                                         " v %" G_GINT64_FORMAT "/%"
173                                         G_GINT64_FORMAT,
174                                         (*num).num, (*num).denom,
175                                         val.num, val.denom);
176             }
177             if (!gnc_numeric_equal (*num, val))
178             {
179                 g_free (num);
180                 return g_strdup_printf ("quantities differ: %" G_GINT64_FORMAT
181                                         "/%" G_GINT64_FORMAT " v %"
182                                         G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
183                                         (*num).num, (*num).denom,
184                                         val.num, val.denom);
185             }
186             g_free (num);
187         }
188         else if (g_strcmp0 ((char*)mark->name, "split:account") == 0)
189         {
190             GncGUID* id = dom_tree_to_guid (mark);
191             Account* account = xaccSplitGetAccount (spl);
192 
193             if (!guid_equal (id, xaccAccountGetGUID (account)))
194             {
195                 guid_free (id);
196                 return "accounts differ";
197             }
198             guid_free (id);
199         }
200     }
201     return NULL;
202 }
203 
204 static const char*
equals_node_val_vs_splits(xmlNodePtr node,const Transaction * trn)205 equals_node_val_vs_splits (xmlNodePtr node, const Transaction* trn)
206 {
207     xmlNodePtr spl_node;
208     Split* spl_mark;
209     int i;
210 
211     g_return_val_if_fail (node, FALSE);
212     g_return_val_if_fail (node->xmlChildrenNode, FALSE);
213 
214     for (i = 0, spl_mark = xaccTransGetSplit ((Transaction*)trn, i);
215          spl_mark;
216          i++, spl_mark = xaccTransGetSplit ((Transaction*)trn, i))
217     {
218         spl_node = find_appropriate_node (node, spl_mark);
219 
220         if (!spl_node)
221         {
222             gchar guidstr[GUID_ENCODING_LENGTH + 1];
223             guid_to_string_buff (xaccSplitGetGUID (spl_mark), guidstr);
224             g_print ("Split GUID %s", guidstr);
225             return "no matching split found";
226         }
227 
228         const char* msg = equals_node_val_vs_split_internal (spl_node, spl_mark);
229         if (msg != NULL)
230         {
231             return msg;
232         }
233     }
234 
235     return NULL;
236 }
237 
238 static const char*
node_and_transaction_equal(xmlNodePtr node,Transaction * trn)239 node_and_transaction_equal (xmlNodePtr node, Transaction* trn)
240 {
241     xmlNodePtr mark;
242 
243     while (g_strcmp0 ((char*)node->name, "text") == 0)
244         node = node->next;
245 
246     if (!check_dom_tree_version (node, "2.0.0"))
247     {
248         return "version wrong.  Not 2.0.0 or not there";
249     }
250 
251     if (!node->name || g_strcmp0 ((char*)node->name, "gnc:transaction"))
252     {
253         return "Name of toplevel node is bad";
254     }
255 
256     for (mark = node->xmlChildrenNode; mark; mark = mark->next)
257     {
258         if (g_strcmp0 ((char*)mark->name, "text") == 0)
259         {
260         }
261         else if (g_strcmp0 ((char*)mark->name, "trn:id") == 0)
262         {
263             if (!equals_node_val_vs_guid (mark, xaccTransGetGUID (trn)))
264             {
265                 return "ids differ";
266             }
267         }
268 
269         /* This test will fail for many splits where the transaction has
270          * splits in different commodities -- eg, buying or selling a
271          * stock. jralls 2010-11-02 */
272         else if (g_strcmp0 ((char*)mark->name, "trn:currency") == 0)
273         {
274 #if 0
275             if (!equals_node_val_vs_commodity (
276                     mark, xaccTransGetCurrency (trn), xaccTransGetBook (trn)))
277             {
278                 return g_strdup ("currencies differ");
279             }
280 #endif
281         }
282         else if (g_strcmp0 ((char*)mark->name, "trn:num") == 0)
283         {
284             if (!equals_node_val_vs_string (mark, xaccTransGetNum (trn)))
285             {
286                 return "nums differ";
287             }
288         }
289         else if (g_strcmp0 ((char*)mark->name, "trn:date-posted") == 0)
290         {
291             if (!equals_node_val_vs_date (mark, xaccTransRetDatePosted (trn)))
292             {
293                 return "posted dates differ";
294             }
295         }
296         else if (g_strcmp0 ((char*)mark->name, "trn:date-entered") == 0)
297         {
298             if (!equals_node_val_vs_date (mark, xaccTransRetDateEntered (trn)))
299             {
300                 return "entered dates differ";
301             }
302         }
303         else if (g_strcmp0 ((char*)mark->name, "trn:description") == 0)
304         {
305             if (!equals_node_val_vs_string (mark, xaccTransGetDescription (trn)))
306             {
307                 return "descriptions differ";
308             }
309         }
310         else if (g_strcmp0 ((char*)mark->name, "trn:slots") == 0)
311         {
312             if (!equals_node_val_vs_kvp_frame (mark,
313                                                qof_instance_get_slots (QOF_INSTANCE (trn))))
314             {
315                 return "slots differ";
316             }
317         }
318         else if (g_strcmp0 ((char*)mark->name, "trn:splits") == 0)
319         {
320             const char* msg = equals_node_val_vs_splits (mark, trn);
321             if (msg != NULL)
322             {
323                 return msg;
324             }
325         }
326         else
327         {
328             return "unknown node";
329         }
330     }
331 
332     return NULL;
333 }
334 
335 static void
really_get_rid_of_transaction(Transaction * trn)336 really_get_rid_of_transaction (Transaction* trn)
337 {
338     xaccTransBeginEdit (trn);
339     xaccTransDestroy (trn);
340     xaccTransCommitEdit (trn);
341 }
342 
343 struct tran_data_struct
344 {
345     Transaction* trn;
346     Transaction* new_trn;
347     gnc_commodity* com;
348     int value;
349 };
350 typedef struct tran_data_struct tran_data;
351 
352 static gboolean
test_add_transaction(const char * tag,gpointer globaldata,gpointer data)353 test_add_transaction (const char* tag, gpointer globaldata, gpointer data)
354 {
355     Transaction* trans = static_cast<decltype (trans)> (data);
356     tran_data* gdata = static_cast<decltype (gdata)> (globaldata);
357     gboolean retval = TRUE;
358 
359     xaccTransBeginEdit (trans);
360     xaccTransSetCurrency (trans, gdata->com);
361     xaccTransCommitEdit (trans);
362 
363     if (!do_test_args (xaccTransEqual (gdata->trn, trans, TRUE, TRUE, TRUE, FALSE),
364                        "gnc_transaction_sixtp_parser_create",
365                        __FILE__, __LINE__,
366                        "%d", gdata->value))
367         retval = FALSE;
368 
369     gdata->new_trn = trans;
370 
371     return retval;
372 }
373 
374 static void
test_transaction(void)375 test_transaction (void)
376 {
377     int i;
378 
379     for (i = 0; i < 50; i++)
380     {
381         Transaction* ran_trn;
382         xmlNodePtr test_node;
383         gnc_commodity* com, *new_com;
384         gchar* filename1;
385         int fd;
386 
387         /* The next line exists for its side effect of creating the
388          * account tree. */
389         get_random_account_tree (book);
390         ran_trn = get_random_transaction (book);
391         new_com = get_random_commodity (book);
392         if (!ran_trn)
393         {
394             failure_args ("transaction_xml", __FILE__, __LINE__,
395                           "get_random_transaction returned NULL");
396             return;
397         }
398 
399         {
400             /* xaccAccountInsertSplit can reorder the splits. */
401             GList* list = g_list_copy (xaccTransGetSplitList (ran_trn));
402             GList* node = list;
403             for (; node; node = node->next)
404             {
405                 Split* s = static_cast<decltype (s)> (node->data);
406                 Account* a = xaccMallocAccount (book);
407 
408                 xaccAccountBeginEdit (a);
409                 xaccAccountSetCommodity (a, new_com);
410                 xaccAccountSetCommoditySCU (a, xaccSplitGetAmount (s).denom);
411                 xaccAccountInsertSplit (a, s);
412                 xaccAccountCommitEdit (a);
413             }
414             g_list_free (list);
415         }
416 
417         com = xaccTransGetCurrency (ran_trn);
418 
419         test_node = gnc_transaction_dom_tree_create (ran_trn);
420         if (!test_node)
421         {
422             failure_args ("transaction_xml", __FILE__, __LINE__,
423                           "gnc_transaction_dom_tree_create returned NULL");
424             really_get_rid_of_transaction (ran_trn);
425             continue;
426         }
427         auto compare_msg = node_and_transaction_equal (test_node, ran_trn);
428         if (compare_msg != nullptr)
429         {
430             failure_args ("transaction_xml", __FILE__, __LINE__,
431                           "node and transaction were not equal: %s",
432                           compare_msg);
433             xmlElemDump (stdout, NULL, test_node);
434             printf ("\n");
435             fflush (stdout);
436             xmlFreeNode (test_node);
437             really_get_rid_of_transaction (ran_trn);
438             continue;
439         }
440         else
441         {
442             success_args ("transaction_xml", __FILE__, __LINE__, "%d", i);
443         }
444 
445         filename1 = g_strdup_printf ("test_file_XXXXXX");
446 
447         fd = g_mkstemp (filename1);
448 
449         write_dom_node_to_file (test_node, fd);
450 
451         close (fd);
452 
453         {
454             GList* node = xaccTransGetSplitList (ran_trn);
455             for (; node; node = node->next)
456             {
457                 Split* s = static_cast<decltype (s)> (node->data);
458                 Account* a1 = xaccSplitGetAccount (s);
459                 Account* a2 = xaccMallocAccount (book);
460 
461                 xaccAccountBeginEdit (a2);
462                 xaccAccountSetCommoditySCU (a2, xaccAccountGetCommoditySCU (a1));
463                 xaccAccountSetGUID (a2, xaccAccountGetGUID (a1));
464                 xaccAccountCommitEdit (a2);
465             }
466         }
467 
468         {
469             sixtp* parser;
470             tran_data data;
471 
472             const char* msg =
473                 "[xaccAccountScrubCommodity()] Account \"\" does not have a commodity!";
474             const char* logdomain = "gnc.engine.scrub";
475             GLogLevelFlags loglevel = static_cast<decltype (loglevel)>
476                                       (G_LOG_LEVEL_CRITICAL);
477             TestErrorStruct check = { loglevel, const_cast<char*> (logdomain),
478                                       const_cast<char*> (msg)
479                                     };
480             g_log_set_handler (logdomain, loglevel,
481                                (GLogFunc)test_checked_handler, &check);
482             data.trn = ran_trn;
483             data.com = com;
484             data.value = i;
485             parser = gnc_transaction_sixtp_parser_create ();
486 
487             if (!gnc_xml_parse_file (parser, filename1, test_add_transaction,
488                                      (gpointer)&data, book))
489             {
490                 failure_args ("gnc_xml_parse_file returned FALSE",
491                               __FILE__, __LINE__, "%d", i);
492             }
493             else
494                 really_get_rid_of_transaction (data.new_trn);
495         }
496         /* no handling of circular data structures.  We'll do that later */
497         /* sixtp_destroy(parser); */
498 
499 
500         g_unlink (filename1);
501         g_free (filename1);
502         really_get_rid_of_transaction (ran_trn);
503         xmlFreeNode (test_node);
504     }
505 }
506 
507 static gboolean
test_real_transaction(const char * tag,gpointer global_data,gpointer data)508 test_real_transaction (const char* tag, gpointer global_data, gpointer data)
509 {
510     const char* msg;
511 
512     msg = node_and_transaction_equal ((xmlNodePtr)global_data,
513                                       (Transaction*)data);
514     do_test_args (msg == NULL, "test_real_transaction",
515                   __FILE__, __LINE__, msg);
516     really_get_rid_of_transaction ((Transaction*)data);
517     return TRUE;
518 }
519 
520 int
main(int argc,char ** argv)521 main (int argc, char** argv)
522 {
523     qof_init ();
524     cashobjects_register ();
525     xaccLogDisable ();
526 
527     gnc_transaction_xml_v2_testing = TRUE;
528 
529     book = qof_book_new ();
530 
531     if (argc > 1)
532     {
533         test_files_in_dir (argc, argv, test_real_transaction,
534                            gnc_transaction_sixtp_parser_create (),
535                            "gnc:transaction", book);
536     }
537     else
538     {
539         test_transaction ();
540     }
541 
542     print_test_results ();
543     qof_close ();
544     exit (get_rv ());
545 }
546