t_hash_table.cpp

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

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