Skip to content

Commit

Permalink
Merge pull request #94 from craigatk/parse-save
Browse files Browse the repository at this point in the history
(feature) Verifying results payload can parse on server in sync request and adding configurable retry to plugin
  • Loading branch information
craigatk committed May 19, 2020
2 parents 3e92706 + abf1bac commit 82f329b
Show file tree
Hide file tree
Showing 25 changed files with 320 additions and 34 deletions.
2 changes: 2 additions & 0 deletions publishers/gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ dependencies {

implementation "com.fasterxml.jackson.core:jackson-databind:2.9.8"

implementation "io.github.resilience4j:resilience4j-retry:1.4.0"

testImplementation('org.spockframework:spock-core:1.3-groovy-2.5') {
exclude group: 'org.codehaus.groovy'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package projektor.plugin

import okhttp3.OkHttpClient
import org.gradle.BuildListener
import org.gradle.BuildResult
import org.gradle.api.file.FileTree
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ class ProjektorManualPublishTask extends AbstractTask {
@Optional
Boolean compressionEnabled = true

@Input
@Optional
Integer publishRetryMaxAttempts = 3

@Input
@Optional
Long publishRetryInterval = 100

@Input
@Optional
Long publishTimeout = 10_000

@TaskAction
void publish() {
File projectDir = project.projectDir
Expand All @@ -46,7 +58,14 @@ class ProjektorManualPublishTask extends AbstractTask {
)

if (projectTestTaskResultsCollector.hasTestGroups()) {
ClientConfig clientConfig = new ClientConfig(serverUrl, compressionEnabled, java.util.Optional.ofNullable(publishToken))
ClientConfig clientConfig = new ClientConfig(
serverUrl,
compressionEnabled,
java.util.Optional.ofNullable(publishToken),
publishRetryMaxAttempts,
publishRetryInterval,
publishTimeout
)
GroupedResults groupedResults = projectTestTaskResultsCollector.createGroupedResults()

ResultsClient resultsClient = new ResultsClient(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,14 @@ class ProjektorPublishPlugin implements Plugin<Project> {
project.gradle.taskGraph.addTaskExecutionListener(projektorTaskFinishedListener)

ProjektorBuildFinishedListener projektorBuildFinishedListener = new ProjektorBuildFinishedListener(
new ClientConfig(extension.serverUrl, extension.compressionEnabled, Optional.ofNullable(extension.publishToken)),
new ClientConfig(
extension.serverUrl,
extension.compressionEnabled,
Optional.ofNullable(extension.publishToken),
extension.publishRetryMaxAttempts,
extension.publishRetryInterval,
extension.publishTimeout
),
logger,
extension.autoPublishOnFailureOnly,
project.projectDir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ class ProjektorPublishPluginExtension {
List<String> additionalResultsDirs = []
List<FileTree> attachments = []
boolean compressionEnabled = true
int publishRetryMaxAttempts = 3
long publishRetryInterval = 100
long publishTimeout = 10_000
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,23 @@ class ClientConfig {
final String serverUrl
final boolean compressionEnabled
final Optional<String> maybePublishToken
final int retryMaxAttempts
final long retryInterval
final long timeout

ClientConfig(String serverUrl, boolean compressionEnabled, Optional<String> maybePublishToken) {
ClientConfig(
String serverUrl,
boolean compressionEnabled,
Optional<String> maybePublishToken,
int retryMaxAttempts,
long retryInterval,
long timeout
) {
this.serverUrl = serverUrl
this.compressionEnabled = compressionEnabled
this.maybePublishToken = maybePublishToken
this.retryMaxAttempts = retryMaxAttempts
this.retryInterval = retryInterval
this.timeout = timeout
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package projektor.plugin.client

import groovy.json.JsonSlurper
import io.github.resilience4j.retry.Retry
import io.github.resilience4j.retry.RetryConfig
import io.github.resilience4j.retry.RetryRegistry
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Request
Expand All @@ -11,18 +14,41 @@ import projektor.plugin.PublishResult
import projektor.plugin.results.grouped.GroupedResults
import projektor.plugin.results.grouped.GroupedResultsSerializer

import java.time.Duration
import java.util.concurrent.TimeUnit

import static projektor.plugin.client.ClientToken.conditionallyAddPublishTokenToRequest

class ResultsClient {

private final ClientConfig config
private final Logger logger
private final GroupedResultsSerializer groupedResultsSerializer = new GroupedResultsSerializer()
private final OkHttpClient client = new OkHttpClient()
private final OkHttpClient client

private final Retry publishRetry

ResultsClient(ClientConfig clientConfig, Logger logger) {
this.config = clientConfig
this.logger = logger

OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
okHttpClientBuilder.retryOnConnectionFailure = false
okHttpClientBuilder.callTimeout(clientConfig.timeout, TimeUnit.MILLISECONDS)
okHttpClientBuilder.readTimeout(clientConfig.timeout, TimeUnit.MILLISECONDS)
okHttpClientBuilder.connectTimeout(clientConfig.timeout, TimeUnit.MILLISECONDS)
this.client = okHttpClientBuilder.build()

RetryConfig retryConfig = RetryConfig.custom()
.maxAttempts(clientConfig.retryMaxAttempts)
.waitDuration(Duration.ofMillis(clientConfig.retryInterval))
.retryOnResult({ Response response -> !response.successful && response.code() != 401 })
.retryOnException({ Throwable e -> true })
.build()

RetryRegistry registry = RetryRegistry.of(retryConfig)

this.publishRetry = registry.retry("publish")
}

PublishResult sendResultsToServer(GroupedResults groupedResults) {
Expand Down Expand Up @@ -50,7 +76,7 @@ class ResultsClient {
Request request = requestBuilder.build()

try {
Response response = client.newCall(request).execute()
Response response = publishRetry.executeSupplier({ -> client.newCall(request).execute() })

if (response?.successful) {
def parsedResponseJson = new JsonSlurper().parseText(response.body().string())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ class ResultsWireMockStubber extends WireMockStubber {
))
}

void stubResultsPostWithDelay(int delay) {
wireMockServer.stubFor(post(urlEqualTo("/groupedResults")).willReturn(aResponse()
.withFixedDelay(delay)
.withStatus(200)))
}

List<LoggedRequest> findResultsRequests() {
wireMockServer.findRequestsMatching(
postRequestedFor(urlEqualTo("/groupedResults")).build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class AttachmentsClientSpec extends Specification {
String publicId = "ATT123"

AttachmentsClient attachmentsClient = new AttachmentsClient(
new ClientConfig(serverUrl, false, maybePublishToken),
new ClientConfig(serverUrl, false, maybePublishToken, 1, 0, 10_000),
logger
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import projektor.plugin.ResultsWireMockStubber
import projektor.plugin.PublishResult
import projektor.plugin.results.grouped.GroupedResults
import spock.lang.Specification
import spock.lang.Unroll

import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig

Expand All @@ -26,7 +27,7 @@ class ResultsClientSpec extends Specification {
String serverUrl = resultsStubber.serverUrl

ResultsClient resultsClient = new ResultsClient(
new ClientConfig(serverUrl, true, Optional.empty()),
new ClientConfig(serverUrl, true, Optional.empty(), 1, 0, 10_000),
logger
)

Expand Down Expand Up @@ -58,7 +59,7 @@ class ResultsClientSpec extends Specification {
String serverUrl = resultsStubber.serverUrl

ResultsClient resultsClient = new ResultsClient(
new ClientConfig(serverUrl, true, Optional.of("token12345")),
new ClientConfig(serverUrl, true, Optional.of("token12345"), 1, 0, 10_000),
logger
)

Expand Down Expand Up @@ -91,7 +92,7 @@ class ResultsClientSpec extends Specification {
given:
String serverUrl = "http://resolve.failure.fakedotcom:9999/womp"
ResultsClient resultsClient = new ResultsClient(
new ClientConfig(serverUrl, true, Optional.empty()),
new ClientConfig(serverUrl, true, Optional.empty(), 1, 0, 10_000),
logger
)
GroupedResults groupedResults = new GroupedResults()
Expand All @@ -103,12 +104,43 @@ class ResultsClientSpec extends Specification {
!publishResult.successful
}

@Unroll
void "when sending results returns response code #responseCode should retry #expectedRetries"() {
given:
String serverUrl = resultsStubber.serverUrl

ResultsClient resultsClient = new ResultsClient(
new ClientConfig(serverUrl, true, Optional.empty(), retryMaxAttempts, 0, 1000),
logger
)

GroupedResults groupedResults = new GroupedResults(
groupedTestSuites: []
)

resultsStubber.stubResultsPostFailure(responseCode)

when:
resultsClient.sendResultsToServer(groupedResults)

then:
List<LoggedRequest> resultsRequests = resultsStubber.findResultsRequests()
resultsRequests.size() == expectedRetries

where:
responseCode | retryMaxAttempts || expectedRetries
400 | 3 || 3
500 | 2 || 2
401 | 3 || 1
200 | 3 || 1
}

void "when compression enabled should include gzip header"() {
given:
String serverUrl = resultsStubber.serverUrl

ResultsClient resultsClient = new ResultsClient(
new ClientConfig(serverUrl, true, Optional.empty()),
new ClientConfig(serverUrl, true, Optional.empty(), 1, 0, 10_000),
logger
)

Expand All @@ -120,7 +152,7 @@ class ResultsClientSpec extends Specification {
resultsStubber.stubResultsPostSuccess(resultsId)

when:
PublishResult publishResult = resultsClient.sendResultsToServer(groupedResults)
resultsClient.sendResultsToServer(groupedResults)

then:
List<LoggedRequest> resultsRequests = resultsStubber.findResultsRequests()
Expand All @@ -133,7 +165,7 @@ class ResultsClientSpec extends Specification {
String serverUrl = resultsStubber.serverUrl

ResultsClient resultsClient = new ResultsClient(
new ClientConfig(serverUrl, false, Optional.empty()),
new ClientConfig(serverUrl, false, Optional.empty(), 1, 0, 10_000),
logger
)

Expand All @@ -145,7 +177,7 @@ class ResultsClientSpec extends Specification {
resultsStubber.stubResultsPostSuccess(resultsId)

when:
PublishResult publishResult = resultsClient.sendResultsToServer(groupedResults)
resultsClient.sendResultsToServer(groupedResults)

then:
List<LoggedRequest> resultsRequests = resultsStubber.findResultsRequests()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ abstract class ProjectSpec extends Specification {
TemporaryFolder projectRootDir = new TemporaryFolder()

@Rule
WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort().dynamicHttpsPort())
WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort())

ResultsWireMockStubber resultsStubber = new ResultsWireMockStubber(wireMockRule)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package projektor.plugin.testkit

import org.gradle.testkit.runner.BuildResult
import projektor.plugin.SpecWriter

import static org.gradle.testkit.runner.TaskOutcome.SUCCESS

class PublishResultsRetrySpec extends SingleProjectSpec {
def "can configure retry max attempts and interval"() {
given:
buildFile << """
projektor {
serverUrl = '${serverUrl}'
autoPublishOnFailureOnly = false
publishRetryMaxAttempts = 2
publishRetryInterval = 50
}
""".stripIndent()

SpecWriter.createTestDirectoryWithPassingTest(projectRootDir, "SampleSpec")

resultsStubber.stubResultsPostFailure(400)

when:
BuildResult result = runSuccessfulBuild('test')

then:
result.task(":test").outcome == SUCCESS

and:
resultsStubber.findResultsRequests().size() == 2
}

def "can configure timeout"() {
given:
buildFile << """
projektor {
serverUrl = '${serverUrl}'
autoPublishOnFailureOnly = false
publishRetryMaxAttempts = 2
publishRetryInterval = 0
publishTimeout = 500
}
""".stripIndent()

SpecWriter.createTestDirectoryWithPassingTest(projectRootDir, "SampleSpec")

resultsStubber.stubResultsPostWithDelay(600)

when:
BuildResult result = runSuccessfulBuild('test')

then:
result.task(":test").outcome == SUCCESS

and:
resultsStubber.findResultsRequests().size() == 2
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class PublishingResultsFailsSpec extends SingleProjectSpec {
buildFile << """
projektor {
serverUrl = '${serverUrl}'
autoPublishOnFailureOnly = false
}
""".stripIndent()

Expand All @@ -23,13 +24,17 @@ class PublishingResultsFailsSpec extends SingleProjectSpec {

then:
result.task(":test").outcome == SUCCESS

and:
resultsStubber.findResultsRequests().size() == 3
}

def "when publishing fails with non-200 response should not fail build"() {
given:
buildFile << """
projektor {
serverUrl = '${serverUrl}'
autoPublishOnFailureOnly = false
}
""".stripIndent()

Expand All @@ -42,5 +47,8 @@ class PublishingResultsFailsSpec extends SingleProjectSpec {

then:
result.task(":test").outcome == SUCCESS

and:
resultsStubber.findResultsRequests().size() == 3
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package projektor.server.api.results

data class SaveResultsErrorResponse(val id: String, val errorMessage: String)
Loading

0 comments on commit 82f329b

Please sign in to comment.