From 712e4d471e0a9e5f9502d1baf6ab1760240c0f89 Mon Sep 17 00:00:00 2001 From: Yevhen Piskun Date: Sat, 13 Apr 2024 14:12:06 +0300 Subject: [PATCH] RPP_Agents_Java_Spock_LastErrorLog - Task is to add a new feature to RP agent for Spock. Feature - add last error log to step description. --- .../spock/ReportPortalSpockListener.java | 17 ++++ ...iedErrorMessageWithoutDescriptionTest.java | 80 +++++++++++++++++++ ...eptionErrorMessageWithDescriptionTest.java | 69 ++++++++++++++++ ...ithoutErrorMessageWithDescriptionTest.java | 70 ++++++++++++++++ .../FailWithExceptionWithoutMessage.groovy | 17 ++++ 5 files changed, 253 insertions(+) create mode 100644 src/test/groovy/com/epam/reportportal/spock/description/ConditionNotSatisfiedErrorMessageWithoutDescriptionTest.java create mode 100644 src/test/groovy/com/epam/reportportal/spock/description/ExceptionErrorMessageWithDescriptionTest.java create mode 100644 src/test/groovy/com/epam/reportportal/spock/description/ExceptionWithoutErrorMessageWithDescriptionTest.java create mode 100644 src/test/groovy/com/epam/reportportal/spock/features/fail/FailWithExceptionWithoutMessage.groovy diff --git a/src/main/java/com/epam/reportportal/spock/ReportPortalSpockListener.java b/src/main/java/com/epam/reportportal/spock/ReportPortalSpockListener.java index 5e707e9..c3979c1 100644 --- a/src/main/java/com/epam/reportportal/spock/ReportPortalSpockListener.java +++ b/src/main/java/com/epam/reportportal/spock/ReportPortalSpockListener.java @@ -31,6 +31,7 @@ import com.epam.ta.reportportal.ws.model.attribute.ItemAttributesRQ; import com.epam.ta.reportportal.ws.model.launch.StartLaunchRQ; import io.reactivex.Maybe; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.codehaus.groovy.runtime.StackTraceUtils; import org.slf4j.Logger; @@ -44,6 +45,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -74,6 +76,7 @@ public class ReportPortalSpockListener extends AbstractRunListener { .orElseThrow(() -> new IllegalStateException("Unknown Spock version."))); private final MemoizingSupplier launch; + private final Map, Pair> errorDescriptionMap = new ConcurrentHashMap<>(); // stores the bindings of Spock method kinds to the RP-specific notation private static final Map ITEM_TYPES_REGISTRY = Collections.unmodifiableMap(new HashMap() {{ @@ -274,6 +277,7 @@ protected Maybe startIteration(@Nonnull Maybe parentId, @Nonnull protected void reportIterationStart(@Nonnull Maybe parentId, @Nonnull StartTestItemRQ rq, @Nonnull IterationInfo iteration) { Maybe testItemId = startIteration(parentId, rq); launchContext.addRunningIteration(testItemId, iteration); + errorDescriptionMap.put(launchContext.findIterationFootprint(iteration).getId(), Pair.of(rq.getDescription(), StringUtils.EMPTY)); } public void registerIteration(@Nonnull IterationInfo iteration) { @@ -303,6 +307,15 @@ protected FinishTestItemRQ buildFinishTestItemRq(@Nonnull Maybe itemId, FinishTestItemRQ rq = new FinishTestItemRQ(); ofNullable(status).ifPresent(s -> rq.setStatus(s.name())); rq.setEndTime(Calendar.getInstance().getTime()); + if (Objects.equals(status, ItemStatus.FAILED) && errorDescriptionMap.containsKey(itemId)) { + if (Objects.nonNull(errorDescriptionMap.get(itemId).getLeft())) { + rq.setDescription( + String.format("%s\nError:\n%s", errorDescriptionMap.get(itemId).getLeft(), errorDescriptionMap.get(itemId).getRight())); + } else { + rq.setDescription(String.format("Error:\n%s", errorDescriptionMap.get(itemId).getRight())); + } + } + errorDescriptionMap.remove(itemId); return rq; } @@ -439,6 +452,10 @@ public void reportError(@Nonnull ErrorInfo error) { ofNullable(launchContext.findFeatureFootprint(method.getFeature())).ifPresent(f -> f.setStatus(FAILED)); ofNullable(launchContext.getRuntimePointerForSpec(method.getParent()) .getCurrentIteration()).map(launchContext::findIterationFootprint).ifPresent(i -> i.setStatus(FAILED)); + Maybe itemId = launchContext.findIterationFootprint(error.getMethod().getIteration()).getId(); + String startDescriptions = errorDescriptionMap.get(itemId).getLeft(); + Pair startFinishDescriptions = Pair.of(startDescriptions, error.getException().toString()); + errorDescriptionMap.put(itemId, startFinishDescriptions); logError(error); } else if (ITERATION_EXECUTION == kind) { ofNullable(launchContext.findIterationFootprint(method.getIteration())).ifPresent(i -> i.setStatus(FAILED)); diff --git a/src/test/groovy/com/epam/reportportal/spock/description/ConditionNotSatisfiedErrorMessageWithoutDescriptionTest.java b/src/test/groovy/com/epam/reportportal/spock/description/ConditionNotSatisfiedErrorMessageWithoutDescriptionTest.java new file mode 100644 index 0000000..392e37a --- /dev/null +++ b/src/test/groovy/com/epam/reportportal/spock/description/ConditionNotSatisfiedErrorMessageWithoutDescriptionTest.java @@ -0,0 +1,80 @@ +package com.epam.reportportal.spock.description; + +import com.epam.reportportal.listeners.ItemStatus; +import com.epam.reportportal.service.ReportPortal; +import com.epam.reportportal.service.ReportPortalClient; +import com.epam.reportportal.spock.ReportPortalSpockListener; +import com.epam.reportportal.spock.features.fail.HelloSpockSpecFailed; +import com.epam.reportportal.spock.utils.TestExtension; +import com.epam.reportportal.spock.utils.TestUtils; +import com.epam.reportportal.util.test.CommonUtils; +import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.epam.reportportal.spock.utils.TestUtils.runClasses; +import static com.epam.reportportal.spock.utils.TestUtils.standardParameters; +import static com.epam.reportportal.spock.utils.TestUtils.testExecutor; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class ConditionNotSatisfiedErrorMessageWithoutDescriptionTest { + + private final String classId = CommonUtils.namedId("class_"); + private final String methodId = CommonUtils.namedId("method_"); + private final List nestedSteps = Stream.generate(() -> CommonUtils.namedId("method_")).limit(3).collect(Collectors.toList()); + private final List> nestedStepsLink = nestedSteps.stream().map(s -> Pair.of(methodId, s)).collect(Collectors.toList()); + + private final ReportPortalClient client = mock(ReportPortalClient.class); + + private static final String ERROR_DESCRIPTION = "Error:\nCondition not satisfied:\n\nname.size() == length\n| | | |\n| 6 | 7\nScotty false\n"; + + + @BeforeEach + public void setupMock() { + TestUtils.mockLaunch(client, null, classId, methodId); + TestUtils.mockNestedSteps(client, nestedStepsLink); + TestUtils.mockBatchLogging(client); + TestExtension.listener = new ReportPortalSpockListener(ReportPortal.create(client, standardParameters(), testExecutor())); + } + + @Test + public void verify_error_condition_not_satisfied_message_without_description() { + + TestExecutionSummary result = runClasses(HelloSpockSpecFailed.class); + + assertThat(result.getTotalFailureCount(), equalTo(1L)); + + ArgumentCaptor finishNestedStepCaptor = ArgumentCaptor.forClass(FinishTestItemRQ.class); + nestedSteps.forEach(s -> verify(client).finishTestItem(eq(s), finishNestedStepCaptor.capture())); + + List passedFinishItemStatuses = finishNestedStepCaptor.getAllValues().stream() + .filter(finishTestItemRQ -> finishTestItemRQ.getStatus().equals(ItemStatus.PASSED.name())) + .collect(Collectors.toList()); + + assertThat(passedFinishItemStatuses, hasSize(2)); + + passedFinishItemStatuses.forEach(finishTestItemRQ -> assertThat(finishTestItemRQ.getDescription(), is(nullValue()))); + + List failedFinishItemStatuses = finishNestedStepCaptor.getAllValues().stream() + .filter(finishTestItemRQ -> finishTestItemRQ.getStatus().equals(ItemStatus.FAILED.name())) + .collect(Collectors.toList()); + + assertThat(failedFinishItemStatuses, hasSize(1)); + + assertThat(failedFinishItemStatuses.iterator().next().getDescription(), equalTo(ERROR_DESCRIPTION)); + } +} \ No newline at end of file diff --git a/src/test/groovy/com/epam/reportportal/spock/description/ExceptionErrorMessageWithDescriptionTest.java b/src/test/groovy/com/epam/reportportal/spock/description/ExceptionErrorMessageWithDescriptionTest.java new file mode 100644 index 0000000..a3e9003 --- /dev/null +++ b/src/test/groovy/com/epam/reportportal/spock/description/ExceptionErrorMessageWithDescriptionTest.java @@ -0,0 +1,69 @@ +package com.epam.reportportal.spock.description; + +import com.epam.reportportal.listeners.ItemStatus; +import com.epam.reportportal.service.ReportPortal; +import com.epam.reportportal.service.ReportPortalClient; +import com.epam.reportportal.spock.ReportPortalSpockListener; +import com.epam.reportportal.spock.features.fail.FailsInDifferentMethod; +import com.epam.reportportal.spock.utils.TestExtension; +import com.epam.reportportal.spock.utils.TestUtils; +import com.epam.reportportal.util.test.CommonUtils; +import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.epam.reportportal.spock.utils.TestUtils.runClasses; +import static com.epam.reportportal.spock.utils.TestUtils.standardParameters; +import static com.epam.reportportal.spock.utils.TestUtils.testExecutor; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class ExceptionErrorMessageWithDescriptionTest { + + private final String launchId = CommonUtils.namedId("launch_"); + private final String classId = CommonUtils.namedId("class_"); + private final List methodIds = Stream.generate(() -> CommonUtils.namedId("method_")).limit(1).collect(Collectors.toList()); + + private final ReportPortalClient client = mock(ReportPortalClient.class); + + private static final String ERROR_DESCRIPTION_EXCEPTION = "Setup: \nError:\njava.lang.IllegalStateException: Some test flow failure"; + + @BeforeEach + public void setupMock() { + TestUtils.mockLaunch(client, launchId, classId, methodIds); + TestUtils.mockBatchLogging(client); + TestExtension.listener = new ReportPortalSpockListener(ReportPortal.create(client, standardParameters(), testExecutor())); + } + + @Test + public void verify_error_exception_message_with_description() { + + TestUtils.mockLaunch(client, launchId, classId, methodIds); + TestExtension.listener = new ReportPortalSpockListener(ReportPortal.create(client, standardParameters(), testExecutor())); + + TestExecutionSummary result = runClasses(FailsInDifferentMethod.class); + + assertThat(result.getTotalFailureCount(), equalTo(1L)); + + ArgumentCaptor finishStepCaptor = ArgumentCaptor.forClass(FinishTestItemRQ.class); + + methodIds.forEach(s -> verify(client).finishTestItem(eq(s), finishStepCaptor.capture())); + + List failedFinishItemStatuses = finishStepCaptor.getAllValues().stream() + .filter(fis -> fis.getStatus().equals(ItemStatus.FAILED.name())) + .collect(Collectors.toList()); + + assertThat(failedFinishItemStatuses, hasSize(1)); + assertThat(failedFinishItemStatuses.iterator().next().getDescription(), equalTo(ERROR_DESCRIPTION_EXCEPTION)); + } +} \ No newline at end of file diff --git a/src/test/groovy/com/epam/reportportal/spock/description/ExceptionWithoutErrorMessageWithDescriptionTest.java b/src/test/groovy/com/epam/reportportal/spock/description/ExceptionWithoutErrorMessageWithDescriptionTest.java new file mode 100644 index 0000000..445a7d2 --- /dev/null +++ b/src/test/groovy/com/epam/reportportal/spock/description/ExceptionWithoutErrorMessageWithDescriptionTest.java @@ -0,0 +1,70 @@ +package com.epam.reportportal.spock.description; + +import com.epam.reportportal.listeners.ItemStatus; +import com.epam.reportportal.service.ReportPortal; +import com.epam.reportportal.service.ReportPortalClient; +import com.epam.reportportal.spock.ReportPortalSpockListener; +import com.epam.reportportal.spock.features.fail.FailWithExceptionWithoutMessage; +import com.epam.reportportal.spock.utils.TestExtension; +import com.epam.reportportal.spock.utils.TestUtils; +import com.epam.reportportal.util.test.CommonUtils; +import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.epam.reportportal.spock.utils.TestUtils.runClasses; +import static com.epam.reportportal.spock.utils.TestUtils.standardParameters; +import static com.epam.reportportal.spock.utils.TestUtils.testExecutor; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class ExceptionWithoutErrorMessageWithDescriptionTest { + + private final String launchId = CommonUtils.namedId("launch_"); + private final String classId = CommonUtils.namedId("class_"); + private final List methodIds = Stream.generate(() -> CommonUtils.namedId("method_")).limit(1).collect(Collectors.toList()); + + private final ReportPortalClient client = mock(ReportPortalClient.class); + + private static final String ERROR_DESCRIPTION_EXCEPTION = "Setup: \nWhen: \nThen: \nError:\njava.util.NoSuchElementException"; + + @BeforeEach + public void setupMock() { + TestUtils.mockLaunch(client, launchId, classId, methodIds); + TestUtils.mockBatchLogging(client); + TestExtension.listener = new ReportPortalSpockListener(ReportPortal.create(client, standardParameters(), testExecutor())); + } + + @Test + public void verify_exception_without_error_message_with_description() { + + TestUtils.mockLaunch(client, launchId, classId, methodIds); + TestExtension.listener = new ReportPortalSpockListener(ReportPortal.create(client, standardParameters(), testExecutor())); + + TestExecutionSummary result = runClasses(FailWithExceptionWithoutMessage.class); + + assertThat(result.getTotalFailureCount(), equalTo(1L)); + + ArgumentCaptor finishStepCaptor = ArgumentCaptor.forClass(FinishTestItemRQ.class); + + methodIds.forEach(s -> verify(client).finishTestItem(eq(s), finishStepCaptor.capture())); + + List failedFinishItemStatuses = finishStepCaptor.getAllValues() + .stream() + .filter(fis -> fis.getStatus().equals(ItemStatus.FAILED.name())) + .collect(Collectors.toList()); + + assertThat(failedFinishItemStatuses, hasSize(1)); + assertThat(failedFinishItemStatuses.iterator().next().getDescription(), equalTo(ERROR_DESCRIPTION_EXCEPTION)); + } +} \ No newline at end of file diff --git a/src/test/groovy/com/epam/reportportal/spock/features/fail/FailWithExceptionWithoutMessage.groovy b/src/test/groovy/com/epam/reportportal/spock/features/fail/FailWithExceptionWithoutMessage.groovy new file mode 100644 index 0000000..ab87c9c --- /dev/null +++ b/src/test/groovy/com/epam/reportportal/spock/features/fail/FailWithExceptionWithoutMessage.groovy @@ -0,0 +1,17 @@ +package com.epam.reportportal.spock.features.fail + +import spock.lang.Specification + +class FailWithExceptionWithoutMessage extends Specification { + + def "should demonstrate NoSuchElementException given-when-then fail"() { + given: + def polygon = new ArrayList() + + when: + polygon.size() + + then: + throw new NoSuchElementException() + } +} \ No newline at end of file