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..fd415585 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 compatibility alias 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..7ba05d2f 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,28 +17,22 @@ 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; /** + * Captures user-side AssertJ factories and fluent calls, then delegates assertion-chain state + * to {@link AssertJRecorder}. + * * @author charlie (Dmitry Baev). * @author sskorol (Sergey Korol). */ @@ -46,8 +40,6 @@ @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 +47,83 @@ 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*(..))" + + ")" + ) + 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)" + ) + public void assertOperationCall(final AbstractAssert assertion) { //pointcut body, should be empty } - @Pointcut("execution(public * org.assertj.core.api.AbstractAssert+.*(..)) && !proxyMethod()") - public void anyAssert() { + @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); - - final StepResult result = new StepResult() - .setName(name) - .setStatus(Status.PASSED); + @AfterReturning( + pointcut = "assertFactoryCall() && userCodeCall()", + returning = "result" + ) + 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); - - getLifecycle().startStep(uuid, result); - } + @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(); + } - @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(); + 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; + } } - @AfterReturning(pointcut = "anyAssert()") - public void stepStop() { - getLifecycle().updateStep(s -> s.setStatus(Status.PASSED)); - getLifecycle().stopStep(); + @After( + "execution(public void org.assertj.core.api.DefaultAssertionErrorCollector.collectAssertionError(" + + "java.lang.AssertionError)) && args(error)" + ) + public void softAssertionFailed(final AssertionError error) { + getRecorder().softAssertionFailed(error); } /** @@ -122,15 +133,40 @@ public void stepStop() { */ public static void setLifecycle(final AllureLifecycle allure) { lifecycle.set(allure); + clearContext(); } public static AllureLifecycle getLifecycle() { return lifecycle.get(); } - private static String arrayToString(final Object... array) { - return Stream.of(array) - .map(ObjectUtils::toString) - .collect(Collectors.joining(" ")); + 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..c9ea7a1e --- /dev/null +++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJLifecycleListener.java @@ -0,0 +1,44 @@ +/* + * 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 { + + @Override + public void afterTestWrite(final TestResult result) { + AllureAspectJ.clearContext(); + } + + @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..3e33ae78 --- /dev/null +++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AssertJValueRenderer.java @@ -0,0 +1,558 @@ +/* + * 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) { + 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..71e14e7c 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,18 +15,27 @@ */ 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; +import static org.assertj.core.api.Assertions.tuple; /** * @author charlie (Dmitry Baev). @@ -35,90 +44,461 @@ 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("assert Nullable object"); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) + .extracting(StepResult::getName) + .containsExactly("described as \"Nullable object\"", "is null"); + } + + @AllureFeatures.Steps + @Test + void shouldRenderByteArraysWithoutPayload() { + final String value = "some string"; + final AllureResults results = runWithinTestContext(() -> { + assertThat(value.getBytes(StandardCharsets.UTF_8)) + .as("Byte array object") + .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 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 shouldCreateSeparateChainsForMultipleAssertThatCalls() { + final AllureResults results = runWithinTestContext(() -> { + assertThat("Data") + .hasSize(4); + + assertThat(42) + .isPositive() + .isEqualTo(42); + + assertThat(Arrays.asList("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( - "assertThat 'null'", - "as 'Nullable object []'", - "isNull" + "has size 4", + "is positive", + "is equal to 42", + "has size 2", + "contains \"a\"" ); } @AllureFeatures.Steps @Test - void shouldHandleByteArrayObject() { - final String s = "some string"; + void shouldAttachOperationsToStoredAssertionInstances() { + final String targetA = "alpha"; + final String targetB = "bravo"; + final AllureResults results = runWithinTestContext(() -> { - assertThat(s.getBytes(StandardCharsets.UTF_8)) - .as("Byte array object") - .isEqualTo(s.getBytes(StandardCharsets.UTF_8)); + 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); + + 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 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); - assertThat(results.getTestResults()) - .flatExtracting(TestResult::getSteps) + final TestResult result = assertOnlyOneResult(results); + assertThat(result.getSteps()) + .hasSize(5); + assertThat(result.getSteps()) + .flatExtracting(StepResult::getSteps) .extracting(StepResult::getName) .containsExactly( - "assertThat ''", - "describedAs 'Byte array object'", - "isEqualTo ''" + "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 shouldHandleCollections() { + void shouldRenderSerializedLambdaMethodReferences() { + final TestResult model = new TestResult() + .setFullName("my.company.Test.testOne"); + final AllureResults results = runWithinTestContext(() -> { - assertThat(Arrays.asList("a", "b")) - .containsExactly("a", "b"); + 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); - 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, 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 softAssertions() { + 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/AttachmentRenderer.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderer.java index 15423fc8..44029cc8 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 @@ -19,7 +19,6 @@ * @param the type of attachment data * @author charlie (Dmitry Baev). */ -@SuppressWarnings("PMD.AvoidUncheckedExceptionsInSignatures") @FunctionalInterface public interface AttachmentRenderer { 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..3c37c53a 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 @@ -35,7 +35,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/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..a19e5e31 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 @@ -77,8 +77,7 @@ public class AllureAwaitilityListener implements ConditionEvaluationListener LIFECYCLE - = new InheritableThreadLocal() { + private static final InheritableThreadLocal LIFECYCLE = new InheritableThreadLocal() { @Override protected AllureLifecycle initialValue() { return Allure.getLifecycle(); @@ -225,7 +224,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 +239,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/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..b50ec45d 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,6 +59,7 @@ 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; /** @@ -161,25 +162,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 +224,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 +271,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..eb4b05d2 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 @@ -61,6 +61,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/io/qameta/allure/cucumber4jvm/AllureCucumber4Jvm.java b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/AllureCucumber4Jvm.java index 967ee1a3..7cfdc67e 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,6 +71,8 @@ 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; @@ -78,11 +80,14 @@ /** * Allure plugin for Cucumber JVM 4.0. */ -@SuppressWarnings({ - "ClassDataAbstractionCoupling", - "ClassFanOutComplexity", - "MultipleStringLiterals", -}) +@SuppressWarnings( + { + "ClassDataAbstractionCoupling", + "ClassFanOutComplexity", + "MultipleStringLiterals", + "PMD.GodClass", + } +) public class AllureCucumber4Jvm implements ConcurrentEventListener { private static final String COLON = ":"; @@ -144,11 +149,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 +161,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 +217,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 +306,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 +411,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 +457,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 +481,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 +505,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..640148a1 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 @@ -77,6 +77,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 +182,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 +199,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 +237,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 +381,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 +517,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 +660,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 +701,6 @@ void shouldDisplayHooksAsStages() { tuple("Then result is 15", Status.SKIPPED) ); - assertThat(results.getTestResultContainersForTestResult(tr2)) .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) @@ -734,8 +746,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 +798,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 +809,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 +833,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..07de408c 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 @@ -34,7 +34,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/DatatableFeatureSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/DatatableFeatureSteps.java index 158b5ece..235af032 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,8 +15,8 @@ */ package io.qameta.allure.cucumber4jvm.samples; -import io.cucumber.java.en.Given; import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Given; /** * @author charlie (Dmitry Baev). 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..9c4666fa 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 @@ -25,22 +25,22 @@ 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/RuntimeApiSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/RuntimeApiSteps.java index 4c768742..16ac1320 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 @@ -27,12 +27,12 @@ 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/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..682cc378 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,6 +67,8 @@ 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; @@ -74,11 +76,14 @@ /** * Allure plugin for Cucumber JVM 5.0. */ -@SuppressWarnings({ - "ClassDataAbstractionCoupling", - "ClassFanOutComplexity", - "MultipleStringLiterals", -}) +@SuppressWarnings( + { + "ClassDataAbstractionCoupling", + "ClassFanOutComplexity", + "MultipleStringLiterals", + "PMD.GodClass", + } +) public class AllureCucumber5Jvm implements ConcurrentEventListener { private static final String COLON = ":"; @@ -139,31 +144,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 +207,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 +278,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 +364,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 +409,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 +433,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 +457,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..7249c97a 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,6 +28,7 @@ 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; @@ -38,7 +39,8 @@ public final class TestSourcesModel { private final Map> pathToNodeMap = new HashMap<>(); 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; } @@ -73,8 +75,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 +87,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/test/java/io/qameta/allure/cucumber5jvm/AllureCucumber5JvmTest.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/AllureCucumber5JvmTest.java index 7456106a..11f28271 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 @@ -80,6 +80,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 +185,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 +202,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 +240,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 +384,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 +520,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 +663,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 +704,6 @@ void shouldDisplayHooksAsStages() { tuple("Then result is 15", Status.SKIPPED) ); - assertThat(results.getTestResultContainersForTestResult(tr2)) .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) @@ -737,8 +749,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 +801,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 +824,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 +837,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..fe41e55b 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 @@ -34,7 +34,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/HookSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/HookSteps.java index 775d9086..8c7162e7 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 @@ -25,22 +25,22 @@ 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/RuntimeApiSteps.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/samples/RuntimeApiSteps.java index 581b2afe..f507b64a 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 @@ -27,12 +27,12 @@ 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/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..c7384b9b 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,6 +65,8 @@ 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; @@ -72,13 +74,16 @@ /** * Allure plugin for Cucumber JVM 6.0. */ -@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; @@ -134,31 +139,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 +177,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 +202,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 +265,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 +349,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 +388,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 +414,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 +432,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/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java index 75cc8495..6788dc6c 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 @@ -80,6 +80,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 +185,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 +202,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 +240,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 +384,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 +520,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 +663,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 +704,6 @@ void shouldDisplayHooksAsStages() { tuple("Then result is 15", Status.SKIPPED) ); - assertThat(results.getTestResultContainersForTestResult(tr2)) .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) @@ -737,8 +749,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 +801,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 +824,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..22a70c07 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 @@ -34,7 +34,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/HookSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/HookSteps.java index d7fe2a8e..5ca9cc61 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 @@ -25,22 +25,22 @@ 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/RuntimeApiSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/RuntimeApiSteps.java index 7438ea19..4fd9281d 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 @@ -27,12 +27,12 @@ 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/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..ac4ca986 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,6 +66,8 @@ 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; @@ -73,13 +75,16 @@ /** * Allure plugin for Cucumber JVM 7.0. */ -@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; @@ -135,31 +140,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 +178,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 +203,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 +266,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 +350,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 +379,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 +402,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 +428,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 +446,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/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java index 0d8a20a7..ea50e510 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 @@ -80,6 +80,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 +185,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 +202,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 +240,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 +384,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 +520,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 +663,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 +704,6 @@ void shouldDisplayHooksAsStages() { tuple("Then result is 15", Status.SKIPPED) ); - assertThat(results.getTestResultContainersForTestResult(tr2)) .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) @@ -737,8 +749,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 +801,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 +812,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 +825,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..b5fec46c 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 @@ -34,7 +34,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/HookSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/HookSteps.java index b85751af..46ca418a 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 @@ -25,22 +25,22 @@ 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/RuntimeApiSteps.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/RuntimeApiSteps.java index e76d71ac..50eabea7 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,9 +16,9 @@ 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; /** @@ -27,12 +27,12 @@ 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/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/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..289c5f43 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; @@ -96,14 +97,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..b82f4c47 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,28 @@ 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" + + "@author Jane Doe\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..49ab0437 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 @@ -237,10 +237,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\")" ); } @@ -290,12 +290,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..73c0fa52 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. * * @author dtuchs (Dmitry Tuchs). */ -@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); @@ -69,17 +73,18 @@ public class AllureGrpc implements ClientInterceptor { private final String responseTemplatePath; public AllureGrpc() { - this(Allure.getLifecycle(), true, false, - "grpc-request.ftl", "grpc-response.ftl"); + this( + Allure.getLifecycle(), true, false, + "grpc-request.ftl", "grpc-response.ftl" + ); } 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; @@ -89,10 +94,9 @@ public AllureGrpc( @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 +113,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 +158,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 +248,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 +293,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 +305,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 +332,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 +349,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 +375,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 +405,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 +414,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 +432,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/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..4a1782ed 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; @@ -106,7 +106,10 @@ public void catchAndStartStep(final JoinPoint joinPoint) { } } - @AfterThrowing(pointcut = "initAssertThat()", throwing = "e") + @AfterThrowing( + pointcut = "initAssertThat()", + throwing = "e" + ) public void stepFailed(final Throwable e) { getLifecycle().updateStep(s -> s.setStatus(getStatus(e).orElse(Status.BROKEN))); getLifecycle().stopStep(); 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..833f45bc 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 @@ -43,8 +43,9 @@ public class AllureHttpClientRequest implements HttpRequestInterceptor { private final AttachmentProcessor processor; public AllureHttpClientRequest() { - this(new FreemarkerAttachmentRenderer("http-request.ftl"), - new DefaultAttachmentProcessor() + this( + new FreemarkerAttachmentRenderer("http-request.ftl"), + new DefaultAttachmentProcessor() ); } @@ -55,20 +56,25 @@ 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() + ); } @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..5e8daec1 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 @@ -41,7 +41,8 @@ public class AllureHttpClientResponse implements HttpResponseInterceptor { private final AttachmentProcessor processor; public AllureHttpClientResponse() { - this(new FreemarkerAttachmentRenderer("http-response.ftl"), + this( + new FreemarkerAttachmentRenderer("http-response.ftl"), new DefaultAttachmentProcessor() ); } @@ -54,7 +55,8 @@ public AllureHttpClientResponse(final AttachmentRenderer rendere @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..8ed24f1f 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 @@ -62,16 +62,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 +179,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 +205,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/AllureHttpClient5Response.java b/allure-httpclient5/src/main/java/io/qameta/allure/httpclient5/AllureHttpClient5Response.java index 5c9683b4..90cdd03a 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 @@ -37,9 +37,11 @@ /** * @author a-simeshin (Simeshin Artem) */ -@SuppressWarnings({ - "checkstyle:ParameterAssignment", - "PMD.AvoidReassigningParameters"}) +@SuppressWarnings( + { + "checkstyle:ParameterAssignment", + "PMD.AvoidReassigningParameters"} +) public class AllureHttpClient5Response implements HttpResponseInterceptor { private final AttachmentRenderer renderer; private final AttachmentProcessor processor; @@ -71,7 +73,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 +85,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..89ad99f7 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 @@ -58,9 +58,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..468f2580 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 @@ -53,7 +53,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 +61,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..e2a375df 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 @@ -56,7 +56,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 +64,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/AllureResults.java b/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureResults.java index bc922572..d41aacf9 100644 --- a/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureResults.java +++ b/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureResults.java @@ -39,9 +39,11 @@ default TestResult getTestResultByName(final String name) { return getTestResults().stream() .filter(tr -> Objects.equals(name, tr.getName())) .findFirst() - .orElseThrow(() -> new NoSuchElementException( - "test result with name " + name + " is not found" - )); + .orElseThrow( + () -> new NoSuchElementException( + "test result with name " + name + " is not found" + ) + ); } default List getTestResultContainersForTestResult(final TestResult testResult) { diff --git a/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureTestCommonsUtils.java b/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureTestCommonsUtils.java index 6a00c5c4..1065f811 100644 --- a/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureTestCommonsUtils.java +++ b/allure-java-commons-test/src/main/java/io/qameta/allure/test/AllureTestCommonsUtils.java @@ -41,15 +41,21 @@ */ public final class AllureTestCommonsUtils { + private static final String DOT = "."; + private static final String JSON_EXTENSION = "json"; + private static final String JSON_TYPE = "application/json"; + private static final String TEXT_EXTENSION = "txt"; + private static final String TEXT_TYPE = "text/plain"; private static final ObjectWriter WRITER = JsonMapper .builder() .configure(USE_WRAPPER_NAME_AS_PROPERTY_NAME, true) .serializationInclusion(NON_DEFAULT) .build() - .registerModule(new SimpleModule() - .addSerializer(Status.class, new StatusSerializer()) - .addSerializer(Stage.class, new StageSerializer()) - .addSerializer(Parameter.Mode.class, new ParameterModeSerializer()) + .registerModule( + new SimpleModule() + .addSerializer(Status.class, new StatusSerializer()) + .addSerializer(Stage.class, new StageSerializer()) + .addSerializer(Parameter.Mode.class, new ParameterModeSerializer()) ) .writerWithDefaultPrettyPrinter(); @@ -65,7 +71,9 @@ public static void attach(final AllureResults allureResults) { try { Allure.addAttachment( testResult.getUuid() + AllureConstants.TEST_RESULT_FILE_SUFFIX, - WRITER.writeValueAsString(testResult) + JSON_TYPE, + WRITER.writeValueAsString(testResult), + JSON_EXTENSION ); } catch (JsonProcessingException e) { throw new UncheckedIOException(e); @@ -76,21 +84,44 @@ public static void attach(final AllureResults allureResults) { try { Allure.addAttachment( container.getUuid() + AllureConstants.TEST_RESULT_CONTAINER_FILE_SUFFIX, - WRITER.writeValueAsString(container) + JSON_TYPE, + WRITER.writeValueAsString(container), + JSON_EXTENSION ); } catch (JsonProcessingException e) { throw new UncheckedIOException(e); } }); - allureResults.getAttachments().forEach((fileName, body) -> Allure - .addAttachment( - fileName, - new ByteArrayInputStream(body) - ) + allureResults.getAttachments().forEach( + (fileName, body) -> Allure + .addAttachment( + fileName, + type(fileName), + new ByteArrayInputStream(body), + extension(fileName) + ) ); } + private static String type(final String fileName) { + if (fileName.endsWith(DOT + JSON_EXTENSION)) { + return JSON_TYPE; + } + if (fileName.endsWith(DOT + TEXT_EXTENSION)) { + return TEXT_TYPE; + } + return null; + } + + private static String extension(final String fileName) { + final int index = fileName.lastIndexOf('.'); + if (index < 0 || index == fileName.length() - 1) { + return null; + } + return fileName.substring(index + 1); + } + /** * Parameter mode serializer. */ @@ -102,7 +133,8 @@ protected ParameterModeSerializer() { @Override public void serialize(final Parameter.Mode value, final JsonGenerator gen, - final SerializerProvider provider) throws IOException { + final SerializerProvider provider) + throws IOException { gen.writeString(value.name().toLowerCase(Locale.ENGLISH)); } } @@ -118,7 +150,8 @@ protected StageSerializer() { @Override public void serialize(final Stage value, final JsonGenerator gen, - final SerializerProvider provider) throws IOException { + final SerializerProvider provider) + throws IOException { gen.writeString(value.name().toLowerCase(Locale.ENGLISH)); } } @@ -134,7 +167,8 @@ protected StatusSerializer() { @Override public void serialize(final Status value, final JsonGenerator gen, - final SerializerProvider provider) throws IOException { + final SerializerProvider provider) + throws IOException { gen.writeString(value.name().toLowerCase(Locale.ENGLISH)); } } diff --git a/allure-java-commons-test/src/main/java/io/qameta/allure/test/RunUtils.java b/allure-java-commons-test/src/main/java/io/qameta/allure/test/RunUtils.java index d28a685e..6faf9566 100644 --- a/allure-java-commons-test/src/main/java/io/qameta/allure/test/RunUtils.java +++ b/allure-java-commons-test/src/main/java/io/qameta/allure/test/RunUtils.java @@ -41,7 +41,7 @@ private RunUtils() { } public static AllureResults runTests( - final Allure.ThrowableContextRunnableVoid runnable) { + final Allure.ThrowableContextRunnableVoid runnable) { return runTests( runnable, Allure::setLifecycle, @@ -51,8 +51,8 @@ public static AllureResults runTests( } public static AllureResults runTests( - final Function lifecycleFactory, - final Allure.ThrowableContextRunnableVoid runnable) { + final Function lifecycleFactory, + final Allure.ThrowableContextRunnableVoid runnable) { return runTests( lifecycleFactory, runnable, @@ -64,16 +64,16 @@ public static AllureResults runTests( @SafeVarargs public static AllureResults runTests( - final Allure.ThrowableContextRunnableVoid runnable, - final Consumer... configurers) { + final Allure.ThrowableContextRunnableVoid runnable, + final Consumer... configurers) { return runTests(AllureLifecycle::new, runnable, configurers); } @SafeVarargs public static AllureResults runTests( - final Function lifecycleFactory, - final Allure.ThrowableContextRunnableVoid runnable, - final Consumer... configurers) { + final Function lifecycleFactory, + final Allure.ThrowableContextRunnableVoid runnable, + final Consumer... configurers) { final AllureResultsWriterStub writer = new AllureResultsWriterStub(); final AllureLifecycle lifecycle = lifecycleFactory.apply(writer); @@ -94,28 +94,28 @@ public static AllureResults runTests( } public static AllureResults runWithinTestContext( - final Runnable runnable) { + final Runnable runnable) { return runTests(lifecycle -> withTestContext(runnable, lifecycle)); } public static AllureResults runWithinTestContext( - final Function lifecycleFactory, - final Runnable runnable) { + final Function lifecycleFactory, + final Runnable runnable) { return runTests(lifecycleFactory, lifecycle -> withTestContext(runnable, lifecycle)); } @SafeVarargs public static AllureResults runWithinTestContext( - final Runnable runnable, - final Consumer... configurers) { + final Runnable runnable, + final Consumer... configurers) { return runTests(lifecycle -> withTestContext(runnable, lifecycle), configurers); } @SafeVarargs public static AllureResults runWithinTestContext( - final Function lifecycleFactory, - final Runnable runnable, - final Consumer... configurers) { + final Function lifecycleFactory, + final Runnable runnable, + final Consumer... configurers) { return runTests(lifecycleFactory, lifecycle -> withTestContext(runnable, lifecycle), configurers); } diff --git a/allure-java-commons-test/src/test/java/io/qameta/allure/test/AllurePredicatesTest.java b/allure-java-commons-test/src/test/java/io/qameta/allure/test/AllurePredicatesTest.java new file mode 100644 index 00000000..4f5048a5 --- /dev/null +++ b/allure-java-commons-test/src/test/java/io/qameta/allure/test/AllurePredicatesTest.java @@ -0,0 +1,41 @@ +/* + * 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.test; + +import io.qameta.allure.model.Label; +import io.qameta.allure.model.Status; +import io.qameta.allure.model.TestResult; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AllurePredicatesTest { + + @Test + void shouldMatchStatusAndLabels() { + final TestResult result = new TestResult() + .setStatus(Status.PASSED) + .setLabels(List.of(new Label().setName("feature").setValue("attachments"))); + + assertTrue(AllurePredicates.hasStatus(Status.PASSED).test(result)); + assertTrue(AllurePredicates.hasLabel("feature", "attachments").test(result)); + assertFalse(AllurePredicates.hasStatus(Status.FAILED).test(result)); + assertFalse(AllurePredicates.hasLabel("feature", "steps").test(result)); + } +} diff --git a/allure-java-commons-test/src/test/java/io/qameta/allure/test/AllureResultsWriterStubTest.java b/allure-java-commons-test/src/test/java/io/qameta/allure/test/AllureResultsWriterStubTest.java new file mode 100644 index 00000000..607d7013 --- /dev/null +++ b/allure-java-commons-test/src/test/java/io/qameta/allure/test/AllureResultsWriterStubTest.java @@ -0,0 +1,55 @@ +/* + * 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.test; + +import io.qameta.allure.Allure; +import io.qameta.allure.model.TestResult; +import io.qameta.allure.model.TestResultContainer; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +class AllureResultsWriterStubTest { + + @Test + void shouldStoreResultsContainersAndAttachments() { + final AllureResultsWriterStub writer = new AllureResultsWriterStub(); + final TestResult testResult = new TestResult() + .setUuid("test-uuid") + .setName("demo"); + final TestResultContainer container = new TestResultContainer() + .setUuid("container-uuid") + .setChildren(List.of("test-uuid")); + + Allure.step("Store a test result, its container, and an attachment", () -> { + writer.write(testResult); + writer.write(container); + writer.write("payload.txt", new ByteArrayInputStream("payload".getBytes(StandardCharsets.UTF_8))); + }); + + Allure.step("Verify the stub exposes the written runtime artifacts", () -> { + assertSame(testResult, writer.getTestResultByName("demo")); + assertEquals(List.of(container), writer.getTestResultContainersForTestResult(testResult)); + assertArrayEquals("payload".getBytes(StandardCharsets.UTF_8), writer.getAttachments().get("payload.txt")); + }); + } +} diff --git a/allure-java-commons-test/src/test/java/io/qameta/allure/test/RunUtilsTest.java b/allure-java-commons-test/src/test/java/io/qameta/allure/test/RunUtilsTest.java new file mode 100644 index 00000000..265ae9f9 --- /dev/null +++ b/allure-java-commons-test/src/test/java/io/qameta/allure/test/RunUtilsTest.java @@ -0,0 +1,60 @@ +/* + * 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.test; + +import io.qameta.allure.Allure; +import io.qameta.allure.model.Status; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RunUtilsTest { + + @Test + void shouldCaptureFailureStatusWithinSyntheticTestContext() { + final AllureResults results = Allure.step("Execute a synthetic test context that raises an assertion error", () -> RunUtils.runWithinTestContext(() -> { + throw new AssertionError("boom"); + }) + ); + + Allure.step("Verify the captured synthetic test result is marked as failed", () -> { + assertEquals(1, results.getTestResults().size()); + assertEquals(Status.FAILED, results.getTestResults().get(0).getStatus()); + assertTrue(results.getTestResults().get(0).getStatusDetails().getMessage().contains("boom")); + }); + } + + @Test + void shouldAttachNestedRunArtifactsToOuterLifecycle() { + final AllureResults results = Allure + .step("Execute a nested synthetic run and capture its emitted attachments", () -> RunUtils.runWithinTestContext(() -> RunUtils.runWithinTestContext(() -> { + }) + ) + ); + + Allure.addAttachment("nested-attachment-keys", String.join("\n", results.getAttachments().keySet())); + Allure.step("Verify the outer lifecycle receives serialized artifacts from the nested run", () -> { + assertFalse(results.getAttachments().isEmpty()); + assertTrue( + results.getAttachments().values().stream() + .map(bytes -> new String(bytes, java.nio.charset.StandardCharsets.UTF_8)) + .anyMatch(body -> body.contains("\"uuid\"")) + ); + }); + } +} diff --git a/allure-java-commons-test/src/test/java/io/qameta/allure/test/TestUtilitiesTest.java b/allure-java-commons-test/src/test/java/io/qameta/allure/test/TestUtilitiesTest.java new file mode 100644 index 00000000..2ecb6292 --- /dev/null +++ b/allure-java-commons-test/src/test/java/io/qameta/allure/test/TestUtilitiesTest.java @@ -0,0 +1,65 @@ +/* + * 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.test; + +import io.github.benas.randombeans.api.EnhancedRandom; +import io.qameta.allure.Allure; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TestUtilitiesTest { + + @Test + void shouldGenerateStableThreadLocalRandomPerThread() throws Exception { + final EnhancedRandom mainThread = ThreadLocalEnhancedRandom.current(); + final AtomicReference workerThread = new AtomicReference<>(); + final Thread thread = new Thread( + () -> workerThread.set(ThreadLocalEnhancedRandom.current()) + ); + + Allure.step("Resolve thread-local random generators on two threads and compare their identities", () -> { + thread.start(); + thread.join(); + Allure.addAttachment( + "thread-local-random-identities", + "main=" + System.identityHashCode(mainThread) + + "\nworker=" + System.identityHashCode(workerThread.get()) + ); + assertSame(mainThread, ThreadLocalEnhancedRandom.current()); + assertNotSame(mainThread, workerThread.get()); + }); + } + + @Test + void shouldGenerateExpectedRandomTestDataShapes() { + final String name = TestData.randomName(); + final String id = TestData.randomId(); + final String value = TestData.randomString(16); + + assertEquals(10, name.length()); + assertEquals(10, id.length()); + assertEquals(16, value.length()); + assertTrue(name.matches("[A-Za-z]+")); + assertTrue(id.matches("[A-Za-z0-9]+")); + assertTrue(value.matches("[A-Za-z0-9]+")); + } +} diff --git a/allure-java-commons-test/src/test/resources/allure.properties b/allure-java-commons-test/src/test/resources/allure.properties new file mode 100644 index 00000000..c1b2f8a0 --- /dev/null +++ b/allure-java-commons-test/src/test/resources/allure.properties @@ -0,0 +1,3 @@ +allure.results.directory=build/allure-results +allure.label.epic=#project.description# +allure.label.module=allure-java-commons-test diff --git a/allure-java-commons/src/main/java/io/qameta/allure/Allure.java b/allure-java-commons/src/main/java/io/qameta/allure/Allure.java index 25092818..6b9d32a8 100644 --- a/allure-java-commons/src/main/java/io/qameta/allure/Allure.java +++ b/allure-java-commons/src/main/java/io/qameta/allure/Allure.java @@ -45,6 +45,7 @@ /** * The class contains some useful methods to work with {@link AllureLifecycle}. */ +@SuppressWarnings("PMD.TooManyMethods") public final class Allure { private static final String TXT_EXTENSION = ".txt"; @@ -181,9 +182,11 @@ public static T step(final ThrowableContextRunnable runnable getLifecycle().updateStep(uuid, step -> step.setStatus(Status.PASSED)); return result; } catch (Throwable throwable) { - getLifecycle().updateStep(s -> s - .setStatus(getStatus(throwable).orElse(Status.BROKEN)) - .setStatusDetails(getStatusDetails(throwable).orElse(null))); + getLifecycle().updateStep( + s -> s + .setStatus(getStatus(throwable).orElse(Status.BROKEN)) + .setStatusDetails(getStatusDetails(throwable).orElse(null)) + ); throw ExceptionUtils.sneakyThrow(throwable); } finally { getLifecycle().stopStep(uuid); @@ -458,24 +461,23 @@ public static void addAttachment(final String name, final String type, } public static CompletableFuture addByteAttachmentAsync( - final String name, final String type, final Supplier body) { + final String name, final String type, final Supplier body) { return addByteAttachmentAsync(name, type, "", body); } public static CompletableFuture addByteAttachmentAsync( - final String name, final String type, final String fileExtension, final Supplier body) { + final String name, final String type, final String fileExtension, final Supplier body) { final String source = getLifecycle().prepareAttachment(name, type, fileExtension); - return supplyAsync(body).whenComplete((result, ex) -> - getLifecycle().writeAttachment(source, new ByteArrayInputStream(result))); + return supplyAsync(body).whenComplete((result, ex) -> getLifecycle().writeAttachment(source, new ByteArrayInputStream(result))); } public static CompletableFuture addStreamAttachmentAsync( - final String name, final String type, final Supplier body) { + final String name, final String type, final Supplier body) { return addStreamAttachmentAsync(name, type, "", body); } public static CompletableFuture addStreamAttachmentAsync( - final String name, final String type, final String fileExtension, final Supplier body) { + final String name, final String type, final String fileExtension, final Supplier body) { final String source = lifecycle.prepareAttachment(name, type, fileExtension); return supplyAsync(body).whenComplete((result, ex) -> lifecycle.writeAttachment(source, result)); } diff --git a/allure-java-commons/src/main/java/io/qameta/allure/AllureLifecycle.java b/allure-java-commons/src/main/java/io/qameta/allure/AllureLifecycle.java index dc3f9d3b..46e7d447 100644 --- a/allure-java-commons/src/main/java/io/qameta/allure/AllureLifecycle.java +++ b/allure-java-commons/src/main/java/io/qameta/allure/AllureLifecycle.java @@ -49,7 +49,7 @@ /** * The class contains Allure context and methods to change it. */ -@SuppressWarnings("PMD.AvoidSynchronizedStatement") +@SuppressWarnings({"PMD.AvoidSynchronizedStatement", "PMD.TooManyMethods"}) public class AllureLifecycle { private static final Logger LOGGER = LoggerFactory.getLogger(AllureLifecycle.class); @@ -641,9 +641,11 @@ private static FileSystemResultsWriter getDefaultWriter() { final Properties properties = PropertiesUtils.loadAllureProperties(); final String path = properties.getProperty("allure.results.directory", "allure-results"); final boolean cleanBeforeRun = Boolean.parseBoolean( - properties.getProperty("allure.results.clean.before.run", "false")); + properties.getProperty("allure.results.clean.before.run", "false") + ); final boolean cleanOnlyOnce = Boolean.parseBoolean( - properties.getProperty("allure.results.clean.only.once", "true")); + properties.getProperty("allure.results.clean.only.once", "true") + ); return new FileSystemResultsWriter(Paths.get(path), cleanBeforeRun, cleanOnlyOnce); } diff --git a/allure-java-commons/src/main/java/io/qameta/allure/FileSystemResultsWriter.java b/allure-java-commons/src/main/java/io/qameta/allure/FileSystemResultsWriter.java index f4b0e331..14216c0a 100644 --- a/allure-java-commons/src/main/java/io/qameta/allure/FileSystemResultsWriter.java +++ b/allure-java-commons/src/main/java/io/qameta/allure/FileSystemResultsWriter.java @@ -29,8 +29,8 @@ import java.util.Comparator; import java.util.Objects; import java.util.UUID; -import java.util.stream.Stream; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; /** * @author charlie (Dmitry Baev). @@ -54,8 +54,8 @@ public FileSystemResultsWriter(final Path outputDirectory) { } public FileSystemResultsWriter(final Path outputDirectory, - final boolean cleanBeforeRun, - final boolean cleanOnlyOnce) { + final boolean cleanBeforeRun, + final boolean cleanOnlyOnce) { this.outputDirectory = outputDirectory; this.cleanBeforeRun = cleanBeforeRun; this.cleanOnlyOnce = cleanOnlyOnce; diff --git a/allure-java-commons/src/main/java/io/qameta/allure/aspects/AttachmentsAspects.java b/allure-java-commons/src/main/java/io/qameta/allure/aspects/AttachmentsAspects.java index 106a8069..6db4164a 100644 --- a/allure-java-commons/src/main/java/io/qameta/allure/aspects/AttachmentsAspects.java +++ b/allure-java-commons/src/main/java/io/qameta/allure/aspects/AttachmentsAspects.java @@ -39,8 +39,7 @@ @Aspect public class AttachmentsAspects { - private static final InheritableThreadLocal LIFECYCLE = - new InheritableThreadLocal() { + private static final InheritableThreadLocal LIFECYCLE = new InheritableThreadLocal() { @Override protected AllureLifecycle initialValue() { return Allure.getLifecycle(); @@ -70,13 +69,18 @@ public void anyMethod() { * @param joinPoint the join point to process. * @param result the returned value. */ - @AfterReturning(pointcut = "anyMethod() && withAttachmentAnnotation()", returning = "result") + @AfterReturning( + pointcut = "anyMethod() && withAttachmentAnnotation()", + returning = "result" + ) public void attachment(final JoinPoint joinPoint, final Object result) { final MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); final Attachment attachment = methodSignature.getMethod() .getAnnotation(Attachment.class); - final byte[] bytes = (result instanceof byte[]) ? (byte[]) result : Objects.toString(result) - .getBytes(StandardCharsets.UTF_8); + final byte[] bytes = (result instanceof byte[]) + ? (byte[]) result + : Objects.toString(result) + .getBytes(StandardCharsets.UTF_8); final String name = attachment.value().isEmpty() ? methodSignature.getName() diff --git a/allure-java-commons/src/main/java/io/qameta/allure/aspects/StepsAspects.java b/allure-java-commons/src/main/java/io/qameta/allure/aspects/StepsAspects.java index 8f59189d..77e29082 100644 --- a/allure-java-commons/src/main/java/io/qameta/allure/aspects/StepsAspects.java +++ b/allure-java-commons/src/main/java/io/qameta/allure/aspects/StepsAspects.java @@ -45,8 +45,7 @@ @Aspect public class StepsAspects { - private static final InheritableThreadLocal LIFECYCLE - = new InheritableThreadLocal() { + private static final InheritableThreadLocal LIFECYCLE = new InheritableThreadLocal() { @Override protected AllureLifecycle initialValue() { return Allure.getLifecycle(); @@ -79,11 +78,16 @@ public void stepStart(final JoinPoint joinPoint) { getLifecycle().startStep(uuid, result); } - @AfterThrowing(pointcut = "anyMethod() && withStepAnnotation()", throwing = "e") + @AfterThrowing( + pointcut = "anyMethod() && withStepAnnotation()", + throwing = "e" + ) public void stepFailed(final Throwable e) { - getLifecycle().updateStep(s -> s - .setStatus(getStatus(e).orElse(Status.BROKEN)) - .setStatusDetails(getStatusDetails(e).orElse(null))); + getLifecycle().updateStep( + s -> s + .setStatus(getStatus(e).orElse(Status.BROKEN)) + .setStatusDetails(getStatusDetails(e).orElse(null)) + ); getLifecycle().stopStep(); } diff --git a/allure-java-commons/src/main/java/io/qameta/allure/internal/Allure2ModelJackson.java b/allure-java-commons/src/main/java/io/qameta/allure/internal/Allure2ModelJackson.java index 21d2250f..93d46e35 100644 --- a/allure-java-commons/src/main/java/io/qameta/allure/internal/Allure2ModelJackson.java +++ b/allure-java-commons/src/main/java/io/qameta/allure/internal/Allure2ModelJackson.java @@ -52,10 +52,11 @@ public static ObjectMapper createMapper() { .serializationInclusion(NON_NULL) .configure(INDENT_OUTPUT, Boolean.getBoolean(INDENT_OUTPUT_PROPERTY_NAME)) .build() - .registerModule(new SimpleModule() - .addSerializer(Status.class, new StatusSerializer()) - .addSerializer(Stage.class, new StageSerializer()) - .addSerializer(Parameter.Mode.class, new ParameterModeSerializer()) + .registerModule( + new SimpleModule() + .addSerializer(Status.class, new StatusSerializer()) + .addSerializer(Stage.class, new StageSerializer()) + .addSerializer(Parameter.Mode.class, new ParameterModeSerializer()) ); } @@ -70,7 +71,8 @@ protected ParameterModeSerializer() { @Override public void serialize(final Parameter.Mode value, final JsonGenerator gen, - final SerializerProvider provider) throws IOException { + final SerializerProvider provider) + throws IOException { gen.writeString(value.name().toLowerCase(Locale.ENGLISH)); } } @@ -86,7 +88,8 @@ protected StageSerializer() { @Override public void serialize(final Stage value, final JsonGenerator gen, - final SerializerProvider provider) throws IOException { + final SerializerProvider provider) + throws IOException { gen.writeString(value.name().toLowerCase(Locale.ENGLISH)); } } @@ -102,7 +105,8 @@ protected StatusSerializer() { @Override public void serialize(final Status value, final JsonGenerator gen, - final SerializerProvider provider) throws IOException { + final SerializerProvider provider) + throws IOException { gen.writeString(value.name().toLowerCase(Locale.ENGLISH)); } } diff --git a/allure-java-commons/src/main/java/io/qameta/allure/listener/LifecycleNotifier.java b/allure-java-commons/src/main/java/io/qameta/allure/listener/LifecycleNotifier.java index 29174ece..7684b66d 100644 --- a/allure-java-commons/src/main/java/io/qameta/allure/listener/LifecycleNotifier.java +++ b/allure-java-commons/src/main/java/io/qameta/allure/listener/LifecycleNotifier.java @@ -28,8 +28,13 @@ /** * @since 2.0 */ -public class LifecycleNotifier implements ContainerLifecycleListener, - TestLifecycleListener, FixtureLifecycleListener, StepLifecycleListener { +@SuppressWarnings("PMD.TooManyMethods") +public class LifecycleNotifier + implements + ContainerLifecycleListener, + TestLifecycleListener, + FixtureLifecycleListener, + StepLifecycleListener { private static final Logger LOGGER = LoggerFactory.getLogger(LifecycleNotifier.class); @@ -51,7 +56,6 @@ public LifecycleNotifier(final List containerListene this.stepListeners = stepListeners; } - @Override public void beforeTestSchedule(final TestResult result) { runSafely(testListeners, TestLifecycleListener::beforeTestSchedule, result); diff --git a/allure-java-commons/src/main/java/io/qameta/allure/util/AnnotationUtils.java b/allure-java-commons/src/main/java/io/qameta/allure/util/AnnotationUtils.java index b032b35c..1e6ad124 100644 --- a/allure-java-commons/src/main/java/io/qameta/allure/util/AnnotationUtils.java +++ b/allure-java-commons/src/main/java/io/qameta/allure/util/AnnotationUtils.java @@ -141,9 +141,9 @@ public static Set