Author: Valentin Rakush Author: Alex Rousskov Generate a [configurable] large number of SBuf::*find() test cases using random strings (and std::string as the source of correct outcomes). This is a Measurement Factory project. === modified file 'src/Makefile.am' --- src/Makefile.am 2013-02-24 18:39:40 +0000 +++ src/Makefile.am 2013-02-25 17:13:45 +0000 @@ -3801,40 +3801,42 @@ $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(COMPAT_LIB) \ $(SQUID_CPPUNIT_LIBS) \ $(SQUID_CPPUNIT_LA) \ $(SSLLIB) \ $(KRB5LIBS) \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testURL_LDFLAGS = $(LIBADD_DL) tests_testURL_DEPENDENCIES = \ $(REPL_OBJS) \ $(SQUID_CPPUNIT_LA) tests_testSBuf_SOURCES= \ tests/testSBuf.h \ tests/testSBuf.cc \ tests/testMain.cc \ + tests/SBufFindTest.h \ + tests/SBufFindTest.cc \ $(SBUF_SOURCE) \ SBufStream.h \ time.cc \ mem.cc \ tests/stub_debug.cc \ tests/stub_fatal.cc \ tests/stub_HelperChildConfig.cc \ tests/stub_cache_cf.cc \ tests/stub_cache_manager.cc \ tests/stub_store.cc \ tests/stub_store_stats.cc \ tests/stub_tools.cc \ SquidString.h \ String.cc \ wordlist.cc \ MemBuf.cc nodist_tests_testSBuf_SOURCES=$(TESTSOURCES) tests_testSBuf_LDFLAGS = $(LIBADD_DL) tests_testSBuf_LDADD=\ $(SQUID_CPPUNIT_LIBS) \ === added file 'src/tests/SBufFindTest.cc' --- src/tests/SBufFindTest.cc 1970-01-01 00:00:00 +0000 +++ src/tests/SBufFindTest.cc 2013-02-25 22:02:27 +0000 @@ -0,0 +1,418 @@ +#include "squid.h" +#include "SBufFindTest.h" +#include +#include +#include + + +/* TODO: The whole SBufFindTest class is currently implemented as a single + CppUnit test case (because we do not want to register and report every one + of the thousands of generated test cases). Is there a better way to + integrate with CppUnit? + */ + + +SBufFindTest::SBufFindTest(): + caseLimit(std::numeric_limits::max()), + errorLimit(std::numeric_limits::max()), + randomSeed(1), + hushSimilar(true), + maxHayLength(40), + thePos(0), + thePlacement(placeEof), + theStringPos(0), + theBareNeedlePos(0), + caseCount(0), + errorCount(0), + reportCount(0) +{ +} + +void +SBufFindTest::run() +{ + srandom(randomSeed); + + for (SBuf::size_type hayLen = 0; hayLen <= maxHayLength; nextLen(hayLen, maxHayLength)) { + const SBuf cleanHay = RandomSBuf(hayLen); + + const SBuf::size_type maxNeedleLen = hayLen + 10; + for (SBuf::size_type needleLen = 0; needleLen <= maxNeedleLen; nextLen(needleLen, maxNeedleLen)) { + theSBufNeedle = RandomSBuf(needleLen); + + for (int i = 0; i < Placement::placeEof; i++) { + thePlacement = Placement(i); + placeNeedle(cleanHay); + + const SBuf::size_type maxArg = + max(theSBufHay.length(), theSBufNeedle.length()) + 10; + for (thePos = 0; thePos <= maxArg; nextLen(thePos, maxArg)) + testAllMethods(); + + // also test the special npos value + thePos = SBuf::npos; + testAllMethods(); + } + } + } + + if (errorCount > 0) { + std::cerr << "Generated SBuf test cases: " << caseCount << std::endl; + std::cerr << "\tfailed cases: " << errorCount << std::endl; + std::cerr << "\treported cases: " << reportCount << std::endl; + std::cerr << "Asserting because some cases failed..." << std::endl; + CPPUNIT_ASSERT(!SBufFindTest::errorCount); + } +} + +/// tests SBuf::find(string needle) +void +SBufFindTest::testFindDefs() { + theFindString = theBareNeedlePos = theStringHay.find(theStringNeedle); + theFindSBuf = theSBufHay.find(theSBufNeedle); + checkResults("find"); +} + +/// tests SBuf::rfind(string needle) +void +SBufFindTest::testRFindDefs() { + theFindString = theBareNeedlePos = theStringHay.rfind(theStringNeedle); + theFindSBuf = theSBufHay.rfind(theSBufNeedle); + checkResults("rfind"); +} + +/// tests SBuf::find(string needle, pos) +void +SBufFindTest::testFind() { + theFindString = theStringHay.find(theStringNeedle, thePos); + theBareNeedlePos = theStringHay.find(theStringNeedle); + theFindSBuf = theSBufHay.find(theSBufNeedle, thePos); + checkResults("find"); +} + +/// tests SBuf::rfind(string needle, pos) +void +SBufFindTest::testRFind() { + theFindString = theStringHay.rfind(theStringNeedle, thePos); + theBareNeedlePos = theStringHay.rfind(theStringNeedle); + theFindSBuf = theSBufHay.rfind(theSBufNeedle, thePos); + checkResults("rfind"); +} + +/// tests SBuf::find(char needle) +void +SBufFindTest::testFindCharDefs() { + const char c = theStringNeedle[0]; + theFindString = theBareNeedlePos = theStringHay.find(c); + theFindSBuf = theSBufHay.find(c); + checkResults("find"); +} + +/// tests SBuf::find(char needle, pos) +void +SBufFindTest::testFindChar() { + const char c = theStringNeedle[0]; + theFindString = theStringHay.find(c, thePos); + theBareNeedlePos = theStringHay.find(c); + theFindSBuf = theSBufHay.find(c, thePos); + checkResults("find"); +} + +/// tests SBuf::rfind(char needle) +void +SBufFindTest::testRFindCharDefs() { + const char c = theStringNeedle[0]; + theFindString = theBareNeedlePos = theStringHay.rfind(c); + theFindSBuf = theSBufHay.rfind(c); + checkResults("rfind"); +} + +/// tests SBuf::rfind(char needle, pos) +void +SBufFindTest::testRFindChar() { + const char c = theStringNeedle[0]; + theFindString = theStringHay.rfind(c, thePos); + theBareNeedlePos = theStringHay.rfind(c); + theFindSBuf = theSBufHay.rfind(c, thePos); + checkResults("rfind"); +} + +/// whether the last SBuf and std::string find() results are the same +bool +SBufFindTest::resultsMatch() const { + // this method is needed because SBuf and std::string use different + // size_types (and npos values); comparing the result values directly + // would lead to bugs + + if (theFindString == std::string::npos && theFindSBuf == SBuf::npos) + return true; // both npos + + if (theFindSBuf < 0) // should not happen, treat as error + return false; + + // now safe to cast a non-negative SBuf result + return theFindString == static_cast(theFindSBuf); +} + +/// called at the end of test case to update state, detect and report failures +void +SBufFindTest::checkResults(const char *method) { + ++caseCount; + if (!resultsMatch()) + handleFailure(method); +} + +/// helper function to convert "printable" Type to std::string +template +inline std::string +AnyToString(const Type &value) +{ + std::stringstream sbuf; + sbuf << value; + return sbuf.str(); +} + +/// helper function to convert SBuf position to a human-friendly string +inline std::string +PosToString(const SBuf::size_type pos) +{ + return pos == SBuf::npos ? std::string("npos") : AnyToString(pos); +} + +/// helper function to convert std::string position to a human-friendly string +inline std::string +PosToString(const std::string::size_type pos) +{ + return pos == std::string::npos ? std::string("npos") : AnyToString(pos); +} + +/// tests each supported SBuf::*find() method using generated hay, needle, pos +void +SBufFindTest::testAllMethods() { + theStringHay = std::string(theSBufHay.rawContent(), theSBufHay.length()); + theStringNeedle = std::string(theSBufNeedle.rawContent(), theSBufNeedle.length()); + theBareNeedlePos = std::string::npos; + const std::string reportPos = PosToString(thePos); + + // always test string search + { + theReportQuote = '"'; + theReportNeedle = theStringNeedle; + + theReportPos = ""; + testFindDefs(); + testRFindDefs(); + + theReportPos = reportPos; + testFind(); + testRFind(); + } + + // if possible, test char search + if (!theStringNeedle.empty()) { + theReportQuote = '\''; + theReportNeedle = theStringNeedle[0]; + + theReportPos = ""; + testFindCharDefs(); + testRFindCharDefs(); + + theReportPos = reportPos; + testFindChar(); + testRFindChar(); + } +} + +/// helper function to format a length-based key (part of case category string) +inline std::string +lengthKey(const std::string &str) +{ + if (str.length() == 0) + return "0"; + if (str.length() == 1) + return "1"; + return "N"; +} + +/// formats position key (part of the case category string) +std::string +SBufFindTest::posKey() const +{ + // the search position does not matter if needle is not in hay + if (theBareNeedlePos == std::string::npos) + return std::string(); + + if (thePos == SBuf::npos) + return ",npos"; + + if (thePos < 0) + return ",posN"; // negative + + // we know Pos is not negative or special; avoid signed/unsigned warnings + const std::string::size_type pos = + static_cast(thePos); + + if (pos < theBareNeedlePos) + return ",posL"; // to the Left of the needle + if (pos == theBareNeedlePos) + return ",posB"; // Beginning of the needle + if (pos < theBareNeedlePos + theStringNeedle.length()) + return ",posM"; // in the Middle of the needle + if (pos == theBareNeedlePos + theStringNeedle.length()) + return ",posE"; // at the End of the needle + if (pos < theStringHay.length()) + return ",posR"; // to the Right of the needle + return ",posP"; // past the hay +} + +/// formats placement key (part of the case category string) +std::string +SBufFindTest::placementKey() const +{ + // Ignore thePlacement because theBareNeedlePos covers it better: we may + // try to place the needle somewhere, but hay limits the actual placement. + + // the placent does not matter if needle is not in hay + if (theBareNeedlePos == std::string::npos) + return std::string(); + + if (theBareNeedlePos == 0) + return "@B"; // at the beggining of the hay string + if (theBareNeedlePos == theStringHay.length()-theStringNeedle.length()) + return "@E"; // at the end of the hay string + return "@M"; // in the "middle" of the hay string +} + +/// called when a test case fails; counts and possibly reports the failure +void +SBufFindTest::handleFailure(const char *method) { + // line break after "........." printed for previous tests + if (!errorCount) + std::cerr << std::endl; + + ++errorCount; + + if (errorCount > errorLimit) { + std::cerr << "Will stop generating SBuf test cases because the " << + "number of failed ones is over the limit: " << errorCount << + " (after " << caseCount << " test cases)" << std::endl; + CPPUNIT_ASSERT(errorCount <= errorLimit); + /* NOTREACHED */ + } + + // format test case category; category allows us to hush failure reports + // for already seen categories with failed cases (to reduce output noise) + std::string category = "hay" + lengthKey(theStringHay) + + "." + method + '('; + if (theReportQuote == '"') + category += "needle" + lengthKey(theStringNeedle); + else + category += "char"; + category += placementKey(); + category += posKey(); + category += ')'; + + if (hushSimilar) { + if (failedCats.find(category) != failedCats.end()) + return; // do not report another similar test case failure + failedCats.insert(category); + } + + std::string reportPos = theReportPos; + if (!reportPos.empty()) + reportPos = ", " + reportPos; + + std::cerr << "case" << caseCount << ": " << + "SBuf(\"" << theStringHay << "\")." << method << + "(" << theReportQuote << theReportNeedle << theReportQuote << + reportPos << ") returns " << PosToString(theFindSBuf) << + " instead of " << PosToString(theFindString) << + std::endl << + " std::string(\"" << theStringHay << "\")." << method << + "(" << theReportQuote << theReportNeedle << theReportQuote << + reportPos << ") returns " << PosToString(theFindString) << + std::endl << + " category: " << category << std::endl; + + ++reportCount; +} + +/// generates a random string of the specified length +SBuf +SBufFindTest::RandomSBuf(const int length) { + static const char characters[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklomnpqrstuvwxyz"; + // sizeof() counts the terminating zero at the end of characters + // TODO: add \0 character (needs reporting adjustments to print it as \0) + static const size_t charCount = sizeof(characters)-1; + + char buf[length]; + for (int i = 0; i < length; ++i) { + const unsigned int pos = random() % charCount; + assert(pos < sizeof(characters)); + assert(characters[pos] > 32); + buf[i] = characters[random() % charCount]; + } + + return SBuf(buf, 0, length); +} + +/// increments len to quickly cover [0, max] range, slowing down in risky areas +/// jumps to max+1 if caseLimit is reached +void +SBufFindTest::nextLen(int &len, const int max) { + assert(len <= max); + + if (caseCount >= caseLimit) + len = max+1; // avoid future test cases + else if (len <= 10) + ++len; // move slowly at the beginning of the [0,max] range + else if (len >= max - 10) + ++len; // move slowly at the end of the [0,max] range + else { + // move fast in the middle of the [0,max] range + len += len/10 + 1; + + // but do not overshoot the interesting area at the end of the range + if (len > max - 10) + len = max - 10; + } +} + +/// Places the needle into the hay using cleanHay as a starting point. +void +SBufFindTest::placeNeedle(const SBuf &cleanHay) { + // For simplicity, we do not overwrite clean hay characters but use them as + // needle suffix and/or prefix. Should not matter since hay length varies? + + // TODO: support two needles per hay (explicitly) + // TODO: better handle cases where clean hay already contains needle + switch (thePlacement) + { + case placeBeginning: + theSBufHay.assign(theSBufNeedle).append(cleanHay); + break; + + case placeMiddle: + { + const SBuf firstHalf = cleanHay.substr(0, cleanHay.length()/2); + const SBuf secondHalf = cleanHay.substr(cleanHay.length()/2); + theSBufHay.assign(firstHalf).append(theSBufNeedle).append(secondHalf); + break; + } + + case placeEnd: + theSBufHay.assign(cleanHay).append(theSBufNeedle); + break; + + case placeNowhere: + theSBufHay.assign(cleanHay); + break; + + case placeEof: + assert(false); // should not happen + break; + } +} === added file 'src/tests/SBufFindTest.h' --- src/tests/SBufFindTest.h 1970-01-01 00:00:00 +0000 +++ src/tests/SBufFindTest.h 2013-02-25 22:02:43 +0000 @@ -0,0 +1,83 @@ +#ifndef SQUID_SRC_TEST_SBUFFINDTEST_H +#define SQUID_SRC_TEST_SBUFFINDTEST_H + +#include "SBuf.h" + +#if HAVE_STRING +#include +#endif + + +/// Generates and executes a [configurable] large number of SBuf::*find() +/// test cases using random strings. Reports detected failures. +class SBufFindTest +{ +public: + SBufFindTest(); + + void run(); ///< generates and executes cases using configuration params + + /* test configuration parameters; can be optionally set before run() */ + int caseLimit; ///< approximate caseCount limit + int errorLimit; ///< errorCount limit + unsigned int randomSeed; ///< pseudo-random sequence choice + /// whether to report only one failed test case per "category" + bool hushSimilar; + /// approximate maximum generated hay string length + SBuf::size_type maxHayLength; + +protected: + /// Supported algorithms for placing needle in the hay. + typedef enum { placeBeginning, placeMiddle, placeEnd, placeNowhere, + placeEof } Placement; // placeLast marker must terminate + + static SBuf RandomSBuf(const int length); + void nextLen(int &len, const int max); + void placeNeedle(const SBuf &cleanHay); + + void testAllMethods(); + void testFindDefs(); + void testFind(); + void testRFindDefs(); + void testRFind(); + void testFindCharDefs(); + void testFindChar(); + void testRFindCharDefs(); + void testRFindChar(); + + std::string posKey() const; + std::string placementKey() const; + + bool resultsMatch() const; + void checkResults(const char *method); + void handleFailure(const char *method); + +private: + /* test case parameters */ + SBuf theSBufHay; ///< the string to be searched + SBuf theSBufNeedle; ///< the string to be found + SBuf::size_type thePos; ///< search position limit + Placement thePlacement; ///< where in the hay the needle is placed + std::string::size_type theStringPos; ///< thePos converted to std::string::size_type + std::string theStringHay; ///< theHay converted to std::string + std::string theStringNeedle; ///< theNeedle converted to std::string + + /// needle pos w/o thePos restrictions; used for case categorization + std::string::size_type theBareNeedlePos; + + /* test case results */ + std::string::size_type theFindString; + SBuf::size_type theFindSBuf; + std::string theReportFunc; + std::string theReportNeedle; + std::string theReportPos; + char theReportQuote; + + /* test progress indicators */ + int caseCount; ///< cases executed so far + int errorCount; ///< total number of failed test cases so far + int reportCount; ///< total number of test cases reported so far + std::set failedCats; ///< reported failed categories +}; + +#endif === modified file 'src/tests/testSBuf.cc' --- src/tests/testSBuf.cc 2013-02-24 21:04:30 +0000 +++ src/tests/testSBuf.cc 2013-02-25 17:43:43 +0000 @@ -1,29 +1,30 @@ #include "squid.h" #include "Mem.h" #include "SBuf.h" #include "SBufList.h" #include "SBufStream.h" #include "SBufTokenizer.h" #include "SBufUtil.h" #include "SquidString.h" #include "testSBuf.h" +#include "SBufFindTest.h" #include #include CPPUNIT_TEST_SUITE_REGISTRATION( testSBuf ); /* let this test link sanely */ #include "event.h" #include "MemObject.h" void eventAdd(const char *name, EVH * func, void *arg, double when, int, bool cbdata) {} int64_t MemObject::endOffset() const { return 0; } /* end of stubs */ // test string static char fox[]="The quick brown fox jumped over the lazy dog"; static char fox1[]="The quick brown fox "; @@ -560,20 +561,26 @@ { SBuf haystack(literal); SBuf::size_type idx; // not found idx=haystack.find_first_of(SBuf("ADHRWYP")); CPPUNIT_ASSERT(idx==SBuf::npos); // found at beginning idx=haystack.find_first_of(SBuf("THANDF")); CPPUNIT_ASSERT_EQUAL(0,idx); //found at end of haystack idx=haystack.find_first_of(SBuf("QWERYVg")); CPPUNIT_ASSERT_EQUAL(haystack.length()-1,idx); //found in the middle of haystack idx=haystack.find_first_of(SBuf("QWERqYV")); CPPUNIT_ASSERT_EQUAL(4,idx); } + +void testSBuf::testAutoFind() +{ + SBufFindTest test; + test.run(); +} === modified file 'src/tests/testSBuf.h' --- src/tests/testSBuf.h 2012-12-16 22:01:40 +0000 +++ src/tests/testSBuf.h 2013-02-24 18:21:33 +0000 @@ -33,40 +33,41 @@ CPPUNIT_TEST( testConsume ); CPPUNIT_TEST( testRawContent ); //CPPUNIT_TEST( testRawSpace ); CPPUNIT_TEST( testChop ); CPPUNIT_TEST( testChomp ); CPPUNIT_TEST( testSubstr ); CPPUNIT_TEST( testFindChar ); CPPUNIT_TEST( testFindSBuf ); CPPUNIT_TEST( testRFindChar ); CPPUNIT_TEST( testRFindSBuf ); CPPUNIT_TEST( testFindFirstOf ); CPPUNIT_TEST( testPrintf ); CPPUNIT_TEST( testScanf ); CPPUNIT_TEST( testCopy ); CPPUNIT_TEST( testSBufTokenizer ); CPPUNIT_TEST( testStringOps ); CPPUNIT_TEST( testGrow ); CPPUNIT_TEST( testSBufList ); CPPUNIT_TEST( testBaseName ); CPPUNIT_TEST( testSBufStream ); + CPPUNIT_TEST( testAutoFind ); // CPPUNIT_TEST( testDumpStats ); //fake test, to print alloc stats CPPUNIT_TEST_SUITE_END(); protected: void commonInit(); void testSBufConstructDestruct(); void testSBufConstructDestructAfterMemInit(); void testEqualityTest(); void testAppendSBuf(); void testAppendCString(); void testAppendStdString(); void testAppendf(); void testPrintf(); void testScanf(); void testSubscriptOp(); void testSubscriptOpFail(); void testDumpStats(); void testComparisons(); void testConsume(); @@ -74,23 +75,24 @@ void testRawSpace(); void testChop(); void testChomp(); void testSubstr(); void testTailCopy(); void testSBufLength(); void testFindChar(); void testFindSBuf(); void testRFindChar(); void testRFindSBuf(); void testSearchFail(); void testCopy(); void testSBufTokenizer(); void testStringOps(); void testGrow(); void testStartsWith(); void testSBufList(); void testBaseName(); void testSBufStream(); void testFindFirstOf(); + void testAutoFind(); }; #endif