From: Francesco Chemolli Date: Fri, 29 Mar 2013 16:50:25 +0000 (+0100) Subject: Added SBuf-related files X-Git-Tag: SQUID_3_5_0_1~612^2~22 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=412da4279f75d9fa63cf58a7d2eac63e61d35d78;p=thirdparty%2Fsquid.git Added SBuf-related files --- diff --git a/src/MemBlob.cc b/src/MemBlob.cc index 8cb0c8044e..4acdb569a8 100644 --- a/src/MemBlob.cc +++ b/src/MemBlob.cc @@ -46,6 +46,17 @@ InstanceIdDefinitions(MemBlob, "blob"); MemBlobStats::MemBlobStats(): alloc(0), live(0), append(0), liveBytes(0) {} +MemBlobStats& +MemBlobStats::operator += (const MemBlobStats& s) +{ + alloc+=s.alloc; + live+=s.live; + append+=s.append; + liveBytes+=s.liveBytes; + + return *this; +} + std::ostream& MemBlobStats::dump(std::ostream &os) const { diff --git a/src/MemBlob.h b/src/MemBlob.h index 7fe3b99990..0dcca19523 100644 --- a/src/MemBlob.h +++ b/src/MemBlob.h @@ -46,6 +46,8 @@ public: /// dumps class-wide statistics std::ostream& dump(std::ostream& os) const; + MemBlobStats& operator += (const MemBlobStats&); + public: uint64_t alloc; ///< number of MemBlob instances created so far uint64_t live; ///< number of MemBlob instances currently alive diff --git a/src/OutOfBoundsException.h b/src/OutOfBoundsException.h new file mode 100644 index 0000000000..44fd4b08e9 --- /dev/null +++ b/src/OutOfBoundsException.h @@ -0,0 +1,22 @@ +#ifndef _SQUID_SRC_OUTOFBOUNDSEXCEPTION_H +#define _SQUID_SRC_OUTOFBOUNDSEXCEPTION_H + +#include "base/TextException.h" +#include "SBuf.h" + +/** + * Exception raised when the user is going out of bounds when accessing + * a char within the SBuf + */ +class OutOfBoundsException : public TextException +{ +public: + OutOfBoundsException(const SBuf &buf, SBuf::size_type &pos, const char *aFileName = 0, int aLineNo = -1); + virtual ~OutOfBoundsException() throw(); + +protected: + SBuf _buf; + SBuf::size_type _pos; +}; + +#endif /* _SQUID_SRC_OUTOFBOUNDSEXCEPTION_H */ diff --git a/src/SBuf.cc b/src/SBuf.cc new file mode 100644 index 0000000000..7ed1341cc6 --- /dev/null +++ b/src/SBuf.cc @@ -0,0 +1,850 @@ +/* + * SBuf.cc (C) 2008 Francesco Chemolli + * + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + */ + +#include "squid.h" +#include "base/RefCount.h" +#include "Debug.h" +#include "OutOfBoundsException.h" +#include "SBuf.h" +#include "SBufExceptions.h" +#include "util.h" + +#if HAVE_STRING_H +#include +#endif + +#if HAVE_SSTREAM +#include +#endif + +#if HAVE_IOSTREAM +#include +#endif + +#ifdef VA_COPY +#undef VA_COPY +#endif +#if defined HAVE_VA_COPY +#define VA_COPY va_copy +#elif defined HAVE___VA_COPY +#define VA_COPY __va_copy +#endif + +InstanceIdDefinitions(SBuf, "SBuf"); + +SBufStats SBuf::stats; + +SBufStats::SBufStats() + : alloc(0), allocCopy(0), allocFromString(0), allocFromCString(0), + assignFast(0), clear(0), append(0), toStream(0), setChar(0), + getChar(0), compareSlow(0), compareFast(0), copyOut(0), + rawAccess(0), chop(0), trim(0), find(0), scanf(0), + caseChange(0), cowFast(0), cowSlow(0), live(0) +{} + +SBufStats& +SBufStats::operator +=(const SBufStats& ss) +{ + alloc += ss.alloc; + allocCopy += ss.allocCopy; + allocFromString += ss.allocFromString; + allocFromCString += ss.allocFromCString; + assignFast += ss.assignFast; + clear += ss.clear; + append += ss.append; + toStream += ss.toStream; + setChar += ss.setChar; + getChar += ss.getChar; + compareSlow += ss.compareSlow; + compareFast += ss.compareFast; + copyOut += ss.copyOut; + rawAccess += ss.rawAccess; + chop += ss.chop; + trim += ss.trim; + find += ss.find; + scanf += ss.scanf; + caseChange += ss.caseChange; + cowFast += ss.cowFast; + cowSlow += ss.cowSlow; + live += ss.live; + + return *this; +} + +SBuf::SBuf() + : store_(GetStorePrototype()), off_(0), len_(0) +{ + debugs(24, 8, id << " created"); + ++stats.alloc; + ++stats.live; +} + +SBuf::SBuf(const SBuf &S) + : store_(S.store_), off_(S.off_), len_(S.len_) +{ + debugs(24, 8, id << " created from id " << S.id); + ++stats.alloc; + ++stats.allocCopy; + ++stats.live; +} + +SBuf::SBuf(const String &S) + : store_(GetStorePrototype()), off_(0), len_(0) +{ + debugs(24, 8, id << " created from string"); + assign(S.rawBuf(), 0, S.size()); + ++stats.alloc; + ++stats.allocFromString; + ++stats.live; +} + +SBuf::SBuf(const char *S, size_type pos, size_type n) + : store_(GetStorePrototype()), off_(0), len_(0) +{ + append(S,pos,n); //bounds checked in append() + ++stats.alloc; + ++stats.allocFromCString; + ++stats.live; +} + +SBuf::~SBuf() +{ + debugs(24, 8, id << " destructed"); + --stats.live; +} + +SBuf& +SBuf::assign(const SBuf &S) +{ + debugs(24, 7, "assigning " << id << " from " << S.id); + if (&S == this) //assignment to self. Noop. + return *this; + ++stats.assignFast; + store_ = S.store_; + off_ = S.off_; + len_ = S.len_; + return *this; +} + +SBuf& +SBuf::assign(const char *S, size_type pos, size_type n) +{ + debugs(24, 6, id << " from c-string, pos=" << pos << "n=" << n << ")"); + clear(); + return append(S, pos, n); //bounds checked in append() +} + +void +SBuf::reserveCapacity(size_type minCapacity) +{ + Must(0 <= minCapacity && minCapacity <= maxSize); + reserveSpace(minCapacity-length()); +} + +void +SBuf::reserveSpace(size_type minSpace) +{ + Must(0 <= minSpace && minSpace <= maxSize); + debugs(24, 7, "reserving " << minSpace << " for " << id); + // we're not concerned about RefCounts here, + // the store knows the last-used portion. If + // it's available, we're effectively claiming ownership + // of it. If it's not, we need to go away (realloc) + if (store_->canAppend(off_+len_, minSpace)) { + debugs(24, 7, "not growing"); + return; + } + // TODO: we may try to memmove before realloc'ing in order to avoid + // one allocation operation, if we're the sole owners of a MemBlob. + // Maybe some heuristic on off_ and length()? + reAlloc(estimateCapacity(minSpace+length())); +} + +void +SBuf::clear() +{ +#if 0 + //enabling this code path, the store will be freed and reinitialized + store_ = GetStorePrototype(); //uncomment to actually free storage upon clear() +#else + //enabling this code path, we try to release the store without deallocating it. + // will be lazily reallocated if needed. + if (store_->LockCount() == 1) + store_->clear(); +#endif + len_ = 0; + off_ = 0; + ++stats.clear; +} + +SBuf& +SBuf::append(const SBuf &S) +{ + return append(S.buf(), 0, S.length()); +} + +SBuf& +SBuf::append(const char * S, size_type pos, size_type n) +{ + Must(pos == npos || pos >= 0); + Must(n == npos || n >= 0); + + if (S == NULL) + return *this; + if (n == npos) + n = strlen(S)-pos; + + debugs(24, 7, "from c-string to id " << id); + + reserveSpace(n); //called method also checks n <= maxSize() + const char *actual_start = S+pos; + store_->append(actual_start, n); + len_ += n; + ++stats.append; + return *this; +} + +SBuf& +SBuf::append(const std::string &str, SBuf::size_type pos, SBuf::size_type n) +{ + return append(str.data(), pos, n); //bounds checked in append() +} + +SBuf& +SBuf::assign(const std::string &str, size_type pos, size_type n) +{ + clear(); + return append(str, pos, n); //bounds checked in append() +} + +SBuf& +SBuf::Printf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + clear(); + vappendf(fmt, args); + va_end(args); + return *this; +} + +SBuf& +SBuf::appendf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vappendf(fmt, args); + va_end(args); + return *this; +} + +SBuf& +SBuf::vappendf(const char *fmt, va_list vargs) +{ +#ifdef VA_COPY + va_list ap; +#endif + int sz = 0; + + Must(fmt != NULL); + + //we can assume that we'll need to append at least strlen(fmt) bytes, + //times 1.2 for instance... + reserveSpace(strlen(fmt)*2); + + while (length() <= maxSize) { +#ifdef VA_COPY + /* Fix of bug 753r. The value of vargs is undefined + * after vsnprintf() returns. Make a copy of vargs + * in case we loop around and call vsnprintf() again. + */ + VA_COPY(ap, vargs); + sz = vsnprintf(bufEnd(), store_->spaceSize(), fmt, ap); + va_end(ap); +#else /* VA_COPY */ + sz = vsnprintf(bufEnd(), store_->spaceSize(), fmt, vargs); +#endif /* VA_COPY*/ + /* check for possible overflow */ + /* snprintf on Linux returns -1 on overflows */ + /* snprintf on FreeBSD returns at least free_space on overflows */ + + if (sz < 0 || sz >= (int)store_->spaceSize()) + reserveSpace(sz*2); // TODO: tune heuristics + else + break; + } + + len_ += sz; + // TODO: this does NOT belong here, but to class-init or autoconf + /* on Linux and FreeBSD, '\0' is not counted in return value */ + /* on XXX it might be counted */ + /* check that '\0' is appended and not counted */ + + if (operator[](len_-1) == 0) { + --sz; + --len_; + } + + store_->size += sz; + ++stats.append; + + return *this; +} + +std::ostream& +SBuf::print(std::ostream &os) const +{ + os.write(buf(), length()); + ++stats.toStream; + return os; +} + +std::ostream& +SBuf::dump(std::ostream &os) const +{ + os << id + << ": "; + store_->dump(os); + os << ",offset:" << off_ + << ",len:" << len_ + << ") : '"; + print(os); + os << std::endl; + return os; +} + +void +SBuf::setAt(SBuf::size_type pos, char toset) +{ + checkAccessBounds(pos); + cow(); + store_->mem[off_+pos] = toset; + ++stats.setChar; +} + +int +SBuf::compare(const SBuf &S, SBufCaseSensitive isCaseSensitive, size_type n) const +{ + Must(n == npos || n >= 0); + size_type sz = min(S.length(), length()); + if (n != npos) + sz = min(n, sz); + ++stats.compareSlow; + int rv; + if (isCaseSensitive == caseSensitive) + rv = strncmp(buf(), S.buf(), sz); + else + rv = strncasecmp(buf(), S.buf(), sz); + if (rv != 0) + return rv; + //if max-length was specified, it was hit, and the tail is ignored. + if (n != npos) + return 0; + //first sz bytes equal. longest string "wins" + return commonCompareChecksPost(S); +} + +bool +SBuf::startsWith(const SBuf &S, SBufCaseSensitive isCaseSensitive) const +{ + if (length() < S.length()) { + ++stats.compareFast; + return false; + } + return (compare(S, isCaseSensitive, S.length()) == 0); +} + +bool +SBuf::operator ==(const SBuf & S) const +{ + debugs(24, 8, id << " == " << S.id); + if (length() != S.length()) { + debugs(24, 8, "no, different lengths"); + ++stats.compareFast; + return false; //shortcut: must be equal length + } + if (store_ == S.store_ && off_ == S.off_) { + debugs(24, 8, "yes, same length and backing store"); + ++stats.compareFast; + return true; //shortcut: same store, offset and length + } + ++stats.compareSlow; + const bool rv = (0 == strncmp(buf(), S.buf(), length())); + debugs(24, 8, "returning " << rv); + return rv; +} + +bool +SBuf::operator !=(const SBuf & S) const +{ + return !(*this == S); +} + +SBuf +SBuf::consume(SBuf::size_type n) +{ + Must (n == npos || n >= 0); + if (n == npos) + n = length(); + else + n = min(n, length()); + SBuf rv(substr(0, n)); + chop(n); + return rv; +} + +const +SBufStats& SBuf::GetStats() +{ + return stats; +} + +SBuf::size_type +SBuf::copy(char *dest, SBuf::size_type n) const +{ + Must(n >= 0); + + SBuf::size_type toexport = length(); + if (toexport > n) + toexport = n; + + memcpy(dest, buf(), toexport); + + ++stats.copyOut; + return toexport; +} + +const char* +SBuf::rawContent() const +{ + ++stats.rawAccess; + return buf(); +} + +char * +SBuf::rawSpace(size_type minSize) +{ + cow(minSize+length()); + ++stats.rawAccess; + return bufEnd(); +} + +void +SBuf::forceSize(SBuf::size_type newSize) +{ + Must(store_->LockCount() == 1); + len_ = newSize; + store_->size = newSize; +} + +const char* +SBuf::c_str() +{ + ++stats.rawAccess; + /* null-terminate the current buffer, by hand-appending a \0 at its tail but + * without increasing its length. May COW, the side-effect is to guarantee that + * the MemBlob's tail is availabe for us to use */ + *rawSpace(1) = '\0'; + ++store_->size; + ++stats.setChar; + return buf(); +} + +SBuf& +SBuf::chop(SBuf::size_type pos, SBuf::size_type n) +{ + Must(pos >= 0); + Must(n == npos || n >= 0); + /* + * TODO: possible optimization: if the SBuf is at the tail of the + * MemBlob we could decrease the MemBlob tail-pointer so that a subsequent + * append will reuse the freed space. + */ + if (pos > length() || n == 0) { + clear(); + return *this; + } + if (n == npos || (pos+n) > length()) + n = length()-pos; + ++stats.chop; + off_ += pos; + len_ = n; + return *this; +} + +SBuf& +SBuf::trim(const SBuf &toRemove, bool atBeginning, bool atEnd) +{ + ++stats.trim; + if (atEnd) { + const char *p = bufEnd()-1; + while (!isEmpty() && memchr(toRemove.buf(), *p, toRemove.length()) != NULL) { + //current end-of-buf is in the searched set + --len_; + --p; + } + } + if (atBeginning) { + const char *p = buf(); + while (!isEmpty() && memchr(toRemove.buf(), *p, toRemove.length()) != NULL) { + --len_; + ++off_; + ++p; + } + } + if (isEmpty()) + clear(); + return *this; +} + +SBuf +SBuf::substr(SBuf::size_type pos, SBuf::size_type n) const +{ + SBuf rv(*this); + rv.chop(pos, n); //stats handled by callee + return rv; +} + +SBuf::size_type +SBuf::find(char c, SBuf::size_type startPos) const +{ + ++stats.find; + + if (startPos == SBuf::npos) + return SBuf::npos; + + // std::string returns npos if needle is outside hay + if (startPos >= length()) + return SBuf::npos; + + // ignore invalid startPos + if (startPos < 0) + startPos = 0; + + const void *i = memchr(buf()+startPos, (int)c, (size_type)length()-startPos); + + if (i == NULL) + return SBuf::npos; + + return (static_cast(i)-buf()); +} + +SBuf::size_type +SBuf::find(const SBuf &needle, size_type startPos) const +{ + // if needle length is 1 use the char search. Stats updated there + if (needle.length() == 1) + return find(needle[0], startPos); + + // if needle length is 1, the stats were + ++stats.find; + + if (startPos == SBuf::npos) + return SBuf::npos; + + // std::string allows needle to overhang hay but not start outside + if (startPos > length()) + return SBuf::npos; + + // ignore invalid startPos + if (startPos < 0) + startPos = 0; + + // for empty needle std::string returns startPos + if (needle.length() == 0) + return startPos; + + char *begin = buf()+startPos; + char *lastPossible = buf()+length()-needle.length()+1; + char needleBegin = needle[0]; + + debugs(24, 7, "looking for " << needle << "starting at " << startPos << + " in id " << id); + while (begin < lastPossible) { + char *tmp; + debugs(24, 8, " begin=" << (void *) begin << + ", lastPossible=" << (void*) lastPossible ); + tmp = static_cast(memchr(begin, needleBegin, lastPossible-begin)); + if (tmp == NULL) { + debugs(24, 8 , "First byte not found"); + return SBuf::npos; + } + // lastPossible guarrantees no out-of-bounds with memcmp() + if (0 == memcmp(needle.buf(), tmp, needle.length())) { + debugs(24, 8, "Found at " << (tmp-buf())); + return (tmp-buf()); + } + begin = tmp+1; + } + debugs(24, 8, "not found"); + return SBuf::npos; +} + +SBuf::size_type +SBuf::rfind(const SBuf &needle, SBuf::size_type endPos) const +{ + // when the needle is 1 char, use the 1-char rfind() + if (needle.length() == 1) + return rfind(needle[0], endPos); + + ++stats.find; + + // on npos input std::string scans from the end of hay + if (endPos == SBuf::npos || endPos > length()) + endPos=length(); + + // on empty hay std::string returns npos + if (length() < needle.length()) + return SBuf::npos; + + if (endPos < 0) + return SBuf::npos; + + // on empty needle std::string returns the position the search starts + if (needle.length() == 0) + return endPos; + +/* std::string permits needle to overhang endPos + if (endPos <= needle.length()) + return npos; +*/ + + char *bufBegin = buf(); + char *cur = bufBegin+endPos; + char needleBegin = needle[0]; + while (cur >= bufBegin) { + if (*cur == needleBegin) { + if (0 == memcmp(needle.buf(), cur, needle.length())) { + // found + return (cur-buf()); + } + } + --cur; + } + return npos; +} + +SBuf::size_type +SBuf::rfind(char c, SBuf::size_type endPos) const +{ + ++stats.find; + + // on empty hay std::string returns size of hay + if (length() < 1) + return SBuf::npos; + + // on npos input std::string compares last octet of hay + if (endPos == SBuf::npos || endPos >= length()) { + endPos=length(); + } else if (endPos < 0 ) { + return SBuf::npos; + } else { + // NP: off-by-one weirdness: + // endPos is an offset ... 0-based + // length() is a count ... 1-based + // memrhr() requires a 1-based count of space to scan. + ++endPos; + } + + const void *i = memrchr(buf(), (int)c, (size_type)endPos); + + if (i == NULL) + return SBuf::npos; + + return (static_cast(i)-buf()); +} + +SBuf::size_type +SBuf::find_first_of(const SBuf &set, size_type startPos) const +{ + // if set is 1 char big, use the char search. Stats updated there + if (set.length() == 1) + return find(set[0], startPos); + + ++stats.find; + + if (startPos == npos) + return SBuf::npos; + + if (startPos > length()) + return SBuf::npos; + + if (startPos < 0) + startPos = 0; + + if (set.length() == 0) + return npos; + + debugs(24, 7, "any of '" << set << "' " << " in id " << id); + char *cur = buf()+startPos, *end = bufEnd(); + while (cur < end) { + if (memchr(set.buf(), *cur, set.length())) + return (cur-buf()); + ++cur; + } + debugs(24, 7, "not found"); + return SBuf::npos; +} + +/* + * TODO: borrow a sscanf implementation from Linux or similar? + * we'd really need a vsnscanf(3)... ? As an alternative, a + * light-regexp-like domain-specific syntax might be an idea. + */ +int +SBuf::scanf(const char *format, ...) +{ + va_list arg; + int rv; + ++stats.scanf; + va_start(arg, format); + rv = vsscanf(c_str(), format, arg); + va_end(arg); + return rv; +} + +std::ostream & +operator <<(std::ostream& os, const SBuf& S) +{ + return S.print(os); +} + +std::ostream & +SBufStats::dump(std::ostream& os) const +{ + MemBlobStats ststats = MemBlob::GetStats(); + os << + "SBuf stats:\nnumber of allocations: " << alloc << + "\ncopy-allocations: " << allocCopy << + "\ncopy-allocations from SquidString: " << allocFromString << + "\ncopy-allocations from C String: " << allocFromCString << + "\nlive references: " << live << + "\nno-copy assignments: " << assignFast << + "\nclearing operations: " << clear << + "\nappend operations: " << append << + "\ndump-to-ostream: " << toStream << + "\nset-char: " << setChar << + "\nget-char: " << getChar << + "\ncomparisons with data-scan: " << compareSlow << + "\ncomparisons not requiring data-scan: " << compareFast << + "\ncopy-out ops: " << copyOut << + "\nraw access to memory: " << rawAccess << + "\nchop operations: " << chop << + "\ntrim operations: " << trim << + "\nfind: " << find << + "\nscanf: " << scanf << + "\ncase-change ops: " << caseChange << + "\nCOW not actually requiring a copy: " << cowFast << + "\nCOW: " << cowSlow << + "\naverage store share factor: " << + (ststats.live != 0 ? static_cast(live)/ststats.live : 0) << + std::endl; + return os; +} + +SBuf +SBuf::toLower() const +{ + debugs(24, 8, "\"" << *this << "\""); + SBuf rv(*this); + for (size_type j = 0; j < length(); ++j) { + const int c = (*this)[j]; + if (isupper(c)) + rv.setAt(j, tolower(c)); //will cow() if needed + } + debugs(24, 8, "result: \"" << *this << "\""); + ++stats.caseChange; + return rv; +} + +SBuf +SBuf::toUpper() const +{ + debugs(24, 8, "\"" << *this << "\""); + SBuf rv(*this); + for (size_type j = 0; j < length(); ++j) { + const int c = (*this)[j]; + if (islower(c)) + rv.setAt(j, toupper(c)); //will cow() if needed + } + debugs(24, 8, "result: \"" << *this << "\""); + ++stats.caseChange; + return rv; +} + +/** + * checks whether the requested 'pos' is within the bounds of the SBuf + * \throw OutOfBoundsException if access is out of bounds + */ +void +SBuf::checkAccessBounds(SBuf::size_type pos) const +{ + if (pos < 0) + throw OutOfBoundsException(*this, pos, __FILE__, __LINE__); + if (pos > length()) + throw OutOfBoundsException(*this, pos, __FILE__, __LINE__); +} + +String +SBuf::toString() const +{ + String rv; + rv.limitInit(buf(), length()); + ++stats.copyOut; + return rv; +} + +/* + * re-allocate the backing store of the SBuf. + * If there are contents in the SBuf, they will be copied over. + * NO verifications are made on the size parameters, it's up to the caller to + * make sure that the new size is big enough to hold the copied contents. + * The re-allocated storage MAY be bigger than the requested size due to size-chunking + * algorithms in MemBlock, it is guarranteed NOT to be smaller. + */ +void +SBuf::reAlloc(SBuf::size_type newsize) +{ + debugs(24, DBG_DATA, "new size: " << newsize); + if (newsize > maxSize) + throw SBufTooBigException(__FILE__, __LINE__); + MemBlob::Pointer newbuf = new MemBlob(newsize); + if (length() > 0) + newbuf->append(buf(), length()); + store_ = newbuf; + off_ = 0; + ++stats.cowSlow; + debugs(24, 7, "new store capacity: " << store_->capacity); +} + +#if !_USE_INLINE_ +#include "SBuf.cci" +#endif + diff --git a/src/SBuf.cci b/src/SBuf.cci new file mode 100644 index 0000000000..714b85102e --- /dev/null +++ b/src/SBuf.cci @@ -0,0 +1,197 @@ +/* + * SBuf.cc (C) 2008 Francesco Chemolli + * + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + */ + +#include "base/RefCount.h" +#include "OutOfBoundsException.h" +#include "SBufExceptions.h" + +#if HAVE_CLIMITS +#include +#elif HAVE_LIMITS_H +#include +#endif + +SBuf& +SBuf::operator =(const SBuf & S) +{ + return assign(S); +} + +SBuf& +SBuf::operator =(const char *S) +{ + return assign(S); +} + +bool +SBuf::operator <(const SBuf &S) const +{ + return (compare(S) < 0); +} + +bool +SBuf::operator >(const SBuf &S) const +{ + return (compare(S) > 0); +} + +bool +SBuf::operator <=(const SBuf &S) const +{ + return (compare(S) <= 0); +} + +bool +SBuf::operator >=(const SBuf &S) const +{ + return (compare(S) >= 0); +} + +SBuf::size_type +SBuf::length() const +{ + return len_; +} + +int +SBuf::plength() const +{ + if (length() > INT_MAX) + throw SBufTooBigException(__FILE__, __LINE__); + return (int)length(); +} + +/** + * obtains a char* to the beginning of this SBuf in memory. + * \note the obtained string is NOT null-terminated. + */ +char * +SBuf::buf() const +{ + return store_->mem+off_; +} + +/** returns the pointer to the first char after this SBuf end + * + */ +char * +SBuf::bufEnd() const +{ + return store_->mem+off_+len_; +} + +/** + * copy-on-write: make sure that we are the only holder of the backing store. + * If not, reallocate. If a new size is specified, and it is greater than the + * current length, the backing store will be extended as needed + * \retval false no grow was needed + * \retval true had to copy + */ +bool +SBuf::cow(SBuf::size_type newsize) +{ + debugs(24, DBG_DATA, "new size:" << newsize); + if (newsize == npos || newsize < length()) + newsize = length(); + + if (store_->LockCount() == 1 && newsize == length()) { + debugs(24, DBG_DATA, "no cow needed"); + ++stats.cowFast; + return false; + } + reAlloc(newsize); + return true; +} + +/** + * Try to guesstimate how big a MemBlob to allocate. + * The result is guarranteed to be to be at least the desired + * size. + */ +const SBuf::size_type +SBuf::estimateCapacity(SBuf::size_type desired) const +{ + return 2*desired; +} + +/** + * To be called after having determined that the buffers are equal up to the + * length of the shortest one. + * If the buffers' length is the same, then they're equal. Otherwise, the + * longest one is deemed to be greater than the other. + * This matches the behavior of strcmp(1) and strcasecmp(1) + */ +int +SBuf::commonCompareChecksPost(const SBuf &S) const +{ + if (length() == S.length()) //I'll be damned..they're REALLY the same.. + return 0; + if (length() > S.length()) + return 1; + return -1; +} + +/** obtain prototype store + * + * Just-created SBufs all share to the same MemBlob. + * This call instantiates and returns it. + */ +MemBlob::Pointer +SBuf::GetStorePrototype() +{ + static MemBlob::Pointer InitialStore = NULL; + if (InitialStore == NULL) { + static char lowPrototype[] = ""; + InitialStore = new MemBlob(lowPrototype, 0); + } + return InitialStore; +} + +const char +SBuf::operator [](SBuf::size_type pos) const +{ + ++stats.getChar; + if (pos < 0 || pos >= length()) + return '\0'; + return store_->mem[off_+pos]; +} + +const char +SBuf::at(SBuf::size_type pos) const +{ + checkAccessBounds(pos); + return operator[](pos); +} + +bool +SBuf::isEmpty() const +{ + return (len_ == 0); +} diff --git a/src/SBuf.h b/src/SBuf.h new file mode 100644 index 0000000000..33fccfaf26 --- /dev/null +++ b/src/SBuf.h @@ -0,0 +1,566 @@ +/* + * SBuf.h (C) 2008 Francesco Chemolli + * + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + */ + +#ifndef SQUID_SBUF_H +#define SQUID_SBUF_H + +#include "base/InstanceId.h" +#include "Debug.h" +#include "MemBlob.h" +#include "SquidString.h" + +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_STDARG_H +#include +#endif +#if HAVE_IOSFWD +#include +#endif + +/* squid string placeholder (for printf) */ +#ifndef SQUIDSBUFPH +#define SQUIDSBUFPH "%.*s" +#define SQUIDSBUFPRINT(s) (s).plength(),(s).rawContent() +#endif /* SQUIDSBUFPH */ + +typedef enum { + caseSensitive, + caseInsensitive +} SBufCaseSensitive; + +/** + * Container for various SBuf class-wide statistics. + * + * The stats are not completely accurate; they're mostly meant to + * understand whether Squid is leaking resources + * and whether SBuf is paying off the expected gains. + */ +class SBufStats +{ +public: + u_int64_t alloc; ///0 argument of the call is greater than called SBuf + * \retval <0 argument of the call is smaller than called SBuf + * \retval 0 argument of the call has the same contents of called SBuf + */ + int compare(const SBuf &S, SBufCaseSensitive isCaseSensitive = caseSensitive, size_type n = npos) const; + + /** check whether the entire supplied argument is a prefix of the SBuf. + * \param S the prefix to match against + * \param isCaseSensitive one of caseSensitive or caseInsensitive + * \retval true argument is a prefix of the SBuf + */ + bool startsWith(const SBuf &S, SBufCaseSensitive isCaseSensitive = caseSensitive) const; + + /** equality check + */ + bool operator ==(const SBuf & S) const; + bool operator !=(const SBuf & S) const; + _SQUID_INLINE_ bool operator <(const SBuf &S) const; + _SQUID_INLINE_ bool operator >(const SBuf &S) const; + _SQUID_INLINE_ bool operator <=(const SBuf &S) const; + _SQUID_INLINE_ bool operator >=(const SBuf &S) const; + + /** Consume bytes at the head of the SBuf + * + * Consume N chars at SBuf head, or to SBuf's end, + * whichever is shorter. If more bytes are consumed than available, + * the SBuf is emptied + * \param n how many bytes to remove; could be zero. + * SBuf::npos (or no argument) means 'to the end of SBuf' + * \return a new SBuf containing the consumed bytes. + */ + SBuf consume(size_type n = npos); + + /** gets global statistic informations + * + */ + static const SBufStats& GetStats(); + + /** Copy SBuf contents into user-supplied C buffer. + * + * Export a copy of the SBuf's contents into the user-supplied + * buffer, up to the user-supplied-length. No zero-termination is performed + * \return num the number of actually-copied chars. + */ + size_type copy(char *dest, size_type n) const; + + /** exports a pointer to the SBuf internal storage. + * \warning ACCESSING RAW STORAGE IS DANGEROUS! + * + * Returns a pointer to SBuf's content. No terminating null character + * is appended (use c_str() for that). + * The returned value points to an internal location whose contents + * are guaranteed to remain unchanged only until the next call + * to a non-constant member function of the SBuf object. Such a + * call may be implicit (e.g., when SBuf is destroyed + * upon leaving the current context). + * This is a very UNSAFE way of accessing the data. + * This call never returns NULL. + * \see c_str + * \note the memory management system guarantees that the exported region + * of memory will remain valid if the caller keeps holding + * a valid reference to the SBuf object and does not write or append to + * it. For example: + * \code + * SBuf foo("some string"); + * const char *bar = foo.rawContent(); + * doSomething(bar); //safe + * foo.append(" other string"); + * doSomething(bar); //unsafe + * \endcode + */ + const char* rawContent() const; + + /** Exports a writable pointer to the SBuf internal storage. + * \warning Use with EXTREME caution, this is a dangerous operation. + * + * Returns a pointer to the first unused byte in the SBuf's storage, + * to be used for writing. If minsize is specified, it is guaranteed + * that at least minsize bytes will be available for writing. Otherwise + * it is guaranteed that at least as much storage as is currently + * available will be available for the call. A COW will be performed + * if necessary to ensure that a following write will not trample + * a shared MemBlob. The returned pointer must not be stored, and will + * become invalid at the first call to a non-const method call + * on the SBuf. + * This call guarantees to never return NULL + * This call always forces a cow() + * \throw SBufTooBigException if the user tries to allocate too big a SBuf + */ + char *rawSpace(size_type minSize = npos); + + /** Force a SBuf's size + * \warning use with EXTREME caution, this is a dangerous operation + * + * Adapt the SBuf internal state after external interference + * such as writing into it via rawSpace. + * \throw TextException if we + */ + void forceSize(size_type newSize); + + /** exports a null-terminated reference to the SBuf internal storage. + * \warning ACCESSING RAW STORAGE IS DANGEROUS! DO NOT EVER USE + * THE RETURNED POINTER FOR WRITING + * + * The returned value points to an internal location whose contents + * are guaranteed to remain unchanged only until the next call + * to a non-constant member function of the SBuf object. Such a + * call may be implicit (e.g., when SBuf is destroyed + * upon leaving the current context). + * This is a very UNSAFE way of accessing the data. + * This call never returns NULL. + * \see rawContent + * \note the memory management system guarantees that the exported region + * of memory will remain valid if the caller keeps holding + * a valid reference to the SBuf object and does not write or append to + * it + */ + const char* c_str(); + + /** Returns the number of bytes stored in SBuf. + */ + _SQUID_INLINE_ size_type length() const; + + /** Get the length of the SBuf, as a signed integer + * + * Compatibility function for printf(3) which requires a signed int + * \throw SBufTooBigException if the SBuf is too big for a signed integer + */ + _SQUID_INLINE_ int plength() const; + + /** Check whether the SBuf is empty + * + * \return true if length() == 0 + */ + _SQUID_INLINE_ bool isEmpty() const; + + /** Request to extend the SBuf's free store space. + * + * After the reserveSpace request, the SBuf is guaranteed to have at + * least minSpace bytes of append-able backing store (on top of the + * currently-used portion). + * \throw SBufTooBigException if the user tries to allocate too big a SBuf + */ + void reserveSpace(size_type minSpace); + + /** Request to resize the SBuf's store + * + * After this method is called, the SBuf is guaranteed to have at least + * minCapcity bytes of total space, including the currently-used portion + * \throw SBufTooBigException if the user tries to allocate too big a SBuf + */ + void reserveCapacity(size_type minCapacity); + + /** slicing method + * + * Removes SBuf prefix and suffix, leaving a sequence of n + * bytes starting from position pos first byte is at pos 0. + * \param pos start sub-stringing from this byte. If it is + * greater than the SBuf length, the SBuf is emptied and + * an empty SBuf is returned + * \param n maximum number of bytes of the resulting SBuf. + * SBuf::npos means "to end of SBuf". + * if 0 returns an empty SBuf. + */ + SBuf& chop(size_type pos, size_type n = npos); + + /** Remove characters in the toremove set at the beginning, end or both + * + * \param toremove characters to be removed. Stops chomping at the first + * found char not in the set + * \param atBeginning if true (default), strips at the beginning of the SBuf + * \param atEnd if true (default), strips at the end of the SBuf + */ + SBuf& trim(const SBuf &toRemove, bool atBeginning = true, bool atEnd = true); + + /** Extract a part of the current SBuf. + * + * Return a fresh a fresh copy of a portion the current SBuf, which is left untouched. + * \see trim + */ + SBuf substr(size_type pos, size_type n = npos) const; + + /** Find first occurrence of character in SBuf + * + * Returns the index in the SBuf of the first occurrence of char c. + * \return SBuf::npos if the char was not found + * \param startPos if specified, ignore any occurrences before that position + * if startPos is SBuf::npos, npos is always returned + * if startPos is < 0, it is ignored + */ + size_type find(char c, size_type startPos = 0) const; + + /** Find first occurrence of SBuf in SBuf. + * + * Returns the index in the SBuf of the first occurrence of the + * sequence contained in the str argument. + * \param startPos if specified, ignore any occurrences before that position + * if startPos is SBuf::npos, npos is always returned + * if startPos is < 0, it is ignored + * \return SBuf::npos if the SBuf was not found + */ + size_type find(const SBuf & str, size_type startPos = 0) const; + + /** Find last occurrence of character in SBuf + * + * Returns the index in the SBuf of the last occurrence of char c. + * \return SBuf::npos if the char was not found + * \param endPos if specified, ignore any occurrences after that position. + * if unspecified or npos, the whole SBuf is considered. + * If < 0, npos is returned + */ + size_type rfind(char c, size_type endPos = npos) const; + + /** Find last occurrence of SBuf in SBuf + * + * Returns the index in the SBuf of the last occurrence of the + * sequence contained in the str argument. + * \return SBuf::npos if the sequence was not found + * \param endPos if specified, ignore any occurrences after that position + * if unspecified or npos, the whole SBuf is considered + * if < 0, then npos is always returned + */ + size_type rfind(const SBuf &str, size_type endPos = npos) const; + + /** Find first occurrence of character of set in SBuf + * + * Finds the first occurrence of ANY of the characters in the supplied set in + * the SBuf. + * \return SBuf::npos if no character in the set could be found + * \param startPos if specified, ignore any occurrences before that position + * if SBuf::npos, then npos is always returned + * if <0, it is ignored. + */ + size_type find_first_of(const SBuf &set, size_type startPos = 0) const; + + /** sscanf-alike + * + * sscanf re-implementation. Non-const, and not \0-clean. + * \return same as sscanf + * \see man sscanf(3) + */ + int scanf(const char *format, ...); + + /** Lower-case SBuf + * + * Returns a lower-cased COPY of the SBuf + * \see man tolower(3) + */ + SBuf toLower() const; + + /** Upper-case SBuf + * + * Returns an upper-cased COPY of the SBuf + * \see man toupper(3) + */ + SBuf toUpper() const; + + /** String export function + * converts the SBuf to a legacy String, by copy. Transitional. + */ + String toString() const; + + /// TODO: possibly implement erase() similar to std::string's erase + /// TODO: possibly implement a replace() call +private: + + MemBlob::Pointer store_; ///< memory block, possibly shared with other SBufs + size_type off_; ///< our content start offset from the beginning of shared store_ + size_type len_; ///< number of our content bytes in shared store_ + static SBufStats stats; ///< class-wide statistics + + const InstanceId id; ///< blob identifier + + _SQUID_INLINE_ static MemBlob::Pointer GetStorePrototype(); + + _SQUID_INLINE_ char * buf() const; + _SQUID_INLINE_ char * bufEnd() const; + _SQUID_INLINE_ const size_type estimateCapacity(size_type desired) const; + void reAlloc(size_type newsize); + + _SQUID_INLINE_ bool cow(size_type minsize = npos); + + void checkAccessBounds(size_type pos) const; + _SQUID_INLINE_ int commonCompareChecksPre(const SBuf &S) const; + _SQUID_INLINE_ int commonCompareChecksPost(const SBuf &S) const; + +}; + +/** + * Prints a SBuf to the supplied stream, allowing for chaining + */ +std::ostream& operator <<(std::ostream &os, const SBuf &S); + +#if _USE_INLINE_ +#include "SBuf.cci" +#endif + +#endif /* SQUID_SBUF_H */ diff --git a/src/SBufExceptions.cc b/src/SBufExceptions.cc new file mode 100644 index 0000000000..89dab78dfe --- /dev/null +++ b/src/SBufExceptions.cc @@ -0,0 +1,72 @@ +/* + * SBufExceptions.cc (C) 2008 Francesco Chemolli + * + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + */ + +#include "squid.h" +#include "OutOfBoundsException.h" +#include "SBuf.h" +#include "SBufExceptions.h" + +// Note: the SBuf is intentionally passed by copy rather than reference, +// to let refcounting act. +OutOfBoundsException::OutOfBoundsException(const SBuf &throwingBuf, + SBuf::size_type &pos, + const char *aFileName, int aLineNo) + :TextException(NULL, aFileName, aLineNo) +{ + _buf = throwingBuf; + _pos = pos; + SBuf explanatoryText("OutOfBoundsException"); + if (aLineNo != -1) + explanatoryText.appendf(" at line %d", aLineNo); + if (aFileName != 0) + explanatoryText.appendf(" in file %s", aFileName); + explanatoryText.appendf(" while accessing position %d in a SBuf long %d", + pos, throwingBuf.length()); + // we can safely alias c_str as both are local to the object + // and will not further manipulated. + message = xstrndup(explanatoryText.c_str(),explanatoryText.length()); +} + +OutOfBoundsException::~OutOfBoundsException() throw() +{ } + +NullSBufException::NullSBufException(const char *aFilename, int aLineNo) + : TextException("Trying to access a null SBuf", aFilename, aLineNo) +{ } + +InvalidParamException::InvalidParamException(const char *aFilename, int aLineNo) + : TextException("Invalid parameter", aFilename, aLineNo) +{ } + +SBufTooBigException::SBufTooBigException(const char *aFilename, int aLineNo) + : TextException("Trying to create an oversize SBuf", aFilename, aLineNo) +{ } + +/* */ diff --git a/src/SBufExceptions.h b/src/SBufExceptions.h new file mode 100644 index 0000000000..e30761923a --- /dev/null +++ b/src/SBufExceptions.h @@ -0,0 +1,65 @@ +/* + * SBufExceptions.h (C) 2008 Francesco Chemolli + * + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + */ + +#ifndef SQUID_SBUFEXCEPTIONS_H +#define SQUID_SBUFEXCEPTIONS_H + +#include "base/TextException.h" + +/** + * Exception raised when the user is trying to operate on a Null SBuf + * \todo move to an Exceptions.h? + */ +class NullSBufException : public TextException +{ +public: + explicit NullSBufException(const char *aFilename = 0, int aLineNo = -1); +}; + +/** + * Exception raised when call parameters are not valid + * \todo move to an Exceptions.h? + */ +class InvalidParamException : public TextException +{ +public: + explicit InvalidParamException(const char *aFilename = 0, int aLineNo = -1); +}; + +/** + * Exception raised when an attempt to resize a SBuf would cause it to reserve too big + */ +class SBufTooBigException : public TextException +{ +public: + explicit SBufTooBigException(const char *aFilename = 0, int aLineNo = -1); +}; + +#endif /* SQUID_SBUFEXCEPTIONS_H */ diff --git a/src/SBufStream.h b/src/SBufStream.h new file mode 100644 index 0000000000..fc6044e6fe --- /dev/null +++ b/src/SBufStream.h @@ -0,0 +1,141 @@ +/* + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + * + */ + +#ifndef SQUID_SBUFSTREAM_H +#define SQUID_SBUFSTREAM_H + +#include "SBuf.h" + +#if HAVE_OSTREAM +#include +#endif + +/** + * streambuf class for a SBuf-backed stream interface. + * + */ +class SBufStreamBuf : public std::streambuf +{ +public: + /// initialize streambuf; use supplied SBuf as backing store + SBufStreamBuf(SBuf aBuf) : theBuf(aBuf) {} + + /// get a copy of the stream's contents + SBuf getBuf() { + return theBuf; + } + + /// clear the stream's store + void clearBuf() { + theBuf.clear(); + } + +protected: + virtual int_type overflow(int_type aChar = traits_type::eof()) { + std::streamsize pending(pptr() - pbase()); + + if (pending && sync()) + return traits_type::eof(); + + if (aChar != traits_type::eof()) { + char chars[1] = {static_cast(aChar)}; + + if (aChar != traits_type::eof()) + theBuf.append(chars, 0, 1); + } + + pbump(-pending); // Reset pptr(). + return aChar; + } + + /* push the streambuf to the backing SBuf */ + virtual int sync() { + std::streamsize pending(pptr() - pbase()); + + if (pending) + theBuf.append(pbase(), 0, pending); + + return 0; + } + + /* write multiple characters to the store entry + * - this is an optimisation method. + */ + virtual std::streamsize xsputn(const char * chars, std::streamsize number) { + if (number) + theBuf.append(chars, 0, number); + + return number; + } + +private: + SBuf theBuf; + SBufStreamBuf(); // no default constructor +}; + +/** Stream interface to write to a SBuf. + * + * Data is appended using standard operator << semantics, and extracted + * using the buf() method, in analogy with std::strstream . + */ +class SBufStream : public std::ostream +{ +public: + /** Create a SBufStream preinitialized with the argument's SBuf. + * + * The supplied SBuf is not aliased: in order to retrieve the altered contents + * they must be fetched using the buf() class method. + */ + SBufStream(SBuf aBuf): std::ostream(0), theBuffer(aBuf) { + rdbuf(&theBuffer); // set the buffer to now-initialized theBuffer + clear(); //clear badbit set by calling init(0) + } + + /// Create an empty SBufStream + SBufStream(): std::ostream(0), theBuffer(SBuf()) { + rdbuf(&theBuffer); // set the buffer to now-initialized theBuffer + clear(); //clear badbit set by calling init(0) + } + + /// Retrieve a copy of the current stream status + SBuf buf() { + return theBuffer.getBuf(); + } + + /// Clear the stream's backing store + SBufStream& clearBuf() { + theBuffer.clearBuf(); + return *this; + } + +private: + SBufStreamBuf theBuffer; +}; + +#endif /* SQUID_SBUFSTREAM_H */ diff --git a/src/tests/SBufFindTest.cc b/src/tests/SBufFindTest.cc new file mode 100644 index 0000000000..f8425401b2 --- /dev/null +++ b/src/tests/SBufFindTest.cc @@ -0,0 +1,431 @@ +#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 < 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(); + + // the special npos value is not tested as the behavior is + // different from std::string (where the behavior is undefined) + // It is ad-hoc tested in testSBuf instead + //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::find_first_of(string needle, pos) +void +SBufFindTest::testFindFirstOf() { + theFindString = theStringHay.find_first_of(theStringNeedle, thePos); + theBareNeedlePos = theStringHay.find_first_of(theStringNeedle); + theFindSBuf = theSBufHay.find_first_of(theSBufNeedle, thePos); + checkResults("find_first_of"); +} + + +/// 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(); + testFindFirstOf(); + } + + // 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; + } +} diff --git a/src/tests/SBufFindTest.h b/src/tests/SBufFindTest.h new file mode 100644 index 0000000000..bd65361258 --- /dev/null +++ b/src/tests/SBufFindTest.h @@ -0,0 +1,86 @@ +#ifndef SQUID_SRC_TEST_SBUFFINDTEST_H +#define SQUID_SRC_TEST_SBUFFINDTEST_H + +#include "SBuf.h" + +#if HAVE_STRING +#include +#endif +#include + +/// 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; + + /// Supported algorithms for placing needle in the hay. + typedef enum { placeBeginning, placeMiddle, placeEnd, placeNowhere, + placeEof } Placement; // placeLast marker must terminate +protected: + + 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(); + void testFindFirstOf(); + + 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 +}; + +typedef SBufFindTest::Placement Placement; + +#endif diff --git a/src/tests/testSBuf.cc b/src/tests/testSBuf.cc new file mode 100644 index 0000000000..15ad95dc1d --- /dev/null +++ b/src/tests/testSBuf.cc @@ -0,0 +1,723 @@ +#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 "; +static 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 +// initialization. Check for correct constructor operation. +SBuf empty_sbuf; +SBuf literal("The quick brown fox jumped over the lazy dog"); + +void +testSBuf::testSBufConstructDestruct() +{ + /* NOTE: Do not initialize memory here because we need + * to test correct operation before and after Mem::Init + */ + + // XXX: partial demo below of how to do constructor unit-test. use scope to ensure each test + // is working on local-scope variables constructed fresh for the test, and destructed when + // scope exists. use nested scopes to test destructor affects on copied data (MemBlob etc) + + // TEST: default constructor (implicit destructor non-crash test) + // test accessors on empty SBuf. + { + SBuf s1; + CPPUNIT_ASSERT_EQUAL(s1.length(),0); + CPPUNIT_ASSERT_EQUAL(s1,SBuf("")); + CPPUNIT_ASSERT_EQUAL(s1,empty_sbuf); + CPPUNIT_ASSERT(0==strcmp("",s1.c_str())); + } + + // TEST: copy-construct NULL string (implicit destructor non-crash test) + { + SBuf s1(NULL); + CPPUNIT_ASSERT_EQUAL(s1.length(),0); + CPPUNIT_ASSERT_EQUAL(s1,SBuf("")); + CPPUNIT_ASSERT_EQUAL(s1,empty_sbuf); + CPPUNIT_ASSERT(0==strcmp("",s1.c_str())); + } + + // TEST: copy-construct empty string (implicit destructor non-crash test) + { + SBuf s1(""); + CPPUNIT_ASSERT_EQUAL(s1.length(),0); + CPPUNIT_ASSERT_EQUAL(s1,SBuf("")); + CPPUNIT_ASSERT_EQUAL(s1,empty_sbuf); + CPPUNIT_ASSERT(0==strcmp("",s1.c_str())); + } + + // TEST: copy-construct from a SBuf + { + SBuf s1(empty_sbuf); + CPPUNIT_ASSERT_EQUAL(s1.length(),0); + CPPUNIT_ASSERT_EQUAL(s1,SBuf("")); + CPPUNIT_ASSERT_EQUAL(s1,empty_sbuf); + CPPUNIT_ASSERT(0==strcmp("",s1.c_str())); + + SBuf s5(literal); + CPPUNIT_ASSERT_EQUAL(s5,literal); + SBuf s6(fox); + CPPUNIT_ASSERT_EQUAL(s6,literal); + // XXX: other state checks. expected result of calling any state accessor on s4 ? + } + + // TEST: check that COW doesn't happen upon copy-construction + { + SBuf s1(empty_sbuf), s2(s1); + CPPUNIT_ASSERT_EQUAL(s1.rawContent(), s2.rawContent()); + SBuf s3(literal), s4(literal); + CPPUNIT_ASSERT_EQUAL(s3.rawContent(), s4.rawContent()); + } + + // TEST: sub-string copy + { + SBuf s1=SBuf(fox,4), s2(fox); + SBuf s3=s2.substr(4,s2.length()); //n is out-of-bounds + CPPUNIT_ASSERT_EQUAL(s1,s3); + SBuf s4=SBuf(fox,0,4); + s3=s2.substr(0,4); + CPPUNIT_ASSERT_EQUAL(s4,s3); + } + + // TEST: go via SquidString adapters. + { + String str(fox); + SBuf s1(str); + CPPUNIT_ASSERT_EQUAL(s1,literal); + } +} + +void +testSBuf::testSBufConstructDestructAfterMemInit() +{ + Mem::Init(); + testSBufConstructDestruct(); + +// XXX: or perhapse ... +// repeat all of the tests inside testSBufConstructDestructBeforeMemInit() +// with additional checks on Mem usage stats after each operation ?? +} + +void +testSBuf::testEqualityTest() +{ + SBuf s1(fox),s2(fox); + CPPUNIT_ASSERT_EQUAL(s1,s1); //self-equality + CPPUNIT_ASSERT_EQUAL(s1,s2); //same contents + s2.assign("The quick brown fox jumped over the lazy doe"); + CPPUNIT_ASSERT(!(s1 == s2)); //same length, different contents + s2.assign("foo"); + CPPUNIT_ASSERT(!(s1 == s2)); //different length and contents + CPPUNIT_ASSERT(s1 != s2); //while we're ready, let's test inequality + s2.clear(); + CPPUNIT_ASSERT(!(s1 == s2)); //null and not-null + CPPUNIT_ASSERT(s1 != s2); //while we're ready, let's test inequality + s1.clear(); + CPPUNIT_ASSERT_EQUAL(s1,s2); //null and null +} + +void +testSBuf::testAppendSBuf() +{ + SBuf s1(fox1),s2(fox2); + s1.append(s2); + CPPUNIT_ASSERT_EQUAL(s1,literal); +} + +void +testSBuf::testPrintf() +{ + SBuf s1,s2; + s1.Printf("%s:%d:%03.3f","fox",10,12345.67); + s2.assign("fox:10:12345.670"); + CPPUNIT_ASSERT_EQUAL(s1,s2); +} + +void +testSBuf::testAppendCString() +{ + SBuf s1(fox1); + s1.append(fox2); + CPPUNIT_ASSERT_EQUAL(s1,literal); +} + +void +testSBuf::testAppendStdString() +{ + SBuf s1(fox1); + std::string str(fox2); + s1.append(str); + CPPUNIT_ASSERT_EQUAL(s1,literal); +} + +void +testSBuf::testAppendf() +{ + SBuf s1,s2; + s1.appendf("%s:%d:%03.2f",fox,1234,1234.56); + s2.assign("The quick brown fox jumped over the lazy dog:1234:1234.56"); + CPPUNIT_ASSERT_EQUAL(s1,s2); +} + +void +testSBuf::testDumpStats() +{ + SBuf::GetStats().dump(std::cout); + MemBlob::GetStats().dump(std::cout); + std::cout << "sizeof(SBuf): " << sizeof(SBuf) << std::endl; + std::cout << "sizeof(MemBlob): " << sizeof(MemBlob) << std::endl; +} + +void +testSBuf::testSubscriptOp() +{ + SBuf chg(literal); + CPPUNIT_ASSERT_EQUAL(chg[5],'u'); + chg.setAt(5,'e'); + CPPUNIT_ASSERT_EQUAL(literal[5],'u'); + CPPUNIT_ASSERT_EQUAL(chg[5],'e'); +// std::cout << chg << std::endl << empty_sbuf << std::endl ; +} + +// note: can't use cppunit's CPPUNIT_TEST_EXCEPTION because TextException asserts, and +// so the test can't be properly completed. +void +testSBuf::testSubscriptOpFail() +{ + char c; + c=literal.at(1234); //out of bounds + //notreached + std::cout << c << std::endl; +} + +static int sign(int v) +{ + if (v < 0) + return -1; + if (v>0) + return 1; + return 0; +} + +void +testSBuf::testComparisons() +{ + //same length + SBuf s1("foo"),s2("foe"); + CPPUNIT_ASSERT(s1.compare(s2)>0); + CPPUNIT_ASSERT(s1.compare(s2,caseInsensitive,2)==0); + CPPUNIT_ASSERT(s1 > s2); + CPPUNIT_ASSERT(s2 < s1); + CPPUNIT_ASSERT_EQUAL(sign(s1.compare(s2)),sign(strcmp(s1.c_str(),s2.c_str()))); + //different lengths + s1.assign("foo"); + s2.assign("foof"); + CPPUNIT_ASSERT(s1.compare(s2)<0); + CPPUNIT_ASSERT_EQUAL(sign(s1.compare(s2)),sign(strcmp(s1.c_str(),s2.c_str()))); + CPPUNIT_ASSERT(s1 < s2); +} + +void +testSBuf::testConsume() +{ + SBuf s1(literal),s2,s3; + s2=s1.consume(4); + s3.assign("The "); + CPPUNIT_ASSERT_EQUAL(s2,s3); + s3.assign("quick brown fox jumped over the lazy dog"); + CPPUNIT_ASSERT_EQUAL(s1,s3); + s1.consume(40); + CPPUNIT_ASSERT_EQUAL(s1,SBuf()); +} + +void +testSBuf::testRawContent() +{ + SBuf s1(literal); + SBuf s2(s1); + s2.append("foo"); + const char *foo; + foo = s1.rawContent(); + CPPUNIT_ASSERT(strncmp(fox,foo,s1.length())==0); + foo = s1.c_str(); + CPPUNIT_ASSERT(!strcmp(fox,foo)); +} + +void +testSBuf::testRawSpace() +{ + SBuf s1(literal); + SBuf s2(fox1); + char *rb=s2.rawSpace(strlen(fox2)+1); + strcat(rb,fox2); + CPPUNIT_ASSERT_EQUAL(s1,s2); +} + +void +testSBuf::testChop() +{ + SBuf s1(literal),s2; + s1.chop(4,5); + s2.assign("quick"); + CPPUNIT_ASSERT_EQUAL(s1,s2); + s1=literal; + s2.clear(); + s1.chop(5,0); + CPPUNIT_ASSERT_EQUAL(s1,s2); +} + +void +testSBuf::testChomp() +{ + SBuf s1("complete string"); + SBuf s2(s1); + s2.trim(SBuf(" ,")); + CPPUNIT_ASSERT_EQUAL(s1,s2); + s2.assign(" complete string ,"); + s2.trim(SBuf(" ,")); + CPPUNIT_ASSERT_EQUAL(s1,s2); + s1.assign(", complete string ,"); + s2=s1; + s2.trim(SBuf(" ")); + CPPUNIT_ASSERT_EQUAL(s1,s2); +} + +void +testSBuf::testSubstr() +{ + SBuf s1(literal),s2,s3; + s2=s1.substr(4,5); + s3.assign("quick"); + CPPUNIT_ASSERT_EQUAL(s2,s3); + s1.chop(4,5); + CPPUNIT_ASSERT_EQUAL(s1,s2); +} + +void +testSBuf::testFindChar() +{ + const char *alphabet="abcdefghijklmnopqrstuvwxyz"; + SBuf s1(alphabet); + SBuf::size_type idx; + SBuf::size_type nposResult=SBuf::npos; + + // FORWARD SEARCH + // needle in haystack + idx=s1.find('d'); + CPPUNIT_ASSERT(idx == 3); + CPPUNIT_ASSERT(s1[idx]=='d'); + + // needle not present in haystack + idx=s1.find(' '); //fails + CPPUNIT_ASSERT_EQUAL(nposResult,idx); + + // search in portion + idx=s1.find('e',3); + CPPUNIT_ASSERT_EQUAL(4,idx); + + // char not in searched portion + idx=s1.find('e',5); + CPPUNIT_ASSERT_EQUAL(nposResult,idx); + + // invalid start position + idx=s1.find('d',SBuf::npos); + CPPUNIT_ASSERT_EQUAL(nposResult,idx); + + // invalid start position + idx=s1.find('d', -5); + CPPUNIT_ASSERT_EQUAL(3, idx); + + // search outside of haystack + idx=s1.find('d',s1.length()+1); + CPPUNIT_ASSERT_EQUAL(nposResult,idx); + + // REVERSE SEARCH + // needle in haystack + idx=s1.rfind('d'); + CPPUNIT_ASSERT_EQUAL(3, idx); + CPPUNIT_ASSERT_EQUAL('d', s1[idx]); + + // needle not present in haystack + idx=s1.rfind(' '); //fails + CPPUNIT_ASSERT_EQUAL(nposResult,idx); + + // search in portion + idx=s1.rfind('e',5); + CPPUNIT_ASSERT_EQUAL(4,idx); + + // char not in searched portion + idx=s1.rfind('e',3); + CPPUNIT_ASSERT_EQUAL(nposResult,idx); + + // invalid start position + idx=s1.rfind('d', -5); + CPPUNIT_ASSERT_EQUAL(nposResult,idx); + + // overlong haystack specification + idx=s1.rfind('d',s1.length()+1); + CPPUNIT_ASSERT_EQUAL(3,idx); +} + +void +testSBuf::testFindSBuf() +{ + const char *alphabet="abcdefghijklmnopqrstuvwxyz"; + SBuf haystack(alphabet); + SBuf::size_type idx; + SBuf::size_type nposResult=SBuf::npos; + + // FORWARD search + // needle in haystack + idx = haystack.find(SBuf("def")); + CPPUNIT_ASSERT_EQUAL(3,idx); + + idx = haystack.find(SBuf("xyz")); + CPPUNIT_ASSERT_EQUAL(23,idx); + + // needle not in haystack, no initial char match + idx = haystack.find(SBuf(" eq")); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // needle not in haystack, initial sequence match + idx = haystack.find(SBuf("deg")); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // needle past end of haystack + idx = haystack.find(SBuf("xyz1")); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // search in portion: needle not in searched part + idx = haystack.find(SBuf("def"),7); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // search in portion: overhang + idx = haystack.find(SBuf("def"),4); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // invalid start position + idx = haystack.find(SBuf("def"),SBuf::npos); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // invalid start position: negative + idx = haystack.find(SBuf("def"),-5); + CPPUNIT_ASSERT_EQUAL(3, idx); + + // needle bigger than haystack + idx = SBuf("def").find(haystack); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // search in a double-matching haystack + { + SBuf h2=haystack; + h2.append(haystack); + + idx = h2.find(SBuf("def")); + CPPUNIT_ASSERT_EQUAL(3,idx); + + idx = h2.find(SBuf("xyzab")); + CPPUNIT_ASSERT_EQUAL(23,idx); + } + + + // REVERSE search + // needle in haystack + idx = haystack.rfind(SBuf("def")); + CPPUNIT_ASSERT_EQUAL(3,idx); + + idx = haystack.rfind(SBuf("xyz")); + CPPUNIT_ASSERT_EQUAL(23,idx); + + // needle not in haystack, no initial char match + idx = haystack.rfind(SBuf(" eq")); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // needle not in haystack, initial sequence match + idx = haystack.rfind(SBuf("deg")); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // needle past end of haystack + idx = haystack.rfind(SBuf("xyz1")); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // search in portion: needle in searched part + idx = haystack.rfind(SBuf("def"),7); + CPPUNIT_ASSERT_EQUAL(3, idx); + + // search in portion: needle not in searched part + idx = haystack.rfind(SBuf("mno"),3); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // search in portion: overhang + idx = haystack.rfind(SBuf("def"),4); + CPPUNIT_ASSERT_EQUAL(3, idx); + + // npos start position + idx = haystack.rfind(SBuf("def"),SBuf::npos); + CPPUNIT_ASSERT_EQUAL(3, idx); + + // invalid start position: negative + idx = haystack.rfind(SBuf("def"),-5); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // needle bigger than haystack + idx = SBuf("def").rfind(haystack); + CPPUNIT_ASSERT_EQUAL(nposResult, idx); + + // search in a double-matching haystack + { + SBuf h2=haystack; + h2.append(haystack); + + idx = h2.rfind(SBuf("def")); + CPPUNIT_ASSERT_EQUAL(29,idx); + + idx = h2.find(SBuf("xyzab")); + CPPUNIT_ASSERT_EQUAL(23,idx); + } +} + +void +testSBuf::testRFindChar() +{ + SBuf s1(literal); + SBuf::size_type idx; + idx=s1.rfind(' '); + CPPUNIT_ASSERT_EQUAL(40,idx); + CPPUNIT_ASSERT_EQUAL(' ',s1[idx]); +} + +void +testSBuf::testRFindSBuf() +{ + SBuf haystack(literal),afox("fox"); + SBuf goobar("goobar"); + SBuf::size_type idx; + + // corner case: search for a zero-length SBuf + idx=haystack.rfind(SBuf("")); + CPPUNIT_ASSERT_EQUAL(haystack.length(),idx); + + // corner case: search for a needle longer than the haystack + idx=afox.rfind(SBuf(" ")); + CPPUNIT_ASSERT(idx==SBuf::npos); + + idx=haystack.rfind(SBuf("fox")); + CPPUNIT_ASSERT_EQUAL(16,idx); + + // needle not found, no match for first char + idx=goobar.rfind(SBuf("foo")); + CPPUNIT_ASSERT(idx==SBuf::npos); + + // needle not found, match for first char but no match for SBuf + idx=haystack.rfind(SBuf("foe")); + CPPUNIT_ASSERT(idx==SBuf::npos); + + SBuf g("g"); //match at the last char + idx=haystack.rfind(g); + CPPUNIT_ASSERT_EQUAL(43,idx); + CPPUNIT_ASSERT_EQUAL('g',haystack[idx]); + + idx=haystack.rfind(SBuf("The")); + CPPUNIT_ASSERT_EQUAL(0,idx); + + haystack.append("The"); + idx=haystack.rfind(SBuf("The")); + CPPUNIT_ASSERT_EQUAL(44,idx); + + //partial match + haystack="The quick brown fox"; + SBuf needle("foxy lady"); + idx=haystack.rfind(needle); + CPPUNIT_ASSERT(idx==SBuf::npos); +} + +void +testSBuf::testSBufLength() +{ + SBuf s(fox); + CPPUNIT_ASSERT((size_t)s.length()==strlen(fox)); +} + +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(3 == rv); + CPPUNIT_ASSERT(0 == strcmp(s,"string")); + CPPUNIT_ASSERT(i == 123); + CPPUNIT_ASSERT(f == 123.5); +} + +void testSBuf::testCopy() +{ + char buf[40]; //shorter than literal() + SBuf s(fox1),s2; + CPPUNIT_ASSERT(s.copy(buf,40)==s.length()); + CPPUNIT_ASSERT(strncmp(s.rawContent(),buf,s.length())==0); + s=literal; + CPPUNIT_ASSERT(s.copy(buf,40)==40); + s2.assign(buf,0,40); + s.chop(0,40); + CPPUNIT_ASSERT(s==s2); +} + +// XXX: please split each class into a separate unit-test .cc / even if they share a binary. +// rule-of-thumb guideline for layering is 'one class Foo -> one Foo.cc, one Foo.cc -> one testFoo.cc' + +static int sbuf_tokens_number=9; +static SBuf tokens[]={ + SBuf("The",0,3), SBuf("quick",0,5), SBuf("brown",0,5), SBuf("fox",0,3), + SBuf("jumped",0,6), SBuf("over",0,4), SBuf("the",0,3), SBuf("lazy",0,4), + SBuf("dog",0,3) +}; + +void testSBuf::testSBufTokenizer() +{ + int j=0; + SBuf s; + for (SBufTokenizer st(literal,SBuf(" ",0,1)); !st.atEnd(); st.next()) { + s=st.token(); + CPPUNIT_ASSERT(s==tokens[j]); + j++; + } + CPPUNIT_ASSERT(j==9); +} + +void testSBuf::testStringOps() +{ + SBuf sng(literal), + ref("the quick brown fox jumped over the lazy dog"); + sng=sng.toLower(); + CPPUNIT_ASSERT_EQUAL(ref,sng); + sng=literal; + CPPUNIT_ASSERT(0==sng.compare(ref,caseInsensitive)); +} + +void testSBuf::testGrow() +{ + SBuf t; + t.assign("foo"); + const char *ref=t.rawContent(); + t.reserveCapacity(10240); + const char *match=t.rawContent(); + CPPUNIT_ASSERT(match!=ref); + ref=match; + t.append(literal).append(literal).append(literal).append(literal).append(literal); + t.append(t).append(t).append(t).append(t).append(t); + CPPUNIT_ASSERT(match==ref); +} + +void testSBuf::testStartsWith() +{ + static SBuf casebuf("THE QUICK"); + CPPUNIT_ASSERT(literal.startsWith(SBuf(fox1))); + CPPUNIT_ASSERT(!SBuf("The quick brown").startsWith(SBuf(fox1))); //too short + CPPUNIT_ASSERT(!literal.startsWith(SBuf(fox2))); //wrong contents + + CPPUNIT_ASSERT(literal.startsWith(casebuf,caseInsensitive)); + casebuf=SBuf(fox1).toUpper(); + CPPUNIT_ASSERT(literal.startsWith(casebuf,caseInsensitive)); + CPPUNIT_ASSERT(literal.startsWith(SBuf(fox1),caseInsensitive)); + casebuf = "tha quick"; + CPPUNIT_ASSERT(!literal.startsWith(casebuf,caseInsensitive)); +} + +void testSBuf::testSBufList() +{ + SBufList foo; + for (int j=0; j + +#if 0 // guideline violation: only #include into .h when used by the .h +#include "SBuf.h" +#include "SBufExceptions.h" +#include "SBufList.h" +#include "SBufUtil.h" +#endif +#include "OutOfBoundsException.h" + +/* + * test the SBuf functionalities + */ + +class testSBuf : public CPPUNIT_NS::TestFixture +{ + CPPUNIT_TEST_SUITE( testSBuf ); + CPPUNIT_TEST( testSBufConstructDestruct ); + CPPUNIT_TEST( testSBufConstructDestructAfterMemInit ); + CPPUNIT_TEST( testSBufLength ); + CPPUNIT_TEST( testEqualityTest ); + CPPUNIT_TEST( testStartsWith ); + CPPUNIT_TEST( testAppendSBuf ); + CPPUNIT_TEST( testAppendCString ); + CPPUNIT_TEST( testAppendStdString ); + CPPUNIT_TEST( testAppendf ); + CPPUNIT_TEST( testSubscriptOp ); + CPPUNIT_TEST_EXCEPTION( testSubscriptOpFail , OutOfBoundsException ); + CPPUNIT_TEST( testComparisons ); + 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(); + void testRawContent(); + 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