Skip to content

Commit

Permalink
Support diff toolwindow in IntelliJ IDEA
Browse files Browse the repository at this point in the history
For the toolwindow are required the "expected" and "actual" attributes in the test-failed-output message.

This fixes issue: cucumber#2607
  • Loading branch information
avokin committed Sep 9, 2022
1 parent 2fa32d2 commit f374d01
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Changed
- [JUnit] Support comparison of expected and actual values in IntelliJ IDEA ([#2607](https://github.com/cucumber/cucumber-jvm/issues/2607)

## [7.6.0] - 2022-08-08
### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ public class TeamCityPlugin implements EventListener {
+ "[testFinished timestamp = '%s' duration = '%s' name = '%s']";
private static final String TEMPLATE_TEST_FAILED = TEAMCITY_PREFIX
+ "[testFailed timestamp = '%s' duration = '%s' message = '%s' details = '%s' name = '%s']";

private static final String TEMPLATE_TEST_COMPARISON_FAILED = TEAMCITY_PREFIX
+ "[testFailed timestamp = '%s' duration = '%s' message = '%s' details = '%s' expected = '%s' actual = '%s' name = '%s']";
private static final String TEMPLATE_TEST_IGNORED = TEAMCITY_PREFIX
+ "[testIgnored timestamp = '%s' duration = '%s' message = '%s' name = '%s']";

Expand All @@ -101,6 +104,11 @@ public class TeamCityPlugin implements EventListener {
private static final Pattern ANNOTATION_GLUE_CODE_LOCATION_PATTERN = Pattern.compile("^(.*)\\.(.*)\\([^:]*\\)");
private static final Pattern LAMBDA_GLUE_CODE_LOCATION_PATTERN = Pattern.compile("^(.*)\\.(.*)\\(.*:.*\\)");

private static final Pattern[] COMPARE_PATTERNS = new Pattern[] {
Pattern.compile("^expected:\\s*(.*)\\s+but was:\\s*(.*)",
Pattern.DOTALL | Pattern.CASE_INSENSITIVE),
};

private final PrintStream out;
private final List<SnippetsSuggestedEvent> suggestions = new ArrayList<>();
private final Map<URI, Collection<Node>> parsedTestSources = new HashMap<>();
Expand Down Expand Up @@ -281,7 +289,16 @@ private void printTestStepFinished(TestStepFinished event) {
case AMBIGUOUS:
case FAILED: {
String details = printStackTrace(error);
print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step failed", details, name);
String message = error.getMessage();
ComparisonFailureData comparisonFailureData = message != null
? createExceptionNotification(message.trim())
: null;
if (comparisonFailureData != null) {
print(TEMPLATE_TEST_COMPARISON_FAILED, timeStamp, duration, "Step failed", details,
comparisonFailureData.getExpected(), comparisonFailureData.getActual(), name);
} else {
print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step failed", details, name);
}
break;
}
default:
Expand All @@ -290,6 +307,26 @@ private void printTestStepFinished(TestStepFinished event) {
print(TEMPLATE_TEST_FINISHED, timeStamp, duration, name);
}

private static ComparisonFailureData createExceptionNotification(String message, Pattern pattern) {
final Matcher matcher = pattern.matcher(message);
if (matcher.find()) {
String expected = matcher.group(1);
String actual = matcher.group(2);
return new ComparisonFailureData(expected, actual);
}
return null;
}

private static ComparisonFailureData createExceptionNotification(String message) {
for (Pattern pattern : COMPARE_PATTERNS) {
ComparisonFailureData result = createExceptionNotification(message, pattern);
if (result != null) {
return result;
}
}
return null;
}

private String extractName(TestStep step) {
if (step instanceof PickleStepTestStep) {
PickleStepTestStep pickleStepTestStep = (PickleStepTestStep) step;
Expand Down Expand Up @@ -420,4 +457,39 @@ private String escape(String source) {
.replace("]", "|]");
}

private static class ComparisonFailureData {
private final String expected;

private final String actual;

ComparisonFailureData(String expected, String actual) {
if (isWrappedWith(expected, '<', '>') && isWrappedWith(actual, '<', '>')) {
expected = unwrap(expected);
actual = unwrap(actual);
}
if (isWrappedWith(expected, '[', ']') && isWrappedWith(actual, '[', ']')) {
expected = unwrap(expected);
actual = unwrap(actual);
}
this.expected = expected;
this.actual = actual;
}

private static boolean isWrappedWith(String text, char startChar, char endChar) {
return !text.isEmpty() &&
text.charAt(0) == startChar && text.charAt(text.length() - 1) == endChar;
}

private static String unwrap(String text) {
return text.substring(1, text.length() - 1);
}

public String getExpected() {
return expected;
}

public String getActual() {
return actual;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,49 @@ void should_print_system_failure_for_failed_hooks() {
"##teamcity[testFinished timestamp = '1970-01-01T12:00:00.000+0000' name = 'Before All/After All']"));
}

@Test
void should_print_comparison_failure_for_failed_assert_equal() {
Feature feature = TestFeatureParser.parse("path/test.feature", "" +
"Feature: feature name\n" +
" Scenario: scenario name\n" +
" Given first step\n");

ByteArrayOutputStream out = new ByteArrayOutputStream();
Runtime.builder()
.withFeatureSupplier(new StubFeatureSupplier(feature))
.withAdditionalPlugins(new TeamCityPlugin(new PrintStream(out)))
.withEventBus(new TimeServiceEventBus(fixed(EPOCH, of("UTC")), UUID::randomUUID))
.withBackendSupplier(new StubBackendSupplier(
emptyList(),
singletonList(
new StubStepDefinition("first step", new RuntimeException("expected: <1> but was: <2>"))),
emptyList()))
.build()
.run();

assertThat(out, bytesContainsString("expected = '1' actual = '2' name = 'first step']"));
}

@Test
void should_print_comparison_failure_for_failed_assert_equal_for_strings() {
Feature feature = TestFeatureParser.parse("path/test.feature", "" +
"Feature: feature name\n" +
" Scenario: scenario name\n" +
" Given first step\n");

ByteArrayOutputStream out = new ByteArrayOutputStream();
Runtime.builder()
.withFeatureSupplier(new StubFeatureSupplier(feature))
.withAdditionalPlugins(new TeamCityPlugin(new PrintStream(out)))
.withEventBus(new TimeServiceEventBus(fixed(EPOCH, of("UTC")), UUID::randomUUID))
.withBackendSupplier(new StubBackendSupplier(
emptyList(),
singletonList(new StubStepDefinition("first step",
new RuntimeException("expected: <[one value]> but was: <[another value]>"))),
emptyList()))
.build()
.run();

assertThat(out, bytesContainsString("expected = 'one value' actual = 'another value' name = 'first step']"));
}
}

0 comments on commit f374d01

Please sign in to comment.