#include "config.h" #include #include #include #include #include #include #include #include #include #include "date.h" #include "gnc-engine.h" #include "gnc-engine-util.h" #include "test-engine-stuff.h" #include "test-stuff.h" static gboolean glist_strings_only = FALSE; static GHashTable *exclude_kvp_types = NULL; static gint kvp_max_depth = 5; static gint kvp_frame_max_elements = 10; static gint max_group_depth = 4; static gint max_group_accounts = 10; static kvp_value* get_random_kvp_value_depth (int type, gint depth); static gpointer get_random_list_element (GList *list); static void add_random_splits(GNCSession *session, Transaction *trn); /***********************************************************************/ void set_max_group_depth (gint max_group_depth_in) { max_group_depth = MAX (max_group_depth_in, 1); } void set_max_group_accounts (gint max_group_accounts_in) { max_group_accounts = MAX (max_group_accounts_in, 1); } void set_max_kvp_depth (gint max_kvp_depth) { kvp_max_depth = MAX (max_kvp_depth, 1); } void set_max_kvp_frame_elements (gint max_kvp_frame_elements) { kvp_frame_max_elements = MAX (max_kvp_frame_elements, 1); } void glist_exclude_type (kvp_value_t kvp_type) { gint *key; if (!exclude_kvp_types) exclude_kvp_types = g_hash_table_new (g_int_hash, g_int_equal); key = g_new (gint, 1); *key = kvp_type; g_hash_table_insert (exclude_kvp_types, key, exclude_kvp_types); } static gboolean glist_type_excluded (kvp_value_t kvp_type) { gint key = kvp_type; if (!exclude_kvp_types) return FALSE; if (g_hash_table_lookup (exclude_kvp_types, &key)) return TRUE; return FALSE; } void random_glist_strings_only (gboolean strings_only) { glist_strings_only = strings_only; } static gboolean zero_nsec = FALSE; void random_timespec_zero_nsec (gboolean zero_nsec_in) { zero_nsec = zero_nsec_in; } Timespec* get_random_timespec(void) { Timespec *ret; ret = g_new(Timespec, 1); ret->tv_sec = rand(); ret->tv_nsec = zero_nsec ? 0 : rand(); return ret; } gnc_numeric get_random_gnc_numeric(void) { return gnc_numeric_create(get_random_gint64(), rand()); } const char *types[] = { "NASDAQ", "NYSE", "EUREX", "FUND", "AMEX", NULL }; const char* get_random_commodity_namespace(void) { return get_random_string_in_array(types); } GNCPrice * get_random_price(GNCSession *session) { GNCPrice *p; Timespec *ts; char *string; gnc_commodity *c; p = gnc_price_create (session); c = get_random_commodity (session); gnc_price_set_commodity (p, c); c = get_random_commodity (session); gnc_price_set_currency (p, c); ts = get_random_timespec (); gnc_price_set_time (p, *ts); g_free (ts); string = get_random_string (); gnc_price_set_source (p, string); g_free (string); string = get_random_string (); gnc_price_set_type (p, string); g_free (string); gnc_price_set_value (p, get_random_gnc_numeric ()); return p; } void make_random_pricedb (GNCSession *session, GNCPriceDB *db) { int num_prices; num_prices = get_random_int_in_range (0, 40); while (num_prices-- > 0) { GNCPrice *p; p = get_random_price (session); gnc_pricedb_add_price (db, p); gnc_price_unref (p); } } GNCPriceDB * get_random_pricedb(GNCSession *session) { GNCPriceDB *db; db = gnc_pricedb_create (); make_random_pricedb (session, db); return db; } GUID* get_random_guid(void) { GUID *ret; ret = g_new(GUID, 1); guid_new(ret); return ret; } static GList* get_random_glist_depth (gint depth) { GList *ret = NULL; int count = get_random_int_in_range(1, 5); int i; if (depth >= kvp_max_depth) return NULL; for (i = 0; i < count; i++) { kvp_value_t kvpt; kvp_value *value; kvpt = glist_strings_only ? KVP_TYPE_STRING : -2; do { value = get_random_kvp_value_depth (kvpt, depth + 1); } while (!value); ret = g_list_prepend(ret, value); } return ret; } GList* get_random_glist(void) { return get_random_glist_depth (0); } bin_data* get_random_binary_data(void) { int len; bin_data *ret; len = get_random_int_in_range(20,100); ret = g_new(bin_data, 1); ret->data = g_new(char, len); ret->len = len; for(len--; len >= 0; len--) { ret->data[len] = (char)get_random_int_in_range(0,255); } return ret; } static kvp_frame* get_random_kvp_frame_depth (gint depth) { kvp_frame *ret; int vals_to_add; gboolean val_added; if (depth >= kvp_max_depth) return NULL; ret = kvp_frame_new(); vals_to_add = get_random_int_in_range(1,kvp_frame_max_elements); val_added = FALSE; for(;vals_to_add > 0; vals_to_add--) { gchar *key; kvp_value *val; key = get_random_string_without("/"); val = get_random_kvp_value_depth (-1, depth + 1); if(!key) { return NULL; } if (!val) { if (!val_added) vals_to_add++; continue; } val_added = TRUE; kvp_frame_set_slot_nc(ret, key, val); g_free(key); } return ret; } kvp_frame* get_random_kvp_frame (void) { return get_random_kvp_frame_depth (0); } static kvp_value* get_random_kvp_value_depth (int type, gint depth) { int datype = type; if (datype == -1) { datype = get_random_int_in_range(KVP_TYPE_GINT64, KVP_TYPE_FRAME); } if (datype == -2) { datype = get_random_int_in_range(KVP_TYPE_GINT64, KVP_TYPE_FRAME - 1); } if (datype == KVP_TYPE_FRAME && depth >= kvp_max_depth) return NULL; if (datype == KVP_TYPE_GLIST && depth >= kvp_max_depth) return NULL; if (glist_type_excluded (datype)) return NULL; switch(datype) { case KVP_TYPE_GINT64: return kvp_value_new_gint64(get_random_gint64()); break; case KVP_TYPE_DOUBLE: return kvp_value_new_double(get_random_double()); break; case KVP_TYPE_NUMERIC: return kvp_value_new_gnc_numeric(get_random_gnc_numeric()); break; case KVP_TYPE_STRING: { gchar *tmp_str; kvp_value *ret; tmp_str = get_random_string(); if(!tmp_str) { return NULL; } ret = kvp_value_new_string(tmp_str); g_free(tmp_str); return ret; } break; case KVP_TYPE_GUID: { GUID *tmp_guid; kvp_value *ret; tmp_guid = get_random_guid(); ret = kvp_value_new_guid(tmp_guid); g_free(tmp_guid); return ret; } break; case KVP_TYPE_BINARY: { bin_data *tmp_data; kvp_value *ret; tmp_data = get_random_binary_data(); ret = kvp_value_new_binary(tmp_data->data, tmp_data->len); g_free(tmp_data->data); g_free(tmp_data); return ret; } break; case KVP_TYPE_GLIST: return kvp_value_new_glist_nc(get_random_glist_depth (depth + 1)); break; case KVP_TYPE_FRAME: { kvp_frame *tmp_frame; kvp_value *ret; tmp_frame = get_random_kvp_frame_depth(depth + 1); ret = kvp_value_new_frame(tmp_frame); kvp_frame_delete(tmp_frame); return ret; } break; default: return NULL; break; } } kvp_value * get_random_kvp_value(int type) { return get_random_kvp_value_depth (type, 0); } static void set_account_random_string(Account* act, void(*func)(Account *act, const gchar*str)) { gchar *tmp_str = get_random_string(); if(tmp_str) { (func)(act, tmp_str); g_free(tmp_str); } } static void account_add_subaccounts (GNCSession *session, Account *account, int depth) { int num_accounts; if (depth == 0) return; num_accounts = get_random_int_in_range (1, 10); while (num_accounts-- > 0) { Account *sub = get_random_account (session); xaccAccountInsertSubAccount (account, sub); account_add_subaccounts (session, sub, depth - 1); } } static AccountGroup * get_random_group_depth(GNCSession *session, int depth) { AccountGroup *group; int num_accounts; if (depth <= 0) return NULL; group = xaccMallocAccountGroup (session); num_accounts = get_random_int_in_range (1, max_group_accounts); while (num_accounts-- > 0) { Account *account = get_random_account (session); xaccGroupInsertAccount (group, account); account_add_subaccounts (session, account, depth - 1); } return group; } AccountGroup * get_random_group (GNCSession *session) { int depth; depth = get_random_int_in_range (1, max_group_depth); return get_random_group_depth (session, depth); } typedef struct { GUID guid; } TransInfo; static void change_trans_helper (GNCSession *session, Transaction *trans, GList *accounts) { GList *splits; GList *node; Split *split; xaccTransBeginEdit (trans); make_random_changes_to_transaction (session, trans); switch (get_random_int_in_range (0, 3)) { case 0: /* delete some splits, add some more */ do { split = xaccTransGetSplit (trans, 0); xaccSplitDestroy (split); } while (split); add_random_splits (session, trans); /* fall through */ case 1: /* move the splits around */ splits = xaccTransGetSplitList (trans); for (node = splits; node; node = node->next) { Split *split = node->data; Account *account; account = get_random_list_element (accounts); xaccAccountInsertSplit (account, split); } break; case 2: /* destroy the transaction */ xaccTransDestroy (trans); xaccTransCommitEdit (trans); return; default: /* do nothing */ break; } /* mess with the splits */ splits = xaccTransGetSplitList (trans); for (node = splits; node; node = node->next) { Split *split = node->data; if (get_random_boolean ()) make_random_changes_to_split (split); } if (get_random_boolean ()) xaccTransCommitEdit (trans); else xaccTransRollbackEdit (trans); } static gboolean add_trans_helper (Transaction *trans, gpointer data) { TransInfo *ti; GList **list = data; ti = g_new (TransInfo, 1); ti->guid = *xaccTransGetGUID (trans); *list = g_list_prepend (*list, ti); return TRUE; } void make_random_changes_to_group (GNCSession *session, AccountGroup *group) { Account *new_account; Account *account; GList *accounts; GList *transes; GList *splits; GList *node; g_return_if_fail (group && session); accounts = xaccGroupGetSubAccounts (group); /* Add a new account */ new_account = get_random_account (session); if (get_random_boolean ()) xaccGroupInsertAccount (group, new_account); else { account = get_random_list_element (accounts); xaccAccountInsertSubAccount (account, new_account); } g_list_free (accounts); accounts = xaccGroupGetSubAccounts (group); /* Add some new transactions */ add_random_transactions_to_session (session, get_random_int_in_range (1, 6)); /* Mess with the accounts */ for (node = accounts; node; node = node->next) { Account *account = node->data; if (get_random_boolean ()) make_random_changes_to_account (session, account); } /* Mess with the transactions & splits */ transes = NULL; xaccGroupForEachTransaction (group, add_trans_helper, &transes); for (node = transes; node; node = node->next) { TransInfo *ti = node->data; Transaction *trans = xaccTransLookup (&ti->guid, session); if (!trans) continue; change_trans_helper (session, trans, accounts); } for (node = transes; node; node = node->next) { TransInfo *ti = node->data; g_free (ti); } g_list_free (transes); transes = NULL; /* delete an account */ account = get_random_list_element (accounts); splits = xaccAccountGetSplitList (account); splits = g_list_copy (splits); for (node = splits; node; node = node->next) { Split *split = node->data; do { new_account = get_random_list_element (accounts); } while (new_account == account); xaccAccountInsertSplit (new_account, split); } xaccAccountBeginEdit (account); xaccAccountDestroy (account); g_list_free (splits); g_list_free (accounts); /* TODO: move some accounts around */ } Account* get_random_account(GNCSession *session) { Account *ret; int tmp_int; ret = xaccMallocAccount(session); xaccAccountBeginEdit(ret); set_account_random_string(ret, xaccAccountSetName); tmp_int = get_random_int_in_range(BANK, NUM_ACCOUNT_TYPES - 1); xaccAccountSetType(ret, tmp_int); set_account_random_string(ret, xaccAccountSetCode); set_account_random_string(ret, xaccAccountSetDescription); xaccAccountSetCommodity(ret, get_random_commodity(session)); xaccAccountSetSlots_nc(ret, get_random_kvp_frame()); xaccAccountCommitEdit(ret); return ret; } void make_random_changes_to_account (GNCSession *session, Account *account) { int tmp_int; g_return_if_fail (account); xaccAccountBeginEdit (account); set_account_random_string (account, xaccAccountSetName); tmp_int = get_random_int_in_range (BANK, CREDITLINE); xaccAccountSetType (account, tmp_int); set_account_random_string (account, xaccAccountSetCode); set_account_random_string (account, xaccAccountSetDescription); xaccAccountSetCommodity (account, get_random_commodity(session)); xaccAccountSetSlots_nc (account, get_random_kvp_frame()); xaccAccountCommitEdit (account); } static void set_split_random_string(Split *spl, void(*func)(Split *act, const gchar*str)) { gchar *tmp_str = get_random_string(); if(tmp_str) { (func)(spl, tmp_str); g_free(tmp_str); } } static char possible_chars[] = { 'c', 'y', 'f', 'n', 'v' }; Split* get_random_split(GNCSession *session, gnc_numeric num) { Split *ret; gnc_numeric oneVal; ret = xaccMallocSplit(session); set_split_random_string(ret, xaccSplitSetMemo); set_split_random_string(ret, xaccSplitSetAction); xaccSplitSetReconcile(ret, possible_chars[get_random_int_in_range(0, 4)]); xaccSplitSetDateReconciledTS(ret, get_random_timespec()); xaccSplitSetValue(ret, num); xaccSplitSetAmount(ret, num); oneVal = gnc_numeric_create(1,1); xaccSplitSetSharePrice(ret, oneVal); xaccSplitSetSlots_nc(ret, get_random_kvp_frame()); { GUID *ranguid = get_random_guid(); xaccSplitSetAccountGUID(ret, *ranguid); g_free(ranguid); } return ret; } void make_random_changes_to_split (Split *split) { Transaction *trans; g_return_if_fail (split); trans = xaccSplitGetParent (split); xaccTransBeginEdit (trans); set_split_random_string (split, xaccSplitSetMemo); set_split_random_string (split, xaccSplitSetAction); xaccSplitSetReconcile (split, possible_chars[get_random_int_in_range(0, 4)]); xaccSplitSetDateReconciledTS (split, get_random_timespec()); xaccSplitSetSlots_nc (split, get_random_kvp_frame()); /* Don't change share values/prices here, since that would * throw transactions out of balance. Do that in the corresponding * change transaction function. */ xaccTransCommitEdit (trans); } static void set_tran_random_string(Transaction* trn, void(*func)(Transaction *act, const gchar*str)) { gchar *tmp_str = get_random_string(); if(tmp_str) { (func)(trn, tmp_str); g_free(tmp_str); } } static void add_random_splits(GNCSession *session, Transaction *trn) { gnc_numeric num = get_random_gnc_numeric(); xaccTransAppendSplit(trn, get_random_split(session, num)); xaccTransAppendSplit(trn, get_random_split(session, gnc_numeric_neg(num))); } static void trn_add_ran_timespec(Transaction *trn, void (*func)(Transaction*, const Timespec*)) { Timespec *to_set; to_set = get_random_timespec(); func(trn, to_set); g_free(to_set); } Transaction * get_random_transaction_with_currency(GNCSession *session, gnc_commodity *currency) { Transaction* ret; ret = xaccMallocTransaction(session); xaccTransBeginEdit(ret); xaccTransSetCurrency (ret, currency ? currency : get_random_commodity (session)); set_tran_random_string(ret, xaccTransSetNum); trn_add_ran_timespec(ret, xaccTransSetDatePostedTS); trn_add_ran_timespec(ret, xaccTransSetDateEnteredTS); set_tran_random_string(ret, xaccTransSetDescription); xaccTransSetSlots_nc(ret, get_random_kvp_frame()); add_random_splits(session, ret); xaccTransCommitEdit(ret); return ret; } Transaction* get_random_transaction (GNCSession *session) { return get_random_transaction_with_currency (session, NULL); } void make_random_changes_to_transaction (GNCSession *session, Transaction *trans) { GList *list; GList *node; g_return_if_fail (trans && session); xaccTransBeginEdit (trans); xaccTransSetCurrency (trans, get_random_commodity (session)); set_tran_random_string (trans, xaccTransSetNum); trn_add_ran_timespec (trans, xaccTransSetDatePostedTS); trn_add_ran_timespec (trans, xaccTransSetDateEnteredTS); set_tran_random_string (trans, xaccTransSetDescription); xaccTransSetSlots_nc (trans, get_random_kvp_frame()); /* Do split manipulations in higher-level functions */ xaccTransCommitEdit (trans); } static gpointer get_random_list_element (GList *list) { g_return_val_if_fail (list, NULL); return g_list_nth_data (list, get_random_int_in_range (0, g_list_length (list) - 1)); } static gnc_commodity * get_random_commodity_from_table (gnc_commodity_table *table) { GList *namespaces; gnc_commodity *com = NULL; g_return_val_if_fail (table, NULL); namespaces = gnc_commodity_table_get_namespaces (table); do { GList *commodities; char *namespace; namespace = get_random_list_element (namespaces); commodities = gnc_commodity_table_get_commodities (table, namespace); if (!commodities) continue; com = get_random_list_element (commodities); g_list_free (commodities); } while (!com); g_list_free (namespaces); return com; } gnc_commodity* get_random_commodity (GNCSession *session) { gnc_commodity *ret; gchar *name; const gchar *space; gchar *mn; gchar *xcode; int ran_int; gnc_commodity_table *table; table = gnc_book_get_commodity_table (gnc_session_get_book (session)); #if 0 if (table && (gnc_commodity_table_get_size (table) > 0) && get_random_int_in_range (1, 5) < 5) return get_random_commodity_from_table (table); #endif mn = get_random_string(); space = get_random_commodity_namespace(); if (table) { ret = gnc_commodity_table_lookup (table, space, mn); if (ret) { g_free (mn); return ret; } } name = get_random_string(); xcode = get_random_string(); ran_int = get_random_int_in_range(1, 100000); ret = gnc_commodity_new (name, space, mn, xcode, ran_int); g_free(mn); g_free(name); g_free(xcode); if (table) ret = gnc_commodity_table_insert (table, ret); return ret; } static GList * get_random_guids(int max) { GList *guids = NULL; int num_guids; if (max < 1) return NULL; num_guids = get_random_int_in_range (1, max); while (num_guids-- > 0) g_list_prepend (guids, get_random_guid ()); return guids; } static void free_random_guids(GList *guids) { GList *node; for (node = guids; node; node = node->next) g_free (node->data); g_list_free (guids); } static QueryOp get_random_queryop(void) { return get_random_int_in_range (1, QUERY_XOR); } Query * get_random_query(void) { Query *q; Query *temp_q; int num_terms; num_terms = get_random_int_in_range (1, 4); q = xaccMallocQuery (); while (num_terms-- > 0) { pr_type_t pr_type; Timespec *start; Timespec *end; GList *guids; char *string; GUID *guid; pr_type = get_random_int_in_range (1, 20); switch (pr_type) { case PR_ACCOUNT: guids = get_random_guids (10); xaccQueryAddAccountGUIDMatch (q, guids, get_random_int_in_range (1, ACCT_MATCH_NONE), get_random_queryop ()); free_random_guids (guids); break; case PR_ACTION: string = get_random_string (); xaccQueryAddActionMatch (q, string, get_random_boolean (), get_random_boolean (), get_random_queryop ()); g_free (string); break; case PR_AMOUNT: DxaccQueryAddAmountMatch (q, get_random_double (), get_random_int_in_range (1, AMT_SGN_MATCH_DEBIT), get_random_int_in_range (1, AMT_MATCH_EXACTLY), get_random_queryop ()); break; case PR_BALANCE: xaccQueryAddBalanceMatch (q, get_random_int_in_range (1, BALANCE_UNBALANCED), get_random_queryop ()); break; case PR_CLEARED: xaccQueryAddClearedMatch (q, get_random_int_in_range (1, CLEARED_VOIDED), get_random_queryop ()); break; case PR_DATE: start = get_random_timespec (); end = get_random_timespec (); xaccQueryAddDateMatchTS (q, get_random_boolean (), *start, get_random_boolean (), *end, get_random_queryop ()); g_free (start); g_free (end); break; case PR_DESC: string = get_random_string (); xaccQueryAddDescriptionMatch (q, string, get_random_boolean (), get_random_boolean (), get_random_queryop ()); g_free (string); break; case PR_GUID: guid = get_random_guid (); xaccQueryAddGUIDMatch (q, guid, get_random_queryop ()); g_free (guid); break; case PR_MEMO: string = get_random_string (); xaccQueryAddMemoMatch (q, string, get_random_boolean (), get_random_boolean (), get_random_queryop ()); g_free (string); break; case PR_NUM: string = get_random_string (); xaccQueryAddNumberMatch (q, string, get_random_boolean (), get_random_boolean (), get_random_queryop ()); g_free (string); break; case PR_PRICE: DxaccQueryAddSharePriceMatch (q, get_random_double (), get_random_int_in_range (1, AMT_MATCH_EXACTLY), get_random_queryop ()); break; case PR_SHRS: DxaccQueryAddSharesMatch (q, get_random_double (), get_random_int_in_range (1, AMT_MATCH_EXACTLY), get_random_queryop ()); break; case PR_MISC: /* PR_MISC shouldn't be used anyway :) */ default: num_terms++; break; } } xaccQuerySetSortOrder (q, get_random_int_in_range (1, BY_NONE), get_random_int_in_range (1, BY_NONE), get_random_int_in_range (1, BY_NONE)); xaccQuerySetSortIncreasing (q, get_random_boolean (), get_random_boolean (), get_random_boolean ()); xaccQuerySetMaxSplits (q, get_random_int_in_range (-50000, 50000)); return q; } GNCBook * get_random_book (GNCSession *session) { GNCBook *book; book = gnc_book_new (session); gnc_book_set_group (book, get_random_group (session)); /* make_random_pricedb (gnc_book_get_pricedb (book)); */ return book; } GNCSession * get_random_session (void) { GNCSession *session; GNCBook *book; session = gnc_session_new (); book = gnc_session_get_book (session); gnc_book_set_group (book, get_random_group (session)); return session; } void add_random_transactions_to_session (GNCSession *session, gint num_transactions) { gnc_commodity_table *table; GList *accounts; gint num_accounts; GNCBook *book; if (num_transactions <= 0) return; g_return_if_fail (session); book = gnc_session_get_book (session); accounts = xaccGroupGetSubAccounts (gnc_book_get_group (book)); g_return_if_fail (accounts); num_accounts = g_list_length (accounts); table = gnc_book_get_commodity_table (gnc_session_get_book (session)); while (num_transactions--) { gnc_commodity *com; Transaction *trans; Account *account; Split *split; com = get_random_commodity_from_table (table); trans = get_random_transaction_with_currency (session, com); xaccTransBeginEdit (trans); split = xaccTransGetSplit (trans, 0); account = get_random_list_element (accounts); xaccAccountInsertSplit (account, split); split = xaccTransGetSplit (trans, 1); account = get_random_list_element (accounts); xaccAccountInsertSplit (account, split); xaccTransCommitEdit (trans); } g_list_free (accounts); } void make_random_changes_to_book (GNCSession *session, GNCBook *book) { g_return_if_fail (session && book); make_random_changes_to_group (session, gnc_book_get_group (book)); } void make_random_changes_to_session (GNCSession *session) { g_return_if_fail (session); make_random_changes_to_book (session, gnc_session_get_book (session)); }