diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76fe22870..f08fce06b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,10 +19,10 @@ jobs: matrix: java-version: [ '8.0.x', '11.0.x' ] steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3.0.2 - name: Set up JDK ${{ matrix.java-version }} - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: ${{ matrix.java-version }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 956bf2f95..e0d99fc81 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -7,6 +7,6 @@ jobs: triage: runs-on: ubuntu-latest steps: - - uses: actions/labeler@v3 + - uses: actions/labeler@v4 with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 79868e2a2..9ff564c62 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,10 +8,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3.0.2 - name: Set up JDK 1.8 - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: 8.0.x diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10a55d378..cdefd986d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - name: "Check next version" run: | expr "${{ github.event.inputs.nextVersion }}" : '[[:digit:]][[:digit:]]*\.[[:digit:]][[:digit:]]*$' - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3.0.2 with: token: ${{ secrets.QAMETA_CI }} diff --git a/README.md b/README.md index 210b43700..afccb2d6b 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,40 @@ You can specify custom templates, which should be placed in src/main/resources/t ``` +## 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. diff --git a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java index 724e058db..cd0c8aca5 100644 --- a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java +++ b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java @@ -51,6 +51,7 @@ import java.io.ByteArrayInputStream; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; import java.util.Collections; import java.util.Deque; import java.util.LinkedList; @@ -100,6 +101,7 @@ public class AllureCucumber5Jvm implements ConcurrentEventListener { private static final String TXT_EXTENSION = ".txt"; private static final String TEXT_PLAIN = "text/plain"; + private static final String CUCUMBER_WORKING_DIR = Paths.get("").toUri().toString(); @SuppressWarnings("unused") public AllureCucumber5Jvm() { @@ -276,12 +278,18 @@ private String getHookStepUuid(final HookTestStep step) { } private String getHistoryId(final TestCase testCase) { - final String testCaseLocation = testCase.getUri().toString() - .substring(testCase.getUri().toString().lastIndexOf('/') + 1) - + ":" + testCase.getLine(); + final String testCaseLocation = getTestCaseUri(testCase) + ":" + testCase.getLine(); return md5(testCaseLocation); } + private String getTestCaseUri(final TestCase testCase) { + final String testCaseUri = testCase.getUri().toString(); + if (testCaseUri.startsWith(CUCUMBER_WORKING_DIR)) { + return testCaseUri.substring(CUCUMBER_WORKING_DIR.length()); + } + return testCaseUri; + } + private Status translateTestCaseStatus(final Result testCaseResult) { switch (testCaseResult.getStatus()) { case FAILED: diff --git a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/AllureCucumber5JvmTest.java b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/AllureCucumber5JvmTest.java index 6541f639b..023c52017 100644 --- a/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/AllureCucumber5JvmTest.java +++ b/allure-cucumber5-jvm/src/test/java/io/qameta/allure/cucumber5jvm/AllureCucumber5JvmTest.java @@ -565,7 +565,7 @@ void shouldPersistHistoryIdForScenarios() { final List testResults = writer.getTestResults(); assertThat(testResults.get(0).getHistoryId()) - .isEqualTo("8eea9ed4458a49d418859d1398580671"); + .isEqualTo("892e5eabe51184301cf1358453c9f052"); } @AllureFeatures.History @@ -577,11 +577,20 @@ void shouldPersistHistoryIdForExamples() { final List testResults = writer.getTestResults(); assertThat(testResults) .extracting(TestResult::getHistoryId) - .containsExactlyInAnyOrder("42a7821e775ec18b112f92e96f0510a5", "afb27d131ed8d41b3f867895a26d2590"); + .containsExactlyInAnyOrder("c0f824814a130048e9f86358363cf23e", "646aca5d0775cd4f13161e1ea1a68c39"); } - private Comparator byHistoryId = - Comparator.comparing(TestResult::getHistoryId); + @AllureFeatures.History + @Test + void shouldPersistDifferentHistoryIdComparedToTheSameTestCaseInDifferentLocation() { + final AllureResultsWriterStub writer = new AllureResultsWriterStub(); + runFeature(writer, "features/simple.feature"); + runFeature(writer, "features/same/simple.feature"); + + final List testResults = writer.getTestResults(); + assertThat(testResults.get(0).getHistoryId()) + .isNotEqualTo(testResults.get(1).getHistoryId()); + } @AllureFeatures.Parallel @Test diff --git a/allure-cucumber5-jvm/src/test/resources/features/same/simple.feature b/allure-cucumber5-jvm/src/test/resources/features/same/simple.feature new file mode 100644 index 000000000..c5b5f2b5f --- /dev/null +++ b/allure-cucumber5-jvm/src/test/resources/features/same/simple.feature @@ -0,0 +1,7 @@ +Feature: Simple feature + + Scenario: Add a to b + Given a is 5 + And b is 10 + When I add a to b + Then result is 15 diff --git a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java index c208ecd9f..ed3bc9e66 100644 --- a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java +++ b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java @@ -50,6 +50,7 @@ import java.io.ByteArrayInputStream; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; import java.util.Collections; import java.util.Deque; import java.util.LinkedList; @@ -99,6 +100,7 @@ public class AllureCucumber6Jvm implements ConcurrentEventListener { private static final String TXT_EXTENSION = ".txt"; private static final String TEXT_PLAIN = "text/plain"; + private static final String CUCUMBER_WORKING_DIR = Paths.get("").toUri().toString(); @SuppressWarnings("unused") public AllureCucumber6Jvm() { @@ -279,12 +281,18 @@ private String getHookStepUuid(final HookTestStep step) { } private String getHistoryId(final TestCase testCase) { - final String testCaseLocation = testCase.getUri().toString() - .substring(testCase.getUri().toString().lastIndexOf('/') + 1) - + ":" + testCase.getLocation().getLine(); + final String testCaseLocation = getTestCaseUri(testCase) + ":" + testCase.getLocation().getLine(); return md5(testCaseLocation); } + private String getTestCaseUri(final TestCase testCase) { + final String testCaseUri = testCase.getUri().toString(); + if (testCaseUri.startsWith(CUCUMBER_WORKING_DIR)) { + return testCaseUri.substring(CUCUMBER_WORKING_DIR.length()); + } + return testCaseUri; + } + private Status translateTestCaseStatus(final Result testCaseResult) { switch (testCaseResult.getStatus()) { case FAILED: diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java index 98c79cce8..68c61e24f 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java @@ -565,7 +565,7 @@ void shouldPersistHistoryIdForScenarios() { final List testResults = writer.getTestResults(); assertThat(testResults.get(0).getHistoryId()) - .isEqualTo("8eea9ed4458a49d418859d1398580671"); + .isEqualTo("892e5eabe51184301cf1358453c9f052"); } @AllureFeatures.History @@ -577,11 +577,20 @@ void shouldPersistHistoryIdForExamples() { final List testResults = writer.getTestResults(); assertThat(testResults) .extracting(TestResult::getHistoryId) - .containsExactlyInAnyOrder("42a7821e775ec18b112f92e96f0510a5", "afb27d131ed8d41b3f867895a26d2590"); + .containsExactlyInAnyOrder("c0f824814a130048e9f86358363cf23e", "646aca5d0775cd4f13161e1ea1a68c39"); } - private Comparator byHistoryId = - Comparator.comparing(TestResult::getHistoryId); + @AllureFeatures.History + @Test + void shouldPersistDifferentHistoryIdComparedToTheSameTestCaseInDifferentLocation() { + final AllureResultsWriterStub writer = new AllureResultsWriterStub(); + runFeature(writer, "features/simple.feature"); + runFeature(writer, "features/same/simple.feature"); + + final List testResults = writer.getTestResults(); + assertThat(testResults.get(0).getHistoryId()) + .isNotEqualTo(testResults.get(1).getHistoryId()); + } @AllureFeatures.Parallel @Test diff --git a/allure-cucumber6-jvm/src/test/resources/features/same/simple.feature b/allure-cucumber6-jvm/src/test/resources/features/same/simple.feature new file mode 100644 index 000000000..c5b5f2b5f --- /dev/null +++ b/allure-cucumber6-jvm/src/test/resources/features/same/simple.feature @@ -0,0 +1,7 @@ +Feature: Simple feature + + Scenario: Add a to b + Given a is 5 + And b is 10 + When I add a to b + Then result is 15 diff --git a/allure-cucumber7-jvm/build.gradle.kts b/allure-cucumber7-jvm/build.gradle.kts index b6104c0aa..eceb85428 100644 --- a/allure-cucumber7-jvm/build.gradle.kts +++ b/allure-cucumber7-jvm/build.gradle.kts @@ -1,7 +1,7 @@ description = "Allure CucumberJVM 7.0" -val cucumberVersion = "7.0.0" -val cucumberGherkinVersion = "22.0.0" +val cucumberVersion = "7.3.2" +val cucumberGherkinVersion = "23.0.1" dependencies { api(project(":allure-java-commons")) diff --git a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java index 467f533af..38f0e52f1 100644 --- a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java +++ b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java @@ -50,6 +50,7 @@ import java.io.ByteArrayInputStream; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; import java.util.Collections; import java.util.Deque; import java.util.LinkedList; @@ -99,6 +100,7 @@ public class AllureCucumber7Jvm implements ConcurrentEventListener { private static final String TXT_EXTENSION = ".txt"; private static final String TEXT_PLAIN = "text/plain"; + private static final String CUCUMBER_WORKING_DIR = Paths.get("").toUri().toString(); @SuppressWarnings("unused") public AllureCucumber7Jvm() { @@ -279,12 +281,18 @@ private String getHookStepUuid(final HookTestStep step) { } private String getHistoryId(final TestCase testCase) { - final String testCaseLocation = testCase.getUri().toString() - .substring(testCase.getUri().toString().lastIndexOf('/') + 1) - + ":" + testCase.getLocation().getLine(); + final String testCaseLocation = getTestCaseUri(testCase) + ":" + testCase.getLocation().getLine(); return md5(testCaseLocation); } + private String getTestCaseUri(final TestCase testCase) { + final String testCaseUri = testCase.getUri().toString(); + if (testCaseUri.startsWith(CUCUMBER_WORKING_DIR)) { + return testCaseUri.substring(CUCUMBER_WORKING_DIR.length()); + } + return testCaseUri; + } + private Status translateTestCaseStatus(final Result testCaseResult) { switch (testCaseResult.getStatus()) { case FAILED: @@ -329,9 +337,9 @@ private List getExamplesAsParameters( final TableRow row = maybeRow.get(); - return IntStream.range(0, examples.getTableHeader().getCells().size()) + return IntStream.range(0, examples.getTableHeader().get().getCells().size()) .mapToObj(index -> { - final String name = examples.getTableHeader().getCells().get(index).getValue(); + final String name = examples.getTableHeader().get().getCells().get(index).getValue(); final String value = row.getCells().get(index).getValue(); return createParameter(name, value); }) diff --git a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModel.java b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModel.java index 7741c41dc..5ce13730f 100644 --- a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModel.java +++ b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModel.java @@ -15,7 +15,7 @@ */ package io.qameta.allure.cucumber7jvm.testsourcemodel; -import io.cucumber.gherkin.Gherkin; +import io.cucumber.gherkin.GherkinParser; import io.cucumber.messages.types.Background; import io.cucumber.messages.types.Envelope; import io.cucumber.messages.types.Examples; @@ -24,20 +24,17 @@ import io.cucumber.messages.types.GherkinDocument; import io.cucumber.messages.types.RuleChild; import io.cucumber.messages.types.Scenario; +import io.cucumber.messages.types.Source; +import io.cucumber.messages.types.SourceMediaType; import io.cucumber.messages.types.Step; import io.cucumber.messages.types.TableRow; import io.cucumber.plugin.event.TestSourceRead; import java.net.URI; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -import static io.cucumber.gherkin.Gherkin.makeSourceEnvelope; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; +import java.util.Optional; +import java.util.stream.Stream; final class TestSourcesModel { private final Map pathToReadEventMap = new HashMap<>(); @@ -61,7 +58,7 @@ public Feature getFeature(final URI path) { parseGherkinSource(path); } if (pathToAstMap.containsKey(path)) { - return pathToAstMap.get(path).getFeature(); + return pathToAstMap.get(path).getFeature().orElse(null); } return null; } @@ -72,26 +69,27 @@ private void parseGherkinSource(final URI path) { } final String source = pathToReadEventMap.get(path).getSource(); - final List sources = singletonList( - makeSourceEnvelope(source, path.toString())); + final GherkinParser parser = GherkinParser.builder() + .build(); - final List envelopes = Gherkin.fromSources( - sources, - true, - true, - true, - () -> String.valueOf(UUID.randomUUID())).collect(toList()); + final Stream envelopes = parser.parse( + Envelope.of(new Source(path.toString(), source, SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN))); - final GherkinDocument gherkinDocument = envelopes.stream() + // TODO: What about empty gherkin docs? + final GherkinDocument gherkinDocument = envelopes .map(Envelope::getGherkinDocument) - .filter(Objects::nonNull) + .filter(Optional::isPresent) + .map(Optional::get) .findFirst() .orElse(null); pathToAstMap.put(path, gherkinDocument); final Map nodeMap = new HashMap<>(); - final AstNode currentParent = createAstNode(Objects.requireNonNull(gherkinDocument).getFeature(), null); - for (FeatureChild child : gherkinDocument.getFeature().getChildren()) { + + // TODO: What about gherkin docs with no features? + final Feature feature = gherkinDocument.getFeature().get(); + final AstNode currentParent = new AstNode(feature, null); + for (FeatureChild child : feature.getChildren()) { processFeatureDefinition(nodeMap, child, currentParent); } pathToNodeMap.put(path, nodeMap); @@ -100,17 +98,13 @@ private void parseGherkinSource(final URI path) { private void processFeatureDefinition( final Map nodeMap, final FeatureChild child, final AstNode currentParent) { - if (child.getBackground() != null) { - processBackgroundDefinition(nodeMap, child.getBackground(), currentParent); - } else if (child.getScenario() != null) { - processScenarioDefinition(nodeMap, child.getScenario(), currentParent); - } else if (child.getRule() != null) { - final AstNode childNode = createAstNode(child.getRule(), currentParent); - nodeMap.put(child.getRule().getLocation().getLine(), childNode); - for (RuleChild ruleChild : child.getRule().getChildren()) { - processRuleDefinition(nodeMap, ruleChild, childNode); - } - } + child.getBackground().ifPresent(background -> processBackgroundDefinition(nodeMap, background, currentParent)); + child.getScenario().ifPresent(scenario -> processScenarioDefinition(nodeMap, scenario, currentParent)); + child.getRule().ifPresent(rule -> { + final AstNode childNode = new AstNode(rule, currentParent); + nodeMap.put(rule.getLocation().getLine(), childNode); + rule.getChildren().forEach(ruleChild -> processRuleDefinition(nodeMap, ruleChild, childNode)); + }); } private void processBackgroundDefinition( @@ -137,11 +131,8 @@ private void processScenarioDefinition( private void processRuleDefinition( final Map nodeMap, final RuleChild child, final AstNode currentParent) { - if (child.getBackground() != null) { - processBackgroundDefinition(nodeMap, child.getBackground(), currentParent); - } else if (child.getScenario() != null) { - processScenarioDefinition(nodeMap, child.getScenario(), currentParent); - } + child.getBackground().ifPresent(background -> processBackgroundDefinition(nodeMap, background, currentParent)); + child.getScenario().ifPresent(scenario -> processScenarioDefinition(nodeMap, scenario, currentParent)); } private void processScenarioOutlineExamples( @@ -149,7 +140,8 @@ private void processScenarioOutlineExamples( ) { for (Examples examples : scenarioOutline.getExamples()) { final AstNode examplesNode = createAstNode(examples, parent); - final TableRow headerRow = examples.getTableHeader(); + // TODO: Can tables without headers even exist? + final TableRow headerRow = examples.getTableHeader().get(); final AstNode headerNode = createAstNode(headerRow, examplesNode); nodeMap.put(headerRow.getLocation().getLine(), headerNode); for (int i = 0; i < examples.getTableBody().size(); ++i) { diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java index 56cd80f72..34a42887a 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java @@ -565,7 +565,7 @@ void shouldPersistHistoryIdForScenarios() { final List testResults = writer.getTestResults(); assertThat(testResults.get(0).getHistoryId()) - .isEqualTo("8eea9ed4458a49d418859d1398580671"); + .isEqualTo("892e5eabe51184301cf1358453c9f052"); } @AllureFeatures.History @@ -577,11 +577,20 @@ void shouldPersistHistoryIdForExamples() { final List testResults = writer.getTestResults(); assertThat(testResults) .extracting(TestResult::getHistoryId) - .containsExactlyInAnyOrder("42a7821e775ec18b112f92e96f0510a5", "afb27d131ed8d41b3f867895a26d2590"); + .containsExactlyInAnyOrder("c0f824814a130048e9f86358363cf23e", "646aca5d0775cd4f13161e1ea1a68c39"); } - private Comparator byHistoryId = - Comparator.comparing(TestResult::getHistoryId); + @AllureFeatures.History + @Test + void shouldPersistDifferentHistoryIdComparedToTheSameTestCaseInDifferentLocation() { + final AllureResultsWriterStub writer = new AllureResultsWriterStub(); + runFeature(writer, "features/simple.feature"); + runFeature(writer, "features/same/simple.feature"); + + final List testResults = writer.getTestResults(); + assertThat(testResults.get(0).getHistoryId()) + .isNotEqualTo(testResults.get(1).getHistoryId()); + } @AllureFeatures.Parallel @Test diff --git a/allure-cucumber7-jvm/src/test/resources/features/same/simple.feature b/allure-cucumber7-jvm/src/test/resources/features/same/simple.feature new file mode 100644 index 000000000..c5b5f2b5f --- /dev/null +++ b/allure-cucumber7-jvm/src/test/resources/features/same/simple.feature @@ -0,0 +1,7 @@ +Feature: Simple feature + + Scenario: Add a to b + Given a is 5 + And b is 10 + When I add a to b + Then result is 15 diff --git a/allure-grpc/build.gradle.kts b/allure-grpc/build.gradle.kts new file mode 100644 index 000000000..0826a212c --- /dev/null +++ b/allure-grpc/build.gradle.kts @@ -0,0 +1,74 @@ +import com.google.protobuf.gradle.* + +plugins { + id("com.google.protobuf") +} + +description = "Allure gRPC Integration" + +val agent: Configuration by configurations.creating + +val grpcVersion = "1.46.0" +val protobufVersion = "3.20.1" + +dependencies { + agent("org.aspectj:aspectjweaver") + api(project(":allure-attachments")) + implementation("io.grpc:grpc-core:$grpcVersion") + implementation("com.google.protobuf:protobuf-java-util:$protobufVersion") + + testImplementation("io.grpc:grpc-stub:$grpcVersion") + testImplementation("io.grpc:grpc-protobuf:$grpcVersion") + testImplementation("io.grpc:grpc-netty-shaded:$grpcVersion") + testImplementation("com.google.protobuf:protobuf-java:$protobufVersion") + testImplementation("org.grpcmock:grpcmock-junit5") + testImplementation("javax.annotation:javax.annotation-api") + testImplementation("org.assertj:assertj-core") + testImplementation("org.junit.jupiter:junit-jupiter-api") + testImplementation("org.slf4j:slf4j-simple") + testImplementation(project(":allure-java-commons-test")) + testImplementation(project(":allure-junit-platform")) + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") +} + +tasks.jar { + manifest { + attributes(mapOf( + "Automatic-Module-Name" to "io.qameta.allure.grpc" + )) + } +} + +tasks.test { + useJUnitPlatform() + doFirst { + jvmArgs("-javaagent:${agent.singleFile}") + } +} + +sourceSets { + test { + java { + srcDir("build/generated/source/proto/test") + srcDir("src/test/java") + } + } +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:$protobufVersion" + } + plugins { + id("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" + } + } + generateProtoTasks { + ofSourceSet("test").forEach { + it.plugins { + id("grpc") + } + } + } +} diff --git a/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java b/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java new file mode 100644 index 000000000..2d2858a39 --- /dev/null +++ b/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java @@ -0,0 +1,203 @@ +/* + * 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.grpc; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.MessageOrBuilder; +import com.google.protobuf.util.JsonFormat; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.qameta.allure.Allure; +import io.qameta.allure.attachment.AttachmentData; +import io.qameta.allure.attachment.AttachmentProcessor; +import io.qameta.allure.attachment.DefaultAttachmentProcessor; +import io.qameta.allure.attachment.FreemarkerAttachmentRenderer; +import io.qameta.allure.model.Status; +import io.qameta.allure.model.StepResult; +import io.qameta.allure.util.ResultsUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static java.util.Objects.requireNonNull; + +/** + * Allure interceptor logger for gRPC. + * + * @author dtuchs (Dmitry Tuchs). + */ +@SuppressWarnings({ + "PMD.AvoidFieldNameMatchingMethodName", + "checkstyle:ClassFanOutComplexity", + "checkstyle:AnonInnerLength", + "checkstyle:JavaNCSS" +}) +public class AllureGrpc implements ClientInterceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(AllureGrpc.class); + private static final JsonFormat.Printer JSON_PRINTER = JsonFormat.printer(); + + private String requestTemplatePath = "grpc-request.ftl"; + private String responseTemplatePath = "grpc-response.ftl"; + + private boolean markStepFailedOnNonZeroCode = true; + private boolean interceptResponseMetadata; + + public AllureGrpc setRequestTemplate(final String templatePath) { + this.requestTemplatePath = templatePath; + return this; + } + + public AllureGrpc setResponseTemplate(final String templatePath) { + this.responseTemplatePath = templatePath; + return this; + } + + public AllureGrpc markStepFailedOnNonZeroCode(final boolean value) { + this.markStepFailedOnNonZeroCode = value; + return this; + } + + public AllureGrpc interceptResponseMetadata(final boolean value) { + this.interceptResponseMetadata = value; + return this; + } + + @SuppressWarnings({"PMD.MethodArgumentCouldBeFinal", "PMD.NPathComplexity"}) + @Override + public ClientCall interceptCall(MethodDescriptor method, + CallOptions callOptions, + Channel next) { + final AttachmentProcessor processor = new DefaultAttachmentProcessor(); + + return new ForwardingClientCall.SimpleForwardingClientCall( + next.newCall(method, callOptions.withoutWaitForReady())) { + + private String stepUuid; + private List parsedResponses = new ArrayList<>(); + + @SuppressWarnings("PMD.MethodArgumentCouldBeFinal") + @Override + public void sendMessage(T message) { + stepUuid = UUID.randomUUID().toString(); + Allure.getLifecycle().startStep(stepUuid, (new StepResult()).setName( + "Send gRPC request to " + + next.authority() + + trimGrpcMethodName(method.getFullMethodName()) + )); + try { + final GrpcRequestAttachment rpcRequestAttach = GrpcRequestAttachment.Builder + .create("gRPC request", method.getFullMethodName()) + .setBody(JSON_PRINTER.print((MessageOrBuilder) message)) + .build(); + processor.addAttachment(rpcRequestAttach, new FreemarkerAttachmentRenderer(requestTemplatePath)); + super.sendMessage(message); + } catch (InvalidProtocolBufferException e) { + LOGGER.warn("Can`t parse gRPC request", e); + } catch (Throwable e) { + Allure.getLifecycle().updateStep(stepResult -> + stepResult.setStatus(ResultsUtils.getStatus(e).orElse(Status.BROKEN)) + .setStatusDetails(ResultsUtils.getStatusDetails(e).orElse(null)) + ); + Allure.getLifecycle().stopStep(stepUuid); + stepUuid = null; + } + } + + @SuppressWarnings("PMD.MethodArgumentCouldBeFinal") + @Override + public void start(Listener responseListener, Metadata headers) { + final ClientCall.Listener listener = new ForwardingClientCallListener() { + @Override + protected Listener delegate() { + return responseListener; + } + + @SuppressWarnings({"PMD.MethodArgumentCouldBeFinal", "PMD.AvoidLiteralsInIfCondition"}) + @Override + public void onClose(io.grpc.Status status, Metadata trailers) { + GrpcResponseAttachment.Builder responseAttachmentBuilder = null; + + if (parsedResponses.size() == 1) { + responseAttachmentBuilder = GrpcResponseAttachment.Builder + .create("gRPC response") + .setBody(parsedResponses.iterator().next()); + } else if (parsedResponses.size() > 1) { + responseAttachmentBuilder = GrpcResponseAttachment.Builder + .create("gRPC response (collection of elements from Server stream)") + .setBody("[" + String.join(",\n", parsedResponses) + "]"); + } + + requireNonNull(responseAttachmentBuilder).setStatus(status.toString()); + if (interceptResponseMetadata) { + for (String key : headers.keys()) { + requireNonNull(responseAttachmentBuilder).setMetadata( + key, + headers.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)) + ); + } + } + processor.addAttachment( + requireNonNull(responseAttachmentBuilder).build(), + new FreemarkerAttachmentRenderer(responseTemplatePath) + ); + + if (status.isOk() || !markStepFailedOnNonZeroCode) { + Allure.getLifecycle().updateStep(stepUuid, step -> step.setStatus(Status.PASSED)); + } else { + Allure.getLifecycle().updateStep(stepUuid, step -> step.setStatus(Status.FAILED)); + } + Allure.getLifecycle().stopStep(stepUuid); + stepUuid = null; + super.onClose(status, trailers); + } + + @SuppressWarnings("PMD.MethodArgumentCouldBeFinal") + @Override + public void onMessage(A message) { + try { + parsedResponses.add(JSON_PRINTER.print((MessageOrBuilder) message)); + super.onMessage(message); + } catch (InvalidProtocolBufferException e) { + LOGGER.warn("Can`t parse gRPC response", e); + } catch (Throwable e) { + Allure.getLifecycle().updateStep(step -> + step.setStatus(ResultsUtils.getStatus(e).orElse(Status.BROKEN)) + .setStatusDetails(ResultsUtils.getStatusDetails(e).orElse(null)) + ); + Allure.getLifecycle().stopStep(stepUuid); + stepUuid = null; + } + } + }; + super.start(listener, headers); + } + + private String trimGrpcMethodName(final String source) { + return source.substring(source.lastIndexOf('/')); + } + }; + } +} diff --git a/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcRequestAttachment.java b/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcRequestAttachment.java new file mode 100644 index 000000000..ef32e4dde --- /dev/null +++ b/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcRequestAttachment.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021 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.grpc; + +import io.qameta.allure.attachment.AttachmentData; + +import java.util.Objects; + +public class GrpcRequestAttachment implements AttachmentData { + + private final String name; + private final String url; + private final String body; + + public GrpcRequestAttachment(final String name, final String url, final String body) { + this.name = name; + this.url = url; + this.body = body; + } + + public String getUrl() { + return url; + } + + public String getBody() { + return body; + } + + @Override + public String getName() { + return name; + } + + /** + * Builder for GrpcRequestAttachment. + */ + @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName") + static final class Builder { + + private final String name; + + private final String url; + + private String body; + + 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"); + this.name = name; + this.url = url; + } + + public static Builder create(final String attachmentName, final String url) { + return new Builder(attachmentName, url); + } + + public Builder setBody(final String body) { + Objects.requireNonNull(body, "Body should not be null value"); + this.body = body; + return this; + } + + public GrpcRequestAttachment build() { + return new GrpcRequestAttachment(name, url, body); + } + } +} diff --git a/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcResponseAttachment.java b/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcResponseAttachment.java new file mode 100644 index 000000000..7824d9ee2 --- /dev/null +++ b/allure-grpc/src/main/java/io/qameta/allure/grpc/GrpcResponseAttachment.java @@ -0,0 +1,107 @@ +/* + * Copyright 2021 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.grpc; + +import io.qameta.allure.attachment.AttachmentData; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class GrpcResponseAttachment implements AttachmentData { + + private final String name; + private final String body; + private final String status; + private final Map metadata; + + public GrpcResponseAttachment(final String name, + final String body, + final String status, + final Map metadata) { + this.name = name; + this.body = body; + this.status = status; + this.metadata = metadata; + } + + public String getBody() { + return body; + } + + public Map getMetadata() { + return metadata; + } + + public String getStatus() { + return status; + } + + @Override + public String getName() { + return name; + } + + /** + * Builder for GrpcResponseAttachment. + */ + @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName") + static final class Builder { + + private final String name; + private final Map metadata = new HashMap<>(); + private String body; + private String status; + + private Builder(final String name) { + Objects.requireNonNull(name, "Name must not be null value"); + this.name = name; + } + + public static Builder create(final String attachmentName) { + return new Builder(attachmentName); + } + + public Builder setBody(final String body) { + Objects.requireNonNull(body, "Body should not be null value"); + this.body = body; + return this; + } + + public Builder setStatus(final String status) { + Objects.requireNonNull(status, "Status should not be null value"); + this.status = status; + return this; + } + + public GrpcResponseAttachment.Builder setMetadata(final String key, final String value) { + Objects.requireNonNull(key, "Matadata key must not be null value"); + Objects.requireNonNull(value, "Matadata value must not be null value"); + this.metadata.put(key, value); + return this; + } + + public GrpcResponseAttachment.Builder addMetadata(final Map metadata) { + Objects.requireNonNull(metadata, "Metadata Map must not be null value"); + this.metadata.putAll(metadata); + return this; + } + + public GrpcResponseAttachment build() { + return new GrpcResponseAttachment(name, body, status, metadata); + } + } +} diff --git a/allure-grpc/src/main/resources/tpl/grpc-request.ftl b/allure-grpc/src/main/resources/tpl/grpc-request.ftl new file mode 100644 index 000000000..7bab82e5e --- /dev/null +++ b/allure-grpc/src/main/resources/tpl/grpc-request.ftl @@ -0,0 +1,33 @@ + +<#-- @ftlvariable name="data" type="io.qameta.allure.grpc.GrpcRequestAttachment" --> + + + + + + + + + + + + + + + +
+
gRPC: <#if data.url??>${data.url}<#else>Unknown
+
+ +<#if data.body??> +

Request body

+
+
${data.body}
+
+ + + diff --git a/allure-grpc/src/main/resources/tpl/grpc-response.ftl b/allure-grpc/src/main/resources/tpl/grpc-response.ftl new file mode 100644 index 000000000..48929888e --- /dev/null +++ b/allure-grpc/src/main/resources/tpl/grpc-response.ftl @@ -0,0 +1,46 @@ + +<#-- @ftlvariable name="data" type="io.qameta.allure.grpc.GrpcResponseAttachment" --> + + + + + + + + + + + + + + + + +

Status

+ <#if data.status??> +
${data.status}
+ <#else>Unknown +
+ +<#if data.body??> +

Response body

+
+
${data.body}
+
+ + +<#if (data.metadata)?has_content> +

Metadata

+
+ <#list data.metadata as key, value> +
${key}: ${value}
+ +
+ + + + diff --git a/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java b/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java new file mode 100644 index 000000000..9e0bac54b --- /dev/null +++ b/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java @@ -0,0 +1,137 @@ +/* + * 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.grpc; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.qameta.allure.model.Attachment; +import io.qameta.allure.model.StepResult; +import io.qameta.allure.test.AllureResults; +import org.grpcmock.GrpcMock; +import org.grpcmock.junit5.GrpcMockExtension; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.util.Iterator; +import java.util.Optional; + +import static io.qameta.allure.test.RunUtils.runWithinTestContext; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.grpcmock.GrpcMock.serverStreamingMethod; +import static org.grpcmock.GrpcMock.unaryMethod; + +/** + * @author dtuchs (Dmitry Tuchs). + */ +@ExtendWith(GrpcMockExtension.class) +class AllureGrpcTest { + + private static final String RESPONSE_MESSAGE = "Hello world!"; + + private ManagedChannel channel; + private TestServiceGrpc.TestServiceBlockingStub blockingStub; + + @BeforeEach + void configureMock() { + channel = ManagedChannelBuilder.forAddress("localhost", GrpcMock.getGlobalPort()) + .usePlaintext() + .build(); + blockingStub = TestServiceGrpc.newBlockingStub(channel) + .withInterceptors(new AllureGrpc()); + + GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()) + .willReturn(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build())); + GrpcMock.stubFor(serverStreamingMethod(TestServiceGrpc.getCalculateServerStreamMethod()) + .willReturn(asList( + Response.newBuilder().setMessage(RESPONSE_MESSAGE).build(), + Response.newBuilder().setMessage(RESPONSE_MESSAGE).build() + ))); + } + + @AfterEach + void shutdownChannel() { + Optional.ofNullable(channel).ifPresent(ManagedChannel::shutdownNow); + } + + @Test + void shouldCreateRequestAttachment() { + final Request request = Request.newBuilder() + .setTopic("1") + .build(); + + final AllureResults results = execute(request); + + assertThat(results.getTestResults().get(0).getSteps()) + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC request"); + } + + @Test + void shouldCreateResponseAttachment() { + final Request request = Request.newBuilder() + .setTopic("1") + .build(); + + final AllureResults results = execute(request); + + assertThat(results.getTestResults().get(0).getSteps()) + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC response"); + } + + @Test + void shouldCreateResponseAttachmentForServerStreamingResponse() { + final Request request = Request.newBuilder() + .setTopic("1") + .build(); + + final AllureResults results = executeStreaming(request); + + assertThat(results.getTestResults().get(0).getSteps()) + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC response (collection of elements from Server stream)"); + } + + protected final AllureResults execute(final Request request) { + return runWithinTestContext(() -> { + try { + final Response response = blockingStub.calculate(request); + assertThat(response.getMessage()).isEqualTo(RESPONSE_MESSAGE); + } catch (Exception e) { + throw new RuntimeException("Could not execute request " + request, e); + } + }); + } + + protected final AllureResults executeStreaming(final Request request) { + return runWithinTestContext(() -> { + try { + Iterator responseIterator = blockingStub.calculateServerStream(request); + while (responseIterator.hasNext()) { + assertThat(responseIterator.next().getMessage()).isEqualTo(RESPONSE_MESSAGE); + } + } catch (Exception e) { + throw new RuntimeException("Could not execute request " + request, e); + } + }); + } +} diff --git a/allure-grpc/src/test/proto/api.proto b/allure-grpc/src/test/proto/api.proto new file mode 100644 index 000000000..552e76f6c --- /dev/null +++ b/allure-grpc/src/test/proto/api.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.qameta.allure.grpc"; + +service TestService { + rpc Calculate (Request) returns (Response); + rpc CalculateServerStream (Request) returns (stream Response); +} + +message Request { + string topic = 1; +} + +message Response { + string message = 1; +} diff --git a/allure-grpc/src/test/resources/allure.properties b/allure-grpc/src/test/resources/allure.properties new file mode 100644 index 000000000..9c0b0a2d7 --- /dev/null +++ b/allure-grpc/src/test/resources/allure.properties @@ -0,0 +1,2 @@ +allure.results.directory=build/allure-results +allure.label.epic=#project.description# diff --git a/allure-jbehave/build.gradle.kts b/allure-jbehave/build.gradle.kts index a5270026f..160a03e87 100644 --- a/allure-jbehave/build.gradle.kts +++ b/allure-jbehave/build.gradle.kts @@ -1,6 +1,6 @@ description = "Allure JBehave Integration" -val jbehaveVersion = "4.8.2" +val jbehaveVersion = "4.8.3" dependencies { api(project(":allure-java-commons")) diff --git a/allure-selenide/build.gradle.kts b/allure-selenide/build.gradle.kts index e3c33fe7b..f630385a0 100644 --- a/allure-selenide/build.gradle.kts +++ b/allure-selenide/build.gradle.kts @@ -1,6 +1,6 @@ description = "Allure Selenide Integration" -val selenideVersion = "6.1.1" +val selenideVersion = "6.4.0" dependencies { api(project(":allure-java-commons")) diff --git a/build.gradle.kts b/build.gradle.kts index 472a978e3..5d5ab5595 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -145,13 +145,15 @@ configure(libs) { dependency("org.apache.commons:commons-lang3:3.12.0") dependency("org.apache.httpcomponents:httpclient:4.5.13") dependency("org.aspectj:aspectjrt:1.9.7") - dependency("org.aspectj:aspectjweaver:1.9.7") + dependency("org.aspectj:aspectjweaver:1.9.9.1") dependency("org.assertj:assertj-core:3.21.0") dependency("org.codehaus.groovy:groovy-all:2.5.13") + dependency("org.grpcmock:grpcmock-junit5:0.5.1") dependency("org.freemarker:freemarker:2.3.31") dependency("org.jboss.resteasy:resteasy-client:4.7.3.Final") - dependency("org.mock-server:mockserver-netty:5.11.2") - dependency("org.mockito:mockito-core:4.1.0") + dependency("org.mock-server:mockserver-netty:5.13.2") + dependency("org.mockito:mockito-core:4.5.1") + dependency("javax.annotation:javax.annotation-api:1.3.1") dependencySet("org.slf4j:1.7.30") { entry("slf4j-api") entry("slf4j-nop") diff --git a/gradle.properties b/gradle.properties index 8a23e01c8..a5b143533 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,12 @@ -version=2.17.3 +version=2.18.0 org.gradle.daemon=true org.gradle.parallel=true org.gradle.warning.mode=none +org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m test.maxHeapSize=512m test.maxParallelForks=4 # https://kotlinlang.org/docs/gradle.html#dependency-on-the-standard-library -kotlin.stdlib.default.dependency=false \ No newline at end of file +kotlin.stdlib.default.dependency=false diff --git a/settings.gradle.kts b/settings.gradle.kts index b302ad05e..1484d2003 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,6 +12,7 @@ include("allure-cucumber5-jvm") include("allure-cucumber6-jvm") include("allure-cucumber7-jvm") include("allure-descriptions-javadoc") +include("allure-grpc") include("allure-hamcrest") include("allure-httpclient") include("allure-java-commons") @@ -44,8 +45,8 @@ pluginManagement { } plugins { id("com.diffplug.spotless") version "6.1.2" - id("com.github.johnrengelman.shadow") version "7.1.0" - id("com.gradle.enterprise") version "3.7.2" + id("com.github.johnrengelman.shadow") version "7.1.2" + id("com.gradle.enterprise") version "3.10" id("io.github.gradle-nexus.publish-plugin") version "1.1.0" id("io.qameta.allure-adapter") version "2.9.6" id("io.qameta.allure-aggregate-report") version "2.9.6" @@ -53,6 +54,7 @@ pluginManagement { id("io.qameta.allure-report") version "2.9.6" id("io.spring.dependency-management") version "1.0.11.RELEASE" id("ru.vyarus.quality") version "4.7.0" + id("com.google.protobuf") version "0.8.17" kotlin("jvm") version "1.5.0" } }