diff --git a/api/src/main/java/com/google/common/flogger/CountingRateLimiter.java b/api/src/main/java/com/google/common/flogger/CountingRateLimiter.java new file mode 100644 index 00000000..570b5471 --- /dev/null +++ b/api/src/main/java/com/google/common/flogger/CountingRateLimiter.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 The Flogger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.flogger; + +import static com.google.common.flogger.LogContext.Key.LOG_EVERY_N; + +import com.google.common.flogger.backend.Metadata; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Rate limiter to support {@code every(N)} functionality. This class is mutable, but thread safe. + */ +final class CountingRateLimiter { + private static final LogSiteMap map = + new LogSiteMap() { + @Override + protected CountingRateLimiter initialValue() { + return new CountingRateLimiter(); + } + }; + + static boolean shouldLog(Metadata metadata, LogSiteKey logSiteKey) { + // Fast path is "there's no metadata so return true" and this must not allocate. + Integer rateLimitCount = metadata.findValue(LOG_EVERY_N); + if (rateLimitCount == null) { + return true; + } + return map.get(logSiteKey, metadata).incrementAndCheckInvocationCount(rateLimitCount); + } + + private final AtomicLong invocationCount = new AtomicLong(); + + /** + * Increments the invocation count and returns true if it was a multiple of the specified rate + * limit count; implying that the log statement should be emitted. This is invoked during + * post-processing if a rate limiting count was set via {@link LoggingApi#every(int)}. + */ + // Visible for testing. + boolean incrementAndCheckInvocationCount(int rateLimitCount) { + // Assume overflow cannot happen for a Long counter. + return (invocationCount.getAndIncrement() % rateLimitCount) == 0; + } +} diff --git a/api/src/main/java/com/google/common/flogger/LogSiteStats.java b/api/src/main/java/com/google/common/flogger/DurationRateLimiter.java similarity index 75% rename from api/src/main/java/com/google/common/flogger/LogSiteStats.java rename to api/src/main/java/com/google/common/flogger/DurationRateLimiter.java index 4b957ed6..5e73eb31 100644 --- a/api/src/main/java/com/google/common/flogger/LogSiteStats.java +++ b/api/src/main/java/com/google/common/flogger/DurationRateLimiter.java @@ -16,6 +16,7 @@ package com.google.common.flogger; +import static com.google.common.flogger.LogContext.Key.LOG_AT_MOST_EVERY; import static com.google.common.flogger.util.Checks.checkNotNull; import com.google.common.flogger.backend.LogData; @@ -25,25 +26,46 @@ import java.util.concurrent.atomic.AtomicLong; /** - * Statistics for individual log sites for determining when rate limited log statements should be - * emitted. This class is mutable, but thread safe. + * Rate limiter to support {@code atMostEvery(N, units)} functionality. This class is mutable, but + * thread safe. */ -final class LogSiteStats { +final class DurationRateLimiter { + private static final LogSiteMap map = + new LogSiteMap() { + @Override + protected DurationRateLimiter initialValue() { + return new DurationRateLimiter(); + } + }; + /** Creates a period for rate limiting for the specified duration. */ static RateLimitPeriod newRateLimitPeriod(int n, TimeUnit unit) { return new RateLimitPeriod(n, unit); } + /** + * Returns whether the log site should log based on the value of the {@code LOG_AT_MOST_EVERY} + * metadata value and the current log site timestamp. + */ + static boolean shouldLogForTimestamp( + Metadata metadata, LogSiteKey logSiteKey, long timestampNanos) { + // Fast path is "there's no metadata so return true" and this must not allocate. + RateLimitPeriod rateLimitPeriod = metadata.findValue(LOG_AT_MOST_EVERY); + if (rateLimitPeriod == null) { + return true; + } + return map.get(logSiteKey, metadata).checkLastTimestamp(timestampNanos, rateLimitPeriod); + } + /** * Immutable metadata for rate limiting based on a fixed count. This corresponds to the - * LOG_AT_MOST_EVERY metadata key in {@link LogData}. Unlike the metadata for {@code every(N)}, - * we need to use a wrapper class here to preserve the time unit information. + * LOG_AT_MOST_EVERY metadata key in {@link LogData}. Unlike the metadata for {@code every(N)}, we + * need to use a wrapper class here to preserve the time unit information. */ - // TODO: Consider making this a public class to allow backends to handle it explicitly. static final class RateLimitPeriod { private final int n; private final TimeUnit unit; - // Count of the number of log statements skipped in the last period. See during post processing. + // Count of the number of log statements skipped in the last period. Set during post processing. private int skipCount = -1; private RateLimitPeriod(int n, TimeUnit unit) { @@ -70,12 +92,9 @@ private void setSkipCount(int skipCount) { @Override public String toString() { // TODO: Make this less ugly and internationalization friendly. - StringBuilder out = new StringBuilder() - .append(n) - .append(' ') - .append(unit); + StringBuilder out = new StringBuilder().append(n).append(' ').append(unit); if (skipCount > 0) { - out.append(" [skipped: ").append(skipCount).append(']'); + out.append(" [skipped: ").append(skipCount).append(']'); } return out.toString(); } @@ -96,35 +115,15 @@ public boolean equals(Object obj) { } } - private static final LogSiteMap map = new LogSiteMap() { - @Override - protected LogSiteStats initialValue() { - return new LogSiteStats(); - } - }; - - static LogSiteStats getStatsForKey(LogSiteKey logSiteKey, Metadata metadata) { - return map.get(logSiteKey, metadata); - } - - private final AtomicLong invocationCount = new AtomicLong(); private final AtomicLong lastTimestampNanos = new AtomicLong(); private final AtomicInteger skippedLogStatements = new AtomicInteger(); - /** - * Increments the invocation count and returns true if it was a multiple of the specified rate - * limit count; implying that the log statement should be emitted. This is invoked during - * post-processing if a rate limiting count was set via {@link LoggingApi#every(int)}. - */ - boolean incrementAndCheckInvocationCount(int rateLimitCount) { - return (invocationCount.getAndIncrement() % rateLimitCount) == 0; - } - /** * Checks whether the current time stamp is after the rate limiting period and if so, updates the * time stamp and returns true. This is invoked during post-processing if a rate limiting duration * was set via {@link LoggingApi#atMostEvery(int, TimeUnit)}. */ + // Visible for testing. boolean checkLastTimestamp(long timestampNanos, RateLimitPeriod period) { long lastNanos = lastTimestampNanos.get(); // Avoid a race condition where two threads log at the same time. This is safe as lastNanos diff --git a/api/src/main/java/com/google/common/flogger/LogContext.java b/api/src/main/java/com/google/common/flogger/LogContext.java index e27ff8b8..ea8576a5 100644 --- a/api/src/main/java/com/google/common/flogger/LogContext.java +++ b/api/src/main/java/com/google/common/flogger/LogContext.java @@ -20,7 +20,7 @@ import static com.google.common.flogger.util.Checks.checkNotNull; import static java.util.concurrent.TimeUnit.NANOSECONDS; -import com.google.common.flogger.LogSiteStats.RateLimitPeriod; +import com.google.common.flogger.DurationRateLimiter.RateLimitPeriod; import com.google.common.flogger.backend.LogData; import com.google.common.flogger.backend.Metadata; import com.google.common.flogger.backend.Platform; @@ -524,23 +524,14 @@ protected boolean postProcess(@NullableDecl LogSiteKey logSiteKey) { if (metadata != null) { // Without a log site we ignore any log-site specific behaviour. if (logSiteKey != null) { - // Don't "return true" early from this code since we also need to handle stack size. - // Only get the stats instance when we need it, but cache it if we do. This avoids getting - // an instance for every log statement when the vast majority don't need it. - LogSiteStats stats = null; - Integer rateLimitCount = metadata.findValue(Key.LOG_EVERY_N); - if (rateLimitCount != null) { - stats = ensureStats(stats, logSiteKey, metadata); - if (!stats.incrementAndCheckInvocationCount(rateLimitCount)) { - return false; - } + // Note: The current behaviour for handling multiple rate limiters if not ideal, and there's + // an implicit order here. Ideally all rate limiting would occur in any order, but this is + // actually subtle and needs more work (so for now the old behaviour is preserved). + if (!CountingRateLimiter.shouldLog(metadata, logSiteKey)) { + return false; } - RateLimitPeriod rateLimitPeriod = metadata.findValue(Key.LOG_AT_MOST_EVERY); - if (rateLimitPeriod != null) { - stats = ensureStats(stats, logSiteKey, metadata); - if (!stats.checkLastTimestamp(getTimestampNanos(), rateLimitPeriod)) { - return false; - } + if (!DurationRateLimiter.shouldLogForTimestamp(metadata, logSiteKey, getTimestampNanos())) { + return false; } } @@ -574,16 +565,6 @@ protected boolean postProcess(@NullableDecl LogSiteKey logSiteKey) { return true; } - // Helper to get the stats instance on demand while reusing an instance if already obtained. - // Usage: - // // Possible null stats here... - // stats = ensureStats(stats, logSiteKey, metadata); - // // Non-null stats here (assigned to variable for next time) - private static LogSiteStats ensureStats( - @NullableDecl LogSiteStats stats, LogSiteKey logSiteKey, Metadata metadata) { - return stats != null ? stats : LogSiteStats.getStatsForKey(logSiteKey, metadata); - } - /** * Pre-processes log metadata and determines whether we should make the pending logging call. * @@ -783,7 +764,7 @@ public final API atMostEvery(int n, TimeUnit unit) { // Rate limiting with a zero length period is a no-op, but if the time unit is nanoseconds then // the value is rounded up inside the rate limit object. if (n > 0) { - addMetadata(Key.LOG_AT_MOST_EVERY, LogSiteStats.newRateLimitPeriod(n, unit)); + addMetadata(Key.LOG_AT_MOST_EVERY, DurationRateLimiter.newRateLimitPeriod(n, unit)); } return api(); } diff --git a/api/src/test/java/com/google/common/flogger/CountingRateLimiterTest.java b/api/src/test/java/com/google/common/flogger/CountingRateLimiterTest.java new file mode 100644 index 00000000..8badaa63 --- /dev/null +++ b/api/src/test/java/com/google/common/flogger/CountingRateLimiterTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 The Flogger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.flogger; + +import static com.google.common.flogger.LogContext.Key.LOG_EVERY_N; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.flogger.testing.FakeLogSite; +import com.google.common.flogger.testing.FakeMetadata; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CountingRateLimiterTest { + @Test + public void testMetadataKey() { + FakeMetadata metadata = new FakeMetadata().add(LOG_EVERY_N, 3); + LogSite logSite = FakeLogSite.unique(); + + // The first log is always emitted. + assertThat(CountingRateLimiter.shouldLog(metadata, logSite)).isTrue(); + assertThat(CountingRateLimiter.shouldLog(metadata, logSite)).isFalse(); + // Not supplying the metadata disables rate limiting. + assertThat(CountingRateLimiter.shouldLog(new FakeMetadata(), logSite)).isTrue(); + assertThat(CountingRateLimiter.shouldLog(metadata, logSite)).isFalse(); + // The fourth log is emitted (ignoring the case when there was no metadata). + assertThat(CountingRateLimiter.shouldLog(metadata, logSite)).isTrue(); + } + + @Test + public void testDistinctLogSites() { + FakeMetadata metadata = new FakeMetadata().add(LOG_EVERY_N, 3); + LogSite fooLog = FakeLogSite.unique(); + LogSite barLog = FakeLogSite.unique(); + + // The first log is always emitted. + assertThat(CountingRateLimiter.shouldLog(metadata, fooLog)).isTrue(); + assertThat(CountingRateLimiter.shouldLog(metadata, barLog)).isTrue(); + + assertThat(CountingRateLimiter.shouldLog(metadata, fooLog)).isFalse(); + assertThat(CountingRateLimiter.shouldLog(metadata, fooLog)).isFalse(); + + assertThat(CountingRateLimiter.shouldLog(metadata, barLog)).isFalse(); + assertThat(CountingRateLimiter.shouldLog(metadata, barLog)).isFalse(); + + assertThat(CountingRateLimiter.shouldLog(metadata, fooLog)).isTrue(); + assertThat(CountingRateLimiter.shouldLog(metadata, barLog)).isTrue(); + } + + @Test + public void testIncrementAndCheckInvocationCount() { + CountingRateLimiter limiter = new CountingRateLimiter(); + + // Always log for the first call. + assertThat(limiter.incrementAndCheckInvocationCount(2)).isTrue(); + + // Alternating for a rate limit count of 2 + assertThat(limiter.incrementAndCheckInvocationCount(2)).isFalse(); + assertThat(limiter.incrementAndCheckInvocationCount(2)).isTrue(); + + // Every third for a rate limit count of 3 (counter starts at 3, so returns true immediately). + assertThat(limiter.incrementAndCheckInvocationCount(3)).isTrue(); + assertThat(limiter.incrementAndCheckInvocationCount(3)).isFalse(); + assertThat(limiter.incrementAndCheckInvocationCount(3)).isFalse(); + assertThat(limiter.incrementAndCheckInvocationCount(3)).isTrue(); + } +} diff --git a/api/src/test/java/com/google/common/flogger/DurationRateLimiterTest.java b/api/src/test/java/com/google/common/flogger/DurationRateLimiterTest.java new file mode 100644 index 00000000..45b5f49f --- /dev/null +++ b/api/src/test/java/com/google/common/flogger/DurationRateLimiterTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 The Flogger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.flogger; + +import static com.google.common.flogger.LogContext.Key.LOG_AT_MOST_EVERY; +import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import com.google.common.flogger.DurationRateLimiter.RateLimitPeriod; +import com.google.common.flogger.testing.FakeLogSite; +import com.google.common.flogger.testing.FakeMetadata; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class DurationRateLimiterTest { + @Test + public void testMetadataKey() { + FakeMetadata metadata = + new FakeMetadata() + .add(LOG_AT_MOST_EVERY, DurationRateLimiter.newRateLimitPeriod(1, SECONDS)); + LogSite logSite = FakeLogSite.unique(); + + // The first log is always emitted (and sets the deadline). + assertThat(DurationRateLimiter.shouldLogForTimestamp(metadata, logSite, 1_000_000_000L)) + .isTrue(); + assertThat(DurationRateLimiter.shouldLogForTimestamp(metadata, logSite, 1_500_000_000L)) + .isFalse(); + // Not supplying the metadata disables rate limiting. + assertThat( + DurationRateLimiter.shouldLogForTimestamp(new FakeMetadata(), logSite, 1_500_000_000L)) + .isTrue(); + // The next log is emitted after 1 second. + assertThat(DurationRateLimiter.shouldLogForTimestamp(metadata, logSite, 1_999_999_999L)) + .isFalse(); + assertThat(DurationRateLimiter.shouldLogForTimestamp(metadata, logSite, 2_000_000_000L)) + .isTrue(); + } + + @Test + public void testDistinctLogSites() { + FakeMetadata metadata = + new FakeMetadata() + .add(LOG_AT_MOST_EVERY, DurationRateLimiter.newRateLimitPeriod(1, SECONDS)); + LogSite fooLog = FakeLogSite.unique(); + LogSite barLog = FakeLogSite.unique(); + + // The first log is always emitted (and sets the deadline). + assertThat(DurationRateLimiter.shouldLogForTimestamp(metadata, fooLog, 1_000_000_000L)) + .isTrue(); + assertThat(DurationRateLimiter.shouldLogForTimestamp(metadata, barLog, 5_000_000_000L)) + .isTrue(); + assertThat(DurationRateLimiter.shouldLogForTimestamp(metadata, fooLog, 1_500_000_000L)) + .isFalse(); + assertThat(DurationRateLimiter.shouldLogForTimestamp(metadata, barLog, 5_500_000_000L)) + .isFalse(); + assertThat(DurationRateLimiter.shouldLogForTimestamp(metadata, fooLog, 2_000_000_000L)) + .isTrue(); + assertThat(DurationRateLimiter.shouldLogForTimestamp(metadata, barLog, 6_000_000_000L)) + .isTrue(); + } + + @Test + public void testCheckLastTimestamp() { + + DurationRateLimiter limiter = new DurationRateLimiter(); + RateLimitPeriod period = DurationRateLimiter.newRateLimitPeriod(1, SECONDS); + // Arbitrary start time (but within the first period to ensure we still log the first call). + long startNanos = 123456000L; + + // Always log for the first call, but not again in the same period. + assertThat(limiter.checkLastTimestamp(startNanos, period)).isTrue(); + assertThat(period.toString()).isEqualTo("1 SECONDS"); + assertThat(limiter.checkLastTimestamp(startNanos + MILLISECONDS.toNanos(500), period)) + .isFalse(); + + // Return true exactly when next period begins. + long nextStartNanos = startNanos + SECONDS.toNanos(1); + assertThat(limiter.checkLastTimestamp(nextStartNanos - 1, period)).isFalse(); + assertThat(limiter.checkLastTimestamp(nextStartNanos, period)).isTrue(); + assertThat(period.toString()).isEqualTo("1 SECONDS [skipped: 2]"); + + // Only return true once, even for duplicate calls. + assertThat(limiter.checkLastTimestamp(nextStartNanos, period)).isFalse(); + } + + @Test + public void testPeriodToString() { + assertThat(DurationRateLimiter.newRateLimitPeriod(23, SECONDS).toString()) + .isEqualTo("23 SECONDS"); + } +} diff --git a/api/src/test/java/com/google/common/flogger/LogContextTest.java b/api/src/test/java/com/google/common/flogger/LogContextTest.java index fac6d99b..5b0bdc98 100644 --- a/api/src/test/java/com/google/common/flogger/LogContextTest.java +++ b/api/src/test/java/com/google/common/flogger/LogContextTest.java @@ -30,6 +30,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; +import com.google.common.flogger.DurationRateLimiter.RateLimitPeriod; import com.google.common.flogger.LogContext.Key; import com.google.common.flogger.context.Tags; import com.google.common.flogger.testing.FakeLogSite; @@ -58,8 +59,8 @@ public class LogContextTest { private static final MetadataKey REPEATED_KEY = MetadataKey.repeated("str", String.class); private static final MetadataKey FLAG_KEY = MetadataKey.repeated("flag", Boolean.class); - private static final LogSiteStats.RateLimitPeriod ONCE_PER_SECOND = - LogSiteStats.newRateLimitPeriod(1, SECONDS); + private static final RateLimitPeriod ONCE_PER_SECOND = + DurationRateLimiter.newRateLimitPeriod(1, SECONDS); @Test public void testIsEnabled() { @@ -204,10 +205,8 @@ public void testAtMostEvery() { } assertThat(backend.getLoggedCount()).isEqualTo(3); - backend - .assertLogged(0) - .metadata() - .containsUniqueEntry(Key.LOG_AT_MOST_EVERY, LogSiteStats.newRateLimitPeriod(2, SECONDS)); + RateLimitPeriod rateLimit = DurationRateLimiter.newRateLimitPeriod(2, SECONDS); + backend.assertLogged(0).metadata().containsUniqueEntry(Key.LOG_AT_MOST_EVERY, rateLimit); backend.assertLogged(0).hasArguments(0); backend.assertLogged(1).hasArguments(4); backend.assertLogged(2).hasArguments(8); diff --git a/api/src/test/java/com/google/common/flogger/LogSiteStatsTest.java b/api/src/test/java/com/google/common/flogger/LogSiteStatsTest.java deleted file mode 100644 index a007c4a3..00000000 --- a/api/src/test/java/com/google/common/flogger/LogSiteStatsTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2014 The Flogger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.flogger; - -import static com.google.common.truth.Truth.assertThat; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; - -import com.google.common.flogger.LogSiteStats.RateLimitPeriod; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class LogSiteStatsTest { - - @Test - public void testIncrementAndCheckInvocationCount() { - LogSiteStats stats = new LogSiteStats(); - - // Always log for the first call. - assertThat(stats.incrementAndCheckInvocationCount(2)).isTrue(); - - // Alternating for a rate limit count of 2 - assertThat(stats.incrementAndCheckInvocationCount(2)).isFalse(); - assertThat(stats.incrementAndCheckInvocationCount(2)).isTrue(); - - // Every third for a rate limit count of 3 (counter starts at 3, so returns true immediately). - assertThat(stats.incrementAndCheckInvocationCount(3)).isTrue(); - assertThat(stats.incrementAndCheckInvocationCount(3)).isFalse(); - assertThat(stats.incrementAndCheckInvocationCount(3)).isFalse(); - assertThat(stats.incrementAndCheckInvocationCount(3)).isTrue(); - } - - @Test - public void testCheckLastTimestamp() { - LogSiteStats stats = new LogSiteStats(); - RateLimitPeriod period = LogSiteStats.newRateLimitPeriod(1, SECONDS); - // Arbitrary start time (but within the first period to ensure we still log the first call). - long startNanos = 123456000L; - - // Always log for the first call, but not again in the same period. - assertThat(stats.checkLastTimestamp(startNanos, period)).isTrue(); - assertThat(period.toString()).isEqualTo("1 SECONDS"); - assertThat(stats.checkLastTimestamp(startNanos + MILLISECONDS.toNanos(500), period)) - .isFalse(); - - // Return true exactly when next period begins. - long nextStartNanos = startNanos + SECONDS.toNanos(1); - assertThat(stats.checkLastTimestamp(nextStartNanos - 1, period)).isFalse(); - assertThat(stats.checkLastTimestamp(nextStartNanos, period)).isTrue(); - assertThat(period.toString()).isEqualTo("1 SECONDS [skipped: 2]"); - - // Only return true once, even for duplicate calls. - assertThat(stats.checkLastTimestamp(nextStartNanos, period)).isFalse(); - } - - @Test - public void testPeriodToString() { - assertThat(LogSiteStats.newRateLimitPeriod(23, SECONDS).toString()).isEqualTo("23 SECONDS"); - } -} diff --git a/api/src/test/java/com/google/common/flogger/testing/FakeLogSite.java b/api/src/test/java/com/google/common/flogger/testing/FakeLogSite.java index cf96640b..f777001f 100644 --- a/api/src/test/java/com/google/common/flogger/testing/FakeLogSite.java +++ b/api/src/test/java/com/google/common/flogger/testing/FakeLogSite.java @@ -18,15 +18,23 @@ import com.google.common.flogger.LogSite; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** A simplified LogSite implementation used for testing. */ public final class FakeLogSite extends LogSite { + private static final AtomicInteger uid = new AtomicInteger(); + /** Creates a fake log site (with plausible behavior) from the given parameters. */ public static LogSite create( String className, String methodName, int lineNumber, String sourcePath) { return new FakeLogSite(className, methodName, lineNumber, sourcePath); } + /** Creates a unique fake log site for use as a key when testing shared static maps. */ + public static LogSite unique() { + return create("ClassName", "method_" + uid.incrementAndGet(), 123, "ClassName.java"); + } + private final String className; private final String methodName; private final int lineNumber; diff --git a/google/src/test/java/com/google/common/flogger/PackageSanityTest.java b/google/src/test/java/com/google/common/flogger/PackageSanityTest.java index 81aa6cfa..5579d5a7 100644 --- a/google/src/test/java/com/google/common/flogger/PackageSanityTest.java +++ b/google/src/test/java/com/google/common/flogger/PackageSanityTest.java @@ -46,5 +46,6 @@ public PackageSanityTest() { setDefault(MetadataKey.class, new MetadataKey<>("dummy", String.class, false)); setDefault(Metadata.class, new FakeMetadata()); ignoreClasses(Predicates.in(IGNORE_CLASSES)); + publicApiOnly(); } }