From e97c06cf0c1b7bc81d787b54d98b8870da22166c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 10 Oct 2012 10:45:37 -0400 Subject: On lluuidhashmap_tut<1> failure, save/reload data in temp file. Generating new random data on every test run makes it impossible to debug a test failure. While in general we do want to generate random data to thoroughly exercise the generator logic, if every new run generates new data, the only thing we can do about an observed failure is shrug and ignore it. Add logic to save data on failure, with corresponding logic to notice and reload from a previously-generated save file. In case of a merge collision, this version SUPERCEDES my previous efforts with this file. (My other changes may still be in a backed-up merge request.) It is okay to resolve collisions in favor of this version. --- indra/test/lluuidhashmap_tut.cpp | 141 +++++++++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 22 deletions(-) (limited to 'indra/test') diff --git a/indra/test/lluuidhashmap_tut.cpp b/indra/test/lluuidhashmap_tut.cpp index 0544e832ce..408bc3faf1 100644 --- a/indra/test/lluuidhashmap_tut.cpp +++ b/indra/test/lluuidhashmap_tut.cpp @@ -30,6 +30,10 @@ #include "linden_common.h" #include "lluuidhashmap.h" #include "llsdserialize.h" +#include "lldir.h" +#include "stringize.h" +#include +#include namespace tut { @@ -79,40 +83,133 @@ namespace tut template<> template<> void hash_index_object_t::test<1>() { - LLUUIDHashMap hashTable(UUIDTableEntry::uuidEq, UUIDTableEntry()); + set_test_name("stress test"); + // As of 2012-10-10, I (nat) have observed sporadic failures of this + // test: "set/get did not work." The trouble is that since test data + // are randomly generated with every run, it is impossible to debug a + // test failure. One is left with the uneasy suspicion that + // LLUUID::generate() can sometimes produce duplicates even within the + // moderately small number requested here. Since rerunning the test + // generally allows it to pass, it's too easy to shrug and forget it. + // The following code is intended to support reproducing such test + // failures. The idea is that, on test failure, we save the generated + // data to a canonical filename in a temp directory. Then on every + // subsequent run, we check for that filename. If it exists, we reload + // that specific data rather than generating fresh data -- which + // should presumably reproduce the same test failure. But we inform + // the user that to resume normal (random) test runs, s/he need only + // delete that file. And since it's in a temp directory, sooner or + // later the system will clean it up anyway. + const char* tempvar = "TEMP"; + const char* tempdir = getenv(tempvar); // Windows convention + if (! tempdir) + { + tempvar = "TMPDIR"; + tempdir = getenv(tempvar); // Mac convention + } + if (! tempdir) + { + // reset tempvar to the first var we check; it's just a + // recommendation + tempvar = "TEMP"; + tempdir = "/tmp"; // Posix in general + } + std::string savefile(gDirUtilp->add(tempdir, "lluuidhashmap_tut.save.txt")); const int numElementsToCheck = 32*256*32; - std::vector idList(numElementsToCheck); - int i; - - for (i = 0; i < numElementsToCheck; i++) + std::vector idList; + if (gDirUtilp->fileExists(savefile)) { - LLUUID id; - id.generate(); - UUIDTableEntry entry(id, i); - hashTable.set(id, entry); - idList[i] = id; + // We have saved data from a previous failed run. Reload that data. + std::ifstream inf(savefile.c_str()); + if (! inf.is_open()) + { + fail(STRINGIZE("Although save file '" << savefile << "' exists, it cannot be opened")); + } + std::string item; + while (std::getline(inf, item)) + { + idList.push_back(LLUUID(item)); + } + std::cout << "Reloaded " << idList.size() << " items from '" << savefile << "'"; + if (idList.size() != numElementsToCheck) + { + std::cout << " (expected " << numElementsToCheck << ")"; + } + std::cout << " -- delete this file to generate new data" << std::endl; + } + else + { + // savefile does not exist (normal case): regenerate idList from + // scratch. + for (int i = 0; i < numElementsToCheck; ++i) + { + LLUUID id; + id.generate(); + idList.push_back(id); + } } - for (i = 0; i < numElementsToCheck; i++) + LLUUIDHashMap hashTable(UUIDTableEntry::uuidEq, UUIDTableEntry()); + int i; + + for (i = 0; i < idList.size(); ++i) { - LLUUID idToCheck = idList[i]; - UUIDTableEntry entryToCheck = hashTable.get(idToCheck); - ensure("set/get did not work", entryToCheck.getID() == idToCheck && entryToCheck.getValue() == (size_t)i); + UUIDTableEntry entry(idList[i], i); + hashTable.set(idList[i], entry); } - for (i = 0; i < numElementsToCheck; i++) + try { - LLUUID idToCheck = idList[i]; - if (i % 2 != 0) + for (i = 0; i < idList.size(); i++) { - hashTable.remove(idToCheck); + LLUUID idToCheck = idList[i]; + UUIDTableEntry entryToCheck = hashTable.get(idToCheck); + ensure_equals(STRINGIZE("set/get ID (entry " << i << ")").c_str(), + entryToCheck.getID(), idToCheck); + ensure_equals(STRINGIZE("set/get value (ID " << idToCheck << ")").c_str(), + entryToCheck.getValue(), (size_t)i); } - } - for (i = 0; i < numElementsToCheck; i++) + for (i = 0; i < idList.size(); i++) + { + LLUUID idToCheck = idList[i]; + if (i % 2 != 0) + { + hashTable.remove(idToCheck); + } + } + + for (i = 0; i < idList.size(); i++) + { + LLUUID idToCheck = idList[i]; + ensure("remove or check did not work", (i % 2 == 0 && hashTable.check(idToCheck)) || (i % 2 != 0 && !hashTable.check(idToCheck))); + } + } + catch (const failure&) { - LLUUID idToCheck = idList[i]; - ensure("remove or check did not work", (i % 2 == 0 && hashTable.check(idToCheck)) || (i % 2 != 0 && !hashTable.check(idToCheck))); + // One of the above tests failed. Try to save idList to repro with + // a later run. + std::ofstream outf(savefile.c_str()); + if (! outf.is_open()) + { + // Sigh, don't use fail() here because we want to preserve + // the original test failure. + std::cout << "Cannot open file '" << savefile + << "' to save data -- check and fix " << tempvar << std::endl; + } + else + { + // outf.is_open() + for (int i = 0; i < idList.size(); ++i) + { + outf << idList[i] << std::endl; + } + std::cout << "Saved " << idList.size() << " entries to '" << savefile + << "' -- rerun test to debug with these" << std::endl; + } + // re-raise the same exception -- we WANT this test failure to + // be reported! We just needed to save the data on the way out. + throw; } } -- cgit v1.2.3