From: Francesco Chemolli <5175948+kinkie@users.noreply.github.com> Date: Tue, 12 Dec 2023 23:25:53 +0000 (+0000) Subject: Improve and broadly use asHex() (#1578) X-Git-Tag: SQUID_7_0_1~257 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a4dd5bfacace2547497859176b6452be9a80ce00;p=thirdparty%2Fsquid.git Improve and broadly use asHex() (#1578) Replaced all raw std::hex uses for integers outside of IoManip code. Also fixed std::ostream flags restoration in AsHex printing code. Also fixed or polished a few level-2+ debugs() statements. Also added AsHex unit tests. --- diff --git a/src/Makefile.am b/src/Makefile.am index 224ac18c84..8921c4f143 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2757,3 +2757,18 @@ tests_testEventLoop_LDADD = \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testEventLoop_LDFLAGS = $(LIBADD_DL) + +check_PROGRAMS += tests/testIoManip +tests_testIoManip_SOURCES = \ + tests/testIoManip.cc +nodist_tests_testIoManip_SOURCES = \ + tests/stub_SBuf.cc \ + tests/stub_debug.cc \ + tests/stub_libmem.cc \ + tests/stub_libtime.cc +tests_testIoManip_LDADD = \ + base/libbase.la \ + $(LIBCPPUNIT_LIBS) \ + $(COMPAT_LIB) \ + $(XTRA_LIBS) +tests_testIoManip_LDFLAGS = $(LIBADD_DL) diff --git a/src/acl/ConnMark.cc b/src/acl/ConnMark.cc index 50ffd25a30..41291f427e 100644 --- a/src/acl/ConnMark.cc +++ b/src/acl/ConnMark.cc @@ -50,10 +50,10 @@ Acl::ConnMark::match(ACLChecklist *cl) for (const auto &m : marks) { if (m.matches(connmark)) { - debugs(28, 5, "found " << m << " matching " << asHex(connmark)); + debugs(28, 5, "found " << m << " matching 0x" << asHex(connmark)); return 1; } - debugs(28, 7, "skipped " << m << " mismatching " << asHex(connmark)); + debugs(28, 7, "skipped " << m << " mismatching 0x" << asHex(connmark)); } } else { debugs(28, 7, "fails: no client connection"); diff --git a/src/acl/TimeData.cc b/src/acl/TimeData.cc index 3c48934546..1852937091 100644 --- a/src/acl/TimeData.cc +++ b/src/acl/TimeData.cc @@ -11,6 +11,7 @@ #include "squid.h" #include "acl/Checklist.h" #include "acl/TimeData.h" +#include "base/IoManip.h" #include "cache_cf.h" #include "ConfigParser.h" #include "debug/Stream.h" @@ -53,7 +54,7 @@ ACLTimeData::match(time_t when) while (data) { debugs(28, 3, "aclMatchTime: checking " << t << " in " << data->start << "-" << data->stop << ", weekbits=" << - std::hex << data->weekbits); + asHex(data->weekbits)); if (t >= data->start && t <= data->stop && (data->weekbits & (1 << tm.tm_wday))) return 1; diff --git a/src/acl/external/LDAP_group/ext_ldap_group_acl.cc b/src/acl/external/LDAP_group/ext_ldap_group_acl.cc index 435a49c387..b5899bf1fd 100644 --- a/src/acl/external/LDAP_group/ext_ldap_group_acl.cc +++ b/src/acl/external/LDAP_group/ext_ldap_group_acl.cc @@ -38,6 +38,7 @@ * OpenSSL libraries linked into openldap. See http://www.openssl.org/ */ #include "squid.h" +#include "base/IoManip.h" #include "helper/protocol_defines.h" #include "rfc1738.h" #include "util.h" @@ -634,7 +635,7 @@ ldap_escape_value(const std::string &src) case '(': case ')': case '\\': - str << '\\' << std::setfill('0') << std::setw(2) << std::hex << static_cast(c); + str << '\\' << asHex(c).minDigits(2); break; default: str << c; diff --git a/src/base/IoManip.h b/src/base/IoManip.h index b34a313e7d..49312afb8f 100644 --- a/src/base/IoManip.h +++ b/src/base/IoManip.h @@ -13,6 +13,36 @@ #include #include +#include + +/// \section Custom manipulator tuning methods +/// +/// Our convenience manipulator/wrapper classes often have methods that tune +/// their "printing" effects (e.g., AsHex::minDigits()). STL streams also have +/// manipulators that tune how subsequent operator "<<" parameters are printed +/// (e.g., std::setw()). The calling code can also print various decorations +/// (i.e. prefixes and suffixes). The following principles are useful when +/// deciding what manipulator methods to add and how to implement them: +/// +/// \li Add a manipulator method if callers would otherwise have to restore +/// stream format after calling the manipulator. For example, AsHex::toUpper() +/// frees callers from doing `std::uppercase << asHex(n) << std::nouppercase`. +/// +/// \li Add a manipulator method if callers would otherwise have to use +/// conditionals to get the same effect. For example, AsList::prefixedBy() frees +/// callers from doing `(c.empty() ? "" : "/") << asList(c)`. +/// +/// \li Add a manipulator method if callers would otherwise have to repeat a +/// combination of actions to get the right effect. For example, +/// AsList::minDigits() prevents duplication of the following caller code: +/// `std::setfill('0') << std::setw(8) << asHex(n)`. +/// +/// \li Avoid adding a manipulator method that can be _fully_ replaced with a +/// _single_ caller item. For example, do not add AsX::foo() if callers can do +/// `bar << asX(y)` or `asX(y) << bar` and get exactly the same effect. +/// +/// \li Manipulators should honor existing stream formatting to the extent +/// possible (e.g., AsHex honors std::uppercase by default). /// Safely prints an object pointed to by the given pointer: [label] /// Prints nothing at all if the pointer is nil. @@ -75,13 +105,37 @@ operator <<(std::ostream &os, const RawPointerT &pd) return os; } -/// std::ostream manipulator to print integers as hex numbers prefixed by 0x +/// std::ostream manipulator to print integers and alike as hex numbers. +/// Normally used through the asHex() convenience function. template class AsHex { public: + // Without this assertion, asHex(pointer) and AsHex(3.14) compile, but their + // caller is likely confused about the actual argument type and expects + // different output. Enum values are not integral types but arguably do not + // cause similar problems. + static_assert(std::is_integral::value || std::is_enum::value); + explicit AsHex(const Integer n): io_manip(n) {} + + /// Sets the minimum number of digits to print. If the integer has fewer + /// digits than the given width, then we also print leading zero(s). + /// Otherwise, this method has no effect. + auto &minDigits(const size_t w) { forcePadding = w; return *this; } + + /// Print hex digits in upper (or, with a false parameter value, lower) case. + auto &upperCase(const bool u = true) { forceCase = u; return *this; } + Integer io_manip; ///< the integer to print + + /// \copydoc minDigits() + /// The default is to use stream's field width and stream's fill character. + std::optional forcePadding; + + /// \copydoc upperCase() + /// The default is to use stream's std::uppercase flag. + std::optional forceCase; }; template @@ -89,8 +143,24 @@ inline std::ostream & operator <<(std::ostream &os, const AsHex number) { const auto oldFlags = os.flags(); - os << std::hex << std::showbase << number.io_manip; - os.setf(oldFlags); + const auto oldFill = os.fill(); + + if (number.forceCase) + os << (*number.forceCase ? std::uppercase : std::nouppercase); + + if (number.forcePadding) { + os.width(*number.forcePadding); + os.fill('0'); + } + + // When Integer is smaller than int, the unary plus converts the stored + // value into an equivalent integer because C++ "arithmetic operators do not + // accept types smaller than int as arguments, and integral promotions are + // automatically applied". For larger integer types, plus is a no-op. + os << std::hex << +number.io_manip; + + os.fill(oldFill); + os.flags(oldFlags); return os; } diff --git a/src/base/RandomUuid.cc b/src/base/RandomUuid.cc index 7034491628..bf21c9922e 100644 --- a/src/base/RandomUuid.cc +++ b/src/base/RandomUuid.cc @@ -91,22 +91,15 @@ RandomUuid::serialize() const void RandomUuid::print(std::ostream &os) const { - const auto savedFlags = os.flags(); - const auto savedFill = os.fill('0'); - - os << std::hex; - os << - std::setw(8) << timeLow << '-' << - std::setw(4) << timeMid << '-' << - std::setw(4) << timeHiAndVersion << '-' << - std::setw(2) << +clockSeqHiAndReserved << std::setw(2) << +clockSeqLow << '-'; + asHex(timeLow).minDigits(8) << '-' << + asHex(timeMid).minDigits(4) << '-' << + asHex(timeHiAndVersion).minDigits(4) << '-' << + asHex(clockSeqHiAndReserved).minDigits(2) << + asHex(clockSeqLow).minDigits(2) << '-'; for (size_t i = 0; i < sizeof(node); ++i) - os << std::setw(2) << +node[i]; - - os.fill(savedFill); - os.flags(savedFlags); + os << asHex(node[i]).minDigits(2); } bool diff --git a/src/comm/ModDevPoll.cc b/src/comm/ModDevPoll.cc index f218526183..dd996a58f6 100644 --- a/src/comm/ModDevPoll.cc +++ b/src/comm/ModDevPoll.cc @@ -28,6 +28,7 @@ #if USE_DEVPOLL +#include "base/IoManip.h" #include "comm/Loops.h" #include "fd.h" #include "fde.h" @@ -347,8 +348,8 @@ Comm::DoSelect(int msec) 5, DEBUG_DEVPOLL ? 0 : 8, "got FD " << fd - << ",events=" << std::hex << do_poll.dp_fds[i].revents - << ",monitoring=" << devpoll_state[fd].state + << ",events=" << asHex(do_poll.dp_fds[i].revents) + << ",monitoring=" << asHex(devpoll_state[fd].state) << ",F->read_handler=" << F->read_handler << ",F->write_handler=" << F->write_handler ); diff --git a/src/comm/ModEpoll.cc b/src/comm/ModEpoll.cc index 2b9ac4bac6..23b479b6bb 100644 --- a/src/comm/ModEpoll.cc +++ b/src/comm/ModEpoll.cc @@ -33,6 +33,7 @@ #if USE_EPOLL #include "base/CodeContext.h" +#include "base/IoManip.h" #include "comm/Loops.h" #include "fde.h" #include "globals.h" @@ -253,7 +254,7 @@ Comm::DoSelect(int msec) F = &fd_table[fd]; CodeContext::Reset(F->codeContext); debugs(5, DEBUG_EPOLL ? 0 : 8, "got FD " << fd << " events=" << - std::hex << cevents->events << " monitoring=" << F->epoll_state << + asHex(cevents->events) << " monitoring=" << asHex(F->epoll_state) << " F->read_handler=" << F->read_handler << " F->write_handler=" << F->write_handler); // TODO: add EPOLLPRI?? diff --git a/src/dns_internal.cc b/src/dns_internal.cc index e684e1433b..fb09f8cc27 100644 --- a/src/dns_internal.cc +++ b/src/dns_internal.cc @@ -11,6 +11,7 @@ #include "squid.h" #include "base/CodeContext.h" #include "base/InstanceId.h" +#include "base/IoManip.h" #include "base/Random.h" #include "base/RunnersRegistry.h" #include "comm.h" @@ -1169,7 +1170,7 @@ idnsGrokReply(const char *buf, size_t sz, int /*from_ns*/) return; } - debugs(78, 3, "idnsGrokReply: QID 0x" << std::hex << message->id << ", " << std::dec << n << " answers"); + debugs(78, 3, "idnsGrokReply: QID 0x" << asHex(message->id) << ", " << n << " answers"); idns_query *q = idnsFindQuery(message->id); @@ -1423,8 +1424,7 @@ idnsCheckQueue(void *) } debugs(78, 3, "idnsCheckQueue: ID " << q->xact_id << - " QID 0x" << std::hex << std::setfill('0') << - std::setw(4) << q->query_id << ": timeout" ); + " QID 0x" << asHex(q->query_id).minDigits(4) << ": timeout"); dlinkDelete(&q->lru, &lru_list); q->pending = 0; @@ -1433,8 +1433,8 @@ idnsCheckQueue(void *) idnsSendQuery(q); } else { debugs(78, 2, "idnsCheckQueue: ID " << q->xact_id << - " QID 0x" << std::hex << q->query_id << - " : giving up after " << std::dec << q->nsends << " tries and " << + " QID 0x" << asHex(q->query_id) << + ": giving up after " << q->nsends << " tries and " << std::setw(5)<< std::setprecision(2) << tvSubDsec(q->start_t, current_time) << " seconds"); if (q->rcode != 0) @@ -1727,7 +1727,7 @@ idnsSendSlaveAAAAQuery(idns_query *master) q->sz = rfc3596BuildAAAAQuery(q->name, q->buf, sizeof(q->buf), q->query_id, &q->query, Config.dns.packet_max); debugs(78, 3, "buf is " << q->sz << " bytes for " << q->name << - ", id = 0x" << std::hex << q->query_id); + ", id = 0x" << asHex(q->query_id)); if (!q->sz) { delete q; return; @@ -1791,7 +1791,7 @@ idnsALookup(const char *name, IDNSCB * callback, void *data) } debugs(78, 3, "idnsALookup: buf is " << q->sz << " bytes for " << q->name << - ", id = 0x" << std::hex << q->query_id); + ", id = 0x" << asHex(q->query_id)); idnsCheckMDNS(q); idnsStartQuery(q, callback, data); @@ -1834,7 +1834,7 @@ idnsPTRLookup(const Ip::Address &addr, IDNSCB * callback, void *data) } debugs(78, 3, "idnsPTRLookup: buf is " << q->sz << " bytes for " << ip << - ", id = 0x" << std::hex << q->query_id); + ", id = 0x" << asHex(q->query_id)); q->permit_mdns = Config.onoff.dns_mdns; idnsStartQuery(q, callback, data); diff --git a/src/error/ExceptionErrorDetail.h b/src/error/ExceptionErrorDetail.h index f64a5845af..eb75c9f263 100644 --- a/src/error/ExceptionErrorDetail.h +++ b/src/error/ExceptionErrorDetail.h @@ -9,6 +9,7 @@ #ifndef _SQUID_SRC_ERROR_EXCEPTIONERRORDETAIL_H #define _SQUID_SRC_ERROR_EXCEPTIONERRORDETAIL_H +#include "base/IoManip.h" #include "error/Detail.h" #include "sbuf/SBuf.h" #include "sbuf/Stream.h" @@ -27,11 +28,11 @@ public: /* ErrorDetail API */ SBuf brief() const override { - return ToSBuf("exception=", std::hex, exceptionId); + return ToSBuf("exception=", asHex(exceptionId)); } SBuf verbose(const HttpRequestPointer &) const override { - return ToSBuf("Exception (ID=", std::hex, exceptionId, ')'); + return ToSBuf("Exception (ID=", asHex(exceptionId), ')'); } private: diff --git a/src/eui/Eui48.cc b/src/eui/Eui48.cc index b2ab2fda5d..30b7808a16 100644 --- a/src/eui/Eui48.cc +++ b/src/eui/Eui48.cc @@ -12,6 +12,7 @@ #if USE_SQUID_EUI +#include "base/IoManip.h" #include "debug/Stream.h" #include "eui/Eui48.h" #include "globals.h" @@ -93,6 +94,30 @@ struct arpreq { * Solaris code by R. Gancarz */ +/// I/O manipulator to print EUI48 addresses +template +class AsEui48 { +public: + /// caller is responsible for the passed address storage lifetime + explicit AsEui48(const HardwareAddress * const a): hardwareAddress(a) {} + const HardwareAddress * const hardwareAddress; +}; + +template +static std::ostream & +operator <<(std::ostream &os, const AsEui48 &manipulator) +{ + const auto &ha = *manipulator.hardwareAddress; + os << + asHex(ha.sa_data[0] & 0xff).minDigits(2) << ':' << + asHex(ha.sa_data[1] & 0xff).minDigits(2) << ':' << + asHex(ha.sa_data[2] & 0xff).minDigits(2) << ':' << + asHex(ha.sa_data[3] & 0xff).minDigits(2) << ':' << + asHex(ha.sa_data[4] & 0xff).minDigits(2) << ':' << + asHex(ha.sa_data[5] & 0xff).minDigits(2); + return os; +} + bool Eui::Eui48::decode(const char *asc) { @@ -187,13 +212,7 @@ Eui::Eui48::lookup(const Ip::Address &c) return false; } - debugs(28, 4, "id=" << (void*)this << " got address "<< std::setfill('0') << std::hex << - std::setw(2) << (arpReq.arp_ha.sa_data[0] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[1] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[2] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[3] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[4] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[5] & 0xff)); + debugs(28, 4, "id=" << static_cast(this) << " got address " << AsEui48(&arpReq.arp_ha)); set(arpReq.arp_ha.sa_data, 6); return true; @@ -265,14 +284,8 @@ Eui::Eui48::lookup(const Ip::Address &c) continue; } - debugs(28, 4, "id=" << (void*)this << " got address "<< std::setfill('0') << std::hex << - std::setw(2) << (arpReq.arp_ha.sa_data[0] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[1] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[2] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[3] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[4] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[5] & 0xff) << " on "<< - std::setfill(' ') << ifr->ifr_name); + debugs(28, 4, "id=" << static_cast(this) << " got address " << AsEui48(&arpReq.arp_ha) << + " on " << ifr->ifr_name); set(arpReq.arp_ha.sa_data, 6); @@ -323,13 +336,7 @@ Eui::Eui48::lookup(const Ip::Address &c) return false; } - debugs(28, 4, "Got address "<< std::setfill('0') << std::hex << - std::setw(2) << (arpReq.arp_ha.sa_data[0] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[1] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[2] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[3] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[4] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[5] & 0xff)); + debugs(28, 4, "Got address " << AsEui48(&arpReq.arp_ha)); set(arpReq.arp_ha.sa_data, 6); return true; @@ -429,13 +436,7 @@ Eui::Eui48::lookup(const Ip::Address &c) return false; } - debugs(28, 4, "Got address "<< std::setfill('0') << std::hex << - std::setw(2) << (arpReq.arp_ha.sa_data[0] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[1] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[2] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[3] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[4] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[5] & 0xff)); + debugs(28, 4, "Got address " << AsEui48(&arpReq.arp_ha)); set(arpReq.arp_ha.sa_data, 6); return true; @@ -494,13 +495,7 @@ Eui::Eui48::lookup(const Ip::Address &c) return false; } - debugs(28, 4, "Got address "<< std::setfill('0') << std::hex << - std::setw(2) << (arpReq.arp_ha.sa_data[0] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[1] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[2] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[3] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[4] & 0xff) << ":" << - std::setw(2) << (arpReq.arp_ha.sa_data[5] & 0xff)); + debugs(28, 4, "Got address " << AsEui48(&arpReq.arp_ha)); set(arpReq.arp_ha.sa_data, 6); return true; diff --git a/src/fs/rock/RockSwapDir.cc b/src/fs/rock/RockSwapDir.cc index ad376aad26..c9403e50fa 100644 --- a/src/fs/rock/RockSwapDir.cc +++ b/src/fs/rock/RockSwapDir.cc @@ -9,6 +9,7 @@ /* DEBUG: section 47 Store Directory Routines */ #include "squid.h" +#include "base/IoManip.h" #include "cache_cf.h" #include "CollapsedForwarding.h" #include "ConfigOption.h" @@ -638,8 +639,7 @@ Rock::SwapDir::createStoreIO(StoreEntry &e, StoreIOState::STIOCB * const cbIo, v sio->writeableAnchor_ = slot; debugs(47,5, "dir " << index << " created new filen " << - std::setfill('0') << std::hex << std::uppercase << std::setw(8) << - sio->swap_filen << std::dec << " starting at " << + asHex(sio->swap_filen).upperCase().minDigits(8) << " starting at " << diskOffset(sio->swap_filen)); sio->file(theFile); @@ -667,8 +667,7 @@ Rock::SwapDir::createUpdateIO(const Ipc::StoreMapUpdate &update, StoreIOState::S sio->writeableAnchor_ = update.fresh.anchor; debugs(47,5, "dir " << index << " updating filen " << - std::setfill('0') << std::hex << std::uppercase << std::setw(8) << - sio->swap_filen << std::dec << " starting at " << + asHex(sio->swap_filen).upperCase().minDigits(8) << " starting at " << diskOffset(sio->swap_filen)); sio->file(theFile); @@ -788,8 +787,7 @@ Rock::SwapDir::openStoreIO(StoreEntry &e, StoreIOState::STIOCB * const cbIo, voi sio->file(theFile); debugs(47,5, "dir " << index << " has old filen: " << - std::setfill('0') << std::hex << std::uppercase << std::setw(8) << - sio->swap_filen); + asHex(sio->swap_filen).upperCase().minDigits(8)); // When StoreEntry::swap_filen for e was set by our anchorEntry(), e had a // public key, but it could have gone private since then (while keeping the diff --git a/src/fs/ufs/RebuildState.cc b/src/fs/ufs/RebuildState.cc index 5801a18b91..3551d53a58 100644 --- a/src/fs/ufs/RebuildState.cc +++ b/src/fs/ufs/RebuildState.cc @@ -9,6 +9,7 @@ /* DEBUG: section 47 Store Directory Routines */ #include "squid.h" +#include "base/IoManip.h" #include "fs_io.h" #include "globals.h" #include "RebuildState.h" @@ -297,9 +298,8 @@ Fs::Ufs::RebuildState::rebuildFromSwapLog() swapData.swap_filen &= 0x00FFFFFF; debugs(47, 3, swap_log_op_str[(int) swapData.op] << " " << - storeKeyText(swapData.key) << " "<< std::setfill('0') << - std::hex << std::uppercase << std::setw(8) << - swapData.swap_filen); + storeKeyText(swapData.key) << " " << + asHex(swapData.swap_filen).upperCase().minDigits(8)); if (swapData.op == SWAP_LOG_ADD) { (void) 0; @@ -356,9 +356,9 @@ Fs::Ufs::RebuildState::getNextFile(sfileno * filn_p, int *) int fd = -1; int dirs_opened = 0; debugs(47, 3, "flag=" << flags.init << ", " << - sd->index << ": /"<< std::setfill('0') << std::hex << - std::uppercase << std::setw(2) << curlvl1 << "/" << std::setw(2) << - curlvl2); + sd->index << ": /" << + asHex(curlvl1).upperCase().minDigits(2) << "/" << + asHex(curlvl2).upperCase().minDigits(2)); if (done) return -2; @@ -410,11 +410,10 @@ Fs::Ufs::RebuildState::getNextFile(sfileno * filn_p, int *) } if (!UFSSwapDir::FilenoBelongsHere(fn, sd->index, curlvl1, curlvl2)) { - debugs(47, 3, std::setfill('0') << - std::hex << std::uppercase << std::setw(8) << fn << - " does not belong in " << std::dec << sd->index << "/" << - curlvl1 << "/" << curlvl2); - + debugs(47, 3, asHex(fn).upperCase().minDigits(8) << + " does not belong in " << sd->index << "/" << + asHex(curlvl1).upperCase().minDigits(2) << "/" << + asHex(curlvl2).upperCase().minDigits(2)); continue; } diff --git a/src/fs/ufs/UFSStoreState.cc b/src/fs/ufs/UFSStoreState.cc index 01f3eac865..e6d6ec8d70 100644 --- a/src/fs/ufs/UFSStoreState.cc +++ b/src/fs/ufs/UFSStoreState.cc @@ -9,6 +9,7 @@ /* DEBUG: section 79 Storage Manager UFS Interface */ #include "squid.h" +#include "base/IoManip.h" #include "DiskIO/DiskFile.h" #include "DiskIO/DiskIOStrategy.h" #include "DiskIO/ReadRequest.h" @@ -27,10 +28,9 @@ Fs::Ufs::UFSStoreState::ioCompletedNotification() { if (opening) { opening = false; - debugs(79, 3, "UFSStoreState::ioCompletedNotification: dirno " << - swap_dirn << ", fileno "<< std::setfill('0') << std::hex << - std::setw(8) << swap_filen << " status "<< std::setfill(' ') << - std::dec << theFile->error()); + debugs(79, 3, "opening: dirno " << swap_dirn << + ", fileno " << asHex(swap_filen).minDigits(8) << + " status " << theFile->error()); assert (FILE_MODE(mode) == O_RDONLY); openDone(); @@ -40,10 +40,9 @@ Fs::Ufs::UFSStoreState::ioCompletedNotification() if (creating) { creating = false; - debugs(79, 3, "UFSStoreState::ioCompletedNotification: dirno " << - swap_dirn << ", fileno "<< std::setfill('0') << std::hex << - std::setw(8) << swap_filen << " status "<< std::setfill(' ') << - std::dec << theFile->error()); + debugs(79, 3, "creating: dirno " << swap_dirn << + ", fileno " << asHex(swap_filen).minDigits(8) << + " status " << theFile->error()); openDone(); @@ -51,9 +50,9 @@ Fs::Ufs::UFSStoreState::ioCompletedNotification() } assert (!(closing ||opening)); - debugs(79, 3, "diskd::ioCompleted: dirno " << swap_dirn << ", fileno "<< - std::setfill('0') << std::hex << std::setw(8) << swap_filen << - " status "<< std::setfill(' ') << std::dec << theFile->error()); + debugs(79, 3, "error: dirno " << swap_dirn << + ", fileno " << asHex(swap_filen).minDigits(8) << + " status " << theFile->error()); /* Ok, notification past open means an error has occurred */ assert (theFile->error()); @@ -89,10 +88,9 @@ void Fs::Ufs::UFSStoreState::closeCompleted() { assert (closing); - debugs(79, 3, "UFSStoreState::closeCompleted: dirno " << swap_dirn << - ", fileno "<< std::setfill('0') << std::hex << std::setw(8) << - swap_filen << " status "<< std::setfill(' ') << std::dec << - theFile->error()); + debugs(79, 3, "dirno " << swap_dirn << + ", fileno " << asHex(swap_filen).minDigits(8) << + " status " << theFile->error()); if (theFile->error()) { debugs(79,3, "theFile->error() ret " << theFile->error()); @@ -116,8 +114,9 @@ Fs::Ufs::UFSStoreState::closeCompleted() void Fs::Ufs::UFSStoreState::close(int) { - debugs(79, 3, "UFSStoreState::close: dirno " << swap_dirn << ", fileno "<< - std::setfill('0') << std::hex << std::uppercase << std::setw(8) << swap_filen); + // TODO: De-duplicate position printing Fs::Ufs code and fix upperCase() inconsistency. + debugs(79, 3, "dirno " << swap_dirn << + ", fileno " << asHex(swap_filen).upperCase().minDigits(8)); tryClosing(); // UFS does not distinguish different closure types } @@ -139,8 +138,8 @@ Fs::Ufs::UFSStoreState::read_(char *buf, size_t size, off_t aOffset, STRCB * aCa read.callback = aCallback; read.callback_data = cbdataReference(aCallbackData); - debugs(79, 3, "UFSStoreState::read_: dirno " << swap_dirn << ", fileno "<< - std::setfill('0') << std::hex << std::uppercase << std::setw(8) << swap_filen); + debugs(79, 3, "dirno " << swap_dirn << + ", fileno " << asHex(swap_filen).minDigits(8)); offset_ = aOffset; read_buf = buf; reading = true; @@ -158,8 +157,8 @@ Fs::Ufs::UFSStoreState::read_(char *buf, size_t size, off_t aOffset, STRCB * aCa bool Fs::Ufs::UFSStoreState::write(char const *buf, size_t size, off_t aOffset, FREE * free_func) { - debugs(79, 3, "UFSStoreState::write: dirn " << swap_dirn << ", fileno "<< - std::setfill('0') << std::hex << std::uppercase << std::setw(8) << swap_filen); + debugs(79, 3, "dirno " << swap_dirn << + ", fileno " << asHex(swap_filen).minDigits(8)); if (theFile->error()) { debugs(79, DBG_IMPORTANT, "ERROR: avoid write on theFile with error"); @@ -231,9 +230,9 @@ Fs::Ufs::UFSStoreState::readCompleted(const char *buf, int len, int, RefCount 0) offset_ += len; @@ -272,8 +271,8 @@ Fs::Ufs::UFSStoreState::readCompleted(const char *buf, int len, int, RefCount) { - debugs(79, 3, "dirno " << swap_dirn << ", fileno " << - std::setfill('0') << std::hex << std::uppercase << std::setw(8) << swap_filen << + debugs(79, 3, "dirno " << swap_dirn << + ", fileno " << asHex(swap_filen).upperCase().minDigits(8) << ", len " << len); /* * DPW 2006-05-24 diff --git a/src/fs/ufs/UFSStrategy.cc b/src/fs/ufs/UFSStrategy.cc index 5e1a30a586..e0b4a0f493 100644 --- a/src/fs/ufs/UFSStrategy.cc +++ b/src/fs/ufs/UFSStrategy.cc @@ -10,6 +10,7 @@ #include "squid.h" +#include "base/IoManip.h" #include "DiskIO/DiskIOStrategy.h" #include "UFSStoreState.h" #include "UFSStrategy.h" @@ -58,8 +59,7 @@ Fs::Ufs::UFSStrategy::open(SwapDir * const SD, StoreEntry * const e, StoreIOState::STIOCB * aCallback, void *callback_data) { assert (((UFSSwapDir *)SD)->IO == this); - debugs(79, 3, "fileno "<< std::setfill('0') << std::hex - << std::uppercase << std::setw(8) << e->swap_filen); + debugs(79, 3, "fileno " << asHex(e->swap_filen).upperCase().minDigits(8)); /* to consider: make createstate a private UFSStrategy call */ StoreIOState::Pointer sio = createState (SD, e, aCallback, callback_data); @@ -96,8 +96,7 @@ Fs::Ufs::UFSStrategy::create(SwapDir * const SD, StoreEntry * const e, assert (((UFSSwapDir *)SD)->IO == this); /* Allocate a number */ sfileno filn = ((UFSSwapDir *)SD)->mapBitAllocate(); - debugs(79, 3, "fileno "<< std::setfill('0') << - std::hex << std::uppercase << std::setw(8) << filn); + debugs(79, 3, "fileno " << asHex(filn).upperCase().minDigits(8)); /* Shouldn't we handle a 'bitmap full' error here? */ diff --git a/src/fs/ufs/UFSSwapDir.cc b/src/fs/ufs/UFSSwapDir.cc index d4e744d31a..8dd6459871 100644 --- a/src/fs/ufs/UFSSwapDir.cc +++ b/src/fs/ufs/UFSSwapDir.cc @@ -11,6 +11,7 @@ #define CLEAN_BUF_SZ 16384 #include "squid.h" +#include "base/IoManip.h" #include "base/Random.h" #include "cache_cf.h" #include "ConfigOption.h" @@ -339,8 +340,8 @@ Fs::Ufs::UFSSwapDir::~UFSSwapDir() void Fs::Ufs::UFSSwapDir::dumpEntry(StoreEntry &e) const { - debugs(47, DBG_CRITICAL, "FILENO "<< std::setfill('0') << std::hex << std::uppercase << std::setw(8) << e.swap_filen); - debugs(47, DBG_CRITICAL, "PATH " << fullPath(e.swap_filen, nullptr) ); + debugs(47, DBG_CRITICAL, "FILENO " << asHex(e.swap_filen).upperCase().minDigits(8) << + ", PATH " << fullPath(e.swap_filen, nullptr)); e.dump(0); } @@ -796,8 +797,7 @@ Fs::Ufs::UFSSwapDir::addDiskRestore(const cache_key * key, int) { StoreEntry *e = nullptr; - debugs(47, 5, storeKeyText(key) << - ", fileno="<< std::setfill('0') << std::hex << std::uppercase << std::setw(8) << file_number); + debugs(47, 5, storeKeyText(key) << ", fileno=" << asHex(file_number).upperCase().minDigits(8)); /* if you call this you'd better be sure file_number is not * already in use! */ e = new StoreEntry(); @@ -1161,8 +1161,7 @@ Fs::Ufs::UFSSwapDir::validFileno(sfileno filn, int flag) const void Fs::Ufs::UFSSwapDir::unlinkFile(sfileno f) { - debugs(79, 3, "unlinking fileno " << std::setfill('0') << - std::hex << std::uppercase << std::setw(8) << f << " '" << + debugs(79, 3, "unlinking fileno " << asHex(f).upperCase().minDigits(8) << " '" << fullPath(f,nullptr) << "'"); /* commonUfsDirMapBitReset(this, f); */ IO->unlinkFile(fullPath(f,nullptr)); @@ -1375,7 +1374,7 @@ Fs::Ufs::UFSSwapDir::DirClean(int swap_index) k = 10; for (n = 0; n < k; ++n) { - debugs(36, 3, "Cleaning file "<< std::setfill('0') << std::hex << std::uppercase << std::setw(8) << files[n]); + debugs(36, 3, "Cleaning file " << asHex(files[n]).upperCase().minDigits(8)); SBuf p2(p1); p2.appendf("/%08X", files[n]); safeunlink(p2.c_str(), 0); diff --git a/src/ip/QosConfig.cc b/src/ip/QosConfig.cc index 2e4b256474..c9c75b6795 100644 --- a/src/ip/QosConfig.cc +++ b/src/ip/QosConfig.cc @@ -97,7 +97,7 @@ getNfmarkCallback(enum nf_conntrack_msg_type, struct nf_conntrack *ct, void *con { auto *mark = static_cast(connmark); *mark = nfct_get_attr_u32(ct, ATTR_MARK); - debugs(17, 3, asHex(*mark)); + debugs(17, 3, "mark=0x" << asHex(*mark)); return NFCT_CB_CONTINUE; } diff --git a/src/ipc/StoreMap.cc b/src/ipc/StoreMap.cc index cd18ce2c00..6be0694dcd 100644 --- a/src/ipc/StoreMap.cc +++ b/src/ipc/StoreMap.cc @@ -9,6 +9,7 @@ /* DEBUG: section 54 Interprocess Communication */ #include "squid.h" +#include "base/IoManip.h" #include "ipc/StoreMap.h" #include "sbuf/SBuf.h" #include "SquidConfig.h" @@ -856,7 +857,7 @@ Ipc::StoreMap::validateHit(const sfileno fileno) " expires=" << anchor.basics.expires << "\n" << " lastmod=" << anchor.basics.lastmod << "\n" << " refcount=" << anchor.basics.refcount << "\n" << - " flags=0x" << std::hex << anchor.basics.flags << std::dec << "\n" << + " flags=0x" << asHex(anchor.basics.flags) << "\n" << " start=" << anchor.start << "\n" << " splicingPoint=" << anchor.splicingPoint << "\n" << " lock=" << anchor.lock << "\n" << diff --git a/src/ipcache.cc b/src/ipcache.cc index 18cd5c3461..41290fdaee 100644 --- a/src/ipcache.cc +++ b/src/ipcache.cc @@ -9,6 +9,7 @@ /* DEBUG: section 14 IP Cache */ #include "squid.h" +#include "base/IoManip.h" #include "CacheManager.h" #include "cbdata.h" #include "debug/Messages.h" @@ -729,7 +730,7 @@ ipcache_gethostbyname(const char *name, int flags) { ipcache_entry *i = nullptr; assert(name); - debugs(14, 3, "ipcache_gethostbyname: '" << name << "', flags=" << std::hex << flags); + debugs(14, 3, "'" << name << "', flags=" << asHex(flags)); ++IpcacheStats.requests; i = ipcache_get(name); diff --git a/src/security/ErrorDetail.cc b/src/security/ErrorDetail.cc index 1629815256..2884abb492 100644 --- a/src/security/ErrorDetail.cc +++ b/src/security/ErrorDetail.cc @@ -453,7 +453,7 @@ Security::ErrorDetail::ErrorDetail(const ErrorCode err, const int aSysErrorNo): #if USE_OPENSSL /// Extract and remember errors stored internally by the TLS library. if ((lib_error_no = ERR_get_error())) { - debugs(83, 7, "got " << asHex(lib_error_no)); + debugs(83, 7, "got 0x" << asHex(lib_error_no)); // more errors may be stacked // TODO: Save/detail all stacked errors by always flushing stale ones. ForgetErrors(); @@ -507,7 +507,7 @@ Security::ErrorDetail::brief() const #if USE_OPENSSL // TODO: Log ERR_error_string_n() instead, despite length, whitespace? // Example: `error:1408F09C:SSL routines:ssl3_get_record:http request`. - os << "+TLS_LIB_ERR=" << std::hex << std::uppercase << lib_error_no << std::nouppercase << std::dec; + os << "+TLS_LIB_ERR=" << asHex(lib_error_no).upperCase(); #elif USE_GNUTLS os << '+' << gnutls_strerror_name(lib_error_no); #endif diff --git a/src/security/Handshake.cc b/src/security/Handshake.cc index 1b7f095d8c..264479ffc4 100644 --- a/src/security/Handshake.cc +++ b/src/security/Handshake.cc @@ -127,9 +127,9 @@ ParseProtocolVersionBase(Parser::BinaryTokenizer &tk, const char *contextLabel, /* handle unsupported versions */ const uint16_t vRaw = (vMajor << 8) | vMinor; - debugs(83, 7, "unsupported: " << asHex(vRaw)); + debugs(83, 7, "unsupported: 0x" << asHex(vRaw)); if (beStrict) - throw TextException(ToSBuf("unsupported TLS version: ", asHex(vRaw)), Here()); + throw TextException(ToSBuf("unsupported TLS version: 0x", asHex(vRaw)), Here()); // else hide unsupported version details from the caller behind PROTO_NONE return AnyP::ProtocolVersion(); } diff --git a/src/security/ServerOptions.cc b/src/security/ServerOptions.cc index 8d9a8f4583..2118058edd 100644 --- a/src/security/ServerOptions.cc +++ b/src/security/ServerOptions.cc @@ -8,6 +8,7 @@ #include "squid.h" #include "anyp/PortCfg.h" +#include "base/IoManip.h" #include "base/Packable.h" #include "cache_cf.h" #include "error/SysErrorDetail.h" @@ -380,7 +381,7 @@ Security::ServerOptions::loadDhParams() int codes; if (DH_check(dhp, &codes) == 0) { if (codes) { - debugs(83, DBG_IMPORTANT, "WARNING: Failed to verify DH parameters '" << dhParamsFile << "' (" << std::hex << codes << ")"); + debugs(83, DBG_IMPORTANT, "WARNING: Failed to verify DH parameters '" << dhParamsFile << "' (" << asHex(codes) << ")"); DH_free(dhp); dhp = nullptr; } diff --git a/src/ssl/bio.cc b/src/ssl/bio.cc index 0791b8b6c8..3f045ed0ed 100644 --- a/src/ssl/bio.cc +++ b/src/ssl/bio.cc @@ -9,6 +9,7 @@ /* DEBUG: section 83 SSL accelerator support */ #include "squid.h" +#include "base/IoManip.h" #include "ssl/support.h" /* support.cc says this is needed */ @@ -160,7 +161,7 @@ Ssl::Bio::stateChanged(const SSL *ssl, int where, int) // else if (where & SSL_CB_HANDSHAKE_DONE) // debugs(83, 9, "SSL connection established"); - debugs(83, 7, "FD " << fd_ << " now: 0x" << std::hex << where << std::dec << ' ' << + debugs(83, 7, "FD " << fd_ << " now: 0x" << asHex(where) << ' ' << SSL_state_string(ssl) << " (" << SSL_state_string_long(ssl) << ")"); } diff --git a/src/ssl/gadgets.cc b/src/ssl/gadgets.cc index 3ebe1666de..0d6c6f1838 100644 --- a/src/ssl/gadgets.cc +++ b/src/ssl/gadgets.cc @@ -35,7 +35,7 @@ Ssl::ReportAndForgetErrors(std::ostream &os) { unsigned int reported = 0; // efficiently marks ForgetErrors() call boundary while (const auto errorToForget = ERR_get_error()) - os << Debug::Extra << "OpenSSL-saved error #" << (++reported) << ": " << asHex(errorToForget); + os << Debug::Extra << "OpenSSL-saved error #" << (++reported) << ": 0x" << asHex(errorToForget); return os; } diff --git a/src/store.cc b/src/store.cc index 9083f22308..97d2e87c61 100644 --- a/src/store.cc +++ b/src/store.cc @@ -10,6 +10,7 @@ #include "squid.h" #include "base/AsyncCbdataCalls.h" +#include "base/IoManip.h" #include "base/PackableStream.h" #include "base/TextException.h" #include "CacheDigest.h" @@ -1927,8 +1928,7 @@ StoreEntry::attachToDisk(const sdirno dirn, const sfileno fno, const swap_status { debugs(88, 3, "attaching entry with key " << getMD5Text() << " : " << swapStatusStr[status] << " " << dirn << " " << - std::hex << std::setw(8) << std::setfill('0') << - std::uppercase << fno); + asHex(fno).upperCase().minDigits(8)); checkDisk(); swap_dirn = dirn; swap_filen = fno; diff --git a/src/store/Disks.cc b/src/store/Disks.cc index 1fa4756ca8..b25ad253e5 100644 --- a/src/store/Disks.cc +++ b/src/store/Disks.cc @@ -9,6 +9,7 @@ /* DEBUG: section 47 Store Directory Routines */ #include "squid.h" +#include "base/IoManip.h" #include "cache_cf.h" #include "ConfigParser.h" #include "debug/Messages.h" @@ -853,7 +854,7 @@ storeDirSwapLog(const StoreEntry * e, int op) swap_log_op_str[op] << " " << e->getMD5Text() << " " << e->swap_dirn << " " << - std::hex << std::uppercase << std::setfill('0') << std::setw(8) << e->swap_filen); + asHex(e->swap_filen).upperCase().minDigits(8)); e->disk().logEntry(*e, op); } diff --git a/src/store_swapout.cc b/src/store_swapout.cc index 5c61e7238a..d65c5f50d6 100644 --- a/src/store_swapout.cc +++ b/src/store_swapout.cc @@ -9,6 +9,7 @@ /* DEBUG: section 20 Storage Manager Swapout Functions */ #include "squid.h" +#include "base/IoManip.h" #include "cbdata.h" #include "CollapsedForwarding.h" #include "globals.h" @@ -44,8 +45,7 @@ storeSwapOutStart(StoreEntry * e) * metadata there is to store */ debugs(20, 5, "storeSwapOutStart: Begin SwapOut '" << e->url() << "' to dirno " << - e->swap_dirn << ", fileno " << std::hex << std::setw(8) << std::setfill('0') << - std::uppercase << e->swap_filen); + e->swap_dirn << ", fileno " << asHex(e->swap_filen).upperCase().minDigits(8)); /* If we start swapping out objects with OutOfBand Metadata, * then this code needs changing */ @@ -279,8 +279,8 @@ storeSwapOutFileClosed(void *data, int errflag, StoreIOState::Pointer self) // if object_size is still unknown, the entry was probably aborted if (errflag || e->objectLen() < 0) { debugs(20, 2, "storeSwapOutFileClosed: dirno " << e->swap_dirn << ", swapfile " << - std::hex << std::setw(8) << std::setfill('0') << std::uppercase << - e->swap_filen << ", errflag=" << errflag); + asHex(e->swap_filen).upperCase().minDigits(8) << + ", errflag=" << errflag); if (errflag == DISK_NO_SPACE_LEFT) { /* TODO: this should be handle by the link from store IO to @@ -298,8 +298,7 @@ storeSwapOutFileClosed(void *data, int errflag, StoreIOState::Pointer self) } else { /* swapping complete */ debugs(20, 3, "storeSwapOutFileClosed: SwapOut complete: '" << e->url() << "' to " << - e->swap_dirn << ", " << std::hex << std::setw(8) << std::setfill('0') << - std::uppercase << e->swap_filen); + e->swap_dirn << ", " << asHex(e->swap_filen).upperCase().minDigits(8)); debugs(20, 5, "swap_file_sz = " << e->objectLen() << " + " << mem->swap_hdr_sz); diff --git a/src/tests/testIoManip.cc b/src/tests/testIoManip.cc new file mode 100644 index 0000000000..22abc60993 --- /dev/null +++ b/src/tests/testIoManip.cc @@ -0,0 +1,161 @@ +/* + * 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/IoManip.h" +#include "compat/cppunit.h" +#include "unitTestMain.h" + +#include +#include +#include + +class TestIoManip: public CPPUNIT_NS::TestFixture +{ + CPPUNIT_TEST_SUITE(TestIoManip); + CPPUNIT_TEST(testAsHex); + CPPUNIT_TEST_SUITE_END(); + +protected: + void testAsHex(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION( TestIoManip ); + +/// resets the given stream, including any formatting +static void +resetStream(std::ostringstream &dirty) +{ + std::ostringstream clean; + dirty.swap(clean); +} + +/// returns the result of printing the given manipulator +template +static std::string +toStdString(const AsHex &manipulator) +{ + std::ostringstream os; + os << manipulator; + return os.str(); +} + +void +TestIoManip::testAsHex() +{ + // basic values + CPPUNIT_ASSERT_EQUAL(std::string("0"), toStdString(asHex(0))); + CPPUNIT_ASSERT_EQUAL(std::string("123abc"), toStdString(asHex(0x123abc))); + + // large values + CPPUNIT_ASSERT_EQUAL(std::string("7fffffffffffffff"), toStdString(asHex(std::numeric_limits::max()))); + CPPUNIT_ASSERT_EQUAL(std::string("ffffffffffffffff"), toStdString(asHex(std::numeric_limits::max()))); + + // negative values + // C++ defines printing with std::hex in terms of calling std::printf() with + // %x (or %X) conversion specifier; printf(%x) interprets its value argument + // as an unsigned integer, making it impossible for std::hex to print + // negative values as negative hex integers. AsHex has the same limitation. + CPPUNIT_ASSERT_EQUAL(std::string("80000000"), toStdString(asHex(std::numeric_limits::min()))); + + // integer and integer-like types that std::ostream formats specially by default + CPPUNIT_ASSERT_EQUAL(std::string("0"), toStdString(asHex(false))); + CPPUNIT_ASSERT_EQUAL(std::string("1"), toStdString(asHex(true))); + CPPUNIT_ASSERT_EQUAL(std::string("5a"), toStdString(asHex('Z'))); + CPPUNIT_ASSERT_EQUAL(std::string("77"), toStdString(asHex(int8_t(0x77)))); + CPPUNIT_ASSERT_EQUAL(std::string("ff"), toStdString(asHex(uint8_t(0xFF)))); + + // other interesting integer-like types we may want to print + enum { enumValue = 0xABCD }; + CPPUNIT_ASSERT_EQUAL(std::string("abcd"), toStdString(asHex(enumValue))); + struct { uint8_t bitField:2; } s; + s.bitField = 3; // TODO: Convert to default initializer after switching to C++20. + CPPUNIT_ASSERT_EQUAL(std::string("3"), toStdString(asHex(s.bitField))); + + // padding with zeros works + CPPUNIT_ASSERT_EQUAL(std::string("1"), toStdString(asHex(1).minDigits(1))); + CPPUNIT_ASSERT_EQUAL(std::string("01"), toStdString(asHex(1).minDigits(2))); + CPPUNIT_ASSERT_EQUAL(std::string("001"), toStdString(asHex(1).minDigits(3))); + + // padding with zeros works even for zero values + CPPUNIT_ASSERT_EQUAL(std::string("0000"), toStdString(asHex(0).minDigits(4))); + + // minDigits() does not truncate + CPPUNIT_ASSERT_EQUAL(std::string("1"), toStdString(asHex(0x1).minDigits(0))); + CPPUNIT_ASSERT_EQUAL(std::string("12"), toStdString(asHex(0x12).minDigits(1))); + CPPUNIT_ASSERT_EQUAL(std::string("123"), toStdString(asHex(0x123).minDigits(2))); + + // upperCase() forces uppercase + CPPUNIT_ASSERT_EQUAL(std::string("A"), toStdString(asHex(0xA).upperCase())); + CPPUNIT_ASSERT_EQUAL(std::string("1A2B"), toStdString(asHex(0x1a2b).upperCase(true))); + + std::ostringstream ss; + + // upperCase(false) forces lowercase + ss << std::uppercase << asHex(0xABC).upperCase(false); + CPPUNIT_ASSERT_EQUAL(std::string("abc"), ss.str()); + resetStream(ss); + + // a combination of formatting options + CPPUNIT_ASSERT_EQUAL(std::string("01A"), toStdString(asHex(0x1A).upperCase().minDigits(3))); + + // Test the effects of stream formatting flags on AsHex printing and the + // effects of AsHex printing on stream formatting flags. + + // upperCase() effects are not leaked into the stream + ss << asHex(0xa0).upperCase() << asHex(0xa0); + CPPUNIT_ASSERT_EQUAL(std::string("A0a0"), ss.str()); + resetStream(ss); + + // original std::showbase is honored + ss << std::showbase << asHex(1); + CPPUNIT_ASSERT_EQUAL(std::string("0x1"), ss.str()); + resetStream(ss); + + // original std::uppercase is honored + ss << std::uppercase << std::hex << 0xA << asHex(0xB) << 0xC; + CPPUNIT_ASSERT_EQUAL(std::string("ABC"), ss.str()); + resetStream(ss); + + // original std::uppercase is preserved + ss << std::uppercase << std::hex << 0xA << asHex(0xB).upperCase(false) << 0xC; + CPPUNIT_ASSERT_EQUAL(std::string("AbC"), ss.str()); + resetStream(ss); + + // original std::oct is preserved + ss << std::oct << 9 << asHex(0xA) << 11; + CPPUNIT_ASSERT_EQUAL(std::string("11a13"), ss.str()); + resetStream(ss); + + // original std::setw() is honored + ss << std::setw(4) << asHex(0x1); + CPPUNIT_ASSERT_EQUAL(std::string(" 1"), ss.str()); + resetStream(ss); + + // original std::setw() is consumed (by the printed number) + ss << std::setw(4) << asHex(0x1) << 2; + CPPUNIT_ASSERT_EQUAL(std::string(" 12"), ss.str()); + resetStream(ss); + + // original std::setfill() is honored + ss << std::setfill('.') << std::setw(4) << asHex(0x2); + CPPUNIT_ASSERT_EQUAL(std::string("...2"), ss.str()); + resetStream(ss); + + // original std::setfill() is preserved + ss << std::setfill('.') << asHex(0x3).minDigits(2) << std::setw(4) << 4; + CPPUNIT_ASSERT_EQUAL(std::string("03...4"), ss.str()); + resetStream(ss); +} + +int +main(int argc, char *argv[]) +{ + return TestProgram().run(argc, argv); +} +