From 1b42c4c495b219385283102a6b55c404ea4b38ec Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 15 Mar 2021 17:59:44 +0100 Subject: [PATCH] [Core] Mark pending steps as failed in teamcity plugin (#2264) * [Core] Mark pending steps as failed in teamcity plugin The teamcity plugin communicates test results to IntelliJ IDEA using teamcity service messages[1]. Currently the teamcity plugin marks pending steps as skipped rather then failed. This is inconsistent with the changes from #1960 in which pending steps are considered a test failure. 1: https://www.jetbrains.com/help/teamcity/service-messages.html * [Core] Fix teamcity link for cucumber-java8 hooks The teamcity plugin communicates test results to IntelliJ IDEA using teamcity service messages[1]. These messages can link to files using a url-like protocol. The code use to convert code locations from cucumber-java8 step definitions did not correctly extract the method from this string representation. --- CHANGELOG.md | 1 + .../cucumber/core/plugin/TeamCityPlugin.java | 40 +++++++++------ .../core/plugin/TeamCityPluginTest.java | 50 ++++++++++++++++++- 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 895b5d204b..94ecdacdec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Removed ### Fixed + * [Core] Mark pending steps as failed in teamcity plugin ([#2264](https://github.com/cucumber/cucumber-jvm/pull/2264)) M.P. Korstanje) ## [6.10.1] (2021-03-08) diff --git a/core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java b/core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java index 1af941d9a7..f9929323ce 100644 --- a/core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java +++ b/core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java @@ -48,6 +48,13 @@ import static java.util.Collections.emptyList; import static java.util.stream.Collectors.joining; +/** + * Outputs Teamcity services messages to std out. + * + * @see TeamCity + * - Service Messages + */ public class TeamCityPlugin implements EventListener { private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'hh:mm:ss.SSSZ"); @@ -230,7 +237,7 @@ private String extractSourceLocation(TestStep testStep) { if (java8Matcher.matches()) { String fqDeclaringClassName = java8Matcher.group(1); String declaringClassName; - int indexOfPackageSeparator = fqDeclaringClassName.indexOf("."); + int indexOfPackageSeparator = fqDeclaringClassName.lastIndexOf("."); if (indexOfPackageSeparator != -1) { declaringClassName = fqDeclaringClassName.substring(indexOfPackageSeparator + 1); } else { @@ -250,22 +257,27 @@ private void printTestStepFinished(TestStepFinished event) { Throwable error = event.getResult().getError(); Status status = event.getResult().getStatus(); switch (status) { - case SKIPPED: - print(TEMPLATE_TEST_IGNORED, timeStamp, duration, error == null ? "Step skipped" : error.getMessage(), - name); + case SKIPPED: { + String message = error == null ? "Step skipped" : error.getMessage(); + print(TEMPLATE_TEST_IGNORED, timeStamp, duration, message, name); break; - case PENDING: - print(TEMPLATE_TEST_IGNORED, timeStamp, duration, error == null ? "Step pending" : error.getMessage(), - name); + } + case PENDING: { + String details = error == null ? "" : error.getMessage(); + print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step pending", details, name); break; - case UNDEFINED: - print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step undefined", getSnippets(currentTestCase), name); + } + case UNDEFINED: { + String snippets = getSnippets(currentTestCase); + print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step undefined", snippets, name); break; + } case AMBIGUOUS: - case FAILED: + case FAILED: { String details = extractStackTrace(error); print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step failed", details, name); break; + } default: break; } @@ -307,8 +319,8 @@ private String getSnippets(TestCase testCase) { URI uri = testCase.getUri(); Location location = testCase.getLocation(); List suggestionForTestCase = suggestions.stream() - .filter(suggestions -> suggestions.getUri().equals(uri) && - suggestions.getTestCaseLocation().equals(location)) + .filter(suggestion -> suggestion.getUri().equals(uri) && + suggestion.getTestCaseLocation().equals(location)) .map(SnippetsSuggestedEvent::getSuggestion) .collect(Collectors.toList()); return createMessage(suggestionForTestCase); @@ -322,7 +334,7 @@ private static String createMessage(Collection suggestions) { if (suggestions.size() > 1) { sb.append(" and ").append(suggestions.size() - 1).append(" other step(s)"); } - sb.append("using the snippet(s) below:\n\n"); + sb.append(" using the snippet(s) below:\n\n"); String snippets = suggestions .stream() .map(Suggestion::getSnippets) @@ -379,7 +391,7 @@ private String formatCommand(String command, Object... parameters) { escapedParameters[i] = escape(parameters[i].toString()); } - return String.format(command, escapedParameters); + return String.format(command, (Object[]) escapedParameters); } private String escape(String source) { diff --git a/core/src/test/java/io/cucumber/core/plugin/TeamCityPluginTest.java b/core/src/test/java/io/cucumber/core/plugin/TeamCityPluginTest.java index 04c1c47bc2..159f866b77 100755 --- a/core/src/test/java/io/cucumber/core/plugin/TeamCityPluginTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/TeamCityPluginTest.java @@ -1,6 +1,7 @@ package io.cucumber.core.plugin; import io.cucumber.core.backend.StubHookDefinition; +import io.cucumber.core.backend.StubPendingException; import io.cucumber.core.backend.StubStepDefinition; import io.cucumber.core.backend.TestCaseState; import io.cucumber.core.feature.TestFeatureParser; @@ -212,7 +213,29 @@ void should_print_error_message_for_undefined_steps() { .run(); assertThat(out, bytesContainsString("" + - "##teamcity[testFailed timestamp = '1970-01-01T12:00:00.000+0000' duration = '0' message = 'Step undefined' details = 'You can implement this step and 1 other step(s)using the snippet(s) below:|n|ntest snippet 0|ntest snippet 1|n' name = 'first step']")); + "##teamcity[testFailed timestamp = '1970-01-01T12:00:00.000+0000' duration = '0' message = 'Step undefined' details = 'You can implement this step and 1 other step(s) using the snippet(s) below:|n|ntest snippet 0|ntest snippet 1|n' name = 'first step']")); + } + + @Test + void should_print_error_message_for_pending_steps() { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Given first step\n" + + " Given second 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(new StubStepDefinition("first step", new StubPendingException()))) + .build() + .run(); + + assertThat(out, bytesContainsString("" + + "##teamcity[testFailed timestamp = '1970-01-01T12:00:00.000+0000' duration = '0' message = 'Step pending' details = 'TODO: implement me' name = 'first step']")); } @Test @@ -241,7 +264,7 @@ void should_print_error_message_for_before_hooks() { } @Test - void should_print_location_hint_for_hooks() { + void should_print_location_hint_for_java_hooks() { Feature feature = TestFeatureParser.parse("path/test.feature", "" + "Feature: feature name\n" + " Scenario: scenario name\n" + @@ -263,4 +286,27 @@ void should_print_location_hint_for_hooks() { "##teamcity[testStarted timestamp = '1970-01-01T12:00:00.000+0000' locationHint = 'java:test://com.example.HookDefinition/beforeHook' captureStandardOutput = 'true' name = 'Before']\n")); } + @Test + void should_print_location_hint_for_lambda_hooks() { + 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( + singletonList(new StubHookDefinition("com.example.HookDefinition.(HookDefinition.java:12)")), + singletonList(new StubStepDefinition("first step")), + emptyList())) + .build() + .run(); + + assertThat(out, bytesContainsString("" + + "##teamcity[testStarted timestamp = '1970-01-01T12:00:00.000+0000' locationHint = 'java:test://com.example.HookDefinition/HookDefinition' captureStandardOutput = 'true' name = 'Before']\n")); + } + }