blob: 4cda2cca6259c01c23478f0664f5af4517e77525 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.maven.junit;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.event.ChangeListener;
import org.apache.maven.project.MavenProject;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.gsf.testrunner.api.RerunHandler;
import org.netbeans.modules.gsf.testrunner.api.RerunType;
import org.netbeans.modules.gsf.testrunner.api.Status;
import org.netbeans.modules.gsf.testrunner.api.TestSession;
import org.netbeans.modules.gsf.testrunner.api.TestSuite;
import org.netbeans.modules.gsf.testrunner.api.Testcase;
import org.netbeans.modules.gsf.testrunner.api.Trouble;
import org.netbeans.modules.gsf.testrunner.api.UnitTestsUsage;
import org.netbeans.modules.gsf.testrunner.api.CommonUtils;
import org.netbeans.modules.gsf.testrunner.api.CoreManager;
import org.netbeans.modules.junit.api.JUnitTestSuite;
import org.netbeans.modules.junit.api.JUnitTestcase;
import org.netbeans.modules.maven.api.Constants;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.api.PluginPropertyUtils;
import org.netbeans.modules.maven.api.execute.RunConfig;
import org.netbeans.modules.maven.api.execute.RunUtils;
import org.netbeans.modules.maven.api.output.OutputProcessor;
import org.netbeans.modules.maven.api.output.OutputVisitor;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
/**
*
* @author mkleint
*/
public class JUnitOutputListenerProvider implements OutputProcessor {
private static final String TESTTYPE_UNIT = "UNIT"; //NOI81N
private static final String TESTTYPE_INTEGRATION = "INTEGRATION"; //NOI81N
private TestSession session;
private String testType;
private String reportNameSuffix;
private final Pattern runningPattern;
private final Pattern outDirPattern2;
private final Pattern outDirPattern;
private File outputDir;
private String runningTestClass;
private final Set<String> usedNames;
private final long startTimeStamp;
private static final Logger LOG = Logger.getLogger(JUnitOutputListenerProvider.class.getName());
private final RunConfig config;
private boolean surefireRunningInParallel = false;
private ArrayList<String> runningTestClasses;
private ArrayList<String> runningTestClassesInParallel;
private static final String GROUP_FILE_NAME = "dir";
public JUnitOutputListenerProvider(RunConfig config) {
runningPattern = Pattern.compile("(?:\\[surefire\\] )?Running (.*)", Pattern.DOTALL); //NOI18N
outDirPattern = Pattern.compile ("(?:\\[INFO\\] )?Surefire report directory\\: (?<" + GROUP_FILE_NAME + ">.*)", Pattern.DOTALL); //NOI18N
outDirPattern2 = Pattern.compile("(?:\\[INFO\\] )?Setting reports dir\\: (?<" + GROUP_FILE_NAME + ">.*)", Pattern.DOTALL); //NOI18N
this.config = config;
usedNames = new HashSet<String>();
startTimeStamp = System.currentTimeMillis();
runningTestClasses = new ArrayList<String>();
runningTestClassesInParallel = new ArrayList<String>();
surefireRunningInParallel = isSurefireRunningInParallel();
}
private boolean isSurefireRunningInParallel() {
// http://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html
// http://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html
String parallel = PluginPropertyUtils.getPluginProperty(config.getMavenProject(),
Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_SUREFIRE, "parallel", "test", "parallel"); //NOI18N
if (parallel != null) {
return true;
}
String forkMode = PluginPropertyUtils.getPluginProperty(config.getMavenProject(),
Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_SUREFIRE, "forkMode", "test", "forkMode"); //NOI18N
if ("perthread".equals(forkMode)) {
String threadCount = PluginPropertyUtils.getPluginProperty(config.getMavenProject(),
Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_SUREFIRE, "threadCount", "test", "threadCount");
if (threadCount != null) {
if (Integer.parseInt(threadCount) > 1) {
return true;
}
}
}
String forkCount = PluginPropertyUtils.getPluginProperty(config.getMavenProject(),
Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_SUREFIRE, "forkCount", "test", "forkCount");
if (forkCount != null) {
int index = forkCount.indexOf("C");
int cpuCores = 1;
if (index != -1) {
forkCount = forkCount.substring(0, index);
cpuCores = Runtime.getRuntime().availableProcessors();
}
float forks;
try {
// example values: "1.5C", "4". http://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#forkCount
forks = NumberFormat.getNumberInstance(Locale.ENGLISH).parse(forkCount).floatValue();
} catch (ParseException ex) {
LOG.log(Level.FINE, null, ex);
forks = 1;
}
if (forks * cpuCores > 1) {
return true;
}
}
return false;
}
public @Override String[] getRegisteredOutputSequences() {
return new String[] {
"mojo-execute#surefire:test", //NOI18N
"mojo-execute#failsafe:integration-test" //NOI18N
};
}
public @Override void processLine(String line, OutputVisitor visitor) {
Matcher match = outDirPattern.matcher(line);
if (match.matches()) {
outputDir = new File(match.group(GROUP_FILE_NAME));
if (session == null) {
createSession(outputDir);
}
return;
}
match = outDirPattern2.matcher(line);
if (match.matches()) {
outputDir = new File(match.group(GROUP_FILE_NAME));
if (session == null) {
createSession(outputDir);
}
return;
}
if (session == null) {
return;
}
match = runningPattern.matcher(line);
if (match.matches()) {
if (surefireRunningInParallel) {
// make sure results are displayed in case of a failure
runningTestClassesInParallel.add(match.group(1));
} else {
if (runningTestClass != null && outputDir != null) {
// match.group(1) should be the FQN of a running test class but let's check to be on the safe side
// If the matcher matches it means that we have a new test class running,
// if not it probably means that this is user's text, e.g. "Running my cool test", so we can safely ignore it
if (!isFullJavaId(match.group(1))) {
return;
}
// tests are running sequentially, so update Test Results Window
generateTest();
}
runningTestClass = match.group(1);
}
}
match = testSuiteStatsPattern.matcher(line);
if (match.matches() && surefireRunningInParallel) {
runningTestClass = match.group(6);
if (runningTestClass != null && outputDir != null && !runningTestClasses.contains(runningTestClass)) {
// When using reuseForks=true and a forkCount value larger than one,
// the same output is produced many times, so show it only once in Test Results window
runningTestClasses.add(runningTestClass);
// runningTestClass should be the FQN of a running test class but let's check to be on the safe side
// If the matcher matches it means that we have a new test class running,
// if not it probably means that this is user's text, e.g. "Running my cool test", so we can safely ignore it
if (!isFullJavaId(runningTestClass)) {
return;
}
generateTest();
// runningTestClass did not fail so remove it from the list
runningTestClassesInParallel.remove(runningTestClass);
// runningTestClass might be the last one so make it null to avoid appearing twice when sequenceEnd() is called
runningTestClass = null;
}
}
}
private static final String SECONDS_REGEX = "s(?:ec(?:ond)?(?:s|\\(s\\))?)?"; //NOI18N
private static final String TESTSUITE_STATS_REGEX = "Tests run: +([0-9]+), +Failures: +([0-9]+), +Errors: +([0-9]+), +Skipped: +([0-9]+), +Time elapsed: +(.+)" + SECONDS_REGEX + " - in (.*)";
private static final Pattern testSuiteStatsPattern = Pattern.compile(TESTSUITE_STATS_REGEX);
static boolean isTestSuiteStats(String line) {
return testSuiteStatsPattern.matcher(line).matches();
}
static String getTestSuiteFromStats(String line) {
Matcher matcher = testSuiteStatsPattern.matcher(line);
return matcher.matches() ? matcher.group(6) : null;
}
private static final String JAVA_ID_START_REGEX = "\\p{javaJavaIdentifierStart}"; //NOI18N
private static final String JAVA_ID_PART_REGEX = "\\p{javaJavaIdentifierPart}"; //NOI18N
private static final String JAVA_ID_REGEX = "(?:" + JAVA_ID_START_REGEX + ')' + "(?:" + JAVA_ID_PART_REGEX + ")*"; //NOI18N
private static final String JAVA_ID_REGEX_FULL = JAVA_ID_REGEX + "(?:\\." + JAVA_ID_REGEX + ")*"; //NOI18N
private static final Pattern fullJavaIdPattern = Pattern.compile(JAVA_ID_REGEX_FULL);
static boolean isFullJavaId(String possibleNewRunningTestClass) {
return fullJavaIdPattern.matcher(possibleNewRunningTestClass).matches();
}
public @Override void sequenceStart(String sequenceId, OutputVisitor visitor) {
session = null;
reportNameSuffix = null;
testType = null;
String reportsDirectory = null;
if (("mojo-execute#surefire:test".equals(sequenceId))) { // NOI18N
// Fix for #257563 / SUREFIRE-1158, where surefire 2.19.x
// removed the printing of the output directory. Get the directory from
// the configuration directly or fallback to the plugin standard
if (usingSurefire219(this.config.getMavenProject())) {
// http://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#reportsDirectory
reportsDirectory = getReportsDirectory(
Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_SUREFIRE,
"test", "${project.build.directory}/surefire-reports"); //NOI81N
}
reportNameSuffix = PluginPropertyUtils.getPluginProperty(
config.getMavenProject(), Constants.GROUP_APACHE_PLUGINS,
Constants.PLUGIN_SUREFIRE, "reportNameSuffix", "test", //NOI81N
"surefire.reportNameSuffix"); //NOI81N
testType = TESTTYPE_UNIT;
} else if ("mojo-execute#failsafe:integration-test".equals(sequenceId)) { //NOI81N
reportsDirectory = getReportsDirectory(
Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_FAILSAFE,
"integration-test", "${project.build.directory}/failsafe-reports"); //NOI81N
reportNameSuffix = PluginPropertyUtils.getPluginProperty(
config.getMavenProject(), Constants.GROUP_APACHE_PLUGINS,
Constants.PLUGIN_FAILSAFE, "reportNameSuffix", "integration-test", //NOI81N
"surefire.reportNameSuffix"); //NOI81N
testType = TESTTYPE_INTEGRATION; //NOI81N
}
if (null != reportsDirectory) {
File absoluteFile = new File(reportsDirectory);
// configuration might be "target/directory", which is relative
// to the maven project or an absolute path
File relativeFile = new File(this.config.getMavenProject().getBasedir(), reportsDirectory);
if (absoluteFile.exists() && absoluteFile.isDirectory()) {
outputDir = absoluteFile;
} else {
if (relativeFile.exists() && relativeFile.isDirectory()) {
outputDir = relativeFile;
} else {
File parentFile = absoluteFile.getParentFile();
if (parentFile.exists() && parentFile.isDirectory()) {
absoluteFile.mkdir();
if (absoluteFile.exists() && absoluteFile.isDirectory()) {
outputDir = absoluteFile;
}
} else {
File parentRelativeFile = relativeFile.getParentFile();
if (parentRelativeFile.exists() && parentRelativeFile.isDirectory()) {
relativeFile.mkdir();
if (relativeFile.exists() && relativeFile.isDirectory()) {
outputDir = relativeFile;
}
}
}
}
}
if (null != outputDir) {
createSession(outputDir);
}
}
}
private String getReportsDirectory(String groupId, String artifactId, String goal, String fallbackExpression) {
String reportsDirectory = PluginPropertyUtils.getPluginProperty(config.getMavenProject(),
groupId, artifactId, "reportsDirectory", goal, null); // NOI18N
if (null == reportsDirectory) {
// fallback to default value
try {
Object defaultValue = PluginPropertyUtils
.createEvaluator(config.getMavenProject())
.evaluate(fallbackExpression);
if (defaultValue instanceof String) {
reportsDirectory = (String) defaultValue;
}
} catch (ExpressionEvaluationException ex) {
// NOP could not resolved default value
}
}
return reportsDirectory;
}
//#179703 allow multiple sessions per project, in case there are multiple executions of surefire plugin.
@NbBundle.Messages({
"# {0} - projectId",
"LBL_TESTTYPE_UNIT={0} (Unit)",
"# {0} - projectId",
"LBL_TESTTYPE_INTEGRATION={0} (Integration)",
"# {0} - projectId",
"# {1} - index (1 based) of created session",
"LBL_TESTTYPE_UNIT_INDEXED={0} (Unit) #{1}",
"# {0} - projectId",
"# {1} - index (1 based) of created session",
"LBL_TESTTYPE_INTEGRATION_INDEXED={0} (Integration) #{1}"
})
private String createSessionName(String projectId) {
String name;
if(testType != null && testType.equals(TESTTYPE_INTEGRATION)) {
name = Bundle.LBL_TESTTYPE_INTEGRATION(projectId);
} else {
name = Bundle.LBL_TESTTYPE_UNIT(projectId);
}
int index = 2;
while (usedNames.contains(name)) {
if (testType != null && testType.equals(TESTTYPE_INTEGRATION)) {
name = Bundle.LBL_TESTTYPE_INTEGRATION_INDEXED(projectId, index);
} else {
name = Bundle.LBL_TESTTYPE_UNIT_INDEXED(projectId, index);
}
index++;
}
usedNames.add(name);
return name;
}
private CoreManager getManagerProvider() {
Collection<? extends Lookup.Item<CoreManager>> providers = Lookup.getDefault().lookupResult(CoreManager.class).allItems();
for (Lookup.Item<CoreManager> provider : providers) {
if(provider.getDisplayName().equals(CommonUtils.MAVEN_PROJECT_TYPE.concat("_").concat(CommonUtils.JUNIT_TF))) {
return provider.getInstance();
}
}
return null;
}
private void createSession(File nonNormalizedFile) {
if (session == null) {
File fil = FileUtil.normalizeFile(nonNormalizedFile);
Project prj = FileOwnerQuery.getOwner(Utilities.toURI(fil));
if (prj != null) {
NbMavenProject mvnprj = prj.getLookup().lookup(NbMavenProject.class);
if (mvnprj != null) {
File projectFile = FileUtil.toFile(prj.getProjectDirectory());
if (projectFile != null) {
UnitTestsUsage.getInstance().logUnitTestUsage(Utilities.toURI(projectFile), getJUnitVersion(config.getMavenProject()));
}
TestSession.SessionType type = TestSession.SessionType.TEST;
String action = config.getActionName();
if (action != null) { //custom
if (action.contains("debug")) { //NOI81N
type = TestSession.SessionType.DEBUG;
}
}
final TestSession.SessionType fType = type;
CoreManager junitManager = getManagerProvider();
if (junitManager != null) {
junitManager.registerNodeFactory();
}
session = new TestSession(createSessionName(mvnprj.getMavenProject().getId()), prj, TestSession.SessionType.TEST);
session.setRerunHandler(new RerunHandler() {
public @Override
void rerun() {
RunUtils.executeMaven(config);
}
public @Override
void rerun(Set<Testcase> tests) {
RunConfig brc = RunUtils.cloneRunConfig(config);
StringBuilder tst = new StringBuilder();
Map<String, Collection<String>> methods = new HashMap<String, Collection<String>>();
//#222776 calculate the approximate space the failed tests will occupy on the cmd line.
//important on windows which places a limit on the length.
int windowslimitcount = 0;
for (Testcase tc : tests) {
//TODO just when is the classname null??
if (tc.getClassName() != null) {
Collection<String> lst = methods.get(tc.getClassName());
if (lst == null) {
lst = new ArrayList<String>();
methods.put(tc.getClassName(), lst);
windowslimitcount = windowslimitcount + tc.getClassName().length() + 1; // + 1 for ,
}
lst.add(tc.getName());
windowslimitcount = windowslimitcount + tc.getName().length() + 1; // + 1 for # or +
}
}
boolean exceedsWindowsLimit = Utilities.isWindows() && windowslimitcount > 6000; //just be conservative here, the limit is more (8000+)
for (Map.Entry<String, Collection<String>> ent : methods.entrySet()) {
tst.append(",");
if (exceedsWindowsLimit) {
String clazzName = ent.getKey();
int lastDot = ent.getKey().lastIndexOf(".");
if (lastDot > -1) {
clazzName = clazzName.substring(lastDot + 1);
}
tst.append(clazzName);
} else {
tst.append(ent.getKey());
}
//#name only in surefire > 2.7.2 and junit > 4.0 or testng
// bug works with the setting also for junit 3.x
tst.append("#");
boolean first = true;
for (String meth : ent.getValue()) {
if (!first) {
tst.append("+");
}
first = false;
tst.append(meth);
}
}
if (tst.length() > 0) {
brc.setProperty("test", tst.substring(1));
}
RunUtils.executeMaven(brc);
}
public @Override
boolean enabled(RerunType type) {
//debug should now properly update debug port in runconfig...
if (fType.equals(TestSession.SessionType.TEST) || fType.equals(TestSession.SessionType.DEBUG)) {
if (RerunType.ALL.equals(type)) {
return true;
}
if (RerunType.CUSTOM.equals(type)) {
if (usingTestNG(config.getMavenProject())) { //#214334 test for testng has to come first, as itself depends on junit
return usingSurefire28(config.getMavenProject());
} else if (usingJUnit4(config.getMavenProject())) { //#214334
return usingSurefire2121(config.getMavenProject());
}
}
}
return false;
}
public @Override
void addChangeListener(ChangeListener listener) {
}
public @Override
void removeChangeListener(ChangeListener listener) {
}
});
if (junitManager != null) {
junitManager.testStarted(session);
}
}
}
}
}
private boolean usingSurefire219(MavenProject prj) {
String v = PluginPropertyUtils.getPluginVersion(prj, Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_SUREFIRE);
return v != null && new ComparableVersion(v).compareTo(new ComparableVersion("2.19")) >= 0;
}
private boolean usingSurefire2121(MavenProject prj) {
String v = PluginPropertyUtils.getPluginVersion(prj, Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_SUREFIRE);
return v != null && new ComparableVersion(v).compareTo(new ComparableVersion("2.12.1")) >= 0;
}
private boolean usingSurefire28(MavenProject prj) {
String v = PluginPropertyUtils.getPluginVersion(prj, Constants.GROUP_APACHE_PLUGINS, Constants.PLUGIN_SUREFIRE);
return v != null && new ComparableVersion(v).compareTo(new ComparableVersion("2.8")) >= 0;
}
private boolean usingTestNG(MavenProject prj) {
for (Artifact a : prj.getArtifacts()) {
if ("org.testng".equals(a.getGroupId()) && "testng".equals(a.getArtifactId())) {
return true;
}
}
return false;
}
private String getJUnitVersion(MavenProject prj) {
String juVersion = "";
for (Artifact a : prj.getArtifacts()) {
if ("junit".equals(a.getGroupId()) && ("junit".equals(a.getArtifactId()) || "junit-dep".equals(a.getArtifactId()))) { //junit-dep see #214238
String version = a.getVersion();
if (version != null && new ComparableVersion(version).compareTo(new ComparableVersion("4.8")) >= 0) {
return "JUNIT4"; //NOI18N
}
if (version != null && new ComparableVersion(version).compareTo(new ComparableVersion("3.8")) >= 0) {
return "JUNIT3"; //NOI18N
}
}
}
return juVersion;
}
private boolean usingJUnit4(MavenProject prj) { // SUREFIRE-724
for (Artifact a : prj.getArtifacts()) {
if ("junit".equals(a.getGroupId()) && ("junit".equals(a.getArtifactId()) || "junit-dep".equals(a.getArtifactId()))) { //junit-dep see #214238
String version = a.getVersion();
if (version != null && new ComparableVersion(version).compareTo(new ComparableVersion("4.8")) >= 0) {
return true;
}
}
}
return false;
}
public @Override void sequenceEnd(String sequenceId, OutputVisitor visitor) {
if (session == null) {
return;
}
if (runningTestClass != null && outputDir != null) {
generateTest();
}
CoreManager junitManager = getManagerProvider();
if (junitManager != null) {
junitManager.sessionFinished(session);
}
runningTestClass = null;
outputDir = null;
session = null;
surefireRunningInParallel = false;
runningTestClasses = null;
runningTestClassesInParallel = null;
}
private static final Pattern COMPARISON_PATTERN = Pattern.compile(".*expected:<(.*)> but was:<(.*)>$"); //NOI18N
private static final Pattern COMPARISON_PATTERN_AFTER_65 = Pattern.compile(".*expected \\[(.*)\\] but found \\[(.*)\\]$"); //NOI18N
static Trouble constructTrouble(@NonNull String type, @NullAllowed String message, @NullAllowed String text, boolean error) {
Trouble t = new Trouble(error);
if (message != null) {
Matcher match = COMPARISON_PATTERN.matcher(message);
if (match.matches()) {
t.setComparisonFailure(new Trouble.ComparisonFailure(match.group(1), match.group(2)));
} else {
match = COMPARISON_PATTERN_AFTER_65.matcher(message);
if (match.matches()) {
t.setComparisonFailure(new Trouble.ComparisonFailure(match.group(1), match.group(2)));
}
}
}
if (text != null) {
String[] strs = StringUtils.split(text, "\n");
List<String> lines = new ArrayList<String>();
if (message != null) {
lines.add(message);
}
lines.add(type);
for (int i = 1; i < strs.length; i++) {
lines.add(strs[i]);
}
t.setStackTrace(lines.toArray(new String[0]));
}
return t;
}
public @Override void sequenceFail(String sequenceId, OutputVisitor visitor) {
// try to get the failed test class. How can this be solved if it is not the first one in the list?
if(surefireRunningInParallel) {
if(runningTestClassesInParallel.isEmpty()) {
// no test case is currently running, so do nothing (is this a more serious failure?)
return;
}
runningTestClass = runningTestClassesInParallel.get(0);
}
sequenceEnd(sequenceId, visitor);
}
private void generateTest() {
String suffix = reportNameSuffix;
if (suffix == null) {
suffix = "";
} else {
//#204480
suffix = "-" + suffix;
}
File report = new File(outputDir, "TEST-" + runningTestClass + suffix + ".xml");
if (!report.isFile() || report.lastModified() < startTimeStamp) { //#219097 ignore results from previous invokation.
if(surefireRunningInParallel) { // try waiting a bit to give time for the result file to be created
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
}
}
if (!report.isFile() || report.lastModified() < startTimeStamp) { // and now try again
return;
}
}
if (report.length() > 50 * 1024 * 1024) {
LOG.log(Level.INFO, "Skipping report file as size is too big (> 50MB): {0}", report.getPath());
return;
}
try {
SAXBuilder builder = new SAXBuilder();
Document document = null;
try {
document = builder.build(report);
} catch (Exception x) {
try { // maybe the report file was not created yet, try waiting a bit and then try again
Thread.sleep(500);
document = builder.build(report);
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
} catch (JDOMException ex) {
LOG.log(Level.WARNING, "parsing " + report, x);
}
}
if(document == null) {
return;
}
Element testSuite = document.getRootElement();
assert "testsuite".equals(testSuite.getName()) : "Root name " + testSuite.getName(); //NOI18N
TestSuite suite = new JUnitTestSuite(testSuite.getAttributeValue("name"), session);
session.addSuite(suite);
CoreManager junitManager = getManagerProvider();
if (junitManager != null) {
junitManager.displaySuiteRunning(session, suite.getName());
}
File output = new File(outputDir, runningTestClass + suffix + "-output.txt");
@SuppressWarnings("unchecked")
List<Element> testcases = testSuite.getChildren("testcase"); //NOI18N
String nameSuffix = reportNameSuffix != null ? "(" + reportNameSuffix + ")" : "";
for (Element testcase : testcases) {
//#204480
String name = testcase.getAttributeValue("name");
if (name.endsWith(nameSuffix)) {
name = name.substring(0, name.length() - nameSuffix.length());
}
String displayName = name;
String methodName = name;
//In some cases (e.g. parameterized tests) Surefire appends some extra text to the test method name.
//Remove anything past the first non-dot non-Java identifier character (i.e. anything that would not be part of a legal method name)
int dotCodePoint = ".".codePointAt(0);
for (int i = 0; i < name.length(); i++) {
int codePoint = name.codePointAt(i);
if (!Character.isJavaIdentifierPart(codePoint) && codePoint != dotCodePoint) {
methodName = methodName.substring(0, i);
break;
}
}
Testcase test = new JUnitTestcase(methodName, displayName, testType, session);
Element stdout = testcase.getChild("system-out"); //NOI18N
// If *-output.txt file exists do not log standard output here to avoid logging it twice.
// By default surefire only reports standard output for failing testcases.
if (!output.isFile() && stdout != null) {
logText(stdout.getText(), test, false);
}
Element failure = testcase.getChild("failure"); //NOI18N
Status status = Status.PASSED;
Trouble trouble = null;
if (failure != null) {
status = Status.FAILED;
trouble = constructTrouble(failure.getAttributeValue("type"), failure.getAttributeValue("message"), failure.getText(), false);
}
Element error = testcase.getChild("error"); //NOI18N
if (error != null) {
status = Status.ERROR;
trouble = constructTrouble(error.getAttributeValue("type"), error.getAttributeValue("message"), error.getText(), true);
}
Element skipped = testcase.getChild("skipped"); //NOI18N
if (skipped != null) {
status = Status.SKIPPED;
}
test.setStatus(status);
if (trouble != null) {
test.setTrouble(trouble);
}
String time = testcase.getAttributeValue("time");
if (time != null) {
// the surefire plugin does not print out localised numbers, so use the english format
float fl = NumberFormat.getNumberInstance(Locale.ENGLISH).parse(time).floatValue();
test.setTimeMillis((long)(fl * 1000));
}
String classname = testcase.getAttributeValue("classname");
if (classname != null) {
//#204480
if (classname.endsWith(nameSuffix)) {
classname = classname.substring(0, classname.length() - nameSuffix.length());
}
test.setClassName(classname);
test.setLocation(test.getClassName().replace('.', '/') + ".java");
}
session.addTestCase(test);
}
String time = testSuite.getAttributeValue("time");
// the surefire plugin does not print out localised numbers, so use the english format
float fl = NumberFormat.getNumberInstance(Locale.ENGLISH).parse(time).floatValue();
long timeinmilis = (long) (fl * 1000);
if (junitManager != null) {
junitManager.displayReport(session, session.getReport(timeinmilis));
} else { // update report status as a minimum
session.getReport(timeinmilis).setCompleted(true);
}
session.finishSuite(suite);
if (output.isFile()) {
if (junitManager != null) {
junitManager.displayOutput(session, FileUtils.fileRead(output), false);
}
}
} catch (IOException x) {
LOG.log(Level.WARNING, "parsing " + report, x);
} catch (ParseException x) {
LOG.log(Level.WARNING, "parsing " + report, x);
}
}
private void logText(String text, Testcase test, boolean failure) {
StringTokenizer tokens = new StringTokenizer(text, "\n"); //NOI18N
List<String> lines = new ArrayList<String>();
while (tokens.hasMoreTokens()) {
lines.add(tokens.nextToken());
}
CoreManager junitManager = getManagerProvider();
if (junitManager != null) {
junitManager.displayOutput(session, text, failure);
}
test.addOutputLines(lines);
}
}