+/*
+ * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
#include "squid.h"
#include "base/CharacterSet.h"
-#include "Mem.h"
-#include "SBuf.h"
-#include "SBufFindTest.h"
-#include "SBufStream.h"
-#include "SquidString.h"
-#include "testSBuf.h"
+#include "HttpReply.h"
+#include "sbuf/Algorithms.h"
+#include "sbuf/SBuf.h"
+#include "sbuf/Stream.h"
+#include "tests/SBufFindTest.h"
+#include "tests/testSBuf.h"
+#include "unitTestMain.h"
#include <iostream>
#include <stdexcept>
+#include <unordered_map>
CPPUNIT_TEST_SUITE_REGISTRATION( testSBuf );
#include "event.h"
#include "MemObject.h"
void
-eventAdd(const char *name, EVH * func, void *arg, double when, int, bool cbdata)
+eventAdd(const char *, EVH *, void *, double, int, bool)
{}
int64_t
MemObject::endOffset() const
/* end of stubs */
// test string
-static char fox[]="The quick brown fox jumped over the lazy dog";
-static char fox1[]="The quick brown fox ";
-static char fox2[]="jumped over the lazy dog";
+static const char fox[] = "The quick brown fox jumped over the lazy dog";
+static const char fox1[] = "The quick brown fox ";
+static const char fox2[] = "jumped over the lazy dog";
// TEST: globals variables (default/empty and with contents) are
// created outside and before any unit tests and memory subsystem
// TEST: copy-construct NULL string (implicit destructor non-crash test)
{
- SBuf s1(NULL);
+ SBuf s1(nullptr);
CPPUNIT_ASSERT_EQUAL(0U,s1.length());
CPPUNIT_ASSERT_EQUAL(SBuf(""),s1);
CPPUNIT_ASSERT_EQUAL(empty_sbuf,s1);
CPPUNIT_ASSERT_EQUAL(s4,s3);
}
- // TEST: go via SquidString adapters.
- {
- String str(fox);
- SBuf s1(str);
- CPPUNIT_ASSERT_EQUAL(literal,s1);
- }
-
// TEST: go via std::string adapter.
{
std::string str(fox);
void
testSBuf::testAppendSBuf()
{
- SBuf s1(fox1),s2(fox2);
- s1.append(s2);
- CPPUNIT_ASSERT_EQUAL(s1,literal);
+ const SBuf appendix(fox1);
+ const char * const rawAppendix = appendix.rawContent();
+
+ // check whether the optimization that prevents copying when append()ing to
+ // default-constructed SBuf actually works
+ SBuf s0;
+ s0.append(appendix);
+ CPPUNIT_ASSERT_EQUAL(s0.rawContent(), appendix.rawContent());
+ CPPUNIT_ASSERT_EQUAL(s0, appendix);
+
+ // paranoid: check that the above code can actually detect copies
+ SBuf s1(fox1);
+ s1.append(appendix);
+ CPPUNIT_ASSERT(s1.rawContent() != appendix.rawContent());
+ CPPUNIT_ASSERT(s1 != appendix);
+ CPPUNIT_ASSERT_EQUAL(rawAppendix, appendix.rawContent());
}
void
return 0;
}
+static void
+testComparisonStdFull(const char *left, const char *right)
+{
+ if (sign(strcmp(left, right)) != sign(SBuf(left).cmp(SBuf(right))))
+ std::cerr << std::endl << " cmp(SBuf) npos " << left << " ?= " << right << std::endl;
+ CPPUNIT_ASSERT_EQUAL(sign(strcmp(left, right)), sign(SBuf(left).cmp(SBuf(right))));
+
+ if (sign(strcmp(left, right)) != sign(SBuf(left).cmp(right)))
+ std::cerr << std::endl << " cmp(char*) npos " << left << " ?= " << right << std::endl;
+ CPPUNIT_ASSERT_EQUAL(sign(strcmp(left, right)), sign(SBuf(left).cmp(right)));
+
+ if (sign(strcasecmp(left, right)) != sign(SBuf(left).caseCmp(SBuf(right))))
+ std::cerr << std::endl << " caseCmp(SBuf) npos " << left << " ?= " << right << std::endl;
+ CPPUNIT_ASSERT_EQUAL(sign(strcasecmp(left, right)), sign(SBuf(left).caseCmp(SBuf(right))));
+
+ if (sign(strcasecmp(left, right)) != sign(SBuf(left).caseCmp(right)))
+ std::cerr << std::endl << " caseCmp(char*) npos " << left << " ?= " << right << std::endl;
+ CPPUNIT_ASSERT_EQUAL(sign(strcasecmp(left, right)), sign(SBuf(left).caseCmp(right)));
+}
+
+static void
+testComparisonStdN(const char *left, const char *right, const size_t n)
+{
+ if (sign(strncmp(left, right, n)) != sign(SBuf(left).cmp(SBuf(right), n)))
+ std::cerr << std::endl << " cmp(SBuf) " << n << ' ' << left << " ?= " << right << std::endl;
+ CPPUNIT_ASSERT_EQUAL(sign(strncmp(left, right, n)), sign(SBuf(left).cmp(SBuf(right), n)));
+
+ if (sign(strncmp(left, right, n)) != sign(SBuf(left).cmp(right, n)))
+ std::cerr << std::endl << " cmp(char*) " << n << ' ' << SBuf(left) << " ?= " << right << std::endl;
+ CPPUNIT_ASSERT_EQUAL(sign(strncmp(left, right, n)), sign(SBuf(left).cmp(right, n)));
+
+ if (sign(strncasecmp(left, right, n)) != sign(SBuf(left).caseCmp(SBuf(right), n)))
+ std::cerr << std::endl << " caseCmp(SBuf) " << n << ' ' << left << " ?= " << right << std::endl;
+ CPPUNIT_ASSERT_EQUAL(sign(strncasecmp(left, right, n)), sign(SBuf(left).caseCmp(SBuf(right), n)));
+
+ if (sign(strncasecmp(left, right, n)) != sign(SBuf(left).caseCmp(right, n)))
+ std::cerr << std::endl << " caseCmp(char*) " << n << ' ' << SBuf(left) << " ?= " << right << std::endl;
+ CPPUNIT_ASSERT_EQUAL(sign(strncasecmp(left, right, n)), sign(SBuf(left).caseCmp(right, n)));
+}
+
+static void
+testComparisonStdOneWay(const char *left, const char *right)
+{
+ testComparisonStdFull(left, right);
+ const size_t maxN = 2 + min(strlen(left), strlen(right));
+ for (size_t n = 0; n <= maxN; ++n) {
+ testComparisonStdN(left, right, n);
+ }
+}
+
+static void
+testComparisonStd(const char *s1, const char *s2)
+{
+ testComparisonStdOneWay(s1, s2);
+ testComparisonStdOneWay(s2, s1);
+}
+
void
testSBuf::testComparisons()
{
CPPUNIT_ASSERT_EQUAL(0,s1.caseCmp(s2,3));
CPPUNIT_ASSERT_EQUAL(0,s1.caseCmp(s2,2));
CPPUNIT_ASSERT_EQUAL(0,s1.cmp(s2,2));
+
+ testComparisonStd("foo", "fooz");
+ testComparisonStd("foo", "foo");
+ testComparisonStd("foo", "f");
+ testComparisonStd("foo", "bar");
+
+ testComparisonStd("foo", "FOOZ");
+ testComparisonStd("foo", "FOO");
+ testComparisonStd("foo", "F");
+
+ testComparisonStdOneWay("", "");
+
+ // rare case C-string input matching SBuf with N>strlen(s)
+ {
+ char *right = xstrdup("foo34567890123456789012345678");
+ SBuf left("fooZYXWVUTSRQPONMLKJIHGFEDCBA");
+ // is 3 bytes in length. NEVER more.
+ right[3] = '\0';
+ left.setAt(3, '\0');
+
+ // pick another spot to truncate at if something goes horribly wrong.
+ right[14] = '\0';
+ left.setAt(14, '\0');
+
+ const SBuf::size_type maxN = 20 + min(left.length(), static_cast<SBuf::size_type>(strlen(right)));
+ for (SBuf::size_type n = 0; n <= maxN; ++n) {
+ if (sign(strncmp(left.rawContent(), right, n)) != sign(left.cmp(right, n)) )
+ std::cerr << std::endl << " cmp(char*) " << n << ' ' << left << " ?= " << right;
+ CPPUNIT_ASSERT_EQUAL(sign(strncmp(left.rawContent(), right, n)), sign(left.cmp(right, n)));
+ if (sign(strncasecmp(left.rawContent(), right, n)) != sign(left.caseCmp(right, n)))
+ std::cerr << std::endl << " caseCmp(char*) " << n << ' ' << left << " ?= " << right;
+ CPPUNIT_ASSERT_EQUAL(sign(strncasecmp(left.rawContent(), right, n)), sign(left.caseCmp(right, n)));
+ }
+ xfree(right);
+ }
}
void
{
SBuf s1(literal);
SBuf s2(fox1);
- SBuf::size_type sz=s2.length();
- char *rb=s2.rawSpace(strlen(fox2)+1);
+ char *rb=s2.rawAppendStart(strlen(fox2)+1);
strcpy(rb,fox2);
- s2.forceSize(sz+strlen(fox2));
+ s2.rawAppendFinish(rb, strlen(fox2));
CPPUNIT_ASSERT_EQUAL(s1,s2);
}
const char *alphabet="abcdefghijklmnopqrstuvwxyz";
SBuf a(alphabet);
std::string s(alphabet); // TODO
- { //regular chopping
+ { //regular chopping
SBuf b(a);
b.chop(3,3);
SBuf ref("def");
CPPUNIT_ASSERT_EQUAL(ref,b);
}
- { // chop at end
+ { // chop at end
SBuf b(a);
b.chop(b.length()-3);
SBuf ref("xyz");
CPPUNIT_ASSERT_EQUAL(ref,b);
}
- { // chop at beginning
+ { // chop at beginning
SBuf b(a);
b.chop(0,3);
SBuf ref("abc");
CPPUNIT_ASSERT_EQUAL(ref,b);
}
- { // chop to zero length
+ { // chop to zero length
SBuf b(a);
b.chop(5,0);
SBuf ref("");
CPPUNIT_ASSERT_EQUAL(ref,b);
}
- { // chop beyond end (at npos)
+ { // chop beyond end (at npos)
SBuf b(a);
b.chop(SBuf::npos,4);
SBuf ref("");
CPPUNIT_ASSERT_EQUAL(ref,b);
}
- { // chop beyond end
+ { // chop beyond end
SBuf b(a);
b.chop(b.length()+2,4);
SBuf ref("");
CPPUNIT_ASSERT_EQUAL(ref,b);
}
- { // null-chop
+ { // null-chop
SBuf b(a);
b.chop(0,b.length());
SBuf ref(a);
CPPUNIT_ASSERT_EQUAL(ref,b);
}
- { // overflow chopped area
+ { // overflow chopped area
SBuf b(a);
b.chop(b.length()-3,b.length());
SBuf ref("xyz");
CPPUNIT_ASSERT_EQUAL(strlen(fox),(size_t)s.length());
}
-void
-testSBuf::testScanf()
-{
- SBuf s1;
- char s[128];
- int i;
- float f;
- int rv;
- s1.assign("string , 123 , 123.50");
- rv=s1.scanf("%s , %d , %f",s,&i,&f);
- CPPUNIT_ASSERT_EQUAL(3,rv);
- CPPUNIT_ASSERT_EQUAL(0,strcmp(s,"string"));
- CPPUNIT_ASSERT_EQUAL(123,i);
- CPPUNIT_ASSERT_EQUAL(static_cast<float>(123.5),f);
-}
-
void
testSBuf::testCopy()
{
void
testSBuf::testStringOps()
{
- SBuf sng(literal.toLower()),
- ref("the quick brown fox jumped over the lazy dog");
+ SBuf sng(ToLower(literal)),
+ ref("the quick brown fox jumped over the lazy dog");
CPPUNIT_ASSERT_EQUAL(ref,sng);
sng=literal;
CPPUNIT_ASSERT_EQUAL(0,sng.compare(ref,caseInsensitive));
CPPUNIT_ASSERT_EQUAL(ref,match);
}
+void
+testSBuf::testReserve()
+{
+ SBufReservationRequirements requirements;
+ // use unusual numbers to ensure we do not hit a lucky boundary situation
+ requirements.minSpace = 10;
+ requirements.idealSpace = 82;
+ requirements.maxCapacity = 259;
+ requirements.allowShared = true;
+
+ // for each possible starting buffer length within the capacity
+ for (SBuf::size_type startLength = 0; startLength <= requirements.maxCapacity; ++startLength) {
+ std::cerr << ".";
+ SBuf b;
+ b.reserveCapacity(startLength);
+ CPPUNIT_ASSERT_EQUAL(b.length(), static_cast<unsigned int>(0));
+ CPPUNIT_ASSERT_EQUAL(b.spaceSize(), startLength);
+
+ // check that it never grows outside capacity.
+ // do 5 excess cycles to check that.
+ for (SBuf::size_type filled = 0; filled < requirements.maxCapacity +5; ++filled) {
+ CPPUNIT_ASSERT_EQUAL(b.length(), min(filled, requirements.maxCapacity));
+ auto x = b.reserve(requirements);
+ // the amount of space advertized must not cause users to exceed capacity
+ CPPUNIT_ASSERT(x <= requirements.maxCapacity - filled);
+ CPPUNIT_ASSERT(b.spaceSize() <= requirements.maxCapacity - filled);
+ // the total size of buffer must not cause users to exceed capacity
+ CPPUNIT_ASSERT(b.length() + b.spaceSize() <= requirements.maxCapacity);
+ if (x > 0)
+ b.append('X');
+ }
+ }
+
+ // the minimal space requirement should overwrite idealSpace preferences
+ requirements.minSpace = 10;
+ for (const int delta: {-1,0,+1}) {
+ requirements.idealSpace = requirements.minSpace + delta;
+ SBuf buffer;
+ buffer.reserve(requirements);
+ CPPUNIT_ASSERT(buffer.spaceSize() >= requirements.minSpace);
+ }
+
+ // TODO: Decide whether to encapsulate the (nearly identical) code of the
+ // gap-related test cases below into a function, obscuring each case logic a
+ // little, but facilitating new test cases (and removing code duplication).
+
+ { // reserveSpace() uses the trailing space before the front gap
+ SBuf buffer(fox);
+
+ // assure there is some trailing space
+ buffer.reserveSpace(1);
+ CPPUNIT_ASSERT(buffer.spaceSize() > 0);
+
+ // create a leading gap and (weak-)check that it was created
+ const auto gap = 1U; // the smallest gap may be the most challenging
+ CPPUNIT_ASSERT(gap < buffer.length());
+ const void *gapEnd = buffer.rawContent() + gap;
+ buffer.consume(gap);
+ CPPUNIT_ASSERT_EQUAL(gapEnd, static_cast<const void*>(buffer.rawContent()));
+
+ const auto before = SBuf::GetStats();
+ const auto beforeSpaceSize = buffer.spaceSize();
+ const void * const beforePosition = buffer.rawContent();
+ buffer.reserveSpace(beforeSpaceSize);
+ const auto after = SBuf::GetStats();
+ const void * const afterPosition = buffer.rawContent();
+ CPPUNIT_ASSERT_EQUAL(before.cowAvoided + 1, after.cowAvoided);
+ CPPUNIT_ASSERT_EQUAL(before.cowShift, after.cowShift);
+ CPPUNIT_ASSERT_EQUAL(before.cowJustAlloc, after.cowJustAlloc);
+ CPPUNIT_ASSERT_EQUAL(before.cowAllocCopy, after.cowAllocCopy);
+ CPPUNIT_ASSERT_EQUAL(beforeSpaceSize, buffer.spaceSize());
+ CPPUNIT_ASSERT_EQUAL(beforePosition, afterPosition);
+ CPPUNIT_ASSERT(strcmp(fox + gap, buffer.c_str()) == 0);
+ }
+
+ { // reserveSpace() uses the front gap when the trailing space is not enough
+ SBuf buffer(fox);
+
+ // assure there is some trailing space to keep the test case challenging
+ buffer.reserveSpace(1);
+ CPPUNIT_ASSERT(buffer.spaceSize() > 0);
+ const void * const initialStorage = buffer.rawContent();
+
+ // create a leading gap and (weak-)check that it was created
+ const auto gap = 1U; // the smallest gap may be the most challenging
+ CPPUNIT_ASSERT(gap < buffer.length());
+ const void *gapEnd = buffer.rawContent() + gap;
+ buffer.consume(gap);
+ CPPUNIT_ASSERT_EQUAL(gapEnd, static_cast<const void*>(buffer.rawContent()));
+
+ const auto before = SBuf::GetStats();
+ const auto beforeSpaceSize = buffer.spaceSize();
+ buffer.reserveSpace(beforeSpaceSize + gap); // force (entire) gap use
+ const auto after = SBuf::GetStats();
+ const void * const afterStorage = buffer.rawContent();
+ CPPUNIT_ASSERT_EQUAL(before.cowAvoided, after.cowAvoided);
+ CPPUNIT_ASSERT_EQUAL(before.cowShift + 1, after.cowShift);
+ CPPUNIT_ASSERT_EQUAL(before.cowJustAlloc, after.cowJustAlloc);
+ CPPUNIT_ASSERT_EQUAL(before.cowAllocCopy, after.cowAllocCopy);
+ CPPUNIT_ASSERT_EQUAL(initialStorage, afterStorage);
+ CPPUNIT_ASSERT(beforeSpaceSize + gap <= buffer.spaceSize());
+ CPPUNIT_ASSERT(strcmp(fox + gap, buffer.c_str()) == 0);
+ }
+
+ { // reserveSpace() uses the entire front gap when using the front gap
+ SBuf buffer(fox);
+
+ // assure there is some trailing space to keep the test case challenging
+ buffer.reserveSpace(1);
+ CPPUNIT_ASSERT(buffer.spaceSize() > 0);
+ const void * const initialStorage = buffer.rawContent();
+
+ // create a leading gap and (weak-)check that it was created
+ const auto gap = 2U; // the smallest extra gap may be the most challenging
+ CPPUNIT_ASSERT(gap < buffer.length());
+ const void *gapEnd = buffer.rawContent() + gap;
+ buffer.consume(gap);
+ CPPUNIT_ASSERT_EQUAL(gapEnd, static_cast<const void*>(buffer.rawContent()));
+
+ const auto before = SBuf::GetStats();
+ const auto beforeSpaceSize = buffer.spaceSize();
+ buffer.reserveSpace(beforeSpaceSize + 1); // force (minimal) gap use
+ const auto after = SBuf::GetStats();
+ const void * const afterStorage = buffer.rawContent();
+ CPPUNIT_ASSERT_EQUAL(before.cowAvoided, after.cowAvoided);
+ CPPUNIT_ASSERT_EQUAL(before.cowShift + 1, after.cowShift);
+ CPPUNIT_ASSERT_EQUAL(before.cowJustAlloc, after.cowJustAlloc);
+ CPPUNIT_ASSERT_EQUAL(before.cowAllocCopy, after.cowAllocCopy);
+ CPPUNIT_ASSERT_EQUAL(initialStorage, afterStorage);
+ CPPUNIT_ASSERT(beforeSpaceSize + gap <= buffer.spaceSize());
+ CPPUNIT_ASSERT(strcmp(fox + gap, buffer.c_str()) == 0);
+ }
+}
+
void
testSBuf::testStartsWith()
{
// case-insensitive checks
CPPUNIT_ASSERT(literal.startsWith(casebuf,caseInsensitive));
- casebuf=SBuf(fox1).toUpper();
+ casebuf=ToUpper(SBuf(fox1));
CPPUNIT_ASSERT(literal.startsWith(casebuf,caseInsensitive));
CPPUNIT_ASSERT(literal.startsWith(SBuf(fox1),caseInsensitive));
casebuf = "tha quick";
SBuf sb(alphabet);
CPPUNIT_ASSERT_EQUAL(astr,sb.toStdString());
}
+
+void
+testSBuf::testIterators()
+{
+ SBuf text("foo"), text2("foo");
+ CPPUNIT_ASSERT(text.begin() == text.begin());
+ CPPUNIT_ASSERT(text.begin() != text.end());
+ CPPUNIT_ASSERT(text.begin() != text2.begin());
+ {
+ auto i = text.begin();
+ auto e = text.end();
+ CPPUNIT_ASSERT_EQUAL('f', *i);
+ CPPUNIT_ASSERT(i != e);
+ ++i;
+ CPPUNIT_ASSERT_EQUAL('o', *i);
+ CPPUNIT_ASSERT(i != e);
+ ++i;
+ CPPUNIT_ASSERT_EQUAL('o', *i);
+ CPPUNIT_ASSERT(i != e);
+ ++i;
+ CPPUNIT_ASSERT(i == e);
+ }
+ {
+ auto i = text.rbegin();
+ auto e = text.rend();
+ CPPUNIT_ASSERT_EQUAL('o', *i);
+ CPPUNIT_ASSERT(i != e);
+ ++i;
+ CPPUNIT_ASSERT_EQUAL('o', *i);
+ CPPUNIT_ASSERT(i != e);
+ ++i;
+ CPPUNIT_ASSERT_EQUAL('f', *i);
+ CPPUNIT_ASSERT(i != e);
+ ++i;
+ CPPUNIT_ASSERT(i == e);
+ }
+}
+
+void
+testSBuf::testSBufHash()
+{
+ // same SBuf must have same hash
+ auto hasher=std::hash<SBuf>();
+ CPPUNIT_ASSERT_EQUAL(hasher(literal),hasher(literal));
+
+ // same content must have same hash
+ CPPUNIT_ASSERT_EQUAL(hasher(literal),hasher(SBuf(fox)));
+ CPPUNIT_ASSERT_EQUAL(hasher(SBuf(fox)),hasher(SBuf(fox)));
+
+ //different content should have different hash
+ CPPUNIT_ASSERT(hasher(SBuf(fox)) != hasher(SBuf(fox1)));
+
+ {
+ std::unordered_map<SBuf, int> um;
+ um[SBuf("one")] = 1;
+ um[SBuf("two")] = 2;
+
+ auto i = um.find(SBuf("one"));
+ CPPUNIT_ASSERT(i != um.end());
+ CPPUNIT_ASSERT(i->second == 1);
+
+ i = um.find(SBuf("eleventy"));
+ CPPUNIT_ASSERT(i == um.end());
+ }
+}
+