diff --git a/src/main/java/com/github/invictum/reportportal/Utils.java b/src/main/java/com/github/invictum/reportportal/Utils.java index 3f47a61..054b12f 100644 --- a/src/main/java/com/github/invictum/reportportal/Utils.java +++ b/src/main/java/com/github/invictum/reportportal/Utils.java @@ -1,5 +1,6 @@ package com.github.invictum.reportportal; +import net.thucydides.core.model.TestOutcome; import net.thucydides.core.model.TestResult; import net.thucydides.core.model.TestStep; @@ -30,6 +31,17 @@ public static Date stepEndDate(TestStep step) { return Date.from(endTimeZoned.toInstant()); } + /** + * Calculates test's end time. + * + * @param test to calculate time on + * @return test end time in {@link Date} format + */ + public static Date testEndDate(TestOutcome test) { + ZonedDateTime endTimeZoned = test.getStartTime().plus(Duration.ofMillis(test.getDuration())); + return Date.from(endTimeZoned.toInstant()); + } + /** * Calculates step's start time * diff --git a/src/main/java/com/github/invictum/reportportal/log/unit/Error.java b/src/main/java/com/github/invictum/reportportal/log/unit/Error.java index 7fde4e7..eb8d43c 100644 --- a/src/main/java/com/github/invictum/reportportal/log/unit/Error.java +++ b/src/main/java/com/github/invictum/reportportal/log/unit/Error.java @@ -2,6 +2,8 @@ import com.epam.ta.reportportal.ws.model.log.SaveLogRQ; import com.github.invictum.reportportal.Utils; + +import net.thucydides.core.model.TestOutcome; import net.thucydides.core.model.TestStep; import java.io.PrintWriter; @@ -12,17 +14,26 @@ public class Error { - private static final Function DEFAULT_FORMATTER = step -> { + private static final Function STEP_FORMATTER = step -> { Throwable cause = step.getException().getOriginalCause(); + return stringifyStackTrace(cause); + }; + + private static final Function TEST_FORMATTER = testOutcome -> { + Throwable cause = testOutcome.getTestFailureCause().toException(); + return stringifyStackTrace(cause); + }; + + private static String stringifyStackTrace(Throwable cause){ StringWriter writer = new StringWriter(); cause.printStackTrace(new PrintWriter(writer)); return writer.toString(); - }; + } /** * Logs error using preconfigured {@link Function} representation of formatter */ - public static Function> configuredError(Function errorFormatter) { + public static Function> configuredStepError(Function errorFormatter) { return step -> { if (step.getException() != null) { SaveLogRQ log = new SaveLogRQ(); @@ -36,9 +47,32 @@ public static Function> configuredError(Function } /** - * Logs error using default error formatter - print error with full stack trace + * Logs error using preconfigured {@link Function} representation of formatter + */ + public static Function> configuredTestError(Function errorFormatter) { + return testOutcome -> { + if (!testOutcome.getFailingStep().isPresent() && testOutcome.getTestFailureCause() != null) { + SaveLogRQ log = new SaveLogRQ(); + log.setMessage(errorFormatter.apply(testOutcome)); + log.setLevel(Utils.logLevel(testOutcome.getResult())); + log.setLogTime(Utils.testEndDate(testOutcome)); + return Collections.singleton(log); + } + return Collections.emptySet(); + }; + } + + /** + * Logs error in step using default error formatter - print error with full stack trace */ public static Function> basic() { - return configuredError(DEFAULT_FORMATTER); + return configuredStepError(STEP_FORMATTER); + } + + /** + * Logs error in test (outside of a step) using default error formatter - print error with full stack trace + */ + public static Function> errorInTest() { + return configuredTestError(TEST_FORMATTER); } } diff --git a/src/main/java/com/github/invictum/reportportal/recorder/Regular.java b/src/main/java/com/github/invictum/reportportal/recorder/Regular.java index 7f921fe..04df2f6 100644 --- a/src/main/java/com/github/invictum/reportportal/recorder/Regular.java +++ b/src/main/java/com/github/invictum/reportportal/recorder/Regular.java @@ -1,12 +1,23 @@ package com.github.invictum.reportportal.recorder; +import java.util.Collection; + +import net.thucydides.core.model.TestOutcome; + import com.epam.reportportal.service.Launch; +import com.epam.reportportal.service.ReportPortal; import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; import com.epam.ta.reportportal.ws.model.StartTestItemRQ; -import com.github.invictum.reportportal.*; +import com.epam.ta.reportportal.ws.model.log.SaveLogRQ; +import com.github.invictum.reportportal.FinishEventBuilder; +import com.github.invictum.reportportal.ItemType; +import com.github.invictum.reportportal.LogUnitsHolder; +import com.github.invictum.reportportal.StartEventBuilder; +import com.github.invictum.reportportal.Status; +import com.github.invictum.reportportal.SuiteStorage; +import com.github.invictum.reportportal.log.unit.Error; import com.google.inject.Inject; import io.reactivex.Maybe; -import net.thucydides.core.model.TestOutcome; /** * Common test recorder suitable for most cases @@ -40,6 +51,8 @@ public void record(TestOutcome out) { Maybe testId = launch.startTestItem(id, builder.build()); // Steps out.getFlattenedTestSteps().forEach(holder::proceed); + // failed assertions in test itself + recordNonStepFailure(out); FinishTestItemRQ finishTest = new FinishEventBuilder() .withStatus(Status.mapTo(out.getResult())) .withEndTime(out.getStartTime(), out.getDuration()) @@ -51,4 +64,12 @@ public void record(TestOutcome out) { .build(); suiteStorage.suiteFinisher(out.getUserStory().getId(), () -> launch.finishTestItem(id, finishSuite)); } + + private void recordNonStepFailure(TestOutcome out){ + Collection logs = Error.errorInTest().apply(out); + logs.forEach(l -> ReportPortal.emitLog(id -> { + l.setTestItemId(id); + return l; + })); + } } diff --git a/src/test/java/com/github/invictum/reportportal/log/unit/ErrorTest.java b/src/test/java/com/github/invictum/reportportal/log/unit/ErrorTest.java index 12dfecd..d570ad5 100644 --- a/src/test/java/com/github/invictum/reportportal/log/unit/ErrorTest.java +++ b/src/test/java/com/github/invictum/reportportal/log/unit/ErrorTest.java @@ -1,10 +1,15 @@ package com.github.invictum.reportportal.log.unit; -import com.epam.ta.reportportal.ws.model.log.SaveLogRQ; -import com.github.invictum.reportportal.LogLevel; +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.Iterator; +import java.util.Optional; + +import net.thucydides.core.model.TestOutcome; import net.thucydides.core.model.TestResult; import net.thucydides.core.model.TestStep; import net.thucydides.core.model.stacktrace.FailureCause; + import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -12,8 +17,8 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import java.time.ZonedDateTime; -import java.util.Collection; +import com.epam.ta.reportportal.ws.model.log.SaveLogRQ; +import com.github.invictum.reportportal.LogLevel; @RunWith(MockitoJUnitRunner.StrictStubs.class) public class ErrorTest { @@ -21,6 +26,9 @@ public class ErrorTest { @Mock private TestStep stepMock; + @Mock(lenient = true) + private TestOutcome testOutcomeMock; + @Mock private FailureCause failureCauseMock; @@ -36,12 +44,29 @@ public void customErrorProcessing() { Mockito.when(stepMock.getStartTime()).thenReturn(ZonedDateTime.now()); Mockito.when(stepMock.getException()).thenReturn(failureCauseMock); Mockito.when(stepMock.getConciseErrorMessage()).thenReturn("Custom error"); - SaveLogRQ actual = Error.configuredError(TestStep::getConciseErrorMessage).apply(stepMock).iterator().next(); + SaveLogRQ actual = Error.configuredStepError(TestStep::getConciseErrorMessage).apply(stepMock).iterator().next(); // Verification Assert.assertEquals("Custom error", actual.getMessage()); Assert.assertEquals(LogLevel.ERROR.toString(), actual.getLevel()); } + @Test + public void errorAtTestLevelShouldBeLogged(){ + Mockito.when(testOutcomeMock.getStartTime()).thenReturn(ZonedDateTime.now()); + Mockito.when(testOutcomeMock.getTestFailureCause()).thenReturn(new FailureCause(new RuntimeException())); + Iterator iterator = Error.configuredTestError(TestOutcome::getConciseErrorMessage).apply(testOutcomeMock).iterator(); + Assert.assertTrue(iterator.hasNext()); + } + + @Test + public void errorAtStepLevelShouldNotBeLoggedAtTestLevel(){ + Mockito.when(testOutcomeMock.getStartTime()).thenReturn(ZonedDateTime.now()); + Mockito.when(testOutcomeMock.getTestFailureCause()).thenReturn(new FailureCause(new RuntimeException())); + Mockito.when(testOutcomeMock.getFailingStep()).thenReturn(Optional.of(new TestStep())); + Iterator iterator = Error.configuredTestError(TestOutcome::getConciseErrorMessage).apply(testOutcomeMock).iterator(); + Assert.assertFalse(iterator.hasNext()); + } + @Test public void defaultError() { // Setup mock