Skip to content

Commit

Permalink
Adding API for repo laky tests (#1220)
Browse files Browse the repository at this point in the history
  • Loading branch information
craigatk authored Apr 4, 2024
1 parent e38c65e commit 95e5540
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 3 deletions.
2 changes: 1 addition & 1 deletion server/server-app/src/main/kotlin/projektor/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ fun Application.main(meterRegistry: MeterRegistry? = null) {
val codeQualityReportRepository: CodeQualityReportRepository by inject()

routing {
api(organizationCoverageService, repositoryCoverageService)
api(organizationCoverageService, repositoryCoverageService, repositoryTestRunService)
attachments(attachmentService, authService)
badgeCoverage(coverageBadgeService)
badgeTests(testRunBadgeService)
Expand Down
35 changes: 35 additions & 0 deletions server/server-app/src/main/kotlin/projektor/route/ApiRoutes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import io.ktor.server.routing.*
import io.ktor.server.util.*
import projektor.organization.coverage.OrganizationCoverageService
import projektor.repository.coverage.RepositoryCoverageService
import projektor.repository.testrun.RepositoryTestRunService
import projektor.server.api.repository.BranchSearch
import projektor.server.api.repository.BranchType
import projektor.server.api.repository.RepositoryFlakyTests

fun Route.api(
organizationCoverageService: OrganizationCoverageService,
repositoryCoverageService: RepositoryCoverageService,
repositoryTestRunService: RepositoryTestRunService,
) {
get("/api/v1/org/{orgName}/coverage/current") {
val orgName = call.parameters.getOrFail("orgName")
Expand Down Expand Up @@ -49,4 +52,36 @@ fun Route.api(
repositoryCurrentCoverage?.let { call.respond(HttpStatusCode.OK, it) }
?: call.respond(HttpStatusCode.NoContent)
}

get("/api/v1/repo/{orgPart}/{repoPart}/tests/flaky") {
val orgPart = call.parameters.getOrFail("orgPart")
val repoPart = call.parameters.getOrFail("repoPart")
val projectName = call.request.queryParameters["project"]
val maxRuns = call.request.queryParameters["max_runs"]?.toInt() ?: 50
val flakyThreshold = call.request.queryParameters["threshold"]?.toInt() ?: 5
val branchType: BranchType = BranchType.valueOf(call.request.queryParameters["branch_type"] ?: "MAINLINE")

val fullRepoName = "$orgPart/$repoPart"

val flakyTests = repositoryTestRunService.fetchFlakyTests(
repoName = fullRepoName,
projectName = projectName,
maxRuns = maxRuns,
flakyFailureThreshold = flakyThreshold,
branchType = branchType
)

if (flakyTests.isNotEmpty()) {
call.respond(
HttpStatusCode.OK,
RepositoryFlakyTests(
tests = flakyTests,
maxRuns = maxRuns,
failureCountThreshold = flakyThreshold
)
)
} else {
call.respond(HttpStatusCode.NoContent)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import strikt.assertions.hasSize
import strikt.assertions.isEqualTo
import kotlin.test.assertNotNull

class ApiOrganizationApplicationTestCase : ApplicationTestCase() {
class ApiOrganizationApplicationTest : ApplicationTestCase() {
@Test
fun `when three repos in org should find their coverage data`() {
val orgName = RandomStringUtils.randomAlphabetic(12)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import strikt.assertions.isEqualTo
import java.time.ZoneOffset
import kotlin.test.assertNotNull

class ApiRepositoryApplicationTestCase : ApplicationTestCase() {
class ApiRepositoryApplicationTest : ApplicationTestCase() {
@Test
fun `should fetch current coverage for repository without project name`() {
val orgName = RandomStringUtils.randomAlphabetic(12)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package projektor.api

import io.ktor.http.*
import io.ktor.server.testing.*
import org.apache.commons.lang3.RandomStringUtils
import org.junit.jupiter.api.Test
import projektor.ApplicationTestCase
import projektor.TestSuiteData
import projektor.incomingresults.randomPublicId
import projektor.server.api.repository.RepositoryFlakyTests
import strikt.api.expectThat
import strikt.assertions.any
import strikt.assertions.contains
import strikt.assertions.hasSize
import strikt.assertions.isEqualTo
import kotlin.test.assertNotNull

class ApiRepositoryFlakyTestsApplicationTest : ApplicationTestCase() {
@Test
fun `when flaky tests without a project name should return them`() {
val orgName = RandomStringUtils.randomAlphabetic(12)
val repoName = "$orgName/repo"
val projectName = null

val publicIds = (1..5).map { randomPublicId() }

val testSuiteDataList = listOf(
TestSuiteData(
"projektor.failingTestSuite1",
listOf("passing1"),
listOf("failing1"),
listOf()
),
TestSuiteData(
"projektor.failingTestSuite2",
listOf("passing2"),
listOf("failing2"),
listOf()
)
)

withTestApplication(::createTestApplication) {
handleRequest(HttpMethod.Get, "/api/v1/repo/$repoName/tests/flaky") {
publicIds.forEach { publicId ->
testRunDBGenerator.createTestRunInRepo(publicId, testSuiteDataList, repoName, true, projectName, "main")
}
}.apply {
expectThat(response.status()).isEqualTo(HttpStatusCode.OK)

val flakyTests = objectMapper.readValue(response.content, RepositoryFlakyTests::class.java)
assertNotNull(flakyTests)

expectThat(flakyTests.tests).hasSize(2).and {
any { get { testCase }.get { name }.isEqualTo("failing1") }
any { get { testCase }.get { name }.isEqualTo("failing2") }
}
}
}
}

@Test
fun `when flaky tests within specified max runs and threshold should find them`() {
val orgName = RandomStringUtils.randomAlphabetic(12)
val repoName = "$orgName/repo"
val projectName = null

val failingPublicIds = (1..3).map { randomPublicId() }
val failingTestSuiteDataList = listOf(
TestSuiteData(
"projektor.failingTestSuite1",
listOf("passing1"),
listOf("failing1"),
listOf()
),
TestSuiteData(
"projektor.failingTestSuite2",
listOf("passing2"),
listOf("failing2"),
listOf()
)
)

val passingPublicIds = (1..7).map { randomPublicId() }
val passingTestSuiteDataList = listOf(
TestSuiteData(
"projektor.passingTestSuite",
listOf("passing1", "passing2"),
listOf(),
listOf()
)
)

withTestApplication(::createTestApplication) {
handleRequest(HttpMethod.Get, "/api/v1/repo/$repoName/tests/flaky?max_runs=10&threshold=3") {
failingPublicIds.forEach { publicId ->
testRunDBGenerator.createTestRunInRepo(publicId, failingTestSuiteDataList, repoName, true, projectName)
}
passingPublicIds.forEach { publicId ->
testRunDBGenerator.createTestRunInRepo(publicId, passingTestSuiteDataList, repoName, true, projectName)
}
}.apply {
expectThat(response.status()).isEqualTo(HttpStatusCode.OK)

val flakyTests = objectMapper.readValue(response.content, RepositoryFlakyTests::class.java)
assertNotNull(flakyTests)

expectThat(flakyTests.tests).hasSize(2)
}
}
}

@Test
fun `when flaky tests with a project name should return them`() {
val orgName = RandomStringUtils.randomAlphabetic(12)
val repoName = "$orgName/repo"
val projectName = "my-project"

val publicIds = (1..5).map { randomPublicId() }

val testSuiteDataList = listOf(
TestSuiteData(
"projektor.failingTestSuite1",
listOf("passing1"),
listOf("failing1"),
listOf()
),
TestSuiteData(
"projektor.failingTestSuite2",
listOf("passing2"),
listOf("failing2"),
listOf()
)
)

withTestApplication(::createTestApplication) {
handleRequest(HttpMethod.Get, "/api/v1/repo/$repoName/tests/flaky?project=$projectName") {
publicIds.forEach { publicId ->
testRunDBGenerator.createTestRunInRepo(publicId, testSuiteDataList, repoName, true, projectName)
}
}.apply {
expectThat(response.status()).isEqualTo(HttpStatusCode.OK)

val flakyTests = objectMapper.readValue(response.content, RepositoryFlakyTests::class.java)
assertNotNull(flakyTests)

expectThat(flakyTests.tests).hasSize(2).and {
any { get { testCase }.get { name }.isEqualTo("failing1") }
any { get { testCase }.get { name }.isEqualTo("failing2") }
}
}
}
}

@Test
fun `should find flaky tests in mainline only`() {
val orgName = RandomStringUtils.randomAlphabetic(12)
val repoName = "$orgName/repo"
val projectName = null

val failingMainlinePublicIds = (1..3).map { randomPublicId() }
val failingMainlineTestSuiteDataList = listOf(
TestSuiteData(
"projektor.failingMainlineTestSuite1",
listOf("passing1"),
listOf("failingMainline1"),
listOf()
),
TestSuiteData(
"projektor.failingMainlineTestSuite2",
listOf("passing2"),
listOf("failingMainline2"),
listOf()
)
)

val failingBranchPublicIds = (1..3).map { randomPublicId() }
val failingBranchTestSuiteDataList = listOf(
TestSuiteData(
"projektor.failingBranchTestSuite1",
listOf("passing1"),
listOf("failingBranch1"),
listOf()
),
TestSuiteData(
"projektor.failingBranchTestSuite2",
listOf("passing2"),
listOf("failingBranch2"),
listOf()
)
)

val passingPublicIds = (1..7).map { randomPublicId() }
val passingTestSuiteDataList = listOf(
TestSuiteData(
"projektor.passingTestSuite",
listOf("passing1", "passing2"),
listOf(),
listOf()
)
)

withTestApplication(::createTestApplication) {
handleRequest(HttpMethod.Get, "/api/v1/repo/$repoName/tests/flaky?max_runs=20&threshold=3&branch_type=MAINLINE") {
failingMainlinePublicIds.forEach { publicId ->
testRunDBGenerator.createTestRunInRepo(publicId, failingMainlineTestSuiteDataList, repoName, true, projectName, "main")
}
failingBranchPublicIds.forEach { publicId ->
testRunDBGenerator.createTestRunInRepo(publicId, failingBranchTestSuiteDataList, repoName, true, projectName, "my-branch")
}
passingPublicIds.forEach { publicId ->
testRunDBGenerator.createTestRunInRepo(publicId, passingTestSuiteDataList, repoName, true, projectName)
}
}.apply {
expectThat(response.status()).isEqualTo(HttpStatusCode.OK)

val flakyTests = objectMapper.readValue(response.content, RepositoryFlakyTests::class.java)
assertNotNull(flakyTests)

expectThat(flakyTests.tests).hasSize(2)

val flakyTestCaseNames = flakyTests.tests.map { it.testCase }.map { it.fullName }

expectThat(flakyTestCaseNames)
.contains(
"projektor.failingMainline1ClassName.failingMainline1",
"projektor.failingMainline2ClassName.failingMainline2"
)
.not().contains(
"projektor.failingBranch1ClassName.failingBranch1",
"projektor.failingBranch2ClassName.failingBranch2"
)
}
}
}
}

0 comments on commit 95e5540

Please sign in to comment.