This is an automated email from the ASF dual-hosted git repository.

swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git


The following commit(s) were added to refs/heads/master by this push:
     new d358f087 Add some name abbreviation test cases (#747)
d358f087 is described below

commit d358f08706aae3b67c75a884c4fe7c5ef5ca4e88
Author: Stephen Webb <[email protected]>
AuthorDate: Sun Aug 30 10:15:45 2026 +1000

    Add some name abbreviation test cases (#747)
---
 src/main/cpp/nameabbreviator.cpp   |  43 ++++-
 src/test/cpp/patternlayouttest.cpp | 346 +++++++++++++++++++++++++++++++++++++
 2 files changed, 380 insertions(+), 9 deletions(-)

diff --git a/src/main/cpp/nameabbreviator.cpp b/src/main/cpp/nameabbreviator.cpp
index fca76985..38c8ae90 100644
--- a/src/main/cpp/nameabbreviator.cpp
+++ b/src/main/cpp/nameabbreviator.cpp
@@ -89,7 +89,7 @@ class MaxElementAbbreviator : public NameAbbreviator
                 * Create new instance.
                 * @param count maximum number of path elements to output.
                 */
-               MaxElementAbbreviator(const int count1) : count(count1)
+               MaxElementAbbreviator(const int count1) : count(count1 < 1 ? 1 
: count1)
                {
                }
 
@@ -193,6 +193,37 @@ class PatternAbbreviatorFragment
 
                        return nextDot;
                }
+
+               /**
+                * Abbreviate all '.' separated elements of \c buf from \c 
startPos.
+                * @param buf buffer to receive element.
+                * @param startPos starting index of name element.
+                */
+               void abbreviateAll(LogString& buf, LogString::size_type 
startPos) const
+               {
+                       logchar separ = 0x2E; /* '.' */
+                       LogString abbreviation;
+                       abbreviation.reserve(buf.length());
+                       auto pos = startPos;
+                       LogString::size_type nextDot;
+                       while (pos < buf.length() && buf.npos != (nextDot = 
buf.find(separ, pos)))
+                       {
+                               if (pos + charCount < nextDot)
+                               {
+                                       abbreviation.append(buf.begin() + pos, 
buf.begin() + (pos + charCount));
+                                       if (ellipsis != 0x00)
+                                               abbreviation.append(1, 
ellipsis);
+                               }
+                               else
+                                       abbreviation.append(buf.begin() + pos, 
buf.begin() + nextDot);
+                               abbreviation.append(1, separ);
+                               pos = nextDot + 1;
+                       }
+                       if (pos < buf.length())
+                               abbreviation.append(buf.begin() + pos, 
buf.end());
+                       buf.erase(buf.begin() + startPos, buf.end());
+                       buf.append(abbreviation);
+               }
 };
 
 /**
@@ -246,15 +277,9 @@ class PatternAbbreviator : public NameAbbreviator
                        }
 
                        //
-                       //   last pattern in executed repeatedly
+                       //   apply the last pattern to all the remaining name 
parts
                        //
-                       PatternAbbreviatorFragment terminalFragment =
-                               fragments[fragments.size() - 1];
-
-                       while (pos < buf.length())
-                       {
-                               pos = terminalFragment.abbreviate(buf, pos);
-                       }
+                       fragments.back().abbreviateAll(buf, pos);
                }
 };
 }
diff --git a/src/test/cpp/patternlayouttest.cpp 
b/src/test/cpp/patternlayouttest.cpp
index d07d8887..0602660b 100644
--- a/src/test/cpp/patternlayouttest.cpp
+++ b/src/test/cpp/patternlayouttest.cpp
@@ -39,6 +39,7 @@
 #include "testchar.h"
 #include "logunit.h"
 #include <log4cxx/spi/loggerrepository.h>
+#include <log4cxx/spi/loggingevent.h>
 #include <log4cxx/helpers/stringhelper.h>
 
 
@@ -82,6 +83,23 @@ LOGUNIT_CLASS(PatternLayoutTest)
        LOGUNIT_TEST(test14);
        LOGUNIT_TEST(testMDC1);
        LOGUNIT_TEST(testMDC2);
+       LOGUNIT_TEST(testAbbreviateNoDots);
+       LOGUNIT_TEST(testAbbreviatePrecisionExceedsSegments);
+       LOGUNIT_TEST(testAbbreviateKeepLastTwoSegments);
+       LOGUNIT_TEST(testAbbreviateKeepLastSegmentOnly);
+       LOGUNIT_TEST(testAbbreviatePrecisionZero);
+       LOGUNIT_TEST(testAbbreviateEachSegmentToOneChar);
+       LOGUNIT_TEST(testAbbreviateEachSegmentToTwoChars);
+       LOGUNIT_TEST(testAbbreviateShortSegmentUnchanged);
+       LOGUNIT_TEST(testAbbreviateConsecutiveDots);
+       LOGUNIT_TEST(testAbbreviateLeadingDot);
+       LOGUNIT_TEST(testAbbreviateTrailingDot);
+       LOGUNIT_TEST(testAbbreviateEmptyName);
+       LOGUNIT_TEST(testAbbreviateSegmentEqualToCharCountBoundary);
+       LOGUNIT_TEST(testAbbreviateSegmentOneCharOverBoundary);
+       LOGUNIT_TEST(testAbbreviateManySegmentsDeepHierarchy);
+       LOGUNIT_TEST(testAbbreviateLongFinalSegmentPreserved);
+       LOGUNIT_TEST(testAbbreviateIdempotentOnAlreadyAbbreviated);
        LOGUNIT_TEST_SUITE_END();
 
        LoggerPtr root;
@@ -567,6 +585,334 @@ public:
                LOGUNIT_ASSERT(Compare::compare(OUTPUT_FILE, WITNESS_FILE));
        }
 
+       /*
+        * Tests for logger-name abbreviation, exercised via %c{precision} in 
PatternLayout.
+        */
+
+       /**
+        * No dots at all: name shorter than or equal to any precision should be
+        * returned unchanged (nextDot == npos path, no truncation possible).
+        */
+       void testAbbreviateNoDots()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{2}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("NoDotsHere")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("NoDotsHere")), 
result);
+       }
+
+       /**
+        * Precision larger than number of segments: entire name is preserved,
+        * exercising the "keep trailing/last element(s) whole" path.
+        */
+       void testAbbreviatePrecisionExceedsSegments()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{5}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("org.apache.log4cxx")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               
LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("org.apache.log4cxx")), result);
+       }
+
+       /**
+        * Standard case: precision 2 keeps the last two dotted segments and
+        * drops the leading ones entirely (no ellipsis involved at this level).
+        */
+       void testAbbreviateKeepLastTwoSegments()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{2}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("org.apache.log4cxx.PatternLayout")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               
LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("log4cxx.PatternLayout")), result);
+       }
+
+       /**
+        * Precision 1 keeps only the final segment; every prior segment,
+        * however long, must be dropped by abbreviate()'s erase/rewrite step.
+        */
+       void testAbbreviateKeepLastSegmentOnly()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{1}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( 
LOG4CXX_STR("com.example.verylongcompany.module.ClassName")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("ClassName")), 
result);
+       }
+
+       /**
+        * Precision 0: degenerate case, only the final element remains
+        * (mirrors log4j semantics where 0 behaves like 1 for the last 
element).
+        */
+       void testAbbreviatePrecisionZero()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{0}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("a.b.c.d.Ending")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("Ending")), result);
+       }
+
+       /**
+        * Fractional / dotted-count abbreviator ("%c{1.}" family): each 
retained
+        * leading segment is itself truncated to 1 character plus a trailing 
dot,
+        * directly exercising the charCount/erase/insert logic inside 
abbreviate().
+        */
+       void testAbbreviateEachSegmentToOneChar()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{1.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("org.apache.log4cxx.PatternLayout")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               
LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("o.a.l.PatternLayout")), result);
+       }
+
+       /**
+        * Segment truncated to 2 characters ("%c{2.}"): checks charCount > 1
+        * truncation path (nextDot - startPos > charCount) rather than the
+        * charCount == 1 special case.
+        */
+       void testAbbreviateEachSegmentToTwoChars()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{2.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("org.apache.log4cxx.PatternLayout")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               
LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("or.ap.lo.PatternLayout")), result);
+       }
+
+       /**
+        * Segment shorter than or equal to charCount is left untouched
+        * (the "(nextDot - startPos) > charCount" guard should skip 
erase/insert).
+        */
+       void testAbbreviateShortSegmentUnchanged()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{3.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("a.bb.ccc.LongFinalSegment")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               // "a" (1<=3), "bb" (2<=3), "ccc" (3<=3) all pass through 
unabbreviated
+               
LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("a.bb.ccc.LongFinalSegment")), 
result);
+       }
+
+       /**
+        * Consecutive dots produce a zero-length segment; abbreviate() must
+        * not throw or corrupt the buffer, and should leave the empty
+        * segment as-is (nextDot - startPos == 0, not > charCount).
+        */
+       void testAbbreviateConsecutiveDots()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{1.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("org..apache.Logger")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("o..a.Logger")), 
result);
+       }
+
+       /**
+        * Leading dot creates an empty first segment; verifies startPos 
handling
+        * at position 0 doesn't underflow or mis-index into the buffer.
+        */
+       void testAbbreviateLeadingDot()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{1.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR(".apache.Logger")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR(".a.Logger")), 
result);
+       }
+
+       /**
+        * Trailing dot with nothing after it: the final "segment" after the
+        * last dot is empty, and nextDot == npos is never reached for that
+        * position, so the loop must still terminate correctly.
+        */
+       void testAbbreviateTrailingDot()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{1.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("org.apache.")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("o.a.")), result);
+       }
+
+       /**
+        * Empty logger name: startPos == buf.size() from the start, find()
+        * immediately returns npos, function must be a no-op.
+        */
+       void testAbbreviateEmptyName()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{2}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("")), result);
+       }
+
+       /**
+        * Single-character segments equal to charCount exactly: boundary check
+        * on "> charCount" vs ">= charCount" (should NOT truncate when equal,
+        * since the guard is strictly "(nextDot - startPos) > charCount").
+        */
+       void testAbbreviateSegmentEqualToCharCountBoundary()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{2.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("ab.cd.RestOfName")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               // "ab" (len 2 == charCount 2) and "cd" (len 2 == charCount 2) 
both
+               // pass through untouched; only the final segment is never 
abbreviated.
+               
LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("ab.cd.RestOfName")), result);
+       }
+
+       /**
+        * Segment exactly one character longer than charCount: smallest case
+        * that must actually trigger the erase/insert (or rewrite) path.
+        */
+       void testAbbreviateSegmentOneCharOverBoundary()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{2.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("abc.de.RestOfName")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               // "abc" (len 3 > 2) truncates to "ab"; "de" (len 2 == 2) stays 
whole.
+               
LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("ab.de.RestOfName")), result);
+       }
+
+       /**
+        * Many short segments (deep package hierarchy): the classic case that
+        * exposes O(n*k) blowup in an erase/insert-per-segment implementation.
+        */
+       void testAbbreviateManySegmentsDeepHierarchy()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{1.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( 
LOG4CXX_STR("a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.FinalClass")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               LOGUNIT_ASSERT_EQUAL
+                       ( 
LogString(LOG4CXX_STR("a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.FinalClass"))
+                       , result
+                       );
+       }
+
+       /**
+        * Very long single final segment combined with many short leading ones:
+        * ensures the tail (unabbreviated, no trailing dot) is copied intact
+        * even when it is far longer than charCount, since the last element is
+        * never subject to truncation regardless of length.
+        */
+       void testAbbreviateLongFinalSegmentPreserved()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{1.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( 
LOG4CXX_STR("x.y.z.ThisIsAVeryLongFinalClassNameThatShouldNotBeTruncatedAtAll")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               LOGUNIT_ASSERT_EQUAL
+                       ( 
LogString(LOG4CXX_STR("x.y.z.ThisIsAVeryLongFinalClassNameThatShouldNotBeTruncatedAtAll"))
+                       , result
+                       );
+       }
+
+       /**
+        * Idempotency / stability check: running the same abbreviation pattern
+        * against an already-abbreviated name should be a no-op, since every
+        * remaining segment is already <= charCount.
+        */
+       void testAbbreviateIdempotentOnAlreadyAbbreviated()
+       {
+               PatternLayout layout(LOG4CXX_STR("%c{1.}"));
+               auto event = std::make_shared<spi::LoggingEvent>
+                       ( LOG4CXX_STR("o.a.l.PatternLayout")
+                       , Level::getInfo()
+                       , LOG4CXX_STR("msg")
+                       , LOG4CXX_LOCATION
+                       );
+               LogString result;
+               layout.format(result, event);
+               
LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("o.a.l.PatternLayout")), result);
+       }
+
        std::string createMessage(Pool & pool, int i)
        {
                std::string msg("Message ");

Reply via email to