diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index bb09719d..319290ea 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -45,7 +45,10 @@ body: - allure-jsonunit - allure-junit-platform - allure-junit4 + - allure-jupiter + - allure-jupiter-assert - allure-junit5 + - allure-junit5-assert - allure-karate - allure-okhttp - allure-okhttp3 diff --git a/.github/labeler.yml b/.github/labeler.yml index a419c97f..367271a0 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -48,8 +48,8 @@ - "allure-junit4-aspect/**" "theme:junit-platform": - - "allure-junit5/**" - - "allure-junit5-assert/**" + - "allure-jupiter/**" + - "allure-jupiter-assert/**" - "allure-junit-platform/**" "theme:karate": diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f537bc9..341788f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,22 +13,83 @@ on: - 'main' - 'hotfix-*' +concurrency: + # On main, we don't want any jobs cancelled. + # On PR branches, we cancel the job if new commits are pushed. + group: ${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + jobs: build: name: "Build" runs-on: ubuntu-latest + env: + ALLURE_MATRIX_ENV: ubuntu-jdk-21 + ALLURE_TEST_DUMP_NAME: allure-results-test-jdk-21 steps: - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: '20.x' + - name: "Set up JDK" uses: actions/setup-java@v5 with: distribution: 'zulu' java-version: 21 + - name: "Setup Gradle" + uses: gradle/actions/setup-gradle@v6 + with: + gradle-version: 'wrapper' + - name: "Build with Gradle" run: ./gradlew build -x test --scan - - name: "Run tests" + - name: "Run tests with Allure" if: always() - run: ./gradlew --no-build-cache cleanTest test + run: npx -y allure@3 run --config ./allurerc.mjs --rerun 2 --environment="${{ env.ALLURE_MATRIX_ENV }}" --dump="${{ env.ALLURE_TEST_DUMP_NAME }}" -- ./gradlew --no-build-cache cleanTest test + + - name: "Upload Allure test dump" + if: always() + uses: actions/upload-artifact@v7 + with: + name: ${{ env.ALLURE_TEST_DUMP_NAME }} + path: ./${{ env.ALLURE_TEST_DUMP_NAME }}.zip + + report: + needs: [build] + name: "Build report" + runs-on: ubuntu-latest + if: always() + permissions: + contents: read + pull-requests: write + checks: write + env: + ALLURE_SERVICE_TOKEN: ${{ secrets.ALLURE_SERVICE_TOKEN }} + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v6 + with: + node-version: '20.x' + + - name: "Download Allure dumps" + uses: actions/download-artifact@v8 + continue-on-error: true + with: + pattern: allure-results-* + path: ./ + merge-multiple: true + + - name: "Generate Allure report" + run: npx -y allure@3 generate --config ./allurerc.mjs --dump="allure-results-*.zip" --output=./build/allure-report + + - name: "Post Allure summary" + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false + uses: allure-framework/allure-action@v0 + with: + report-directory: ./build/allure-report + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.idea/vcs.xml b/.idea/vcs.xml index aeaa9e45..95443a12 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -10,7 +10,4 @@ - - - - + \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..8b7a6aa4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,8 @@ +# Project Guide + +Use [Allure Agent Mode](docs/allure-agent-mode.md) for all test-related work in this repository. + +- Read `docs/allure-agent-mode.md` before designing, writing, reviewing, validating, debugging, or enriching tests. +- Run test-executing commands through `allure run`, including smoke checks after small edits. +- Use `./gradlew` for repo-local test commands and scope runs to the smallest relevant module or task. +- If agent-mode output is missing or incomplete, debug that first rather than relying on console-only conclusions. diff --git a/README.md b/README.md index e452787b..2c6f44a1 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,12 @@ - 📚 Example project — https://github.com/allure-examples?q=topic%3Ajunit4 - ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/ - -## JUnit 5 - -- 🚀 Documentation — https://allurereport.org/docs/junit5/ -- 📚 Example project — https://github.com/allure-examples?q=topic%3Ajunit5 -- ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/ +## JUnit Jupiter (JUnit 5 and 6) + +- 🚀 Documentation — https://allurereport.org/docs/junit5/ +- 📚 Example project — https://github.com/allure-examples?q=topic%3Ajunit5 +- ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/ +- 🧩 Use `io.qameta.allure:allure-jupiter` for new setups. `allure-junit5` remains available as a deprecated relocation coordinate during migration. ## Cucumber JVM @@ -76,10 +77,72 @@ SelenideLogger.addListener("AllureSelenide", new AllureSelenide().enableLogs(Log https://github.com/SeleniumHQ/selenium/wiki/Logging ``` - -## Rest Assured - -Filter for rest-assured http client, that generates attachment for allure. +## Playwright Java + +AspectJ-based integration for Playwright Java that reports browser actions as Allure steps and attaches +Playwright screenshots automatically: + +```xml + + io.qameta.allure + allure-playwright + $LATEST_VERSION + +``` + +Enable the AspectJ weaver for automatic action steps: +``` +-javaagent:/path/to/aspectjweaver.jar +``` + +Usage example with Playwright Java JUnit fixtures: +```java +@UsePlaywright +class UiTest { + + @Test + void shouldOpenPage(Page page) { + page.navigate("https://playwright.dev"); + page.screenshot(); + } +} +``` + +The module registers an Allure test lifecycle listener automatically, so per-test cleanup, failure diagnostics, +and final trace/log flush work with any test framework that reports through Allure. Playwright pages and +contexts are tracked by the AspectJ integration when they are created or used. Use +`AllurePlaywright.register(...)` only for pages or contexts the aspect cannot observe. + +Frameworks or custom runners that do not use the Allure lifecycle can call the reporting hooks directly: +```java +AllurePlaywright.beforeTest(); +try { + testBody(); +} catch (Throwable e) { + AllurePlaywright.afterTestFailure(e); + throw e; +} finally { + AllurePlaywright.afterTest(); +} +``` + +The following defaults can be overridden in `allure.properties`: +``` +allure.playwright.steps.enabled=true +allure.playwright.steps.mode=actions +allure.playwright.parameters=redacted +allure.playwright.screenshots.attach=true +allure.playwright.failure.screenshot=true +allure.playwright.failure.page-source=true +allure.playwright.close.trace=true +allure.playwright.close.video=true +allure.playwright.close.page-logs=true +``` + + +## Rest Assured + +Filter for rest-assured http client, that generates attachment for allure. ```xml @@ -95,14 +158,50 @@ Usage example: ``` You can specify custom templates, which should be placed in src/main/resources/tpl folder: ``` -.filter(new AllureRestAssured() - .withRequestTemplate("custom-http-request.ftl") - .withResponseTemplate("custom-http-response.ftl")) -``` - -## OkHttp - -Interceptor for OkHttp client, that generates attachment for allure. +.filter(new AllureRestAssured() + .withRequestTemplate("custom-http-request.ftl") + .withResponseTemplate("custom-http-response.ftl")) +``` + +## Spring Web + +Interceptor for Spring synchronous HTTP clients, that generates attachments for allure. + +```xml + + io.qameta.allure + allure-spring-web + $LATEST_VERSION + +``` + +Usage example with `RestClient`: +``` +RestClient restClient = RestClient.builder() + .requestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory())) + .requestInterceptor(new AllureRestTemplate()) + .build(); +``` +Use a buffering request factory when the client should still be able to read the response body after Allure captures it. + +`RestTemplate` remains supported: +``` +RestTemplate restTemplate = new RestTemplate( + new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()) +); +restTemplate.setInterceptors(Collections.singletonList(new AllureRestTemplate())); +``` + +You can specify custom templates, which should be placed in src/main/resources/tpl folder: +``` +new AllureRestTemplate() + .setRequestTemplate("custom-http-request.ftl") + .setResponseTemplate("custom-http-response.ftl") +``` + +## OkHttp + +Interceptor for OkHttp client, that generates attachment for allure. ```xml diff --git a/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java b/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java index dbd92dc3..33e2a3de 100644 --- a/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java +++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java @@ -17,37 +17,27 @@ import io.qameta.allure.Allure; import io.qameta.allure.AllureLifecycle; -import io.qameta.allure.model.Status; -import io.qameta.allure.model.StepResult; -import io.qameta.allure.util.ObjectUtils; import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.assertj.core.api.AbstractAssert; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static io.qameta.allure.util.ResultsUtils.getStatus; -import static io.qameta.allure.util.ResultsUtils.getStatusDetails; +import java.util.function.Supplier; /** - * @author charlie (Dmitry Baev). - * @author sskorol (Sergey Korol). + * Captures user-side AssertJ factories and fluent calls, then delegates assertion-chain state + * to {@link AssertJRecorder}. + * */ @SuppressWarnings("all") @Aspect public class AllureAspectJ { - private static final Logger LOGGER = LoggerFactory.getLogger(AllureAspectJ.class); - private static InheritableThreadLocal lifecycle = new InheritableThreadLocal() { @Override protected AllureLifecycle initialValue() { @@ -55,64 +45,117 @@ protected AllureLifecycle initialValue() { } }; - @Pointcut("execution(!private org.assertj.core.api.AbstractAssert.new(..))") - public void anyAssertCreation() { + private static final ThreadLocal RECORDER = ThreadLocal.withInitial(AssertJRecorder::new); + + private static final ThreadLocal RECORDING_MUTED = ThreadLocal.withInitial(() -> false); + + @Pointcut( + "(" + + "call(public static * org.assertj.core.api.Assertions*.assertThat*(..))" + + " || call(public static * org.assertj.core.api.BDDAssertions*.then*(..))" + + " || call(public * org.assertj.core.api.*SoftAssertionsProvider+.assertThat*(..))" + + " || call(public * org.assertj.core.api.*SoftAssertionsProvider+.then*(..))" + + ")" + ) + + /** + * Handles the assert factory call callback. + */ + public void assertFactoryCall() { //pointcut body, should be empty } - @Pointcut("execution(* org.assertj.core.api.AssertJProxySetup.*(..))") - public void proxyMethod() { + @Pointcut( + "(" + + "call(public * org.assertj.core.api.AbstractAssert+.*(..))" + + " || call(public * org.assertj.core.api.Assert+.*(..))" + + " || call(public * org.assertj.core.api.Descriptable+.*(..))" + + ")" + + " && target(assertion)" + ) + + /** + * Handles the assert operation call callback. + * + * @param assertion the assertion + */ + public void assertOperationCall(final AbstractAssert assertion) { //pointcut body, should be empty } - @Pointcut("execution(public * org.assertj.core.api.AbstractAssert+.*(..)) && !proxyMethod()") - public void anyAssert() { + /** + * Handles the user code call callback. + */ + @Pointcut("!within(org.assertj..*) && !within(io.qameta.allure.assertj.AllureAspectJ)") + public void userCodeCall() { //pointcut body, should be empty } - @After("anyAssertCreation()") - public void logAssertCreation(final JoinPoint joinPoint) { - final String actual = joinPoint.getArgs().length > 0 - ? ObjectUtils.toString(joinPoint.getArgs()[0]) - : ""; - final String uuid = UUID.randomUUID().toString(); - final String name = String.format("assertThat \'%s\'", actual); + @AfterReturning( + pointcut = "assertFactoryCall() && userCodeCall()", + returning = "result" + ) - final StepResult result = new StepResult() - .setName(name) - .setStatus(Status.PASSED); + /** + * Handles the log assert creation callback. + * + * @param joinPoint the join point + * @param result the model object or framework result to process + */ + public void logAssertCreation(final JoinPoint joinPoint, final Object result) { + if (isRecordingMuted() || !(result instanceof AbstractAssert)) { + return; + } - getLifecycle().startStep(uuid, result); - getLifecycle().stopStep(uuid); + final AbstractAssert assertion = (AbstractAssert) result; + getRecorder().assertionCreated(getLifecycle(), assertion, firstArgumentOf(joinPoint)); } - @Before("anyAssert()") - public void stepStart(final JoinPoint joinPoint) { - final MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); - - final String uuid = UUID.randomUUID().toString(); - final String name = joinPoint.getArgs().length > 0 - ? String.format("%s \'%s\'", methodSignature.getName(), arrayToString(joinPoint.getArgs())) - : methodSignature.getName(); - - final StepResult result = new StepResult() - .setName(name); + /** + * Returns the log assert operation. + * + * @param joinPoint the join point + * @param assertion the assertion + * @return the log assert operation + * @throws Throwable if the underlying framework operation fails + */ + @Around("assertOperationCall(assertion) && userCodeCall()") + public Object logAssertOperation(final ProceedingJoinPoint joinPoint, + final AbstractAssert assertion) + throws Throwable { + final String methodName = getMethodName(joinPoint); + if (isRecordingMuted() || getRecorder().isIgnored(methodName)) { + return joinPoint.proceed(); + } - getLifecycle().startStep(uuid, result); + final AssertJOperation operation = getRecorder().startOperation( + getLifecycle(), + assertion, + methodName, + joinPoint.getArgs() + ); + try { + final Object result = joinPoint.proceed(); + getRecorder().operationPassed(operation, result); + return result; + } catch (Throwable throwable) { + getRecorder().operationFailed(operation, throwable); + throw throwable; + } } - @AfterThrowing(pointcut = "anyAssert()", throwing = "e") - public void stepFailed(final Throwable e) { - getLifecycle().updateStep(s -> s - .setStatus(getStatus(e).orElse(Status.BROKEN)) - .setStatusDetails(getStatusDetails(e).orElse(null))); - getLifecycle().stopStep(); - } + @After( + "execution(public void org.assertj.core.api.DefaultAssertionErrorCollector.collectAssertionError(" + + "java.lang.AssertionError)) && args(error)" + ) - @AfterReturning(pointcut = "anyAssert()") - public void stepStop() { - getLifecycle().updateStep(s -> s.setStatus(Status.PASSED)); - getLifecycle().stopStep(); + /** + * Handles the soft assertion failed callback. + * + * @param error the error reported by the framework + */ + public void softAssertionFailed(final AssertionError error) { + getRecorder().softAssertionFailed(error); } /** @@ -122,15 +165,48 @@ public void stepStop() { */ public static void setLifecycle(final AllureLifecycle allure) { lifecycle.set(allure); + clearContext(); } + /** + * Returns the lifecycle. + * + * @return the Allure lifecycle used by this integration + */ public static AllureLifecycle getLifecycle() { return lifecycle.get(); } - private static String arrayToString(final Object... array) { - return Stream.of(array) - .map(ObjectUtils::toString) - .collect(Collectors.joining(" ")); + /** + * Handles the clear context callback. + */ + public static void clearContext() { + RECORDER.remove(); + } + + static T withoutRecording(final Supplier supplier) { + final boolean previous = RECORDING_MUTED.get(); + RECORDING_MUTED.set(true); + try { + return supplier.get(); + } finally { + RECORDING_MUTED.set(previous); + } + } + + private static AssertJRecorder getRecorder() { + return RECORDER.get(); + } + + private static boolean isRecordingMuted() { + return RECORDING_MUTED.get(); + } + + private static Object firstArgumentOf(final JoinPoint joinPoint) { + return joinPoint.getArgs().length == 0 ? null : joinPoint.getArgs()[0]; + } + + private static String getMethodName(final ProceedingJoinPoint joinPoint) { + return ((MethodSignature) joinPoint.getSignature()).getMethod().getName(); } } diff --git a/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJChain.java b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJChain.java new file mode 100644 index 00000000..c4d30c75 --- /dev/null +++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJChain.java @@ -0,0 +1,123 @@ +/* + * Copyright 2016-2026 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.assertj; + +import io.qameta.allure.model.Stage; +import io.qameta.allure.model.Status; +import io.qameta.allure.model.StatusDetails; +import io.qameta.allure.model.StepResult; +import org.assertj.core.api.AbstractAssert; + +import java.util.Optional; +import java.util.UUID; + +/** + * Parent Allure step for one AssertJ assertion chain. + * + *

A chain is the stable container for all meaningful fluent operations produced by one AssertJ assertion object. + * {@link AssertJRecorder} creates it when user code calls an AssertJ factory such as {@code assertThat(actual)}, + * stores it by assertion object identity, and appends one {@link AssertJOperation} child for every reported fluent + * call. Methods such as {@code extracting}, {@code first}, or {@code asInstanceOf} can return another assertion + * object, but they should still read as the same assertion story, so the returned assertion is associated with this + * chain instead of creating an unrelated top-level step.

+ * + *

For a scalar assertion:

+ *
{@code
+ * assertThat("Data").hasSize(4)
+ *
+ * assert "Data"
+ *   has size 4
+ * }
+ * + *

For an assertion with a description, the parent step is renamed while the operation history stays visible:

+ *
{@code
+ * assertThat(user).as("user profile").isNotNull()
+ *
+ * assert user profile
+ *   described as "user profile"
+ *   is not null
+ * }
+ * + *

For navigation or extraction, later checks remain under the same parent:

+ *
{@code
+ * assertThat(results).extracting(Result::getName).containsExactly("passed")
+ *
+ * assert 1 Result item
+ *   extracts Result::getName -> 1 string
+ *   contains exactly ["passed"]
+ * }
+ * + *

This class is intentionally only a small mutable model around the retained {@link StepResult}. It owns the + * parent step name, status, timing, and child operation list. It does not decide which AssertJ methods are meaningful + * or how subjects and arguments are rendered; those decisions belong to {@link AssertJRecorder}, + * {@link AssertJMethodSupport}, and {@link AssertJValueRenderer}.

+ */ +final class AssertJChain { + + private static final String ASSERTJ_STEP_PREFIX = "assert "; + + private final String uuid; + + private final AbstractAssert assertion; + + private final StepResult step; + + AssertJChain(final AbstractAssert assertion, final String subject) { + this.uuid = UUID.randomUUID().toString(); + this.assertion = assertion; + this.step = new StepResult() + .setName(chainName(subject)) + .setStatus(Status.PASSED) + .setStage(Stage.FINISHED) + .setStart(System.currentTimeMillis()) + .setStop(System.currentTimeMillis()); + } + + String getUuid() { + return uuid; + } + + AbstractAssert getAssertion() { + return assertion; + } + + StepResult getStep() { + return step; + } + + void addOperation(final AssertJOperation operation) { + step.getSteps().add(operation.getStep()); + } + + void rename(final Optional description) { + description.ifPresent(value -> step.setName(chainName(value))); + } + + void updateStatus(final Status status, final StatusDetails details) { + step + .setStatus(status) + .setStatusDetails(details); + finish(); + } + + void finish() { + step.setStop(System.currentTimeMillis()); + } + + private String chainName(final String subject) { + return AssertJValueRenderer.truncateStepName(ASSERTJ_STEP_PREFIX + subject); + } +} diff --git a/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJLifecycleListener.java b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJLifecycleListener.java new file mode 100644 index 00000000..d85316e2 --- /dev/null +++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJLifecycleListener.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016-2026 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.assertj; + +import io.qameta.allure.listener.FixtureLifecycleListener; +import io.qameta.allure.listener.TestLifecycleListener; +import io.qameta.allure.model.FixtureResult; +import io.qameta.allure.model.TestResult; + +/** + * Clears per-thread AssertJ recorder state after Allure has finished owning the current result. + * + *

{@link AllureAspectJ} keeps an {@link AssertJRecorder} in a {@link ThreadLocal} so assertion objects can + * be matched by identity across later fluent calls. Test engines commonly reuse worker threads, so that + * thread-local map would otherwise keep old assertion objects, rendered steps, and operation stack state after + * the test or fixture result has already been written. The retained {@code StepResult}s are already attached to + * the Allure model by reference, so removing the recorder here does not remove any reported steps; it only + * releases per-thread bookkeeping before the next test or fixture starts on the same thread.

+ */ +public class AssertJLifecycleListener implements TestLifecycleListener, FixtureLifecycleListener { + + /** + * {@inheritDoc} + */ + @Override + public void afterTestWrite(final TestResult result) { + AllureAspectJ.clearContext(); + } + + /** + * {@inheritDoc} + */ + @Override + public void afterFixtureStop(final FixtureResult result) { + AllureAspectJ.clearContext(); + } +} diff --git a/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJMethodSupport.java b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJMethodSupport.java new file mode 100644 index 00000000..455b2803 --- /dev/null +++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJMethodSupport.java @@ -0,0 +1,98 @@ +/* + * Copyright 2016-2026 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.assertj; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Keeps method-name decisions out of the aspect and recorder flow. + */ +final class AssertJMethodSupport { + + private static final String AS = "as"; + private static final String DESCRIBED_AS = "describedAs"; + + private static final List IGNORED_METHODS = Arrays.asList( + "actual", + "descriptionText", + "equals", + "getWritableAssertionInfo", + "hashCode", + "toString" + ); + + private static final Set NAVIGATION_METHODS = new HashSet<>( + Arrays.asList( + "asBase64Decoded", + "asBoolean", + "asByte", + "asDouble", + "asFloat", + "asInstanceOf", + "asInt", + "asList", + "asLong", + "asShort", + "asString", + "bytes", + "decodedAsBase64", + "element", + "elements", + "extracting", + "extractingResultOf", + "first", + "flatExtracting", + "flatMap", + "last", + "map", + "rootCause", + "singleElement", + "size", + "usingRecursiveAssertion", + "usingRecursiveComparison" + ) + ); + + private AssertJMethodSupport() { + throw new IllegalStateException("do not instantiate"); + } + + static boolean isIgnored(final String methodName) { + return IGNORED_METHODS.contains(methodName); + } + + static String normalize(final String methodName) { + final int accessorIndex = methodName.indexOf("$accessor$"); + if (accessorIndex > 0) { + return methodName.substring(0, accessorIndex); + } + if (DESCRIBED_AS.equals(methodName)) { + return AS; + } + return methodName; + } + + static boolean isDescription(final String methodName) { + return AS.equals(methodName) || DESCRIBED_AS.equals(methodName); + } + + static boolean isNavigation(final String methodName) { + return NAVIGATION_METHODS.contains(methodName); + } +} diff --git a/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJOperation.java b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJOperation.java new file mode 100644 index 00000000..f06b8be4 --- /dev/null +++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJOperation.java @@ -0,0 +1,160 @@ +/* + * Copyright 2016-2026 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.assertj; + +import io.qameta.allure.model.Parameter; +import io.qameta.allure.model.Stage; +import io.qameta.allure.model.Status; +import io.qameta.allure.model.StatusDetails; +import io.qameta.allure.model.StepResult; + +import java.util.List; + +import static io.qameta.allure.util.ResultsUtils.getStatus; +import static io.qameta.allure.util.ResultsUtils.getStatusDetails; + +/** + * Child Allure step for one meaningful AssertJ fluent operation. + * + *

An operation is the report entry for one fluent method call inside an {@link AssertJChain}. The recorder creates + * it before proceeding with the intercepted AssertJ call, marks it passed or failed after the call returns, and keeps + * it attached to the chain that owns the assertion object. Earlier operations remain passed when a later operation + * fails, so the report shows the exact point where the assertion chain stopped matching the expectation.

+ * + *

For a simple assertion, each checked method becomes one operation:

+ *
{@code
+ * assertThat("Data").startsWith("Da").endsWith("ta")
+ *
+ * assert "Data"
+ *   starts with "Da"
+ *   ends with "ta"
+ * }
+ * + *

For navigation methods, the operation name is enriched with the returned subject. The returned AssertJ object + * still belongs to the same chain, so the report stays readable as one story:

+ *
{@code
+ * assertThat(users).first(InstanceOfAssertFactories.STRING).startsWith("alice")
+ *
+ * assert 1 string
+ *   first element as InstanceOfAssertFactory -> "alice@example.org"
+ *   starts with "alice"
+ * }
+ * + *

For failures, this operation receives the failure status and status details, and the parent chain receives the + * same status. This makes the failed operation visible without losing the successful context before it:

+ *
{@code
+ * assertThat("Data").startsWith("Da").hasSize(5)
+ *
+ * assert "Data"                 FAILED
+ *   starts with "Da"             PASSED
+ *   has size 5                   FAILED
+ * }
+ * + *

Some AssertJ methods call other assertion methods internally. Those calls should not become extra child steps + * because they would duplicate implementation details instead of user intent. The {@code nestedLevel} counter lets the + * recorder reuse the active operation while those internal calls run, then finish only the user-visible operation.

+ */ +final class AssertJOperation { + + private final AssertJChain chain; + + private final String methodName; + + private final StepResult step; + + private final boolean navigation; + + private String returnedSubject; + + private int nestedLevel; + + AssertJOperation(final AssertJChain chain, + final String methodName, + final String name, + final List parameters, + final boolean navigation) { + this.chain = chain; + this.methodName = methodName; + this.navigation = navigation; + this.step = new StepResult() + .setName(name) + .setParameters(parameters) + .setStage(Stage.RUNNING) + .setStart(System.currentTimeMillis()); + } + + AssertJChain getChain() { + return chain; + } + + StepResult getStep() { + return step; + } + + boolean isNavigation() { + return navigation; + } + + boolean isDescription() { + return AssertJMethodSupport.isDescription(methodName); + } + + boolean isNested() { + return nestedLevel > 0; + } + + AssertJOperation nested() { + nestedLevel++; + return this; + } + + void leaveNested() { + nestedLevel--; + } + + void setReturnedSubject(final String subject) { + if (returnedSubject != null) { + return; + } + + returnedSubject = subject; + step.setName(AssertJValueRenderer.truncateStepName(step.getName() + " -> " + subject)); + } + + void passed() { + if (step.getStatus() == null) { + step.setStatus(Status.PASSED); + } + finish(); + } + + void failed(final Throwable throwable) { + final Status status = getStatus(throwable).orElse(Status.BROKEN); + final StatusDetails details = getStatusDetails(throwable).orElse(null); + step + .setStatus(status) + .setStatusDetails(details); + chain.updateStatus(status, details); + finish(); + } + + private void finish() { + step + .setStage(Stage.FINISHED) + .setStop(System.currentTimeMillis()); + chain.finish(); + } +} diff --git a/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJRecorder.java b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJRecorder.java new file mode 100644 index 00000000..50bc7753 --- /dev/null +++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJRecorder.java @@ -0,0 +1,259 @@ +/* + * Copyright 2016-2026 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.assertj; + +import io.qameta.allure.AllureLifecycle; +import org.assertj.core.api.AbstractAssert; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Records AssertJ objects by identity and builds one Allure step tree per assertion chain. + * + *

The recorder is the stateful part behind {@link AllureAspectJ}. The aspect only detects user-side AssertJ + * factory calls and fluent operation calls; this class decides which {@link AssertJChain} owns the assertion object, + * where a new {@link AssertJOperation} should be attached, and how pass/fail state should be reflected in the retained + * Allure {@code StepResult} tree.

+ * + *

Each aspect thread gets its own recorder instance. Assertion objects are tracked in an {@link IdentityHashMap} + * because AssertJ assertion classes can override {@code equals} and {@code hashCode}; object identity is the only safe + * way to know that a later fluent call belongs to the same assertion object that was created earlier.

+ * + *

The normal hard-assertion flow is:

+ *
{@code
+ * assertThat("Data").startsWith("Da").endsWith("ta")
+ *
+ * assertionCreated(assertThat result, "Data")
+ * startOperation(startsWith, ["Da"])
+ * operationPassed(startsWith)
+ * startOperation(endsWith, ["ta"])
+ * operationPassed(endsWith)
+ *
+ * assert "Data"
+ *   starts with "Da"
+ *   ends with "ta"
+ * }
+ * + *

Stored assertion instances keep separate chains because the map key is the assertion instance itself:

+ *
{@code
+ * final AbstractStringAssert a = assertThat("alpha");
+ * final AbstractStringAssert b = assertThat("bravo");
+ *
+ * a.isEqualTo("alpha");
+ * b.isEqualTo("bravo");
+ *
+ * assert "alpha"
+ *   is equal to "alpha"
+ * assert "bravo"
+ *   is equal to "bravo"
+ * }
+ * + *

Navigation operations such as {@code extracting}, {@code first}, and {@code asInstanceOf} may return new AssertJ + * assertion objects. Those returned objects are registered against the existing chain, so later checks stay under the + * same parent step:

+ *
{@code
+ * assertThat(results).extracting(Result::getName).containsExactly("passed")
+ *
+ * assert 1 Result item
+ *   extracts Result::getName -> 1 string
+ *   contains exactly ["passed"]
+ * }
+ * + *

The {@code operations} stack tracks the currently executing user-visible operation. It has two jobs: assertions + * created inside callbacks such as {@code satisfies} are attached beneath the active operation, and AssertJ internal + * calls on the same chain are counted as nested work instead of being reported as extra steps.

+ * + *
{@code
+ * assertThat("alpha").satisfies(value -> assertThat(value).startsWith("al"))
+ *
+ * assert "alpha"
+ *   satisfies 
+ *     assert "alpha"
+ *       starts with "al"
+ * }
+ * + *

Soft assertion failures are reported before {@code assertAll()} throws. The AssertJ error collector callback calls + * {@link #softAssertionFailed(AssertionError)}, which marks the active operation and its chain as failed while + * preserving the earlier passed operations.

+ */ +final class AssertJRecorder { + + private final Map, AssertJChain> chains = new IdentityHashMap<>(); + + private final Deque operations = new ArrayDeque<>(); + + private final AssertJValueRenderer renderer = new AssertJValueRenderer(); + + void assertionCreated(final AllureLifecycle lifecycle, + final AbstractAssert assertion, + final Object actual) { + if (chains.containsKey(assertion)) { + return; + } + + final AssertJOperation activeOperation = activeOperation(); + if (isNavigationResult(activeOperation)) { + chains.put(assertion, activeOperation.getChain()); + return; + } + + final AssertJChain chain = new AssertJChain(assertion, renderer.renderSubject(actual)); + chains.put(assertion, chain); + attachChain(lifecycle, chain, activeOperation); + } + + AssertJOperation startOperation(final AllureLifecycle lifecycle, + final AbstractAssert assertion, + final String methodName, + final Object... args) { + final AssertJChain chain = chainFor(lifecycle, assertion); + final String normalizedName = AssertJMethodSupport.normalize(methodName); + + final AssertJOperation activeOperation = activeOperation(); + if (isInternalCallOnSameChain(activeOperation, chain)) { + return activeOperation.nested(); + } + + final AssertJOperation operation = new AssertJOperation( + chain, + normalizedName, + renderer.renderOperation(normalizedName, args), + renderer.renderParameters(normalizedName, args), + AssertJMethodSupport.isNavigation(normalizedName) + ); + chain.addOperation(operation); + operations.push(operation); + return operation; + } + + void operationPassed(final AssertJOperation operation, final Object result) { + if (operation.isNested()) { + pop(operation); + return; + } + + registerReturnedAssertion(operation, result); + renameChainFromDescription(operation); + operation.passed(); + pop(operation); + } + + void operationFailed(final AssertJOperation operation, final Throwable throwable) { + operation.failed(throwable); + pop(operation); + } + + void softAssertionFailed(final AssertionError error) { + final AssertJOperation current = activeOperation(); + if (current != null) { + current.failed(error); + } + } + + boolean isIgnored(final String methodName) { + return AssertJMethodSupport.isIgnored(methodName); + } + + private AssertJChain chainFor(final AllureLifecycle lifecycle, final AbstractAssert assertion) { + final AssertJChain chain = chains.get(assertion); + if (chain != null) { + return chain; + } + + final AssertJChain created = new AssertJChain(assertion, renderer.renderSubject(actualOf(assertion))); + chains.put(assertion, created); + attachChain(lifecycle, created, activeOperation()); + return created; + } + + private void attachChain(final AllureLifecycle lifecycle, + final AssertJChain chain, + final AssertJOperation parentOperation) { + if (parentOperation == null) { + lifecycle.startStep(chain.getUuid(), chain.getStep()); + lifecycle.stopStep(chain.getUuid()); + return; + } + + parentOperation.getStep().getSteps().add(chain.getStep()); + } + + private void registerReturnedAssertion(final AssertJOperation operation, final Object result) { + if (!(result instanceof AbstractAssert)) { + return; + } + + final AbstractAssert returned = (AbstractAssert) result; + chains.put(returned, operation.getChain()); + if (operation.isNavigation()) { + operation.setReturnedSubject(renderer.renderSubject(actualOf(returned))); + } + } + + private void renameChainFromDescription(final AssertJOperation operation) { + if (operation.isDescription()) { + operation.getChain().rename(descriptionOf(operation.getChain().getAssertion())); + } + } + + private AssertJOperation activeOperation() { + return operations.peek(); + } + + private boolean isNavigationResult(final AssertJOperation activeOperation) { + return activeOperation != null && activeOperation.isNavigation(); + } + + private boolean isInternalCallOnSameChain(final AssertJOperation activeOperation, final AssertJChain chain) { + return activeOperation != null && activeOperation.getChain() == chain; + } + + private void pop(final AssertJOperation operation) { + if (operation.isNested()) { + operation.leaveNested(); + return; + } + if (!operations.isEmpty() && operations.peek() == operation) { + operations.pop(); + } + } + + private Object actualOf(final AbstractAssert assertion) { + return AllureAspectJ.withoutRecording(() -> { + try { + return assertion.actual(); + } catch (RuntimeException e) { + return null; + } + }); + } + + private Optional descriptionOf(final AbstractAssert assertion) { + return AllureAspectJ.withoutRecording(() -> { + try { + return Optional.ofNullable(assertion.descriptionText()) + .map(String::trim) + .filter(value -> !value.isEmpty()); + } catch (RuntimeException e) { + return Optional.empty(); + } + }); + } +} diff --git a/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJValueRenderer.java b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJValueRenderer.java new file mode 100644 index 00000000..49f9ba6a --- /dev/null +++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJValueRenderer.java @@ -0,0 +1,561 @@ +/* + * Copyright 2016-2026 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.assertj; + +import io.qameta.allure.model.Parameter; +import io.qameta.allure.util.ObjectUtils; +import org.assertj.core.description.Description; +import org.assertj.core.groups.Tuple; + +import java.lang.reflect.Array; +import java.net.URI; +import java.net.URL; +import java.nio.file.Path; +import java.time.temporal.TemporalAccessor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import static io.qameta.allure.util.ResultsUtils.getLambdaName; + +/** + * Renders AssertJ subjects and arguments into semantic step names. + */ +@SuppressWarnings("all") +final class AssertJValueRenderer { + + private static final int STEP_NAME_LIMIT = 1000; + + private static final int INLINE_VALUE_LIMIT = 3; + + private static final String LAMBDA = ""; + + private static final String TRUNCATED = "..."; + + String renderSubject(final Object value) { + return truncateStepName(renderSubjectValue(value)); + } + + String renderOperation(final String methodName, final Object[] args) { + return truncateStepName(renderOperationName(methodName, args)); + } + + List renderParameters(final String methodName, final Object[] args) { + final Object[] values = parameterArguments(methodName, args); + if (values.length == 0) { + return Collections.emptyList(); + } + + final String renderedOperation = renderOperation(methodName, args); + final List parameters = new ArrayList<>(); + for (int index = 0; index < values.length; index++) { + final String value = renderParameterValue(values[index]); + if (renderedOperation.contains(value)) { + continue; + } + parameters.add( + new Parameter() + .setName(parameterName(methodName, index)) + .setValue(value) + .setMode(Parameter.Mode.DEFAULT) + ); + } + return parameters; + } + + static String truncateStepName(final String value) { + if (value == null || value.length() <= STEP_NAME_LIMIT) { + return value; + } + return value.substring(0, STEP_NAME_LIMIT - TRUNCATED.length()) + TRUNCATED; + } + + private String renderSubjectValue(final Object value) { + if (value == null) { + return "null"; + } + if (value instanceof CharSequence || isSimple(value)) { + return renderSimple(value); + } + if (value instanceof Collection) { + if (isInlineCollection((Collection) value)) { + return renderCollectionValue((Collection) value); + } + return renderCollectionSubject((Collection) value); + } + if (value instanceof Map) { + return "map with " + renderEntryCount(((Map) value).size()); + } + if (value.getClass().isArray()) { + return renderArraySubject(value); + } + if (value instanceof Iterable) { + return "iterable"; + } + return simpleClassName(value); + } + + private Object[] parameterArguments(final String methodName, final Object[] args) { + if (isDescriptionWithEmptyValues(args)) { + return new Object[]{args[0]}; + } + if (isSingleVarargToUnwrap(methodName, args)) { + return new Object[]{Array.get(args[0], 0)}; + } + return args; + } + + private String parameterName(final String methodName, final int index) { + if ("hasFieldOrPropertyWithValue".equals(methodName)) { + return index == 0 ? "field or property" : "expected value"; + } + + if (index > 0) { + return "argument " + (index + 1); + } + + switch (methodName) { + case "as": + return "description"; + case "asInstanceOf": + case "first": + case "singleElement": + return "factory"; + case "extracting": + case "flatExtracting": + return "extractor"; + case "hasSize": + return "expected size"; + case "satisfies": + return "condition"; + case "contains": + case "containsExactly": + case "containsExactlyInAnyOrder": + case "endsWith": + case "isEqualTo": + case "startsWith": + return "expected"; + default: + return "argument 1"; + } + } + + private String renderParameterValue(final Object value) { + return renderArgument(value); + } + + private String renderOperationName(final String methodName, final Object[] args) { + if (args.length == 0) { + return readableMethodName(methodName); + } + + final String arguments = renderArguments(methodName, args); + switch (methodName) { + case "as": + return "described as " + arguments; + case "asInstanceOf": + return "as instance of " + arguments; + case "contains": + return "contains " + arguments; + case "containsExactly": + return "contains exactly " + arguments; + case "containsExactlyInAnyOrder": + return "contains exactly in any order " + arguments; + case "endsWith": + return "ends with " + arguments; + case "extracting": + return "extracts " + arguments; + case "flatExtracting": + return "flat extracts " + arguments; + case "first": + return "first element as " + arguments; + case "hasFieldOrPropertyWithValue": + return renderHasFieldOrPropertyWithValue(args); + case "hasSize": + return "has size " + arguments; + case "isEqualTo": + return "is equal to " + arguments; + case "singleElement": + return "single element as " + arguments; + case "startsWith": + return "starts with " + arguments; + case "satisfies": + return "satisfies " + arguments; + default: + return readableMethodName(methodName) + " " + arguments; + } + } + + private String renderHasFieldOrPropertyWithValue(final Object[] args) { + if (args.length != 2) { + return "has field or property with value " + renderEach(args); + } + return "has field or property " + renderArgument(args[0]) + " with value " + renderArgument(args[1]); + } + + private String readableMethodName(final String methodName) { + if (methodName.startsWith("is") && methodName.length() > 2 && Character.isUpperCase(methodName.charAt(2))) { + return "is " + splitCamelCase(methodName.substring(2)); + } + if (methodName.startsWith("has") && methodName.length() > 3 && Character.isUpperCase(methodName.charAt(3))) { + return "has " + splitCamelCase(methodName.substring(3)); + } + return splitCamelCase(methodName); + } + + private String splitCamelCase(final String value) { + return value + .replaceAll("([a-z0-9])([A-Z])", "$1 $2") + .toLowerCase(); + } + + private String renderArguments(final String methodName, final Object[] args) { + if (isDescriptionWithEmptyValues(args)) { + return renderArgument(args[0]); + } + if (isSingleVarargToUnwrap(methodName, args)) { + return renderArgument(Array.get(args[0], 0)); + } + if (isSingleArrayArgument(args)) { + return renderArray(args[0]); + } + return renderEach(args); + } + + private boolean isDescriptionWithEmptyValues(final Object[] args) { + return args.length == 2 + && args[1] != null + && args[1].getClass().isArray() + && Array.getLength(args[1]) == 0; + } + + private boolean isSingleVarargToUnwrap(final String methodName, final Object[] args) { + return args.length == 1 + && args[0] != null + && args[0].getClass().isArray() + && Array.getLength(args[0]) == 1 + && shouldUnwrapSingleVararg(methodName); + } + + private boolean isSingleArrayArgument(final Object[] args) { + return args.length == 1 + && args[0] != null + && args[0].getClass().isArray(); + } + + private boolean shouldUnwrapSingleVararg(final String methodName) { + return !methodName.contains("Any") + && !methodName.contains("Exactly") + && !methodName.contains("Only") + && !methodName.contains("Sequence") + && !methodName.contains("Subsequence") + && !methodName.endsWith("In"); + } + + private String renderEach(final Object[] args) { + final List values = new ArrayList<>(); + for (Object arg : args) { + values.add(renderArgument(arg)); + } + return values.stream().collect(Collectors.joining(", ")); + } + + private String renderArgument(final Object value) { + if (value == null) { + return "null"; + } + if (isLambda(value)) { + return renderLambda(value); + } + if (value instanceof Description) { + return renderSimple(value.toString()); + } + if (value instanceof Tuple) { + return renderTuple((Tuple) value); + } + if (value instanceof CharSequence || isSimple(value)) { + return renderSimple(value); + } + if (value instanceof Collection) { + if (isInlineCollection((Collection) value)) { + return renderCollectionValue((Collection) value); + } + return renderCollectionSubject((Collection) value); + } + if (value instanceof Map) { + return "map with " + renderEntryCount(((Map) value).size()); + } + if (value.getClass().isArray()) { + return renderArray(value); + } + return simpleClassName(value); + } + + private String renderArray(final Object array) { + if (array instanceof byte[]) { + return ObjectUtils.toString(array); + } + + final int length = Array.getLength(array); + if (array.getClass().getComponentType().isPrimitive()) { + return ObjectUtils.toString(array); + } + if (allLambdas(array, length)) { + return length == 1 ? renderLambda(Array.get(array, 0)) : lambdaList(array, length); + } + if (allSimple(array, length) || !array.getClass().getComponentType().isPrimitive()) { + return renderObjectArray(array, length); + } + return array.getClass().getComponentType().getSimpleName() + "[](length=" + length + ")"; + } + + private String renderCollectionSubject(final Collection value) { + final int size = value.size(); + if (size == 0) { + return "empty collection"; + } + return commonElementType(value) + .map(type -> renderElementCount(size, type)) + .orElseGet(() -> renderItemCount(size)); + } + + private String renderArraySubject(final Object array) { + final int length = Array.getLength(array); + if (isInlineArray(array, length)) { + return renderArrayValue(array, length); + } + if (array instanceof byte[]) { + return "byte array with " + renderByteCount(length); + } + return renderElementCount(length, array.getClass().getComponentType()); + } + + private boolean isInlineCollection(final Collection value) { + return value.size() <= INLINE_VALUE_LIMIT && allInlineValues(value); + } + + private boolean allInlineValues(final Collection value) { + for (Object item : value) { + if (!isInlineValue(item)) { + return false; + } + } + return true; + } + + private boolean isInlineValue(final Object value) { + return value == null + || isLambda(value) + || value instanceof Description + || isInlineTuple(value) + || value instanceof CharSequence + || isSimple(value); + } + + private boolean isInlineTuple(final Object value) { + if (!(value instanceof Tuple)) { + return false; + } + final Object[] values = ((Tuple) value).toArray(); + return isInlineArray(values, values.length); + } + + private String renderTuple(final Tuple tuple) { + final Object[] values = tuple.toArray(); + return renderObjectArray(values, values.length) + .replaceFirst("^\\[", "(") + .replaceFirst("]$", ")"); + } + + private String renderCollectionValue(final Collection value) { + final List values = new ArrayList<>(); + for (Object item : value) { + values.add(renderArgument(item)); + } + return values.stream().collect(Collectors.joining(", ", "[", "]")); + } + + private boolean isInlineArray(final Object array, final int length) { + if (length > INLINE_VALUE_LIMIT || array instanceof byte[]) { + return false; + } + if (array.getClass().getComponentType().isPrimitive()) { + return true; + } + for (int i = 0; i < length; i++) { + if (!isInlineValue(Array.get(array, i))) { + return false; + } + } + return true; + } + + private String renderArrayValue(final Object array, final int length) { + if (array.getClass().getComponentType().isPrimitive()) { + return ObjectUtils.toString(array); + } + return renderObjectArray(array, length); + } + + private Optional> commonElementType(final Collection value) { + Class result = null; + for (Object item : value) { + if (item == null) { + continue; + } + final Class itemType = elementTypeOf(item); + if (result == null) { + result = itemType; + } else if (!result.equals(itemType)) { + return java.util.Optional.empty(); + } + } + return java.util.Optional.ofNullable(result); + } + + private Class elementTypeOf(final Object item) { + if (item instanceof Collection) { + return Collection.class; + } + if (item instanceof Map) { + return Map.class; + } + return item.getClass(); + } + + private String renderElementCount(final int size, final Class type) { + if (String.class.equals(type)) { + return size + " " + pluralize("string", size); + } + if (Boolean.class.equals(type) || Boolean.TYPE.equals(type)) { + return size + " " + pluralize("boolean", size); + } + if (Character.class.equals(type) || Character.TYPE.equals(type)) { + return size + " " + pluralize("character", size); + } + if (Number.class.isAssignableFrom(type) || type.isPrimitive() && !Boolean.TYPE.equals(type) + && !Character.TYPE.equals(type)) { + return size + " " + pluralize("number", size); + } + if (Collection.class.equals(type)) { + return size + " " + pluralize("collection", size); + } + if (Map.class.equals(type)) { + return size + " " + pluralize("map", size); + } + return size + " " + type.getSimpleName() + " " + pluralize("item", size); + } + + private String renderItemCount(final int size) { + return size + " " + pluralize("item", size); + } + + private String renderEntryCount(final int size) { + return size + " " + pluralize("entry", size); + } + + private String renderByteCount(final int size) { + return size + " " + pluralize("byte", size); + } + + private String pluralize(final String word, final int count) { + return count == 1 ? word : word + "s"; + } + + private String renderObjectArray(final Object array, final int length) { + final List values = new ArrayList<>(); + for (int i = 0; i < length; i++) { + values.add(renderArgument(Array.get(array, i))); + } + return values.stream().collect(Collectors.joining(", ", "[", "]")); + } + + private boolean allLambdas(final Object array, final int length) { + if (length == 0) { + return false; + } + for (int i = 0; i < length; i++) { + if (!isLambda(Array.get(array, i))) { + return false; + } + } + return true; + } + + private String lambdaList(final Object array, final int length) { + final List values = new ArrayList<>(); + for (int i = 0; i < length; i++) { + values.add(renderLambda(Array.get(array, i))); + } + return values.stream().collect(Collectors.joining(", ", "[", "]")); + } + + private boolean allSimple(final Object array, final int length) { + for (int i = 0; i < length; i++) { + final Object item = Array.get(array, i); + if (item != null && !isSimple(item) && !(item instanceof CharSequence)) { + return false; + } + } + return true; + } + + private boolean isSimple(final Object value) { + return value instanceof Number + || value instanceof Boolean + || value instanceof Character + || value instanceof Enum + || value instanceof Path + || value instanceof URI + || value instanceof URL + || value instanceof TemporalAccessor; + } + + private boolean isLambda(final Object value) { + if (value == null) { + return false; + } + final Class type = value.getClass(); + return type.isSynthetic() || type.getName().contains("$$Lambda$"); + } + + private String renderLambda(final Object value) { + return getLambdaName(value) + .orElse(LAMBDA); + } + + private String renderSimple(final Object value) { + if (value instanceof CharSequence || value instanceof Character) { + return "\"" + ObjectUtils.toString(value) + "\""; + } + if (value instanceof Enum) { + return ((Enum) value).name(); + } + return ObjectUtils.toString(value); + } + + private String simpleClassName(final Object value) { + final Class type = value.getClass(); + if (type.isAnonymousClass()) { + return type.getSuperclass().getSimpleName(); + } + return type.getSimpleName(); + } +} diff --git a/allure-assertj/src/main/resources/META-INF/services/io.qameta.allure.listener.FixtureLifecycleListener b/allure-assertj/src/main/resources/META-INF/services/io.qameta.allure.listener.FixtureLifecycleListener new file mode 100644 index 00000000..65aaf3ec --- /dev/null +++ b/allure-assertj/src/main/resources/META-INF/services/io.qameta.allure.listener.FixtureLifecycleListener @@ -0,0 +1 @@ +io.qameta.allure.assertj.AssertJLifecycleListener diff --git a/allure-assertj/src/main/resources/META-INF/services/io.qameta.allure.listener.TestLifecycleListener b/allure-assertj/src/main/resources/META-INF/services/io.qameta.allure.listener.TestLifecycleListener new file mode 100644 index 00000000..65aaf3ec --- /dev/null +++ b/allure-assertj/src/main/resources/META-INF/services/io.qameta.allure.listener.TestLifecycleListener @@ -0,0 +1 @@ +io.qameta.allure.assertj.AssertJLifecycleListener diff --git a/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java b/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java index 957657b1..377e7a6b 100644 --- a/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java +++ b/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java @@ -15,110 +15,536 @@ */ package io.qameta.allure.assertj; +import io.qameta.allure.model.Parameter; +import io.qameta.allure.model.Status; +import io.qameta.allure.model.StatusDetails; import io.qameta.allure.model.StepResult; import io.qameta.allure.model.TestResult; import io.qameta.allure.test.AllureFeatures; import io.qameta.allure.test.AllureResults; +import org.assertj.core.api.AbstractStringAssert; +import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; +import java.io.Serializable; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collections; +import java.util.function.Function; import static io.qameta.allure.test.RunUtils.runWithinTestContext; import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author charlie (Dmitry Baev). - */ +import static org.assertj.core.api.Assertions.tuple; class AllureAspectJTest { @AllureFeatures.Steps @Test - void shouldCreateStepsForAsserts() { + void shouldCreateSemanticChainForScalarAssert() { final AllureResults results = runWithinTestContext(() -> { assertThat("Data") .hasSize(4); }, AllureAspectJ::setLifecycle); - assertThat(results.getTestResults()) - .flatExtracting(TestResult::getSteps) + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName, StepResult::getStatus) + .containsExactly(tuple("assert \"Data\"", Status.PASSED)); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) .extracting(StepResult::getName) - .containsExactly( - "assertThat 'Data'", - "hasSize '4'" - ); + .containsExactly("has size 4"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .flatExtracting(StepResult::getParameters) + .isEmpty(); } @AllureFeatures.Steps @Test - void shouldHandleNullableObject() { + void shouldUseAssertDescriptionAsChainName() { final AllureResults results = runWithinTestContext(() -> { assertThat((Object) null) .as("Nullable object") .isNull(); }, AllureAspectJ::setLifecycle); - assertThat(results.getTestResults()) - .flatExtracting(TestResult::getSteps) + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) .extracting(StepResult::getName) - .containsExactly( - "assertThat 'null'", - "as 'Nullable object []'", - "isNull" - ); + .containsExactly("assert Nullable object"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("described as \"Nullable object\"", "is null"); } @AllureFeatures.Steps @Test - void shouldHandleByteArrayObject() { - final String s = "some string"; + void shouldRenderByteArraysWithoutPayload() { + final String value = "some string"; final AllureResults results = runWithinTestContext(() -> { - assertThat(s.getBytes(StandardCharsets.UTF_8)) + assertThat(value.getBytes(StandardCharsets.UTF_8)) .as("Byte array object") - .isEqualTo(s.getBytes(StandardCharsets.UTF_8)); + .isEqualTo(value.getBytes(StandardCharsets.UTF_8)); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .containsExactly("assert Byte array object"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("described as \"Byte array object\"", "is equal to "); + } + + @AllureFeatures.Steps + @Test + void shouldRenderCollectionsAsSubjectsAndExpectedValuesAsValues() { + final AllureResults results = runWithinTestContext(() -> { + assertThat(Arrays.asList("a", "b")) + .containsExactly("a", "b"); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .containsExactly("assert [\"a\", \"b\"]"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("contains exactly [\"a\", \"b\"]"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .flatExtracting(StepResult::getParameters) + .isEmpty(); + } + + @AllureFeatures.Steps + @Test + void shouldRenderSmallArraysAsValues() { + final AllureResults results = runWithinTestContext(() -> { + assertThat(new int[]{1, 2}) + .containsExactly(1, 2); + + assertThat(new String[]{"alpha", "bravo"}) + .containsExactly("alpha", "bravo"); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .containsExactly("assert [1, 2]", "assert [\"alpha\", \"bravo\"]"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("contains exactly [1, 2]", "contains exactly [\"alpha\", \"bravo\"]"); + } + + @AllureFeatures.Steps + @Test + void shouldRenderTuplesAsValues() { + final AllureResults results = runWithinTestContext(() -> { + assertThat( + Arrays.asList( + tuple("first", Status.PASSED), + tuple("second", Status.FAILED) + ) + ) + .containsExactly( + tuple("first", Status.PASSED), + tuple("second", Status.FAILED) + ); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .containsExactly("assert [(\"first\", PASSED), (\"second\", FAILED)]"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("contains exactly [(\"first\", PASSED), (\"second\", FAILED)]"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .flatExtracting(StepResult::getParameters) + .isEmpty(); + } + + @AllureFeatures.Steps + @Test + void shouldRenderNullValuesInContainsExactlyInAnyOrder() { + final AllureResults results = runWithinTestContext(() -> { + assertThat(Arrays.asList(null, "a", "b")) + .containsExactlyInAnyOrder(null, "a", "b"); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .containsExactly("assert [null, \"a\", \"b\"]"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("contains exactly in any order [null, \"a\", \"b\"]"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .flatExtracting(StepResult::getParameters) + .isEmpty(); + } + + @AllureFeatures.Steps + @Test + void shouldRenderNullValuesAfterExtractingAndKeepLambdaVarargs() { + final TestResult model = new TestResult(); + + final AllureResults results = runWithinTestContext(() -> { + assertThat(model) + .extracting(TestResult::getDescription, TestResult::getDescriptionHtml) + .containsExactly(null, null); }, AllureAspectJ::setLifecycle); - assertThat(results.getTestResults()) - .flatExtracting(TestResult::getSteps) + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .containsExactly("assert TestResult"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) .extracting(StepResult::getName) .containsExactly( - "assertThat ''", - "describedAs 'Byte array object'", - "isEqualTo ''" + "extracts [, ] -> [null, null]", + "contains exactly [null, null]" ); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .flatExtracting(StepResult::getParameters) + .isEmpty(); + } + + @AllureFeatures.Steps + @Test + void shouldRenderFieldOrPropertyValueAssertions() { + final StatusDetails details = new StatusDetails() + .setMessage("Make the test failed"); + + final AllureResults results = runWithinTestContext(() -> { + assertThat(details) + .hasFieldOrPropertyWithValue("message", "Make the test failed"); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .containsExactly("assert StatusDetails"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("has field or property \"message\" with value \"Make the test failed\""); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .flatExtracting(StepResult::getParameters) + .isEmpty(); + } + + @AllureFeatures.Steps + @Test + void shouldTruncateLongStepNamesAndAddOnlyTruncatedValuesAsParameters() { + final String value = String.join("", Collections.nCopies(1200, "a")); + + final AllureResults results = runWithinTestContext(() -> { + assertThat(value) + .isEqualTo(value); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .singleElement() + .asString() + .hasSize(1000) + .startsWith("assert \"") + .endsWith("..."); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .singleElement() + .asString() + .hasSize(1000) + .startsWith("is equal to \"") + .endsWith("..."); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .flatExtracting(StepResult::getParameters) + .extracting(Parameter::getName, Parameter::getValue) + .containsExactly(tuple("expected", "\"" + value + "\"")); } @AllureFeatures.Steps @Test - void shouldHandleCollections() { + void shouldCreateSeparateChainsForMultipleAssertThatCalls() { final AllureResults results = runWithinTestContext(() -> { + assertThat("Data") + .hasSize(4); + + assertThat(42) + .isPositive() + .isEqualTo(42); + assertThat(Arrays.asList("a", "b")) - .containsExactly("a", "b"); + .hasSize(2) + .contains("a"); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName, StepResult::getStatus) + .containsExactly( + tuple("assert \"Data\"", Status.PASSED), + tuple("assert 42", Status.PASSED), + tuple("assert [\"a\", \"b\"]", Status.PASSED) + ); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly( + "has size 4", + "is positive", + "is equal to 42", + "has size 2", + "contains \"a\"" + ); + } + + @AllureFeatures.Steps + @Test + void shouldAttachOperationsToStoredAssertionInstances() { + final String targetA = "alpha"; + final String targetB = "bravo"; + + final AllureResults results = runWithinTestContext(() -> { + final AbstractStringAssert a = assertThat(targetA); + final AbstractStringAssert b = assertThat(targetB); + + a.isEqualTo("alpha"); + b.isEqualTo("bravo"); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName, StepResult::getStatus) + .containsExactly( + tuple("assert \"alpha\"", Status.PASSED), + tuple("assert \"bravo\"", Status.PASSED) + ); + assertThat(result.getSteps()) + .filteredOn("name", "assert \"alpha\"") + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("is equal to \"alpha\""); + assertThat(result.getSteps()) + .filteredOn("name", "assert \"bravo\"") + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("is equal to \"bravo\""); + } + + @AllureFeatures.Steps + @Test + void shouldAvoidVerboseModelToStringPayloads() { + final TestResult model = new TestResult() + .setUuid("uid") + .setName("testPassed") + .setFullName("other.PassingTest.testPassed"); + + final AllureResults results = runWithinTestContext(() -> { + assertThat(Collections.singletonList(model)) + .hasSize(1) + .containsExactly(model); }, AllureAspectJ::setLifecycle); - assertThat(results.getTestResults()) - .flatExtracting(TestResult::getSteps) - .extracting(StepResult::getName) - .containsExactly( - "assertThatList '[a, b]'", - "containsExactly '[a, b]'" - ); + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .containsExactly("assert 1 TestResult item"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("has size 1", "contains exactly [TestResult]"); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .noneMatch(name -> name.contains("fullName=")) + .noneMatch(name -> name.contains("other.PassingTest.testPassed")); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .noneMatch(name -> name.contains("fullName=")) + .noneMatch(name -> name.contains("other.PassingTest.testPassed")); } @AllureFeatures.Steps @Test - void softAssertions() { + void shouldKeepNavigationInsideTheSameChain() { + final TestResult model = new TestResult() + .setFullName("my.company.Test.testOne"); + + final AllureResults results = runWithinTestContext(() -> { + assertThat(Collections.singletonList(model)) + .extracting(TestResult::getFullName) + .containsExactly("my.company.Test.testOne"); + + assertThat(Collections.singletonList("alpha")) + .first(InstanceOfAssertFactories.STRING) + .startsWith("al"); + + assertThat(Collections.singletonList("bravo")) + .singleElement(InstanceOfAssertFactories.STRING) + .endsWith("vo"); + + assertThat((Object) "charlie") + .asInstanceOf(InstanceOfAssertFactories.STRING) + .contains("har"); + + assertThat(Collections.singletonList(Collections.singletonList("delta"))) + .flatExtracting(value -> value) + .containsExactly("delta"); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .hasSize(5); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly( + "extracts -> [\"my.company.Test.testOne\"]", + "contains exactly [\"my.company.Test.testOne\"]", + "first element as InstanceOfAssertFactory -> \"alpha\"", + "starts with \"al\"", + "single element as InstanceOfAssertFactory -> \"bravo\"", + "ends with \"vo\"", + "as instance of InstanceOfAssertFactory -> \"charlie\"", + "contains \"har\"", + "flat extracts -> [\"delta\"]", + "contains exactly [\"delta\"]" + ); + } + + @AllureFeatures.Steps + @Test + void shouldRenderSerializedLambdaMethodReferences() { + final TestResult model = new TestResult() + .setFullName("my.company.Test.testOne"); + + final AllureResults results = runWithinTestContext(() -> { + assertThat(Collections.singletonList(model)) + .extracting((Function & Serializable) TestResult::getFullName) + .containsExactly("my.company.Test.testOne"); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly( + "extracts TestResult::getFullName -> [\"my.company.Test.testOne\"]", + "contains exactly [\"my.company.Test.testOne\"]" + ); + } + + @AllureFeatures.Steps + @Test + void shouldMarkTheFailedHardAssertionOperation() { + final AllureResults results = runWithinTestContext(() -> { + assertThat("Data") + .hasSize(5); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName, StepResult::getStatus) + .containsExactly(tuple("assert \"Data\"", Status.FAILED)); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName, StepResult::getStatus) + .containsExactly(tuple("has size 5", Status.FAILED)); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .filteredOn("name", "has size 5") + .extracting(step -> step.getStatusDetails().getMessage()) + .singleElement() + .asString() + .contains("size"); + } + + @AllureFeatures.Steps + @Test + void shouldMarkTheFailedSoftAssertionOperationBeforeAssertAll() { final AllureResults results = runWithinTestContext(() -> { final SoftAssertions soft = new SoftAssertions(); soft.assertThat(25) - .as("Test description") + .as("Age") .isEqualTo(26); soft.assertAll(); }, AllureAspectJ::setLifecycle); - assertThat(results.getTestResults()) - .flatExtracting(TestResult::getSteps) + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName, StepResult::getStatus) + .containsExactly(tuple("assert Age", Status.FAILED)); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName, StepResult::getStatus) + .containsExactly( + tuple("described as \"Age\"", Status.PASSED), + tuple("is equal to 26", Status.FAILED) + ); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .filteredOn("name", "is equal to 26") + .extracting(step -> step.getStatusDetails().getMessage()) + .singleElement() + .asString() + .contains("expected: 26"); + } + + @AllureFeatures.Steps + @Test + void shouldAttachNestedAssertionsUnderCallbackOperations() { + final AllureResults results = runWithinTestContext(() -> { + assertThat("alpha") + .satisfies( + value -> assertThat(value) + .startsWith("al") + .endsWith("ha") + ); + }, AllureAspectJ::setLifecycle); + + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .extracting(StepResult::getName) + .containsExactly("assert \"alpha\""); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) .extracting(StepResult::getName) - .contains("as 'Test description []'", "isEqualTo '26'"); + .containsExactly("satisfies "); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .filteredOn("name", "satisfies ") + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("assert \"alpha\""); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .filteredOn("name", "satisfies ") + .flatExtracting(StepResult::getSteps) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("starts with \"al\"", "ends with \"ha\""); + } + + private TestResult assertOnlyOneResult(final AllureResults results) { + assertThat(results.getTestResults()).hasSize(1); + return results.getTestResults().get(0); } } diff --git a/allure-assertj/src/test/resources/allure.properties b/allure-assertj/src/test/resources/allure.properties index 9c0b0a2d..c881472e 100644 --- a/allure-assertj/src/test/resources/allure.properties +++ b/allure-assertj/src/test/resources/allure.properties @@ -1,2 +1,3 @@ allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-assertj diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java index 070f2b5c..f3c57536 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java @@ -16,14 +16,31 @@ package io.qameta.allure.attachment; /** - * @author charlie (Dmitry Baev). + * Defines the attachment content contract used by Allure attachment support. + * + *

Implement this interface when custom code needs to participate in the same integration flow as the built-in Allure adapter components.

*/ public interface AttachmentContent { + /** + * Returns the content. + * + * @return the content + */ String getContent(); + /** + * Returns the content type. + * + * @return the content type + */ String getContentType(); + /** + * Returns the file extension. + * + * @return the file extension + */ String getFileExtension(); } diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java index f7f6af5f..77d8befe 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java @@ -18,11 +18,15 @@ /** * Marker interface for complex Allure attachments. * - * @author charlie (Dmitry Baev). */ @FunctionalInterface public interface AttachmentData { + /** + * Returns the name. + * + * @return the attachment or display name + */ String getName(); } diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java index 06ef35fc..2bfbb211 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java @@ -17,11 +17,16 @@ /** * @param the type of attachment data. - * @author charlie (Dmitry Baev). */ @FunctionalInterface public interface AttachmentProcessor { + /** + * Adds the attachment. + * + * @param attachmentData the attachment data + * @param renderer the renderer used to turn attachment data into content + */ void addAttachment(T attachmentData, AttachmentRenderer renderer); } diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java index 3a6303e7..b097a0e1 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java @@ -16,10 +16,18 @@ package io.qameta.allure.attachment; /** - * @author charlie (Dmitry Baev). + * Supports Allure attachment integration with Allure reporting. + * + *

Use this type through the module that owns it when translating framework execution, result metadata, or attachments into Allure report data.

*/ public class AttachmentRenderException extends RuntimeException { + /** + * Creates an attachment render exception with the supplied values. + * + * @param message the message + * @param cause the failure cause reported by the framework + */ public AttachmentRenderException(final String message, final Throwable cause) { super(message, cause); } diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderer.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderer.java index 15423fc8..ea2cee7d 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderer.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderer.java @@ -17,9 +17,7 @@ /** * @param the type of attachment data - * @author charlie (Dmitry Baev). */ -@SuppressWarnings("PMD.AvoidUncheckedExceptionsInSignatures") @FunctionalInterface public interface AttachmentRenderer { diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java index 2e3f200d..fb70c14f 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java @@ -16,7 +16,9 @@ package io.qameta.allure.attachment; /** - * @author charlie (Dmitry Baev). + * Supports Allure attachment integration with Allure reporting. + * + *

Use this type through the module that owns it when translating framework execution, result metadata, or attachments into Allure report data.

*/ public class DefaultAttachmentContent implements AttachmentContent { @@ -26,6 +28,13 @@ public class DefaultAttachmentContent implements AttachmentContent { private final String fileExtension; + /** + * Creates a default attachment content with the supplied values. + * + * @param content the attachment content + * @param contentType the attachment content type + * @param fileExtension the attachment file extension + */ public DefaultAttachmentContent(final String content, final String contentType, final String fileExtension) { @@ -34,16 +43,25 @@ public DefaultAttachmentContent(final String content, this.fileExtension = fileExtension; } + /** + * {@inheritDoc} + */ @Override public String getContent() { return content; } + /** + * {@inheritDoc} + */ @Override public String getContentType() { return contentType; } + /** + * {@inheritDoc} + */ @Override public String getFileExtension() { return fileExtension; diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java index 017d1f61..3bfc71a4 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java @@ -21,20 +21,33 @@ import java.nio.charset.StandardCharsets; /** - * @author charlie (Dmitry Baev). + * Supports Allure attachment integration with Allure reporting. + * + *

Use this type through the module that owns it when translating framework execution, result metadata, or attachments into Allure report data.

*/ public class DefaultAttachmentProcessor implements AttachmentProcessor { private final AllureLifecycle lifecycle; + /** + * Creates a default attachment processor with default configuration. + */ public DefaultAttachmentProcessor() { this(Allure.getLifecycle()); } + /** + * Creates a default attachment processor with the supplied values. + * + * @param lifecycle the Allure lifecycle to use + */ public DefaultAttachmentProcessor(final AllureLifecycle lifecycle) { this.lifecycle = lifecycle; } + /** + * {@inheritDoc} + */ @Override public void addAttachment(final AttachmentData attachmentData, final AttachmentRenderer renderer) { diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java index 354956d8..6442cb62 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java @@ -26,7 +26,9 @@ import java.util.Collections; /** - * @author charlie (Dmitry Baev). + * Supports Allure attachment integration with Allure reporting. + * + *

Use this type through the module that owns it when translating framework execution, result metadata, or attachments into Allure report data.

*/ public class FreemarkerAttachmentRenderer implements AttachmentRenderer { @@ -36,6 +38,11 @@ public class FreemarkerAttachmentRenderer implements AttachmentRendererUse this model to carry request metadata and body content from client interceptors to attachment renderers and processors.

*/ public class HttpRequestAttachment implements AttachmentData { @@ -44,12 +46,35 @@ public class HttpRequestAttachment implements AttachmentData { private final Map formParams; + /** + * Creates an HTTP request attachment with the supplied values. + * + * @param name the display name or logical name to use + * @param url the request URL or service method name + * @param method the framework or Java method to inspect + * @param body the attachment body + * @param curl the curl + * @param headers the headers + * @param cookies the cookies + */ public HttpRequestAttachment(final String name, final String url, final String method, final String body, final String curl, final Map headers, final Map cookies) { this(name, url, method, body, curl, headers, cookies, Collections.emptyMap()); } + /** + * Creates an HTTP request attachment with the supplied values. + * + * @param name the display name or logical name to use + * @param url the request URL or service method name + * @param method the framework or Java method to inspect + * @param body the attachment body + * @param curl the curl + * @param headers the headers + * @param cookies the cookies + * @param formParams the form params + */ @SuppressWarnings("checkstyle:parameternumber") public HttpRequestAttachment(final String name, final String url, final String method, final String body, final String curl, final Map headers, @@ -64,39 +89,80 @@ public HttpRequestAttachment(final String name, final String url, final String m this.formParams = formParams; } + /** + * Returns the url. + * + * @return the url + */ public String getUrl() { return url; } + /** + * Returns the method. + * + * @return the method + */ public String getMethod() { return method; } + /** + * Returns the body. + * + * @return the body + */ public String getBody() { return body; } + /** + * Returns the headers. + * + * @return the headers + */ public Map getHeaders() { return headers; } + /** + * Returns the cookies. + * + * @return the cookies + */ public Map getCookies() { return cookies; } + /** + * Returns the form params. + * + * @return the form params + */ public Map getFormParams() { return formParams; } + /** + * Returns the curl. + * + * @return the curl + */ public String getCurl() { return curl; } + /** + * {@inheritDoc} + */ @Override public String getName() { return name; } + /** + * {@inheritDoc} + */ @Override public String toString() { return "HttpRequestAttachment(" @@ -135,16 +201,36 @@ private Builder(final String name, final String url) { this.url = url; } + /** + * Creates a builder for a builder. + * + * @param attachmentName the attachment display name + * @param url the request URL or service method name + * @return a new builder instance + */ public static Builder create(final String attachmentName, final String url) { return new Builder(attachmentName, url); } + /** + * Sets the method. + * + * @param method the framework or Java method to inspect + * @return this instance for method chaining + */ public Builder setMethod(final String method) { Objects.requireNonNull(method, "Method must not be null value"); this.method = method; return this; } + /** + * Sets the header. + * + * @param name the display name or logical name to use + * @param value the value to set + * @return this instance for method chaining + */ public Builder setHeader(final String name, final String value) { Objects.requireNonNull(name, "Header name must not be null value"); Objects.requireNonNull(value, "Header value must not be null value"); @@ -152,12 +238,25 @@ public Builder setHeader(final String name, final String value) { return this; } + /** + * Sets the headers. + * + * @param headers the headers + * @return this instance for method chaining + */ public Builder setHeaders(final Map headers) { Objects.requireNonNull(headers, "Headers must not be null value"); this.headers.putAll(headers); return this; } + /** + * Sets the cookie. + * + * @param name the display name or logical name to use + * @param value the value to set + * @return this instance for method chaining + */ public Builder setCookie(final String name, final String value) { Objects.requireNonNull(name, "Cookie name must not be null value"); Objects.requireNonNull(value, "Cookie value must not be null value"); @@ -165,18 +264,36 @@ public Builder setCookie(final String name, final String value) { return this; } + /** + * Sets the cookies. + * + * @param cookies the cookies + * @return this instance for method chaining + */ public Builder setCookies(final Map cookies) { Objects.requireNonNull(cookies, "Cookies must not be null value"); this.cookies.putAll(cookies); return this; } + /** + * Sets the body. + * + * @param body the attachment body + * @return this instance for method chaining + */ public Builder setBody(final String body) { Objects.requireNonNull(body, "Body should not be null value"); this.body = body; return this; } + /** + * Sets the form params. + * + * @param formParams the form params + * @return this instance for method chaining + */ public Builder setFormParams(final Map formParams) { Objects.requireNonNull(formParams, "Form params must not be null value"); this.formParams.putAll(formParams); @@ -237,6 +354,11 @@ public Builder withBody(final String body) { return setBody(body); } + /** + * Builds a builder from the configured values. + * + * @return the built attachment model + */ public HttpRequestAttachment build() { return new HttpRequestAttachment(name, url, method, body, getCurl(), headers, cookies, formParams); } diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java index 20187c7c..4a956dba 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java @@ -23,7 +23,9 @@ import java.util.Objects; /** - * @author charlie (Dmitry Baev). + * Describes an HTTP or RPC response attachment rendered in an Allure report. + * + *

Use this model to carry response metadata and body content from client interceptors to attachment renderers and processors.

*/ public class HttpResponseAttachment implements AttachmentData { @@ -39,6 +41,16 @@ public class HttpResponseAttachment implements AttachmentData { private final Map cookies; + /** + * Creates an HTTP response attachment with the supplied values. + * + * @param name the display name or logical name to use + * @param url the request URL or service method name + * @param body the attachment body + * @param responseCode the response code + * @param headers the headers + * @param cookies the cookies + */ public HttpResponseAttachment(final String name, final String url, final String body, final int responseCode, final Map headers, final Map cookies) { @@ -50,31 +62,62 @@ public HttpResponseAttachment(final String name, final String url, this.cookies = cookies; } + /** + * {@inheritDoc} + */ @Override public String getName() { return name; } + /** + * Returns the url. + * + * @return the url + */ public String getUrl() { return url; } + /** + * Returns the body. + * + * @return the body + */ public String getBody() { return body; } + /** + * Returns the response code. + * + * @return the response code + */ public int getResponseCode() { return responseCode; } + /** + * Returns the headers. + * + * @return the headers + */ public Map getHeaders() { return headers; } + /** + * Returns the cookies. + * + * @return the cookies + */ public Map getCookies() { return cookies; } + /** + * {@inheritDoc} + */ @Override public String toString() { return "HttpResponseAttachment(" @@ -109,21 +152,46 @@ private Builder(final String name) { this.name = name; } + /** + * Creates a builder for a builder. + * + * @param attachmentName the attachment display name + * @return a new builder instance + */ public static Builder create(final String attachmentName) { return new Builder(attachmentName); } + /** + * Sets the url. + * + * @param url the request URL or service method name + * @return this instance for method chaining + */ public Builder setUrl(final String url) { Objects.requireNonNull(url, "Url must not be null value"); this.url = url; return this; } + /** + * Sets the response code. + * + * @param responseCode the response code + * @return this instance for method chaining + */ public Builder setResponseCode(final int responseCode) { this.responseCode = responseCode; return this; } + /** + * Sets the header. + * + * @param name the display name or logical name to use + * @param value the value to set + * @return this instance for method chaining + */ public Builder setHeader(final String name, final String value) { Objects.requireNonNull(name, "Header name must not be null value"); Objects.requireNonNull(value, "Header value must not be null value"); @@ -131,12 +199,25 @@ public Builder setHeader(final String name, final String value) { return this; } + /** + * Sets the headers. + * + * @param headers the headers + * @return this instance for method chaining + */ public Builder setHeaders(final Map headers) { Objects.requireNonNull(headers, "Headers must not be null value"); this.headers.putAll(headers); return this; } + /** + * Sets the cookie. + * + * @param name the display name or logical name to use + * @param value the value to set + * @return this instance for method chaining + */ public Builder setCookie(final String name, final String value) { Objects.requireNonNull(name, "Cookie name must not be null value"); Objects.requireNonNull(value, "Cookie value must not be null value"); @@ -144,12 +225,24 @@ public Builder setCookie(final String name, final String value) { return this; } + /** + * Sets the cookies. + * + * @param cookies the cookies + * @return this instance for method chaining + */ public Builder setCookies(final Map cookies) { Objects.requireNonNull(cookies, "Cookies must not be null value"); this.cookies.putAll(cookies); return this; } + /** + * Sets the body. + * + * @param body the attachment body + * @return this instance for method chaining + */ public Builder setBody(final String body) { Objects.requireNonNull(body, "Body should not be null value"); this.body = body; @@ -226,6 +319,11 @@ public Builder withBody(final String body) { return setBody(body); } + /** + * Builds a builder from the configured values. + * + * @return the built attachment model + */ public HttpResponseAttachment build() { return new HttpResponseAttachment(name, url, body, responseCode, headers, cookies); } diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java index fe84d9cf..f1defa78 100644 --- a/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java +++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java @@ -29,10 +29,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; - -/** - * @author charlie (Dmitry Baev). - */ class DefaultAttachmentProcessorTest { @SuppressWarnings("unchecked") diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/FreemarkerAttachmentRendererTest.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/FreemarkerAttachmentRendererTest.java index 4f0c235e..956080c3 100644 --- a/allure-attachments/src/test/java/io/qameta/allure/attachment/FreemarkerAttachmentRendererTest.java +++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/FreemarkerAttachmentRendererTest.java @@ -23,10 +23,6 @@ import static io.qameta.allure.attachment.testdata.TestData.randomHttpRequestAttachment; import static io.qameta.allure.attachment.testdata.TestData.randomHttpResponseAttachment; import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author charlie (Dmitry Baev). - */ class FreemarkerAttachmentRendererTest { private static final String CONTENT = "content"; @@ -35,7 +31,6 @@ class FreemarkerAttachmentRendererTest { private static final String FILE_EXTENSION = "fileExtension"; private static final String HTML = ".html"; - @AllureFeatures.Attachments @Test void shouldRenderRequestAttachment() { diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/NegativeFreemarkerAttachmentRendererTest.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/NegativeFreemarkerAttachmentRendererTest.java index 444e5c61..5f5ba6d4 100644 --- a/allure-attachments/src/test/java/io/qameta/allure/attachment/NegativeFreemarkerAttachmentRendererTest.java +++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/NegativeFreemarkerAttachmentRendererTest.java @@ -29,10 +29,6 @@ import static io.qameta.allure.attachment.testdata.TestData.negativeHttpRequestAttachment; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * @author a-simeshin (Simeshin Artem). - */ class NegativeFreemarkerAttachmentRendererTest { private static final String TEMPLATE_FOR_EXCEPTION = "body-npe-non-safe-attachment.ftl"; diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java index d30634da..3ef9c4c6 100644 --- a/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java +++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java @@ -24,10 +24,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; - -/** - * @author charlie (Dmitry Baev). - */ public final class TestData { private TestData() { diff --git a/allure-attachments/src/test/resources/allure.properties b/allure-attachments/src/test/resources/allure.properties index 9c0b0a2d..b47a01f6 100644 --- a/allure-attachments/src/test/resources/allure.properties +++ b/allure-attachments/src/test/resources/allure.properties @@ -1,2 +1,3 @@ allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-attachments diff --git a/allure-awaitility/src/main/java/io/qameta/allure/awaitility/AllureAwaitilityListener.java b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/AllureAwaitilityListener.java index 451e3fd9..5cd3b273 100644 --- a/allure-awaitility/src/main/java/io/qameta/allure/awaitility/AllureAwaitilityListener.java +++ b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/AllureAwaitilityListener.java @@ -58,7 +58,6 @@ * *

* - * @author a-simeshin (Simeshin Artem) * @see org.awaitility.core.ConditionEvaluationListener * @see Awaitility#setDefaultConditionEvaluationListener(ConditionEvaluationListener) * @see ConditionFactory#conditionEvaluationListener(ConditionEvaluationListener) @@ -77,14 +76,18 @@ public class AllureAwaitilityListener implements ConditionEvaluationListener LIFECYCLE - = new InheritableThreadLocal() { + private static final InheritableThreadLocal LIFECYCLE = new InheritableThreadLocal() { @Override protected AllureLifecycle initialValue() { return Allure.getLifecycle(); } }; + /** + * Returns the lifecycle. + * + * @return the Allure lifecycle used by this integration + */ public static AllureLifecycle getLifecycle() { return LIFECYCLE.get(); } @@ -225,7 +228,8 @@ public void exceptionIgnored(final IgnoredException ignoredException) { getLifecycle().updateStep(awaitilityCondition -> { final String currentExceptionIgnoredStepUUID = UUID.randomUUID().toString(); final String message = String.format( - onExceptionStepTextPattern, ignoredException.getThrowable().getMessage()); + onExceptionStepTextPattern, ignoredException.getThrowable().getMessage() + ); final StringWriter stringWriter = new StringWriter(); ignoredException.getThrowable().printStackTrace(new PrintWriter(stringWriter)); final String stackTrace = stringWriter.toString(); @@ -239,7 +243,8 @@ public void exceptionIgnored(final IgnoredException ignoredException) { ); getLifecycle().addAttachment( ignoredException.getThrowable().getMessage(), "text/plain", ".txt", - stackTrace.getBytes(StandardCharsets.UTF_8)); + stackTrace.getBytes(StandardCharsets.UTF_8) + ); getLifecycle().stopStep(currentExceptionIgnoredStepUUID); }); } diff --git a/allure-awaitility/src/main/java/io/qameta/allure/awaitility/TemporalDuration.java b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/TemporalDuration.java index 5632b1be..d6243f7f 100644 --- a/allure-awaitility/src/main/java/io/qameta/allure/awaitility/TemporalDuration.java +++ b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/TemporalDuration.java @@ -58,11 +58,17 @@ public class TemporalDuration implements TemporalAccessor { this.temporal = duration.addTo(BASE); } + /** + * {@inheritDoc} + */ @Override public boolean isSupported(final TemporalField field) { return temporal.isSupported(field) && temporal.getLong(field) - BASE.getLong(field) != 0L; } + /** + * {@inheritDoc} + */ @Override public long getLong(final TemporalField temporalField) { if (!isSupported(temporalField)) { @@ -71,6 +77,9 @@ public long getLong(final TemporalField temporalField) { return temporal.getLong(temporalField) - BASE.getLong(temporalField); } + /** + * {@inheritDoc} + */ @Override public String toString() { if (duration.compareTo(Duration.ofMillis(1)) < 0) { diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java index 59207847..9eb6d7d6 100644 --- a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java @@ -58,27 +58,27 @@ static void setup() { @TestFactory Stream globalSettingsAwaitWoAliasCheckTopLevelPassedStep() { final List testResult = runWithinTestContext(() -> { - final AtomicInteger atomicInteger = new AtomicInteger(0); - await().with() - .conditionEvaluationListener(new AllureAwaitilityListener()) - .atMost(Duration.of(1000, ChronoUnit.MILLIS)) - .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) - .until(atomicInteger::getAndIncrement, is(3)); - }, + final AtomicInteger atomicInteger = new AtomicInteger(0); + await().with() + .conditionEvaluationListener(new AllureAwaitilityListener()) + .atMost(Duration.of(1000, ChronoUnit.MILLIS)) + .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) + .until(atomicInteger::getAndIncrement, is(3)); + }, AllureAwaitilityListener::setLifecycle ).getTestResults(); return Stream.of( - DynamicTest.dynamicTest("Exactly 1 top level step for 1 awaitility condition", () -> - assertThat(testResult.get(0).getSteps()) + DynamicTest.dynamicTest( + "Exactly 1 top level step for 1 awaitility condition", () -> assertThat(testResult.get(0).getSteps()) .hasSize(1) ), - DynamicTest.dynamicTest("Top level step has passed status", () -> - assertThat(testResult.get(0).getSteps()) + DynamicTest.dynamicTest( + "Top level step has passed status", () -> assertThat(testResult.get(0).getSteps()) .allMatch(step -> Status.PASSED.equals(step.getStatus())) ), - DynamicTest.dynamicTest("Top level step has default name because await() wo alias", () -> - assertThat(testResult.get(0).getSteps()) + DynamicTest.dynamicTest( + "Top level step has default name because await() wo alias", () -> assertThat(testResult.get(0).getSteps()) .extracting(StepResult::getName) .containsExactly("Awaitility: Starting evaluation") ) @@ -96,13 +96,13 @@ Stream globalSettingsAwaitWoAliasCheckTopLevelPassedStep() { @Test void globalSettingsAwaitWithAliasCheckTopLevelPassedStep() { final List testResult = runWithinTestContext(() -> { - final AtomicInteger atomicInteger = new AtomicInteger(0); - await("Counter should be at least 3").with() - .conditionEvaluationListener(new AllureAwaitilityListener()) - .atMost(Duration.of(1000, ChronoUnit.MILLIS)) - .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) - .until(atomicInteger::getAndIncrement, is(3)); - }, + final AtomicInteger atomicInteger = new AtomicInteger(0); + await("Counter should be at least 3").with() + .conditionEvaluationListener(new AllureAwaitilityListener()) + .atMost(Duration.of(1000, ChronoUnit.MILLIS)) + .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) + .until(atomicInteger::getAndIncrement, is(3)); + }, AllureAwaitilityListener::setLifecycle ).getTestResults(); assertEquals( @@ -125,51 +125,51 @@ void globalSettingsAwaitWithAliasCheckTopLevelPassedStep() { @TestFactory Stream globalSettingsCheckAwaitWoAliasSecondLevelPassedSteps() { final List testResult = runWithinTestContext(() -> { - final AtomicInteger atomicInteger = new AtomicInteger(0); - await().with() - .conditionEvaluationListener(new AllureAwaitilityListener()) - .atMost(Duration.of(1000, ChronoUnit.MILLIS)) - .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) - .until(atomicInteger::getAndIncrement, is(3)); - }, + final AtomicInteger atomicInteger = new AtomicInteger(0); + await().with() + .conditionEvaluationListener(new AllureAwaitilityListener()) + .atMost(Duration.of(1000, ChronoUnit.MILLIS)) + .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) + .until(atomicInteger::getAndIncrement, is(3)); + }, AllureAwaitilityListener::setLifecycle ).getTestResults(); return Stream.of( - dynamicTest("Exactly 4 second level steps for 4 polling iterations", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps()) + dynamicTest( + "Exactly 4 second level steps for 4 polling iterations", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps()) .hasSize(4) ), - dynamicTest("All second level steps has passed statuses", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps()) + dynamicTest( + "All second level steps has passed statuses", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps()) .allMatch(x -> x.getStatus().equals(Status.PASSED)) ), - dynamicTest("Second level step 1 name", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getName()) + dynamicTest( + "Second level step 1 name", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getName()) .contains("io.qameta.allure.awaitility.ConditionListenersPositiveTest") .contains("expected <3> but was <0>") .contains("elapsed time") .contains("remaining time") .contains("last poll interval was") ), - dynamicTest("Second level step 2 name", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getName()) + dynamicTest( + "Second level step 2 name", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getName()) .contains("io.qameta.allure.awaitility.ConditionListenersPositiveTest") .contains("expected <3> but was <1>") .contains("elapsed time") .contains("remaining time") .contains("last poll interval was") ), - dynamicTest("Second level step 3 name", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(2).getName()) + dynamicTest( + "Second level step 3 name", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(2).getName()) .contains("io.qameta.allure.awaitility.ConditionListenersPositiveTest") .contains("expected <3> but was <2>") .contains("elapsed time") .contains("remaining time") .contains("last poll interval was") ), - dynamicTest("Second level step 4 name", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(3).getName()) + dynamicTest( + "Second level step 4 name", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(3).getName()) .contains("io.qameta.allure.awaitility.ConditionListenersPositiveTest") .contains("reached its end value of <3> after") .contains("remaining time") diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java index b01c36cd..c00fcf77 100644 --- a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java @@ -63,12 +63,12 @@ void setup() { @Test void globalSettingsAwaitWoAliasCheckTopLevelBrokenStep() { final List testResult = runWithinTestContext(() -> { - final AtomicInteger atomicInteger = new AtomicInteger(0); - await().with() - .atMost(Duration.of(1000, ChronoUnit.MILLIS)) - .pollInterval(Duration.of(500, ChronoUnit.MILLIS)) - .until(atomicInteger::getAndIncrement, is(3)); - }, + final AtomicInteger atomicInteger = new AtomicInteger(0); + await().with() + .atMost(Duration.of(1000, ChronoUnit.MILLIS)) + .pollInterval(Duration.of(500, ChronoUnit.MILLIS)) + .until(atomicInteger::getAndIncrement, is(3)); + }, AllureAwaitilityListener::setLifecycle ).getTestResults(); assertEquals( @@ -90,37 +90,42 @@ void globalSettingsAwaitWoAliasCheckTopLevelBrokenStep() { @TestFactory Stream globalSettingsCheckAwaitWoAliasSecondLevelTimeoutStep() { final List testResult = runWithinTestContext(() -> { - final AtomicInteger atomicInteger = new AtomicInteger(0); - await().with() - .atMost(Duration.of(1000, ChronoUnit.MILLIS)) - .pollInterval(Duration.of(500, ChronoUnit.MILLIS)) - .until(atomicInteger::getAndIncrement, is(3)); - }, + final AtomicInteger atomicInteger = new AtomicInteger(0); + await().with() + .atMost(Duration.of(1000, ChronoUnit.MILLIS)) + .pollInterval(Duration.of(500, ChronoUnit.MILLIS)) + .until(atomicInteger::getAndIncrement, is(3)); + }, AllureAwaitilityListener::setLifecycle ).getTestResults(); return Stream.of( - dynamicTest("Second level steps count", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps()) + dynamicTest( + "Second level steps count", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps()) .as("Exactly 2 second level steps for 2 polling iterations") - .hasSize(2)), - dynamicTest("Second level step 1 name", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getName()) + .hasSize(2) + ), + dynamicTest( + "Second level step 1 name", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getName()) .contains("io.qameta.allure.awaitility.GlobalSettingsNegativeTest") .contains("expected <3> but was <0>") .contains("elapsed time") .contains("remaining time") - .contains("last poll interval was")), - dynamicTest("Second level step 1 status", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getStatus()) - .isEqualTo(Status.PASSED)), - dynamicTest("Second level step 2 name", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getName()) + .contains("last poll interval was") + ), + dynamicTest( + "Second level step 1 status", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getStatus()) + .isEqualTo(Status.PASSED) + ), + dynamicTest( + "Second level step 2 name", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getName()) .contains("Condition timeout.") - .contains("io.qameta.allure.awaitility.GlobalSettingsNegativeTest")), - dynamicTest("Second level step 2 status", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getStatus()) - .isEqualTo(Status.BROKEN)) + .contains("io.qameta.allure.awaitility.GlobalSettingsNegativeTest") + ), + dynamicTest( + "Second level step 2 status", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getStatus()) + .isEqualTo(Status.BROKEN) + ) ); } diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java index df782ada..dfb3ff3b 100644 --- a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java @@ -65,29 +65,29 @@ void setup() { @Test Stream globalSettingsAwaitWoAliasCheckTopLevelPassedStep() { final List testResult = runWithinTestContext(() -> { - final AtomicInteger atomicInteger = new AtomicInteger(0); - await().with() - .atMost(Duration.of(1000, ChronoUnit.MILLIS)) - .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) - .until(atomicInteger::getAndIncrement, is(3)); - }, + final AtomicInteger atomicInteger = new AtomicInteger(0); + await().with() + .atMost(Duration.of(1000, ChronoUnit.MILLIS)) + .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) + .until(atomicInteger::getAndIncrement, is(3)); + }, AllureAwaitilityListener::setLifecycle ).getTestResults(); return Stream.of( - dynamicTest("Steps count", () -> - assertThat(testResult.get(0).getSteps()) + dynamicTest( + "Steps count", () -> assertThat(testResult.get(0).getSteps()) .as("Exactly 1 top level step for 1 awaitility condition") .hasSize(1) ), - dynamicTest("Top level step status", () -> - assertEquals( + dynamicTest( + "Top level step status", () -> assertEquals( Status.PASSED, testResult.get(0).getSteps().get(0).getStatus(), "Top level step has passed status" ) ), - dynamicTest("Top level step name", () -> - assertEquals( + dynamicTest( + "Top level step name", () -> assertEquals( "Awaitility: Starting evaluation", testResult.get(0).getSteps().get(0).getName(), "Top level step has default name because await() wo alias" @@ -107,12 +107,12 @@ Stream globalSettingsAwaitWoAliasCheckTopLevelPassedStep() { @Test void globalSettingsAwaitWithAliasCheckTopLevelPassedStep() { final List testResult = runWithinTestContext(() -> { - final AtomicInteger atomicInteger = new AtomicInteger(0); - await("Counter should be at least 3").with() - .atMost(Duration.of(1000, ChronoUnit.MILLIS)) - .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) - .until(atomicInteger::getAndIncrement, is(3)); - }, + final AtomicInteger atomicInteger = new AtomicInteger(0); + await("Counter should be at least 3").with() + .atMost(Duration.of(1000, ChronoUnit.MILLIS)) + .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) + .until(atomicInteger::getAndIncrement, is(3)); + }, AllureAwaitilityListener::setLifecycle ).getTestResults(); assertEquals( @@ -135,24 +135,24 @@ void globalSettingsAwaitWithAliasCheckTopLevelPassedStep() { @Test Stream globalSettingsCheckAwaitWoAliasSecondLevelPassedSteps() { final List testResult = runWithinTestContext(() -> { - final AtomicInteger atomicInteger = new AtomicInteger(0); - await().with() - .atMost(Duration.of(1000, ChronoUnit.MILLIS)) - .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) - .until(atomicInteger::getAndIncrement, is(3)); - }, + final AtomicInteger atomicInteger = new AtomicInteger(0); + await().with() + .atMost(Duration.of(1000, ChronoUnit.MILLIS)) + .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) + .until(atomicInteger::getAndIncrement, is(3)); + }, AllureAwaitilityListener::setLifecycle ).getTestResults(); return Stream.of( - dynamicTest("Second level steps count", () -> - assertEquals( + dynamicTest( + "Second level steps count", () -> assertEquals( 4, testResult.get(0).getSteps().get(0).getSteps().size(), "Exactly 4 second level steps for 4 polling iterations" ) ), - dynamicTest("Second level steps all passed", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps()) + dynamicTest( + "Second level steps all passed", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps()) .extracting(StepResult::getStatus) .containsExactlyInAnyOrder( Status.PASSED, @@ -161,32 +161,32 @@ Stream globalSettingsCheckAwaitWoAliasSecondLevelPassedSteps() { Status.PASSED ) ), - dynamicTest("Second level step 1 name", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getName()) + dynamicTest( + "Second level step 1 name", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getName()) .contains("io.qameta.allure.awaitility.GlobalSettingsPositiveTest") .contains("expected <3> but was <0>") .contains("elapsed time") .contains("remaining time") .contains("last poll interval was") ), - dynamicTest("Second level step 2 name", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getName()) + dynamicTest( + "Second level step 2 name", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getName()) .contains("io.qameta.allure.awaitility.GlobalSettingsPositiveTest") .contains("expected <3> but was <1>") .contains("elapsed time") .contains("remaining time") .contains("last poll interval was") ), - dynamicTest("Second level step 3 name", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(2).getName()) + dynamicTest( + "Second level step 3 name", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(2).getName()) .contains("io.qameta.allure.awaitility.GlobalSettingsPositiveTest") .contains("expected <3> but was <2>") .contains("elapsed time") .contains("remaining time") .contains("last poll interval was") ), - dynamicTest("Second level step 4 name", () -> - assertThat(testResult.get(0).getSteps().get(0).getSteps().get(3).getName()) + dynamicTest( + "Second level step 4 name", () -> assertThat(testResult.get(0).getSteps().get(0).getSteps().get(3).getName()) .contains("io.qameta.allure.awaitility.GlobalSettingsPositiveTest") .contains("java.util.concurrent.atomic.AtomicInteger:") .contains("reached its end value of <3> after") diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java index 8acb2782..6b2fff5f 100644 --- a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java @@ -43,24 +43,24 @@ void setup() { @TestFactory Stream bothAwaitilityStepsShouldAppearTest() { final List testResult = runWithinTestContext(() -> { - await().with() - .alias("First waiting") - .until(() -> true); - await().with() - .alias("Second waiting") - .until(() -> true); - }, + await().with() + .alias("First waiting") + .until(() -> true); + await().with() + .alias("Second waiting") + .until(() -> true); + }, AllureAwaitilityListener::setLifecycle ).getTestResults(); return Stream.of( - DynamicTest.dynamicTest("Exactly 2 top level step for 2 awaitility condition", () -> - assertThat(testResult.get(0).getSteps()) + DynamicTest.dynamicTest( + "Exactly 2 top level step for 2 awaitility condition", () -> assertThat(testResult.get(0).getSteps()) .describedAs("Allure TestResult contains exactly 2 top level step for 2 awaitility condition") .hasSize(2) ), - DynamicTest.dynamicTest("All top level step for all awaitility condition has PASSED", () -> - assertThat(testResult.get(0).getSteps()) + DynamicTest.dynamicTest( + "All top level step for all awaitility condition has PASSED", () -> assertThat(testResult.get(0).getSteps()) .describedAs("Allure TestResult contains all top level step for all awaitility with PASSED condition") .allMatch(step -> Status.PASSED.equals(step.getStatus())) ) diff --git a/allure-awaitility/src/test/resources/allure.properties b/allure-awaitility/src/test/resources/allure.properties new file mode 100644 index 00000000..0486d8a7 --- /dev/null +++ b/allure-awaitility/src/test/resources/allure.properties @@ -0,0 +1,3 @@ +allure.results.directory=build/allure-results +allure.label.epic=#project.description# +allure.label.module=allure-awaitility diff --git a/allure-bom/build.gradle.kts b/allure-bom/build.gradle.kts index 9593300a..0d83d95e 100644 --- a/allure-bom/build.gradle.kts +++ b/allure-bom/build.gradle.kts @@ -8,6 +8,8 @@ dependencies { constraints { rootProject.subprojects.sorted() .forEach { api("${it.group}:${it.name}:${it.version}") } + api("io.qameta.allure:allure-junit5:${project.version}") + api("io.qameta.allure:allure-junit5-assert:${project.version}") } } diff --git a/allure-citrus/src/main/java/io/qameta/allure/citrus/AllureCitrus.java b/allure-citrus/src/main/java/io/qameta/allure/citrus/AllureCitrus.java index d501b3a2..cc5e669d 100644 --- a/allure-citrus/src/main/java/io/qameta/allure/citrus/AllureCitrus.java +++ b/allure-citrus/src/main/java/io/qameta/allure/citrus/AllureCitrus.java @@ -59,10 +59,13 @@ import static io.qameta.allure.util.ResultsUtils.createParameter; import static io.qameta.allure.util.ResultsUtils.createSuiteLabel; import static io.qameta.allure.util.ResultsUtils.createThreadLabel; +import static io.qameta.allure.util.ResultsUtils.createTitlePath; import static io.qameta.allure.util.ResultsUtils.getProvidedLabels; /** - * @author charlie (Dmitry Baev). + * Reports Citrus test execution to Allure. + * + *

Register this listener with Citrus so suite, test case, and test action events are reflected as Allure containers, fixtures, tests, and steps. The listener can use the global lifecycle or an explicitly provided lifecycle.

*/ public class AllureCitrus implements TestListener, TestSuiteListener, TestActionListener { @@ -72,64 +75,107 @@ public class AllureCitrus implements TestListener, TestSuiteListener, TestAction private final AllureLifecycle lifecycle; + /** + * Creates an Allure citrus with the supplied values. + * + * @param lifecycle the Allure lifecycle to use + */ public AllureCitrus(final AllureLifecycle lifecycle) { this.lifecycle = lifecycle; } + /** + * Creates an Allure citrus with default configuration. + */ @SuppressWarnings("unused") public AllureCitrus() { this.lifecycle = Allure.getLifecycle(); } + /** + * Returns the lifecycle. + * + * @return the Allure lifecycle used by this integration + */ public AllureLifecycle getLifecycle() { return lifecycle; } + /** + * {@inheritDoc} + */ @Override public void onStart() { //do nothing } + /** + * {@inheritDoc} + */ @Override public void onStartSuccess() { //do nothing } + /** + * {@inheritDoc} + */ @Override public void onStartFailure(final Throwable cause) { //do nothing } + /** + * {@inheritDoc} + */ @Override public void onFinish() { //do nothing } + /** + * {@inheritDoc} + */ @Override public void onFinishSuccess() { //do nothing } + /** + * {@inheritDoc} + */ @Override public void onFinishFailure(final Throwable cause) { //do nothing } + /** + * {@inheritDoc} + */ @Override public void onTestStart(final TestCase test) { startTestCase(test); } + /** + * {@inheritDoc} + */ @Override public void onTestFinish(final TestCase test) { //do nothing } + /** + * {@inheritDoc} + */ @Override public void onTestSuccess(final TestCase test) { stopTestCase(test, Status.PASSED, null); } + /** + * {@inheritDoc} + */ @Override public void onTestFailure(final TestCase test, final Throwable cause) { final Status status = ResultsUtils.getStatus(cause).orElse(Status.BROKEN); @@ -137,11 +183,17 @@ public void onTestFailure(final TestCase test, final Throwable cause) { stopTestCase(test, status, details); } + /** + * {@inheritDoc} + */ @Override public void onTestSkipped(final TestCase test) { //do nothing } + /** + * {@inheritDoc} + */ @Override public void onTestActionStart(final TestCase testCase, final TestAction testAction) { final String parentUuid = getUuid(testCase); @@ -149,11 +201,17 @@ public void onTestActionStart(final TestCase testCase, final TestAction testActi getLifecycle().startStep(parentUuid, uuid, new StepResult().setName(testAction.getName())); } + /** + * {@inheritDoc} + */ @Override public void onTestActionFinish(final TestCase testCase, final TestAction testAction) { getLifecycle().stopStep(); } + /** + * {@inheritDoc} + */ @Override public void onTestActionSkipped(final TestCase testCase, final TestAction testAction) { //do nothing @@ -161,25 +219,31 @@ public void onTestActionSkipped(final TestCase testCase, final TestAction testAc private void startTestCase(final TestCase testCase) { final String uuid = createUuid(testCase); + final Optional> testClass = Optional.ofNullable(testCase.getTestClass()); final TestResult result = new TestResult() .setUuid(uuid) .setName(testCase.getName()) + .setTitlePath( + testClass + .map(ResultsUtils::createTitlePathFromJavaClass) + .orElseGet(() -> createTitlePath(testCase.getName())) + ) .setStage(Stage.RUNNING); result.getLabels().addAll(getProvidedLabels()); - - final Optional> testClass = Optional.ofNullable(testCase.getTestClass()); testClass.map(this::getLabels).ifPresent(result.getLabels()::addAll); testClass.map(this::getLinks).ifPresent(result.getLinks()::addAll); - result.getLabels().addAll(Arrays.asList( - createHostLabel(), - createThreadLabel(), - createFrameworkLabel("citrus"), - createLanguageLabel("java") - )); + result.getLabels().addAll( + Arrays.asList( + createHostLabel(), + createThreadLabel(), + createFrameworkLabel("citrus"), + createLanguageLabel("java") + ) + ); testClass.ifPresent(aClass -> { final String suiteName = aClass.getCanonicalName(); @@ -217,7 +281,6 @@ private void stopTestCase(final TestCase testCase, getLifecycle().writeTestCase(uuid); } - private String createUuid(final TestCase testCase) { final String uuid = UUID.randomUUID().toString(); try { @@ -265,7 +328,8 @@ private List getLinks(final AnnotatedElement annotatedElement) { return Stream.of( getAnnotations(annotatedElement, io.qameta.allure.Link.class).map(ResultsUtils::createLink), getAnnotations(annotatedElement, io.qameta.allure.Issue.class).map(ResultsUtils::createLink), - getAnnotations(annotatedElement, io.qameta.allure.TmsLink.class).map(ResultsUtils::createLink)) + getAnnotations(annotatedElement, io.qameta.allure.TmsLink.class).map(ResultsUtils::createLink) + ) .reduce(Stream::concat).orElseGet(Stream::empty).collect(Collectors.toList()); } diff --git a/allure-citrus/src/test/java/io/qameta/allure/citrus/AllureCitrusTest.java b/allure-citrus/src/test/java/io/qameta/allure/citrus/AllureCitrusTest.java index bf12e299..71202f2b 100644 --- a/allure-citrus/src/test/java/io/qameta/allure/citrus/AllureCitrusTest.java +++ b/allure-citrus/src/test/java/io/qameta/allure/citrus/AllureCitrusTest.java @@ -44,10 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; - -/** - * @author charlie (Dmitry Baev). - */ @SuppressWarnings("unchecked") class AllureCitrusTest { @@ -61,6 +57,8 @@ void shouldSetName() { assertThat(results.getTestResults()) .extracting(TestResult::getName) .containsExactly("Simple test"); + assertThat(results.getTestResults().get(0).getTitlePath()) + .containsExactly("com", "consol", "citrus", "dsl", "design", "DefaultTestDesigner"); } @AllureFeatures.PassedTests diff --git a/allure-citrus/src/test/resources/allure.properties b/allure-citrus/src/test/resources/allure.properties index 9c0b0a2d..0833b8e0 100644 --- a/allure-citrus/src/test/resources/allure.properties +++ b/allure-citrus/src/test/resources/allure.properties @@ -1,2 +1,3 @@ allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-citrus diff --git a/allure-cucumber4-jvm/src/main/java/cucumber/runtime/formatter/TestSourcesModelProxy.java b/allure-cucumber4-jvm/src/main/java/cucumber/runtime/formatter/TestSourcesModelProxy.java index f0209d58..1c19d47b 100644 --- a/allure-cucumber4-jvm/src/main/java/cucumber/runtime/formatter/TestSourcesModelProxy.java +++ b/allure-cucumber4-jvm/src/main/java/cucumber/runtime/formatter/TestSourcesModelProxy.java @@ -20,28 +20,59 @@ import gherkin.ast.ScenarioDefinition; /** - * Proxy class to internal Cucumber implementation of TestSourcesModel. + * Compatibility proxy around Cucumber feature source storage. + * + *

The proxy hides version-specific Cucumber source model APIs from the reporting plugin. Integrations use it to add source-read events and resolve feature, scenario, and step keyword metadata during execution.

*/ public class TestSourcesModelProxy { private final TestSourcesModel testSources; + /** + * Creates a test sources model proxy with default configuration. + */ public TestSourcesModelProxy() { this.testSources = new TestSourcesModel(); } + /** + * Adds the test source read event. + * + * @param path the path to read from or write to + * @param event the framework event to process + */ public void addTestSourceReadEvent(final String path, final TestSourceRead event) { testSources.addTestSourceReadEvent(path, event); } + /** + * Returns the feature. + * + * @param path the path to read from or write to + * @return the feature + */ public Feature getFeature(final String path) { return testSources.getFeature(path); } + /** + * Returns the scenario definition. + * + * @param path the path to read from or write to + * @param line the source line number to resolve + * @return the scenario definition + */ public ScenarioDefinition getScenarioDefinition(final String path, final int line) { return testSources.getScenarioDefinition(path, line); } + /** + * Returns the keyword from source. + * + * @param uri the feature file URI + * @param stepLine the feature file line number of the step + * @return the keyword from source + */ public String getKeywordFromSource(final String uri, final int stepLine) { return testSources.getKeywordFromSource(uri, stepLine); } diff --git a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/AllureCucumber4Jvm.java b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/AllureCucumber4Jvm.java index 967ee1a3..cc291892 100644 --- a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/AllureCucumber4Jvm.java +++ b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/AllureCucumber4Jvm.java @@ -71,18 +71,25 @@ import static cucumber.api.HookType.Before; import static io.qameta.allure.util.ResultsUtils.createParameter; +import static io.qameta.allure.util.ResultsUtils.createTitlePath; +import static io.qameta.allure.util.ResultsUtils.createTitlePathFromSourcePath; import static io.qameta.allure.util.ResultsUtils.getStatus; import static io.qameta.allure.util.ResultsUtils.getStatusDetails; import static io.qameta.allure.util.ResultsUtils.md5; /** - * Allure plugin for Cucumber JVM 4.0. + * Reports Cucumber JVM 4 execution to Allure. + * + *

Add this plugin to the Cucumber runtime so feature, scenario, step, hook, and attachment events are converted into Allure results. Use the default lifecycle for normal runs or pass one explicitly for embedded runners and tests.

*/ -@SuppressWarnings({ - "ClassDataAbstractionCoupling", - "ClassFanOutComplexity", - "MultipleStringLiterals", -}) +@SuppressWarnings( + { + "ClassDataAbstractionCoupling", + "ClassFanOutComplexity", + "MultipleStringLiterals", + "PMD.GodClass", + } +) public class AllureCucumber4Jvm implements ConcurrentEventListener { private static final String COLON = ":"; @@ -108,15 +115,26 @@ public class AllureCucumber4Jvm implements ConcurrentEventListener { private static final String TEXT_PLAIN = "text/plain"; private static final String CUCUMBER_WORKING_DIR = Paths.get("").toUri().getSchemeSpecificPart(); + /** + * Creates an Allure cucumber4 jvm with default configuration. + */ @SuppressWarnings("unused") public AllureCucumber4Jvm() { this(Allure.getLifecycle()); } + /** + * Creates an Allure cucumber4 jvm with the supplied values. + * + * @param lifecycle the Allure lifecycle to use + */ public AllureCucumber4Jvm(final AllureLifecycle lifecycle) { this.lifecycle = lifecycle; } + /** + * {@inheritDoc} + */ @Override public void setEventPublisher(final EventPublisher publisher) { publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler); @@ -144,11 +162,11 @@ private void handleTestCaseStarted(final TestCaseStarted event) { final String name = testCase.getName(); - // the same way full name is generated for // org.junit.platform.engine.support.descriptor.ClasspathResourceSource // to support io.qameta.allure.junitplatform.AllurePostDiscoveryFilter - final String fullName = String.format("%s:%d", + final String fullName = String.format( + "%s:%d", getTestCaseUri(testCase), testCase.getLine() ); @@ -156,20 +174,23 @@ private void handleTestCaseStarted(final TestCaseStarted event) { final String testCaseUuid = testCaseUuids .computeIfAbsent(testCase, tc -> UUID.randomUUID().toString()); + final List titlePath = createTitlePathFromSourcePath(getTestCaseUri(testCase)); + titlePath.addAll(createTitlePath(feature.getName())); + final TestResult result = new TestResult() .setUuid(testCaseUuid) .setTestCaseId(getTestCaseId(testCase)) .setHistoryId(getHistoryId(testCase)) .setFullName(fullName) + .setTitlePath(titlePath) .setName(name) .setLabels(labelBuilder.getScenarioLabels()) .setLinks(labelBuilder.getScenarioLinks()); - final ScenarioDefinition scenarioDefinition = - testSources.getScenarioDefinition( - testCase.getUri(), - testCase.getLine() - ); + final ScenarioDefinition scenarioDefinition = testSources.getScenarioDefinition( + testCase.getUri(), + testCase.getLine() + ); if (scenarioDefinition instanceof ScenarioOutline) { result.setParameters( @@ -209,9 +230,10 @@ private void handleTestCaseFinished(final TestCaseFinished event) { .setMuted(tagParser.isMuted()) .setKnown(tagParser.isKnown()); - lifecycle.updateTestCase(uuid, testResult -> testResult - .setStatus(status) - .setStatusDetails(statusDetails) + lifecycle.updateTestCase( + uuid, testResult -> testResult + .setStatus(status) + .setStatusDetails(statusDetails) ); lifecycle.stopTestCase(uuid); @@ -297,13 +319,13 @@ private void handleStartFixtureHook(final TestCase testCase, return; } - final String containerUuid = hookStepContainerUuid .computeIfAbsent(hook, unused -> UUID.randomUUID().toString()); - lifecycle.startTestContainer(new TestResultContainer() - .setUuid(containerUuid) - .setChildren(Collections.singletonList(uuid)) + lifecycle.startTestContainer( + new TestResultContainer() + .setUuid(containerUuid) + .setChildren(Collections.singletonList(uuid)) ); final FixtureResult hookResult = new FixtureResult() @@ -402,15 +424,14 @@ private Status translateTestCaseStatus(final Result testCaseResult) { } private List getExamplesAsParameters( - final ScenarioOutline scenario, - final TestCase localCurrentTestCase) { - final Optional maybeExample = - scenario.getExamples().stream() - .filter(example -> example.getTableBody().stream() - .anyMatch(row -> row.getLocation().getLine() - == localCurrentTestCase.getLine()) - ) - .findFirst(); + final ScenarioOutline scenario, + final TestCase localCurrentTestCase) { + final Optional maybeExample = scenario.getExamples().stream() + .filter( + example -> example.getTableBody().stream() + .anyMatch(row -> row.getLocation().getLine() == localCurrentTestCase.getLine()) + ) + .findFirst(); if (!maybeExample.isPresent()) { return Collections.emptyList(); @@ -449,8 +470,10 @@ private void createDataTableAttachment(final PickleTable pickleTable) { } final String attachmentSource = lifecycle .prepareAttachment("Data table", "text/tab-separated-values", "csv"); - lifecycle.writeAttachment(attachmentSource, - new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8))); + lifecycle.writeAttachment( + attachmentSource, + new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8)) + ); } private void handleStopHookStep(final Result eventResult, @@ -471,9 +494,10 @@ private void handleStopHookStep(final Result eventResult, final StatusDetails statusDetails = getStatusDetails(eventResult.getError()) .orElseGet(StatusDetails::new); - lifecycle.updateFixture(uuid, result -> result - .setStatus(status) - .setStatusDetails(statusDetails) + lifecycle.updateFixture( + uuid, result -> result + .setStatus(status) + .setStatusDetails(statusDetails) ); lifecycle.stopFixture(uuid); @@ -494,8 +518,7 @@ private void handleStopStep(final TestCase testCase, final Status stepStatus = translateTestCaseStatus(eventResult); - final StatusDetails statusDetails - = eventResult.getStatus() == Result.Type.UNDEFINED + final StatusDetails statusDetails = eventResult.getStatus() == Result.Type.UNDEFINED ? new StatusDetails().setMessage("Undefined Step. Please add step definition") : getStatusDetails(eventResult.getError()) .orElse(new StatusDetails()); diff --git a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/LabelBuilder.java b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/LabelBuilder.java index 0424e6e1..1f54ea8f 100644 --- a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/LabelBuilder.java +++ b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/LabelBuilder.java @@ -49,7 +49,7 @@ /** * Scenario labels and links builder. */ -@SuppressWarnings({"CyclomaticComplexity", "MultipleStringLiterals"}) +@SuppressWarnings({"CyclomaticComplexity", "MultipleStringLiterals", "PMD.CognitiveComplexity"}) final class LabelBuilder { private static final Logger LOGGER = LoggerFactory.getLogger(LabelBuilder.class); private static final String COMPOSITE_TAG_DELIMITER = "="; @@ -116,17 +116,19 @@ final class LabelBuilder { final String uri = scenario.getUri(); scenarioLabels.addAll(ResultsUtils.getProvidedLabels()); - scenarioLabels.addAll(Arrays.asList( - createHostLabel(), - createThreadLabel(), - createFeatureLabel(featureName), - createStoryLabel(scenario.getName()), - createSuiteLabel(featureName), - createTestClassLabel(scenario.getName()), - createFrameworkLabel("cucumber4jvm"), - createLanguageLabel("java"), - createLabel("gherkin_uri", uri) - )); + scenarioLabels.addAll( + Arrays.asList( + createHostLabel(), + createThreadLabel(), + createFeatureLabel(featureName), + createStoryLabel(scenario.getName()), + createSuiteLabel(featureName), + createTestClassLabel(scenario.getName()), + createFrameworkLabel("cucumber4jvm"), + createLanguageLabel("java"), + createLabel("gherkin_uri", uri) + ) + ); featurePackage(uri, featureName) .map(ResultsUtils::createPackageLabel) @@ -159,8 +161,10 @@ private void tryHandleNamedLink(final String tagString) { final String name = tagString.split(COMPOSITE_TAG_DELIMITER)[1]; scenarioLinks.add(ResultsUtils.createLink(null, name, null, type)); } else { - LOGGER.warn("Composite named tag {} does not match regex {}. Skipping", tagString, - namedLinkPatternString); + LOGGER.warn( + "Composite named tag {} does not match regex {}. Skipping", tagString, + namedLinkPatternString + ); } } @@ -178,10 +182,12 @@ private Optional featurePackage(final String uriString, final String fea final String schemeSpecificPart = uri.normalize().getSchemeSpecificPart(); final Stream folders = Stream.of(schemeSpecificPart.replaceAll("\\.", "_").split("/")); final Stream name = Stream.of(featureName); - return Optional.of(Stream.concat(folders, name) - .filter(Objects::nonNull) - .filter(s -> !s.isEmpty()) - .collect(Collectors.joining("."))); + return Optional.of( + Stream.concat(folders, name) + .filter(Objects::nonNull) + .filter(s -> !s.isEmpty()) + .collect(Collectors.joining(".")) + ); } private static Optional safeUri(final String uri) { diff --git a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/TagParser.java b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/TagParser.java index 4839d4ea..cc4b120d 100644 --- a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/TagParser.java +++ b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/TagParser.java @@ -52,9 +52,9 @@ public boolean isKnown() { private boolean getStatusDetailByTag(final String tagName) { return scenario.getTags().stream() - .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName)) - || feature.getTags().stream() - .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName)); + .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName)) + || feature.getTags().stream() + .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName)); } public boolean isResultTag(final PickleTag tag) { diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/AllureCucumber4JvmTest.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/AllureCucumber4JvmTest.java index 85a1d093..dc6b6bb0 100644 --- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/AllureCucumber4JvmTest.java +++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/AllureCucumber4JvmTest.java @@ -62,10 +62,6 @@ import static org.assertj.core.api.Assertions.tuple; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; - -/** - * @author charlie (Dmitry Baev). - */ class AllureCucumber4JvmTest { @AllureFeatures.Base @@ -77,6 +73,8 @@ void shouldSetName() { assertThat(testResults) .extracting(TestResult::getName) .containsExactlyInAnyOrder("Add a to b"); + assertThat(testResults.get(0).getTitlePath()) + .containsExactly("src", "test", "resources", "features", "simple.feature", "Simple feature"); } @AllureFeatures.PassedTests @@ -180,7 +178,7 @@ void shouldSetDescription() { final AllureResults results = runFeature("features/description.feature"); final String expected = "This is description for current feature.\n" - + "It should appear on each scenario in report"; + + "It should appear on each scenario in report"; final List testResults = results.getTestResults(); assertThat(testResults) @@ -197,7 +195,7 @@ void shouldSetScenarioDescription() { final AllureResults results = runFeature("features/scenario_description.feature"); final String expected = "This is description for current feature.\n" - + "It should appear on each scenario in report"; + + "It should appear on each scenario in report"; final List testResults = results.getTestResults(); assertThat(testResults) @@ -235,11 +233,12 @@ void shouldAddDataTableAttachment() { final String attachmentContent = new String(bytes, StandardCharsets.UTF_8); assertThat(attachmentContent) - .isEqualTo(""" - name\tlogin\temail - Viktor\tclicman\tclicman@ya.ru - Viktor2\tclicman2\tclicman2@ya.ru + .isEqualTo( """ + name\tlogin\temail + Viktor\tclicman\tclicman@ya.ru + Viktor2\tclicman2\tclicman2@ya.ru + """ ); } @@ -378,8 +377,14 @@ void shouldAddTags() { @AllureFeatures.Links @ExtendWith(SystemPropertyExtension.class) - @SystemProperty(name = "allure.link.issue.pattern", value = "https://example.org/issue/{}") - @SystemProperty(name = "allure.link.tms.pattern", value = "https://example.org/tms/{}") + @SystemProperty( + name = "allure.link.issue.pattern", + value = "https://example.org/issue/{}" + ) + @SystemProperty( + name = "allure.link.tms.pattern", + value = "https://example.org/tms/{}" + ) @Test void shouldAddLinks() { final AllureResults results = runFeature("features/tags.feature"); @@ -508,8 +513,10 @@ void shouldSupportDryRunForSimpleFeatures() { @AllureFeatures.Base @Test void shouldSupportDryRunForHooks() { - final AllureResults results = runFeature("features/hooks.feature", "--dry-run", "-t", - "@WithHooks or @BeforeHookWithException or @AfterHookWithException"); + final AllureResults results = runFeature( + "features/hooks.feature", "--dry-run", "-t", + "@WithHooks or @BeforeHookWithException or @AfterHookWithException" + ); final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks"); @@ -649,8 +656,10 @@ void shouldProcessScenariosInParallelMode() { @AllureFeatures.Stages @Test void shouldDisplayHooksAsStages() { - final AllureResults results = runFeature("features/hooks.feature", "-t", - "@WithHooks or @BeforeHookWithException or @AfterHookWithException"); + final AllureResults results = runFeature( + "features/hooks.feature", "-t", + "@WithHooks or @BeforeHookWithException or @AfterHookWithException" + ); final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks"); final TestResult tr2 = results.getTestResultByName("Simple scenario with Before hook with Exception"); @@ -688,7 +697,6 @@ void shouldDisplayHooksAsStages() { tuple("Then result is 15", Status.SKIPPED) ); - assertThat(results.getTestResultContainersForTestResult(tr2)) .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) @@ -734,8 +742,14 @@ void shouldHandleAmbigiousStepsExceptions() { ); } - @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) - @SystemProperty(name = "allure.label.x-provided", value = "cucumberjvm5-test-provided") + @ResourceLock( + value = SYSTEM_PROPERTIES, + mode = READ_WRITE + ) + @SystemProperty( + name = "allure.label.x-provided", + value = "cucumberjvm5-test-provided" + ) @Test void shouldSupportProvidedLabels() { final AllureResults results = runFeature("features/simple.feature"); @@ -780,7 +794,10 @@ void shouldSupportRuntimeApiInStepsWhenHooksAreUsed() { ); } - @SystemProperty(name = "cucumber.junit-platform.naming-strategy", value = "long") + @SystemProperty( + name = "cucumber.junit-platform.naming-strategy", + value = "long" + ) @Step private AllureResults runFeature(final String featureResource, final String... moreOptions) { @@ -788,17 +805,18 @@ private AllureResults runFeature(final String featureResource, final AllureCucumber4Jvm cucumber4Jvm = new AllureCucumber4Jvm(lifecycle); final ClassLoader classLoader = currentThread().getContextClassLoader(); final ResourceLoader resourceLoader = new MultiLoader(classLoader); - final List opts = new ArrayList<>(Arrays.asList( - "--glue", "io.qameta.allure.cucumber4jvm.samples", - "--plugin", "null_summary" - )); + final List opts = new ArrayList<>( + Arrays.asList( + "--glue", "io.qameta.allure.cucumber4jvm.samples", + "--plugin", "null_summary" + ) + ); opts.addAll(Arrays.asList(moreOptions)); final FeatureWithLines featureWithLines = FeatureWithLines.parse("src/test/resources/" + featureResource); final RuntimeOptions options = new CommandlineOptionsParser() .parse(opts.toArray(new String[]{})).addFeature(featureWithLines).build(); - final FeaturePathFeatureSupplier supplier - = new FeaturePathFeatureSupplier(new FeatureLoader(resourceLoader), options); + final FeaturePathFeatureSupplier supplier = new FeaturePathFeatureSupplier(new FeatureLoader(resourceLoader), options); final Runtime runtime = Runtime.builder() .withClassLoader(classLoader) @@ -811,5 +829,4 @@ private AllureResults runFeature(final String featureResource, }); } - } diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AmbigiousSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AmbigiousSteps.java index 429e6290..06e9fbee 100644 --- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AmbigiousSteps.java +++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AmbigiousSteps.java @@ -17,10 +17,6 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; - -/** - * @author charlie (Dmitry Baev). - */ public class AmbigiousSteps { @When("^ambigious step (.+)$") @@ -34,7 +30,7 @@ public void ambigious_2() { } @Then("^something bad should happen") - public void somethingBadStep(){ + public void somethingBadStep() { //nothing here } } diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AttachmentSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AttachmentSteps.java index d8307110..75b44942 100644 --- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AttachmentSteps.java +++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AttachmentSteps.java @@ -19,25 +19,21 @@ import io.cucumber.java.Before; import io.cucumber.java.en.Given; -public class AttachmentSteps -{ +public class AttachmentSteps { private Scenario scenario; @Before("@attachments") - public void setup(Scenario scenario) - { + public void setup(Scenario scenario) { this.scenario = scenario; } @Given("step with scenario write") - public void stepWithScenarioWrite() - { + public void stepWithScenarioWrite() { scenario.write("text attachment"); } @Given("step with scenario embed") - public void stepWithScenarioEmbed() - { + public void stepWithScenarioEmbed() { scenario.embed("image attachment".getBytes(), "image/png"); } } diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BackgroundFeatureSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BackgroundFeatureSteps.java index 261680d8..9658b4f9 100644 --- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BackgroundFeatureSteps.java +++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BackgroundFeatureSteps.java @@ -18,10 +18,6 @@ import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; - -/** - * @author charlie (Dmitry Baev). - */ public class BackgroundFeatureSteps { @Given("^cat is sad$") diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BrokenFeatureSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BrokenFeatureSteps.java index 7521a2f4..e10e3cea 100644 --- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BrokenFeatureSteps.java +++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BrokenFeatureSteps.java @@ -16,10 +16,6 @@ package io.qameta.allure.cucumber4jvm.samples; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ public class BrokenFeatureSteps { @Given("^everything is broken$") diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/DatatableFeatureSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/DatatableFeatureSteps.java index 158b5ece..0861d463 100644 --- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/DatatableFeatureSteps.java +++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/DatatableFeatureSteps.java @@ -15,12 +15,8 @@ */ package io.qameta.allure.cucumber4jvm.samples; -import io.cucumber.java.en.Given; import io.cucumber.datatable.DataTable; - -/** - * @author charlie (Dmitry Baev). - */ +import io.cucumber.java.en.Given; @SuppressWarnings("unused") public class DatatableFeatureSteps { diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/HookSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/HookSteps.java index 2aa7f01f..2a13ec8e 100644 --- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/HookSteps.java +++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/HookSteps.java @@ -18,29 +18,25 @@ import io.cucumber.java.After; import io.cucumber.java.Before; import org.assertj.core.api.Assertions; - -/** - * @author letsrokk (Dmitry Mayer). - */ public class HookSteps { @Before("@WithHooks") - public void beforeHook(){ + public void beforeHook() { // nothing } @After("@WithHooks") - public void afterHook(){ + public void afterHook() { // nothing } @Before("@BeforeHookWithException") - public void beforeHookWithException(){ + public void beforeHookWithException() { Assertions.fail("Exception in Hook step"); } @After("@AfterHookWithException") - public void afterHookWithException(){ + public void afterHookWithException() { Assertions.fail("Exception in Hook step"); } diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/PendingSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/PendingSteps.java index 1ab2b3f7..3fa6e3db 100644 --- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/PendingSteps.java +++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/PendingSteps.java @@ -17,10 +17,6 @@ import cucumber.api.PendingException; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ public class PendingSteps { @Given("^step is yet to be implemented$") diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/RuntimeApiSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/RuntimeApiSteps.java index 4c768742..7ce4af5c 100644 --- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/RuntimeApiSteps.java +++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/RuntimeApiSteps.java @@ -20,19 +20,15 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import io.qameta.allure.Allure; - -/** - * @author charlie (Dmitry Baev). - */ public class RuntimeApiSteps { @Before("@beforeScenario") - public void beforeScenario(){ + public void beforeScenario() { // nothing } @Before("@beforeFeature") - public void beforeFeature(){ + public void beforeFeature() { // nothing } diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/SimpleFeatureSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/SimpleFeatureSteps.java index 0d1f7723..e336b5e6 100644 --- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/SimpleFeatureSteps.java +++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/SimpleFeatureSteps.java @@ -19,10 +19,6 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import org.assertj.core.api.Assertions; - -/** - * @author charlie (Dmitry Baev). - */ public class SimpleFeatureSteps { private int a; diff --git a/allure-cucumber4-jvm/src/test/resources/allure.properties b/allure-cucumber4-jvm/src/test/resources/allure.properties index dbfefee4..e026a61f 100644 --- a/allure-cucumber4-jvm/src/test/resources/allure.properties +++ b/allure-cucumber4-jvm/src/test/resources/allure.properties @@ -1,3 +1,4 @@ allure.model.indentOutput=true allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-cucumber4-jvm diff --git a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java index e1e9debb..cdcd95bd 100644 --- a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java +++ b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java @@ -67,18 +67,25 @@ import java.util.stream.Stream; import static io.qameta.allure.util.ResultsUtils.createParameter; +import static io.qameta.allure.util.ResultsUtils.createTitlePath; +import static io.qameta.allure.util.ResultsUtils.createTitlePathFromSourcePath; import static io.qameta.allure.util.ResultsUtils.getStatus; import static io.qameta.allure.util.ResultsUtils.getStatusDetails; import static io.qameta.allure.util.ResultsUtils.md5; /** - * Allure plugin for Cucumber JVM 5.0. + * Reports Cucumber JVM 5 execution to Allure. + * + *

Add this plugin to the Cucumber runtime so feature, scenario, step, hook, and attachment events are converted into Allure results. Use the default lifecycle for normal runs or pass one explicitly for embedded runners and tests.

*/ -@SuppressWarnings({ - "ClassDataAbstractionCoupling", - "ClassFanOutComplexity", - "MultipleStringLiterals", -}) +@SuppressWarnings( + { + "ClassDataAbstractionCoupling", + "ClassFanOutComplexity", + "MultipleStringLiterals", + "PMD.GodClass", + } +) public class AllureCucumber5Jvm implements ConcurrentEventListener { private static final String COLON = ":"; @@ -103,15 +110,26 @@ public class AllureCucumber5Jvm implements ConcurrentEventListener { private static final String TEXT_PLAIN = "text/plain"; private static final String CUCUMBER_WORKING_DIR = Paths.get("").toUri().getSchemeSpecificPart(); + /** + * Creates an Allure cucumber5 jvm with default configuration. + */ @SuppressWarnings("unused") public AllureCucumber5Jvm() { this(Allure.getLifecycle()); } + /** + * Creates an Allure cucumber5 jvm with the supplied values. + * + * @param lifecycle the Allure lifecycle to use + */ public AllureCucumber5Jvm(final AllureLifecycle lifecycle) { this.lifecycle = lifecycle; } + /** + * {@inheritDoc} + */ @Override public void setEventPublisher(final EventPublisher publisher) { publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler); @@ -139,31 +157,34 @@ private void handleTestCaseStarted(final TestCaseStarted event) { final String name = testCase.getName(); - // the same way full name is generated for // org.junit.platform.engine.support.descriptor.ClasspathResourceSource // to support io.qameta.allure.junitplatform.AllurePostDiscoveryFilter - final String fullName = String.format("%s:%d", + final String fullName = String.format( + "%s:%d", getTestCaseUri(testCase), testCase.getLine() ); final String testCaseUuid = testCase.getId().toString(); + final List titlePath = createTitlePathFromSourcePath(getTestCaseUri(testCase)); + titlePath.addAll(createTitlePath(feature.getName())); + final TestResult result = new TestResult() .setUuid(testCaseUuid) .setTestCaseId(getTestCaseId(testCase)) .setHistoryId(getHistoryId(testCase)) .setFullName(fullName) + .setTitlePath(titlePath) .setName(name) .setLabels(labelBuilder.getScenarioLabels()) .setLinks(labelBuilder.getScenarioLinks()); - final ScenarioDefinition scenarioDefinition = - testSources.getScenarioDefinition( - testCase.getUri(), - testCase.getLine() - ); + final ScenarioDefinition scenarioDefinition = testSources.getScenarioDefinition( + testCase.getUri(), + testCase.getLine() + ); if (scenarioDefinition instanceof ScenarioOutline) { result.setParameters( @@ -199,9 +220,10 @@ private void handleTestCaseFinished(final TestCaseFinished event) { .setMuted(tagParser.isMuted()) .setKnown(tagParser.isKnown()); - lifecycle.updateTestCase(uuid, testResult -> testResult - .setStatus(status) - .setStatusDetails(statusDetails) + lifecycle.updateTestCase( + uuid, testResult -> testResult + .setStatus(status) + .setStatusDetails(statusDetails) ); lifecycle.stopTestCase(uuid); @@ -269,9 +291,10 @@ private void handleStartFixtureHook(final TestCase testCase, final String containerUuid = hookStepContainerUuid .computeIfAbsent(hook, unused -> UUID.randomUUID().toString()); - lifecycle.startTestContainer(new TestResultContainer() - .setUuid(containerUuid) - .setChildren(Collections.singletonList(uuid)) + lifecycle.startTestContainer( + new TestResultContainer() + .setUuid(containerUuid) + .setChildren(Collections.singletonList(uuid)) ); final FixtureResult hookResult = new FixtureResult() @@ -354,15 +377,14 @@ private Status translateTestCaseStatus(final Result testCaseResult) { } private List getExamplesAsParameters( - final ScenarioOutline scenario, - final TestCase localCurrentTestCase) { - final Optional maybeExample = - scenario.getExamples().stream() - .filter(example -> example.getTableBody().stream() - .anyMatch(row -> row.getLocation().getLine() - == localCurrentTestCase.getLine()) - ) - .findFirst(); + final ScenarioOutline scenario, + final TestCase localCurrentTestCase) { + final Optional maybeExample = scenario.getExamples().stream() + .filter( + example -> example.getTableBody().stream() + .anyMatch(row -> row.getLocation().getLine() == localCurrentTestCase.getLine()) + ) + .findFirst(); if (!maybeExample.isPresent()) { return Collections.emptyList(); @@ -400,8 +422,10 @@ private void createDataTableAttachment(final DataTableArgument dataTableArgument } final String attachmentSource = lifecycle .prepareAttachment("Data table", "text/tab-separated-values", "csv"); - lifecycle.writeAttachment(attachmentSource, - new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8))); + lifecycle.writeAttachment( + attachmentSource, + new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8)) + ); } private void handleStopHookStep(final Result eventResult, @@ -422,9 +446,10 @@ private void handleStopHookStep(final Result eventResult, final StatusDetails statusDetails = getStatusDetails(eventResult.getError()) .orElseGet(StatusDetails::new); - lifecycle.updateFixture(uuid, result -> result - .setStatus(status) - .setStatusDetails(statusDetails) + lifecycle.updateFixture( + uuid, result -> result + .setStatus(status) + .setStatusDetails(statusDetails) ); lifecycle.stopFixture(uuid); @@ -445,8 +470,7 @@ private void handleStopStep(final TestCase testCase, final Status stepStatus = translateTestCaseStatus(eventResult); - final StatusDetails statusDetails - = eventResult.getStatus() == io.cucumber.plugin.event.Status.UNDEFINED + final StatusDetails statusDetails = eventResult.getStatus() == io.cucumber.plugin.event.Status.UNDEFINED ? new StatusDetails().setMessage("Undefined Step. Please add step definition") : getStatusDetails(eventResult.getError()) .orElse(new StatusDetails()); diff --git a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/LabelBuilder.java b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/LabelBuilder.java index 73321148..31d7d53f 100644 --- a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/LabelBuilder.java +++ b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/LabelBuilder.java @@ -15,8 +15,8 @@ */ package io.qameta.allure.cucumber5jvm; -import io.cucumber.plugin.event.TestCase; import gherkin.ast.Feature; +import io.cucumber.plugin.event.TestCase; import io.qameta.allure.model.Label; import io.qameta.allure.model.Link; import io.qameta.allure.util.ResultsUtils; @@ -48,7 +48,7 @@ /** * Scenario labels and links builder. */ -@SuppressWarnings({"CyclomaticComplexity", "MultipleStringLiterals"}) +@SuppressWarnings({"CyclomaticComplexity", "MultipleStringLiterals", "PMD.CognitiveComplexity"}) class LabelBuilder { private static final Logger LOGGER = LoggerFactory.getLogger(LabelBuilder.class); private static final String COMPOSITE_TAG_DELIMITER = "="; @@ -113,17 +113,19 @@ class LabelBuilder { final URI uri = scenario.getUri(); scenarioLabels.addAll(ResultsUtils.getProvidedLabels()); - scenarioLabels.addAll(Arrays.asList( - createHostLabel(), - createThreadLabel(), - createFeatureLabel(featureName), - createStoryLabel(scenario.getName()), - createSuiteLabel(featureName), - createTestClassLabel(scenario.getName()), - createFrameworkLabel("cucumber5jvm"), - createLanguageLabel("java"), - createLabel("gherkin_uri", uri.toString()) - )); + scenarioLabels.addAll( + Arrays.asList( + createHostLabel(), + createThreadLabel(), + createFeatureLabel(featureName), + createStoryLabel(scenario.getName()), + createSuiteLabel(featureName), + createTestClassLabel(scenario.getName()), + createFrameworkLabel("cucumber5jvm"), + createLanguageLabel("java"), + createLabel("gherkin_uri", uri.toString()) + ) + ); featurePackage(uri.toString(), featureName) .map(ResultsUtils::createPackageLabel) @@ -156,8 +158,10 @@ private void tryHandleNamedLink(final String tagString) { final String name = tagString.split(COMPOSITE_TAG_DELIMITER)[1]; scenarioLinks.add(ResultsUtils.createLink(null, name, null, type)); } else { - LOGGER.warn("Composite named tag {} does not match regex {}. Skipping", tagString, - namedLinkPatternString); + LOGGER.warn( + "Composite named tag {} does not match regex {}. Skipping", tagString, + namedLinkPatternString + ); } } @@ -175,10 +179,12 @@ private Optional featurePackage(final String uriString, final String fea final String schemeSpecificPart = uri.normalize().getSchemeSpecificPart(); final Stream folders = Stream.of(schemeSpecificPart.replaceAll("\\.", "_").split("/")); final Stream name = Stream.of(featureName); - return Optional.of(Stream.concat(folders, name) - .filter(Objects::nonNull) - .filter(s -> !s.isEmpty()) - .collect(Collectors.joining("."))); + return Optional.of( + Stream.concat(folders, name) + .filter(Objects::nonNull) + .filter(s -> !s.isEmpty()) + .collect(Collectors.joining(".")) + ); } private static Optional safeUri(final String uri) { diff --git a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/TagParser.java b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/TagParser.java index 5e980a98..e2e31d22 100644 --- a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/TagParser.java +++ b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/TagParser.java @@ -51,9 +51,9 @@ public boolean isKnown() { private boolean getStatusDetailByTag(final String tagName) { return scenario.getTags().stream() - .anyMatch(tag -> tag.equalsIgnoreCase(tagName)) - || feature.getTags().stream() - .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName)); + .anyMatch(tag -> tag.equalsIgnoreCase(tagName)) + || feature.getTags().stream() + .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName)); } public boolean isResultTag(final String tag) { diff --git a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/testsourcemodel/TestSourcesModel.java b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/testsourcemodel/TestSourcesModel.java index 356783cb..4b390e11 100644 --- a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/testsourcemodel/TestSourcesModel.java +++ b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/testsourcemodel/TestSourcesModel.java @@ -28,24 +28,49 @@ import gherkin.ast.Step; import gherkin.ast.TableRow; import io.cucumber.plugin.event.TestSourceRead; + import java.net.URI; import java.util.HashMap; import java.util.Map; +/** + * Stores parsed Cucumber feature source information. + * + *

Cucumber integrations use this model to map runtime events back to feature, scenario, and step definitions. It helps build accurate Allure names, labels, and step keywords from source files and line numbers.

+ */ public final class TestSourcesModel { private final Map pathToReadEventMap = new HashMap<>(); private final Map pathToAstMap = new HashMap<>(); private final Map> pathToNodeMap = new HashMap<>(); + /** + * Returns the scenario definition. + * + * @param astNode the Cucumber AST node to inspect + * @return the scenario definition + */ public static ScenarioDefinition getScenarioDefinition(final AstNode astNode) { - return astNode.node instanceof ScenarioDefinition ? (ScenarioDefinition) astNode.node + return astNode.node instanceof ScenarioDefinition + ? (ScenarioDefinition) astNode.node : (ScenarioDefinition) astNode.parent.parent.node; } + /** + * Adds the test source read event. + * + * @param path the path to read from or write to + * @param event the framework event to process + */ public void addTestSourceReadEvent(final URI path, final TestSourceRead event) { pathToReadEventMap.put(path, event); } + /** + * Returns the feature. + * + * @param path the path to read from or write to + * @return the feature + */ public Feature getFeature(final URI path) { if (!pathToAstMap.containsKey(path)) { parseGherkinSource(path); @@ -56,6 +81,13 @@ public Feature getFeature(final URI path) { return null; } + /** + * Returns the ast node. + * + * @param path the path to read from or write to + * @param line the source line number to resolve + * @return the ast node + */ public AstNode getAstNode(final URI path, final int line) { if (!pathToNodeMap.containsKey(path)) { parseGherkinSource(path); @@ -73,8 +105,10 @@ private void parseGherkinSource(final URI path) { final Parser parser = new Parser<>(new AstBuilder()); final TokenMatcher matcher = new TokenMatcher(); try { - final GherkinDocument gherkinDocument = parser.parse(pathToReadEventMap.get(path).getSource(), - matcher); + final GherkinDocument gherkinDocument = parser.parse( + pathToReadEventMap.get(path).getSource(), + matcher + ); pathToAstMap.put(path, gherkinDocument); final Map nodeMap = new HashMap<>(); final AstNode currentParent = new AstNode(gherkinDocument.getFeature(), null); @@ -83,8 +117,10 @@ private void parseGherkinSource(final URI path) { } pathToNodeMap.put(path, nodeMap); } catch (ParserException e) { - throw new IllegalStateException("You are using a plugin that only supports till Gherkin 5.\n" - + "Please check if the Gherkin provided follows the standard of Gherkin 5\n", e + throw new IllegalStateException( + "You are using a plugin that only supports till Gherkin 5.\n" + + "Please check if the Gherkin provided follows the standard of Gherkin 5\n", + e ); } } diff --git a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/testsourcemodel/TestSourcesModelProxy.java b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/testsourcemodel/TestSourcesModelProxy.java index 8a1bbe7c..ec7415ee 100644 --- a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/testsourcemodel/TestSourcesModelProxy.java +++ b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/testsourcemodel/TestSourcesModelProxy.java @@ -25,28 +25,62 @@ import java.util.HashMap; import java.util.Map; +/** + * Compatibility proxy around Cucumber feature source storage. + * + *

The proxy hides version-specific Cucumber source model APIs from the reporting plugin. Integrations use it to add source-read events and resolve feature, scenario, and step keyword metadata during execution.

+ */ public class TestSourcesModelProxy { private final Map pathToReadEventMap = new HashMap<>(); private final TestSourcesModel testSources; + /** + * Creates a test sources model proxy with default configuration. + */ public TestSourcesModelProxy() { this.testSources = new TestSourcesModel(); } + /** + * Adds the test source read event. + * + * @param path the path to read from or write to + * @param event the framework event to process + */ public void addTestSourceReadEvent(final URI path, final TestSourceRead event) { this.pathToReadEventMap.put(path, event); testSources.addTestSourceReadEvent(path, event); } + /** + * Returns the feature. + * + * @param path the path to read from or write to + * @return the feature + */ public Feature getFeature(final URI path) { return testSources.getFeature(path); } + /** + * Returns the scenario definition. + * + * @param path the path to read from or write to + * @param line the source line number to resolve + * @return the scenario definition + */ public ScenarioDefinition getScenarioDefinition(final URI path, final int line) { return testSources.getScenarioDefinition(testSources.getAstNode(path, line)); } + /** + * Returns the keyword from source. + * + * @param uri the feature file URI + * @param stepLine the feature file line number of the step + * @return the keyword from source + */ public String getKeywordFromSource(final URI uri, final int stepLine) { return this.getKeywordFromSourceInternal(uri, stepLine); } diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/AllureCucumber5JvmTest.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/AllureCucumber5JvmTest.java index 7456106a..d70b8741 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/AllureCucumber5JvmTest.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/AllureCucumber5JvmTest.java @@ -65,10 +65,6 @@ import static org.assertj.core.api.Assertions.tuple; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; - -/** - * @author charlie (Dmitry Baev). - */ class AllureCucumber5JvmTest { @AllureFeatures.Base @@ -80,6 +76,8 @@ void shouldSetName() { assertThat(testResults) .extracting(TestResult::getName) .containsExactlyInAnyOrder("Add a to b"); + assertThat(testResults.get(0).getTitlePath()) + .containsExactly("src", "test", "resources", "features", "simple.feature", "Simple feature"); } @AllureFeatures.PassedTests @@ -183,7 +181,7 @@ void shouldSetDescription() { final AllureResults results = runFeature("features/description.feature"); final String expected = "This is description for current feature.\n" - + "It should appear on each scenario in report"; + + "It should appear on each scenario in report"; final List testResults = results.getTestResults(); assertThat(testResults) @@ -200,7 +198,7 @@ void shouldSetScenarioDescription() { final AllureResults results = runFeature("features/scenario_description.feature"); final String expected = "This is description for current feature.\n" - + "It should appear on each scenario in report"; + + "It should appear on each scenario in report"; final List testResults = results.getTestResults(); assertThat(testResults) @@ -238,11 +236,12 @@ void shouldAddDataTableAttachment() { final String attachmentContent = new String(bytes, StandardCharsets.UTF_8); assertThat(attachmentContent) - .isEqualTo(""" - name\tlogin\temail - Viktor\tclicman\tclicman@ya.ru - Viktor2\tclicman2\tclicman2@ya.ru + .isEqualTo( """ + name\tlogin\temail + Viktor\tclicman\tclicman@ya.ru + Viktor2\tclicman2\tclicman2@ya.ru + """ ); } @@ -381,8 +380,14 @@ void shouldAddTags() { @AllureFeatures.Links @ExtendWith(SystemPropertyExtension.class) - @SystemProperty(name = "allure.link.issue.pattern", value = "https://example.org/issue/{}") - @SystemProperty(name = "allure.link.tms.pattern", value = "https://example.org/tms/{}") + @SystemProperty( + name = "allure.link.issue.pattern", + value = "https://example.org/issue/{}" + ) + @SystemProperty( + name = "allure.link.tms.pattern", + value = "https://example.org/tms/{}" + ) @Test void shouldAddLinks() { final AllureResults results = runFeature("features/tags.feature"); @@ -511,8 +516,10 @@ void shouldSupportDryRunForSimpleFeatures() { @AllureFeatures.Base @Test void shouldSupportDryRunForHooks() { - final AllureResults results = runFeature("features/hooks.feature", "--dry-run", "-t", - "@WithHooks or @BeforeHookWithException or @AfterHookWithException"); + final AllureResults results = runFeature( + "features/hooks.feature", "--dry-run", "-t", + "@WithHooks or @BeforeHookWithException or @AfterHookWithException" + ); final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks"); @@ -652,8 +659,10 @@ void shouldProcessScenariosInParallelMode() { @AllureFeatures.Stages @Test void shouldDisplayHooksAsStages() { - final AllureResults results = runFeature("features/hooks.feature", "-t", - "@WithHooks or @BeforeHookWithException or @AfterHookWithException"); + final AllureResults results = runFeature( + "features/hooks.feature", "-t", + "@WithHooks or @BeforeHookWithException or @AfterHookWithException" + ); final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks"); final TestResult tr2 = results.getTestResultByName("Simple scenario with Before hook with Exception"); @@ -691,7 +700,6 @@ void shouldDisplayHooksAsStages() { tuple("Then result is 15", Status.SKIPPED) ); - assertThat(results.getTestResultContainersForTestResult(tr2)) .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) @@ -737,8 +745,14 @@ void shouldHandleAmbigiousStepsExceptions() { ); } - @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) - @SystemProperty(name = "allure.label.x-provided", value = "cucumberjvm5-test-provided") + @ResourceLock( + value = SYSTEM_PROPERTIES, + mode = READ_WRITE + ) + @SystemProperty( + name = "allure.label.x-provided", + value = "cucumberjvm5-test-provided" + ) @Test void shouldSupportProvidedLabels() { final AllureResults results = runFeature("features/simple.feature"); @@ -783,17 +797,22 @@ void shouldSupportRuntimeApiInStepsWhenHooksAreUsed() { ); } - @SystemProperty(name = "cucumber.junit-platform.naming-strategy", value = "long") + @SystemProperty( + name = "cucumber.junit-platform.naming-strategy", + value = "long" + ) @Step private AllureResults runFeature(final String featureResource, final String... moreOptions) { return RunUtils.runTests(lifecycle -> { final AllureCucumber5Jvm cucumber5jvm = new AllureCucumber5Jvm(lifecycle); final Supplier classLoader = ClassLoaders::getDefaultClassLoader; - final List opts = new ArrayList<>(Arrays.asList( - "--glue", "io.qameta.allure.cucumber5jvm.samples", - "--plugin", "null_summary" - )); + final List opts = new ArrayList<>( + Arrays.asList( + "--glue", "io.qameta.allure.cucumber5jvm.samples", + "--plugin", "null_summary" + ) + ); opts.addAll(Arrays.asList(moreOptions)); final FeatureWithLines featureWithLines = FeatureWithLines.parse("src/test/resources/" + featureResource); final RuntimeOptions options = new CommandlineOptionsParser() @@ -801,8 +820,7 @@ private AllureResults runFeature(final String featureResource, final EventBus bus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID); final FeatureParser parser = new FeatureParser(bus::generateId); - final FeaturePathFeatureSupplier supplier - = new FeaturePathFeatureSupplier(classLoader, options, parser); + final FeaturePathFeatureSupplier supplier = new FeaturePathFeatureSupplier(classLoader, options, parser); final Runtime runtime = Runtime.builder() .withClassLoader(classLoader) @@ -815,5 +833,4 @@ private AllureResults runFeature(final String featureResource, }); } - } diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/AmbigiousSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/AmbigiousSteps.java index 3e605bb6..eca7e0f3 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/AmbigiousSteps.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/AmbigiousSteps.java @@ -17,10 +17,6 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; - -/** - * @author charlie (Dmitry Baev). - */ public class AmbigiousSteps { @When("^ambigious step (.+)$") @@ -34,7 +30,7 @@ public void ambigious_2() { } @Then("^something bad should happen") - public void somethingBadStep(){ + public void somethingBadStep() { //nothing here } } diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/AttachmentSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/AttachmentSteps.java index a4489b08..1310cb9d 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/AttachmentSteps.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/AttachmentSteps.java @@ -15,29 +15,25 @@ */ package io.qameta.allure.cucumber5jvm.samples; -import io.cucumber.java.Scenario; import io.cucumber.java.Before; +import io.cucumber.java.Scenario; import io.cucumber.java.en.Given; -public class AttachmentSteps -{ +public class AttachmentSteps { private Scenario scenario; @Before("@attachments") - public void setup(Scenario scenario) - { + public void setup(Scenario scenario) { this.scenario = scenario; } @Given("step with scenario write") - public void stepWithScenarioWrite() - { + public void stepWithScenarioWrite() { scenario.write("text attachment"); } @Given("step with scenario embed") - public void stepWithScenarioEmbed() - { - scenario.embed("image attachment".getBytes(), "image/png","ImageAttachment"); + public void stepWithScenarioEmbed() { + scenario.embed("image attachment".getBytes(), "image/png", "ImageAttachment"); } } diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/BackgroundFeatureSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/BackgroundFeatureSteps.java index 8a8a75e4..57cd03e2 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/BackgroundFeatureSteps.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/BackgroundFeatureSteps.java @@ -18,10 +18,6 @@ import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; - -/** - * @author charlie (Dmitry Baev). - */ public class BackgroundFeatureSteps { @Given("^cat is sad$") diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/BrokenFeatureSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/BrokenFeatureSteps.java index 900968ad..2fde1f09 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/BrokenFeatureSteps.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/BrokenFeatureSteps.java @@ -16,10 +16,6 @@ package io.qameta.allure.cucumber5jvm.samples; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ public class BrokenFeatureSteps { @Given("^everything is broken$") diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/DatatableFeatureSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/DatatableFeatureSteps.java index 98f22865..db70f804 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/DatatableFeatureSteps.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/DatatableFeatureSteps.java @@ -17,10 +17,6 @@ import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ @SuppressWarnings("unused") public class DatatableFeatureSteps { diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/HookSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/HookSteps.java index 775d9086..786c53c2 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/HookSteps.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/HookSteps.java @@ -18,29 +18,25 @@ import io.cucumber.java.After; import io.cucumber.java.Before; import org.assertj.core.api.Assertions; - -/** - * @author letsrokk (Dmitry Mayer). - */ public class HookSteps { @Before("@WithHooks") - public void beforeHook(){ + public void beforeHook() { // nothing } @After("@WithHooks") - public void afterHook(){ + public void afterHook() { // nothing } @Before("@BeforeHookWithException") - public void beforeHookWithException(){ + public void beforeHookWithException() { Assertions.fail("Exception in Hook step"); } @After("@AfterHookWithException") - public void afterHookWithException(){ + public void afterHookWithException() { Assertions.fail("Exception in Hook step"); } diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/PendingSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/PendingSteps.java index 2b09a1f5..65a521e4 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/PendingSteps.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/PendingSteps.java @@ -17,10 +17,6 @@ import io.cucumber.java.PendingException; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ public class PendingSteps { @Given("^step is yet to be implemented$") diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/RuntimeApiSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/RuntimeApiSteps.java index 581b2afe..adc9e2fa 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/RuntimeApiSteps.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/RuntimeApiSteps.java @@ -20,19 +20,15 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import io.qameta.allure.Allure; - -/** - * @author charlie (Dmitry Baev). - */ public class RuntimeApiSteps { @Before("@beforeScenario") - public void beforeScenario(){ + public void beforeScenario() { // nothing } @Before("@beforeFeature") - public void beforeFeature(){ + public void beforeFeature() { // nothing } diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/SimpleFeatureSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/SimpleFeatureSteps.java index 46b0e946..4f489a54 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/SimpleFeatureSteps.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/SimpleFeatureSteps.java @@ -19,10 +19,6 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import org.assertj.core.api.Assertions; - -/** - * @author charlie (Dmitry Baev). - */ public class SimpleFeatureSteps { private int a; diff --git a/allure-cucumber5-jvm/src/test/resources/allure.properties b/allure-cucumber5-jvm/src/test/resources/allure.properties index dbfefee4..8e296072 100644 --- a/allure-cucumber5-jvm/src/test/resources/allure.properties +++ b/allure-cucumber5-jvm/src/test/resources/allure.properties @@ -1,3 +1,4 @@ allure.model.indentOutput=true allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-cucumber5-jvm diff --git a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java index e8c71e51..148dfe4c 100644 --- a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java +++ b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java @@ -65,20 +65,27 @@ import java.util.stream.Stream; import static io.qameta.allure.util.ResultsUtils.createParameter; +import static io.qameta.allure.util.ResultsUtils.createTitlePath; +import static io.qameta.allure.util.ResultsUtils.createTitlePathFromSourcePath; import static io.qameta.allure.util.ResultsUtils.getStatus; import static io.qameta.allure.util.ResultsUtils.getStatusDetails; import static io.qameta.allure.util.ResultsUtils.md5; /** - * Allure plugin for Cucumber JVM 6.0. + * Reports Cucumber JVM 6 execution to Allure. + * + *

Add this plugin to the Cucumber runtime so feature, scenario, step, hook, and attachment events are converted into Allure results. Use the default lifecycle for normal runs or pass one explicitly for embedded runners and tests.

*/ -@SuppressWarnings({ - "ClassDataAbstractionCoupling", - "ClassFanOutComplexity", -}) +@SuppressWarnings( + { + "ClassDataAbstractionCoupling", + "ClassFanOutComplexity", + } +) public class AllureCucumber6Jvm implements ConcurrentEventListener { private static final String COLON = ":"; + private static final String NEW_LINE = "\n"; private final AllureLifecycle lifecycle; @@ -98,15 +105,26 @@ public class AllureCucumber6Jvm implements ConcurrentEventListener { private static final String TEXT_PLAIN = "text/plain"; private static final String CUCUMBER_WORKING_DIR = Paths.get("").toUri().getSchemeSpecificPart(); + /** + * Creates an Allure cucumber6 jvm with default configuration. + */ @SuppressWarnings("unused") public AllureCucumber6Jvm() { this(Allure.getLifecycle()); } + /** + * Creates an Allure cucumber6 jvm with the supplied values. + * + * @param lifecycle the Allure lifecycle to use + */ public AllureCucumber6Jvm(final AllureLifecycle lifecycle) { this.lifecycle = lifecycle; } + /** + * {@inheritDoc} + */ @Override public void setEventPublisher(final EventPublisher publisher) { publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler); @@ -134,31 +152,34 @@ private void handleTestCaseStarted(final TestCaseStarted event) { final String name = testCase.getName(); - // the same way full name is generated for // org.junit.platform.engine.support.descriptor.ClasspathResourceSource // to support io.qameta.allure.junitplatform.AllurePostDiscoveryFilter - final String fullName = String.format("%s:%d", + final String fullName = String.format( + "%s:%d", getTestCaseUri(testCase), testCase.getLocation().getLine() ); final String testCaseUuid = testCase.getId().toString(); + final List titlePath = createTitlePathFromSourcePath(getTestCaseUri(testCase)); + titlePath.addAll(createTitlePath(feature.getName())); + final TestResult result = new TestResult() .setUuid(testCaseUuid) .setTestCaseId(getTestCaseId(testCase)) .setHistoryId(getHistoryId(testCase)) .setFullName(fullName) + .setTitlePath(titlePath) .setName(name) .setLabels(labelBuilder.getScenarioLabels()) .setLinks(labelBuilder.getScenarioLinks()); - final Scenario scenarioDefinition = - testSources.getScenarioDefinition( - testCase.getUri(), - testCase.getLocation().getLine() - ); + final Scenario scenarioDefinition = testSources.getScenarioDefinition( + testCase.getUri(), + testCase.getLocation().getLine() + ); if (scenarioDefinition.getExamplesList() != null) { result.setParameters( @@ -169,7 +190,7 @@ private void handleTestCaseStarted(final TestCaseStarted event) { final String description = Stream.of(feature.getDescription(), scenarioDefinition.getDescription()) .filter(Objects::nonNull) .filter(s -> !s.isEmpty()) - .collect(Collectors.joining("\n")); + .collect(Collectors.joining(NEW_LINE)); if (!description.isEmpty()) { result.setDescription(description); @@ -194,9 +215,10 @@ private void handleTestCaseFinished(final TestCaseFinished event) { .setMuted(tagParser.isMuted()) .setKnown(tagParser.isKnown()); - lifecycle.updateTestCase(uuid, testResult -> testResult - .setStatus(status) - .setStatusDetails(statusDetails) + lifecycle.updateTestCase( + uuid, testResult -> testResult + .setStatus(status) + .setStatusDetails(statusDetails) ); lifecycle.stopTestCase(uuid); @@ -256,9 +278,10 @@ private void handleStartFixtureHook(final TestCase testCase, final String containerUuid = hookStepContainerUuid .computeIfAbsent(hookId, unused -> UUID.randomUUID().toString()); - lifecycle.startTestContainer(new TestResultContainer() - .setUuid(containerUuid) - .setChildren(Collections.singletonList(uuid)) + lifecycle.startTestContainer( + new TestResultContainer() + .setUuid(containerUuid) + .setChildren(Collections.singletonList(uuid)) ); final FixtureResult hookResult = new FixtureResult() @@ -339,15 +362,14 @@ private Status translateTestCaseStatus(final Result testCaseResult) { } private List getExamplesAsParameters( - final Scenario scenario, - final TestCase localCurrentTestCase) { - final Optional maybeExample = - scenario.getExamplesList().stream() - .filter(example -> example.getTableBodyList().stream() - .anyMatch(row -> row.getLocation().getLine() - == localCurrentTestCase.getLocation().getLine()) - ) - .findFirst(); + final Scenario scenario, + final TestCase localCurrentTestCase) { + final Optional maybeExample = scenario.getExamplesList().stream() + .filter( + example -> example.getTableBodyList().stream() + .anyMatch(row -> row.getLocation().getLine() == localCurrentTestCase.getLocation().getLine()) + ) + .findFirst(); if (!maybeExample.isPresent()) { return Collections.emptyList(); @@ -379,14 +401,16 @@ private void createDataTableAttachment(final DataTableArgument dataTableArgument final StringBuilder dataTableCsv = new StringBuilder(); for (List columns : rowsInTable) { if (!columns.isEmpty()) { - final String rowValue = columns.stream().collect(Collectors.joining("\t", "", "\n")); + final String rowValue = columns.stream().collect(Collectors.joining("\t", "", NEW_LINE)); dataTableCsv.append(rowValue); } } final String attachmentSource = lifecycle .prepareAttachment("Data table", "text/tab-separated-values", "csv"); - lifecycle.writeAttachment(attachmentSource, - new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8))); + lifecycle.writeAttachment( + attachmentSource, + new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8)) + ); } private void handleStopHookStep(final Result eventResult, @@ -403,9 +427,10 @@ private void handleStopHookStep(final Result eventResult, final StatusDetails statusDetails = getStatusDetails(eventResult.getError()) .orElseGet(StatusDetails::new); - lifecycle.updateFixture(uuid, result -> result - .setStatus(status) - .setStatusDetails(statusDetails) + lifecycle.updateFixture( + uuid, result -> result + .setStatus(status) + .setStatusDetails(statusDetails) ); lifecycle.stopFixture(uuid); @@ -420,8 +445,7 @@ private void handleStopStep(final TestCase testCase, final Status stepStatus = translateTestCaseStatus(eventResult); - final StatusDetails statusDetails - = eventResult.getStatus() == io.cucumber.plugin.event.Status.UNDEFINED + final StatusDetails statusDetails = eventResult.getStatus() == io.cucumber.plugin.event.Status.UNDEFINED ? new StatusDetails().setMessage("Undefined Step. Please add step definition") : getStatusDetails(eventResult.getError()) .orElse(new StatusDetails()); diff --git a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/LabelBuilder.java b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/LabelBuilder.java index 9684b9d5..794b78af 100644 --- a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/LabelBuilder.java +++ b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/LabelBuilder.java @@ -48,7 +48,7 @@ /** * Scenario labels and links builder. */ -@SuppressWarnings({"CyclomaticComplexity", "MultipleStringLiterals"}) +@SuppressWarnings({"CyclomaticComplexity", "MultipleStringLiterals", "PMD.CognitiveComplexity"}) class LabelBuilder { private static final Logger LOGGER = LoggerFactory.getLogger(LabelBuilder.class); private static final String COMPOSITE_TAG_DELIMITER = "="; @@ -113,17 +113,19 @@ class LabelBuilder { final URI uri = scenario.getUri(); scenarioLabels.addAll(ResultsUtils.getProvidedLabels()); - scenarioLabels.addAll(Arrays.asList( - createHostLabel(), - createThreadLabel(), - createFeatureLabel(featureName), - createStoryLabel(scenario.getName()), - createSuiteLabel(featureName), - createTestClassLabel(scenario.getName()), - createFrameworkLabel("cucumber6jvm"), - createLanguageLabel("java"), - createLabel("gherkin_uri", uri.toString()) - )); + scenarioLabels.addAll( + Arrays.asList( + createHostLabel(), + createThreadLabel(), + createFeatureLabel(featureName), + createStoryLabel(scenario.getName()), + createSuiteLabel(featureName), + createTestClassLabel(scenario.getName()), + createFrameworkLabel("cucumber6jvm"), + createLanguageLabel("java"), + createLabel("gherkin_uri", uri.toString()) + ) + ); featurePackage(uri.toString(), featureName) .map(ResultsUtils::createPackageLabel) @@ -156,8 +158,10 @@ private void tryHandleNamedLink(final String tagString) { final String name = tagString.split(COMPOSITE_TAG_DELIMITER)[1]; scenarioLinks.add(ResultsUtils.createLink(null, name, null, type)); } else { - LOGGER.warn("Composite named tag {} does not match regex {}. Skipping", tagString, - namedLinkPatternString); + LOGGER.warn( + "Composite named tag {} does not match regex {}. Skipping", tagString, + namedLinkPatternString + ); } } @@ -175,10 +179,12 @@ private Optional featurePackage(final String uriString, final String fea final String schemeSpecificPart = uri.normalize().getSchemeSpecificPart(); final Stream folders = Stream.of(schemeSpecificPart.replaceAll("\\.", "_").split("/")); final Stream name = Stream.of(featureName); - return Optional.of(Stream.concat(folders, name) - .filter(Objects::nonNull) - .filter(s -> !s.isEmpty()) - .collect(Collectors.joining("."))); + return Optional.of( + Stream.concat(folders, name) + .filter(Objects::nonNull) + .filter(s -> !s.isEmpty()) + .collect(Collectors.joining(".")) + ); } private static Optional safeUri(final String uri) { diff --git a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/TagParser.java b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/TagParser.java index 3d31fdef..66644299 100644 --- a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/TagParser.java +++ b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/TagParser.java @@ -51,9 +51,9 @@ public boolean isKnown() { private boolean getStatusDetailByTag(final String tagName) { return scenario.getTags().stream() - .anyMatch(tag -> tag.equalsIgnoreCase(tagName)) - || feature.getTagsList().stream() - .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName)); + .anyMatch(tag -> tag.equalsIgnoreCase(tagName)) + || feature.getTagsList().stream() + .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName)); } public boolean isResultTag(final String tag) { diff --git a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/testsourcemodel/TestSourcesModel.java b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/testsourcemodel/TestSourcesModel.java index 2a338e95..2fa406ea 100644 --- a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/testsourcemodel/TestSourcesModel.java +++ b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/testsourcemodel/TestSourcesModel.java @@ -73,14 +73,16 @@ private void parseGherkinSource(final URI path) { final String source = pathToReadEventMap.get(path).getSource(); final List sources = singletonList( - makeSourceEnvelope(source, path.toString())); + makeSourceEnvelope(source, path.toString()) + ); final List envelopes = Gherkin.fromSources( sources, true, true, true, - () -> String.valueOf(UUID.randomUUID())).collect(toList()); + () -> String.valueOf(UUID.randomUUID()) + ).collect(toList()); final GherkinDocument gherkinDocument = envelopes.stream() .filter(Messages.Envelope::hasGherkinDocument) @@ -99,7 +101,7 @@ private void parseGherkinSource(final URI path) { } private void processFeatureDefinition( - final Map nodeMap, final FeatureChild child, final AstNode currentParent) { + final Map nodeMap, final FeatureChild child, final AstNode currentParent) { if (child.hasBackground()) { processBackgroundDefinition(nodeMap, child.getBackground(), currentParent); } else if (child.hasScenario()) { @@ -114,8 +116,7 @@ private void processFeatureDefinition( } private void processBackgroundDefinition( - final Map nodeMap, final Background background, final AstNode currentParent - ) { + final Map nodeMap, final Background background, final AstNode currentParent) { final AstNode childNode = createAstNode(background, currentParent); nodeMap.put(background.getLocation().getLine(), childNode); for (Step step : background.getStepsList()) { @@ -124,7 +125,7 @@ private void processBackgroundDefinition( } private void processScenarioDefinition( - final Map nodeMap, final Scenario child, final AstNode currentParent) { + final Map nodeMap, final Scenario child, final AstNode currentParent) { final AstNode childNode = createAstNode(child, currentParent); nodeMap.put(child.getLocation().getLine(), childNode); for (Step step : child.getStepsList()) { @@ -136,7 +137,7 @@ private void processScenarioDefinition( } private void processRuleDefinition( - final Map nodeMap, final RuleChild child, final AstNode currentParent) { + final Map nodeMap, final RuleChild child, final AstNode currentParent) { if (child.hasBackground()) { processBackgroundDefinition(nodeMap, child.getBackground(), currentParent); } else if (child.hasScenario()) { @@ -145,8 +146,7 @@ private void processRuleDefinition( } private void processScenarioOutlineExamples( - final Map nodeMap, final Scenario scenarioOutline, final AstNode parent - ) { + final Map nodeMap, final Scenario scenarioOutline, final AstNode parent) { for (Examples examples : scenarioOutline.getExamplesList()) { final AstNode examplesNode = createAstNode(examples, parent); final TableRow headerRow = examples.getTableHeader(); diff --git a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/testsourcemodel/TestSourcesModelProxy.java b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/testsourcemodel/TestSourcesModelProxy.java index 36096f18..2e88546c 100644 --- a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/testsourcemodel/TestSourcesModelProxy.java +++ b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/testsourcemodel/TestSourcesModelProxy.java @@ -25,28 +25,62 @@ import java.util.HashMap; import java.util.Map; +/** + * Compatibility proxy around Cucumber feature source storage. + * + *

The proxy hides version-specific Cucumber source model APIs from the reporting plugin. Integrations use it to add source-read events and resolve feature, scenario, and step keyword metadata during execution.

+ */ public class TestSourcesModelProxy { private final Map pathToReadEventMap = new HashMap<>(); private final TestSourcesModel testSources; + /** + * Creates a test sources model proxy with default configuration. + */ public TestSourcesModelProxy() { this.testSources = new TestSourcesModel(); } + /** + * Adds the test source read event. + * + * @param path the path to read from or write to + * @param event the framework event to process + */ public void addTestSourceReadEvent(final URI path, final TestSourceRead event) { this.pathToReadEventMap.put(path, event); testSources.addTestSourceReadEvent(path, event); } + /** + * Returns the feature. + * + * @param path the path to read from or write to + * @return the feature + */ public Feature getFeature(final URI path) { return testSources.getFeature(path); } + /** + * Returns the scenario definition. + * + * @param path the path to read from or write to + * @param line the source line number to resolve + * @return the scenario definition + */ public Scenario getScenarioDefinition(final URI path, final int line) { return TestSourcesModel.getScenarioDefinition(testSources.getAstNode(path, line)); } + /** + * Returns the keyword from source. + * + * @param uri the feature file URI + * @param stepLine the feature file line number of the step + * @return the keyword from source + */ public String getKeywordFromSource(final URI uri, final int stepLine) { return this.getKeywordFromSourceInternal(uri, stepLine); } diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java index 75cc8495..3cb35169 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java @@ -65,10 +65,6 @@ import static org.assertj.core.api.Assertions.tuple; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; - -/** - * @author charlie (Dmitry Baev). - */ class AllureCucumber6JvmTest { @AllureFeatures.Base @@ -80,6 +76,8 @@ void shouldSetName() { assertThat(testResults) .extracting(TestResult::getName) .containsExactlyInAnyOrder("Add a to b"); + assertThat(testResults.get(0).getTitlePath()) + .containsExactly("src", "test", "resources", "features", "simple.feature", "Simple feature"); } @AllureFeatures.PassedTests @@ -183,7 +181,7 @@ void shouldSetDescription() { final AllureResults results = runFeature("features/description.feature"); final String expected = "This is description for current feature.\n" - + "It should appear on each scenario in report"; + + "It should appear on each scenario in report"; final List testResults = results.getTestResults(); assertThat(testResults) @@ -200,7 +198,7 @@ void shouldSetScenarioDescription() { final AllureResults results = runFeature("features/scenario_description.feature"); final String expected = "This is description for current feature.\n" - + "It should appear on each scenario in report"; + + "It should appear on each scenario in report"; final List testResults = results.getTestResults(); assertThat(testResults) @@ -238,11 +236,12 @@ void shouldAddDataTableAttachment() { final String attachmentContent = new String(bytes, StandardCharsets.UTF_8); assertThat(attachmentContent) - .isEqualTo(""" - name\tlogin\temail - Viktor\tclicman\tclicman@ya.ru - Viktor2\tclicman2\tclicman2@ya.ru + .isEqualTo( """ + name\tlogin\temail + Viktor\tclicman\tclicman@ya.ru + Viktor2\tclicman2\tclicman2@ya.ru + """ ); } @@ -381,8 +380,14 @@ void shouldAddTags() { @AllureFeatures.Links @ExtendWith(SystemPropertyExtension.class) - @SystemProperty(name = "allure.link.issue.pattern", value = "https://example.org/issue/{}") - @SystemProperty(name = "allure.link.tms.pattern", value = "https://example.org/tms/{}") + @SystemProperty( + name = "allure.link.issue.pattern", + value = "https://example.org/issue/{}" + ) + @SystemProperty( + name = "allure.link.tms.pattern", + value = "https://example.org/tms/{}" + ) @Test void shouldAddLinks() { final AllureResults results = runFeature("features/tags.feature"); @@ -511,8 +516,10 @@ void shouldSupportDryRunForSimpleFeatures() { @AllureFeatures.Base @Test void shouldSupportDryRunForHooks() { - final AllureResults results = runFeature("features/hooks.feature", "--dry-run", "-t", - "@WithHooks or @BeforeHookWithException or @AfterHookWithException"); + final AllureResults results = runFeature( + "features/hooks.feature", "--dry-run", "-t", + "@WithHooks or @BeforeHookWithException or @AfterHookWithException" + ); final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks"); @@ -652,8 +659,10 @@ void shouldProcessScenariosInParallelMode() { @AllureFeatures.Stages @Test void shouldDisplayHooksAsStages() { - final AllureResults results = runFeature("features/hooks.feature", "-t", - "@WithHooks or @BeforeHookWithException or @AfterHookWithException"); + final AllureResults results = runFeature( + "features/hooks.feature", "-t", + "@WithHooks or @BeforeHookWithException or @AfterHookWithException" + ); final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks"); final TestResult tr2 = results.getTestResultByName("Simple scenario with Before hook with Exception"); @@ -691,7 +700,6 @@ void shouldDisplayHooksAsStages() { tuple("Then result is 15", Status.SKIPPED) ); - assertThat(results.getTestResultContainersForTestResult(tr2)) .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) @@ -737,8 +745,14 @@ void shouldHandleAmbigiousStepsExceptions() { ); } - @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) - @SystemProperty(name = "allure.label.x-provided", value = "cucumberjvm6-test-provided") + @ResourceLock( + value = SYSTEM_PROPERTIES, + mode = READ_WRITE + ) + @SystemProperty( + name = "allure.label.x-provided", + value = "cucumberjvm6-test-provided" + ) @Test void shouldSupportProvidedLabels() { final AllureResults results = runFeature("features/simple.feature"); @@ -783,17 +797,22 @@ void shouldSupportRuntimeApiInStepsWhenHooksAreUsed() { ); } - @SystemProperty(name = "cucumber.junit-platform.naming-strategy", value = "long") + @SystemProperty( + name = "cucumber.junit-platform.naming-strategy", + value = "long" + ) @Step private AllureResults runFeature(final String featureResource, final String... moreOptions) { return RunUtils.runTests(lifecycle -> { final AllureCucumber6Jvm cucumber6jvm = new AllureCucumber6Jvm(lifecycle); final Supplier classLoader = ClassLoaders::getDefaultClassLoader; - final List opts = new ArrayList<>(Arrays.asList( - "--glue", "io.qameta.allure.cucumber6jvm.samples", - "--plugin", "null_summary" - )); + final List opts = new ArrayList<>( + Arrays.asList( + "--glue", "io.qameta.allure.cucumber6jvm.samples", + "--plugin", "null_summary" + ) + ); opts.addAll(Arrays.asList(moreOptions)); final FeatureWithLines featureWithLines = FeatureWithLines.parse("src/test/resources/" + featureResource); final RuntimeOptions options = new CommandlineOptionsParser(System.out) @@ -801,8 +820,7 @@ private AllureResults runFeature(final String featureResource, final EventBus bus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID); final FeatureParser parser = new FeatureParser(bus::generateId); - final FeaturePathFeatureSupplier supplier - = new FeaturePathFeatureSupplier(classLoader, options, parser); + final FeaturePathFeatureSupplier supplier = new FeaturePathFeatureSupplier(classLoader, options, parser); final Runtime runtime = Runtime.builder() .withClassLoader(classLoader) diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/AmbigiousSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/AmbigiousSteps.java index 5a2e5a12..3ea3058c 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/AmbigiousSteps.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/AmbigiousSteps.java @@ -17,10 +17,6 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; - -/** - * @author charlie (Dmitry Baev). - */ public class AmbigiousSteps { @When("^ambigious step (.+)$") @@ -34,7 +30,7 @@ public void ambigious_2() { } @Then("^something bad should happen") - public void somethingBadStep(){ + public void somethingBadStep() { //nothing here } } diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/AttachmentSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/AttachmentSteps.java index 6a9393a8..2b72de70 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/AttachmentSteps.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/AttachmentSteps.java @@ -15,8 +15,8 @@ */ package io.qameta.allure.cucumber6jvm.samples; -import io.cucumber.java.Scenario; import io.cucumber.java.Before; +import io.cucumber.java.Scenario; import io.cucumber.java.en.Given; public class AttachmentSteps { diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/BackgroundFeatureSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/BackgroundFeatureSteps.java index 578abfdf..5d909884 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/BackgroundFeatureSteps.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/BackgroundFeatureSteps.java @@ -18,10 +18,6 @@ import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; - -/** - * @author charlie (Dmitry Baev). - */ public class BackgroundFeatureSteps { @Given("^cat is sad$") diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/BrokenFeatureSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/BrokenFeatureSteps.java index 1222c28f..b1922783 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/BrokenFeatureSteps.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/BrokenFeatureSteps.java @@ -16,10 +16,6 @@ package io.qameta.allure.cucumber6jvm.samples; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ public class BrokenFeatureSteps { @Given("^everything is broken$") diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/DatatableFeatureSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/DatatableFeatureSteps.java index b8a69f6b..d278ec28 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/DatatableFeatureSteps.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/DatatableFeatureSteps.java @@ -17,10 +17,6 @@ import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ @SuppressWarnings("unused") public class DatatableFeatureSteps { diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/HookSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/HookSteps.java index d7fe2a8e..94ceb8d7 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/HookSteps.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/HookSteps.java @@ -18,29 +18,25 @@ import io.cucumber.java.After; import io.cucumber.java.Before; import org.assertj.core.api.Assertions; - -/** - * @author letsrokk (Dmitry Mayer). - */ public class HookSteps { @Before("@WithHooks") - public void beforeHook(){ + public void beforeHook() { // nothing } @After("@WithHooks") - public void afterHook(){ + public void afterHook() { // nothing } @Before("@BeforeHookWithException") - public void beforeHookWithException(){ + public void beforeHookWithException() { Assertions.fail("Exception in Hook step"); } @After("@AfterHookWithException") - public void afterHookWithException(){ + public void afterHookWithException() { Assertions.fail("Exception in Hook step"); } diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/PendingSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/PendingSteps.java index f0a8fb7a..d4d1498c 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/PendingSteps.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/PendingSteps.java @@ -17,10 +17,6 @@ import io.cucumber.java.PendingException; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ public class PendingSteps { @Given("^step is yet to be implemented$") diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/RuntimeApiSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/RuntimeApiSteps.java index 7438ea19..61010571 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/RuntimeApiSteps.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/RuntimeApiSteps.java @@ -20,19 +20,15 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import io.qameta.allure.Allure; - -/** - * @author charlie (Dmitry Baev). - */ public class RuntimeApiSteps { @Before("@beforeScenario") - public void beforeScenario(){ + public void beforeScenario() { // nothing } @Before("@beforeFeature") - public void beforeFeature(){ + public void beforeFeature() { // nothing } diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/SimpleFeatureSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/SimpleFeatureSteps.java index 8d1c005a..e7618526 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/SimpleFeatureSteps.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/SimpleFeatureSteps.java @@ -19,10 +19,6 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import org.assertj.core.api.Assertions; - -/** - * @author charlie (Dmitry Baev). - */ public class SimpleFeatureSteps { private int a; diff --git a/allure-cucumber6-jvm/src/test/resources/allure.properties b/allure-cucumber6-jvm/src/test/resources/allure.properties index dbfefee4..6b16969f 100644 --- a/allure-cucumber6-jvm/src/test/resources/allure.properties +++ b/allure-cucumber6-jvm/src/test/resources/allure.properties @@ -1,3 +1,4 @@ allure.model.indentOutput=true allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-cucumber6-jvm diff --git a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java index 60883143..032335b8 100644 --- a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java +++ b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java @@ -66,20 +66,27 @@ import java.util.stream.Stream; import static io.qameta.allure.util.ResultsUtils.createParameter; +import static io.qameta.allure.util.ResultsUtils.createTitlePath; +import static io.qameta.allure.util.ResultsUtils.createTitlePathFromSourcePath; import static io.qameta.allure.util.ResultsUtils.getStatus; import static io.qameta.allure.util.ResultsUtils.getStatusDetails; import static io.qameta.allure.util.ResultsUtils.md5; /** - * Allure plugin for Cucumber JVM 7.0. + * Reports Cucumber JVM 7 execution to Allure. + * + *

Add this plugin to the Cucumber runtime so feature, scenario, step, hook, and attachment events are converted into Allure results. Use the default lifecycle for normal runs or pass one explicitly for embedded runners and tests.

*/ -@SuppressWarnings({ - "ClassDataAbstractionCoupling", - "ClassFanOutComplexity", -}) +@SuppressWarnings( + { + "ClassDataAbstractionCoupling", + "ClassFanOutComplexity", + } +) public class AllureCucumber7Jvm implements ConcurrentEventListener { private static final String COLON = ":"; + private static final String NEW_LINE = "\n"; private final AllureLifecycle lifecycle; @@ -99,15 +106,26 @@ public class AllureCucumber7Jvm implements ConcurrentEventListener { private static final String TEXT_PLAIN = "text/plain"; private static final String CUCUMBER_WORKING_DIR = Paths.get("").toUri().getSchemeSpecificPart(); + /** + * Creates an Allure cucumber7 jvm with default configuration. + */ @SuppressWarnings("unused") public AllureCucumber7Jvm() { this(Allure.getLifecycle()); } + /** + * Creates an Allure cucumber7 jvm with the supplied values. + * + * @param lifecycle the Allure lifecycle to use + */ public AllureCucumber7Jvm(final AllureLifecycle lifecycle) { this.lifecycle = lifecycle; } + /** + * {@inheritDoc} + */ @Override public void setEventPublisher(final EventPublisher publisher) { publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler); @@ -135,31 +153,34 @@ private void handleTestCaseStarted(final TestCaseStarted event) { final String name = testCase.getName(); - // the same way full name is generated for // org.junit.platform.engine.support.descriptor.ClasspathResourceSource // to support io.qameta.allure.junitplatform.AllurePostDiscoveryFilter - final String fullName = String.format("%s:%d", + final String fullName = String.format( + "%s:%d", getTestCaseUri(testCase), testCase.getLocation().getLine() ); final String testCaseUuid = testCase.getId().toString(); + final List titlePath = createTitlePathFromSourcePath(getTestCaseUri(testCase)); + titlePath.addAll(createTitlePath(feature.getName())); + final TestResult result = new TestResult() .setUuid(testCaseUuid) .setTestCaseId(getTestCaseId(testCase)) .setHistoryId(getHistoryId(testCase)) .setFullName(fullName) + .setTitlePath(titlePath) .setName(name) .setLabels(labelBuilder.getScenarioLabels()) .setLinks(labelBuilder.getScenarioLinks()); - final Scenario scenarioDefinition = - testSources.getScenarioDefinition( - testCase.getUri(), - testCase.getLocation().getLine() - ); + final Scenario scenarioDefinition = testSources.getScenarioDefinition( + testCase.getUri(), + testCase.getLocation().getLine() + ); if (scenarioDefinition.getExamples() != null) { result.setParameters( @@ -170,7 +191,7 @@ private void handleTestCaseStarted(final TestCaseStarted event) { final String description = Stream.of(feature.getDescription(), scenarioDefinition.getDescription()) .filter(Objects::nonNull) .filter(s -> !s.isEmpty()) - .collect(Collectors.joining("\n")); + .collect(Collectors.joining(NEW_LINE)); if (!description.isEmpty()) { result.setDescription(description); @@ -195,9 +216,10 @@ private void handleTestCaseFinished(final TestCaseFinished event) { .setMuted(tagParser.isMuted()) .setKnown(tagParser.isKnown()); - lifecycle.updateTestCase(uuid, testResult -> testResult - .setStatus(status) - .setStatusDetails(statusDetails) + lifecycle.updateTestCase( + uuid, testResult -> testResult + .setStatus(status) + .setStatusDetails(statusDetails) ); lifecycle.stopTestCase(uuid); @@ -257,9 +279,10 @@ private void handleStartFixtureHook(final TestCase testCase, final String containerUuid = hookStepContainerUuid .computeIfAbsent(hookId, unused -> UUID.randomUUID().toString()); - lifecycle.startTestContainer(new TestResultContainer() - .setUuid(containerUuid) - .setChildren(Collections.singletonList(uuid)) + lifecycle.startTestContainer( + new TestResultContainer() + .setUuid(containerUuid) + .setChildren(Collections.singletonList(uuid)) ); final FixtureResult hookResult = new FixtureResult() @@ -340,16 +363,15 @@ private Status translateTestCaseStatus(final Result testCaseResult) { } private List getExamplesAsParameters( - final Scenario scenario, - final TestCase localCurrentTestCase) { + final Scenario scenario, + final TestCase localCurrentTestCase) { - final Optional maybeExample = - scenario.getExamples().stream() - .filter(example -> example.getTableBody().stream() - .anyMatch(row -> row.getLocation().getLine() - == localCurrentTestCase.getLocation().getLine()) - ) - .findFirst(); + final Optional maybeExample = scenario.getExamples().stream() + .filter( + example -> example.getTableBody().stream() + .anyMatch(row -> row.getLocation().getLine() == localCurrentTestCase.getLocation().getLine()) + ) + .findFirst(); if (!maybeExample.isPresent()) { return Collections.emptyList(); @@ -370,9 +392,10 @@ private List getExamplesAsParameters( final List headerNames = examples.getTableHeader() .map(TableRow::getCells) - .map(rows -> rows.stream() - .map(TableCell::getValue) - .collect(Collectors.toList()) + .map( + rows -> rows.stream() + .map(TableCell::getValue) + .collect(Collectors.toList()) ) .orElse(null); @@ -392,14 +415,16 @@ private void createDataTableAttachment(final DataTableArgument dataTableArgument final StringBuilder dataTableCsv = new StringBuilder(); for (List columns : rowsInTable) { if (!columns.isEmpty()) { - final String rowValue = columns.stream().collect(Collectors.joining("\t", "", "\n")); + final String rowValue = columns.stream().collect(Collectors.joining("\t", "", NEW_LINE)); dataTableCsv.append(rowValue); } } final String attachmentSource = lifecycle .prepareAttachment("Data table", "text/tab-separated-values", "csv"); - lifecycle.writeAttachment(attachmentSource, - new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8))); + lifecycle.writeAttachment( + attachmentSource, + new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8)) + ); } private void handleStopHookStep(final Result eventResult, @@ -416,9 +441,10 @@ private void handleStopHookStep(final Result eventResult, final StatusDetails statusDetails = getStatusDetails(eventResult.getError()) .orElseGet(StatusDetails::new); - lifecycle.updateFixture(uuid, result -> result - .setStatus(status) - .setStatusDetails(statusDetails) + lifecycle.updateFixture( + uuid, result -> result + .setStatus(status) + .setStatusDetails(statusDetails) ); lifecycle.stopFixture(uuid); @@ -433,8 +459,7 @@ private void handleStopStep(final TestCase testCase, final Status stepStatus = translateTestCaseStatus(eventResult); - final StatusDetails statusDetails - = eventResult.getStatus() == io.cucumber.plugin.event.Status.UNDEFINED + final StatusDetails statusDetails = eventResult.getStatus() == io.cucumber.plugin.event.Status.UNDEFINED ? new StatusDetails().setMessage("Undefined Step. Please add step definition") : getStatusDetails(eventResult.getError()) .orElse(new StatusDetails()); diff --git a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/LabelBuilder.java b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/LabelBuilder.java index e4d96d5f..62123bdf 100644 --- a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/LabelBuilder.java +++ b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/LabelBuilder.java @@ -48,7 +48,7 @@ /** * Scenario labels and links builder. */ -@SuppressWarnings({"CyclomaticComplexity", "MultipleStringLiterals"}) +@SuppressWarnings({"CyclomaticComplexity", "MultipleStringLiterals", "PMD.CognitiveComplexity"}) class LabelBuilder { private static final Logger LOGGER = LoggerFactory.getLogger(LabelBuilder.class); private static final String COMPOSITE_TAG_DELIMITER = "="; @@ -117,17 +117,19 @@ class LabelBuilder { final URI uri = scenario.getUri(); scenarioLabels.addAll(ResultsUtils.getProvidedLabels()); - scenarioLabels.addAll(Arrays.asList( - createHostLabel(), - createThreadLabel(), - createFeatureLabel(featureName), - createStoryLabel(scenario.getName()), - createSuiteLabel(featureName), - createTestClassLabel(scenario.getName()), - createFrameworkLabel("cucumber4jvm"), - createLanguageLabel("java"), - createLabel("gherkin_uri", uri.toString()) - )); + scenarioLabels.addAll( + Arrays.asList( + createHostLabel(), + createThreadLabel(), + createFeatureLabel(featureName), + createStoryLabel(scenario.getName()), + createSuiteLabel(featureName), + createTestClassLabel(scenario.getName()), + createFrameworkLabel("cucumber4jvm"), + createLanguageLabel("java"), + createLabel("gherkin_uri", uri.toString()) + ) + ); featurePackage(uri.toString(), featureName) .map(ResultsUtils::createPackageLabel) @@ -160,8 +162,10 @@ private void tryHandleNamedLink(final String tagString) { final String name = tagString.split(COMPOSITE_TAG_DELIMITER)[1]; scenarioLinks.add(ResultsUtils.createLink(null, name, null, type)); } else { - LOGGER.warn("Composite named tag {} does not match regex {}. Skipping", tagString, - namedLinkPatternString); + LOGGER.warn( + "Composite named tag {} does not match regex {}. Skipping", tagString, + namedLinkPatternString + ); } } @@ -179,10 +183,12 @@ private Optional featurePackage(final String uriString, final String fea final String schemeSpecificPart = uri.normalize().getSchemeSpecificPart(); final Stream folders = Stream.of(schemeSpecificPart.replaceAll("\\.", "_").split("/")); final Stream name = Stream.of(featureName); - return Optional.of(Stream.concat(folders, name) - .filter(Objects::nonNull) - .filter(s -> !s.isEmpty()) - .collect(Collectors.joining("."))); + return Optional.of( + Stream.concat(folders, name) + .filter(Objects::nonNull) + .filter(s -> !s.isEmpty()) + .collect(Collectors.joining(".")) + ); } private static Optional safeUri(final String uri) { diff --git a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/TagParser.java b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/TagParser.java index ea7e10ef..faca7e91 100644 --- a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/TagParser.java +++ b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/TagParser.java @@ -51,9 +51,9 @@ public boolean isKnown() { private boolean getStatusDetailByTag(final String tagName) { return scenario.getTags().stream() - .anyMatch(tag -> tag.equalsIgnoreCase(tagName)) - || feature.getTags().stream() - .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName)); + .anyMatch(tag -> tag.equalsIgnoreCase(tagName)) + || feature.getTags().stream() + .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName)); } public boolean isResultTag(final String tag) { diff --git a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModel.java b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModel.java index 479ada73..91e4e185 100644 --- a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModel.java +++ b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModel.java @@ -73,7 +73,8 @@ private void parseGherkinSource(final URI path) { .build(); final Stream envelopes = parser.parse( - Envelope.of(new Source(path.toString(), source, SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN))); + Envelope.of(new Source(path.toString(), source, SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN)) + ); // TODO: What about empty gherkin docs? final GherkinDocument gherkinDocument = envelopes @@ -97,7 +98,7 @@ private void parseGherkinSource(final URI path) { } private void processFeatureDefinition( - final Map nodeMap, final FeatureChild child, final AstNode currentParent) { + final Map nodeMap, final FeatureChild child, final AstNode currentParent) { child.getBackground().ifPresent(background -> processBackgroundDefinition(nodeMap, background, currentParent)); child.getScenario().ifPresent(scenario -> processScenarioDefinition(nodeMap, scenario, currentParent)); child.getRule().ifPresent(rule -> { @@ -108,8 +109,7 @@ private void processFeatureDefinition( } private void processBackgroundDefinition( - final Map nodeMap, final Background background, final AstNode currentParent - ) { + final Map nodeMap, final Background background, final AstNode currentParent) { final AstNode childNode = createAstNode(background, currentParent); nodeMap.put(background.getLocation().getLine(), childNode); for (Step step : background.getSteps()) { @@ -118,7 +118,7 @@ private void processBackgroundDefinition( } private void processScenarioDefinition( - final Map nodeMap, final Scenario child, final AstNode currentParent) { + final Map nodeMap, final Scenario child, final AstNode currentParent) { final AstNode childNode = createAstNode(child, currentParent); nodeMap.put(child.getLocation().getLine(), childNode); for (Step step : child.getSteps()) { @@ -130,14 +130,13 @@ private void processScenarioDefinition( } private void processRuleDefinition( - final Map nodeMap, final RuleChild child, final AstNode currentParent) { + final Map nodeMap, final RuleChild child, final AstNode currentParent) { child.getBackground().ifPresent(background -> processBackgroundDefinition(nodeMap, background, currentParent)); child.getScenario().ifPresent(scenario -> processScenarioDefinition(nodeMap, scenario, currentParent)); } private void processScenarioOutlineExamples( - final Map nodeMap, final Scenario scenarioOutline, final AstNode parent - ) { + final Map nodeMap, final Scenario scenarioOutline, final AstNode parent) { for (Examples examples : scenarioOutline.getExamples()) { final AstNode examplesNode = createAstNode(examples, parent); // TODO: Can tables without headers even exist? diff --git a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModelProxy.java b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModelProxy.java index 9a922620..155257b6 100644 --- a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModelProxy.java +++ b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModelProxy.java @@ -26,28 +26,62 @@ import java.util.Map; import java.util.Objects; +/** + * Compatibility proxy around Cucumber feature source storage. + * + *

The proxy hides version-specific Cucumber source model APIs from the reporting plugin. Integrations use it to add source-read events and resolve feature, scenario, and step keyword metadata during execution.

+ */ public class TestSourcesModelProxy { private final Map pathToReadEventMap = new HashMap<>(); private final TestSourcesModel testSources; + /** + * Creates a test sources model proxy with default configuration. + */ public TestSourcesModelProxy() { this.testSources = new TestSourcesModel(); } + /** + * Adds the test source read event. + * + * @param path the path to read from or write to + * @param event the framework event to process + */ public void addTestSourceReadEvent(final URI path, final TestSourceRead event) { this.pathToReadEventMap.put(path, event); testSources.addTestSourceReadEvent(path, event); } + /** + * Returns the feature. + * + * @param path the path to read from or write to + * @return the feature + */ public Feature getFeature(final URI path) { return testSources.getFeature(path); } + /** + * Returns the scenario definition. + * + * @param path the path to read from or write to + * @param line the source line number to resolve + * @return the scenario definition + */ public Scenario getScenarioDefinition(final URI path, final int line) { return TestSourcesModel.getScenarioDefinition(testSources.getAstNode(path, line)); } + /** + * Returns the keyword from source. + * + * @param uri the feature file URI + * @param stepLine the feature file line number of the step + * @return the keyword from source + */ public String getKeywordFromSource(final URI uri, final int stepLine) { return this.getKeywordFromSourceInternal(uri, stepLine); } diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java index 0d8a20a7..109de2c4 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java @@ -65,10 +65,6 @@ import static org.assertj.core.api.Assertions.tuple; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; - -/** - * @author charlie (Dmitry Baev). - */ class AllureCucumber7JvmTest { @AllureFeatures.Base @@ -80,6 +76,8 @@ void shouldSetName() { assertThat(testResults) .extracting(TestResult::getName) .containsExactlyInAnyOrder("Add a to b"); + assertThat(testResults.get(0).getTitlePath()) + .containsExactly("src", "test", "resources", "features", "simple.feature", "Simple feature"); } @AllureFeatures.PassedTests @@ -183,7 +181,7 @@ void shouldSetDescription() { final AllureResults results = runFeature("features/description.feature"); final String expected = "This is description for current feature.\n" - + "It should appear on each scenario in report"; + + "It should appear on each scenario in report"; final List testResults = results.getTestResults(); assertThat(testResults) @@ -200,7 +198,7 @@ void shouldSetScenarioDescription() { final AllureResults results = runFeature("features/scenario_description.feature"); final String expected = "This is description for current feature.\n" - + "It should appear on each scenario in report"; + + "It should appear on each scenario in report"; final List testResults = results.getTestResults(); assertThat(testResults) @@ -238,11 +236,12 @@ void shouldAddDataTableAttachment() { final String attachmentContent = new String(bytes, StandardCharsets.UTF_8); assertThat(attachmentContent) - .isEqualTo(""" - name\tlogin\temail - Viktor\tclicman\tclicman@ya.ru - Viktor2\tclicman2\tclicman2@ya.ru + .isEqualTo( """ + name\tlogin\temail + Viktor\tclicman\tclicman@ya.ru + Viktor2\tclicman2\tclicman2@ya.ru + """ ); } @@ -381,8 +380,14 @@ void shouldAddTags() { @AllureFeatures.Links @ExtendWith(SystemPropertyExtension.class) - @SystemProperty(name = "allure.link.issue.pattern", value = "https://example.org/issue/{}") - @SystemProperty(name = "allure.link.tms.pattern", value = "https://example.org/tms/{}") + @SystemProperty( + name = "allure.link.issue.pattern", + value = "https://example.org/issue/{}" + ) + @SystemProperty( + name = "allure.link.tms.pattern", + value = "https://example.org/tms/{}" + ) @Test void shouldAddLinks() { final AllureResults results = runFeature("features/tags.feature"); @@ -511,8 +516,10 @@ void shouldSupportDryRunForSimpleFeatures() { @AllureFeatures.Base @Test void shouldSupportDryRunForHooks() { - final AllureResults results = runFeature("features/hooks.feature", "--dry-run", "-t", - "@WithHooks or @BeforeHookWithException or @AfterHookWithException"); + final AllureResults results = runFeature( + "features/hooks.feature", "--dry-run", "-t", + "@WithHooks or @BeforeHookWithException or @AfterHookWithException" + ); final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks"); @@ -652,8 +659,10 @@ void shouldProcessScenariosInParallelMode() { @AllureFeatures.Stages @Test void shouldDisplayHooksAsStages() { - final AllureResults results = runFeature("features/hooks.feature", "-t", - "@WithHooks or @BeforeHookWithException or @AfterHookWithException"); + final AllureResults results = runFeature( + "features/hooks.feature", "-t", + "@WithHooks or @BeforeHookWithException or @AfterHookWithException" + ); final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks"); final TestResult tr2 = results.getTestResultByName("Simple scenario with Before hook with Exception"); @@ -691,7 +700,6 @@ void shouldDisplayHooksAsStages() { tuple("Then result is 15", Status.SKIPPED) ); - assertThat(results.getTestResultContainersForTestResult(tr2)) .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) @@ -737,8 +745,14 @@ void shouldHandleAmbigiousStepsExceptions() { ); } - @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) - @SystemProperty(name = "allure.label.x-provided", value = "cucumberjvm7-test-provided") + @ResourceLock( + value = SYSTEM_PROPERTIES, + mode = READ_WRITE + ) + @SystemProperty( + name = "allure.label.x-provided", + value = "cucumberjvm7-test-provided" + ) @Test void shouldSupportProvidedLabels() { final AllureResults results = runFeature("features/simple.feature"); @@ -783,7 +797,10 @@ void shouldSupportRuntimeApiInStepsWhenHooksAreUsed() { ); } - @SystemProperty(name = "cucumber.junit-platform.naming-strategy", value = "long") + @SystemProperty( + name = "cucumber.junit-platform.naming-strategy", + value = "long" + ) @Step private AllureResults runFeature(final String featureResource, final String... moreOptions) { @@ -791,10 +808,12 @@ private AllureResults runFeature(final String featureResource, return RunUtils.runTests(lifecycle -> { final AllureCucumber7Jvm cucumber7jvm = new AllureCucumber7Jvm(lifecycle); final Supplier classLoader = ClassLoaders::getDefaultClassLoader; - final List opts = new ArrayList<>(Arrays.asList( - "--glue", "io.qameta.allure.cucumber7jvm.samples", - "--no-summary" - )); + final List opts = new ArrayList<>( + Arrays.asList( + "--glue", "io.qameta.allure.cucumber7jvm.samples", + "--no-summary" + ) + ); opts.addAll(Arrays.asList(moreOptions)); final FeatureWithLines featureWithLines = FeatureWithLines.parse("src/test/resources/" + featureResource); final RuntimeOptions options = new CommandlineOptionsParser(System.out) @@ -802,8 +821,7 @@ private AllureResults runFeature(final String featureResource, final EventBus bus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID); final FeatureParser parser = new FeatureParser(bus::generateId); - final FeaturePathFeatureSupplier supplier - = new FeaturePathFeatureSupplier(classLoader, options, parser); + final FeaturePathFeatureSupplier supplier = new FeaturePathFeatureSupplier(classLoader, options, parser); final Runtime runtime = Runtime.builder() .withClassLoader(classLoader) diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/AmbigiousSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/AmbigiousSteps.java index 0406db2f..baaeee7a 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/AmbigiousSteps.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/AmbigiousSteps.java @@ -17,10 +17,6 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; - -/** - * @author charlie (Dmitry Baev). - */ public class AmbigiousSteps { @When("^ambigious step (.+)$") @@ -34,7 +30,7 @@ public void ambigious_2() { } @Then("^something bad should happen") - public void somethingBadStep(){ + public void somethingBadStep() { //nothing here } } diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/AttachmentSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/AttachmentSteps.java index 455aaffe..bd1fb2a4 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/AttachmentSteps.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/AttachmentSteps.java @@ -15,8 +15,8 @@ */ package io.qameta.allure.cucumber7jvm.samples; -import io.cucumber.java.Scenario; import io.cucumber.java.Before; +import io.cucumber.java.Scenario; import io.cucumber.java.en.Given; public class AttachmentSteps { diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/BackgroundFeatureSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/BackgroundFeatureSteps.java index 1cb2041c..93f55c03 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/BackgroundFeatureSteps.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/BackgroundFeatureSteps.java @@ -18,10 +18,6 @@ import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; - -/** - * @author charlie (Dmitry Baev). - */ public class BackgroundFeatureSteps { @Given("^cat is sad$") diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/BrokenFeatureSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/BrokenFeatureSteps.java index e2b7676b..3dbcfa76 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/BrokenFeatureSteps.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/BrokenFeatureSteps.java @@ -16,10 +16,6 @@ package io.qameta.allure.cucumber7jvm.samples; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ public class BrokenFeatureSteps { @Given("^everything is broken$") diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/DatatableFeatureSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/DatatableFeatureSteps.java index d43bacfc..d01b6ce7 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/DatatableFeatureSteps.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/DatatableFeatureSteps.java @@ -17,10 +17,6 @@ import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ @SuppressWarnings("unused") public class DatatableFeatureSteps { diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/HookSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/HookSteps.java index b85751af..c20d9dbe 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/HookSteps.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/HookSteps.java @@ -18,29 +18,25 @@ import io.cucumber.java.After; import io.cucumber.java.Before; import org.assertj.core.api.Assertions; - -/** - * @author letsrokk (Dmitry Mayer). - */ public class HookSteps { @Before("@WithHooks") - public void beforeHook(){ + public void beforeHook() { // nothing } @After("@WithHooks") - public void afterHook(){ + public void afterHook() { // nothing } @Before("@BeforeHookWithException") - public void beforeHookWithException(){ + public void beforeHookWithException() { Assertions.fail("Exception in Hook step"); } @After("@AfterHookWithException") - public void afterHookWithException(){ + public void afterHookWithException() { Assertions.fail("Exception in Hook step"); } diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/PendingSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/PendingSteps.java index b3c74e1b..bbdc1fce 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/PendingSteps.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/PendingSteps.java @@ -17,10 +17,6 @@ import io.cucumber.java.PendingException; import io.cucumber.java.en.Given; - -/** - * @author charlie (Dmitry Baev). - */ public class PendingSteps { @Given("^step is yet to be implemented$") diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/RuntimeApiSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/RuntimeApiSteps.java index e76d71ac..393f880f 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/RuntimeApiSteps.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/RuntimeApiSteps.java @@ -16,23 +16,19 @@ package io.qameta.allure.cucumber7jvm.samples; import io.cucumber.java.Before; +import io.cucumber.java.en.And; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import io.cucumber.java.en.And; import io.qameta.allure.Allure; - -/** - * @author charlie (Dmitry Baev). - */ public class RuntimeApiSteps { @Before("@beforeScenario") - public void beforeScenario(){ + public void beforeScenario() { // nothing } @Before("@beforeFeature") - public void beforeFeature(){ + public void beforeFeature() { // nothing } diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/SimpleFeatureSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/SimpleFeatureSteps.java index 1280d9a5..b82ddb3d 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/SimpleFeatureSteps.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/SimpleFeatureSteps.java @@ -19,10 +19,6 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import org.assertj.core.api.Assertions; - -/** - * @author charlie (Dmitry Baev). - */ public class SimpleFeatureSteps { private int a; diff --git a/allure-cucumber7-jvm/src/test/resources/allure.properties b/allure-cucumber7-jvm/src/test/resources/allure.properties index dbfefee4..20fde5c1 100644 --- a/allure-cucumber7-jvm/src/test/resources/allure.properties +++ b/allure-cucumber7-jvm/src/test/resources/allure.properties @@ -1,3 +1,4 @@ allure.model.indentOutput=true allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-cucumber7-jvm diff --git a/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/ClassNames.java b/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/ClassNames.java index 09def58c..b3338a2f 100644 --- a/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/ClassNames.java +++ b/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/ClassNames.java @@ -14,10 +14,6 @@ * limitations under the License. */ package io.qameta.allure.description; - -/** - * @author charlie (Dmitry Baev). - */ final class ClassNames { static final String DESCRIPTION_ANNOTATION = "io.qameta.allure.Description"; diff --git a/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/JavaDocDescriptionRenderer.java b/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/JavaDocDescriptionRenderer.java index 6546dbb4..1985af5f 100644 --- a/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/JavaDocDescriptionRenderer.java +++ b/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/JavaDocDescriptionRenderer.java @@ -47,6 +47,7 @@ * description path suitable for open source projects where comments may evolve over time and where * security is more important than pixel-perfect parity with the standard doclet.

*/ +@SuppressWarnings({"PMD.GodClass", "PMD.TooManyMethods"}) final class JavaDocDescriptionRenderer { private static final String PARAGRAPH_BREAK = "\n\n"; @@ -58,6 +59,7 @@ final class JavaDocDescriptionRenderer { private static final String CODE_TAG = "code"; private static final String HTML_TAG_END = ">"; private static final String CLOSING_TAG_PREFIX = " 1 && trimmed.charAt(0) == '@' && Character.isJavaIdentifierStart(trimmed.charAt(1)); } - @SuppressWarnings("checkstyle:CyclomaticComplexity") + @SuppressWarnings({"checkstyle:CyclomaticComplexity", "PMD.CognitiveComplexity"}) private void renderFragment(final String fragment, final StringBuilder rendered) { int index = 0; while (index < fragment.length()) { @@ -182,10 +184,13 @@ private int renderInlineTag(final String fragment, final int start, final String return end + 1; } - @SuppressWarnings({ - "checkstyle:CyclomaticComplexity", - "checkstyle:NPathComplexity", - "checkstyle:ReturnCount"}) + @SuppressWarnings( + { + "checkstyle:CyclomaticComplexity", + "checkstyle:NPathComplexity", + "PMD.CognitiveComplexity", + "checkstyle:ReturnCount"} + ) private int renderHtmlTag(final String fragment, final int start, final StringBuilder rendered) { if (start + 1 >= fragment.length() || Character.isWhitespace(fragment.charAt(start + 1))) { return start; @@ -317,7 +322,7 @@ private void appendLineBreak(final StringBuilder rendered) { } private String cleanup(final String rendered) { - final String[] lines = normalize(rendered).split("\n", -1); + final String[] lines = normalize(rendered).split(NEW_LINE, -1); final StringBuilder cleaned = new StringBuilder(); boolean blankLinePending = false; @@ -331,7 +336,7 @@ private String cleanup(final String rendered) { } if (cleaned.length() > 0) { - cleaned.append(blankLinePending ? PARAGRAPH_BREAK : "\n"); + cleaned.append(blankLinePending ? PARAGRAPH_BREAK : NEW_LINE); } cleaned.append(trimmed); blankLinePending = false; @@ -341,7 +346,7 @@ private String cleanup(final String rendered) { } private String trimBlankLines(final String value) { - final String[] lines = normalize(value).split("\n", -1); + final String[] lines = normalize(value).split(NEW_LINE, -1); int start = 0; int end = lines.length; @@ -452,7 +457,7 @@ private String trimTrailingWhitespace(final String line) { } private String normalize(final String value) { - return value.replace("\r\n", "\n").replace('\r', '\n'); + return value.replace("\r\n", NEW_LINE).replace('\r', '\n'); } private boolean isBlank(final String value) { @@ -479,10 +484,12 @@ private int renderEntityReference(final String fragment, final int start, final return end + 1; } - @SuppressWarnings({ - "checkstyle:CyclomaticComplexity", - "checkstyle:NPathComplexity", - "checkstyle:ReturnCount"}) + @SuppressWarnings( + { + "checkstyle:CyclomaticComplexity", + "checkstyle:NPathComplexity", + "checkstyle:ReturnCount"} + ) private String decodeEntity(final String entity) { if (entity.isEmpty()) { return null; diff --git a/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/JavaDocDescriptionsProcessor.java b/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/JavaDocDescriptionsProcessor.java index acc1d555..bd77d395 100644 --- a/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/JavaDocDescriptionsProcessor.java +++ b/allure-descriptions-javadoc/src/main/java/io/qameta/allure/description/JavaDocDescriptionsProcessor.java @@ -31,6 +31,7 @@ import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; + import java.io.IOException; import java.io.Writer; import java.math.BigInteger; @@ -44,7 +45,9 @@ import static io.qameta.allure.description.ClassNames.DESCRIPTION_ANNOTATION; /** - * @author Egor Borisov ehborisov@gmail.com + * Supports allure-descriptions-javadoc integration with Allure reporting. + * + *

Use this type through the module that owns it when translating framework execution, result metadata, or attachments into Allure report data.

*/ @SupportedAnnotationTypes(DESCRIPTION_ANNOTATION) public class JavaDocDescriptionsProcessor extends AbstractProcessor { @@ -56,6 +59,9 @@ public class JavaDocDescriptionsProcessor extends AbstractProcessor { private Messager messager; private JavaDocDescriptionRenderer renderer; + /** + * {@inheritDoc} + */ @Override @SuppressWarnings("PMD.AvoidSynchronizedAtMethodLevel") public synchronized void init(final ProcessingEnvironment env) { @@ -66,11 +72,17 @@ public synchronized void init(final ProcessingEnvironment env) { renderer = new JavaDocDescriptionRenderer(); } + /** + * {@inheritDoc} + */ @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } + /** + * {@inheritDoc} + */ @Override public boolean process(final Set annotations, final RoundEnvironment env) { final TypeElement typeElement = elementUtils.getTypeElement(DESCRIPTION_ANNOTATION); @@ -96,14 +108,18 @@ public boolean process(final Set annotations, final Round method.getEnclosingElement().toString(), name, typeParams ); try { - final FileObject file = filer.createResource(StandardLocation.CLASS_OUTPUT, "", - ALLURE_DESCRIPTIONS_FOLDER + hash); + final FileObject file = filer.createResource( + StandardLocation.CLASS_OUTPUT, "", + ALLURE_DESCRIPTIONS_FOLDER + hash + ); try (Writer writer = file.openWriter()) { writer.write(docs); } } catch (IOException e) { - messager.printMessage(Diagnostic.Kind.WARNING, - "Unable to create resource from docs comment of method " + name + typeParams); + messager.printMessage( + Diagnostic.Kind.WARNING, + "Unable to create resource from docs comment of method " + name + typeParams + ); } }); diff --git a/allure-descriptions-javadoc/src/test/java/io/qameta/allure/description/JavaDocDescriptionRendererTest.java b/allure-descriptions-javadoc/src/test/java/io/qameta/allure/description/JavaDocDescriptionRendererTest.java index 180d2b03..7c85dfc5 100644 --- a/allure-descriptions-javadoc/src/test/java/io/qameta/allure/description/JavaDocDescriptionRendererTest.java +++ b/allure-descriptions-javadoc/src/test/java/io/qameta/allure/description/JavaDocDescriptionRendererTest.java @@ -29,10 +29,10 @@ class JavaDocDescriptionRendererTest { void shouldRenderPlainTextAndTrimBlankLines() { final String rendered = renderer.render( "\r\n" - + " First line \r\n" - + "\r\n" - + " Second line\t\r\n" - + "\r\n" + + " First line \r\n" + + "\r\n" + + " Second line\t\r\n" + + "\r\n" ); assertThat(rendered) @@ -43,7 +43,7 @@ void shouldRenderPlainTextAndTrimBlankLines() { void shouldReturnEmptyStringWhenBodyContainsOnlyBlockTags() { final String rendered = renderer.render( "@param value description\n" - + "@throws Exception description" + + "@throws Exception description" ); assertThat(rendered) @@ -54,9 +54,9 @@ void shouldReturnEmptyStringWhenBodyContainsOnlyBlockTags() { void shouldIgnoreBlockTagsAndEverythingAfterThem() { final String rendered = renderer.render( "Summary paragraph.\n" - + "\n" - + "@param value Description of the value.\n" - + "Continuation that should also be ignored." + + "\n" + + "@param value Description of the value.\n" + + "Continuation that should also be ignored." ); assertThat(rendered) @@ -95,7 +95,7 @@ void shouldIgnoreStandardBlockTagsAfterMainDescription() { void shouldNotTreatAtSignsInsideTextAsBlockTags() { final String rendered = renderer.render( "Email support@example.com\n" - + "Use @smoke in prose." + + "Use @smoke in prose." ); assertThat(rendered) @@ -106,7 +106,7 @@ void shouldNotTreatAtSignsInsideTextAsBlockTags() { void shouldDecodeEscapedAtEntityBeforeBlockTags() { final String rendered = renderer.render( "@version stays in prose.\n" - + "@version 2.4.0" + + "@version 2.4.0" ); assertThat(rendered) @@ -135,9 +135,9 @@ void shouldDecodeSupportedNamedAndNumericEntities() { void shouldRenderSupportedInlineTags() { final String rendered = renderer.render( "Use {@code a < b}, {@literal }, " - + "{@link java.lang.String}, " - + "{@linkplain java.lang.String#valueOf(Object)}, " - + "{@link java.util.List list docs}." + + "{@link java.lang.String}, " + + "{@linkplain java.lang.String#valueOf(Object)}, " + + "{@link java.util.List list docs}." ); assertThat(rendered) @@ -158,9 +158,9 @@ void shouldSupportBalancedBracesInsideInlineTags() { void shouldNotTreatAtLinesInsideBalancedInlineTagsAsBlockTags() { final String rendered = renderer.render( "Summary {@literal first line\n" - + "@notATag\n" - + "last line}\n" - + "@param ignored" + + "@notATag\n" + + "last line}\n" + + "@param ignored" ); assertThat(rendered) @@ -181,14 +181,14 @@ void shouldRenderNestedInlineTagsInsideLinkLabels() { void shouldSafelyDegradeUnsupportedStandardInlineTags() { final String rendered = renderer.render( "Fallbacks: {@docRoot}, {@inheritDoc}, {@index release}, " - + "{@summary quick summary}, {@systemProperty user.home}, " - + "{@value java.lang.Integer#MAX_VALUE}." + + "{@summary quick summary}, {@systemProperty user.home}, " + + "{@value java.lang.Integer#MAX_VALUE}." ); assertThat(rendered) .isEqualTo( "Fallbacks: docRoot, inheritDoc, index release, summary quick summary, " - + "systemProperty user.home, value java.lang.Integer#MAX_VALUE." + + "systemProperty user.home, value java.lang.Integer#MAX_VALUE." ); } @@ -196,9 +196,9 @@ void shouldSafelyDegradeUnsupportedStandardInlineTags() { void shouldSafelyDegradeSnippetTags() { final String rendered = renderer.render( "Snippet {@snippet :\n" - + "int answer = 42;\n" - + "@highlight substring=\"answer\"\n" - + "}." + + "int answer = 42;\n" + + "@highlight substring=\"answer\"\n" + + "}." ); assertThat(rendered) @@ -301,28 +301,27 @@ void shouldDropUnknownHtmlTagsButKeepTheirTextContentEscaped() { void shouldRenderComplexModernJavadocExampleSafely() { final String rendered = renderer.render( "Fetches release metadata for the current build.\n" - + "\n" - + "

Use {@link java.net.URI URIs} for endpoint configuration.

\n" - + "
    \n" - + "
  • Supports café, Привет, 東京, and λ.
  • \n" - + "
  • See the Javadoc specification " - + "and {@linkplain java.lang.String#formatted(Object...) formatted examples}.
  • \n" - + "
\n" - + "Example: client.fetch(\"v2\")\n" - + "@beta remains prose.\n" - + "@author Jane Doe\n" - + "@version 2.3.0\n" - + "@since 2.0" + + "\n" + + "

Use {@link java.net.URI URIs} for endpoint configuration.

\n" + + "
    \n" + + "
  • Supports café, Привет, 東京, and λ.
  • \n" + + "
  • See the Javadoc specification " + + "and {@linkplain java.lang.String#formatted(Object...) formatted examples}.
  • \n" + + "
\n" + + "Example: client.fetch(\"v2\")\n" + + "@beta remains prose.\n" + + "@version 2.3.0\n" + + "@since 2.0" ); assertThat(rendered) .isEqualTo( "Fetches release metadata for the current build.\n\n" - + "Use URIs for endpoint configuration.\n\n" - + "- Supports café, Привет, 東京, and λ.\n" - + "- See the Javadoc specification and formatted examples.\n\n" - + "Example: `client.fetch(\"v2\")`\n" - + "@beta remains prose." + + "Use URIs for endpoint configuration.\n\n" + + "- Supports café, Привет, 東京, and λ.\n" + + "- See the Javadoc specification and formatted examples.\n\n" + + "Example: `client.fetch(\"v2\")`\n" + + "@beta remains prose." ); } } diff --git a/allure-descriptions-javadoc/src/test/java/io/qameta/allure/description/ProcessDescriptionsTest.java b/allure-descriptions-javadoc/src/test/java/io/qameta/allure/description/ProcessDescriptionsTest.java index 29f80277..f3af3750 100644 --- a/allure-descriptions-javadoc/src/test/java/io/qameta/allure/description/ProcessDescriptionsTest.java +++ b/allure-descriptions-javadoc/src/test/java/io/qameta/allure/description/ProcessDescriptionsTest.java @@ -25,10 +25,6 @@ import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.Compiler.javac; - -/** - * @author Egor Borisov ehborisov@gmail.com - */ class ProcessDescriptionsTest { private static final String ALLURE_DESCRIPTIONS_FOLDER = "META-INF/allureDescriptions/"; @@ -237,10 +233,10 @@ void shouldIgnoreBlockTagsAndRenderSafeMarkdown() { .contentsAsUtf8String() .isEqualTo( "This is my test description with `sample` and <safe>.\n\n" - + "Use String for values.\n\n" - + "- first item\n" - + "- second item\n\n" - + "alert(\"xss\")" + + "Use String for values.\n\n" + + "- first item\n" + + "- second item\n\n" + + "alert(\"xss\")" ); } @@ -267,7 +263,6 @@ void shouldCaptureComplexModernJavadocDescriptionSafely() { "* Example: client.fetch(\"v2\")", "* @beta remains prose.", "*", - "* @author Jane Doe", "* @version 2.3.0", "* @since 2.0", "* @see Javadoc spec", @@ -290,12 +285,12 @@ void shouldCaptureComplexModernJavadocDescriptionSafely() { .contentsAsUtf8String() .isEqualTo( "Fetches release metadata for the current build.\n\n" - + "Use URIs for endpoint configuration.\n\n" - + "- Supports café, Привет, 東京, and λ.\n" - + "- See the Javadoc specification\n" - + "and formatted examples.\n\n" - + "Example: `client.fetch(\"v2\")`\n" - + "@beta remains prose." + + "Use URIs for endpoint configuration.\n\n" + + "- Supports café, Привет, 東京, and λ.\n" + + "- See the Javadoc specification\n" + + "and formatted examples.\n\n" + + "Example: `client.fetch(\"v2\")`\n" + + "@beta remains prose." ); } } diff --git a/allure-descriptions-javadoc/src/test/resources/allure.properties b/allure-descriptions-javadoc/src/test/resources/allure.properties index 9c0b0a2d..384d9cd5 100644 --- a/allure-descriptions-javadoc/src/test/resources/allure.properties +++ b/allure-descriptions-javadoc/src/test/resources/allure.properties @@ -1,2 +1,3 @@ allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-descriptions-javadoc diff --git a/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java b/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java index ce71871a..e98953fc 100644 --- a/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java +++ b/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java @@ -34,6 +34,9 @@ import io.qameta.allure.model.Attachment; import io.qameta.allure.model.Status; import io.qameta.allure.model.StepResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -42,19 +45,20 @@ import java.util.Locale; import java.util.Map; import java.util.UUID; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * Allure interceptor logger for gRPC. + * Captures gRPC client calls as Allure attachments. * - * @author dtuchs (Dmitry Tuchs). + *

Attach this interceptor to a gRPC channel or stub to record request messages, response messages, metadata, and call status. The default constructor uses built-in templates; the explicit constructor accepts custom renderers and processors.

*/ -@SuppressWarnings({ - "checkstyle:ClassFanOutComplexity", - "checkstyle:AnonInnerLength", - "checkstyle:JavaNCSS" -}) +@SuppressWarnings( + { + "checkstyle:ClassFanOutComplexity", + "checkstyle:AnonInnerLength", + "checkstyle:JavaNCSS", + "PMD.GodClass" + } +) public class AllureGrpc implements ClientInterceptor { private static final Logger LOGGER = LoggerFactory.getLogger(AllureGrpc.class); @@ -68,18 +72,31 @@ public class AllureGrpc implements ClientInterceptor { private final String requestTemplatePath; private final String responseTemplatePath; + /** + * Creates an Allure grpc with default configuration. + */ public AllureGrpc() { - this(Allure.getLifecycle(), true, false, - "grpc-request.ftl", "grpc-response.ftl"); + this( + Allure.getLifecycle(), true, false, + "grpc-request.ftl", "grpc-response.ftl" + ); } + /** + * Creates an Allure grpc with the supplied values. + * + * @param lifecycle the Allure lifecycle to use + * @param markStepFailedOnNonZeroCode the mark step failed on non zero code + * @param interceptResponseMetadata the intercept response metadata + * @param requestTemplatePath the request template path + * @param responseTemplatePath the response template path + */ public AllureGrpc( - final AllureLifecycle lifecycle, - final boolean markStepFailedOnNonZeroCode, - final boolean interceptResponseMetadata, - final String requestTemplatePath, - final String responseTemplatePath - ) { + final AllureLifecycle lifecycle, + final boolean markStepFailedOnNonZeroCode, + final boolean interceptResponseMetadata, + final String requestTemplatePath, + final String responseTemplatePath) { this.lifecycle = lifecycle; this.markStepFailedOnNonZeroCode = markStepFailedOnNonZeroCode; this.interceptResponseMetadata = interceptResponseMetadata; @@ -87,12 +104,14 @@ public AllureGrpc( this.responseTemplatePath = responseTemplatePath; } + /** + * {@inheritDoc} + */ @Override public ClientCall interceptCall( - final MethodDescriptor methodDescriptor, - final CallOptions callOptions, - final Channel nextChannel - ) { + final MethodDescriptor methodDescriptor, + final CallOptions callOptions, + final Channel nextChannel) { final AllureLifecycle current = lifecycle; final String parent = current.getCurrentTestCaseOrStep().orElse(null); final String stepUuid = UUID.randomUUID().toString(); @@ -109,12 +128,12 @@ public ClientCall interceptCall( } final StepContext stepContext = new StepContext<>( - stepUuid, methodDescriptor, current, clientMessages, - serverMessages, initialHeaders, trailers + stepUuid, methodDescriptor, current, clientMessages, + serverMessages, initialHeaders, trailers ); return new ForwardingClientCall.SimpleForwardingClientCall( - nextChannel.newCall(methodDescriptor, callOptions) + nextChannel.newCall(methodDescriptor, callOptions) ) { @Override public void start(final Listener responseListener, final Metadata requestHeaders) { @@ -154,59 +173,59 @@ public void sendMessage(final T message) { } private void addRawJsonAttachment( - final String stepUuid, - final String attachmentName, - final String jsonBody, - final AllureLifecycle lifecycle - ) { + final String stepUuid, + final String attachmentName, + final String jsonBody, + final AllureLifecycle lifecycle) { if (jsonBody == null || jsonBody.isEmpty()) { return; } final String source = UUID.randomUUID() + ".json"; - lifecycle.updateStep(stepUuid, step -> step.getAttachments().add( - new Attachment() - .setName(attachmentName) - .setSource(source) - .setType("application/json") - )); + lifecycle.updateStep( + stepUuid, step -> step.getAttachments().add( + new Attachment() + .setName(attachmentName) + .setSource(source) + .setType("application/json") + ) + ); lifecycle.writeAttachment( - source, - new ByteArrayInputStream(jsonBody.getBytes(StandardCharsets.UTF_8)) + source, + new ByteArrayInputStream(jsonBody.getBytes(StandardCharsets.UTF_8)) ); } private void handleClose( - final io.grpc.Status status, - final Metadata responseTrailers, - final StepContext stepContext - ) { + final io.grpc.Status status, + final Metadata responseTrailers, + final StepContext stepContext) { try { if (interceptResponseMetadata && responseTrailers != null) { copyAsciiResponseMetadata(responseTrailers, stepContext.getTrailers()); } attachRequestIfPresent( - stepContext.getStepUuid(), - stepContext.getMethodDescriptor(), - stepContext.getClientMessages(), - stepContext.getLifecycle() + stepContext.getStepUuid(), + stepContext.getMethodDescriptor(), + stepContext.getClientMessages(), + stepContext.getLifecycle() ); attachResponse( - stepContext.getStepUuid(), - stepContext.getServerMessages(), - status, - stepContext.getInitialHeaders(), - stepContext.getTrailers(), - stepContext.getLifecycle() + stepContext.getStepUuid(), + stepContext.getServerMessages(), + status, + stepContext.getInitialHeaders(), + stepContext.getTrailers(), + stepContext.getLifecycle() ); stepContext.getLifecycle().updateStep( - stepContext.getStepUuid(), - step -> step.setStatus(convertStatus(status)) + stepContext.getStepUuid(), + step -> step.setStatus(convertStatus(status)) ); } catch (Throwable throwable) { LOGGER.error("Failed to finalize Allure step for gRPC call", throwable); stepContext.getLifecycle().updateStep( - stepContext.getStepUuid(), - step -> step.setStatus(Status.BROKEN) + stepContext.getStepUuid(), + step -> step.setStatus(Status.BROKEN) ); } finally { stopStepSafely(stepContext.getLifecycle(), stepContext.getStepUuid()); @@ -244,45 +263,43 @@ private void handleServerMessage(final R message, final List destina } private void attachRequestIfPresent( - final String stepUuid, - final MethodDescriptor methodDescriptor, - final List clientMessages, - final AllureLifecycle lifecycle - ) { + final String stepUuid, + final MethodDescriptor methodDescriptor, + final List clientMessages, + final AllureLifecycle lifecycle) { final String body = toJsonBody(clientMessages); if (body == null) { return; } final String name = clientMessages.size() > 1 - ? "gRPC request (collection of elements from Client stream)" - : "gRPC request"; + ? "gRPC request (collection of elements from Client stream)" + : "gRPC request"; final GrpcRequestAttachment requestAttachment = GrpcRequestAttachment.Builder - .create(name, methodDescriptor.getFullMethodName()) - .setBody(body) - .build(); + .create(name, methodDescriptor.getFullMethodName()) + .setBody(body) + .build(); addRenderedAttachmentToStep( - stepUuid, - requestAttachment.getName(), - requestAttachment, - requestTemplatePath, - lifecycle + stepUuid, + requestAttachment.getName(), + requestAttachment, + requestTemplatePath, + lifecycle ); addRawJsonAttachment(stepUuid, name + JSON_SUFFIX, body, lifecycle); } private void attachResponse( - final String stepUuid, - final List serverMessages, - final io.grpc.Status status, - final Map initialHeaders, - final Map trailers, - final AllureLifecycle lifecycle - ) { + final String stepUuid, + final List serverMessages, + final io.grpc.Status status, + final Map initialHeaders, + final Map trailers, + final AllureLifecycle lifecycle) { final String body = toJsonBody(serverMessages); final String name = serverMessages.size() > 1 - ? "gRPC response (collection of elements from Server stream)" - : "gRPC response"; + ? "gRPC response (collection of elements from Server stream)" + : "gRPC response"; final Map metadata = new LinkedHashMap<>(); if (interceptResponseMetadata) { @@ -291,8 +308,8 @@ private void attachResponse( } final GrpcResponseAttachment.Builder builder = GrpcResponseAttachment.Builder - .create(name) - .setStatus(status.toString()); + .create(name) + .setStatus(status.toString()); if (body != null) { builder.setBody(body); @@ -303,11 +320,11 @@ private void attachResponse( final GrpcResponseAttachment responseAttachment = builder.build(); addRenderedAttachmentToStep( - stepUuid, - responseAttachment.getName(), - responseAttachment, - responseTemplatePath, - lifecycle + stepUuid, + responseAttachment.getName(), + responseAttachment, + responseTemplatePath, + lifecycle ); if (body != null) { addRawJsonAttachment(stepUuid, name + JSON_SUFFIX, body, lifecycle); @@ -330,14 +347,13 @@ private Status convertStatus(final io.grpc.Status grpcStatus) { } private static String buildStepName( - final Channel channel, - final MethodDescriptor methodDescriptor - ) { + final Channel channel, + final MethodDescriptor methodDescriptor) { final String authority = channel != null ? channel.authority() : null; final String safeAuthority = authority != null ? authority : UNKNOWN; final String type = toSnakeCase(methodDescriptor.getType()); return "Send " + type + " gRPC request to " - + safeAuthority + "/" + methodDescriptor.getFullMethodName(); + + safeAuthority + "/" + methodDescriptor.getFullMethodName(); } private static String toSnakeCase(final MethodDescriptor.MethodType methodType) { @@ -348,21 +364,19 @@ private static String toSnakeCase(final MethodDescriptor.MethodType methodType) } private void addRenderedAttachmentToStep( - final String stepUuid, - final String attachmentName, - final AttachmentData data, - final String templatePath, - final AllureLifecycle lifecycle - ) { - final AttachmentRenderer renderer = - new FreemarkerAttachmentRenderer(templatePath); + final String stepUuid, + final String attachmentName, + final AttachmentData data, + final String templatePath, + final AllureLifecycle lifecycle) { + final AttachmentRenderer renderer = new FreemarkerAttachmentRenderer(templatePath); final io.qameta.allure.attachment.AttachmentContent content; try { content = renderer.render(data); } catch (Throwable throwable) { LOGGER.warn( - "Could not render attachment '{}' using template '{}'", - attachmentName, templatePath, throwable + "Could not render attachment '{}' using template '{}'", + attachmentName, templatePath, throwable ); return; } @@ -376,21 +390,21 @@ private void addRenderedAttachmentToStep( } final String source = UUID.randomUUID() + fileExtension; lifecycle.updateStep( - stepUuid, - step -> step.getAttachments().add( - new Attachment() - .setName(attachmentName) - .setSource(source) - .setType( - content.getContentType() != null - ? content.getContentType() - : "text/html" - ) - ) + stepUuid, + step -> step.getAttachments().add( + new Attachment() + .setName(attachmentName) + .setSource(source) + .setType( + content.getContentType() != null + ? content.getContentType() + : "text/html" + ) + ) ); lifecycle.writeAttachment( - source, - new ByteArrayInputStream(content.getContent().getBytes(StandardCharsets.UTF_8)) + source, + new ByteArrayInputStream(content.getContent().getBytes(StandardCharsets.UTF_8)) ); } @@ -406,9 +420,8 @@ private static String toJsonBody(final List items) { } private static void copyAsciiResponseMetadata( - final Metadata source, - final Map target - ) { + final Metadata source, + final Map target) { for (String key : source.keys()) { if (key == null) { continue; @@ -416,8 +429,7 @@ private static void copyAsciiResponseMetadata( if (key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { continue; } - final Metadata.Key keyAscii = - Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + final Metadata.Key keyAscii = Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); final String value = source.get(keyAscii); if (value != null) { target.put(key, value); @@ -435,14 +447,13 @@ private static final class StepContext { private final Map trailers; StepContext( - final String stepUuid, - final MethodDescriptor methodDescriptor, - final AllureLifecycle lifecycle, - final List clientMessages, - final List serverMessages, - final Map initialHeaders, - final Map trailers - ) { + final String stepUuid, + final MethodDescriptor methodDescriptor, + final AllureLifecycle lifecycle, + final List clientMessages, + final List serverMessages, + final Map initialHeaders, + final Map trailers) { this.stepUuid = stepUuid; this.methodDescriptor = methodDescriptor; this.lifecycle = lifecycle; diff --git a/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcRequestAttachment.java b/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcRequestAttachment.java index 1f227672..37966d02 100644 --- a/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcRequestAttachment.java +++ b/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcRequestAttachment.java @@ -19,26 +19,51 @@ import java.util.Objects; +/** + * Describes an HTTP or RPC request attachment rendered in an Allure report. + * + *

Use this model to carry request metadata and body content from client interceptors to attachment renderers and processors.

+ */ public class GrpcRequestAttachment implements AttachmentData { private final String name; private final String url; private final String body; + /** + * Creates a gRPC request attachment with the supplied values. + * + * @param name the display name or logical name to use + * @param url the request URL or service method name + * @param body the attachment body + */ public GrpcRequestAttachment(final String name, final String url, final String body) { this.name = name; this.url = url; this.body = body; } + /** + * Returns the url. + * + * @return the url + */ public String getUrl() { return url; } + /** + * Returns the body. + * + * @return the body + */ public String getBody() { return body; } + /** + * {@inheritDoc} + */ @Override public String getName() { return name; diff --git a/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcResponseAttachment.java b/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcResponseAttachment.java index 414aa725..04f12518 100644 --- a/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcResponseAttachment.java +++ b/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcResponseAttachment.java @@ -21,6 +21,11 @@ import java.util.Map; import java.util.Objects; +/** + * Describes an HTTP or RPC response attachment rendered in an Allure report. + * + *

Use this model to carry response metadata and body content from client interceptors to attachment renderers and processors.

+ */ public class GrpcResponseAttachment implements AttachmentData { private final String name; @@ -28,6 +33,14 @@ public class GrpcResponseAttachment implements AttachmentData { private final String status; private final Map metadata; + /** + * Creates a gRPC response attachment with the supplied values. + * + * @param name the display name or logical name to use + * @param body the attachment body + * @param status the response status + * @param metadata the metadata values to include + */ public GrpcResponseAttachment(final String name, final String body, final String status, @@ -38,18 +51,36 @@ public GrpcResponseAttachment(final String name, this.metadata = metadata; } + /** + * Returns the body. + * + * @return the body + */ public String getBody() { return body; } + /** + * Returns the metadata. + * + * @return the metadata + */ public Map getMetadata() { return metadata; } + /** + * Returns the status. + * + * @return the status + */ public String getStatus() { return status; } + /** + * {@inheritDoc} + */ @Override public String getName() { return name; diff --git a/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java b/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java index a98e09e7..ec5d55ca 100644 --- a/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java +++ b/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java @@ -61,37 +61,47 @@ class AllureGrpcTest { @BeforeEach void configureMockServer() { managedChannel = ManagedChannelBuilder - .forAddress("localhost", GrpcMock.getGlobalPort()) - .usePlaintext() - .directExecutor() - .build(); - - GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()) - .willReturn(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build())); - - GrpcMock.stubFor(serverStreamingMethod(TestServiceGrpc.getCalculateServerStreamMethod()) - .willReturn(asList( - Response.newBuilder().setMessage(RESPONSE_MESSAGE).build(), - Response.newBuilder().setMessage(RESPONSE_MESSAGE).build() - ))); - - GrpcMock.stubFor(clientStreamingMethod(TestServiceGrpc.getCalculateClientStreamMethod()) - .willReturn(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build())); - - GrpcMock.stubFor(bidiStreamingMethod(TestServiceGrpc.getCalculateBidiStreamMethod()) - .willProxyTo(responseObserver -> new StreamObserver() { - @Override - public void onNext(Request request) { - responseObserver.onNext(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build()); - } - @Override - public void onError(Throwable throwable) { - } - @Override - public void onCompleted() { - responseObserver.onCompleted(); - } - })); + .forAddress("localhost", GrpcMock.getGlobalPort()) + .usePlaintext() + .directExecutor() + .build(); + + GrpcMock.stubFor( + unaryMethod(TestServiceGrpc.getCalculateMethod()) + .willReturn(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build()) + ); + + GrpcMock.stubFor( + serverStreamingMethod(TestServiceGrpc.getCalculateServerStreamMethod()) + .willReturn( + asList( + Response.newBuilder().setMessage(RESPONSE_MESSAGE).build(), + Response.newBuilder().setMessage(RESPONSE_MESSAGE).build() + ) + ) + ); + + GrpcMock.stubFor( + clientStreamingMethod(TestServiceGrpc.getCalculateClientStreamMethod()) + .willReturn(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build()) + ); + + GrpcMock.stubFor( + bidiStreamingMethod(TestServiceGrpc.getCalculateBidiStreamMethod()) + .willProxyTo(responseObserver -> new StreamObserver() { + @Override + public void onNext(Request request) { + responseObserver.onNext(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build()); + } + @Override + public void onError(Throwable throwable) { + } + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }) + ); } @AfterEach @@ -102,8 +112,8 @@ void shutdownChannel() { @Test void shouldCreateRequestAttachment() { Request request = Request.newBuilder() - .setTopic("1") - .build(); + .setTopic("1") + .build(); Status errorStatus = Status.NOT_FOUND; GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()).willReturn(errorStatus)); @@ -111,40 +121,40 @@ void shouldCreateRequestAttachment() { AllureResults allureResults = executeUnaryExpectingException(request); assertThat(allureResults.getTestResults().get(0).getSteps().get(0).getStatus()) - .isEqualTo(io.qameta.allure.model.Status.FAILED); + .isEqualTo(io.qameta.allure.model.Status.FAILED); assertThat(allureResults.getTestResults().get(0).getSteps()) - .flatExtracting(StepResult::getAttachments) - .extracting(Attachment::getName) - .contains("gRPC request", "gRPC response"); + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC request", "gRPC response"); } @Test void shouldCreateResponseAttachment() { Request request = Request.newBuilder() - .setTopic("1") - .build(); + .setTopic("1") + .build(); AllureResults allureResults = executeUnary(request); assertThat(allureResults.getTestResults().get(0).getSteps()) - .flatExtracting(StepResult::getAttachments) - .extracting(Attachment::getName) - .contains("gRPC response"); + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC response"); } @Test void shouldCreateResponseAttachmentForServerStreamingResponse() { Request request = Request.newBuilder() - .setTopic("1") - .build(); + .setTopic("1") + .build(); AllureResults allureResults = executeServerStreaming(request); assertThat(allureResults.getTestResults().get(0).getSteps()) - .flatExtracting(StepResult::getAttachments) - .extracting(Attachment::getName) - .contains("gRPC response (collection of elements from Server stream)"); + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC response (collection of elements from Server stream)"); } @Test @@ -153,18 +163,18 @@ void shouldCreateResponseAttachmentOnStatusException() { GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()).willReturn(notFoundStatus)); Request request = Request.newBuilder() - .setTopic("2") - .build(); + .setTopic("2") + .build(); AllureResults allureResults = executeUnaryExpectingException(request); assertThat(allureResults.getTestResults().get(0).getSteps().get(0).getStatus()) - .isEqualTo(io.qameta.allure.model.Status.FAILED); + .isEqualTo(io.qameta.allure.model.Status.FAILED); assertThat(allureResults.getTestResults().get(0).getSteps()) - .flatExtracting(StepResult::getAttachments) - .extracting(Attachment::getName) - .contains("gRPC response"); + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC response"); } @Test @@ -173,8 +183,7 @@ void shouldCreateAttachmentsForClientStreamingWithAsynchronousStub() { Request secondClientRequest = Request.newBuilder().setTopic("B").build(); runWithinTestContext(() -> { - TestServiceGrpc.TestServiceStub asynchronousStub = - TestServiceGrpc.newStub(managedChannel).withInterceptors(new AllureGrpc()); + TestServiceGrpc.TestServiceStub asynchronousStub = TestServiceGrpc.newStub(managedChannel).withInterceptors(new AllureGrpc()); final List receivedResponses = new ArrayList(); @@ -209,16 +218,22 @@ void shouldCreateAttachmentsForBidirectionalStreamingWithAsynchronousStub() { Request secondBidirectionalRequest = Request.newBuilder().setTopic("D").build(); runWithinTestContext(() -> { - TestServiceGrpc.TestServiceStub asynchronousStub = - TestServiceGrpc.newStub(managedChannel).withInterceptors(new AllureGrpc()); + TestServiceGrpc.TestServiceStub asynchronousStub = TestServiceGrpc.newStub(managedChannel).withInterceptors(new AllureGrpc()); List receivedResponses = new ArrayList<>(); Allure.step("async-root-bidi-stream", () -> { StreamObserver responseObserver = new StreamObserver() { - @Override public void onNext(Response value) { receivedResponses.add(value); } - @Override public void onError(Throwable throwable) { } - @Override public void onCompleted() { } + @Override + public void onNext(Response value) { + receivedResponses.add(value); + } + @Override + public void onError(Throwable throwable) { + } + @Override + public void onCompleted() { + } }; StreamObserver requestObserver = asynchronousStub.calculateBidiStream(responseObserver); @@ -235,14 +250,15 @@ void shouldCreateAttachmentsForBidirectionalStreamingWithAsynchronousStub() { @Test void unaryRequestBodyIsCapturedAsJsonObject() throws Exception { - GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()) - .willReturn(Response.newBuilder().setMessage("ok").build())); + GrpcMock.stubFor( + unaryMethod(TestServiceGrpc.getCalculateMethod()) + .willReturn(Response.newBuilder().setMessage("ok").build()) + ); Request request = Request.newBuilder().setTopic("topic-1").build(); AllureResults allureResults = runWithinTestContext(() -> { - TestServiceGrpc.TestServiceBlockingStub stub = - TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + TestServiceGrpc.TestServiceBlockingStub stub = TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); Response response = stub.calculate(request); assertThat(response.getMessage()).isEqualTo("ok"); }); @@ -256,14 +272,15 @@ void unaryRequestBodyIsCapturedAsJsonObject() throws Exception { @Test void unaryResponseBodyIsCapturedAsJsonObject() throws Exception { - GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()) - .willReturn(Response.newBuilder().setMessage("hello-world").build())); + GrpcMock.stubFor( + unaryMethod(TestServiceGrpc.getCalculateMethod()) + .willReturn(Response.newBuilder().setMessage("hello-world").build()) + ); Request request = Request.newBuilder().setTopic("x").build(); AllureResults allureResults = runWithinTestContext(() -> { - TestServiceGrpc.TestServiceBlockingStub stub = - TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + TestServiceGrpc.TestServiceBlockingStub stub = TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); Response response = stub.calculate(request); assertThat(response.getMessage()).isEqualTo("hello-world"); }); @@ -277,17 +294,20 @@ void unaryResponseBodyIsCapturedAsJsonObject() throws Exception { @Test void serverStreamingResponseBodyIsJsonArrayInOrder() throws Exception { - GrpcMock.stubFor(serverStreamingMethod(TestServiceGrpc.getCalculateServerStreamMethod()) - .willReturn(asList( - Response.newBuilder().setMessage("first").build(), - Response.newBuilder().setMessage("second").build() - ))); + GrpcMock.stubFor( + serverStreamingMethod(TestServiceGrpc.getCalculateServerStreamMethod()) + .willReturn( + asList( + Response.newBuilder().setMessage("first").build(), + Response.newBuilder().setMessage("second").build() + ) + ) + ); Request request = Request.newBuilder().setTopic("stream-topic").build(); AllureResults allureResults = runWithinTestContext(() -> { - TestServiceGrpc.TestServiceBlockingStub stub = - TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + TestServiceGrpc.TestServiceBlockingStub stub = TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); Iterator responseIterator = stub.calculateServerStream(request); assertThat(responseIterator.hasNext()).isTrue(); assertThat(responseIterator.next().getMessage()).isEqualTo("first"); @@ -297,7 +317,7 @@ void serverStreamingResponseBodyIsJsonArrayInOrder() throws Exception { }); String jsonPayload = readJsonAttachmentByName( - allureResults, "gRPC response (collection of elements from Server stream) (json)" + allureResults, "gRPC response (collection of elements from Server stream) (json)" ); JsonNode actualJsonArray = JSON.readTree(jsonPayload); @@ -310,10 +330,10 @@ private static String readJsonAttachmentByName(AllureResults allureResults, Stri TestResult test = allureResults.getTestResults().get(0); Attachment matchedAttachment = flattenSteps(test.getSteps()).stream() - .flatMap(step -> step.getAttachments().stream()) - .filter(attachment -> jsonAttachmentName.equals(attachment.getName())) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Attachment not found: " + jsonAttachmentName)); + .flatMap(step -> step.getAttachments().stream()) + .filter(attachment -> jsonAttachmentName.equals(attachment.getName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Attachment not found: " + jsonAttachmentName)); String attachmentSourceKey = matchedAttachment.getSource(); Map attachmentsContent = allureResults.getAttachments(); @@ -327,8 +347,7 @@ private static String readJsonAttachmentByName(AllureResults allureResults, Stri protected final AllureResults executeUnary(Request request) { return runWithinTestContext(() -> { try { - TestServiceGrpc.TestServiceBlockingStub stub = - TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + TestServiceGrpc.TestServiceBlockingStub stub = TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); Response response = stub.calculate(request); assertThat(response.getMessage()).isEqualTo(RESPONSE_MESSAGE); } catch (Exception exception) { @@ -340,8 +359,7 @@ protected final AllureResults executeUnary(Request request) { protected final AllureResults executeServerStreaming(Request request) { return runWithinTestContext(() -> { try { - TestServiceGrpc.TestServiceBlockingStub stub = - TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + TestServiceGrpc.TestServiceBlockingStub stub = TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); Iterator responseIterator = stub.calculateServerStream(request); int responseCount = 0; while (responseIterator.hasNext()) { @@ -356,14 +374,13 @@ protected final AllureResults executeServerStreaming(Request request) { } protected final AllureResults executeUnaryExpectingException(Request request) { - return runWithinTestContext(() -> - assertThatExceptionOfType(StatusRuntimeException.class) - .isThrownBy(() -> { - TestServiceGrpc.TestServiceBlockingStub stub = - TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); - Response response = stub.calculate(request); - assertThat(response.getMessage()).isEqualTo("ok"); - }) + return runWithinTestContext( + () -> assertThatExceptionOfType(StatusRuntimeException.class) + .isThrownBy(() -> { + TestServiceGrpc.TestServiceBlockingStub stub = TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + Response response = stub.calculate(request); + assertThat(response.getMessage()).isEqualTo("ok"); + }) ); } diff --git a/allure-grpc/src/test/resources/allure.properties b/allure-grpc/src/test/resources/allure.properties index 9c0b0a2d..55602943 100644 --- a/allure-grpc/src/test/resources/allure.properties +++ b/allure-grpc/src/test/resources/allure.properties @@ -1,2 +1,3 @@ allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-grpc diff --git a/allure-hamcrest/build.gradle.kts b/allure-hamcrest/build.gradle.kts index 6b0e5dd3..3817ab48 100644 --- a/allure-hamcrest/build.gradle.kts +++ b/allure-hamcrest/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { testImplementation("org.slf4j:slf4j-simple") testImplementation(project(":allure-java-commons-test")) testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } tasks.jar { diff --git a/allure-hamcrest/src/main/java/io/qameta/allure/hamcrest/AllureHamcrestAssert.java b/allure-hamcrest/src/main/java/io/qameta/allure/hamcrest/AllureHamcrestAssert.java index 60153b14..99d60fd9 100644 --- a/allure-hamcrest/src/main/java/io/qameta/allure/hamcrest/AllureHamcrestAssert.java +++ b/allure-hamcrest/src/main/java/io/qameta/allure/hamcrest/AllureHamcrestAssert.java @@ -15,17 +15,17 @@ */ package io.qameta.allure.hamcrest; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.aspectj.lang.annotation.Before; -import org.aspectj.lang.annotation.AfterThrowing; -import org.aspectj.lang.annotation.AfterReturning; import io.qameta.allure.Allure; import io.qameta.allure.AllureLifecycle; import io.qameta.allure.model.Status; import io.qameta.allure.model.StepResult; import io.qameta.allure.util.ObjectUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; import org.hamcrest.Matcher; import org.hamcrest.StringDescription; @@ -47,7 +47,6 @@ * methods in the TypeSafeMatcher class. *

* - * @author a-simeshin (Simeshin Artem) * @see org.hamcrest.TypeSafeMatcher */ @Aspect @@ -61,10 +60,18 @@ protected AllureLifecycle initialValue() { } }; + /** + * Returns the lifecycle. + * + * @return the Allure lifecycle used by this integration + */ public static AllureLifecycle getLifecycle() { return lifecycle.get(); } + /** + * Handles the init assert that callback. + */ @Pointcut("execution(void org.hamcrest.MatcherAssert.**(..))") public void initAssertThat() { } @@ -106,12 +113,24 @@ public void catchAndStartStep(final JoinPoint joinPoint) { } } - @AfterThrowing(pointcut = "initAssertThat()", throwing = "e") + @AfterThrowing( + pointcut = "initAssertThat()", + throwing = "e" + ) + + /** + * Handles the step failed callback. + * + * @param e the e + */ public void stepFailed(final Throwable e) { getLifecycle().updateStep(s -> s.setStatus(getStatus(e).orElse(Status.BROKEN))); getLifecycle().stopStep(); } + /** + * Handles the step stop callback. + */ @AfterReturning(pointcut = "initAssertThat()") public void stepStop() { getLifecycle().updateStep(s -> s.setStatus(Status.PASSED)); diff --git a/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestCollectionsMatchersTest.java b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestCollectionsMatchersTest.java index 21f2e5ee..acdd8900 100644 --- a/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestCollectionsMatchersTest.java +++ b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestCollectionsMatchersTest.java @@ -54,7 +54,7 @@ public class AllureHamcrestCollectionsMatchersTest { @Test void hamcrestAssertNameForArrayMatchers() { final TestResult testResult = runWithinTestContext( - () -> assertThat(new Integer[]{1,2,3}, is(array(equalTo(1), equalTo(2), equalTo(3)))), + () -> assertThat(new Integer[]{1, 2, 3}, is(array(equalTo(1), equalTo(2), equalTo(3)))), AllureHamcrestAssert::setLifecycle ).getTestResults().get(0); diff --git a/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestTextMatchersTest.java b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestTextMatchersTest.java index 6c3932a0..515f6ecb 100644 --- a/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestTextMatchersTest.java +++ b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestTextMatchersTest.java @@ -20,7 +20,6 @@ import org.assertj.core.api.Assertions; import org.hamcrest.Matcher; import org.junit.jupiter.api.TestInstance; - import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; diff --git a/allure-hamcrest/src/test/resources/allure.properties b/allure-hamcrest/src/test/resources/allure.properties new file mode 100644 index 00000000..ee8c853e --- /dev/null +++ b/allure-hamcrest/src/test/resources/allure.properties @@ -0,0 +1,3 @@ +allure.results.directory=build/allure-results +allure.label.epic=#project.description# +allure.label.module=allure-hamcrest diff --git a/allure-httpclient/src/main/java/io/qameta/allure/httpclient/AllureHttpClientRequest.java b/allure-httpclient/src/main/java/io/qameta/allure/httpclient/AllureHttpClientRequest.java index e988f922..1530c63a 100644 --- a/allure-httpclient/src/main/java/io/qameta/allure/httpclient/AllureHttpClientRequest.java +++ b/allure-httpclient/src/main/java/io/qameta/allure/httpclient/AllureHttpClientRequest.java @@ -35,19 +35,31 @@ import static io.qameta.allure.attachment.http.HttpRequestAttachment.Builder.create; /** - * @author charlie (Dmitry Baev). + * Captures Apache HttpClient 4 requests as Allure attachments. + * + *

Register an instance as an {@link org.apache.http.HttpRequestInterceptor} on the client. The default constructor uses the standard request template and writer; the explicit constructor accepts custom rendering and processing components.

*/ public class AllureHttpClientRequest implements HttpRequestInterceptor { private final AttachmentRenderer renderer; private final AttachmentProcessor processor; + /** + * Creates an Allure http client request with default configuration. + */ public AllureHttpClientRequest() { - this(new FreemarkerAttachmentRenderer("http-request.ftl"), - new DefaultAttachmentProcessor() + this( + new FreemarkerAttachmentRenderer("http-request.ftl"), + new DefaultAttachmentProcessor() ); } + /** + * Creates an Allure http client request with the supplied values. + * + * @param renderer the renderer used to turn attachment data into content + * @param processor the processor used to write rendered attachments + */ public AllureHttpClientRequest(final AttachmentRenderer renderer, final AttachmentProcessor processor) { this.renderer = renderer; @@ -55,20 +67,28 @@ public AllureHttpClientRequest(final AttachmentRenderer renderer } private static String getAttachmentName(final HttpRequest request) { - return String.format("Request_%s_%s", request.getRequestLine().getMethod(), - request.getRequestLine().getUri()); + return String.format( + "Request_%s_%s", request.getRequestLine().getMethod(), + request.getRequestLine().getUri() + ); } + /** + * {@inheritDoc} + */ @Override public void process(final HttpRequest request, - final HttpContext context) throws IOException { + final HttpContext context) + throws IOException { - final HttpRequestAttachment.Builder builder = create(getAttachmentName(request), - request.getRequestLine().getUri()) + final HttpRequestAttachment.Builder builder = create( + getAttachmentName(request), + request.getRequestLine().getUri() + ) .setMethod(request.getRequestLine().getMethod()); Stream.of(request.getAllHeaders()) - .forEach(header -> builder.setHeader(header.getName(), header.getValue())); + .forEach(header -> builder.setHeader(header.getName(), header.getValue())); if (request instanceof HttpEntityEnclosingRequest) { final HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); diff --git a/allure-httpclient/src/main/java/io/qameta/allure/httpclient/AllureHttpClientResponse.java b/allure-httpclient/src/main/java/io/qameta/allure/httpclient/AllureHttpClientResponse.java index d34476b6..a1ae607a 100644 --- a/allure-httpclient/src/main/java/io/qameta/allure/httpclient/AllureHttpClientResponse.java +++ b/allure-httpclient/src/main/java/io/qameta/allure/httpclient/AllureHttpClientResponse.java @@ -33,28 +33,44 @@ import static io.qameta.allure.attachment.http.HttpResponseAttachment.Builder.create; /** - * @author charlie (Dmitry Baev). + * Captures Apache HttpClient 4 responses as Allure attachments. + * + *

Register an instance as an {@link org.apache.http.HttpResponseInterceptor} on the client. The default constructor uses the standard response template and writer; the explicit constructor accepts custom rendering and processing components.

*/ public class AllureHttpClientResponse implements HttpResponseInterceptor { private final AttachmentRenderer renderer; private final AttachmentProcessor processor; + /** + * Creates an Allure http client response with default configuration. + */ public AllureHttpClientResponse() { - this(new FreemarkerAttachmentRenderer("http-response.ftl"), + this( + new FreemarkerAttachmentRenderer("http-response.ftl"), new DefaultAttachmentProcessor() ); } + /** + * Creates an Allure http client response with the supplied values. + * + * @param renderer the renderer used to turn attachment data into content + * @param processor the processor used to write rendered attachments + */ public AllureHttpClientResponse(final AttachmentRenderer renderer, final AttachmentProcessor processor) { this.renderer = renderer; this.processor = processor; } + /** + * {@inheritDoc} + */ @Override public void process(final HttpResponse response, - final HttpContext context) throws IOException { + final HttpContext context) + throws IOException { final HttpResponseAttachment.Builder builder = create("Response") .setResponseCode(response.getStatusLine().getStatusCode()); diff --git a/allure-httpclient/src/test/java/io/qameta/allure/httpclient/AllureHttpClientTest.java b/allure-httpclient/src/test/java/io/qameta/allure/httpclient/AllureHttpClientTest.java index 4126ca32..36289202 100644 --- a/allure-httpclient/src/test/java/io/qameta/allure/httpclient/AllureHttpClientTest.java +++ b/allure-httpclient/src/test/java/io/qameta/allure/httpclient/AllureHttpClientTest.java @@ -46,10 +46,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; - -/** - * @author charlie (Dmitry Baev). - */ class AllureHttpClientTest { private static final String BODY_STRING = "Hello world!"; @@ -62,16 +58,26 @@ void setUp() { server.start(); configureFor(server.port()); - stubFor(get(urlEqualTo("/hello")) - .willReturn(aResponse() - .withBody(BODY_STRING))); - - stubFor(get(urlEqualTo("/empty")) - .willReturn(aResponse() - .withStatus(304))); - - stubFor(delete(urlEqualTo("/hello")) - .willReturn(noContent())); + stubFor( + get(urlEqualTo("/hello")) + .willReturn( + aResponse() + .withBody(BODY_STRING) + ) + ); + + stubFor( + get(urlEqualTo("/empty")) + .willReturn( + aResponse() + .withStatus(304) + ) + ); + + stubFor( + delete(urlEqualTo("/hello")) + .willReturn(noContent()) + ); } @AfterEach @@ -169,7 +175,7 @@ void shouldCreateRequestAttachmentWithEmptyBodyWhenNoContentIsReturned() throws final AttachmentProcessor processor = mock(AttachmentProcessor.class); final HttpClientBuilder builder = HttpClientBuilder.create() - .addInterceptorLast(new AllureHttpClientRequest(renderer, processor)); + .addInterceptorLast(new AllureHttpClientRequest(renderer, processor)); try (CloseableHttpClient httpClient = builder.build()) { final HttpDelete httpDelete = new HttpDelete(String.format("http://localhost:%d/hello", server.port())); @@ -195,16 +201,16 @@ void shouldNotConsumeBody() throws Exception { final AttachmentProcessor processor = mock(AttachmentProcessor.class); final HttpClientBuilder builder = HttpClientBuilder.create() - .addInterceptorLast(new AllureHttpClientResponse(renderer, processor)); + .addInterceptorLast(new AllureHttpClientResponse(renderer, processor)); try (CloseableHttpClient httpClient = builder.build()) { - final HttpGet httpGet = new HttpGet(String.format("http://localhost:%d/hello", server.port())); - try (CloseableHttpResponse response = httpClient.execute(httpGet)) { - response.getStatusLine().getStatusCode(); - BufferedHttpEntity ent = new BufferedHttpEntity(response.getEntity()); - assertThat(EntityUtils.toString(ent)) - .isEqualTo(BODY_STRING); - } + final HttpGet httpGet = new HttpGet(String.format("http://localhost:%d/hello", server.port())); + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + response.getStatusLine().getStatusCode(); + BufferedHttpEntity ent = new BufferedHttpEntity(response.getEntity()); + assertThat(EntityUtils.toString(ent)) + .isEqualTo(BODY_STRING); + } } } } diff --git a/allure-httpclient/src/test/resources/allure.properties b/allure-httpclient/src/test/resources/allure.properties index 9c0b0a2d..0b9f016c 100644 --- a/allure-httpclient/src/test/resources/allure.properties +++ b/allure-httpclient/src/test/resources/allure.properties @@ -1,2 +1,3 @@ allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-httpclient diff --git a/allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Request.java b/allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Request.java index 60aff399..75a30ef3 100644 --- a/allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Request.java +++ b/allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Request.java @@ -32,21 +32,37 @@ import static io.qameta.allure.attachment.http.HttpRequestAttachment.Builder.create; /** - * @author a-simeshin (Simeshin Artem) + * Captures Apache HttpClient 5 requests as Allure attachments. + * + *

Register an instance as an {@link org.apache.hc.core5.http.HttpRequestInterceptor}. The interceptor records request metadata and delegates attachment rendering to the configured Allure components.

*/ public class AllureHttpClient5Request implements HttpRequestInterceptor { private final AttachmentRenderer renderer; private final AttachmentProcessor processor; + /** + * Creates an Allure http client5 request with default configuration. + */ public AllureHttpClient5Request() { this("http-request.ftl"); } + /** + * Creates an Allure http client5 request with the supplied values. + * + * @param templateName the template name + */ public AllureHttpClient5Request(final String templateName) { this(new FreemarkerAttachmentRenderer(templateName), new DefaultAttachmentProcessor()); } + /** + * Creates an Allure http client5 request with the supplied values. + * + * @param renderer the renderer used to turn attachment data into content + * @param processor the processor used to write rendered attachments + */ public AllureHttpClient5Request(final AttachmentRenderer renderer, final AttachmentProcessor processor) { this.renderer = renderer; diff --git a/allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Response.java b/allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Response.java index 5c9683b4..c2826041 100644 --- a/allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Response.java +++ b/allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Response.java @@ -35,24 +35,42 @@ import static io.qameta.allure.attachment.http.HttpResponseAttachment.Builder.create; /** - * @author a-simeshin (Simeshin Artem) + * Captures Apache HttpClient 5 responses as Allure attachments. + * + *

Register an instance as an {@link org.apache.hc.core5.http.HttpResponseInterceptor}. The interceptor records response metadata and delegates attachment rendering to the configured Allure components.

*/ -@SuppressWarnings({ - "checkstyle:ParameterAssignment", - "PMD.AvoidReassigningParameters"}) +@SuppressWarnings( + { + "checkstyle:ParameterAssignment", + "PMD.AvoidReassigningParameters"} +) public class AllureHttpClient5Response implements HttpResponseInterceptor { private final AttachmentRenderer renderer; private final AttachmentProcessor processor; private static final String NO_BODY = "No body present"; + /** + * Creates an Allure http client5 response with default configuration. + */ public AllureHttpClient5Response() { this("http-response.ftl"); } + /** + * Creates an Allure http client5 response with the supplied values. + * + * @param templateName the template name + */ public AllureHttpClient5Response(final String templateName) { this(new FreemarkerAttachmentRenderer(templateName), new DefaultAttachmentProcessor()); } + /** + * Creates an Allure http client5 response with the supplied values. + * + * @param renderer the renderer used to turn attachment data into content + * @param processor the processor used to write rendered attachments + */ public AllureHttpClient5Response(final AttachmentRenderer renderer, final AttachmentProcessor processor) { this.renderer = renderer; @@ -71,7 +89,8 @@ public AllureHttpClient5Response(final AttachmentRenderer render @Override public void process(final HttpResponse response, EntityDetails entity, - final HttpContext context) throws IOException { + final HttpContext context) + throws IOException { final HttpResponseAttachment.Builder builder = create("Response"); builder.setResponseCode(response.getCode()); @@ -82,8 +101,7 @@ public void process(final HttpResponse response, // Looks like a bug or completely new logic. It's not enough to replace chaining EntityDetails entity. // To read the response body twice, It needs to put in the context also entity = new BufferedHttpEntity(originalHttpEntity); - final BasicClassicHttpResponse responseEntity = - (BasicClassicHttpResponse) context.getAttribute("http.response"); + final BasicClassicHttpResponse responseEntity = (BasicClassicHttpResponse) context.getAttribute("http.response"); responseEntity.setEntity((HttpEntity) entity); final String responseBody = AllureHttpEntityUtils.getBody((HttpEntity) entity); diff --git a/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5DeleteTest.java b/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5DeleteTest.java index 0b968856..f5de9d23 100644 --- a/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5DeleteTest.java +++ b/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5DeleteTest.java @@ -40,10 +40,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; - -/** - * @author a-simeshin (Simeshin Artem). - */ @SuppressWarnings({"unchecked", "PMD.JUnitTestContainsTooManyAsserts"}) class AllureHttpClient5DeleteTest { @@ -58,9 +54,11 @@ void setUp() { server.start(); configureFor(server.port()); - stubFor(delete(HELLO_RESOURCE_PATH).willReturn( - aResponse() - .withStatus(204)) + stubFor( + delete(HELLO_RESOURCE_PATH).willReturn( + aResponse() + .withStatus(204) + ) ); } diff --git a/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5GetTest.java b/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5GetTest.java index d750d5cc..4edadc80 100644 --- a/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5GetTest.java +++ b/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5GetTest.java @@ -41,10 +41,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; - -/** - * @author a-simeshin (Simeshin Artem). - */ @SuppressWarnings({"unchecked", "PMD.JUnitTestContainsTooManyAsserts"}) class AllureHttpClient5GetTest { @@ -53,7 +49,6 @@ class AllureHttpClient5GetTest { private static final String HELLO_GET_RETURN_BODY = "http://localhost:%d/hello"; private static final String HELLO_GET_201_NO_BODY = "http://localhost:%d/empty"; - private WireMockServer server; @BeforeEach @@ -62,14 +57,18 @@ void setUp() { server.start(); configureFor(server.port()); - stubFor(get(HELLO_RESOURCE_PATH).willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(BODY_STRING) - )); - stubFor(get("/empty").willReturn( - aResponse() - .withStatus(200)) + stubFor( + get(HELLO_RESOURCE_PATH).willReturn( + aResponse() + .withHeader("Content-Type", "application/json") + .withBody(BODY_STRING) + ) + ); + stubFor( + get("/empty").willReturn( + aResponse() + .withStatus(200) + ) ); } diff --git a/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5PostTest.java b/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5PostTest.java index 3346e183..61d43da1 100644 --- a/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5PostTest.java +++ b/allure-httpclient5/src/test/java/io/qameta/allure/httpclient5/AllureHttpClient5PostTest.java @@ -43,10 +43,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; - -/** - * @author a-simeshin (Simeshin Artem). - */ @SuppressWarnings({"unchecked", "PMD.JUnitTestContainsTooManyAsserts"}) class AllureHttpClient5PostTest { @@ -56,7 +52,6 @@ class AllureHttpClient5PostTest { private static final String HELLO_POST_RETURN_BODY = "http://localhost:%d/hello"; private static final String HELLO_POST_201_NO_BODY = "http://localhost:%d/empty"; - private WireMockServer server; @BeforeEach @@ -65,14 +60,18 @@ void setUp() { server.start(); configureFor(server.port()); - stubFor(post(HELLO_RESOURCE_PATH).willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(BODY_STRING) - )); - stubFor(post("/empty").willReturn( - aResponse() - .withStatus(201)) + stubFor( + post(HELLO_RESOURCE_PATH).willReturn( + aResponse() + .withHeader("Content-Type", "application/json") + .withBody(BODY_STRING) + ) + ); + stubFor( + post("/empty").willReturn( + aResponse() + .withStatus(201) + ) ); } diff --git a/allure-httpclient5/src/test/resources/allure.properties b/allure-httpclient5/src/test/resources/allure.properties index 9c0b0a2d..a6feadd7 100644 --- a/allure-httpclient5/src/test/resources/allure.properties +++ b/allure-httpclient5/src/test/resources/allure.properties @@ -1,2 +1,3 @@ allure.results.directory=build/allure-results allure.label.epic=#project.description# +allure.label.module=allure-httpclient5 diff --git a/allure-java-commons-test/build.gradle.kts b/allure-java-commons-test/build.gradle.kts index e1d5c399..92c4e2e0 100644 --- a/allure-java-commons-test/build.gradle.kts +++ b/allure-java-commons-test/build.gradle.kts @@ -6,6 +6,9 @@ dependencies { api("org.apache.commons:commons-lang3") api(project(":allure-java-commons")) implementation("com.fasterxml.jackson.core:jackson-databind") + testImplementation("org.junit.jupiter:junit-jupiter-api") + testImplementation(project(":allure-junit-platform")) + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") } tasks.jar { @@ -15,3 +18,7 @@ tasks.jar { )) } } + +tasks.test { + useJUnitPlatform() +} diff --git a/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureFeatures.java b/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureFeatures.java index e3706d29..50e84637 100644 --- a/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureFeatures.java +++ b/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureFeatures.java @@ -25,7 +25,9 @@ import java.lang.annotation.Target; /** - * @author charlie (Dmitry Baev). + * Integrates Allure Java test support with Allure reporting. + * + *

Register this type through the standard Allure Java test support extension, listener, interceptor, or plugin mechanism so framework execution events are written to Allure results. Use explicit dependencies when embedding the integration in tests or custom runtimes.

*/ @SuppressWarnings({"JavadocType"}) @Target({}) diff --git a/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllurePredicates.java b/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllurePredicates.java index cfb24b3d..2a5b7b00 100644 --- a/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllurePredicates.java +++ b/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllurePredicates.java @@ -23,7 +23,9 @@ import java.util.function.Predicate; /** - * @author charlie (Dmitry Baev). + * Integrates Allure Java test support with Allure reporting. + * + *

Register this type through the standard Allure Java test support extension, listener, interceptor, or plugin mechanism so framework execution events are written to Allure results. Use explicit dependencies when embedding the integration in tests or custom runtimes.

*/ public final class AllurePredicates { @@ -31,10 +33,23 @@ private AllurePredicates() { throw new IllegalStateException("Do not instance"); } + /** + * Returns whether status is available. + * + * @param status the response status + * @return true when status; false otherwise + */ public static Predicate hasStatus(final Status status) { return testResult -> status.equals(testResult.getStatus()); } + /** + * Returns whether label is available. + * + * @param name the display name or logical name to use + * @param value the value to set + * @return true when label; false otherwise + */ public static Predicate hasLabel(final String name, final String value) { final Predicate