Skip to content

Commit c66fecc

Browse files
committed
TS-45056 Rework
1 parent b1af65c commit c66fecc

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

agent/src/test/java/com/teamscale/jacoco/agent/PreMainTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22

33
import org.junit.jupiter.api.BeforeEach;
44
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.io.TempDir;
56

7+
import java.io.IOException;
68
import java.lang.instrument.Instrumentation;
9+
import java.nio.charset.StandardCharsets;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
712

13+
import static org.assertj.core.api.Assertions.assertThat;
814
import static org.assertj.core.api.Assertions.assertThatCode;
915

1016
/**
@@ -45,4 +51,46 @@ void premainDoesNotThrowOnConfigIdWithoutCredentials() {
4551
assertThatCode(() -> PreMain.premain("config-id=some-config", null))
4652
.doesNotThrowAnyException();
4753
}
54+
55+
/**
56+
* Exercises the top-level {@code try}/{@code catch} in {@link PreMain#premain(String, Instrumentation)} that routes
57+
* into {@code logStartupFailure} — the safety net for failures that surface <em>after</em> options have been parsed
58+
* (and are therefore not covered by the {@code AgentOption*Exception} catches inside {@code startProfiler}).
59+
* <p>
60+
* The trick: valid options pass parsing, then a {@code null} {@link Instrumentation} forces JaCoCo's runtime setup
61+
* to dereference {@code null} (first at {@code AgentModule.openPackage(inst, …)} inside
62+
* {@code JaCoCoPreMain.createRuntime}). The resulting {@link NullPointerException} is a stand-in for real-world
63+
* post-parse failures — e.g. a Jetty {@code BindException} when {@code http-server-port} is taken, or an
64+
* {@code UploaderException} during uploader construction — which would bubble up the same way. In production the
65+
* JVM always supplies a non-null {@code Instrumentation}; passing {@code null} here is only a cheap way to stage
66+
* the failure without a running JVM agent attach.
67+
* <p>
68+
* A custom {@code logging-config} points logback at a file inside {@code tempDir} so we can read back the emitted
69+
* error event. A plain {@link ch.qos.logback.core.read.ListAppender} attached in the test would be detached again
70+
* by the {@code LoggerContext.reset()} that {@code LoggingUtils.reconfigureLoggerContext} performs during option
71+
* parsing.
72+
*/
73+
@Test
74+
void premainLogsFailureWhenJaCoCoSetupThrows(@TempDir Path tempDir) throws IOException {
75+
Path logFile = tempDir.resolve("agent.log");
76+
Path logbackConfig = tempDir.resolve("logback.xml");
77+
Files.write(logbackConfig, ("<configuration>\n"
78+
+ " <appender name=\"FILE\" class=\"ch.qos.logback.core.FileAppender\">\n"
79+
+ " <file>" + logFile.toAbsolutePath() + "</file>\n"
80+
+ " <encoder><pattern>%-5level %logger - %msg%n%ex</pattern></encoder>\n"
81+
+ " </appender>\n"
82+
+ " <root level=\"INFO\"><appender-ref ref=\"FILE\"/></root>\n"
83+
+ "</configuration>\n").getBytes(StandardCharsets.UTF_8));
84+
85+
assertThatCode(() -> PreMain.premain(
86+
"logging-config=" + logbackConfig.toAbsolutePath() + ",includes=com.example.*", null))
87+
.doesNotThrowAnyException();
88+
89+
assertThat(logFile).exists();
90+
String log = new String(Files.readAllBytes(logFile), StandardCharsets.UTF_8);
91+
assertThat(log)
92+
.contains("ERROR")
93+
.contains("failed to start up")
94+
.contains("NullPointerException");
95+
}
4896
}

0 commit comments

Comments
 (0)