/*
* Copyright 2014 NAVER Corp.
* 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.navercorp.pinpoint.test.plugin;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Pattern;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.Statement;
import com.navercorp.pinpoint.common.util.SystemProperty;
import static com.navercorp.pinpoint.test.plugin.PinpointPluginTestConstants.*;
/**
* @author Jongho Moon
*
*/
public class PinpointPluginTestStatement extends Statement {
private static final String JUNIT_OUTPUT_DELIMITER_REGEXP = Pattern.quote(JUNIT_OUTPUT_DELIMITER);
private final PinpointPluginTestRunner runner;
private final RunNotifier notifier;
private final PinpointPluginTestInstance testCase;
private final PinpointPluginTestContext context;
private final Result result = new Result();
public PinpointPluginTestStatement(PinpointPluginTestRunner runner, RunNotifier notifier, PinpointPluginTestContext context, PinpointPluginTestInstance testCase) {
this.runner = runner;
this.context = context;
this.testCase = testCase;
this.notifier = notifier;
this.notifier.addListener(result.createListener());
}
@Override
public void evaluate() throws Throwable {
ProcessBuilder builder = new ProcessBuilder();
builder.command(buildCommand());
builder.redirectErrorStream(true);
builder.directory(testCase.getWorkingDirectory());
System.out.println("Working directory: " + SystemProperty.INSTANCE.getProperty("user.dir"));
System.out.println("Command: " + builder.command());
Description parentDescription = runner.getDescription();
final Process process = builder.start();
try {
Scanner scanner = testCase.startTest(process);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if(line.endsWith("\\r")) {
line = line.substring(0, line.length() - 2);
}
if (line.startsWith(JUNIT_OUTPUT_DELIMITER)) {
System.out.println(line);
String[] tokens = line.split(JUNIT_OUTPUT_DELIMITER_REGEXP);
String event = tokens[1];
if ("testRunStarted".equals(event)) {
notifier.fireTestRunStarted(parentDescription);
} else if ("testRunFinished".equals(event)) {
notifier.fireTestRunFinished(result);
} else if ("testStarted".equals(event)) {
Description ofTest = findDescription(parentDescription, tokens[2]);
notifier.fireTestStarted(ofTest);
} else if ("testFinished".equals(event)) {
Description ofTest = findDescription(parentDescription, tokens[2]);
notifier.fireTestFinished(ofTest);
} else if ("testFailure".equals(event)) {
List<String> stackTrace = tokens.length > 5 ? Arrays.asList(tokens).subList(5, tokens.length - 1) : Collections.<String>emptyList();
Failure failure = toFailure(parentDescription, tokens[2], tokens[3], tokens[4], stackTrace);
notifier.fireTestFailure(failure);
} else if ("testAssumptionFailure".equals(event)) {
List<String> stackTrace = tokens.length > 5 ? Arrays.asList(tokens).subList(5, tokens.length - 1) : Collections.<String>emptyList();
Failure failure = toFailure(parentDescription, tokens[2], tokens[3], tokens[4], stackTrace);
notifier.fireTestAssumptionFailed(failure);
} else if ("testIgnored".equals(event)) {
Description ofTest = findDescription(parentDescription, tokens[2]);
notifier.fireTestIgnored(ofTest);
}
} else {
System.out.println(line);
}
}
} catch (Throwable t) {
System.err.println("Failed to execute test");
t.printStackTrace();
throw t;
} finally {
try {
testCase.endTest(process);
} finally {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
process.destroy();
}
}, 10 * 1000);
try {
process.waitFor();
} catch (InterruptedException e) {
// ignore
}
timer.cancel();
}
}
}
private String[] buildCommand() {
List<String> list = new ArrayList<String>();
list.add(context.getJavaExecutable());
list.add("-cp");
list.add(getClassPathAsString());
list.add(getAgent());
list.add("-Dpinpoint.agentId=build.test.0");
list.add("-Dpinpoint.applicationName=test");
list.add("-D" + PINPOINT_TEST_ID + "=" + testCase.getTestId());
for (String arg : context.getJvmArguments()) {
list.add(arg);
}
if (context.isDebug()) {
list.addAll(getDebugOptions());
}
if (context.getConfigFile() != null) {
list.add("-Dpinpoint.config=" + context.getConfigFile());
}
for (String arg : testCase.getVmArgs()) {
list.add(arg);
}
String mainClass = testCase.getMainClass();
if (mainClass.endsWith(".jar")) {
list.add("-jar");
}
list.add(mainClass);
list.addAll(testCase.getAppArgs());
return list.toArray(new String[list.size()]);
}
private List<String> getDebugOptions() {
return Arrays.asList("-Xdebug", "-agentlib:jdwp=transport=dt_socket,address=1296,server=y,suspend=y");
}
private String getAgent() {
return "-javaagent:" + context.getAgentJar() + "=AGENT_TYPE=PLUGIN_TEST";
}
private String getClassPathAsString() {
StringBuilder classPath = new StringBuilder();
boolean first = true;
for (String lib : testCase.getClassPath()) {
if (first) {
first = false;
} else {
classPath.append(File.pathSeparatorChar);
}
classPath.append(lib);
}
return classPath.toString();
}
private Description findDescription(Description parentDescription, String displayName) {
if (displayName.equals(parentDescription.getDisplayName())) {
return parentDescription;
}
for (Description desc : parentDescription.getChildren()) {
Description found = findDescription(desc, displayName);
if (found != null) {
return found;
}
}
return null;
}
private Failure toFailure(Description parentDescription, String displayName, String exceptionClass, String message, List<String> trace) {
Description desc = findDescription(parentDescription, displayName);
Exception exception = toException(message, exceptionClass, trace);
Failure failure = new Failure(desc, exception);
return failure;
}
private PinpointPluginTestException toException(String message, String exceptionClass, List<String> traceInText) {
StackTraceElement[] stackTrace = new StackTraceElement[traceInText.size()];
for (int i = 0; i < traceInText.size(); i++) {
String trace = traceInText.get(i);
if (trace.equals("$CAUSE$")) {
PinpointPluginTestException cause = toException(traceInText.get(i + 2), traceInText.get(i + 1), traceInText.subList(i + 3, traceInText.size()));
return new PinpointPluginTestException(exceptionClass + ": " + message, Arrays.copyOf(stackTrace, i), cause);
}
String[] tokens = trace.split(",");
if (tokens.length != 4) {
System.out.println("Unexpected trace string: " + trace);
stackTrace[i] = new StackTraceElement(trace, "", null, -1);
} else {
stackTrace[i] = new StackTraceElement(tokens[0], tokens[1], tokens[2], Integer.parseInt(tokens[3]));
}
}
return new PinpointPluginTestException(exceptionClass + ": " + message, stackTrace);
}
}