diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..bb09719d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,85 @@ +name: 🐞 Bug Report +description: File a Bug report in Java Integration +title: "🐞: " +labels: [ "type:bug", "triage" ] +assignees: [] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: dropdown + id: integration + attributes: + label: What Allure Integration are you using? + multiple: true + description: Please select the Allure integration you + options: + - allure-assertj + - allure-attachments + - allure-awaitility + - allure-citrus + - allure-cucumber2-jvm + - allure-cucumber3-jvm + - allure-cucumber4-jvm + - allure-cucumber5-jvm + - allure-cucumber6-jvm + - allure-cucumber7-jvm + - allure-descriptions-javadoc + - allure-grpc + - allure-hamcrest + - allure-httpclient + - allure-java-commons + - allure-jax-rs + - allure-jbehave + - allure-jbehave5 + - allure-jsonunit + - allure-junit-platform + - allure-junit4 + - allure-junit5 + - allure-karate + - allure-okhttp + - allure-okhttp3 + - allure-reader + - allure-rest-assured + - allure-scalatest + - allure-selenide + - allure-servlet-api + - allure-spock + - allure-spock2 + - allure-spring-web + - allure-test-filter + - allure-testng + validations: + required: true + - type: input + id: integration_version + attributes: + label: What version of Allure Integration you are using? + placeholder: 2.22.3 + validations: + required: true + - type: input + id: allure_report_version + attributes: + label: What version of Allure Report you are using? + placeholder: 2.22.3 + validations: + required: true + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/allure-framework/allure-java/blob/main/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..fb81bedae --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: ⭐️ Allure Report | main repository | give us a ⭐️ + url: https://github.com/allure-framework/allure2 + about: For Allure Report related issues. + - name: 💬 Allure Report Community - for bugs, issues, dedicated support and more! + url: https://github.com/orgs/allure-framework/discussions + about: Please ask and answer questions here. + - name: 💚 Allure TestOps Support + url: https://help.qameta.io/support/home + about: Please report Allure TestOps issues here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0dec83312..8178c9b61 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ Thank you so much for sending us a pull request! Make sure you have a clear name for your pull request. The name should start with a capital letter and no dot is required in the end of the sentence. -To link the request with isses use the following notation: (fixes #123, fixes #321\) +To link the request with issues use the following notation: (fixes #123, fixes #321\) An example of good pull request names: * Add Cucumber integration (fixes #123\) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..240d09840 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + labels: + - "type:dependencies" + + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" + labels: + - "type:dependencies" diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..a419c97f9 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,84 @@ +"theme:workflow": + - ".github/**" + +"theme:build": + - "gradle/**" + - "build.gradle.kts" + - "**/build.gradle.kts" + - "gradle.properties" + - ".gitignore" + +"theme:assertj": + - "allure-assertj/**" + +"theme:attachments": + - "allure-attachments/**" + +"theme:citrus": + - "allure-citrus/**" + +"theme:cucumber-jvm": + - "allure-cucumber*-jvm/**" + +"theme:descriptions-javadoc": + - "allure-descriptions-javadoc/**" + +"theme:httpclient": + - "allure-httpclient/**" + +"theme:model": + - "allure-model/**" + +"theme:core": + - "allure-java-commons/**" + - "allure-java-commons-test/**" + - "allure-test-filter/**" + +"theme:jax-rs": + - "allure-jax-rs/**" + +"theme:jbehave": + - "allure-jbehave*/**" + +"theme:jsonunit": + - "allure-jsonunit/**" + +"theme:junit4": + - "allure-junit4/**" + - "allure-junit4-aspect/**" + +"theme:junit-platform": + - "allure-junit5/**" + - "allure-junit5-assert/**" + - "allure-junit-platform/**" + +"theme:karate": + - "allure-karate/**" + +"theme:okhttp": + - "allure-okhttp/**" + - "allure-okhttp3/**" + +"theme:rest-assured": + - "allure-rest-assured/**" + +"theme:scalatest": + - "allure-scalatest/**" + +"theme:selenide": + - "allure-selenide/**" + +"theme:servlet-api": + - "allure-servlet-api/**" + +"theme:spock": + - "allure-spock/**" + +"theme:spring": + - "allure-spring-web/**" + +"theme:testng": + - "allure-testng/**" + +"theme:hamcrest": + - "allure-hamcrest/**" diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..7c30a5979 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,25 @@ +# release.yml + +changelog: + categories: + - title: '🚀 New Features' + labels: + - 'type:new feature' + - title: '🔬 Improvements' + labels: + - 'type:improvement' + - title: '🐞 Bug Fixes' + labels: + - 'type:bug' + - title: '⬆️ Dependency Updates' + labels: + - 'type:dependencies' + - title: '📖 Documentation improvements' + labels: + - 'type:documentation' + - title: '⛔️ Security' + labels: + - 'type:security' + - title: '👻 Internal changes' + labels: + - 'type:internal' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..8f537bc9c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,34 @@ +name: Build + +permissions: + contents: read + +on: + workflow_dispatch: + pull_request: + branches: + - '*' + push: + branches: + - 'main' + - 'hotfix-*' + +jobs: + build: + name: "Build" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: "Set up JDK" + uses: actions/setup-java@v5 + with: + distribution: 'zulu' + java-version: 21 + + - name: "Build with Gradle" + run: ./gradlew build -x test --scan + + - name: "Run tests" + if: always() + run: ./gradlew --no-build-cache cleanTest test diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml new file mode 100644 index 000000000..748dfe670 --- /dev/null +++ b/.github/workflows/dependency-submission.yml @@ -0,0 +1,22 @@ +name: Dependency Submission + +on: + push: + branches: + - main + +permissions: + contents: write + +jobs: + dependency-submission: + name: Dependency Submission + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v6 + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@v5 + env: + DEPENDENCY_GRAPH_EXCLUDE_PROJECTS: ':allure-java-commons-test' + DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS: 'runtimeClasspath' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 000000000..f49976b59 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,17 @@ +name: "Set theme labels" + +on: + - pull_request_target + +permissions: + contents: read + +jobs: + triage: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/labeler@v4 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/labels-verify.yml b/.github/workflows/labels-verify.yml new file mode 100644 index 000000000..7315a905a --- /dev/null +++ b/.github/workflows/labels-verify.yml @@ -0,0 +1,27 @@ +name: "Verify type labels" + +on: + pull_request_target: + types: [opened, labeled, unlabeled, synchronize] + +permissions: + contents: none + +jobs: + triage: + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - uses: baev/action-label-verify@main + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + allowed: | + type:bug + type:dependencies + type:improvement + type:internal + type:invalid + type:new feature + type:security + type:documentation diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..2295deb90 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,41 @@ +name: Publish + +on: + release: + types: [ published ] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: "Set up JDK" + uses: actions/setup-java@v5 + with: + distribution: 'zulu' + java-version: '21' + + - name: Set up GPG + run: echo -n "${GPG_PRIVATE_KEY}" | base64 --decode > ${GITHUB_WORKSPACE}/${GPG_KEY_ID}.gpg + env: + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + + - name: "Gradle Build" + run: ./gradlew build -Pversion=${GITHUB_REF:10} + + - name: "Gradle Publish" + run: | + ./gradlew publishToSonatype closeSonatypeStagingRepository -Pversion=${GITHUB_REF:10} \ + -Psigning.keyId=${GPG_KEY_ID} \ + -Psigning.password=${GPG_PASSPHRASE} \ + -Psigning.secretKeyRingFile=${GITHUB_WORKSPACE}/${GPG_KEY_ID}.gpg + env: + ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.OSSRH_USERNAME }} + ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.OSSRH_PASSWORD }} + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..7d5ace556 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,62 @@ +name: Release +run-name: Release ${{ inputs.releaseVersion }} (next ${{ inputs.nextVersion }}) by ${{ github.actor }} + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: "The release version in .. format" + required: true + nextVersion: + description: "The next version in . format WITHOUT SNAPSHOT SUFFIX" + required: true + +permissions: + contents: read + +jobs: + triage: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: "Check release version" + run: | + expr "${{ github.event.inputs.releaseVersion }}" : '[[:digit:]][[:digit:]]*\.[[:digit:]][[:digit:]]*\.[[:digit:]][[:digit:]]*$' + - name: "Check next version" + run: | + expr "${{ github.event.inputs.nextVersion }}" : '[[:digit:]][[:digit:]]*\.[[:digit:]][[:digit:]]*$' + - uses: actions/checkout@v6 + with: + token: ${{ secrets.QAMETA_CI }} + + - name: "Configure CI Git User" + run: | + git config --global user.name qameta-ci + git config --global user.email qameta-ci@qameta.io + - name: "Set release version" + run: | + sed -i -e '/version=/s/.*/version=${{ github.event.inputs.releaseVersion }}/g' gradle.properties + cat gradle.properties + - name: "Commit release version and create tag" + run: | + git commit -am "release ${{ github.event.inputs.releaseVersion }}" + git tag ${{ github.event.inputs.releaseVersion }} + git push origin ${{ github.event.inputs.releaseVersion }} + - name: "Set next development version" + run: | + sed -i -e '/version=/s/.*/version=${{ github.event.inputs.nextVersion }}-SNAPSHOT/g' gradle.properties + cat gradle.properties + - name: "Commit next development version and push it" + run: | + git commit -am "set next development version ${{ github.event.inputs.nextVersion }}" + git push origin ${{ github.ref }} + - name: "Publish Github Release" + uses: octokit/request-action@v2.x + with: + route: POST /repos/${{ github.repository }}/releases + tag_name: ${{ github.event.inputs.releaseVersion }} + generate_release_notes: true + target_commitish: ${{ github.ref }} + env: + GITHUB_TOKEN: ${{ secrets.QAMETA_CI }} diff --git a/.gitignore b/.gitignore index fe90411d6..b85107a90 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,9 @@ out .gradletasknamecache #IDEA Files -.idea +.idea/* +!.idea/vcs.xml +!.idea/icon.png *.iml *.ipr @@ -21,4 +23,4 @@ out .DS_Store #Netbeans files -/.nb-gradle/ \ No newline at end of file +/.nb-gradle/ diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 000000000..252994657 Binary files /dev/null and b/.idea/icon.png differ diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..aeaa9e459 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/AUTHORS b/AUTHORS index 0edbd3fb9..8263d9471 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,4 @@ The following authors have created the source code of "Allure Java" -published and distributed by Qameta Software OÜ as the owner: +published and distributed by Qameta Software Inc as the owner: * Dmitry Baev diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 64bac5a9e..000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,40 +0,0 @@ -pipeline { - agent { label 'java' } - parameters { - booleanParam(name: 'RELEASE', defaultValue: false, description: 'Perform release?') - string(name: 'RELEASE_VERSION', defaultValue: '', description: 'Release version') - string(name: 'NEXT_VERSION', defaultValue: '', description: 'Next version (without SNAPSHOT)') - } - stages { - stage('Build') { - steps { - sh './gradlew build' - } - } - stage('Release') { - when { expression { return params.RELEASE } } - steps { - withCredentials([usernamePassword(credentialsId: 'qameta-ci_bintray', - usernameVariable: 'BINTRAY_USER', passwordVariable: 'BINTRAY_API_KEY')]) { - sshagent(['qameta-ci_ssh']) { - sh 'git checkout master && git pull origin master' - sh "./gradlew release -Prelease.useAutomaticVersion=true " + - "-Prelease.releaseVersion=${RELEASE_VERSION} " + - "-Prelease.newVersion=${NEXT_VERSION}-SNAPSHOT" - } - } - } - } - } - post { - always { - allure results: [[path: '**/build/allure-results']] - deleteDir() - } - - failure { - slackSend message: "${env.JOB_NAME} - #${env.BUILD_NUMBER} failed (<${env.BUILD_URL}|Open>)", - color: 'danger', teamDomain: 'qameta', channel: 'allure', tokenCredentialId: 'allure-channel' - } - } -} diff --git a/LICENSE b/LICENSE index a5ff24fc6..aa0c10ae1 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2019 Qameta Software OÜ + Copyright 2016-2024 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. @@ -198,4 +198,4 @@ 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. \ No newline at end of file + limitations under the License. diff --git a/README.md b/README.md index e96e77409..e452787b3 100644 --- a/README.md +++ b/README.md @@ -1,171 +1,242 @@ -[license]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License 2.0" -[blog]: https://qameta.io/blog -[gitter]: https://gitter.im/allure-framework/allure-core -[gitter-ru]: https://gitter.im/allure-framework/allure-ru -[twitter]: https://twitter.com/QametaSoftware "Qameta Software" -[twitter-team]: https://twitter.com/QametaSoftware/lists/team/members "Team" - -[bintray]: https://bintray.com/qameta/maven/allure-java "Bintray" -[bintray-badge]: https://img.shields.io/bintray/v/qameta/maven/allure-java.svg?style=flat - -[CONTRIBUTING.md]: .github/CONTRIBUTING.md -[docs]: https://docs.qameta.io/allure/2.0/ - -# Allure Java Integrations [![bintray-badge][]][bintray] - -The repository contains new versions of adaptors for JVM-based test frameworks. - -All the artifacts are deployed to `https://dl.bintray.com/qameta/maven`. - -## TestNG - -The new TestNG adaptors is pretty much ready. To use the adaptor you should add the following dependency: - -```xml - - io.qameta.allure - allure-testng - $LATEST_VERSION - -``` - -also you need to configure AspectJ weaver to support steps. - -## JUnit 4 - -The first draft of a new JUnit 4 adaptor is ready. To use the adaptor you should add the following dependency: - -```xml - - io.qameta.allure - allure-junit4 - $LATEST_VERSION - -``` - -## JUnit 5 - -To use JUnit 5 simply add the following dependency to your project: - -```xml - - io.qameta.allure - allure-junit5 - $LATEST_VERSION - -``` - -## Selenide - -Listener for Selenide, that logging steps for Allure: - -```xml - - io.qameta.allure - allure-selenide - $LATEST_VERSION - -``` - -Usage example: -``` -SelenideLogger.addListener("AllureSelenide", new AllureSelenide().screenshots(true).savePageSource(false)); - -Capture selenium logs: -SelenideLogger.addListener("AllureSelenide", new AllureSelenide().enableLogs(LogType.BROWSER, Level.ALL)); -https://github.com/SeleniumHQ/selenium/wiki/Logging -``` - - -## Rest Assured - -Filter for rest-assured http client, that generates attachment for allure. - -```xml - - io.qameta.allure - allure-rest-assured - $LATEST_VERSION - -``` - -Usage example: -``` -.filter(new AllureRestAssured()) -``` -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. - -```xml - - io.qameta.allure - allure-okhttp3 - $LATEST_VERSION - -``` - -Usage example: -``` -.addInterceptor(new AllureOkHttp3()) -``` -You can specify custom templates, which should be placed in src/main/resources/tpl folder: -``` -.addInterceptor(new AllureOkHttp3() - .withRequestTemplate("custom-http-request.ftl") - .withResponseTemplate("custom-http-response.ftl")) - -``` - -## Http client - -Interceptors for Apache HTTP client, that generates attachment for allure. - -```xml - - io.qameta.allure - allure-httpclient - $LATEST_VERSION - -``` - -Usage example: -``` -.addInterceptorFirst(new AllureHttpClientRequest()) -.addInterceptorLast(new AllureHttpClientResponse()); -``` - -## JAX-RS Filter - -Filter that can be used with JAX-RS compliant clients such as RESTeasy and Jersey - -```xml - - io.qameta.allure - allure-jax-rs - $LATEST_VERSION - -``` - -Usage example: -``` -.register(AllureJaxRs.class) -``` - -## JsonUnit -JsonPatchMatcher is extension of JsonUnit matcher, that generates pretty html attachment for differences based on [json diff patch](https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md). - -```xml - - io.qameta.allure - allure-jsonunit - $LATEST_VERSION - -``` +[license]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License 2.0" +[blog]: https://qameta.io/blog +[gitter]: https://gitter.im/allure-framework/allure-core +[gitter-ru]: https://gitter.im/allure-framework/allure-ru +[twitter]: https://twitter.com/QametaSoftware "Qameta Software" +[twitter-team]: https://twitter.com/QametaSoftware/lists/team/members "Team" + +[CONTRIBUTING.md]: .github/CONTRIBUTING.md +[docs]: https://allurereport.org/docs/ + +# Allure Java Integrations + +[![Build](https://github.com/allure-framework/allure-java/actions/workflows/build.yml/badge.svg)](https://github.com/allure-framework/allure-java/actions/workflows/build.yml) +[![Allure Java](https://img.shields.io/github/release/allure-framework/allure-java.svg)](https://github.com/allure-framework/allure-java/releases/latest) + +> The repository contains new versions of adaptors for JVM-based test frameworks. + +[Allure Report logo](https://allurereport.org "Allure Report") + +- Learn more about Allure Report at [https://allurereport.org](https://allurereport.org) +- 📚 [Documentation](https://allurereport.org/docs/) – discover official documentation for Allure Report +- ❓ [Questions and Support](https://github.com/orgs/allure-framework/discussions/categories/questions-support) – get help from the team and community +- 📢 [Official announcements](https://github.com/orgs/allure-framework/discussions/categories/announcements) – stay updated with our latest news and updates +- 💬 [General Discussion](https://github.com/orgs/allure-framework/discussions/categories/general-discussion) – engage in casual conversations, share insights and ideas with the community +- 🖥️ [Live Demo](https://demo.allurereport.org/) — explore a live example of Allure Report in action + +--- +## TestNG + +- 🚀 Documentation — https://allurereport.org/docs/testng/ +- 📚 Example project — https://github.com/allure-examples?q=topic%3Atestng +- ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/ + +## JUnit 4 + +- 🚀 Documentation — work in progress +- 📚 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/ + +## Cucumber JVM + +- 🚀 Documentation — https://allurereport.org/docs/cucumberjvm/ +- 📚 Example project — https://github.com/allure-examples?q=cucumber&type=all&language=java +- ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/ + +## Spock + +- 🚀 Documentation — https://allurereport.org/docs/spock/ +- 📚 Example project — https://github.com/allure-examples?q=topic%3Aspock +- ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/ + +## Selenide + +Listener for Selenide, that logging steps for Allure: + +```xml + + io.qameta.allure + allure-selenide + $LATEST_VERSION + +``` + +Usage example: +``` +SelenideLogger.addListener("AllureSelenide", new AllureSelenide().screenshots(true).savePageSource(false)); + +Capture selenium logs: +SelenideLogger.addListener("AllureSelenide", new AllureSelenide().enableLogs(LogType.BROWSER, Level.ALL)); +https://github.com/SeleniumHQ/selenium/wiki/Logging +``` + + +## Rest Assured + +Filter for rest-assured http client, that generates attachment for allure. + +```xml + + io.qameta.allure + allure-rest-assured + $LATEST_VERSION + +``` + +Usage example: +``` +.filter(new AllureRestAssured()) +``` +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. + +```xml + + io.qameta.allure + allure-okhttp3 + $LATEST_VERSION + +``` + +Usage example: +``` +.addInterceptor(new AllureOkHttp3()) +``` +You can specify custom templates, which should be placed in src/main/resources/tpl folder: +``` +.addInterceptor(new AllureOkHttp3() + .withRequestTemplate("custom-http-request.ftl") + .withResponseTemplate("custom-http-response.ftl")) + +``` + +## gRPC + +Interceptor for gRPC stubs, that generates attachment for allure. + +```xml + + io.qameta.allure + allure-grpc + $LATEST_VERSION + +``` + +Usage example: +``` +.newBlockingStub(channel).withInterceptors(new AllureGrpc()); +``` +You can enable interception of response metadata (disabled by default) +``` +.withInterceptors(new AllureGrpc() + .interceptResponseMetadata(true)) +``` +By default, a step will be marked as failed in case that response contains any statuses except 0(OK). +You can change this behavior, for example, for negative scenarios +``` +.withInterceptors(new AllureGrpc() + .markStepFailedOnNonZeroCode(false)) +``` +You can specify custom templates, which should be placed in src/main/resources/tpl folder: +``` +.withInterceptors(new AllureGrpc() + .setRequestTemplate("custom-http-request.ftl") + .setResponseTemplate("custom-http-response.ftl")) +``` + +## Http client + +Interceptors for Apache HTTP client, that generates attachment for allure. + +```xml + + io.qameta.allure + allure-httpclient + $LATEST_VERSION + +``` + +Usage example: +``` +.addInterceptorFirst(new AllureHttpClientRequest()) +.addInterceptorLast(new AllureHttpClientResponse()); +``` + +## Http client 5 +Interceptors for Apache [httpclient5](https://hc.apache.org/httpcomponents-client-5.2.x/index.html). +Additional info can be found in module `allure-httpclient5` + +```xml + + io.qameta.allure + allure-httpclient5 + $LATEST_VERSION + +``` + +Usage example: +```java +final HttpClientBuilder builder = HttpClientBuilder.create() + .addRequestInterceptorFirst(new AllureHttpClient5Request("your-request-template-attachment.ftl")) + .addResponseInterceptorLast(new AllureHttpClient5Response("your-response-template-attachment.ftl")); +``` + +## JAX-RS Filter + +Filter that can be used with JAX-RS compliant clients such as RESTeasy and Jersey + +```xml + + io.qameta.allure + allure-jax-rs + $LATEST_VERSION + +``` + +Usage example: +``` +.register(AllureJaxRs.class) +``` + +## JsonUnit +JsonPatchMatcher is extension of JsonUnit matcher, that generates pretty html attachment for differences based on [json diff patch](https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md). + +```xml + + io.qameta.allure + allure-jsonunit + $LATEST_VERSION + +``` + +## Awaitility +Extended logging for poling and ignored exceptions for [awaitility](https://github.com/awaitility/awaitility). For +more usage example look into module `allure-awaitility` + +```xml + + io.qameta.allure + allure-awaitility + $LATEST_VERSION + +``` + +Usage example: +``` +Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener()); +``` + diff --git a/allure-assertj/build.gradle.kts b/allure-assertj/build.gradle.kts index e8e064ed9..e38cca0ea 100644 --- a/allure-assertj/build.gradle.kts +++ b/allure-assertj/build.gradle.kts @@ -1,9 +1,6 @@ description = "Allure AssertJ Integration" -val agent: Configuration by configurations.creating - dependencies { - agent("org.aspectj:aspectjweaver") api(project(":allure-java-commons")) compileOnly("org.aspectj:aspectjrt") compileOnly("org.assertj:assertj-core") @@ -25,7 +22,4 @@ tasks.jar { tasks.test { useJUnitPlatform() - doFirst { - jvmArgs("-javaagent:${agent.singleFile}") - } } 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 e54ca815e..128791172 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 @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. @@ -55,7 +55,7 @@ protected AllureLifecycle initialValue() { } }; - @Pointcut("execution(public org.assertj.core.api.AbstractAssert.new(..))") + @Pointcut("execution(!private org.assertj.core.api.AbstractAssert.new(..))") public void anyAssertCreation() { //pointcut body, should be empty } 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 7a48210c8..57ddef240 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 @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import static io.qameta.allure.test.RunUtils.runWithinTestContext; import static org.assertj.core.api.Assertions.assertThat; @@ -82,11 +83,28 @@ void shouldHandleByteArrayObject() { .extracting(StepResult::getName) .containsExactly( "assertThat ''", - "as 'Byte array object []'", + "describedAs 'Byte array object'", "isEqualTo ''" ); } + @AllureFeatures.Steps + @Test + void shouldHandleCollections() { + final AllureResults results = runWithinTestContext(() -> { + assertThat(Arrays.asList("a", "b")) + .containsExactly("a", "b"); + }, AllureAspectJ::setLifecycle); + + assertThat(results.getTestResults()) + .flatExtracting(TestResult::getSteps) + .extracting(StepResult::getName) + .containsExactly( + "assertThatList '[a, b]'", + "containsExactly '[a, b]'" + ); + } + @AllureFeatures.Steps @Test void softAssertions() { diff --git a/allure-attachments/build.gradle.kts b/allure-attachments/build.gradle.kts index bfbe95e7c..c5010a3c0 100644 --- a/allure-attachments/build.gradle.kts +++ b/allure-attachments/build.gradle.kts @@ -1,9 +1,6 @@ description = "Allure Attachments" -val agent: Configuration by configurations.creating - dependencies { - agent("org.aspectj:aspectjweaver") api(project(":allure-java-commons")) implementation("org.freemarker:freemarker") testImplementation("org.apache.commons:commons-lang3") @@ -26,7 +23,4 @@ tasks.jar { tasks.test { useJUnitPlatform() - doFirst { - jvmArgs("-javaagent:${agent.singleFile}") - } } diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java index c8fe7c111..50af67ea4 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java index 86b318a05..5c6d09f34 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. @@ -20,6 +20,7 @@ * * @author charlie (Dmitry Baev). */ +@FunctionalInterface public interface AttachmentData { String getName(); diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java index cfc93c716..99129f89a 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. @@ -19,6 +19,7 @@ * @param the type of attachment data. * @author charlie (Dmitry Baev). */ +@FunctionalInterface public interface AttachmentProcessor { void addAttachment(T attachmentData, AttachmentRenderer renderer); diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java index 5d702bffa..daaf9cc91 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. 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 1d239331e..2b6f5b1a5 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 @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. @@ -20,6 +20,7 @@ * @author charlie (Dmitry Baev). */ @SuppressWarnings("PMD.AvoidUncheckedExceptionsInSignatures") +@FunctionalInterface public interface AttachmentRenderer { AttachmentContent render(T attachmentData) throws AttachmentRenderException; diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java index ab26abbb6..ccdc978a8 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java index dc57e9a01..652c8ca5f 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java index 9179f3ed7..50ac53924 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. @@ -17,6 +17,9 @@ import freemarker.template.Configuration; import freemarker.template.Template; +import freemarker.template.TemplateExceptionHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.StringWriter; import java.io.Writer; @@ -27,6 +30,8 @@ */ public class FreemarkerAttachmentRenderer implements AttachmentRenderer { + private static final Logger LOGGER = LoggerFactory.getLogger(FreemarkerAttachmentRenderer.class); + private final Configuration configuration; private final String templateName; @@ -36,6 +41,7 @@ public FreemarkerAttachmentRenderer(final String templateName) { this.configuration = new Configuration(Configuration.VERSION_2_3_23); this.configuration.setLocalizedLookup(false); this.configuration.setTemplateUpdateDelayMilliseconds(0); + this.configuration.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); this.configuration.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "tpl"); } @@ -46,6 +52,7 @@ public DefaultAttachmentContent render(final AttachmentData data) { template.process(Collections.singletonMap("data", data), writer); return new DefaultAttachmentContent(writer.toString(), "text/html", ".html"); } catch (Exception e) { + LOGGER.debug(data.toString()); throw new AttachmentRenderException("Could't render http attachment file", e); } } diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpRequestAttachment.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpRequestAttachment.java index 831b2a5da..a51052cf7 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpRequestAttachment.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpRequestAttachment.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. @@ -16,7 +16,9 @@ package io.qameta.allure.attachment.http; import io.qameta.allure.attachment.AttachmentData; +import io.qameta.allure.util.ObjectUtils; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -40,9 +42,18 @@ public class HttpRequestAttachment implements AttachmentData { private final Map cookies; + private final Map formParams; + public HttpRequestAttachment(final String name, final String url, final String method, final String body, final String curl, final Map headers, final Map cookies) { + this(name, url, method, body, curl, headers, cookies, Collections.emptyMap()); + } + + @SuppressWarnings("checkstyle:parameternumber") + public HttpRequestAttachment(final String name, final String url, final String method, + final String body, final String curl, final Map headers, + final Map cookies, final Map formParams) { this.name = name; this.url = url; this.method = method; @@ -50,6 +61,7 @@ public HttpRequestAttachment(final String name, final String url, final String m this.curl = curl; this.headers = headers; this.cookies = cookies; + this.formParams = formParams; } public String getUrl() { @@ -72,6 +84,10 @@ public Map getCookies() { return cookies; } + public Map getFormParams() { + return formParams; + } + public String getCurl() { return curl; } @@ -81,10 +97,21 @@ public String getName() { return name; } + @Override + public String toString() { + return "HttpRequestAttachment(" + + "\n\tname=" + this.name + + ",\n\turl=" + this.url + + ",\n\tbody=" + this.body + + ",\n\theaders=" + ObjectUtils.mapToString(this.headers) + + ",\n\tcookies=" + ObjectUtils.mapToString(this.cookies) + + ",\n\tformParams=" + ObjectUtils.mapToString(this.formParams) + + "\n)"; + } + /** * Builder for HttpRequestAttachment. */ - @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName") public static final class Builder { private final String name; @@ -99,6 +126,8 @@ public static final class Builder { private final Map cookies = new HashMap<>(); + private final Map formParams = new HashMap<>(); + private Builder(final String name, final String url) { Objects.requireNonNull(name, "Name must not be null value"); Objects.requireNonNull(url, "Url must not be null value"); @@ -148,6 +177,12 @@ public Builder setBody(final String body) { return this; } + public Builder setFormParams(final Map formParams) { + Objects.requireNonNull(formParams, "Form params must not be null value"); + this.formParams.putAll(formParams); + return this; + } + /** * Use setter method instead. * @deprecated scheduled for removal in 3.0 release @@ -203,7 +238,7 @@ public Builder withBody(final String body) { } public HttpRequestAttachment build() { - return new HttpRequestAttachment(name, url, method, body, getCurl(), headers, cookies); + return new HttpRequestAttachment(name, url, method, body, getCurl(), headers, cookies, formParams); } private String getCurl() { @@ -214,6 +249,7 @@ private String getCurl() { builder.append(" '").append(url).append('\''); headers.forEach((key, value) -> appendHeader(builder, key, value)); cookies.forEach((key, value) -> appendCookie(builder, key, value)); + formParams.forEach((key, value) -> appendFormParams(builder, key, value)); if (Objects.nonNull(body)) { builder.append(" -d '").append(body).append('\''); @@ -236,5 +272,13 @@ private static void appendCookie(final StringBuilder builder, final String key, .append(value) .append('\''); } + + private static void appendFormParams(final StringBuilder builder, final String key, final String value) { + builder.append(" --form '") + .append(key) + .append('=') + .append(value) + .append('\''); + } } } diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java index 0b5ef9841..d606af57e 100644 --- a/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java +++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. @@ -16,6 +16,7 @@ package io.qameta.allure.attachment.http; import io.qameta.allure.attachment.AttachmentData; +import io.qameta.allure.util.ObjectUtils; import java.util.HashMap; import java.util.Map; @@ -74,10 +75,21 @@ public Map getCookies() { return cookies; } + @Override + public String toString() { + return "HttpResponseAttachment(" + + "\n\tname=" + this.name + + ",\n\turl=" + this.url + + ",\n\tbody=" + this.body + + ",\n\tresponseCode=" + this.responseCode + + ",\n\theaders=" + ObjectUtils.mapToString(this.headers) + + ",\n\tcookies=" + ObjectUtils.mapToString(this.cookies) + + "\n)"; + } + /** * Builder for HttpRequestAttachment. */ - @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName") public static final class Builder { private final String name; @@ -146,6 +158,7 @@ public Builder setBody(final String body) { /** * Use setter method instead. + * * @deprecated scheduled for removal in 3.0 release */ @Deprecated @@ -155,6 +168,7 @@ public Builder withUrl(final String url) { /** * Use setter method instead. + * * @deprecated scheduled for removal in 3.0 release */ @Deprecated @@ -164,6 +178,7 @@ public Builder withResponseCode(final int responseCode) { /** * Use setter method instead. + * * @deprecated scheduled for removal in 3.0 release */ @Deprecated @@ -173,6 +188,7 @@ public Builder withHeader(final String name, final String value) { /** * Use setter method instead. + * * @deprecated scheduled for removal in 3.0 release */ @Deprecated @@ -182,6 +198,7 @@ public Builder withHeaders(final Map headers) { /** * Use setter method instead. + * * @deprecated scheduled for removal in 3.0 release */ @Deprecated @@ -191,6 +208,7 @@ public Builder withCookie(final String name, final String value) { /** * Use setter method instead. + * * @deprecated scheduled for removal in 3.0 release */ @Deprecated @@ -200,6 +218,7 @@ public Builder withCookies(final Map cookies) { /** * Use setter method instead. + * * @deprecated scheduled for removal in 3.0 release */ @Deprecated diff --git a/allure-attachments/src/main/resources/tpl/http-request.ftl b/allure-attachments/src/main/resources/tpl/http-request.ftl index 5ef76b521..935c1c91a 100644 --- a/allure-attachments/src/main/resources/tpl/http-request.ftl +++ b/allure-attachments/src/main/resources/tpl/http-request.ftl @@ -3,36 +3,45 @@
<#if data.method??>${data.method}<#else>GET to <#if data.url??>${data.url}<#else>Unknown
<#if data.body??> -

Body

-
+

Body

+
-    ${data.body}
+    <#t>${data.body}
     
-
+
<#if (data.headers)?has_content> -

Headers

-
- <#list data.headers as name, value> -
${name}: ${value}
- -
+

Headers

+
+ <#list data.headers as name, value> +
${name}: ${value!"null"}
+ +
<#if (data.cookies)?has_content> -

Cookies

-
- <#list data.cookies as name, value> -
${name}: ${value}
- -
+

Cookies

+
+ <#list data.cookies as name, value> +
${name}: ${value!"null"}
+ +
<#if data.curl??> -

Curl

-
-${data.curl} -
+

Curl

+
+ ${data.curl} +
+ + +<#if (data.formParams)?has_content> +

FormParams

+
+ <#list data.formParams as name, value> +
${name}: ${value!"null"}
+ +
diff --git a/allure-attachments/src/main/resources/tpl/http-response.ftl b/allure-attachments/src/main/resources/tpl/http-response.ftl index 2bc55de9f..91fed7287 100644 --- a/allure-attachments/src/main/resources/tpl/http-response.ftl +++ b/allure-attachments/src/main/resources/tpl/http-response.ftl @@ -7,7 +7,7 @@

Body

-    ${data.body}
+    <#t>${data.body}
     
@@ -16,7 +16,7 @@

Headers

<#list data.headers as name, value> -
${name}: ${value}
+
${name}: ${value!"null"}
@@ -26,7 +26,7 @@

Cookies

<#list data.cookies as name, value> -
${name}: ${value}
+
${name}: ${value!"null"}
diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java index 1283d6116..a1d2def2e 100644 --- a/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java +++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. 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 75ddac32d..0a773e59f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. @@ -16,10 +16,12 @@ package io.qameta.allure.attachment; import io.qameta.allure.attachment.http.HttpRequestAttachment; +import io.qameta.allure.attachment.http.HttpResponseAttachment; import io.qameta.allure.test.AllureFeatures; import org.junit.jupiter.api.Test; import static io.qameta.allure.attachment.testdata.TestData.randomHttpRequestAttachment; +import static io.qameta.allure.attachment.testdata.TestData.randomHttpResponseAttachment; import static org.assertj.core.api.Assertions.assertThat; /** @@ -27,6 +29,13 @@ */ class FreemarkerAttachmentRendererTest { + private static final String CONTENT = "content"; + private static final String CONTENT_TYPE = "contentType"; + private static final String TEXT_HTML = "text/html"; + private static final String FILE_EXTENSION = "fileExtension"; + private static final String HTML = ".html"; + + @AllureFeatures.Attachments @Test void shouldRenderRequestAttachment() { @@ -35,21 +44,21 @@ void shouldRenderRequestAttachment() { .render(data); assertThat(content) - .hasFieldOrPropertyWithValue("contentType", "text/html") - .hasFieldOrPropertyWithValue("fileExtension", ".html") - .hasFieldOrProperty("content"); + .hasFieldOrPropertyWithValue(CONTENT_TYPE, TEXT_HTML) + .hasFieldOrPropertyWithValue(FILE_EXTENSION, HTML) + .hasFieldOrProperty(CONTENT); } @AllureFeatures.Attachments @Test void shouldRenderResponseAttachment() { - final HttpRequestAttachment data = randomHttpRequestAttachment(); + final HttpResponseAttachment data = randomHttpResponseAttachment(); final DefaultAttachmentContent content = new FreemarkerAttachmentRenderer("http-response.ftl") .render(data); assertThat(content) - .hasFieldOrPropertyWithValue("contentType", "text/html") - .hasFieldOrPropertyWithValue("fileExtension", ".html") - .hasFieldOrProperty("content"); + .hasFieldOrPropertyWithValue(CONTENT_TYPE, TEXT_HTML) + .hasFieldOrPropertyWithValue(FILE_EXTENSION, HTML) + .hasFieldOrProperty(CONTENT); } } diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/NegativeFreemarkerAttachmentRendererTest.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/NegativeFreemarkerAttachmentRendererTest.java new file mode 100644 index 000000000..fbc2e6611 --- /dev/null +++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/NegativeFreemarkerAttachmentRendererTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016-2024 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.attachment; + +import io.qameta.allure.attachment.http.HttpRequestAttachment; +import io.qameta.allure.test.AllureFeatures; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +import static io.qameta.allure.attachment.testdata.TestData.negativeHttpRequestAttachment; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author a-simeshin (Simeshin Artem). + */ +class NegativeFreemarkerAttachmentRendererTest { + + private static final String TEMPLATE_FOR_EXCEPTION = "body-npe-non-safe-attachment.ftl"; + + private PrintStream realSysOut; + private ByteArrayOutputStream sysOutBuffer; + + @BeforeEach + void setUpSysOut() throws UnsupportedEncodingException { + realSysOut = System.err; + sysOutBuffer = new ByteArrayOutputStream(); + System.setErr(new PrintStream(sysOutBuffer, false, StandardCharsets.UTF_8.toString())); + } + + @AfterEach + void rollBackSysOut() { + System.setErr(realSysOut); + } + + @AllureFeatures.Attachments + @Test + void shouldThrowExceptionalSituationsForFreeMarketRendererWithIncorrectAttachmentData() { + assertThrows(AttachmentRenderException.class, () -> { + final HttpRequestAttachment data = negativeHttpRequestAttachment(); + new FreemarkerAttachmentRenderer(TEMPLATE_FOR_EXCEPTION).render(data); + }); + } + + @AllureFeatures.Attachments + @Test + void shouldExplainExceptionalSituationsForFreeMarketRenderer() throws UnsupportedEncodingException { + try { + final HttpRequestAttachment data = negativeHttpRequestAttachment(); + new FreemarkerAttachmentRenderer(TEMPLATE_FOR_EXCEPTION).render(data); + } catch (Exception ignored) { + // for test purposes + } + assertThat(sysOutBuffer.toString(StandardCharsets.UTF_8.toString())) + .contains("SEVERE: Error executing FreeMarker template") + .contains("FreeMarker template error:") + .contains("The following has evaluated to null or missing:") + .contains("==> data.body") + .contains("[in template \"body-npe-non-safe-attachment.ftl\" at line 8, column 11]") + .contains("\t- Failed at: ${data.body.size}") + .contains("io.qameta.allure.attachment.FreemarkerAttachmentRenderer - HttpRequestAttachment") + .contains("\tname=null,") + .contains("\turl=null,") + .contains("\tbody=null,") + .contains("\theaders={},") + .contains("\tcookies={}"); + } +} diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java index 26eba1c6f..f5933c6db 100644 --- a/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java +++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. @@ -69,6 +69,20 @@ public static Map randomMap() { final Map map = new HashMap<>(); map.put(randomString(), randomString()); map.put(randomString(), randomString()); + map.put(randomString(), null); return map; } + + public static HttpRequestAttachment negativeHttpRequestAttachment() { + return new HttpRequestAttachment( + null, + null, + null, + null, + null, + null, + null + ); + } + } diff --git a/allure-attachments/src/test/resources/tpl/body-npe-non-safe-attachment.ftl b/allure-attachments/src/test/resources/tpl/body-npe-non-safe-attachment.ftl new file mode 100644 index 000000000..813425567 --- /dev/null +++ b/allure-attachments/src/test/resources/tpl/body-npe-non-safe-attachment.ftl @@ -0,0 +1,36 @@ +<#ftl output_format="HTML"> +<#-- @ftlvariable name="data" type="io.qameta.allure.attachment.http.HttpRequestAttachment" --> +
<#if data.method??>${data.method}<#else>GET to <#if data.url??>${data.url}<#else>Unknown
+ +

Body

+
+
+    <#t>${data.body.size}
+    
+
+ +<#if (data.headers)?has_content> +

Headers

+
+ <#list data.headers as name, value> +
${name}: ${value!"null"}
+ +
+ + + +<#if (data.cookies)?has_content> +

Cookies

+
+ <#list data.cookies as name, value> +
${name}: ${value!"null"}
+ +
+ + +<#if data.curl??> +

Curl

+
+ ${data.curl} +
+ diff --git a/allure-cucumber3-jvm/build.gradle.kts b/allure-awaitility/build.gradle.kts similarity index 56% rename from allure-cucumber3-jvm/build.gradle.kts rename to allure-awaitility/build.gradle.kts index 681f1ab7c..3c9ed6c79 100644 --- a/allure-cucumber3-jvm/build.gradle.kts +++ b/allure-awaitility/build.gradle.kts @@ -1,17 +1,16 @@ -description = "Allure CucumberJVM 3.0 Integration" +description = "Allure Awaitlity Integration" val agent: Configuration by configurations.creating -val cucumberVersion = "3.0.2" +val awaitilityVersion = "4.3.0" dependencies { agent("org.aspectj:aspectjweaver") api(project(":allure-java-commons")) - implementation("io.cucumber:cucumber-core:$cucumberVersion") - implementation("io.cucumber:cucumber-java:$cucumberVersion") - testImplementation("commons-io:commons-io") - testImplementation("io.github.glytching:junit-extensions") + compileOnly("org.awaitility:awaitility:$awaitilityVersion") + testImplementation("javax.annotation:javax.annotation-api") testImplementation("org.assertj:assertj-core") + testImplementation("org.awaitility:awaitility:$awaitilityVersion") testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.slf4j:slf4j-simple") testImplementation(project(":allure-java-commons-test")) @@ -22,14 +21,11 @@ dependencies { tasks.jar { manifest { attributes(mapOf( - "Automatic-Module-Name" to "io.qameta.allure.cucumber3jvm" + "Automatic-Module-Name" to "io.qameta.allure.awaitility" )) } } tasks.test { useJUnitPlatform() - doFirst { - jvmArgs("-javaagent:${agent.singleFile}") - } } diff --git a/allure-awaitility/readme.md b/allure-awaitility/readme.md new file mode 100644 index 000000000..1fcb34691 --- /dev/null +++ b/allure-awaitility/readme.md @@ -0,0 +1,71 @@ +## Allure-awaitility +Extended logging for poling and ignored exceptions for [awaitility](https://github.com/awaitility/awaitility) + + +## Wiki +For more information about awaitility highly recommended look into [awaitility usage guide](https://github.com/awaitility/awaitility/wiki/Usage) + + +### Configuration examples +Single line for all awaitility conditions in project +```java +Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener()); +``` + +And another line to prevent breaking allure lifecycle for Steps inside Awaitility evaluations +```java +Awaitility.pollInSameThread(); +``` + +Moreover, it's possible logging only few unstable conditions with method `.conditionEvaluationListener()` +```java +final AtomicInteger atomicInteger = new AtomicInteger(0); +await().with() + .conditionEvaluationListener(new AllureAwaitilityListener()) + .alias("Checking that important counter reached value around 3") + .atMost(Duration.of(1000, ChronoUnit.MILLIS)) + .pollInterval(Duration.of(50, ChronoUnit.MILLIS)) + .until(atomicInteger::getAndIncrement, is(3)); +``` + +How it looks like: +1. Top-level step with condition evaluation definition such as alias or default information. +2. Second-level steps with poling process information +3. Optional second-level step with timeout information +4. Optional second-level steps with ignored information + + +### TimeUnit +Most awaitility users count time as milliseconds, but you can feel free to change print poll information. +```java +Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener().setUnit(TimeUnit.SECONDS)); +``` + + +### Exceptions handling +By default, it's not possible to handle and log any exceptions, but you can try to +[ignore](https://github.com/awaitility/awaitility/wiki/Usage#ignoring-exceptions) and log ignored exceptions. + +```java +final AtomicInteger atomicInteger = new AtomicInteger(0); +await().with() + .ignoreExceptions() //required + .atMost(Duration.of(1000, ChronoUnit.MILLIS)) + .pollInterval(Duration.of(500, ChronoUnit.MILLIS)) + .until(() -> { + if (atomicInteger.getAndIncrement() != 3) { + //this exception will be ignored by awaitility, but logged into Allure + throw new RuntimeException("Something wrong happens"); + } else { + return true; + } + }); +``` + +Then, if you are not impressed with the large volume of logged exceptions, there is the way to disable logging for +ignored exceptions globally. + +```java +Awaitility.ignoreExceptionsByDefault(); +Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener().setLogIgnoredExceptions(false)); +``` \ No newline at end of file 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 new file mode 100644 index 000000000..01b793cf5 --- /dev/null +++ b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/AllureAwaitilityListener.java @@ -0,0 +1,257 @@ +/* + * Copyright 2016-2024 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.awaitility; + +import io.qameta.allure.Allure; +import io.qameta.allure.AllureLifecycle; +import io.qameta.allure.model.Status; +import io.qameta.allure.model.StepResult; +import org.awaitility.Awaitility; +import org.awaitility.core.ConditionEvaluationListener; +import org.awaitility.core.ConditionFactory; +import org.awaitility.core.EvaluatedCondition; +import org.awaitility.core.IgnoredException; +import org.awaitility.core.StartEvaluationEvent; +import org.awaitility.core.TimeoutEvent; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +/** + *

+ * Class implements Awaitility condition listener to log all awaiting and polls as {@link io.qameta.allure.Step}. + *

+ *

+ * Usage with single condition + *

+ * 
+ * Awaitility.await()
+ *     .conditionEvaluationListener(new AllureAwaitilityListener())
+ *     .until(() -> somethingHappen());
+ * 
+ * 
+ *

+ *

+ * Usage globally for all conditions + *

+ * 
+ * Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener());
+ * 
+ * 
+ *

+ * + * @author a-simeshin (Simeshin Artem) + * @see org.awaitility.core.ConditionEvaluationListener + * @see Awaitility#setDefaultConditionEvaluationListener(ConditionEvaluationListener) + * @see ConditionFactory#conditionEvaluationListener(ConditionEvaluationListener) + * @see awaitlity wiki + */ +@SuppressWarnings("unused") +public class AllureAwaitilityListener implements ConditionEvaluationListener { + + private TimeUnit unit; + private boolean logIgnoredExceptions; + private final String onStartStepTextPattern; + private final String onSatisfiedStepTextPattern; + private final String onAwaitStepTextPattern; + private final String onTimeoutStepTextPattern; + private final String onExceptionStepTextPattern; + + private String currentConditionStepUUID; + + private static final InheritableThreadLocal LIFECYCLE + = new InheritableThreadLocal() { + @Override + protected AllureLifecycle initialValue() { + return Allure.getLifecycle(); + } + }; + + public static AllureLifecycle getLifecycle() { + return LIFECYCLE.get(); + } + + /** + * Default all args constructor with default params. + */ + public AllureAwaitilityListener() { + this.unit = MILLISECONDS; + this.logIgnoredExceptions = true; + this.onStartStepTextPattern = "Awaitility: %s"; + this.onSatisfiedStepTextPattern = "%s after %d %s (remaining time %d %s, last poll interval was %s)"; + this.onAwaitStepTextPattern = "%s (elapsed time %d %s, remaining time %d %s (last poll interval was %s))"; + this.onTimeoutStepTextPattern = "Condition timeout. %s"; + this.onExceptionStepTextPattern = "Exception ignored. %s"; + } + + /** + * Set default timeunit used in awaitility in project for properly printing. + * + * @param unit default timeunit in project + * @return this factory + */ + public AllureAwaitilityListener setUnit(final TimeUnit unit) { + this.unit = unit; + return this; + } + + /** + * Set logging ignored exceptions. True by default. + * + * @param logging to log or not + * @return this factory + */ + public AllureAwaitilityListener setLogIgnoredExceptions(final boolean logging) { + this.logIgnoredExceptions = logging; + return this; + } + + /** + * Method creates top-level step with short description about condition. + * + * @param startEvaluationEvent condition evaluation started + */ + @Override + public void beforeEvaluation(final StartEvaluationEvent startEvaluationEvent) { + currentConditionStepUUID = UUID.randomUUID().toString(); + final String nameWoAlias = String.format(onStartStepTextPattern, startEvaluationEvent.getDescription()); + final String nameWithAlias = String.format(onStartStepTextPattern, startEvaluationEvent.getAlias()); + final String stepName = startEvaluationEvent.getAlias() != null ? nameWithAlias : nameWoAlias; + getLifecycle().startStep( + currentConditionStepUUID, + new StepResult() + .setName(stepName) + .setDescription("Awaitility condition started") + .setStatus(Status.FAILED) + ); + } + + /** + * Logging timeout evaluation result. Method should create second-level step with useful info about timeout. + * + * @param timeoutEvent poling ended with timeout + */ + @Override + public void onTimeout(final TimeoutEvent timeoutEvent) { + getLifecycle().updateStep(awaitilityCondition -> { + final String currentTimeoutStepUUID = UUID.randomUUID().toString(); + getLifecycle().startStep( + currentConditionStepUUID, + currentTimeoutStepUUID, + new StepResult() + .setName(String.format(onTimeoutStepTextPattern, timeoutEvent.getDescription())) + .setDescription("Awaitility condition timeout") + .setStatus(Status.BROKEN) + ); + getLifecycle().stopStep(currentTimeoutStepUUID); + }); + getLifecycle().stopStep(currentConditionStepUUID); + } + + /** + * Logging any evaluation result. Method should create second-level step with evaluation result and useful info. + * + * @param condition evaluation result for poling iteration + */ + @Override + public void conditionEvaluated(final EvaluatedCondition condition) { + final String description = condition.getDescription(); + final long elapsedTime = unit.convert(condition.getElapsedTimeInMS(), MILLISECONDS); + final long remainingTime = unit.convert(condition.getRemainingTimeInMS(), MILLISECONDS); + final String unitAsString = unit.toString().toLowerCase(); + + final String message = String.format( + condition.isSatisfied() ? onSatisfiedStepTextPattern : onAwaitStepTextPattern, + description, + elapsedTime, + unitAsString, + remainingTime, + unitAsString, + new TemporalDuration(condition.getPollInterval()) + ); + + getLifecycle().updateStep(awaitilityCondition -> { + final String lastAwaitStepUUID = UUID.randomUUID().toString(); + getLifecycle().startStep( + currentConditionStepUUID, + lastAwaitStepUUID, + new StepResult() + .setName(message) + .setDescription("Awaitility condition satisfied or not, but awaiting still in progress") + .setStatus(Status.PASSED) + ); + getLifecycle().stopStep(lastAwaitStepUUID); + if (condition.isSatisfied()) { + awaitilityCondition.setStatus(Status.PASSED); + getLifecycle().stopStep(currentConditionStepUUID); + } + }); + } + + /** + * Logging ignored exceptions while poling conditions. Active only with + *
await().with().ignoreExceptions()
+ * or + *
Awaitility.ignoreExceptionsByDefault()
+ * + * @param ignoredException ignored exception + * @see Awaitility#ignoreExceptionsByDefault() + * @see Awaitility#ignoreExceptionByDefault(Class) + * @see ConditionFactory#ignoreExceptions() + * @see ConditionFactory#ignoreException(Class) + * @see awaitlity wiki + */ + @Override + public void exceptionIgnored(final IgnoredException ignoredException) { + if (logIgnoredExceptions) { + getLifecycle().updateStep(awaitilityCondition -> { + final String currentExceptionIgnoredStepUUID = UUID.randomUUID().toString(); + final String message = String.format( + onExceptionStepTextPattern, ignoredException.getThrowable().getMessage()); + final StringWriter stringWriter = new StringWriter(); + ignoredException.getThrowable().printStackTrace(new PrintWriter(stringWriter)); + final String stackTrace = stringWriter.toString(); + getLifecycle().startStep( + currentConditionStepUUID, + currentExceptionIgnoredStepUUID, + new StepResult() + .setName(message) + .setDescription("Exception occurred and ignored, but awaiting still in progress") + .setStatus(Status.SKIPPED) + ); + getLifecycle().addAttachment( + ignoredException.getThrowable().getMessage(), "text/plain", ".txt", + stackTrace.getBytes(StandardCharsets.UTF_8)); + getLifecycle().stopStep(currentExceptionIgnoredStepUUID); + }); + } + } + + /** + * For tests only. + * + * @param allure allure lifecycle to set + */ + public static void setLifecycle(final AllureLifecycle allure) { + LIFECYCLE.set(allure); + } + +} diff --git a/allure-awaitility/src/main/java/io/qameta/allure/awaitility/TemporalDuration.java b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/TemporalDuration.java new file mode 100644 index 000000000..d549ee811 --- /dev/null +++ b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/TemporalDuration.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016-2024 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.awaitility; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.UnsupportedTemporalTypeException; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MILLI_OF_SECOND; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; + +/** + * Class helper for Duration printing purposes. + */ +public class TemporalDuration implements TemporalAccessor { + private static final Temporal BASE = LocalDateTime.of(0, 1, 1, 0, 0, 0, 0); + + private static final DateTimeFormatter DTF = new DateTimeFormatterBuilder().optionalStart() + .appendValue(YEAR) + .appendLiteral(" years ").optionalEnd() + .optionalStart().appendLiteral(' ').appendValue(MONTH_OF_YEAR).appendLiteral(" months ").optionalEnd() + .optionalStart().appendLiteral(' ').appendValue(DAY_OF_MONTH).appendLiteral(" days ").optionalEnd() + .optionalStart().appendLiteral(' ').appendValue(HOUR_OF_DAY).appendLiteral(" hours ").optionalEnd() + .optionalStart().appendLiteral(' ').appendValue(MINUTE_OF_HOUR).appendLiteral(" minutes ").optionalEnd() + .optionalStart().appendLiteral(' ').appendValue(SECOND_OF_MINUTE).appendLiteral(" seconds").optionalEnd() + .optionalStart().appendLiteral(' ').appendValue(MILLI_OF_SECOND).appendLiteral(" milliseconds") + .optionalEnd() + .toFormatter(); + + private final Duration duration; + private final Temporal temporal; + + TemporalDuration(final Duration duration) { + this.duration = duration; + this.temporal = duration.addTo(BASE); + } + + @Override + public boolean isSupported(final TemporalField field) { + return temporal.isSupported(field) && temporal.getLong(field) - BASE.getLong(field) != 0L; + } + + @Override + public long getLong(final TemporalField temporalField) { + if (!isSupported(temporalField)) { + throw new UnsupportedTemporalTypeException(temporalField.toString()); + } + return temporal.getLong(temporalField) - BASE.getLong(temporalField); + } + + @Override + public String toString() { + if (duration.compareTo(Duration.ofMillis(1)) < 0) { + return duration.toNanos() + " nanoseconds"; + } else { + return DTF.format(this).trim(); + } + } + +} 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 new file mode 100644 index 000000000..1a3bad814 --- /dev/null +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java @@ -0,0 +1,181 @@ +/* + * Copyright 2016-2024 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.awaitility; + +import io.qameta.allure.model.Status; +import io.qameta.allure.model.StepResult; +import io.qameta.allure.model.TestResult; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import static io.qameta.allure.test.RunUtils.runWithinTestContext; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +class ConditionListenersPositiveTest { + + @BeforeAll + static void setup() { + Awaitility.pollInSameThread(); + } + + /** + * Positive test to check proper allure steps generation. + *

+ * Precondition: listener into condition declaration, await without alias + *

+ * Test should check that: + *

  • 1. Allure has exactly 1 top-level step for 1 await condition
  • + *
  • 2. Top level step has passed status
  • + *
  • 3. Top level step has default name
  • + */ + @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)); + }, + AllureAwaitilityListener::setLifecycle + ).getTestResults(); + + return Stream.of( + 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()) + .allMatch(step -> Status.PASSED.equals(step.getStatus())) + ), + DynamicTest.dynamicTest("Top level step has default name because await() wo alias", () -> + assertThat(testResult.get(0).getSteps()) + .extracting(StepResult::getName) + .containsExactly("Awaitility: Starting evaluation") + ) + ); + } + + /** + * Positive test to check proper allure steps generation. + *

    + * Precondition: listener into condition declaration, await with alias + *

    + * Test should check that: + *

  • 1. Top level step has name with alias from await('alias')
  • + */ + @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)); + }, + AllureAwaitilityListener::setLifecycle + ).getTestResults(); + assertEquals( + "Awaitility: Counter should be at least 3", + testResult.get(0).getSteps().get(0).getName(), + "Top level step has name with alias" + ); + } + + /** + * Positive test to check proper allure steps generation. + *

    + * Precondition: listener into condition declaration, await without alias + *

    + * Test should check that: + *

  • 1. Allure has exactly 4 second-level steps for condition with 4 polls iteration
  • + *
  • 2. All second-level steps should have passed status for successful condition evaluation
  • + *
  • 3. All second-level steps should have information about polling intervals and evaluation
  • + */ + @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)); + }, + AllureAwaitilityListener::setLifecycle + ).getTestResults(); + + return Stream.of( + 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()) + .allMatch(x -> x.getStatus().equals(Status.PASSED)) + ), + 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()) + .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()) + .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()) + .contains("io.qameta.allure.awaitility.ConditionListenersPositiveTest") + .contains("reached its end value of <3> after") + .contains("remaining time") + .contains("last poll interval was") + ) + ); + } + +} 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 new file mode 100644 index 000000000..5c169fd63 --- /dev/null +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2016-2024 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.awaitility; + +import io.qameta.allure.model.Status; +import io.qameta.allure.model.TestResult; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInstance; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import static io.qameta.allure.test.RunUtils.runWithinTestContext; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +class GlobalSettingsNegativeTest { + + @AfterEach + void reset() { + Awaitility.reset(); + } + + @BeforeEach + void setup() { + Awaitility.pollInSameThread(); + Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener()); + } + + /** + * Negative test to check proper allure steps generation. + *

    + * Precondition: static settings, await without alias + *

    + * Test should check that: + *

  • 1. Top level step has broken status
  • + */ + @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)); + }, + AllureAwaitilityListener::setLifecycle + ).getTestResults(); + assertEquals( + Status.FAILED, testResult.get(0).getSteps().get(0).getStatus(), + "Top level step has broken status" + ); + } + + /** + * Positive test to check proper allure steps generation. + *

    + * Precondition: static settings, await without alias + *

    + * Test should check that: + *

  • 1. Allure has exactly 2 second-level steps for condition with 2 polls iteration
  • + *
  • 2. All second-level steps should have passed or broken status for successful and timeout evaluations
  • + *
  • 3. All second-level steps should have information about polling intervals and evaluation
  • + */ + @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)); + }, + AllureAwaitilityListener::setLifecycle + ).getTestResults(); + + return Stream.of( + 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()) + .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("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)) + ); + } + +} 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 new file mode 100644 index 000000000..52a6b50db --- /dev/null +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java @@ -0,0 +1,199 @@ +/* + * Copyright 2016-2024 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.awaitility; + +import io.qameta.allure.model.Status; +import io.qameta.allure.model.StepResult; +import io.qameta.allure.model.TestResult; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import static io.qameta.allure.test.RunUtils.runWithinTestContext; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +class GlobalSettingsPositiveTest { + + @AfterEach + void reset() { + Awaitility.reset(); + } + + @BeforeEach + void setup() { + Awaitility.pollInSameThread(); + Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener()); + } + + /** + * Positive test to check proper allure steps generation. + *

    + * Precondition: static settings, await without alias + *

    + * Test should check that: + *

  • 1. Allure has exactly 1 top-level step for 1 await condition
  • + *
  • 2. Top level step has passed status
  • + *
  • 3. Top level step has default name
  • + */ + @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)); + }, + AllureAwaitilityListener::setLifecycle + ).getTestResults(); + + return Stream.of( + 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( + Status.PASSED, testResult.get(0).getSteps().get(0).getStatus(), + "Top level step has passed status" + ) + ), + 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" + ) + ) + ); + } + + /** + * Positive test to check proper allure steps generation. + *

    + * Precondition: static settings, await with alias + *

    + * Test should check that: + *

  • 1. Top level step has name with alias from await('alias')
  • + */ + @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)); + }, + AllureAwaitilityListener::setLifecycle + ).getTestResults(); + assertEquals( + "Awaitility: Counter should be at least 3", + testResult.get(0).getSteps().get(0).getName(), + "Top level step has name with alias" + ); + } + + /** + * Positive test to check proper allure steps generation. + *

    + * Precondition: static settings, await without alias + *

    + * Test should check that: + *

  • 1. Allure has exactly 4 second-level steps for condition with 4 polls iteration
  • + *
  • 2. All second-level steps should have passed status for successful condition evaluation
  • + *
  • 3. All second-level steps should have information about polling intervals and evaluation
  • + */ + @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)); + }, + AllureAwaitilityListener::setLifecycle + ).getTestResults(); + + return Stream.of( + 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()) + .extracting(StepResult::getStatus) + .containsExactlyInAnyOrder( + Status.PASSED, + Status.PASSED, + Status.PASSED, + Status.PASSED + ) + ), + 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()) + .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()) + .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()) + .contains("io.qameta.allure.awaitility.GlobalSettingsPositiveTest") + .contains("java.util.concurrent.atomic.AtomicInteger:") + .contains("reached its end value of <3> after") + .contains("remaining time") + .contains("last poll interval was") + ) + ); + } + +} 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 new file mode 100644 index 000000000..36a74a164 --- /dev/null +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016-2024 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.awaitility; + +import io.qameta.allure.model.Status; +import io.qameta.allure.model.TestResult; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.*; + +import java.util.List; +import java.util.stream.Stream; + +import static io.qameta.allure.test.RunUtils.runWithinTestContext; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class MultipleConditionsTest { + + @AfterEach + void reset() { + Awaitility.reset(); + } + + @BeforeEach + void setup() { + Awaitility.pollInSameThread(); + Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener()); + } + + @TestFactory + Stream bothAwaitilityStepsShouldAppearTest() { + final List testResult = runWithinTestContext(() -> { + 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()) + .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()) + .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-bom/build.gradle.kts b/allure-bom/build.gradle.kts new file mode 100644 index 000000000..9593300aa --- /dev/null +++ b/allure-bom/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + `java-platform` +} + +description = "Allure Java (Bill of Materials)" + +dependencies { + constraints { + rootProject.subprojects.sorted() + .forEach { api("${it.group}:${it.name}:${it.version}") } + } +} + +tasks.withType(Jar::class) { + enabled = false +} + +configurations.archives { + artifacts.removeAll{ it.extension == "jar" } +} + +publishing.publications.named("maven") { + pom { + from(components["javaPlatform"]) + description.set("This Bill of Materials POM can be used to ease dependency management " + + "when referencing multiple Allure artifacts using Gradle or Maven.") + packaging = "pom" + withXml { + val filteredContent = asString().replace("\\s*compile".toRegex(), "") + asString().clear().append(filteredContent) + } + } +} diff --git a/allure-citrus/build.gradle.kts b/allure-citrus/build.gradle.kts index bd740f9a7..dbad67a9c 100644 --- a/allure-citrus/build.gradle.kts +++ b/allure-citrus/build.gradle.kts @@ -1,11 +1,8 @@ description = "Allure Citrus Integration" -val agent: Configuration by configurations.creating - val citrusVersion = "2.8.0" dependencies { - agent("org.aspectj:aspectjweaver") api(project(":allure-java-commons")) compileOnly("com.consol.citrus:citrus-core:$citrusVersion") testImplementation("com.consol.citrus:citrus-http:$citrusVersion") @@ -32,7 +29,4 @@ tasks.jar { tasks.test { useJUnitPlatform() exclude("**/samples/*") - doFirst { - jvmArgs("-javaagent:${agent.singleFile}") - } } 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 3db26c236..21f68bebe 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 @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. 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 a31fd6f00..7f29c0490 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 @@ -1,5 +1,5 @@ /* - * Copyright 2019 Qameta Software OÜ + * Copyright 2016-2024 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. diff --git a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/AllureCucumberJvm.java b/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/AllureCucumberJvm.java deleted file mode 100644 index 311802432..000000000 --- a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/AllureCucumberJvm.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright 2019 Qameta Software OÜ - * - * 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.cucumberjvm; - -import cucumber.runtime.StepDefinitionMatch; -import gherkin.I18n; -import gherkin.formatter.Formatter; -import gherkin.formatter.Reporter; -import gherkin.formatter.model.Background; -import gherkin.formatter.model.DataTableRow; -import gherkin.formatter.model.Examples; -import gherkin.formatter.model.Feature; -import gherkin.formatter.model.Match; -import gherkin.formatter.model.Result; -import gherkin.formatter.model.Scenario; -import gherkin.formatter.model.ScenarioOutline; -import gherkin.formatter.model.Step; -import gherkin.formatter.model.Tag; -import io.qameta.allure.Allure; -import io.qameta.allure.AllureLifecycle; -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.util.ResultsUtils; - -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Allure plugin for Cucumber-JVM. - */ -@SuppressWarnings({ - "PMD.ExcessiveImports", - "ClassFanOutComplexity", - "ClassDataAbstractionCoupling", - "unused" -}) -public class AllureCucumberJvm implements Reporter, Formatter { - - private static final List SCENARIO_OUTLINE_KEYWORDS = Collections.synchronizedList(new ArrayList()); - - private static final String FAILED = "failed"; - private static final String PASSED = "passed"; - private static final String SKIPPED = "skipped"; - private static final String PENDING = "pending"; - - private static final String TXT_EXTENSION = ".txt"; - private static final String TEXT_PLAIN = "text/plain"; - - private final Map scenarioUuids = new ConcurrentHashMap<>(); - private final Deque gherkinSteps = new LinkedList<>(); - private final AllureLifecycle lifecycle; - private Feature currentFeature; - private boolean isNullMatch = true; - private Scenario currentScenario; - - - @SuppressWarnings("unused") - public AllureCucumberJvm() { - this(Allure.getLifecycle()); - } - - public AllureCucumberJvm(final AllureLifecycle lifecycle) { - this.lifecycle = lifecycle; - final List i18nList = I18n.getAll(); - i18nList.forEach(i18n -> SCENARIO_OUTLINE_KEYWORDS.addAll(i18n.keywords("scenario_outline"))); - } - - @Override - public void feature(final Feature feature) { - this.currentFeature = feature; - } - - @Override - public void before(final Match match, final Result result) { - new StepUtils(currentFeature, currentScenario).fireFixtureStep(match, result, true); - } - - @Override - public void after(final Match match, final Result result) { - new StepUtils(currentFeature, currentScenario).fireFixtureStep(match, result, false); - } - - @Override - public void startOfScenarioLifeCycle(final Scenario scenario) { - this.currentScenario = scenario; - - final Deque tags = new LinkedList<>(scenario.getTags()); - - if (SCENARIO_OUTLINE_KEYWORDS.contains(scenario.getKeyword())) { - synchronized (gherkinSteps) { - gherkinSteps.clear(); - } - } else { - tags.addAll(currentFeature.getTags()); - } - - final LabelBuilder labelBuilder = new LabelBuilder(currentFeature, scenario, tags); - - final String uuid = UUID.randomUUID().toString(); - scenarioUuids.put(scenario, uuid); - - final TestResult result = new TestResult() - .setUuid(uuid) - .setHistoryId(StepUtils.getHistoryId(scenario.getId())) - .setFullName(String.format("%s: %s", currentFeature.getName(), scenario.getName())) - .setName(scenario.getName()) - .setLabels(labelBuilder.getScenarioLabels()) - .setLinks(labelBuilder.getScenarioLinks()); - - if (!currentFeature.getDescription().isEmpty()) { - result.setDescription(currentFeature.getDescription()); - } - - lifecycle.scheduleTestCase(result); - lifecycle.startTestCase(uuid); - - } - - @Override - public void step(final Step step) { - synchronized (gherkinSteps) { - gherkinSteps.add(step); - } - } - - @Override - public void match(final Match match) { - final StepUtils stepUtils = new StepUtils(currentFeature, currentScenario); - if (match instanceof StepDefinitionMatch) { - isNullMatch = false; - final Step step = stepUtils.extractStep((StepDefinitionMatch) match); - synchronized (gherkinSteps) { - while (gherkinSteps.peek() != null && !stepUtils.isEqualSteps(step, gherkinSteps.peek())) { - stepUtils.fireCanceledStep(gherkinSteps.remove()); - } - if (stepUtils.isEqualSteps(step, gherkinSteps.peek())) { - gherkinSteps.remove(); - } - } - final StepResult stepResult = new StepResult(); - stepResult.setName(String.format("%s %s", step.getKeyword(), getStepName(step))) - .setStart(System.currentTimeMillis()); - - final String scenarioUuid = scenarioUuids.get(currentScenario); - lifecycle.startStep(scenarioUuid, stepUtils.getStepUuid(step), stepResult); - createDataTableAttachment(step.getRows()); - } - } - - @SuppressWarnings("PMD.NcssCount") - @Override - public void result(final Result result) { - if (!isNullMatch) { - final StatusDetails statusDetails = - ResultsUtils.getStatusDetails(result.getError()).orElse(new StatusDetails()); - final TagParser tagParser = new TagParser(currentFeature, currentScenario); - statusDetails - .setFlaky(tagParser.isFlaky()) - .setMuted(tagParser.isMuted()) - .setKnown(tagParser.isKnown()); - - final String scenarioUuid = scenarioUuids.get(currentScenario); - switch (result.getStatus()) { - case FAILED: - final Status status = ResultsUtils.getStatus(result.getError()) - .orElse(Status.FAILED); - lifecycle.updateStep(stepResult -> stepResult.setStatus(Status.FAILED)); - lifecycle.updateTestCase(scenarioUuid, scenarioResult -> - scenarioResult.setStatus(status) - .setStatusDetails(statusDetails)); - lifecycle.stopStep(); - break; - case PENDING: - lifecycle.updateStep(stepResult -> stepResult.setStatus(Status.SKIPPED)); - lifecycle.updateTestCase(scenarioUuid, scenarioResult -> - scenarioResult.setStatus(Status.SKIPPED) - .setStatusDetails(statusDetails)); - lifecycle.stopStep(); - break; - case SKIPPED: - lifecycle.updateStep(stepResult -> stepResult.setStatus(Status.SKIPPED)); - lifecycle.stopStep(); - break; - case PASSED: - lifecycle.updateStep(stepResult -> stepResult.setStatus(Status.PASSED)); - lifecycle.stopStep(); - lifecycle.updateTestCase(scenarioUuid, scenarioResult -> - scenarioResult.setStatus(Status.PASSED) - .setStatusDetails(statusDetails)); - break; - default: - break; - } - isNullMatch = true; - } - } - - @Override - public void endOfScenarioLifeCycle(final Scenario scenario) { - final StepUtils stepUtils = new StepUtils(currentFeature, currentScenario); - synchronized (gherkinSteps) { - while (gherkinSteps.peek() != null) { - stepUtils.fireCanceledStep(gherkinSteps.remove()); - } - } - final String scenarioUuid = scenarioUuids.remove(scenario); - lifecycle.stopTestCase(scenarioUuid); - lifecycle.writeTestCase(scenarioUuid); - } - - public String getStepName(final Step step) { - return step.getName(); - } - - private void createDataTableAttachment(final List dataTableRows) { - final StringBuilder dataTableCsv = new StringBuilder(); - - if (dataTableRows != null && !dataTableRows.isEmpty()) { - dataTableRows.forEach(dataTableRow -> { - dataTableCsv.append(String.join("\t", dataTableRow.getCells())); - dataTableCsv.append('\n'); - }); - - final String attachmentSource = lifecycle - .prepareAttachment("Data table", "text/tab-separated-values", "csv"); - lifecycle.writeAttachment(attachmentSource, - new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8))); - } - } - - @Override - public void embedding(final String string, final byte[] bytes) { - lifecycle.addAttachment("Screenshot", null, null, new ByteArrayInputStream(bytes)); - } - - @Override - public void write(final String string) { - lifecycle.addAttachment( - "Text output", - TEXT_PLAIN, - TXT_EXTENSION, - Objects.toString(string).getBytes(StandardCharsets.UTF_8) - ); - } - - @Override - public void syntaxError(final String state, final String event, - final List legalEvents, final String uri, final Integer line) { - //Nothing to do with Allure - } - - @Override - public void uri(final String uri) { - //Nothing to do with Allure - } - - @Override - public void scenarioOutline(final ScenarioOutline so) { - //Nothing to do with Allure - } - - @Override - public void examples(final Examples exmpls) { - //Nothing to do with Allure - } - - - @Override - public void background(final Background b) { - //Nothing to do with Allure - } - - @Override - public void scenario(final Scenario scnr) { - //Nothing to do with Allure - } - - @Override - public void done() { - //Nothing to do with Allure - } - - @Override - public void close() { - //Nothing to do with Allure - } - - @Override - public void eof() { - //Nothing to do with Allure - - } - -} diff --git a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/LabelBuilder.java b/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/LabelBuilder.java deleted file mode 100644 index 8acfe213d..000000000 --- a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/LabelBuilder.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2019 Qameta Software OÜ - * - * 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.cucumberjvm; - -import gherkin.formatter.model.Feature; -import gherkin.formatter.model.Scenario; -import gherkin.formatter.model.Tag; -import io.qameta.allure.model.Label; -import io.qameta.allure.model.Link; -import io.qameta.allure.util.ResultsUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Deque; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; - -import static io.qameta.allure.util.ResultsUtils.createFeatureLabel; -import static io.qameta.allure.util.ResultsUtils.createFrameworkLabel; -import static io.qameta.allure.util.ResultsUtils.createHostLabel; -import static io.qameta.allure.util.ResultsUtils.createLanguageLabel; -import static io.qameta.allure.util.ResultsUtils.createPackageLabel; -import static io.qameta.allure.util.ResultsUtils.createSeverityLabel; -import static io.qameta.allure.util.ResultsUtils.createStoryLabel; -import static io.qameta.allure.util.ResultsUtils.createSuiteLabel; -import static io.qameta.allure.util.ResultsUtils.createTagLabel; -import static io.qameta.allure.util.ResultsUtils.createTestClassLabel; -import static io.qameta.allure.util.ResultsUtils.createThreadLabel; - -/** - * Scenario labels and links builder. - */ -@SuppressWarnings({"CyclomaticComplexity", "PMD.CyclomaticComplexity", "PMD.NcssCount"}) -class LabelBuilder { - private static final Logger LOGGER = LoggerFactory.getLogger(LabelBuilder.class); - private static final String COMPOSITE_TAG_DELIMITER = "="; - - private static final String SEVERITY = "@SEVERITY"; - private static final String ISSUE_LINK = "@ISSUE"; - private static final String TMS_LINK = "@TMSLINK"; - private static final String PLAIN_LINK = "@LINK"; - - private final List