t_int_hash.cpp

Go to the documentation of this file.
00001 /*****************************************************************************\
00002 *                                                                             *
00003 *  Name   : test_int_hash                                                     *
00004 *  Author : Chris Koeritz                                                     *
00005 *                                                                             *
00006 *  Purpose:                                                                   *
00007 *                                                                             *
00008 *    Tests out hash_table specialization for integers.                        *
00009 *                                                                             *
00010 *******************************************************************************
00011 * Copyright (c) 2001-$now By Author.  This program is free software; you can  *
00012 * redistribute it and/or modify it under the terms of the GNU General Public  *
00013 * License as published by the Free Software Foundation; either version 2 of   *
00014 * the License or (at your option) any later version.  This is online at:      *
00015 *     http://www.fsf.org/copyleft/gpl.html                                    *
00016 * Please send any updates to: fred@gruntose.com                               *
00017 \*****************************************************************************/
00018 
00019 #include <basis/chaos.h>
00020 #include <basis/guards.h>
00021 #include <basis/istring.h>
00022 #include <data_struct/int_hash.cpp>
00023 #include <mechanisms/time_stamp.h>
00024 #include <opsystem/application_shell.h>
00025 #include <loggers/console_logger.h>
00026 #include <loggers/file_logger.h>
00027 #include <data_struct/static_memory_gremlin.h>
00028 #include <textual/string_manipulation.h>
00029 
00030 const int TEST_DURATION = 4 * MINUTE_ms;
00031 //const int TEST_DURATION = 20 * SECOND_ms;
00032 
00033 const int MAX_DEFAULT_BITS = 2;
00034   // we start low since we will rehash occasionally.
00035 
00037 
00038 enum test_actions {
00039   FIRST_TEST = 38,  // place-holder.
00040   ADD = FIRST_TEST,
00041     // adds an item that is probably new.
00042   ADD_ADD,
00043     // adds an item that is probably new, followed by another item under the
00044     // same key id.  this ensures the overwriting gets tested.
00045   ZAP,
00046     // finds an item we know is in the list and whacks it.
00047   ADD_ZAP,
00048     // adds a new item and immediately finds and zaps it.
00049   ZAP_ADD,
00050     // zaps an item that we know about and then adds a new item with the same
00051     // identifier.
00052   FIND,
00053     // locates an item in the list which we know should exist.
00054   ACQUIRE,
00055     // grabs an item out of the list (and tosses it).
00056   FIND_ZAP_ADD,
00057     // finds an item we know should exist, zaps it out of the list, then adds
00058     // a new item with the same id.
00059   ACQUIRE_ADD_ZAP,
00060     // removes an item from the list that we know should be there, adds it back
00061     // in, and then whacks it.
00062   FIND_ADD_FIND,
00063     // find an item with a particular id (one that we know should be in the
00064     // list) and then adds a different item using the same id.  the new item
00065     // is then sought.
00066   RESET,
00067     // tosses all data out of the hash table.  not done very often.
00068   CHECK_SANITY,
00069     // look for any problems or irregularities; print the contents of the list
00070     // if any are found.
00071   REHASH,
00072     // resizes the hash table.
00073   COPY,
00074     // copies a hash table to another hash table.
00075   LAST_TEST = COPY // place-holder; must equal test just prior.
00076 };
00077 
00079 
00080 // a simple object that is used as the contents of the hash_table.
00081 
00082 class data_shuttle
00083 {
00084 public:
00085   int food_bar;
00086   istring snacky_string;
00087   bool hungry;
00088   byte_array chunk;
00089   chaos chao;
00090 
00091   data_shuttle()
00092   : snacky_string(string_manipulation::make_random_name()),
00093     chunk(chao.inclusive(100, 10000)) {}
00094 };
00095 
00097 
00098 class test_int_hash : public application_shell
00099 {
00100 public:
00101   test_int_hash();
00102 
00103   int execute();
00104     // the main startup for the test.
00105 
00106   bool perform_a_test(test_actions test_type);
00107     // carries out the specifics of the "test_type".
00108 
00109   bool pick_a_test();
00110     // randomly picks one of the test types and performs it.
00111 
00112   IMPLEMENT_CLASS_NAME("test_int_hash");
00113 
00114   static bool equivalence_applier(const int &key, data_shuttle &item,
00115           void *dlink);
00116 
00117   static const char *test_name(test_actions test_type);
00118 
00119   // these functions each perform one type of test, which their names indicate.
00120   bool test_add();
00121   bool test_add_add();
00122   bool test_zap();
00123   bool test_add_zap();
00124   bool test_zap_add();
00125   bool test_find();
00126   bool test_acquire();
00127   bool test_find_zap_add();
00128   bool test_acquire_add_zap();
00129   bool test_find_add_find();
00130   bool test_reset();
00131   bool test_check_sanity();
00132   bool test_copy();
00133   bool test_rehash();
00134 
00135 private:
00136   int_set _keys_in_use;  // keys that we think are stored in the table.
00137   int_hash<data_shuttle> _the_table;  // our table under test.
00138   int _hits[LAST_TEST - FIRST_TEST + 1];  // tracks our testing activities.
00139   int _tested;  // simple counter of number of test calls.
00140 };
00141 
00143 
00144 typedef int_hash<data_shuttle> our_hash;  // cleans up somewhat.
00145 
00147 
00148 test_int_hash::test_int_hash()
00149 : application_shell("t_hash_table"),
00150   _the_table(MAX_DEFAULT_BITS),
00151   _tested(0)
00152 {
00153   for (int i = FIRST_TEST; i <= LAST_TEST; i++)
00154     _hits[i - FIRST_TEST] = 0;
00155 }
00156 
00157 int test_int_hash::execute()
00158 {
00159   time_stamp exit_time(TEST_DURATION);
00160   while (time_stamp() < exit_time) pick_a_test();
00161   log(isprintf("did %d tests.\n", _tested));
00162   log("Test Activity:");
00163   for (int i = 0; i < LAST_TEST - FIRST_TEST + 1; i++)
00164     log(istring(istring::SPRINTF, "%d (%s): %d hits", i + FIRST_TEST,
00165         test_name(test_actions(i + FIRST_TEST)), _hits[i]));
00166   log(isprintf("note that test %d will seldom be executed.", RESET));
00167   guards::alert_message("hash_table:: works for those functions tested.");
00168   return 0;
00169 }
00170 
00171 const char *test_int_hash::test_name(test_actions test_type)
00172 {
00173   switch (test_type) {
00174     case ADD: return "ADD";
00175     case ADD_ADD: return "ADD_ADD";
00176     case ZAP: return "ZAP";
00177     case ADD_ZAP: return "ADD_ZAP";
00178     case ZAP_ADD: return "ZAP_ADD";
00179     case FIND: return "FIND";
00180     case ACQUIRE: return "ACQUIRE";
00181     case FIND_ZAP_ADD: return "FIND_ZAP_ADD";
00182     case ACQUIRE_ADD_ZAP: return "ACQUIRE_ADD_ZAP";
00183     case FIND_ADD_FIND: return "FIND_ADD_FIND";
00184     case RESET: return "RESET";
00185     case COPY: return "COPY";
00186     case REHASH: return "REHASH";
00187     case CHECK_SANITY: return "CHECK_SANITY";
00188     default: return "UnknownTest";
00189   }
00190 }
00191 
00192 bool test_int_hash::perform_a_test(test_actions test_type)
00193 {
00194   FUNCDEF("perform_a_test");
00195 
00196 //  log(istring(test_name(test_type)) + " ");
00197 
00198   switch (test_type) {
00199     case ADD: return test_add();
00200     case ADD_ADD: return test_add_add();
00201     case ZAP: return test_zap();
00202     case ADD_ZAP: return test_add_zap();
00203     case ZAP_ADD: return test_zap_add();
00204     case FIND: return test_find();
00205     case ACQUIRE: return test_acquire();
00206     case FIND_ZAP_ADD: return test_find_zap_add();
00207     case ACQUIRE_ADD_ZAP: return test_acquire_add_zap();
00208     case FIND_ADD_FIND: return test_find_add_find();
00209     case RESET: return test_reset();
00210     case COPY: return test_copy();
00211     case REHASH: return test_rehash();
00212     case CHECK_SANITY: return test_check_sanity();
00213     default:
00214       deadly_error(class_name(), func, "missing case has been seen!");
00215       return false;  // never gets here.
00216   }
00217 }
00218 
00219 bool test_int_hash::pick_a_test()
00220 {
00221   _tested++;
00222   return perform_a_test(test_actions(randomizer().inclusive(FIRST_TEST,
00223       LAST_TEST)));
00224 }
00225 
00226 bool test_int_hash::test_add()
00227 {
00228   FUNCDEF("test_add");
00229   _hits[ADD - FIRST_TEST]++;
00230   int random_id = randomizer().inclusive(-MAXINT / 4, MAXINT / 4);
00231   data_shuttle *to_add = new data_shuttle;
00232   to_add->snacky_string = string_manipulation::make_random_name();
00233   to_add->food_bar = random_id;
00234   _the_table.add(random_id, to_add);
00235   if (_keys_in_use.member(random_id))
00236     return true;  // already was there so we replaced.
00237   _keys_in_use.add(random_id);
00238   return true;
00239 }
00240 
00242 
00243 int_hash<data_shuttle> *_hang_on = NIL;
00244   // must be set before calling the apply method.
00245 
00246 bool test_int_hash::equivalence_applier(const int &key, data_shuttle &item,
00247     void *formal(dlink))
00248 {
00249   FUNCDEF("equivalence_applier");
00250   data_shuttle *found = _hang_on->find(key);
00251   if (!found)
00252     deadly_error(test_int_hash::static_class_name(), func, "no equivalent entry was found in second list");
00253 
00254   if (item.food_bar != found->food_bar)
00255     deadly_error(test_int_hash::static_class_name(), func, "food_bar differs");
00256   if (item.snacky_string != found->snacky_string)
00257     deadly_error(test_int_hash::static_class_name(), func, "snacky_string differs");
00258   if (item.hungry != found->hungry)
00259     deadly_error(test_int_hash::static_class_name(), func, "hungry differs");
00260   return true;
00261 }
00262 
00263 bool test_int_hash::test_rehash()
00264 {
00265   FUNCDEF("test_rehash");
00266   // we don't want to rehash too often; it is expensive.
00267   int maybe = randomizer().inclusive(1, 50);
00268   if (maybe < 32) return true;  // not this time.
00269 
00270   _hits[REHASH - FIRST_TEST]++;
00271 
00272   int_hash<data_shuttle> table_copy(_the_table.max_bits());
00273 
00274 //log("copying table...");
00275   copy_hash_table(table_copy, _the_table);
00276     // make a copy of the table.
00277   
00278 //log("rehashing table...");
00279   _the_table.rehash(randomizer().inclusive(1, 20));
00280 //hmmm: need test of non-existent dehash function that reduces max_bits.
00281 
00282 //log("comparing table...");
00283   _hang_on = &_the_table;
00284   table_copy.apply(equivalence_applier, NIL);
00285 //log("done copy and compare.");
00286 
00287   return true;
00288 }
00289 
00290 bool test_int_hash::test_copy()
00291 {
00292   FUNCDEF("test_copy");
00293   // we don't want to copy too often.  it's a heavy operation.
00294   int maybe = randomizer().inclusive(1, 50);
00295   if (maybe > 16) return true;  // not this time.
00296 
00297   _hits[COPY - FIRST_TEST]++;
00298 
00299   int_hash<data_shuttle> table_copy(MAX_DEFAULT_BITS);
00300 
00301 //log("copying table...");
00302   copy_hash_table(table_copy, _the_table);
00303     // make a copy of the table.
00304   
00305 //log("comparing table...");
00306   _hang_on = &_the_table;
00307   table_copy.apply(equivalence_applier, NIL);
00308 //log("done copy and compare.");
00309 
00310   return true;
00311 }
00312 
00314 
00315 bool test_int_hash::test_add_add()
00316 {
00317   FUNCDEF("test_add_add");
00318   _hits[ADD_ADD - FIRST_TEST]++;
00319   int random_id = randomizer().inclusive(-MAXINT / 4, MAXINT / 4);
00320   data_shuttle *to_add = new data_shuttle;
00321   to_add->snacky_string = string_manipulation::make_random_name();
00322   to_add->food_bar = random_id;
00323   _the_table.add(random_id, to_add);
00324   // add the new key if it's really new.
00325   if (!_keys_in_use.member(random_id)) _keys_in_use.add(random_id);
00326 
00327   // second add on same id.
00328   data_shuttle *next_add = new data_shuttle;
00329   next_add->snacky_string = string_manipulation::make_random_name();
00330   next_add->food_bar = random_id;
00331   if (_the_table.add(random_id, next_add) != our_hash::EXISTING)
00332     deadly_error(class_name(), func, "second add said first failed!");
00333 
00334   return true;
00335 }
00336 
00337 bool test_int_hash::test_zap()
00338 {
00339   FUNCDEF("test_zap");
00340   int maybe = randomizer().inclusive(1, 1000);
00341   if (maybe > 50) return true;
00342   if (!_keys_in_use.elements()) return false;  // can't do it yet.
00343   _hits[ZAP - FIRST_TEST]++;
00344   int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1);
00345   int dead_key = _keys_in_use[rand_indy];
00346   _keys_in_use.remove(dead_key);  // remove the record of that key.
00347   if (!_the_table.zap(dead_key))
00348     deadly_error(class_name(), func, "key should have been present but wasn't");
00349   return true;
00350 }
00351 
00352 bool test_int_hash::test_add_zap()
00353 {
00354   FUNCDEF("test_add_zap");
00355   // add.
00356   _hits[ADD_ZAP - FIRST_TEST]++;
00357   int random_id = randomizer().inclusive(-MAXINT / 4, MAXINT / 4);
00358   data_shuttle *to_add = new data_shuttle;
00359   to_add->snacky_string = string_manipulation::make_random_name();
00360   to_add->food_bar = random_id;
00361   _the_table.add(random_id, to_add);
00362   // zap.
00363   if (!_the_table.zap(random_id))
00364     deadly_error(class_name(), func, "key should have been present but wasn't");
00365   return true;
00366 }
00367 
00368 bool test_int_hash::test_zap_add()
00369 {
00370   FUNCDEF("test_zap_add");
00371   if (!_keys_in_use.elements()) return false;  // can't do it yet.
00372   _hits[ZAP_ADD - FIRST_TEST]++;
00373   int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1);
00374   // in the end, the key list state won't be changed unless the test fails.
00375   int dead_key = _keys_in_use[rand_indy];
00376   if (!_the_table.zap(dead_key))
00377     deadly_error(class_name(), func, "key should have been there but was not!");
00378 
00379   data_shuttle *to_add = new data_shuttle;
00380   to_add->snacky_string = string_manipulation::make_random_name();
00381   to_add->food_bar = dead_key;
00382   outcome ret = _the_table.add(dead_key, to_add);
00383   if (ret != our_hash::IS_NEW)
00384     deadly_error(class_name(), func, "key was already present somehow!");
00385   return true;
00386 }
00387 
00388 int functional_return(int to_pass) {
00389   return to_pass;
00390 }
00391 
00392 bool test_int_hash::test_find()
00393 {
00394   FUNCDEF("test_find");
00395   if (!_keys_in_use.elements()) return false;  // can't do it yet.
00396   _hits[FIND - FIRST_TEST]++;
00397   int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1);
00398   int find_key = _keys_in_use[rand_indy];
00399   data_shuttle *found = NIL;
00400   if (!_the_table.find(functional_return(find_key), found))
00401     deadly_error(class_name(), func, "key should have been there but was not!");
00402   if (!_the_table.find(functional_return(find_key)))
00403     deadly_error(class_name(), func, "find2: key should have been there but was not!");
00404   if (!found)
00405     deadly_error(class_name(), func, "key was found but contents were NIL!");
00406   if (found->food_bar != find_key)
00407     deadly_error(class_name(), func, "stored key differed from real key!");
00408   if (!found->snacky_string.length())
00409     deadly_error(class_name(), func, "stored string had zero length!");
00410   return true;
00411 }
00412 
00413 bool test_int_hash::test_acquire()
00414 {
00415   FUNCDEF("test_acquire");
00416   int maybe = randomizer().inclusive(1, 1000);
00417   if (maybe > 150) return true;
00418   if (!_keys_in_use.elements()) return false;  // can't do it yet.
00419   _hits[ACQUIRE - FIRST_TEST]++;
00420   int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1);
00421   int find_key = _keys_in_use[rand_indy];
00422   _keys_in_use.remove(find_key);  // remove the record of that key.
00423   data_shuttle *found = _the_table.acquire(find_key);
00424   if (!found)
00425     deadly_error(class_name(), func, "key should have been there but was not!");
00426   if (found->food_bar != find_key)
00427     deadly_error(class_name(), func, "stored key differed from real key!");
00428   if (!found->snacky_string.length())
00429     deadly_error(class_name(), func, "stored string had zero length!");
00430   WHACK(found);
00431   found = _the_table.acquire(find_key);
00432   if (found)
00433     deadly_error(class_name(), func, "key was supposedly there after zap!");
00434   return true;
00435 }
00436 
00437 bool test_int_hash::test_find_zap_add()
00438 {
00439   FUNCDEF("test_find_zap_add");
00440   // find.
00441   if (!_keys_in_use.elements()) return false;  // can't do it yet.
00442   _hits[FIND_ZAP_ADD - FIRST_TEST]++;
00443   int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1);
00444   // this is another key list invariant function, if it works.
00445   int find_key = _keys_in_use[rand_indy];
00446   data_shuttle *found = NIL;
00447   if (!_the_table.find(find_key, found))
00448     deadly_error(class_name(), func, "key should have been there but was not!");
00449   if (!_the_table.find(find_key))
00450     deadly_error(class_name(), func, "find2: key should have been there but was not!");
00451   if (!found)
00452     deadly_error(class_name(), func, "key was found but contents were NIL!");
00453   if (found->food_bar != find_key)
00454     deadly_error(class_name(), func, "stored key differed from real key!");
00455   if (!found->snacky_string.length())
00456     deadly_error(class_name(), func, "stored string had zero length!");
00457   // zap.
00458   if (!_the_table.zap(find_key))
00459     deadly_error(class_name(), func, "could not zap the item we had found!");
00460   // add.
00461   data_shuttle *to_add = new data_shuttle;
00462   to_add->snacky_string = string_manipulation::make_random_name();
00463   to_add->food_bar = find_key;
00464   if (_the_table.add(find_key, to_add) != our_hash::IS_NEW)
00465     deadly_error(class_name(), func, "the item we zapped was still there!");
00466   return true;
00467 }
00468 
00469 bool test_int_hash::test_reset()
00470 {
00471   FUNCDEF("test_reset");
00472   int maybe = randomizer().inclusive(1, 1000);
00473   // this is hardly ever hit, but it loses all contents too often otherwise.
00474   if ( (maybe > 372) || (maybe < 368) ) return true;
00475 
00476   // we hit the big time; we will reset now.
00477   _hits[RESET - FIRST_TEST]++;
00478   _the_table.reset();
00479   for (int i = _keys_in_use.elements() - 1; i >= 0; i--) {
00480     int dead_key = _keys_in_use[i];
00481     if (_the_table.acquire(dead_key))
00482       deadly_error(class_name(), func, "after reset, we found an item!");
00483     _keys_in_use.remove(dead_key);
00484   }
00485   return true;
00486 }
00487 
00488 //hmmm: implement these tests!
00489 
00490 bool test_int_hash::test_acquire_add_zap()
00491 {
00492   _hits[ACQUIRE_ADD_ZAP - FIRST_TEST]++;
00493 return false;
00494 }
00495 
00496 bool test_int_hash::test_find_add_find()
00497 {
00498   _hits[FIND_ADD_FIND - FIRST_TEST]++;
00499 return false;
00500 }
00501 
00502 bool test_int_hash::test_check_sanity()
00503 {
00504   _hits[CHECK_SANITY - FIRST_TEST]++;
00505 return false;
00506 }
00507 
00509 
00510 HOOPLE_MAIN(test_int_hash, )
00511 

Generated on Fri Nov 21 04:30:08 2008 for HOOPLE Libraries by  doxygen 1.5.1