11package datadog .smoketest ;
22
3+ import static java .util .concurrent .TimeUnit .SECONDS ;
4+
35import datadog .smoketest .backend .TraceBackend ;
46import datadog .smoketest .backend .Traces ;
57import datadog .trace .agent .test .utils .PortUtils ;
8+ import datadog .trace .api .internal .VisibleForTesting ;
69import java .io .BufferedReader ;
710import java .io .File ;
811import java .io .IOException ;
@@ -135,21 +138,22 @@ public static Builder named(String name) {
135138 * #HTTP_PORT_PLACEHOLDER}).
136139 */
137140 public int httpPort () {
138- return httpPort ;
141+ return this . httpPort ;
139142 }
140143
141144 /** Base URL of the app's HTTP server. */
142145 public URI url () {
143- return URI .create ("http://localhost:" + httpPort );
146+ return URI .create ("http://localhost:" + this . httpPort );
144147 }
145148
146149 /**
147150 * Issues a GET to the app and returns the HTTP status code (the response is drained and closed).
148151 */
149- public int get (String path ) {
152+ @ VisibleForTesting
153+ int get (String path ) {
150154 String full = url () + (path .startsWith ("/" ) ? path : "/" + path );
151155 Request request = new Request .Builder ().url (full ).get ().build ();
152- try (Response response = httpClient .newCall (request ).execute ()) {
156+ try (Response response = this . httpClient .newCall (request ).execute ()) {
153157 return response .code ();
154158 } catch (IOException e ) {
155159 throw new IllegalStateException ("GET " + full + " failed" , e );
@@ -158,12 +162,12 @@ public int get(String path) {
158162
159163 /** The trace query/assert facade of this app's backend. */
160164 public Traces traces () {
161- return backend .traces ();
165+ return this . backend .traces ();
162166 }
163167
164168 /** The backend this app sends traces to. */
165169 public TraceBackend backend () {
166- return backend ;
170+ return this . backend ;
167171 }
168172
169173 /**
@@ -172,7 +176,7 @@ public TraceBackend backend() {
172176 */
173177 public boolean awaitLogLine (Function <String , Boolean > predicate ) {
174178 try {
175- return outputThreads .processTestLogLines (predicate );
179+ return this . outputThreads .processTestLogLines (predicate );
176180 } catch (TimeoutException e ) {
177181 return false ;
178182 }
@@ -187,37 +191,38 @@ public boolean awaitLogLine(Function<String, Boolean> predicate) {
187191 public void assertCompletesWithValue (long timeout , TimeUnit unit , int expectedExitValue ) {
188192 boolean exited ;
189193 try {
190- exited = process .waitFor (timeout , unit );
194+ exited = this . process .waitFor (timeout , unit );
191195 } catch (InterruptedException e ) {
192196 Thread .currentThread ().interrupt ();
193- throw new AssertionError ("Interrupted while waiting for app '" + name + "' to complete" , e );
197+ throw new AssertionError (
198+ "Interrupted while waiting for app '" + this .name + "' to complete" , e );
194199 }
195200 if (!exited ) {
196201 throw new AssertionError (
197- "App '" + name + "' did not complete within " + timeout + " " + unit );
202+ "App '" + this . name + "' did not complete within " + timeout + " " + unit );
198203 }
199- int actual = process .exitValue ();
204+ int actual = this . process .exitValue ();
200205 if (actual != expectedExitValue ) {
201206 throw new AssertionError (
202- "App '" + name + "' exited with " + actual + " but expected " + expectedExitValue );
207+ "App '" + this . name + "' exited with " + actual + " but expected " + expectedExitValue );
203208 }
204209 }
205210
206211 // --- Lifecycle (per-class start, per-method reset, teardown) ---
207212
208213 @ Override
209214 public void beforeAll (ExtensionContext context ) throws Exception {
210- // Always start the backend before launching (start() is idempotent). This makes the app robust
211- // to @RegisterExtension callback ordering: a shared backend also self-starts via its own
212- // beforeAll, and an inline (owned) backend is started only here. Ownership governs
213- // clear()/close.
214- backend .start ();
215+ this .backend .start ();
215216 launch ();
216- if (server ) {
217- PortUtils .waitForPortToOpen (httpPort , startupTimeoutSeconds , TimeUnit . SECONDS , process );
218- } else if (!process .isAlive () && process .exitValue () != 0 ) {
217+ if (this . server ) {
218+ PortUtils .waitForPortToOpen (this . httpPort , this . startupTimeoutSeconds , SECONDS , this . process );
219+ } else if (!this . process .isAlive () && this . process .exitValue () != 0 ) {
219220 throw new IllegalStateException (
220- "App '" + name + "' exited abnormally on start (exit " + process .exitValue () + ")" );
221+ "App '"
222+ + this .name
223+ + "' exited abnormally on start (exit "
224+ + this .process .exitValue ()
225+ + ")" );
221226 }
222227 }
223228
@@ -228,15 +233,16 @@ public void beforeEach(ExtensionContext context) {
228233 // (notAServer) may have already run to completion and produced its traces at start-up, so
229234 // neither applies: requiring it alive would spuriously fail, and clearing would wipe its
230235 // traces.
231- if (server ) {
232- if (process == null || !process .isAlive ()) {
233- throw new IllegalStateException ("App '" + name + "' is not alive at the start of a test" );
236+ if (this .server ) {
237+ if (this .process == null || !this .process .isAlive ()) {
238+ throw new IllegalStateException (
239+ "App '" + this .name + "' is not alive at the start of a test" );
234240 }
235- if (ownsBackend ) {
236- backend .clear ();
241+ if (this . ownsBackend ) {
242+ this . backend .clear ();
237243 }
238244 }
239- outputThreads .clearMessages ();
245+ this . outputThreads .clearMessages ();
240246 }
241247
242248 @ Override
@@ -245,8 +251,8 @@ public void afterEach(ExtensionContext context) {
245251 // (the
246252 // app is killed, and the per-method session clear may have wiped a once-only app-started). Only
247253 // for agent-instrumented apps (a no-agent app emits none).
248- if (checkTelemetry && agentJar != null && !telemetryChecked ) {
249- telemetryChecked = true ;
254+ if (this . checkTelemetry && this . agentJar != null && !this . telemetryChecked ) {
255+ this . telemetryChecked = true ;
250256 assertTelemetryReceived ();
251257 }
252258 }
@@ -257,13 +263,13 @@ public void afterAll(ExtensionContext context) {
257263 stopProcess ();
258264 } finally {
259265 // Join the output threads first so the log file is fully flushed before we scan it.
260- outputThreads .close ();
266+ this . outputThreads .close ();
261267 try {
262- if (ownsBackend ) {
263- backend .close ();
268+ if (this . ownsBackend ) {
269+ this . backend .close ();
264270 }
265271 } finally {
266- if (checkErrorLogs ) {
272+ if (this . checkErrorLogs ) {
267273 assertNoErrorLogs ();
268274 }
269275 }
@@ -274,76 +280,76 @@ private void launch() throws IOException {
274280 List <String > command = new ArrayList <>();
275281 command .add (javaExecutable ());
276282
277- if (agentJar != null ) {
278- command .add ("-javaagent:" + agentJar );
279- command .add ("-Ddd.trace.agent.host=" + backend .url ().getHost ());
280- command .add ("-Ddd.trace.agent.port=" + backend .port ());
283+ if (this . agentJar != null ) {
284+ command .add ("-javaagent:" + this . agentJar );
285+ command .add ("-Ddd.trace.agent.host=" + this . backend .url ().getHost ());
286+ command .add ("-Ddd.trace.agent.port=" + this . backend .port ());
281287 command .add ("-Ddd.service.name=" + SERVICE_NAME );
282288 command .add ("-Ddd.env=" + ENV );
283289 command .add ("-Ddd.version=" + VERSION );
284- String sessionToken = backend .sessionToken ();
290+ String sessionToken = this . backend .sessionToken ();
285291 if (sessionToken != null ) {
286292 command .add ("-Ddd.trace.agent.test.session.token=" + sessionToken );
287293 }
288- if (checkTelemetry ) {
294+ if (this . checkTelemetry ) {
289295 // Emit telemetry promptly so app-started is captured before a (long-running server) app is
290296 // killed at teardown — mirrors the Groovy base's telemetry tests.
291297 command .add ("-Ddd.telemetry.heartbeat.interval=1" );
292298 }
293299 }
294- for (String jvmArg : jvmArgs ) {
300+ for (String jvmArg : this . jvmArgs ) {
295301 command .add (substitute (jvmArg ));
296302 }
297- if (jar != null ) {
303+ if (this . jar != null ) {
298304 command .add ("-jar" );
299- command .add (jar );
305+ command .add (this . jar );
300306 } else {
301307 command .add ("-cp" );
302- command .add (classpath );
303- command .add (mainClass );
308+ command .add (this . classpath );
309+ command .add (this . mainClass );
304310 }
305- for (String programArg : programArgs ) {
311+ for (String programArg : this . programArgs ) {
306312 command .add (substitute (programArg ));
307313 }
308314
309315 ProcessBuilder processBuilder = new ProcessBuilder (command );
310- if (workingDirectory != null ) {
311- processBuilder .directory (workingDirectory );
316+ if (this . workingDirectory != null ) {
317+ processBuilder .directory (this . workingDirectory );
312318 }
313319 Map <String , String > env = processBuilder .environment ();
314320 env .put ("JAVA_HOME" , System .getProperty ("java.home" ));
315321 env .put ("DD_API_KEY" , API_KEY );
316322 env .keySet ().removeAll (NOISY_ENVIRONMENT_VARIABLES );
317- env .putAll (extraEnv );
323+ env .putAll (this . extraEnv );
318324 processBuilder .redirectErrorStream (true );
319325
320- logFile = resolveLogFile ();
321- process = processBuilder .start ();
322- outputThreads .captureOutput (process , logFile );
326+ this . logFile = resolveLogFile ();
327+ this . process = processBuilder .start ();
328+ this . outputThreads .captureOutput (this . process , this . logFile );
323329 }
324330
325331 private void stopProcess () {
326- if (process == null ) {
332+ if (this . process == null ) {
327333 return ;
328334 }
329- if (!process .isAlive ()) {
335+ if (!this . process .isAlive ()) {
330336 return ;
331337 }
332- process .destroy ();
338+ this . process .destroy ();
333339 try {
334- if (!process .waitFor (5 , TimeUnit . SECONDS )) {
335- process .destroyForcibly ();
336- process .waitFor (10 , TimeUnit . SECONDS );
340+ if (!this . process .waitFor (5 , SECONDS )) {
341+ this . process .destroyForcibly ();
342+ this . process .waitFor (10 , SECONDS );
337343 }
338344 } catch (InterruptedException e ) {
339345 Thread .currentThread ().interrupt ();
340- process .destroyForcibly ();
346+ this . process .destroyForcibly ();
341347 }
342348 }
343349
344350 private String substitute (String value ) {
345- String result = value .replace (HTTP_PORT_PLACEHOLDER , Integer .toString (httpPort ));
346- for (Map .Entry <String , Supplier <String >> placeholder : placeholders .entrySet ()) {
351+ String result = value .replace (HTTP_PORT_PLACEHOLDER , Integer .toString (this . httpPort ));
352+ for (Map .Entry <String , Supplier <String >> placeholder : this . placeholders .entrySet ()) {
347353 String token = placeholder .getKey ();
348354 if (result .contains (token )) {
349355 // Resolved now (at launch), not when registered — the value's source (e.g. a container's
@@ -363,7 +369,7 @@ private File resolveLogFile() {
363369 dir .mkdirs ();
364370 // TODO Q6 (deferred): retry-safe timestamped log file names so retries don't clobber prior
365371 // logs.
366- return new File (dir , "smoke-app." + name + ".log" );
372+ return new File (dir , "smoke-app." + this . name + ".log" );
367373 }
368374
369375 /**
@@ -373,27 +379,27 @@ private File resolveLogFile() {
373379 * explicitly mid-run.
374380 */
375381 public void assertNoErrorLogs () {
376- if (logFile == null ) {
382+ if (this . logFile == null ) {
377383 return ; // never launched / nothing captured
378384 }
379385 List <String > errors = new ArrayList <>();
380386 try (BufferedReader reader =
381- Files .newBufferedReader (logFile .toPath (), StandardCharsets .UTF_8 )) {
387+ Files .newBufferedReader (this . logFile .toPath (), StandardCharsets .UTF_8 )) {
382388 String line ;
383389 while ((line = reader .readLine ()) != null ) {
384- if (errorLogFilter .test (line )) {
390+ if (this . errorLogFilter .test (line )) {
385391 errors .add (line );
386392 }
387393 }
388394 } catch (NoSuchFileException e ) {
389395 return ; // no output file was produced
390396 } catch (IOException e ) {
391- throw new IllegalStateException ("Failed to read app log " + logFile , e );
397+ throw new IllegalStateException ("Failed to read app log " + this . logFile , e );
392398 }
393399 if (!errors .isEmpty ()) {
394400 StringBuilder message =
395401 new StringBuilder ("App '" )
396- .append (name )
402+ .append (this . name )
397403 .append ("' logged " )
398404 .append (errors .size ())
399405 .append (" error line(s):" );
@@ -413,7 +419,7 @@ public void assertNoErrorLogs() {
413419 * assert it with {@link #traces() backend}.{@code telemetry().waitForFlat(...)}.
414420 */
415421 public void assertTelemetryReceived () {
416- backend .telemetry ().waitForCount (1 , startupTimeoutSeconds );
422+ this . backend .telemetry ().waitForCount (1 , this . startupTimeoutSeconds );
417423 }
418424
419425 /**
@@ -458,7 +464,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte
458464 return traces ();
459465 }
460466 if (type == TraceBackend .class ) {
461- return backend ;
467+ return this . backend ;
462468 }
463469 throw new ParameterResolutionException ("Cannot resolve parameter of type " + type );
464470 // TODO Q7: with multiple same-type apps/backends injection is ambiguous — add a qualifier
@@ -622,17 +628,19 @@ public Builder skipTelemetryCheck() {
622628 }
623629
624630 private String resolveAgentJar () {
625- if (noAgent ) {
631+ if (this . noAgent ) {
626632 return null ;
627633 }
628- return explicitAgentJar != null ? explicitAgentJar : System .getProperty (AGENT_JAR_PROPERTY );
634+ return this .explicitAgentJar != null
635+ ? this .explicitAgentJar
636+ : System .getProperty (AGENT_JAR_PROPERTY );
629637 }
630638
631639 public SmokeApp build () {
632- if (backend == null ) {
640+ if (this . backend == null ) {
633641 throw new IllegalStateException ("A TraceBackend is required — call backend(...)" );
634642 }
635- if ((jar == null ) == (mainClass == null )) {
643+ if ((this . jar == null ) == (this . mainClass == null )) {
636644 throw new IllegalStateException ("Exactly one of jar(...) or mainClass(...) must be set" );
637645 }
638646 // TODO Q6 (deferred, opt-in mixins / .jvmArgs(...) escape hatch — not baked into the base):
0 commit comments