Skip to content

Commit bc13b77

Browse files
committed
WIP
1 parent 759fc19 commit bc13b77

7 files changed

Lines changed: 154 additions & 141 deletions

File tree

dd-smoke-tests/src/main/java/datadog/smoketest/SmokeApp.java

Lines changed: 80 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package datadog.smoketest;
22

3+
import static java.util.concurrent.TimeUnit.SECONDS;
4+
35
import datadog.smoketest.backend.TraceBackend;
46
import datadog.smoketest.backend.Traces;
57
import datadog.trace.agent.test.utils.PortUtils;
8+
import datadog.trace.api.internal.VisibleForTesting;
69
import java.io.BufferedReader;
710
import java.io.File;
811
import 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

Comments
 (0)