mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-13 21:55:01 +07:00
refactoring [junit5, test]: Add reporting structure for JUnit 5 integration
GitOrigin-RevId: 2ccaf0ada562f3de56da6bc70330794d5b14fcb6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f0f4245754
commit
f46575397c
@@ -1,14 +1,25 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.rt.execution.junit;
|
||||
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public final class MapSerializerUtil {
|
||||
public static final String TEST_STARTED = "testStarted";
|
||||
public static final String TEST_FINISHED = "testFinished";
|
||||
|
||||
public static final String TEST_FAILED = "testFailed";
|
||||
public static final String TEST_IGNORED = "testIgnored";
|
||||
public static final String TEST_STD_OUT = "testStdOut";
|
||||
|
||||
public static final String TEST_SUITE_STARTED = "testSuiteStarted";
|
||||
public static final String TEST_SUITE_FINISHED = "testSuiteFinished";
|
||||
|
||||
public static final String SUITE_TREE_STARTED = "suiteTreeStarted";
|
||||
public static final String SUITE_TREE_ENDED = "suiteTreeEnded";
|
||||
public static final String SUITE_TREE_NODE = "suiteTreeNode";
|
||||
|
||||
public static final String ROOT_NAME = "rootName";
|
||||
|
||||
/**
|
||||
* String escaping info provider.
|
||||
@@ -99,14 +110,16 @@ public final class MapSerializerUtil {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String asString(final String messageName, final Map attributes) {
|
||||
String text = "##teamcity[" + messageName;
|
||||
for (Iterator iterator = attributes.keySet().iterator(); iterator.hasNext(); ) {
|
||||
final Object attrName = iterator.next();
|
||||
text += " " + attrName + "='" + escapeStr((String)attributes.get(attrName), STD_ESCAPER) + "'";
|
||||
public static String asString(final String messageName, final Map<String, String> attributes) {
|
||||
StringBuilder text = new StringBuilder("##teamcity[" + messageName);
|
||||
for (final Object attrName : attributes.keySet()) {
|
||||
text.append(" ")
|
||||
.append(attrName)
|
||||
.append("='")
|
||||
.append(escapeStr(attributes.get(attrName), STD_ESCAPER))
|
||||
.append("'");
|
||||
}
|
||||
text += "]";
|
||||
return text;
|
||||
text.append("]");
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,7 @@ public class JUnit5IdeaTestRunner implements IdeaTestRunner<TestIdentifier> {
|
||||
myForkedTestPlan = LauncherFactory.create().discover(discoveryRequest);
|
||||
final Set<TestIdentifier> roots = myForkedTestPlan.getRoots();
|
||||
if (roots.isEmpty()) return null;
|
||||
@SuppressWarnings("SSBasedInspection")
|
||||
List<TestIdentifier> nonEmptyRoots = roots.stream()
|
||||
.filter(identifier -> !myForkedTestPlan.getChildren(identifier).isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
@@ -115,6 +116,7 @@ public class JUnit5IdeaTestRunner implements IdeaTestRunner<TestIdentifier> {
|
||||
final TestIdentifier testIdentifier = child;
|
||||
final String className = JUnit5TestExecutionListener.getClassName(testIdentifier);
|
||||
final String methodSignature = JUnit5TestExecutionListener.getMethodSignature(testIdentifier);
|
||||
//noinspection ConstantValue
|
||||
if (methodSignature != null) {
|
||||
return className + "," + methodSignature;
|
||||
}
|
||||
@@ -155,4 +157,4 @@ public class JUnit5IdeaTestRunner implements IdeaTestRunner<TestIdentifier> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,405 +1,118 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.junit5;
|
||||
|
||||
import com.intellij.junit4.ExpectedPatterns;
|
||||
import com.intellij.rt.execution.junit.ComparisonFailureData;
|
||||
import com.intellij.rt.execution.junit.MapSerializerUtil;
|
||||
import com.intellij.junit5.report.ExecutionState;
|
||||
import com.intellij.junit5.report.TeamCityTestReporter;
|
||||
import org.junit.platform.engine.TestExecutionResult;
|
||||
import org.junit.platform.engine.TestSource;
|
||||
import org.junit.platform.engine.UniqueId;
|
||||
import org.junit.platform.engine.reporting.ReportEntry;
|
||||
import org.junit.platform.engine.support.descriptor.ClassSource;
|
||||
import org.junit.platform.engine.support.descriptor.CompositeTestSource;
|
||||
import org.junit.platform.engine.support.descriptor.FileSource;
|
||||
import org.junit.platform.engine.support.descriptor.MethodSource;
|
||||
import org.junit.platform.launcher.TestExecutionListener;
|
||||
import org.junit.platform.launcher.TestIdentifier;
|
||||
import org.junit.platform.launcher.TestPlan;
|
||||
import org.opentest4j.MultipleFailuresError;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.rt.execution.TestListenerProtocol.CLASS_CONFIGURATION;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
public class JUnit5TestExecutionListener implements TestExecutionListener {
|
||||
private static final String NO_LOCATION_HINT = "";
|
||||
private static final String NO_LOCATION_HINT_VALUE = "";
|
||||
|
||||
private final PrintStream myPrintStream;
|
||||
private TestPlan myTestPlan;
|
||||
private long myCurrentTestStart;
|
||||
private int myFinishCount = 0;
|
||||
private String myRootName;
|
||||
private String myPresentableName;
|
||||
private boolean mySuccessful = true;
|
||||
private String myIdSuffix = "";
|
||||
private boolean mySendTree;
|
||||
private final ExecutionState myState;
|
||||
|
||||
public JUnit5TestExecutionListener() {
|
||||
this(System.out);
|
||||
}
|
||||
|
||||
public JUnit5TestExecutionListener(PrintStream printStream) {
|
||||
myPrintStream = printStream;
|
||||
myPrintStream.println("##teamcity[enteredTheMatrix]");
|
||||
myState = new ExecutionState(printStream);
|
||||
myState.print("##teamcity[enteredTheMatrix]");
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return mySuccessful;
|
||||
return myState.wasSuccessful();
|
||||
}
|
||||
|
||||
public void initializeIdSuffix(boolean forked) {
|
||||
if (forked && myIdSuffix.isEmpty()) {
|
||||
myIdSuffix = String.valueOf(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
public void initializeIdSuffix(int i) {
|
||||
myIdSuffix = i + "th";
|
||||
myState.initializeIdSuffix(forked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("timestamp = ").append(entry.getTimestamp());
|
||||
entry.getKeyValuePairs().forEach((key, value) -> builder.append(", ").append(key).append(" = ").append(value));
|
||||
builder.append("\n");
|
||||
myPrintStream.println("##teamcity[testStdOut" + idAndName(testIdentifier) + " out = '" + escapeName(builder.toString()) + "']");
|
||||
public void initializeIdSuffix(int i) {
|
||||
myState.initializeIdSuffix(i);
|
||||
}
|
||||
|
||||
public void setRootName(String rootName) {
|
||||
myState.setRootName(rootName);
|
||||
}
|
||||
|
||||
public void setPresentableName(String presentableName) {
|
||||
myState.setPresentableName(presentableName);
|
||||
}
|
||||
|
||||
public void setSendTree() {
|
||||
myState.setSendTree(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testPlanExecutionStarted(TestPlan testPlan) {
|
||||
myTestPlan = testPlan;
|
||||
if (mySendTree) {
|
||||
for (TestIdentifier root : myTestPlan.getRoots()) {
|
||||
myState.setPlan(testPlan);
|
||||
|
||||
if (myState.isSendTree()) {
|
||||
for (TestIdentifier root : testPlan.getRoots()) {
|
||||
assert root.isContainer();
|
||||
for (TestIdentifier testIdentifier : myTestPlan.getChildren(root)) {
|
||||
|
||||
for (TestIdentifier testIdentifier : testPlan.getChildren(root)) {
|
||||
String legacyReportingName = testIdentifier.getLegacyReportingName();
|
||||
if (legacyReportingName != null && legacyReportingName.equals(myRootName)) setPresentableName(testIdentifier.getDisplayName());
|
||||
if (legacyReportingName != null && legacyReportingName.equals(myState.getRootName())) {
|
||||
myState.setPresentableName(testIdentifier.getDisplayName());
|
||||
}
|
||||
sendTreeUnderRoot(testIdentifier, new HashSet<>());
|
||||
}
|
||||
}
|
||||
myPrintStream.println("##teamcity[treeEnded]");
|
||||
}
|
||||
|
||||
if (myRootName != null) {
|
||||
int lastPointIdx = myRootName.lastIndexOf('.');
|
||||
String name = myRootName;
|
||||
String comment = null;
|
||||
if (lastPointIdx >= 0) {
|
||||
name = myRootName.substring(lastPointIdx + 1);
|
||||
comment = myRootName.substring(0, lastPointIdx);
|
||||
}
|
||||
|
||||
String messageName = (myPresentableName == null || myPresentableName.isEmpty()) ? name : myPresentableName;
|
||||
|
||||
myPrintStream.println("##teamcity[rootName name = '" + escapeName(messageName) +
|
||||
(comment != null ? ("' comment = '" + escapeName(comment)) : "") + "'" +
|
||||
" location = 'java:suite://" + escapeName(myRootName) +
|
||||
"']");
|
||||
myState.print("##teamcity[treeEnded]");
|
||||
}
|
||||
myState.printRootNameIfNeeded();
|
||||
}
|
||||
|
||||
private void sendTreeUnderRoot(TestIdentifier root, HashSet<TestIdentifier> visited) {
|
||||
String idAndName = idAndName(root);
|
||||
if (root.isContainer()) {
|
||||
boolean skipContainer = shouldSkipContainer(root);
|
||||
if (!skipContainer) myPrintStream.println("##teamcity[suiteTreeStarted" + idAndName + " " + getLocationHint(root) + "]");
|
||||
for (TestIdentifier childIdentifier : myTestPlan.getChildren(root)) {
|
||||
if (visited.add(childIdentifier)) {
|
||||
sendTreeUnderRoot(childIdentifier, visited);
|
||||
}
|
||||
else {
|
||||
System.err.println("Identifier '" + getId(childIdentifier) + "' is reused");
|
||||
}
|
||||
public void sendTreeUnderRoot(TestIdentifier root, Set<TestIdentifier> visited) {
|
||||
TeamCityTestReporter reporter = TeamCityTestReporter.get(root, myState);
|
||||
|
||||
reporter.treeStarted().forEach(myState::print);
|
||||
for (TestIdentifier child : myState.plan().getChildren(root)) {
|
||||
if (visited.add(child)) {
|
||||
sendTreeUnderRoot(child, visited);
|
||||
}
|
||||
else {
|
||||
System.err.println("Identifier '" + child.getUniqueId() + "' is reused");
|
||||
}
|
||||
if (!skipContainer) myPrintStream.println("##teamcity[suiteTreeEnded" + idAndName + "]");
|
||||
}
|
||||
else if (root.isTest()) {
|
||||
myPrintStream.println("##teamcity[suiteTreeNode " + idAndName + " " + getLocationHint(root) + "]");
|
||||
}
|
||||
reporter.treeFinished().forEach(myState::print);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) {
|
||||
TeamCityTestReporter.get(testIdentifier, myState).output(entry)
|
||||
.forEach(myState::print);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executionSkipped(TestIdentifier testIdentifier, String reason) {
|
||||
executionStarted (testIdentifier);
|
||||
executionFinished(testIdentifier, TestExecutionResult.Status.ABORTED, null, reason);
|
||||
executionStarted(testIdentifier);
|
||||
TeamCityTestReporter.get(testIdentifier, myState)
|
||||
.skip(reason)
|
||||
.forEach(myState::print);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executionStarted(TestIdentifier testIdentifier) {
|
||||
if (testIdentifier.isTest()) {
|
||||
testStarted(testIdentifier);
|
||||
myCurrentTestStart = System.nanoTime();
|
||||
}
|
||||
else if (!shouldSkipContainer(testIdentifier)) {
|
||||
myFinishCount = 0;
|
||||
myPrintStream.println("##teamcity[testSuiteStarted" + idAndName(testIdentifier) + getLocationHint(testIdentifier) + "]");
|
||||
}
|
||||
TeamCityTestReporter.get(testIdentifier, myState).start()
|
||||
.forEach(myState::print);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) {
|
||||
TestExecutionResult.Status status = testExecutionResult.getStatus();
|
||||
Throwable throwableOptional = testExecutionResult.getThrowable().orElse(null);
|
||||
executionFinished(testIdentifier, status, throwableOptional, null);
|
||||
mySuccessful &= TestExecutionResult.Status.SUCCESSFUL == testExecutionResult.getStatus();
|
||||
}
|
||||
|
||||
private void executionFinished(TestIdentifier testIdentifier,
|
||||
TestExecutionResult.Status status,
|
||||
Throwable throwableOptional,
|
||||
String reason) {
|
||||
if (testIdentifier.isTest()) {
|
||||
long duration = getDuration();
|
||||
if (status == TestExecutionResult.Status.FAILED) {
|
||||
testFailure(testIdentifier, MapSerializerUtil.TEST_FAILED, throwableOptional, duration, reason, true);
|
||||
}
|
||||
else if (status == TestExecutionResult.Status.ABORTED) {
|
||||
testFailure(testIdentifier, MapSerializerUtil.TEST_IGNORED, throwableOptional, duration, reason, true);
|
||||
}
|
||||
testFinished(testIdentifier, duration);
|
||||
myFinishCount++;
|
||||
}
|
||||
else if (!shouldSkipContainer(testIdentifier) || status == TestExecutionResult.Status.FAILED){
|
||||
String messageName = null;
|
||||
if (status == TestExecutionResult.Status.FAILED) {
|
||||
messageName = MapSerializerUtil.TEST_FAILED;
|
||||
}
|
||||
else if (status == TestExecutionResult.Status.ABORTED) {
|
||||
messageName = MapSerializerUtil.TEST_IGNORED;
|
||||
}
|
||||
if (messageName != null) {
|
||||
Set<TestIdentifier> descendants = myTestPlan != null ? myTestPlan.getDescendants(testIdentifier) : Collections.emptySet();
|
||||
if (status == TestExecutionResult.Status.FAILED) {
|
||||
String parentId = getParentId(testIdentifier);
|
||||
String nameAndId = " name='" + CLASS_CONFIGURATION +
|
||||
"' nodeId='" + escapeName(getId(testIdentifier)) +
|
||||
"' parentNodeId='" + escapeName(parentId) + "' ";
|
||||
myPrintStream.println("##teamcity[testStarted " + nameAndId + " ]");
|
||||
testFailure(CLASS_CONFIGURATION, getId(testIdentifier), parentId, messageName, throwableOptional, 0, reason, true);
|
||||
myPrintStream.println("##teamcity[testFinished" + nameAndId + "]");
|
||||
}
|
||||
else if (descendants.isEmpty()) {
|
||||
testFailure(testIdentifier, MapSerializerUtil.TEST_IGNORED, throwableOptional, 0, reason, true);
|
||||
}
|
||||
|
||||
if (!descendants.isEmpty() && myFinishCount == 0) {
|
||||
for (TestIdentifier childIdentifier : descendants) {
|
||||
testStarted(childIdentifier);
|
||||
Throwable throwable = status == TestExecutionResult.Status.ABORTED ? throwableOptional : null;
|
||||
testFailure(childIdentifier, MapSerializerUtil.TEST_IGNORED, throwable, 0, reason, status == TestExecutionResult.Status.ABORTED);
|
||||
testFinished(childIdentifier, 0);
|
||||
}
|
||||
myFinishCount = 0;
|
||||
}
|
||||
}
|
||||
myPrintStream.println("##teamcity[testSuiteFinished" + idAndName(testIdentifier) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSkipContainer(TestIdentifier testIdentifier) {
|
||||
UniqueId id = UniqueId.parse(testIdentifier.getUniqueId());
|
||||
List<UniqueId.Segment> segments = id.getSegments();
|
||||
if (segments.isEmpty()) return false;
|
||||
UniqueId.Segment lastSegment = segments.get(segments.size() - 1);
|
||||
return lastSegment.getType().equals("engine") ||
|
||||
myRootName != null && myRootName.equals(lastSegment.getValue());
|
||||
}
|
||||
|
||||
protected long getDuration() {
|
||||
return (System.nanoTime() - myCurrentTestStart) / 1_000_000;
|
||||
}
|
||||
|
||||
private void testStarted(TestIdentifier testIdentifier) {
|
||||
myPrintStream.println("##teamcity[testStarted" + idAndName(testIdentifier) + " " + getLocationHint(testIdentifier) + "]");
|
||||
}
|
||||
|
||||
private void testFinished(TestIdentifier testIdentifier, long duration) {
|
||||
myPrintStream.println("##teamcity[testFinished" + idAndName(testIdentifier) + (duration > 0 ? " duration='" + duration + "'" : "") + "]");
|
||||
}
|
||||
|
||||
private void testFailure(TestIdentifier testIdentifier,
|
||||
String messageName,
|
||||
Throwable ex,
|
||||
long duration,
|
||||
String reason,
|
||||
boolean includeThrowable) {
|
||||
testFailure(testIdentifier.getDisplayName(), getId(testIdentifier), getParentId(testIdentifier), messageName, ex, duration, reason, includeThrowable);
|
||||
}
|
||||
|
||||
private void testFailure(String methodName,
|
||||
String id,
|
||||
String parentId,
|
||||
String messageName,
|
||||
Throwable ex,
|
||||
long duration,
|
||||
String reason,
|
||||
boolean includeThrowable) {
|
||||
Map<String, String> attrs = new LinkedHashMap<>();
|
||||
attrs.put("name", methodName);
|
||||
attrs.put("id", id);
|
||||
attrs.put("nodeId", id);
|
||||
attrs.put("parentNodeId", parentId);
|
||||
if (duration > 0) {
|
||||
attrs.put("duration", Long.toString(duration));
|
||||
}
|
||||
if (reason != null) {
|
||||
attrs.put("message", reason);
|
||||
}
|
||||
try {
|
||||
if (ex != null) {
|
||||
ComparisonFailureData failureData = null;
|
||||
if (ex instanceof MultipleFailuresError && ((MultipleFailuresError)ex).hasFailures()) {
|
||||
for (Throwable assertionError : ((MultipleFailuresError)ex).getFailures()) {
|
||||
testFailure(methodName, id, parentId, messageName, assertionError, duration, reason, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
failureData = ExpectedPatterns.createExceptionNotification(ex);
|
||||
}
|
||||
catch (Throwable ignore) {}
|
||||
}
|
||||
|
||||
if (includeThrowable || failureData == null) {
|
||||
ComparisonFailureData.registerSMAttributes(failureData, getTrace(ex), ex.getMessage(), attrs, ex, "Comparison Failure: ", "expected: <");
|
||||
}
|
||||
else {
|
||||
ComparisonFailureData.registerSMAttributes(failureData, "", ex.getMessage(), attrs, ex, "Comparison Failure: ", "expected: <");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
myPrintStream.println(MapSerializerUtil.asString(messageName, attrs));
|
||||
}
|
||||
}
|
||||
|
||||
protected String getTrace(Throwable ex) {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter writer = new PrintWriter(stringWriter);
|
||||
ex.printStackTrace(writer);
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
public void setRootName(String rootName) {
|
||||
myRootName = rootName;
|
||||
}
|
||||
|
||||
public void setPresentableName(String presentableName) {
|
||||
myPresentableName = presentableName;
|
||||
}
|
||||
|
||||
public void setSendTree() {
|
||||
mySendTree = true;
|
||||
}
|
||||
|
||||
private String getId(TestIdentifier identifier) {
|
||||
return identifier.getUniqueId() + myIdSuffix;
|
||||
}
|
||||
|
||||
private String idAndName(TestIdentifier testIdentifier) {
|
||||
return " id='" + escapeName(getId(testIdentifier)) +
|
||||
"' name='" + escapeName(testIdentifier.getDisplayName()) +
|
||||
"' nodeId='" + escapeName(getId(testIdentifier)) +
|
||||
"' parentNodeId='" + escapeName(getParentId(testIdentifier)) + "'";
|
||||
}
|
||||
|
||||
private String getParentId(TestIdentifier testIdentifier) {
|
||||
Optional<TestIdentifier> parent = myTestPlan.getParent(testIdentifier);
|
||||
return parent
|
||||
.map(identifier -> shouldSkipContainer(identifier) ? getParentId(identifier) : identifier.getUniqueId() + myIdSuffix)
|
||||
.orElse("0");
|
||||
}
|
||||
|
||||
|
||||
private String getLocationHint(TestIdentifier root) {
|
||||
return getLocationHint(root, myTestPlan.getParent(root).orElse(null));
|
||||
}
|
||||
|
||||
public static String getLocationHint(TestIdentifier root, TestIdentifier rootParent) {
|
||||
return root.getSource()
|
||||
.map(testSource -> getLocationHintValue(testSource, rootParent != null ? rootParent.getSource().orElse(null) : null))
|
||||
.filter(maybeLocationHintValue -> !NO_LOCATION_HINT_VALUE.equals(maybeLocationHintValue))
|
||||
.map(locationHintValue -> "locationHint='" + locationHintValue + "'" + getMetaInfo(root))
|
||||
.orElse(NO_LOCATION_HINT);
|
||||
}
|
||||
|
||||
private static String getMetaInfo(TestIdentifier root) {
|
||||
return root.getSource()
|
||||
.map(testSource -> {
|
||||
if (testSource instanceof MethodSource) {
|
||||
//noinspection SpellCheckingInspection
|
||||
return " metainfo='" + ((MethodSource)testSource).getMethodParameterTypes() + "'";
|
||||
}
|
||||
if (testSource instanceof ClassSource) {
|
||||
//noinspection SpellCheckingInspection
|
||||
return ((ClassSource)testSource).getPosition()
|
||||
.map(position ->
|
||||
" metainfo='"
|
||||
// Convert JUnit's 1-based values to 0-based
|
||||
+ (position.getLine() - 1) + ":"
|
||||
+ (position.getColumn().orElse(1) - 1) + "'")
|
||||
.orElse(NO_LOCATION_HINT);
|
||||
}
|
||||
return NO_LOCATION_HINT;
|
||||
})
|
||||
.orElse(NO_LOCATION_HINT);
|
||||
}
|
||||
|
||||
public static String getLocationHintValue(TestSource testSource, TestSource parentSource) {
|
||||
|
||||
if (testSource instanceof CompositeTestSource) {
|
||||
CompositeTestSource compositeTestSource = ((CompositeTestSource)testSource);
|
||||
for (TestSource sourceFromComposite : compositeTestSource.getSources()) {
|
||||
String locationHintValue = getLocationHintValue(sourceFromComposite, parentSource);
|
||||
if (!NO_LOCATION_HINT_VALUE.equals(locationHintValue)) {
|
||||
return locationHintValue;
|
||||
}
|
||||
}
|
||||
return NO_LOCATION_HINT_VALUE;
|
||||
}
|
||||
|
||||
if (testSource instanceof FileSource) {
|
||||
FileSource fileSource = (FileSource)testSource;
|
||||
File file = fileSource.getFile();
|
||||
String line = fileSource.getPosition()
|
||||
.map(position -> ":" + position.getLine())
|
||||
.orElse("");
|
||||
return "file://" + file.getAbsolutePath() + line;
|
||||
}
|
||||
|
||||
if (testSource instanceof MethodSource) {
|
||||
MethodSource methodSource = (MethodSource)testSource;
|
||||
return javaLocation(methodSource.getClassName(), methodSource.getMethodName(), true);
|
||||
}
|
||||
|
||||
if (testSource instanceof ClassSource) {
|
||||
String className = ((ClassSource)testSource).getClassName();
|
||||
return javaLocation(className, null, false);
|
||||
}
|
||||
|
||||
if (parentSource != null) {
|
||||
return getLocationHintValue(parentSource,null);
|
||||
}
|
||||
|
||||
return NO_LOCATION_HINT_VALUE;
|
||||
}
|
||||
|
||||
private static String javaLocation(String className, String maybeMethodName, boolean isTest) {
|
||||
String type = isTest ? "test" : "suite";
|
||||
String methodName = maybeMethodName == null ? "" : "/" + maybeMethodName;
|
||||
String location = escapeName(className + methodName);
|
||||
return "java:" + type + "://" + location;
|
||||
}
|
||||
|
||||
private static String escapeName(String str) {
|
||||
return MapSerializerUtil.escapeStr(str, MapSerializerUtil.STD_ESCAPER);
|
||||
TeamCityTestReporter.get(testIdentifier, myState).finish(testExecutionResult)
|
||||
.forEach(myState::print);
|
||||
myState.updateSuccessful(testExecutionResult.getStatus());
|
||||
}
|
||||
|
||||
public static String getClassName(TestIdentifier description) {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.junit5.report;
|
||||
|
||||
import com.intellij.rt.execution.junit.ComparisonFailureData;
|
||||
import com.intellij.rt.execution.junit.MapSerializerUtil;
|
||||
import org.junit.platform.engine.reporting.ReportEntry;
|
||||
import org.junit.platform.launcher.TestIdentifier;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.*;
|
||||
|
||||
public abstract class AbstractTestReporter implements TeamCityTestReporter {
|
||||
protected final TestIdentifier identifier;
|
||||
protected final ExecutionState state;
|
||||
|
||||
protected AbstractTestReporter(TestIdentifier identifier, ExecutionState state) {
|
||||
this.identifier = identifier;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> output(ReportEntry entry) {
|
||||
Map<String, String> attrs = attributes(ReportedField.ID, ReportedField.NAME, ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("timestamp = ").append(entry.getTimestamp());
|
||||
entry.getKeyValuePairs().forEach((key, value) -> builder.append(", ").append(key).append(" = ").append(value));
|
||||
builder.append("\n");
|
||||
|
||||
attrs.put("out", builder.toString());
|
||||
return Collections.singletonList(MapSerializerUtil.asString(MapSerializerUtil.TEST_STD_OUT, attrs));
|
||||
}
|
||||
|
||||
protected String id() {
|
||||
return identifier.getUniqueId() + state.suffix();
|
||||
}
|
||||
|
||||
protected Optional<SuiteReporter> getParent() {
|
||||
//noinspection SimplifyOptionalCallChains
|
||||
return state.plan().getParent(identifier)
|
||||
.map(i -> new SuiteReporter(i, state))
|
||||
.map(r -> r.isSkipped() ? r.getParent() : Optional.of(r))
|
||||
.orElse(Optional.empty());
|
||||
}
|
||||
|
||||
protected Map<String, String> attributes(ReportedField... fields) {
|
||||
Map<String, String> attrs = new LinkedHashMap<>(fields.length * 2);
|
||||
LocationInfo location = locationInfo();
|
||||
|
||||
for (ReportedField field : fields) {
|
||||
String value = null;
|
||||
switch (field) {
|
||||
case ID:
|
||||
case NODE_ID:
|
||||
value = id();
|
||||
break;
|
||||
case NAME:
|
||||
value = name();
|
||||
break;
|
||||
case PARENT_NODE_ID:
|
||||
value = getParent().map(AbstractTestReporter::id).orElse("0");
|
||||
break;
|
||||
case HINT:
|
||||
value = location.locationHint();
|
||||
break;
|
||||
case METAINFO:
|
||||
value = location.metainfo();
|
||||
break;
|
||||
}
|
||||
if (value != null) {
|
||||
attrs.put(field.getId(), value);
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
protected String name() {
|
||||
return identifier.getDisplayName();
|
||||
}
|
||||
|
||||
private LocationInfo locationInfo() {
|
||||
if (state.plan() == null) return LocationInfo.EMPTY;
|
||||
TestIdentifier parent = state.plan().getParent(identifier).orElse(null);
|
||||
return LocationInfo.compute(identifier, parent);
|
||||
}
|
||||
|
||||
protected static String getTrace(Throwable ex) {
|
||||
if (ComparisonFailureData.isAssertionError(ex.getClass())) return "";
|
||||
StringWriter writer = new StringWriter();
|
||||
ex.printStackTrace(new PrintWriter(writer));
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
protected enum ReportedField {
|
||||
ID("id"),
|
||||
NAME("name"),
|
||||
NODE_ID("nodeId"),
|
||||
PARENT_NODE_ID("parentNodeId"),
|
||||
HINT("locationHint"),
|
||||
METAINFO("metainfo");
|
||||
|
||||
private final String myId;
|
||||
|
||||
ReportedField(String id) {
|
||||
myId = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return myId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.junit5.report;
|
||||
|
||||
import com.intellij.rt.execution.junit.MapSerializerUtil;
|
||||
import org.junit.platform.engine.TestExecutionResult;
|
||||
import org.junit.platform.engine.reporting.ReportEntry;
|
||||
import org.junit.platform.launcher.TestIdentifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// e.g., parametrized test
|
||||
public class CompositeTestReporter extends AbstractTestReporter {
|
||||
private final TestReporter asTest;
|
||||
private final SuiteReporter asSuite;
|
||||
|
||||
public CompositeTestReporter(TestIdentifier identifier, ExecutionState state) {
|
||||
super(identifier, state);
|
||||
asTest = new TestReporter(identifier, state);
|
||||
asSuite = new SuiteReporter(identifier, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> start() {
|
||||
List<String> out = new ArrayList<>();
|
||||
out.addAll(asSuite.start());
|
||||
out.addAll(asTest.start());
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> output(ReportEntry entry) {
|
||||
return asTest.output(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> finish(TestExecutionResult result) {
|
||||
List<String> out = new ArrayList<>();
|
||||
out.addAll(asTest.finish(result));
|
||||
out.addAll(asSuite.finish(result));
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> treeStarted() {
|
||||
List<String> out = new ArrayList<>(asSuite.treeStarted());
|
||||
String testNode = MapSerializerUtil.asString(MapSerializerUtil.SUITE_TREE_NODE,
|
||||
attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID,
|
||||
ReportedField.HINT, ReportedField.METAINFO));
|
||||
out.add(testNode);
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> treeFinished() {
|
||||
return asSuite.treeFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> skip(String reason) {
|
||||
return asTest.skip(reason);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.junit5.report;
|
||||
|
||||
import com.intellij.rt.execution.junit.MapSerializerUtil;
|
||||
import org.junit.platform.engine.TestExecutionResult;
|
||||
import org.junit.platform.launcher.TestPlan;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final public class ExecutionState {
|
||||
private final PrintStream myOut;
|
||||
|
||||
private TestPlan myPlan;
|
||||
|
||||
private long myCurrentTestStartNanos;
|
||||
private int myFinishCount;
|
||||
private String myRootName;
|
||||
private String myPresentableName;
|
||||
private boolean mySuccessful = true;
|
||||
private String myIdSuffix = "";
|
||||
private boolean mySendTree;
|
||||
|
||||
public ExecutionState(PrintStream out) {
|
||||
myOut = out;
|
||||
}
|
||||
|
||||
public void setPlan(TestPlan plan) {
|
||||
myPlan = plan;
|
||||
}
|
||||
|
||||
public TestPlan plan() {
|
||||
return myPlan;
|
||||
}
|
||||
|
||||
public void print(String line) {
|
||||
myOut.println(line);
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return mySuccessful;
|
||||
}
|
||||
|
||||
public void updateSuccessful(TestExecutionResult.Status status) {
|
||||
mySuccessful &= (status == TestExecutionResult.Status.SUCCESSFUL);
|
||||
}
|
||||
|
||||
public void initializeIdSuffix(boolean forked) {
|
||||
if (forked && myIdSuffix.isEmpty()) {
|
||||
myIdSuffix = String.valueOf(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
public void initializeIdSuffix(int i) {
|
||||
myIdSuffix = i + "th";
|
||||
}
|
||||
|
||||
public String suffix() {
|
||||
return myIdSuffix;
|
||||
}
|
||||
|
||||
public void setRootName(String rootName) {
|
||||
myRootName = rootName;
|
||||
}
|
||||
|
||||
public String getRootName() {
|
||||
return myRootName;
|
||||
}
|
||||
|
||||
public void setPresentableName(String presentableName) {
|
||||
myPresentableName = presentableName;
|
||||
}
|
||||
|
||||
public String getPresentableName() {
|
||||
return myPresentableName;
|
||||
}
|
||||
|
||||
public void setSendTree(boolean sendTree) {
|
||||
mySendTree = sendTree;
|
||||
}
|
||||
|
||||
public boolean isSendTree() {
|
||||
return mySendTree;
|
||||
}
|
||||
|
||||
public void onLeafTestStarted() {
|
||||
myCurrentTestStartNanos = System.nanoTime();
|
||||
}
|
||||
|
||||
public long onLeafTestFinishedAndGetDurationMs() {
|
||||
return (System.nanoTime() - myCurrentTestStartNanos) / 1_000_000L;
|
||||
}
|
||||
|
||||
public void resetFinishCount() {
|
||||
myFinishCount = 0;
|
||||
}
|
||||
|
||||
public void incrementFinishCount() {
|
||||
myFinishCount++;
|
||||
}
|
||||
|
||||
public int finishCount() {
|
||||
return myFinishCount;
|
||||
}
|
||||
|
||||
public void printRootNameIfNeeded() {
|
||||
if (myRootName == null) return;
|
||||
|
||||
int lastPointIdx = myRootName.lastIndexOf('.');
|
||||
String name = myRootName;
|
||||
String comment = null;
|
||||
if (lastPointIdx >= 0) {
|
||||
name = myRootName.substring(lastPointIdx + 1);
|
||||
comment = myRootName.substring(0, lastPointIdx);
|
||||
}
|
||||
|
||||
String messageName = (myPresentableName == null || myPresentableName.isEmpty()) ? name : myPresentableName;
|
||||
|
||||
Map<String, String> attrs = new LinkedHashMap<>();
|
||||
attrs.put("name", messageName);
|
||||
if (comment != null) attrs.put("comment", comment);
|
||||
attrs.put("location", "java:suite://" + myRootName);
|
||||
|
||||
print(MapSerializerUtil.asString(MapSerializerUtil.ROOT_NAME, attrs));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.junit5.report;
|
||||
|
||||
import com.intellij.rt.execution.junit.MapSerializerUtil;
|
||||
import org.junit.platform.engine.TestSource;
|
||||
import org.junit.platform.engine.support.descriptor.*;
|
||||
import org.junit.platform.launcher.TestIdentifier;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
|
||||
final public class LocationInfo {
|
||||
static final LocationInfo EMPTY = new LocationInfo(null, null, null);
|
||||
|
||||
private final String locationHint;
|
||||
private final String metainfo;
|
||||
|
||||
public LocationInfo(TestSource opt, TestSource parent, TestIdentifier node) {
|
||||
this.locationHint = getLocationHintValue(opt, parent);
|
||||
if (this.locationHint.isEmpty()) {
|
||||
this.metainfo = null;
|
||||
} else {
|
||||
this.metainfo = getMetaInfoValue(node);
|
||||
}
|
||||
}
|
||||
|
||||
public String locationHint() {
|
||||
return locationHint;
|
||||
}
|
||||
|
||||
public String metainfo() {
|
||||
return metainfo;
|
||||
}
|
||||
|
||||
public static LocationInfo compute(TestIdentifier node, TestIdentifier parent) {
|
||||
TestSource parentSource = parent != null ? parent.getSource().orElse(null) : null;
|
||||
Optional<TestSource> sourceOpt = node.getSource();
|
||||
//noinspection OptionalIsPresent
|
||||
if (!sourceOpt.isPresent()) return EMPTY;
|
||||
|
||||
return new LocationInfo(sourceOpt.get(), parentSource, node);
|
||||
}
|
||||
|
||||
private static String getMetaInfoValue(TestIdentifier root) {
|
||||
if (root == null || !root.getSource().isPresent()) return null;
|
||||
|
||||
TestSource source = root.getSource().get();
|
||||
if (source instanceof MethodSource) {
|
||||
return ((MethodSource)source).getMethodParameterTypes();
|
||||
}
|
||||
else if (source instanceof ClassSource) {
|
||||
Optional<FilePosition> position = ((ClassSource)source).getPosition();
|
||||
if (!position.isPresent()) return null;
|
||||
|
||||
FilePosition pos = position.get();
|
||||
return (pos.getLine() - 1) + ":" + (pos.getColumn().orElse(1) - 1);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getLocationHintValue(TestSource testSource, TestSource parentSource) {
|
||||
if (testSource instanceof CompositeTestSource) {
|
||||
CompositeTestSource composite = (CompositeTestSource)testSource;
|
||||
for (TestSource s : composite.getSources()) {
|
||||
String v = getLocationHintValue(s, parentSource);
|
||||
if (!v.isEmpty()) return v;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
if (testSource instanceof FileSource) {
|
||||
FileSource fileSource = (FileSource)testSource;
|
||||
File file = fileSource.getFile();
|
||||
String line = fileSource.getPosition()
|
||||
.map(position -> ":" + position.getLine())
|
||||
.orElse("");
|
||||
return "file://" + file.getAbsolutePath() + line;
|
||||
}
|
||||
|
||||
if (testSource instanceof MethodSource) {
|
||||
MethodSource methodSource = (MethodSource)testSource;
|
||||
return javaLocation(methodSource.getClassName(), methodSource.getMethodName(), true);
|
||||
}
|
||||
|
||||
if (testSource instanceof ClassSource) {
|
||||
String className = ((ClassSource)testSource).getClassName();
|
||||
return javaLocation(className, null, false);
|
||||
}
|
||||
|
||||
if (parentSource != null) {
|
||||
return getLocationHintValue(parentSource, null);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String javaLocation(String className, String maybeMethodName, boolean isTest) {
|
||||
String type = isTest ? "test" : "suite";
|
||||
String methodName = maybeMethodName == null ? "" : "/" + maybeMethodName;
|
||||
return "java:" + type + "://" + MapSerializerUtil.escapeStr(className + methodName, MapSerializerUtil.STD_ESCAPER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.junit5.report;
|
||||
|
||||
import org.junit.platform.engine.TestExecutionResult;
|
||||
import org.junit.platform.engine.UniqueId;
|
||||
import org.junit.platform.launcher.TestIdentifier;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.rt.execution.TestListenerProtocol.CLASS_CONFIGURATION;
|
||||
import static com.intellij.rt.execution.junit.MapSerializerUtil.*;
|
||||
|
||||
public class SuiteReporter extends AbstractTestReporter {
|
||||
public SuiteReporter(TestIdentifier identifier, ExecutionState state) {
|
||||
super(identifier, state);
|
||||
}
|
||||
|
||||
// only for suites
|
||||
public boolean isSkipped() {
|
||||
UniqueId id = UniqueId.parse(identifier.getUniqueId());
|
||||
List<UniqueId.Segment> segments = id.getSegments();
|
||||
if (segments.isEmpty()) return false;
|
||||
|
||||
UniqueId.Segment lastSegment = segments.get(segments.size() - 1);
|
||||
if ("engine".equals(lastSegment.getType())) return true;
|
||||
|
||||
String root = state.getRootName();
|
||||
return root != null && root.equals(lastSegment.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> start() {
|
||||
if (isSkipped()) return Collections.emptyList();
|
||||
|
||||
state.resetFinishCount();
|
||||
String start = asString(TEST_SUITE_STARTED, attributes(ReportedField.ID, ReportedField.NAME, ReportedField.NODE_ID,
|
||||
ReportedField.PARENT_NODE_ID, ReportedField.HINT, ReportedField.METAINFO));
|
||||
return Collections.singletonList(start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> finish(TestExecutionResult result) {
|
||||
if (isSkipped() && result.getStatus() != TestExecutionResult.Status.FAILED) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> out = new ArrayList<>();
|
||||
TestExecutionResult.Status status = result.getStatus();
|
||||
Throwable throwable = result.getThrowable().orElse(null);
|
||||
Set<TestIdentifier> descendants = state.plan() != null ? state.plan().getDescendants(identifier) : Collections.emptySet();
|
||||
|
||||
if (status == TestExecutionResult.Status.FAILED) {
|
||||
// Report class-level failure as CLASS_CONFIGURATION test
|
||||
TestReporter reporter = new TestReporter(identifier, state, CLASS_CONFIGURATION);
|
||||
out.add(asString(TEST_STARTED, reporter.attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID,
|
||||
ReportedField.HINT, ReportedField.METAINFO)));
|
||||
out.addAll(reporter.reportFailure(throwable, 0, true, TEST_FAILED, null));
|
||||
out.add(asString(TEST_FINISHED, reporter.attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID)));
|
||||
}
|
||||
else if (status == TestExecutionResult.Status.ABORTED) {
|
||||
if (descendants.isEmpty()) {
|
||||
// Report as ignored
|
||||
Map<String, String> attrs = attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID);
|
||||
if (throwable != null) {
|
||||
attrs.put("message", throwable.getMessage());
|
||||
}
|
||||
out.add(asString(TEST_IGNORED, attrs));
|
||||
}
|
||||
}
|
||||
|
||||
if (!descendants.isEmpty() && state.finishCount() == 0) {
|
||||
String reason = (throwable != null) ? throwable.getMessage() : null;
|
||||
for (TestIdentifier child : descendants) {
|
||||
AbstractTestReporter reporter = TeamCityTestReporter.get(child, state);
|
||||
if (reporter instanceof TestReporter) {
|
||||
Throwable childEx = (status == TestExecutionResult.Status.ABORTED) ? throwable : null;
|
||||
out.addAll(((TestReporter)reporter).ignore(childEx, reason));
|
||||
}
|
||||
}
|
||||
state.resetFinishCount();
|
||||
}
|
||||
|
||||
if (!isSkipped()) {
|
||||
out.add(asString(TEST_SUITE_FINISHED, attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID)));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> treeStarted() {
|
||||
if (isSkipped()) return Collections.emptyList();
|
||||
|
||||
String started = asString(SUITE_TREE_STARTED, attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID,
|
||||
ReportedField.HINT, ReportedField.METAINFO
|
||||
));
|
||||
return Collections.singletonList(started);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> treeFinished() {
|
||||
if (isSkipped()) return Collections.emptyList();
|
||||
String finished = asString(SUITE_TREE_ENDED, attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID));
|
||||
return Collections.singletonList(finished);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> skip(String reason) {
|
||||
List<String> out = new ArrayList<>();
|
||||
Set<TestIdentifier> descendants = state.plan() != null ? state.plan().getDescendants(identifier) : Collections.emptySet();
|
||||
|
||||
if (descendants.isEmpty()) {
|
||||
Map<String, String> attrs = attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID);
|
||||
if (reason != null && !reason.isEmpty()) attrs.put("message", reason);
|
||||
out.add(asString(TEST_IGNORED, attrs));
|
||||
} else {
|
||||
for (TestIdentifier child : descendants) {
|
||||
AbstractTestReporter reporter = TeamCityTestReporter.get(child, state);
|
||||
out.addAll(reporter.start());
|
||||
out.addAll(reporter.skip(reason));
|
||||
}
|
||||
state.resetFinishCount();
|
||||
}
|
||||
|
||||
if (!isSkipped()) {
|
||||
out.add(asString(TEST_SUITE_FINISHED, attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID)));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.junit5.report;
|
||||
|
||||
import org.junit.platform.engine.TestExecutionResult;
|
||||
import org.junit.platform.engine.reporting.ReportEntry;
|
||||
import org.junit.platform.launcher.TestIdentifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TeamCityTestReporter {
|
||||
List<String> start();
|
||||
|
||||
List<String> output(ReportEntry entry);
|
||||
|
||||
List<String> finish(TestExecutionResult result);
|
||||
|
||||
List<String> skip(String reason);
|
||||
|
||||
List<String> treeStarted();
|
||||
|
||||
List<String> treeFinished();
|
||||
|
||||
static AbstractTestReporter get(TestIdentifier identifier, ExecutionState state) {
|
||||
if (identifier.isTest() && identifier.isContainer()) {
|
||||
return new CompositeTestReporter(identifier, state);
|
||||
}
|
||||
else if (identifier.isTest()) {
|
||||
return new TestReporter(identifier, state);
|
||||
}
|
||||
else {
|
||||
return new SuiteReporter(identifier, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.junit5.report;
|
||||
|
||||
import com.intellij.junit4.ExpectedPatterns;
|
||||
import com.intellij.rt.execution.junit.ComparisonFailureData;
|
||||
import org.junit.platform.engine.TestExecutionResult;
|
||||
import org.junit.platform.launcher.TestIdentifier;
|
||||
import org.opentest4j.MultipleFailuresError;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.intellij.rt.execution.junit.MapSerializerUtil.*;
|
||||
|
||||
public class TestReporter extends AbstractTestReporter {
|
||||
private final String name;
|
||||
|
||||
public TestReporter(TestIdentifier identifier, ExecutionState state, String name) {
|
||||
super(identifier, state);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public TestReporter(TestIdentifier identifier, ExecutionState state) {
|
||||
this(identifier, state, identifier.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> start() {
|
||||
state.onLeafTestStarted();
|
||||
String start = asString(TEST_STARTED, attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID,
|
||||
ReportedField.HINT, ReportedField.METAINFO));
|
||||
return Collections.singletonList(start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> finish(TestExecutionResult result) {
|
||||
Throwable ex = result.getThrowable().orElse(null);
|
||||
long duration = state.onLeafTestFinishedAndGetDurationMs();
|
||||
|
||||
List<String> out = new ArrayList<>();
|
||||
|
||||
switch (result.getStatus()) {
|
||||
case ABORTED:
|
||||
out.addAll(reportFailure(ex, duration, true, TEST_IGNORED));
|
||||
break;
|
||||
case FAILED:
|
||||
out.addAll(reportFailure(ex, duration, true, TEST_FAILED));
|
||||
break;
|
||||
case SUCCESSFUL: // do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
Map<String, String> attributes = attributes(ReportedField.ID, ReportedField.NAME, ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID);
|
||||
if (duration > 0) {
|
||||
attributes.put("duration", Long.toString(duration));
|
||||
}
|
||||
out.add(asString(TEST_FINISHED, attributes));
|
||||
|
||||
state.incrementFinishCount();
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> treeStarted() {
|
||||
String started = asString(SUITE_TREE_NODE, attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID,
|
||||
ReportedField.HINT, ReportedField.METAINFO
|
||||
));
|
||||
return Collections.singletonList(started);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> treeFinished() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> skip(String reason) {
|
||||
List<String> out = new ArrayList<>(reportFailure(null, 0, true, TEST_IGNORED, reason));
|
||||
out.add(asString(TEST_FINISHED, attributes(ReportedField.ID, ReportedField.NAME, ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID)));
|
||||
|
||||
state.incrementFinishCount();
|
||||
return out;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private List<String> reportFailure(Throwable ex,
|
||||
long duration,
|
||||
boolean includeThrowable,
|
||||
String messageType) {
|
||||
return reportFailure(ex, duration, includeThrowable, messageType, null);
|
||||
}
|
||||
|
||||
protected List<String> ignore(Throwable ex, String reason) {
|
||||
List<String> out = new ArrayList<>();
|
||||
out.add(asString(TEST_STARTED, attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID,
|
||||
ReportedField.HINT, ReportedField.METAINFO)));
|
||||
out.addAll(reportFailure(ex, 0, true, TEST_IGNORED, reason));
|
||||
out.add(asString(TEST_FINISHED, attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID)));
|
||||
return out;
|
||||
}
|
||||
|
||||
List<String> reportFailure(Throwable ex,
|
||||
long duration,
|
||||
boolean includeThrowable,
|
||||
String messageType, // TEST_FAILED or TEST_IGNORED
|
||||
String reason) {
|
||||
Map<String, String> attributes = attributes(ReportedField.ID, ReportedField.NAME,
|
||||
ReportedField.NODE_ID, ReportedField.PARENT_NODE_ID);
|
||||
|
||||
if (duration > 0) {
|
||||
attributes.put("duration", Long.toString(duration));
|
||||
}
|
||||
if (reason != null) {
|
||||
attributes.put("message", reason);
|
||||
}
|
||||
|
||||
List<String> out = new ArrayList<>();
|
||||
|
||||
if (ex != null) {
|
||||
ComparisonFailureData failureData = null;
|
||||
if (ex instanceof MultipleFailuresError && ((MultipleFailuresError)ex).hasFailures()) {
|
||||
for (Throwable t : ((MultipleFailuresError)ex).getFailures()) {
|
||||
out.addAll(reportFailure(t, duration, true, messageType, reason));
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
failureData = ExpectedPatterns.createExceptionNotification(ex);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
ComparisonFailureData.registerSMAttributes(failureData, includeThrowable ? getTrace(ex) : "",
|
||||
ex.getMessage(), attributes, ex,
|
||||
"Comparison Failure: ", "expected: <");
|
||||
}
|
||||
|
||||
out.add(asString(messageType, attributes));
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -47,14 +47,14 @@ public class JUnit5EventsTest {
|
||||
.finishWithFailure(multipleFailuresError);
|
||||
|
||||
Assertions.assertEquals("""
|
||||
##teamcity[enteredTheMatrix]
|
||||
##teamcity[rootName name = 'testClass' location = 'java:suite://testClass']
|
||||
##teamcity[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='test1()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' locationHint='java:test://com.intellij.junit5.testData.MyTestClass/test1' metainfo='']
|
||||
##teamcity[testStdOut id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='test1()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' out = 'timestamp = ${timestamp}, key1 = value1, stdout = out1|n']
|
||||
##teamcity[testFailed name='test1()' id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' message='message1|nComparison Failure: ' expected='expected1' actual='actual1' details='TRACE']
|
||||
##teamcity[testFailed name='test1()' id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' message='message2|nComparison Failure: ' expected='expected2' actual='actual2' details='TRACE']
|
||||
##teamcity[testFailed name='test1()' id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' message='2 errors (2 failures)|n\torg.opentest4j.AssertionFailedError: message1|n\torg.opentest4j.AssertionFailedError: message2' details='TRACE']
|
||||
##teamcity[testFinished id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='test1()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0']
|
||||
##TC[enteredTheMatrix]
|
||||
##TC[rootName name='testClass' location='java:suite://testClass']
|
||||
##TC[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='test1()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' locationHint='java:test://com.intellij.junit5.testData.MyTestClass/test1' metainfo='']
|
||||
##TC[testStdOut id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='test1()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' out='timestamp = ${timestamp}, key1 = value1, stdout = out1|n']
|
||||
##TC[testFailed id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='test1()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' message='message1|nComparison Failure: ' expected='expected1' actual='actual1' details='TRACE']
|
||||
##TC[testFailed id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='test1()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' message='message2|nComparison Failure: ' expected='expected2' actual='actual2' details='TRACE']
|
||||
##TC[testFailed id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='test1()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' message='2 errors (2 failures)|n\torg.opentest4j.AssertionFailedError: message1|n\torg.opentest4j.AssertionFailedError: message2' details='TRACE']
|
||||
##TC[testFinished id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='test1()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0']
|
||||
""".replace("${timestamp}", reportEntry.getTimestamp().toString()), builder.getFormattedOutput());
|
||||
}
|
||||
|
||||
@@ -82,11 +82,11 @@ public class JUnit5EventsTest {
|
||||
|
||||
Assertions.assertEquals(
|
||||
"""
|
||||
##teamcity[enteredTheMatrix]
|
||||
##teamcity[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:test1|]' name='test1 display name' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:test1|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass' metainfo='110:221']
|
||||
##teamcity[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:test2|]' name='test2 display name' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:test2|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='file:///directory/test2.java:12']
|
||||
##teamcity[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:test3|]' name='test3 display name' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:test3|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='java:test://MyTestClass/test4Method' metainfo='java.lang.String,java.util.List']
|
||||
##teamcity[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:test4|]' name='test4 display name' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:test4|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='java:suite://com.intellij.junit5.JUnit5EventsTest']
|
||||
##TC[enteredTheMatrix]
|
||||
##TC[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:test1|]' name='test1 display name' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:test1|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass' metainfo='110:221']
|
||||
##TC[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:test2|]' name='test2 display name' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:test2|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='file:///directory/test2.java:12']
|
||||
##TC[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:test3|]' name='test3 display name' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:test3|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='java:test://MyTestClass/test4Method' metainfo='java.lang.String,java.util.List']
|
||||
##TC[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:test4|]' name='test4 display name' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:test4|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='java:suite://com.intellij.junit5.JUnit5EventsTest']
|
||||
""", builder.getFormattedOutput());
|
||||
}
|
||||
|
||||
@@ -104,15 +104,14 @@ public class JUnit5EventsTest {
|
||||
testContext.startTestOnly().finishWithFailure(new IllegalStateException());
|
||||
|
||||
Assertions.assertEquals("""
|
||||
##teamcity[enteredTheMatrix]
|
||||
##teamcity[suiteTreeStarted id='|[engine:engine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[class:testClass|]' parentNodeId='0' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass']
|
||||
##teamcity[suiteTreeEnded id='|[engine:engine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[class:testClass|]' parentNodeId='0']
|
||||
##teamcity[treeEnded]
|
||||
##teamcity[rootName name = 'testMethod' location = 'java:suite://testMethod']
|
||||
##teamcity[testStarted name='Class Configuration' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' ]
|
||||
##teamcity[testFailed name='Class Configuration' id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' error='true' message='' details='TRACE']
|
||||
##teamcity[testFinished name='Class Configuration' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' ]
|
||||
##teamcity[testSuiteFinished id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='brokenStream()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]']
|
||||
##TC[enteredTheMatrix]
|
||||
##TC[suiteTreeStarted id='|[engine:engine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[class:testClass|]' parentNodeId='0' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass']
|
||||
##TC[suiteTreeEnded id='|[engine:engine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[class:testClass|]' parentNodeId='0']
|
||||
##TC[treeEnded]
|
||||
##TC[rootName name='testMethod' location='java:suite://testMethod']
|
||||
##TC[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='Class Configuration' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='java:test://com.intellij.junit5.testData.MyTestClass/brokenStream' metainfo='']
|
||||
##TC[testFailed id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='Class Configuration' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' error='true' message='' details='TRACE']
|
||||
##TC[testFinished id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='Class Configuration' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]']
|
||||
""", builder.getFormattedOutput());
|
||||
}
|
||||
|
||||
@@ -131,14 +130,14 @@ public class JUnit5EventsTest {
|
||||
builder.getExecutionListener().executionFinished(testIdentifier, TestExecutionResult.successful());
|
||||
|
||||
Assertions.assertEquals("""
|
||||
##teamcity[enteredTheMatrix]
|
||||
##teamcity[suiteTreeStarted id='|[engine:engine|]/|[suite:suiteClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[suite:suiteClass|]' parentNodeId='0' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass']
|
||||
##teamcity[suiteTreeStarted id='|[engine:secondEngine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:secondEngine|]/|[class:testClass|]' parentNodeId='|[engine:engine|]/|[suite:suiteClass|]' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass']
|
||||
##teamcity[suiteTreeEnded id='|[engine:secondEngine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:secondEngine|]/|[class:testClass|]' parentNodeId='|[engine:engine|]/|[suite:suiteClass|]']
|
||||
##teamcity[suiteTreeEnded id='|[engine:engine|]/|[suite:suiteClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[suite:suiteClass|]' parentNodeId='0']
|
||||
##teamcity[treeEnded]
|
||||
##teamcity[testSuiteStarted id='|[engine:secondEngine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:secondEngine|]/|[class:testClass|]' parentNodeId='|[engine:engine|]/|[suite:suiteClass|]'locationHint='java:suite://com.intellij.junit5.testData.MyTestClass']
|
||||
##teamcity[testSuiteFinished id='|[engine:secondEngine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:secondEngine|]/|[class:testClass|]' parentNodeId='|[engine:engine|]/|[suite:suiteClass|]']
|
||||
##TC[enteredTheMatrix]
|
||||
##TC[suiteTreeStarted id='|[engine:engine|]/|[suite:suiteClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[suite:suiteClass|]' parentNodeId='0' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass']
|
||||
##TC[suiteTreeStarted id='|[engine:secondEngine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:secondEngine|]/|[class:testClass|]' parentNodeId='|[engine:engine|]/|[suite:suiteClass|]' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass']
|
||||
##TC[suiteTreeEnded id='|[engine:secondEngine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:secondEngine|]/|[class:testClass|]' parentNodeId='|[engine:engine|]/|[suite:suiteClass|]']
|
||||
##TC[suiteTreeEnded id='|[engine:engine|]/|[suite:suiteClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[suite:suiteClass|]' parentNodeId='0']
|
||||
##TC[treeEnded]
|
||||
##TC[testSuiteStarted id='|[engine:secondEngine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:secondEngine|]/|[class:testClass|]' parentNodeId='|[engine:engine|]/|[suite:suiteClass|]' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass']
|
||||
##TC[testSuiteFinished id='|[engine:secondEngine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:secondEngine|]/|[class:testClass|]' parentNodeId='|[engine:engine|]/|[suite:suiteClass|]']
|
||||
""", builder.getFormattedOutput());
|
||||
}
|
||||
|
||||
@@ -153,15 +152,15 @@ public class JUnit5EventsTest {
|
||||
testContext.startTestOnly().finishAborted();
|
||||
|
||||
Assertions.assertEquals("""
|
||||
##teamcity[enteredTheMatrix]
|
||||
##teamcity[suiteTreeStarted id='|[engine:engine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[class:testClass|]' parentNodeId='0' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass']
|
||||
##teamcity[suiteTreeStarted id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='brokenStream()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='java:test://com.intellij.junit5.testData.MyTestClass/brokenStream' metainfo='']
|
||||
##teamcity[suiteTreeEnded id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='brokenStream()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]']
|
||||
##teamcity[suiteTreeEnded id='|[engine:engine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[class:testClass|]' parentNodeId='0']
|
||||
##teamcity[treeEnded]
|
||||
##teamcity[testSuiteStarted id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='brokenStream()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]'locationHint='java:test://com.intellij.junit5.testData.MyTestClass/brokenStream' metainfo='']
|
||||
##teamcity[testIgnored name='brokenStream()' id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]']
|
||||
##teamcity[testSuiteFinished id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='brokenStream()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]']
|
||||
##TC[enteredTheMatrix]
|
||||
##TC[suiteTreeStarted id='|[engine:engine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[class:testClass|]' parentNodeId='0' locationHint='java:suite://com.intellij.junit5.testData.MyTestClass']
|
||||
##TC[suiteTreeStarted id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='brokenStream()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='java:test://com.intellij.junit5.testData.MyTestClass/brokenStream' metainfo='']
|
||||
##TC[suiteTreeEnded id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='brokenStream()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]']
|
||||
##TC[suiteTreeEnded id='|[engine:engine|]/|[class:testClass|]' name='MyTestClass' nodeId='|[engine:engine|]/|[class:testClass|]' parentNodeId='0']
|
||||
##TC[treeEnded]
|
||||
##TC[testSuiteStarted id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='brokenStream()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]' locationHint='java:test://com.intellij.junit5.testData.MyTestClass/brokenStream' metainfo='']
|
||||
##TC[testIgnored id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='brokenStream()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]']
|
||||
##TC[testSuiteFinished id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='brokenStream()' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='|[engine:engine|]/|[class:testClass|]']
|
||||
""", builder.getFormattedOutput());
|
||||
}
|
||||
|
||||
@@ -189,10 +188,10 @@ public class JUnit5EventsTest {
|
||||
testContext.startExecution().finish();
|
||||
|
||||
Assertions.assertEquals("""
|
||||
##teamcity[enteredTheMatrix]
|
||||
##teamcity[rootName name = 'testClass' location = 'java:suite://testClass']
|
||||
##teamcity[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='|[test|'s method|]' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' locationHint='java:test://com.intellij.junit5.testData.AnnotationsTestClass/test1' metainfo='']
|
||||
##teamcity[testFinished id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='|[test|'s method|]' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0']
|
||||
##TC[enteredTheMatrix]
|
||||
##TC[rootName name='testClass' location='java:suite://testClass']
|
||||
##TC[testStarted id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='|[test|'s method|]' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0' locationHint='java:test://com.intellij.junit5.testData.AnnotationsTestClass/test1' metainfo='']
|
||||
##TC[testFinished id='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' name='|[test|'s method|]' nodeId='|[engine:engine|]/|[class:testClass|]/|[method:testMethod|]' parentNodeId='0']
|
||||
""", builder.getFormattedOutput());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.junit5;
|
||||
|
||||
import com.intellij.junit5.report.LocationInfo;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -53,8 +54,7 @@ class JUnit5NavigationTest {
|
||||
myTestSource = anySupportedSource();
|
||||
String locationHint = locationHint();
|
||||
|
||||
Assertions.assertTrue(locationHint.startsWith("locationHint='"), locationHint);
|
||||
Assertions.assertTrue(locationHint.endsWith("'"), locationHint);
|
||||
Assertions.assertEquals("java:suite://java.lang.String", locationHint);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -115,7 +115,8 @@ class JUnit5NavigationTest {
|
||||
|
||||
private String locationHint() {
|
||||
TestIdentifier testIdentifier = TestIdentifier.from(new ConfigurableTestDescriptor(myTestSource));
|
||||
return JUnit5TestExecutionListener.getLocationHint(testIdentifier, null);
|
||||
LocationInfo info = new LocationInfo(testIdentifier.getSource().orElse(null), null, null);
|
||||
return info.locationHint();
|
||||
}
|
||||
|
||||
private String locationHintValue() {
|
||||
@@ -124,7 +125,8 @@ class JUnit5NavigationTest {
|
||||
|
||||
private static String locationHintValue(final ConfigurableTestDescriptor descriptor) {
|
||||
TestIdentifier testIdentifier = TestIdentifier.from(descriptor);
|
||||
return JUnit5TestExecutionListener.getLocationHintValue(testIdentifier.getSource().orElseThrow(IllegalStateException::new), null);
|
||||
LocationInfo info = new LocationInfo(testIdentifier.getSource().orElseThrow(IllegalStateException::new), null, null);
|
||||
return info.locationHint();
|
||||
}
|
||||
|
||||
private static ClassSource anySupportedSource() {
|
||||
|
||||
@@ -87,17 +87,7 @@ public class JUnit5TestRunnerBuilder {
|
||||
public void write(int b) {
|
||||
myStringBuffer.append(new String(new byte[]{(byte)b}, StandardCharsets.UTF_8));
|
||||
}
|
||||
}, false, StandardCharsets.UTF_8)) {
|
||||
@Override
|
||||
protected long getDuration() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTrace(Throwable ex) {
|
||||
return "TRACE";
|
||||
}
|
||||
};
|
||||
}, false, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public TestDescriptorContext withTestMethod(Class<?> testClass, String methodName) throws NoSuchMethodException {
|
||||
@@ -220,7 +210,12 @@ public class JUnit5TestRunnerBuilder {
|
||||
}
|
||||
|
||||
public String getFormattedOutput() {
|
||||
return StringUtil.convertLineSeparators(myStringBuffer.toString()).replaceAll("\\|r", "");
|
||||
String out = myStringBuffer.toString();
|
||||
return StringUtil.convertLineSeparators(out)
|
||||
.replaceAll("\\|r", "")
|
||||
.replaceAll("##teamcity\\[", "##TC[")
|
||||
.replaceAll(" duration='[0-9]+'", "")
|
||||
.replaceAll("details='.*?'\\]", "details='TRACE']");
|
||||
}
|
||||
|
||||
public JUnit5TestExecutionListener getExecutionListener() {
|
||||
|
||||
@@ -11,14 +11,15 @@ import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import jetbrains.buildServer.messages.serviceMessages.*;
|
||||
import jetbrains.buildServer.messages.serviceMessages.BaseTestMessage;
|
||||
import jetbrains.buildServer.messages.serviceMessages.ServiceMessage;
|
||||
import jetbrains.buildServer.messages.serviceMessages.TestStarted;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.idea.maven.aether.ArtifactRepositoryManager;
|
||||
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.junit6.ServiceMessageUtil.replaceAttributes;
|
||||
@@ -44,39 +45,24 @@ public class JUnit6EventsTest extends AbstractTestFrameworkCompilingIntegrationT
|
||||
ProcessOutput output = doStartTestsProcess(createRunMethodConfiguration("com.intellij.junit6.testData.MyTestClass", "test1"));
|
||||
assertEmpty(output.err);
|
||||
|
||||
List<ServiceMessage> messages = output.messages;
|
||||
|
||||
Map<String, TestStarted> tests = getStartedTests(messages);
|
||||
// Ensure our test method started with a proper location hint
|
||||
TestStarted started = tests.values().stream()
|
||||
.filter(t -> "java:test://com.intellij.junit6.testData.MyTestClass/test1".equals(t.getAttributes().get("locationHint")))
|
||||
.findFirst().orElse(null);
|
||||
assertNotNull(started);
|
||||
assertEquals("java:test://com.intellij.junit6.testData.MyTestClass/test1", started.getAttributes().get("locationHint"));
|
||||
String nodeId = started.getAttributes().get("nodeId");
|
||||
|
||||
// There should be multiple failures reported for the same test
|
||||
List<ServiceMessage> failed = messages.stream()
|
||||
.filter(m -> nodeId.equals(m.getAttributes().get("nodeId")))
|
||||
.filter(m -> m instanceof TestFailed).toList();
|
||||
assertSize(3, failed);
|
||||
|
||||
Set<String> errors = failed.stream().filter(m -> m.getAttributes().get("expected") != null)
|
||||
.map(m -> "expected='" + m.getAttributes().get("expected") + "', actual='" + m.getAttributes().get("actual") + "'")
|
||||
.collect(Collectors.toSet());
|
||||
assertEquals(Set.of(
|
||||
"expected='expected1', actual='actual1'",
|
||||
"expected='expected2', actual='actual2'"
|
||||
), errors);
|
||||
|
||||
// StdOut message should include our published entries
|
||||
Set<String> outs = messages.stream()
|
||||
.filter(m -> nodeId.equals(m.getAttributes().get("nodeId")))
|
||||
.filter(m -> m instanceof TestStdOut)
|
||||
.map(m -> m.getAttributes().get("out"))
|
||||
String tests = output.messages.stream().filter(m -> m instanceof BaseTestMessage)
|
||||
.map(m -> replaceAttributes(m, Map.of(
|
||||
"timestamp", "##timestamp##",
|
||||
"duration", "##duration##",
|
||||
"details", "##details##"
|
||||
)))
|
||||
.map(m -> m.replaceAll("##teamcity\\[", "##TC["))
|
||||
.map(s -> s.replaceAll("timestamp = [0-9\\-:.T]+", "timestamp = ##timestamp##"))
|
||||
.collect(Collectors.toSet());
|
||||
assertEquals(Set.of("timestamp = ##timestamp##, key1 = value1, stdout = out1\n"), outs);
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
assertEquals("""
|
||||
##TC[testStarted id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' locationHint='java:test://com.intellij.junit6.testData.MyTestClass/test1' metainfo='org.junit.jupiter.api.TestReporter']
|
||||
##TC[testStdOut id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' out='timestamp = ##timestamp##, key1 = value1, stdout = out1|n']
|
||||
##TC[testFailed id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='message1 ==> expected: <expected1> but was: <actual1>|nComparison Failure: ' expected='expected1' actual='actual1' details='##details##']
|
||||
##TC[testFailed id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='message2 ==> expected: <expected2> but was: <actual2>|nComparison Failure: ' expected='expected2' actual='actual2' details='##details##']
|
||||
##TC[testFailed id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='2 errors (2 failures)|n org.opentest4j.AssertionFailedError: message1 ==> expected: <expected1> but was: <actual1>|n org.opentest4j.AssertionFailedError: message2 ==> expected: <expected2> but was: <actual2>' details='##details##']
|
||||
##TC[testFinished id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##']""",
|
||||
tests);
|
||||
}
|
||||
|
||||
public void testContainerFailure() throws Exception {
|
||||
@@ -84,46 +70,49 @@ public class JUnit6EventsTest extends AbstractTestFrameworkCompilingIntegrationT
|
||||
assertEmpty(output.err);
|
||||
|
||||
// Expect a configuration failure
|
||||
List<String> test = output.messages.stream().filter(BaseTestMessage.class::isInstance)
|
||||
String test = output.messages.stream().filter(BaseTestMessage.class::isInstance)
|
||||
.map(BaseTestMessage.class::cast)
|
||||
.filter(m -> m.getTestName().equals("Class Configuration"))
|
||||
.map(m -> replaceAttributes(m, Map.of("details", "##details##")))
|
||||
.toList();
|
||||
assertEquals(List.of(
|
||||
"##teamcity[testStarted name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0']",
|
||||
"##teamcity[testFailed name='Class Configuration' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0' error='true' message='java.lang.IllegalStateException: broken' details='##details##']",
|
||||
"##teamcity[testFinished name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0']"
|
||||
), test);
|
||||
.map(m -> m.replaceAll("##teamcity\\[", "##TC["))
|
||||
.collect(Collectors.joining("\n"));
|
||||
assertEquals("""
|
||||
##TC[testStarted id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0' locationHint='java:test://com.intellij.junit6.testData.MyTestClass/brokenStream' metainfo='']
|
||||
##TC[testFailed id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0' error='true' message='java.lang.IllegalStateException: broken' details='##details##']
|
||||
##TC[testFinished id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0']""",
|
||||
test);
|
||||
}
|
||||
|
||||
public void testContainerDisabled() throws Exception {
|
||||
ProcessOutput output = doStartTestsProcess(createRunClassConfiguration("com.intellij.junit6.testData.MyTestClass"));
|
||||
assertEmpty(output.err);
|
||||
|
||||
List<String> tests = output.messages.stream().filter(m -> m instanceof BaseTestMessage)
|
||||
String tests = output.messages.stream().filter(m -> m instanceof BaseTestMessage)
|
||||
.map(m -> replaceAttributes(m, Map.of(
|
||||
"timestamp", "##timestamp##",
|
||||
"duration", "##duration##",
|
||||
"message", "##message##",
|
||||
"details", "##details##"
|
||||
)))
|
||||
.map(s -> s.replaceAll("timestamp = [0-9\\-:.T]+", "timestamp = ##timestamp##")).toList();
|
||||
.map(m -> m.replaceAll("##teamcity\\[", "##TC["))
|
||||
.map(s -> s.replaceAll("timestamp = [0-9\\-:.T]+", "timestamp = ##timestamp##"))
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
assertEquals(List.of(
|
||||
"##teamcity[testStarted id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' name='disabledTest()' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' parentNodeId='0' locationHint='java:test://com.intellij.junit6.testData.MyTestClass/disabledTest' metainfo='']",
|
||||
"##teamcity[testIgnored name='disabledTest()' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' parentNodeId='0' message='##message##']",
|
||||
"##teamcity[testFinished id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' name='disabledTest()' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' parentNodeId='0']",
|
||||
"##teamcity[testStarted id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' locationHint='java:test://com.intellij.junit6.testData.MyTestClass/test1' metainfo='org.junit.jupiter.api.TestReporter']",
|
||||
"##teamcity[testStdOut id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' out='timestamp = ##timestamp##, key1 = value1, stdout = out1|n']",
|
||||
"##teamcity[testFailed name='test1(TestReporter)' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='##message##' expected='expected1' actual='actual1' details='##details##']",
|
||||
"##teamcity[testFailed name='test1(TestReporter)' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='##message##' expected='expected2' actual='actual2' details='##details##']",
|
||||
"##teamcity[testFailed name='test1(TestReporter)' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='##message##' details='##details##']",
|
||||
"##teamcity[testFinished id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##']",
|
||||
"##teamcity[testIgnored name='brokenStreamDisabled()' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStreamDisabled()|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStreamDisabled()|]' parentNodeId='0' message='##message##']",
|
||||
"##teamcity[testStarted name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0']",
|
||||
"##teamcity[testFailed name='Class Configuration' id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0' error='true' message='##message##' details='##details##']",
|
||||
"##teamcity[testFinished name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0']"
|
||||
), tests);
|
||||
assertEquals("""
|
||||
##TC[testStarted id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' name='disabledTest()' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' parentNodeId='0' locationHint='java:test://com.intellij.junit6.testData.MyTestClass/disabledTest' metainfo='']
|
||||
##TC[testIgnored id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' name='disabledTest()' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' parentNodeId='0' message='##message##']
|
||||
##TC[testFinished id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' name='disabledTest()' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:disabledTest()|]' parentNodeId='0']
|
||||
##TC[testStarted id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' locationHint='java:test://com.intellij.junit6.testData.MyTestClass/test1' metainfo='org.junit.jupiter.api.TestReporter']
|
||||
##TC[testStdOut id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' out='timestamp = ##timestamp##, key1 = value1, stdout = out1|n']
|
||||
##TC[testFailed id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='##message##' expected='expected1' actual='actual1' details='##details##']
|
||||
##TC[testFailed id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='##message##' expected='expected2' actual='actual2' details='##details##']
|
||||
##TC[testFailed id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##' message='##message##' details='##details##']
|
||||
##TC[testFinished id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' name='test1(TestReporter)' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[method:test1(org.junit.jupiter.api.TestReporter)|]' parentNodeId='0' duration='##duration##']
|
||||
##TC[testIgnored id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStreamDisabled()|]' name='brokenStreamDisabled()' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStreamDisabled()|]' parentNodeId='0' message='##message##']
|
||||
##TC[testStarted id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0' locationHint='java:test://com.intellij.junit6.testData.MyTestClass/brokenStream' metainfo='']
|
||||
##TC[testFailed id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0' error='true' message='##message##' details='##details##']
|
||||
##TC[testFinished id='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' name='Class Configuration' nodeId='|[engine:junit-jupiter|]/|[class:com.intellij.junit6.testData.MyTestClass|]/|[test-factory:brokenStream()|]' parentNodeId='0']""",
|
||||
tests);
|
||||
}
|
||||
|
||||
public void testEscaping() throws Exception {
|
||||
|
||||
@@ -15,8 +15,8 @@ import org.jetbrains.idea.maven.aether.ArtifactRepositoryManager;
|
||||
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.junit6.ServiceMessageUtil.replaceAttributes;
|
||||
|
||||
@@ -33,26 +33,28 @@ public class JUnit6NavigationTest extends AbstractTestFrameworkCompilingIntegrat
|
||||
ModuleRootModificationUtil.updateModel(myModule, model -> model.addContentEntry(getTestContentRoot())
|
||||
.addSourceFolder(getTestContentRoot() + "/test", true));
|
||||
final ArtifactRepositoryManager repoManager = getRepoManager();
|
||||
addMavenLibs(myModule, new JpsMavenRepositoryLibraryDescriptor("org.junit.jupiter", "junit-jupiter-api", JUnit6Constants.VERSION), repoManager);
|
||||
addMavenLibs(myModule, new JpsMavenRepositoryLibraryDescriptor("org.junit.jupiter", "junit-jupiter-api", JUnit6Constants.VERSION),
|
||||
repoManager);
|
||||
}
|
||||
|
||||
public void testNavigation() throws Exception {
|
||||
ProcessOutput output = doStartTestsProcess(createRunClassConfiguration("org.example.impl.NavTest"));
|
||||
assertEmpty(output.err);
|
||||
|
||||
List<String> messages = output.messages.stream().filter(m -> m.getAttributes().containsKey("locationHint"))
|
||||
String messages = output.messages.stream().filter(m -> m.getAttributes().containsKey("locationHint"))
|
||||
.map(m -> m.getAttributes().get("locationHint").startsWith("file://")
|
||||
? replaceAttributes(m, Map.of("locationHint", "file://##path##"))
|
||||
: m.asString())
|
||||
.toList();
|
||||
.map(m -> m.replaceAll("##teamcity\\[", "##TC["))
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
assertEquals(List.of(
|
||||
"##teamcity[suiteTreeStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' name='fileSourceDynamicTests()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/fileSourceDynamicTests' metainfo='']",
|
||||
"##teamcity[suiteTreeNode id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' name='methodNavigation()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/methodNavigation' metainfo='']",
|
||||
"##teamcity[testSuiteStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' name='fileSourceDynamicTests()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/fileSourceDynamicTests' metainfo='']",
|
||||
"##teamcity[testStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]/|[dynamic-test:#1|]' name='fileSource' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]/|[dynamic-test:#1|]' parentNodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' locationHint='file://##path##']",
|
||||
"##teamcity[testStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' name='methodNavigation()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/methodNavigation' metainfo='']"
|
||||
), messages);
|
||||
assertEquals("""
|
||||
##TC[suiteTreeStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' name='fileSourceDynamicTests()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/fileSourceDynamicTests' metainfo='']
|
||||
##TC[suiteTreeNode id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' name='methodNavigation()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/methodNavigation' metainfo='']
|
||||
##TC[testSuiteStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' name='fileSourceDynamicTests()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/fileSourceDynamicTests' metainfo='']
|
||||
##TC[testStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]/|[dynamic-test:#1|]' name='fileSource' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]/|[dynamic-test:#1|]' parentNodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[test-factory:fileSourceDynamicTests()|]' locationHint='file://##path##']
|
||||
##TC[testStarted id='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' name='methodNavigation()' nodeId='|[engine:junit-jupiter|]/|[class:org.example.impl.NavTest|]/|[method:methodNavigation()|]' parentNodeId='0' locationHint='java:test://org.example.impl.NavTest/methodNavigation' metainfo='']""",
|
||||
messages);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -91,7 +91,7 @@ public class JUnit6SuiteApiIntegrationTest extends AbstractTestFrameworkCompilin
|
||||
|
||||
private static Map<String, TestStarted> getStartedTests(List<ServiceMessage> messages) {
|
||||
return messages.stream().filter(TestStarted.class::isInstance).map(TestStarted.class::cast)
|
||||
.collect(Collectors.toMap(t -> t.getAttributes().get("id"), t -> t));
|
||||
.collect(Collectors.toMap(t -> t.getAttributes().get("id"), t -> t, (existing, replacement) -> existing));
|
||||
}
|
||||
|
||||
private static <T extends BaseTestMessage> Set<String> getTestIds(Map<String, TestStarted> tests, Set<String> locationHints) {
|
||||
|
||||
+2
-4
@@ -1,10 +1,8 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.example.api;
|
||||
|
||||
import org.example.impl.FirstTest;
|
||||
import org.example.impl.SecondTest;
|
||||
import org.junit.platform.suite.api.SelectClasses;
|
||||
import org.junit.platform.suite.api.Suite;
|
||||
import org.example.impl.*;
|
||||
import org.junit.platform.suite.api.*;
|
||||
|
||||
@Suite
|
||||
@SelectClasses({FirstTest.class, SecondTest.class})
|
||||
|
||||
Reference in New Issue
Block a user