From 3131c72d617a7ef565772074bf079698af38d534 Mon Sep 17 00:00:00 2001 From: Daniel McCoy Stephenson Date: Thu, 29 Oct 2026 01:08:03 -0600 Subject: [PATCH] Cover coalescing with infinite values, null tags and server-wide tags The coalescing tests drove only finite or NaN values and plain tags. These pin three more paths through report(): an infinite value is the same event as no value; a tag with a null key or value is dropped before the TreeMap key is built, so it neither throws nor makes a different event; and a count carries the install and server-wide tags ahead of its coalesced tag. Co-Authored-By: Claude Opus 5.5 --- .../stephenson/trace/TraceClientTest.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/test/java/software/stephenson/trace/TraceClientTest.java b/src/test/java/software/stephenson/trace/TraceClientTest.java index 43d8f33..8c6016f 100644 --- a/src/test/java/software/stephenson/trace/TraceClientTest.java +++ b/src/test/java/software/stephenson/trace/TraceClientTest.java @@ -2082,6 +2082,77 @@ void coalesce_afterCloseNothingIsCountedOrSent() throws Exception { assertFalse(arrived.await(300, TimeUnit.MILLISECONDS), "nothing should be sent after close()"); } + @Test + void coalesce_infiniteValuesAreTheSameEventAsNoValue() throws Exception { + // Arrange + TraceClient client = TraceClient.builder(baseUrl(), "MyPlugin", "1.2.3").key("k").build(); + + // Act + client.report("command"); + client.report("command", Double.POSITIVE_INFINITY, null); + client.report("command", Double.NEGATIVE_INFINITY, null); + assertTrue(arrived.await(5, TimeUnit.SECONDS), "the first report goes out at once"); + Thread.sleep(200); + int sentAtOnce = received.size(); + int counting = client.coalescing(); + client.close(); + + // Assert + assertEquals(1, sentAtOnce, "json() sends an infinite value as no value, so these are one event"); + assertEquals(1, counting); + assertEquals("{\"application\":\"MyPlugin\",\"name\":\"command\",\"value\":2.0," + + "\"tags\":{\"version\":\"1.2.3\",\"coalesced\":\"2\"}}", received.get(1).body); + } + + @Test + void coalesce_aTagWithANullKeyOrValueIsLeftOutOfTheEventAndNeverThrows() throws Exception { + // Arrange + // The key is built from a TreeMap, which throws on a null key: this + // is only safe because withVersion() drops such tags first. + TraceClient client = TraceClient.builder(baseUrl(), "MyPlugin", "1.2.3").key("k").build(); + Map withNulls = new HashMap<>(); + withNulls.put("name", "home"); + withNulls.put("nullValue", null); + withNulls.put(null, "nullKey"); + + // Act + assertDoesNotThrow(() -> client.report("command", null, withNulls)); + assertDoesNotThrow(() -> client.report("command", null, withNulls)); + client.report("command", null, Collections.singletonMap("name", "home")); + assertTrue(arrived.await(5, TimeUnit.SECONDS), "the first report goes out at once"); + Thread.sleep(200); + int sentAtOnce = received.size(); + client.close(); + + // Assert + assertEquals(1, sentAtOnce, "the same event as one without the null tags"); + assertEquals(2, received.size(), "and one count for both repeats"); + assertEquals("{\"application\":\"MyPlugin\",\"name\":\"command\",\"tags\":{\"name\":\"home\",\"version\":\"1.2.3\"}}", + received.get(0).body); + assertEquals("{\"application\":\"MyPlugin\",\"name\":\"command\",\"value\":2.0," + + "\"tags\":{\"name\":\"home\",\"version\":\"1.2.3\",\"coalesced\":\"2\"}}", received.get(1).body); + } + + @Test + void coalesce_aCountCarriesTheInstallAndServerWideTagsBeforeItsOwn(@TempDir Path plugins) throws Exception { + // Arrange + writeServerWideConfig(plugins, "tags:\n ci: \"true\"\n"); + TraceClient client = TraceClient.builder(baseUrl(), "MyPlugin", "1.2.3").key("k") + .serverWideConfig(plugins.toFile()).build(); + + // Act + for (int i = 0; i < 4; i++) { + client.report("command", null, Collections.singletonMap("name", "home")); + } + client.close(); + + // Assert + assertEquals(2, received.size(), "the first report and one count"); + assertEquals("{\"application\":\"MyPlugin\",\"name\":\"command\",\"value\":3.0," + + "\"tags\":{\"name\":\"home\",\"version\":\"1.2.3\",\"install\":\"test-server\",\"ci\":\"true\",\"coalesced\":\"3\"}}", + received.get(1).body); + } + @Test void withCount_addsTheCountOnlyWhenThereIsRoomAndNoneAlready() { Map tags = new LinkedHashMap<>();