Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Support diff toolwindow in IntelliJ IDEA #2608

Merged
merged 10 commits into from
Sep 15, 2022
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +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]

### Added
- [Core] Support comparison of expected and actual values in IntelliJ IDEA ([#2607](https://github.com/cucumber/cucumber-jvm/issues/2607)
- [Datatable] Support parsing Booleans in Datatables ([#2614](https://github.com/cucumber/cucumber-jvm/pull/2614) G. Jourdan-Weil)

## [7.7.0] - 2022-09-08
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

import static io.cucumber.core.exception.ExceptionUtils.printStackTrace;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;

/**
Expand Down Expand Up @@ -77,6 +78,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 +105,24 @@ 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[] {
// Hamcrest 2 MatcherAssert.assertThat
Pattern.compile("expected: (.*)(?:\r\n|\r|\n) {5}but: was (.*)$",
Pattern.DOTALL | Pattern.CASE_INSENSITIVE),
// AssertJ 3 ShouldBeEqual.smartErrorMessage
Pattern.compile("expected: (.*)(?:\r\n|\r|\n) but was: (.*)$",
Pattern.DOTALL | Pattern.CASE_INSENSITIVE),
// JUnit 5 AssertionFailureBuilder
Pattern.compile("expected: <(.*)> but was: <(.*)>$",
Pattern.DOTALL | Pattern.CASE_INSENSITIVE),
// JUnit 4 Assert.assertEquals
Pattern.compile("expected:\\s?<(.*)> but was:\\s?<(.*)>$",
Pattern.DOTALL | Pattern.CASE_INSENSITIVE),
// TestNG 7 Assert.assertEquals
Pattern.compile("expected \\[(.*)] but found \\[(.*)]\n$",
Pattern.DOTALL | Pattern.CASE_INSENSITIVE),
};
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved

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 +303,18 @@ 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();
if (message == null) {
print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step failed", details, name);
break;
}
ComparisonFailure comparisonFailure = ComparisonFailure.parse(message.trim());
if (comparisonFailure == null) {
print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step failed", details, name);
break;
}
print(TEMPLATE_TEST_COMPARISON_FAILED, timeStamp, duration, "Step failed", details,
comparisonFailure.getExpected(), comparisonFailure.getActual(), name);
break;
}
default:
Expand Down Expand Up @@ -420,4 +453,43 @@ private String escape(String source) {
.replace("]", "|]");
}

private static class ComparisonFailure {

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

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

private final String expected;

private final String actual;

ComparisonFailure(String expected, String actual) {
this.expected = requireNonNull(expected);
this.actual = requireNonNull(actual);
}

public String getExpected() {
return expected;
}

public String getActual() {
return actual;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.cucumber.core.backend;

public class StubLocation implements Located {

private final String location;

public StubLocation(String location) {
this.location = location;
}

@Override
public boolean isDefinedAt(StackTraceElement stackTraceElement) {
return false;
}

@Override
public String getLocation() {
return location;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.cucumber.core.backend;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -12,8 +13,8 @@ public class StubStepDefinition implements StepDefinition {
private static final String STUBBED_LOCATION_WITH_DETAILS = "{stubbed location with details}";
private final List<ParameterInfo> parameterInfos;
private final String expression;
private final RuntimeException exception;
private final String location;
private final Throwable exception;
private final Located location;

public StubStepDefinition(String pattern, String location, Type... types) {
this(pattern, location, null, types);
Expand All @@ -23,14 +24,14 @@ public StubStepDefinition(String pattern, Type... types) {
this(pattern, STUBBED_LOCATION_WITH_DETAILS, null, types);
}

public StubStepDefinition(String pattern, RuntimeException exception, Type... types) {
public StubStepDefinition(String pattern, Throwable exception, Type... types) {
this(pattern, STUBBED_LOCATION_WITH_DETAILS, exception, types);
}

public StubStepDefinition(String pattern, String location, RuntimeException exception, Type... types) {
public StubStepDefinition(String pattern, String location, Throwable exception, Type... types) {
this.parameterInfos = Stream.of(types).map(StubParameterInfo::new).collect(Collectors.toList());
this.expression = pattern;
this.location = location;
this.location = new StubLocation(location);
this.exception = exception;
}

Expand All @@ -41,13 +42,16 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) {

@Override
public String getLocation() {
return location;
return location.getLocation();
}

@Override
public void execute(Object[] args) {
if (exception != null) {
throw exception;
if (exception instanceof CucumberBackendException) {
throw (CucumberBackendException) exception;
}
throw new CucumberInvocationTargetException(location, new InvocationTargetException(exception));
}

assertEquals(parameterInfos.size(), args.length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure;
import static org.junit.jupiter.api.Assertions.assertThrows;

@DisabledOnOs(OS.WINDOWS)
Expand Down Expand Up @@ -342,4 +343,50 @@ 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", assertionFailure().expected(1).actual(2).build())),
emptyList()))
.build()
.run();

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

@Test
void should_print_comparison_failure_for_failed_assert_equal_with_prefix() {
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",
assertionFailure().message("oops").expected("one value").actual("another value").build())),
emptyList()))
.build()
.run();

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