diff --git a/common/src/main/kotlin/flank/common/OutputReportCostNode.kt b/common/src/main/kotlin/flank/common/OutputReportCostNode.kt new file mode 100644 index 0000000000..d49d983a7b --- /dev/null +++ b/common/src/main/kotlin/flank/common/OutputReportCostNode.kt @@ -0,0 +1,9 @@ +package flank.common + +import java.math.BigDecimal + +data class OutputReportCostNode( + val physical: BigDecimal, + val virtual: BigDecimal, + val total: BigDecimal +) diff --git a/common/src/main/kotlin/flank/common/OutputReportKeys.kt b/common/src/main/kotlin/flank/common/OutputReportKeys.kt new file mode 100644 index 0000000000..43fb31c28d --- /dev/null +++ b/common/src/main/kotlin/flank/common/OutputReportKeys.kt @@ -0,0 +1,7 @@ +package flank.common + +const val OUTPUT_ARGS = "args" +const val OUTPUT_WEBLINKS = "weblinks" +const val OUTPUT_TEST_RESULTS = "test_results" +const val OUTPUT_COST = "cost" +const val OUTPUT_ERROR = "error" diff --git a/integration_tests/src/test/kotlin/integration/AllTestFilteredIT.kt b/integration_tests/src/test/kotlin/integration/AllTestFilteredIT.kt index c977bbf9ab..3ec201df7f 100644 --- a/integration_tests/src/test/kotlin/integration/AllTestFilteredIT.kt +++ b/integration_tests/src/test/kotlin/integration/AllTestFilteredIT.kt @@ -6,6 +6,17 @@ import flank.common.isWindows import org.junit.Assume.assumeFalse import org.junit.Test import run +import utils.CONFIGS_PATH +import utils.FLANK_JAR_PATH +import utils.androidRunCommands +import utils.asOutputReport +import utils.assertExitCode +import utils.assertNoOutcomeSummary +import utils.findTestDirectoryFromOutput +import utils.iosRunCommands +import utils.json +import utils.removeUnicode +import utils.toOutputReportFile class AllTestFilteredIT { private val name = this::class.java.simpleName @@ -25,8 +36,15 @@ class AllTestFilteredIT { assertExitCode(result, 1) val resOutput = result.output.removeUnicode() - assertThat(resOutput).containsMatch(findInCompare(name)) + + val outputReport = resOutput.findTestDirectoryFromOutput().toOutputReportFile().json().asOutputReport() + assertNoOutcomeSummary(resOutput) + + assertThat(outputReport.error).contains("There are no tests to run.") + assertThat(outputReport.cost).isNull() + assertThat(outputReport.testResults).isEmpty() + assertThat(outputReport.weblinks).isEmpty() } @Test @@ -45,7 +63,16 @@ class AllTestFilteredIT { assertExitCode(result, 1) val resOutput = result.output.removeUnicode() - assertThat(resOutput).containsMatch(findInCompare(name)) + assertNoOutcomeSummary(resOutput) + + val outputReport = resOutput.findTestDirectoryFromOutput().toOutputReportFile().json().asOutputReport() + + assertNoOutcomeSummary(resOutput) + + assertThat(outputReport.error).contains("Empty shards. Cannot match any method to [nonExisting/Class]") + assertThat(outputReport.cost).isNull() + assertThat(outputReport.testResults).isEmpty() + assertThat(outputReport.weblinks).isEmpty() } } diff --git a/integration_tests/src/test/kotlin/integration/DumpShardsIT.kt b/integration_tests/src/test/kotlin/integration/DumpShardsIT.kt index ede954c018..80f4ae7cda 100644 --- a/integration_tests/src/test/kotlin/integration/DumpShardsIT.kt +++ b/integration_tests/src/test/kotlin/integration/DumpShardsIT.kt @@ -1,14 +1,22 @@ package integration import FlankCommand -import com.google.common.truth.Truth +import com.google.common.truth.Truth.assertThat import flank.common.isWindows import org.junit.Assume import org.junit.Test import run +import utils.CONFIGS_PATH +import utils.FLANK_JAR_PATH +import utils.androidRunCommands +import utils.assertExitCode +import utils.assertNoOutcomeSummary import utils.containsAll +import utils.findInCompare +import utils.iosRunCommands import utils.loadAndroidDumpShards import utils.loadIosDumpShards +import utils.removeUnicode import java.io.File class DumpShardsIT { @@ -29,24 +37,24 @@ class DumpShardsIT { assertExitCode(result, 0) val resOutput = result.output.removeUnicode() - Truth.assertThat(resOutput).containsMatch(findInCompare(name)) + assertThat(resOutput).containsMatch(findInCompare(name)) assertNoOutcomeSummary(resOutput) val matrix = File("android_shards.json").loadAndroidDumpShards() - Truth.assertThat(matrix.shards.count()).isEqualTo(2) + assertThat(matrix.shards.count()).isEqualTo(2) - Truth.assertThat(matrix.shards.values.flatten()).containsAll( + assertThat(matrix.shards.values.flatten()).containsAll( "class com.example.test_app.parametrized.EspressoParametrizedClassParameterizedNamed", "class com.example.test_app.parametrized.EspressoParametrizedClassTestParameterized", "class com.example.test_app.ParameterizedTest", "class com.example.test_app.parametrized.EspressoParametrizedMethodTestJUnitParamsRunner", ) - Truth.assertThat(matrix.junitIgnored.count()).isEqualTo(4) - Truth.assertThat(matrix.junitIgnored).containsNoDuplicates() + assertThat(matrix.junitIgnored.count()).isEqualTo(4) + assertThat(matrix.junitIgnored).containsNoDuplicates() - Truth.assertThat(matrix.junitIgnored) + assertThat(matrix.junitIgnored) .containsExactly( "class com.example.test_app.InstrumentedTest#ignoredTestWitSuppress", "class com.example.test_app.InstrumentedTest#ignoredTestWithIgnore", @@ -71,16 +79,16 @@ class DumpShardsIT { assertExitCode(result, 0) val resOutput = result.output.removeUnicode() - Truth.assertThat(resOutput).containsMatch(findInCompare(name)) + assertThat(resOutput).containsMatch(findInCompare(name)) assertNoOutcomeSummary(resOutput) val shards = File("ios_shards.json").loadIosDumpShards() - Truth.assertThat(shards.count()).isEqualTo(2) + assertThat(shards.count()).isEqualTo(2) shards.first().let { firstShard -> - Truth.assertThat(firstShard.count()).isEqualTo(8) - Truth.assertThat(firstShard) + assertThat(firstShard.count()).isEqualTo(8) + assertThat(firstShard) .contains("EarlGreyExampleSwiftTests/testWithCustomFailureHandler") } } diff --git a/integration_tests/src/test/kotlin/integration/GameloopIT.kt b/integration_tests/src/test/kotlin/integration/GameloopIT.kt index 1b49579d7d..f197a48895 100644 --- a/integration_tests/src/test/kotlin/integration/GameloopIT.kt +++ b/integration_tests/src/test/kotlin/integration/GameloopIT.kt @@ -1,13 +1,24 @@ package integration import FlankCommand -import com.google.common.truth.Truth +import com.google.common.truth.Truth.assertThat import flank.common.isLinux import flank.common.isMacOS import flank.common.isWindows import org.junit.Assume.assumeFalse import org.junit.Test import run +import utils.CONFIGS_PATH +import utils.FLANK_JAR_PATH +import utils.androidRunCommands +import utils.asOutputReport +import utils.assertCostMatches +import utils.assertExitCode +import utils.findTestDirectoryFromOutput +import utils.iosRunCommands +import utils.json +import utils.removeUnicode +import utils.toOutputReportFile class GameloopIT { @@ -28,10 +39,19 @@ class GameloopIT { assertExitCode(result, 0) val resOutput = result.output.removeUnicode() - Truth.assertThat(resOutput).containsMatch(findInCompare(name)) - assertContainsOutcomeSummary(resOutput) { - success = 1 - } + + val outputReport = resOutput.findTestDirectoryFromOutput().toOutputReportFile().json().asOutputReport() + + assertThat(outputReport.error).isEmpty() + assertThat(outputReport.cost).isNotNull() + + outputReport.assertCostMatches() + + assertThat(outputReport.testResults.count()).isEqualTo(1) + assertThat(outputReport.weblinks.count()).isEqualTo(1) + + val testAxis = outputReport.testResults.values.first().testAxises.first() + assertThat(testAxis.outcome).isEqualTo("success") } @Test @@ -52,6 +72,18 @@ class GameloopIT { assertExitCode(result, 0) val resOutput = result.output.removeUnicode() - Truth.assertThat(resOutput).containsMatch(findInCompare(name)) + + val outputReport = resOutput.findTestDirectoryFromOutput().toOutputReportFile().json().asOutputReport() + + assertThat(outputReport.error).isEmpty() + assertThat(outputReport.cost).isNotNull() + + outputReport.assertCostMatches() + + assertThat(outputReport.testResults.count()).isEqualTo(1) + assertThat(outputReport.weblinks.count()).isEqualTo(1) + + val testAxis = outputReport.testResults.values.first().testAxises.first() + assertThat(testAxis.outcome).isEqualTo("success") } } diff --git a/integration_tests/src/test/kotlin/integration/IgnoreFailedIT.kt b/integration_tests/src/test/kotlin/integration/IgnoreFailedIT.kt index 8d6ab1b1f5..e75a77b269 100644 --- a/integration_tests/src/test/kotlin/integration/IgnoreFailedIT.kt +++ b/integration_tests/src/test/kotlin/integration/IgnoreFailedIT.kt @@ -4,9 +4,22 @@ import FlankCommand import com.google.common.truth.Truth.assertThat import org.junit.Test import run +import utils.CONFIGS_PATH +import utils.FLANK_JAR_PATH +import utils.androidRunCommands +import utils.asOutputReport +import utils.assertCostMatches +import utils.assertExitCode +import utils.assertTestCountMatches +import utils.findTestDirectoryFromOutput +import utils.firstTestSuiteOverview +import utils.json +import utils.removeUnicode +import utils.toOutputReportFile class IgnoreFailedIT { private val name = this::class.java.simpleName + @Test fun `return with exit code 0 for failed tests`() { val result = FlankCommand( @@ -21,9 +34,21 @@ class IgnoreFailedIT { assertExitCode(result, 0) val resOutput = result.output.removeUnicode() - assertThat(resOutput).containsMatch(findInCompare(name)) - assertContainsOutcomeSummary(resOutput) { - failure = 1 - } + val outputReport = resOutput.findTestDirectoryFromOutput().toOutputReportFile().json().asOutputReport() + + assertThat(outputReport.error).isEmpty() + assertThat(outputReport.cost).isNotNull() + + outputReport.assertCostMatches() + + assertThat(outputReport.testResults.count()).isEqualTo(1) + assertThat(outputReport.weblinks.count()).isEqualTo(1) + + val testSuiteOverview = outputReport.firstTestSuiteOverview + + testSuiteOverview.assertTestCountMatches( + total = 1, + failures = 1 + ) } } diff --git a/integration_tests/src/test/kotlin/integration/MultipleApksIT.kt b/integration_tests/src/test/kotlin/integration/MultipleApksIT.kt index 65102e93a1..40e893d5b7 100644 --- a/integration_tests/src/test/kotlin/integration/MultipleApksIT.kt +++ b/integration_tests/src/test/kotlin/integration/MultipleApksIT.kt @@ -4,12 +4,24 @@ import FlankCommand import com.google.common.truth.Truth.assertThat import org.junit.Test import run +import utils.CONFIGS_PATH +import utils.FLANK_JAR_PATH +import utils.androidRunCommands +import utils.asOutputReport +import utils.assertContainsUploads +import utils.assertCostMatches +import utils.assertExitCode import utils.assertTestFail import utils.assertTestPass import utils.assertTestResultContainsWebLinks import utils.findTestDirectoryFromOutput +import utils.json import utils.loadAsTestSuite +import utils.multipleFailedTests +import utils.multipleSuccessfulTests +import utils.removeUnicode import utils.toJUnitXmlFile +import utils.toOutputReportFile class MultipleApksIT { private val name = this::class.java.simpleName @@ -25,7 +37,7 @@ class MultipleApksIT { assertExitCode(result, 10) val resOutput = result.output.removeUnicode() - assertThat(resOutput).containsMatch(findInCompare(name)) + assertContainsUploads( resOutput, "app-multiple-success-debug-androidTest.apk", @@ -33,15 +45,28 @@ class MultipleApksIT { "MainActivity_robo_script.json" ) - assertContainsOutcomeSummary(resOutput) { - success = 3 - failure = 1 - } - resOutput.findTestDirectoryFromOutput().toJUnitXmlFile().loadAsTestSuite().run { assertTestResultContainsWebLinks() assertTestPass(multipleSuccessfulTests) assertTestFail(multipleFailedTests) } + + val outputReport = resOutput.findTestDirectoryFromOutput().toOutputReportFile().json().asOutputReport() + + assertThat(outputReport.error).isEmpty() + assertThat(outputReport.cost).isNotNull() + + outputReport.assertCostMatches() + + assertThat(outputReport.testResults.count()).isEqualTo(4) + assertThat(outputReport.weblinks.count()).isEqualTo(4) + + val testsResults = outputReport.testResults + .map { it.value } + .map { it.testAxises } + .flatten() + + assertThat(testsResults.sumBy { it.testSuiteOverview.failures }).isEqualTo(5) + assertThat(testsResults.sumBy { it.testSuiteOverview.total }).isEqualTo(41) } } diff --git a/integration_tests/src/test/kotlin/integration/MultipleDevicesIT.kt b/integration_tests/src/test/kotlin/integration/MultipleDevicesIT.kt index 7857d863cc..2bc23d1efe 100644 --- a/integration_tests/src/test/kotlin/integration/MultipleDevicesIT.kt +++ b/integration_tests/src/test/kotlin/integration/MultipleDevicesIT.kt @@ -4,12 +4,16 @@ import FlankCommand import com.google.common.truth.Truth.assertThat import org.junit.Test import run -import utils.assertTestFail -import utils.assertTestPass -import utils.assertTestResultContainsWebLinks +import utils.CONFIGS_PATH +import utils.FLANK_JAR_PATH +import utils.androidRunCommands +import utils.asOutputReport +import utils.assertCostMatches +import utils.assertExitCode import utils.findTestDirectoryFromOutput -import utils.loadAsTestSuite -import utils.toJUnitXmlFile +import utils.json +import utils.removeUnicode +import utils.toOutputReportFile class MultipleDevicesIT { private val name = this::class.java.simpleName @@ -29,20 +33,19 @@ class MultipleDevicesIT { assertExitCode(result, 10) val resOutput = result.output.removeUnicode() - assertThat(resOutput).containsMatch(findInCompare(name)) - assertContainsUploads( - resOutput, - "app-multiple-success-debug-androidTest.apk", - "app-multiple-error-debug-androidTest.apk", - ) - assertContainsOutcomeSummary(resOutput) { - success = 6 - failure = 3 - } - resOutput.findTestDirectoryFromOutput().toJUnitXmlFile().loadAsTestSuite().run { - assertTestResultContainsWebLinks() - assertTestPass(multipleSuccessfulTests) - assertTestFail(multipleFailedTests) - } + + val outputReport = resOutput.findTestDirectoryFromOutput().toOutputReportFile().json().asOutputReport() + assertThat(outputReport.weblinks.size).isEqualTo(3) + assertThat(outputReport.error).isEmpty() + + outputReport.assertCostMatches() + + val testsResults = outputReport.testResults + .map { it.value } + .map { it.testAxises } + .flatten() + + assertThat(testsResults.sumBy { it.testSuiteOverview.failures }).isEqualTo(15) + assertThat(testsResults.sumBy { it.testSuiteOverview.total }).isEqualTo(123) } } diff --git a/integration_tests/src/test/kotlin/integration/RunTimeoutIT.kt b/integration_tests/src/test/kotlin/integration/RunTimeoutIT.kt index 010d275fd3..5b816e052c 100644 --- a/integration_tests/src/test/kotlin/integration/RunTimeoutIT.kt +++ b/integration_tests/src/test/kotlin/integration/RunTimeoutIT.kt @@ -4,6 +4,15 @@ import FlankCommand import com.google.common.truth.Truth.assertThat import org.junit.Test import run +import utils.CONFIGS_PATH +import utils.FLANK_JAR_PATH +import utils.androidRunCommands +import utils.asOutputReport +import utils.assertExitCode +import utils.findTestDirectoryFromOutput +import utils.json +import utils.removeUnicode +import utils.toOutputReportFile class RunTimeoutIT { private val name = this::class.java.simpleName @@ -21,7 +30,9 @@ class RunTimeoutIT { assertExitCode(result, 1) val resOutput = result.output.removeUnicode() - assertThat(resOutput).containsMatch(findInCompare(name)) - assertNoOutcomeSummary(resOutput) + val outputReport = resOutput.findTestDirectoryFromOutput().toOutputReportFile().json().asOutputReport() + assertThat(outputReport.cost).isNull() + assertThat(outputReport.testResults).isEmpty() + assertThat(outputReport.weblinks).isEmpty() } } diff --git a/integration_tests/src/test/kotlin/integration/SanityRoboIT.kt b/integration_tests/src/test/kotlin/integration/SanityRoboIT.kt index 700582f9ba..47a436a1c1 100644 --- a/integration_tests/src/test/kotlin/integration/SanityRoboIT.kt +++ b/integration_tests/src/test/kotlin/integration/SanityRoboIT.kt @@ -4,11 +4,16 @@ import FlankCommand import com.google.common.truth.Truth.assertThat import org.junit.Test import run -import utils.assertCountOfFailedTests -import utils.assertTestResultContainsWebLinks +import utils.CONFIGS_PATH +import utils.FLANK_JAR_PATH +import utils.androidRunCommands +import utils.asOutputReport +import utils.assertCostMatches +import utils.assertExitCode import utils.findTestDirectoryFromOutput -import utils.loadAsTestSuite -import utils.toJUnitXmlFile +import utils.json +import utils.removeUnicode +import utils.toOutputReportFile class SanityRoboIT { private val name = this::class.java.simpleName @@ -27,13 +32,13 @@ class SanityRoboIT { assertExitCode(result, 0) val resOutput = result.output.removeUnicode() - assertThat(resOutput).containsMatch(findInCompare(name)) - assertContainsOutcomeSummary(resOutput) { - success = 1 - } - resOutput.findTestDirectoryFromOutput().toJUnitXmlFile().loadAsTestSuite().run { - assertTestResultContainsWebLinks() - assertCountOfFailedTests(0) - } + val outputReport = resOutput.findTestDirectoryFromOutput().toOutputReportFile().json().asOutputReport() + + assertThat(outputReport.cost).isNotNull() + outputReport.assertCostMatches() + + assertThat(outputReport.testResults).isNotEmpty() + println(outputReport.testResults) + assertThat(outputReport.weblinks).isNotEmpty() } } diff --git a/integration_tests/src/test/kotlin/integration/TestFilteringIT.kt b/integration_tests/src/test/kotlin/integration/TestFilteringIT.kt index 69f73dba3b..5204ba1440 100644 --- a/integration_tests/src/test/kotlin/integration/TestFilteringIT.kt +++ b/integration_tests/src/test/kotlin/integration/TestFilteringIT.kt @@ -4,12 +4,18 @@ import FlankCommand import com.google.common.truth.Truth.assertThat import org.junit.Test import run -import utils.assertCountOfFailedTests -import utils.assertTestPass -import utils.assertTestResultContainsWebLinks +import utils.CONFIGS_PATH +import utils.FLANK_JAR_PATH +import utils.androidRunCommands +import utils.asOutputReport +import utils.assertCostMatches +import utils.assertExitCode +import utils.assertTestCountMatches import utils.findTestDirectoryFromOutput -import utils.loadAsTestSuite -import utils.toJUnitXmlFile +import utils.firstTestSuiteOverview +import utils.json +import utils.removeUnicode +import utils.toOutputReportFile class TestFilteringIT { private val name = this::class.java.simpleName @@ -28,14 +34,14 @@ class TestFilteringIT { assertExitCode(result, 0) val resOutput = result.output.removeUnicode() - assertThat(resOutput).containsMatch(findInCompare(name)) - assertContainsOutcomeSummary(resOutput) { - success = 1 - } - resOutput.findTestDirectoryFromOutput().toJUnitXmlFile().loadAsTestSuite().run { - assertTestResultContainsWebLinks() - assertCountOfFailedTests(0) - assertTestPass(listOf("test2")) - } + val outputReport = resOutput.findTestDirectoryFromOutput().toOutputReportFile().json().asOutputReport() + + assertThat(outputReport.cost).isNotNull() + assertThat(outputReport.weblinks).isNotEmpty() + assertThat(outputReport.error).isEmpty() + + outputReport.assertCostMatches() + + outputReport.firstTestSuiteOverview.assertTestCountMatches(total = 1) } } diff --git a/integration_tests/src/test/kotlin/integration/IntergrationTestsUtils.kt b/integration_tests/src/test/kotlin/utils/IntergrationTestsUtils.kt similarity index 81% rename from integration_tests/src/test/kotlin/integration/IntergrationTestsUtils.kt rename to integration_tests/src/test/kotlin/utils/IntergrationTestsUtils.kt index 85a23516a3..8fd2c0da49 100644 --- a/integration_tests/src/test/kotlin/integration/IntergrationTestsUtils.kt +++ b/integration_tests/src/test/kotlin/utils/IntergrationTestsUtils.kt @@ -1,10 +1,10 @@ -package integration +package utils import com.google.common.truth.Truth.assertThat import flank.common.isWindows import org.junit.Assert.assertEquals -import utils.ProcessResult import java.io.File +import java.math.BigDecimal const val FLANK_JAR_PATH = "../test_runner/build/libs/flank.jar" const val CONFIGS_PATH = "./src/test/resources/cases" @@ -60,19 +60,25 @@ fun assertContainsUploads(input: String, vararg uploads: String) = uploads.forEa assertThat(input).contains("Uploading [$it]") } -fun assertContainsOutcomeSummary(input: String, block: OutcomeSummary.() -> Unit) = - OutcomeSummary().apply(block).matcher.entries.forEach { (outcome, times) -> - val actual = outcome.regex.findAll(input).toList().size - if (actual != times) throw AssertionError( - """ - |Incorrect number of ${outcome.name} - | expected: $times - | but was: $actual - |Output: - |${"┌[\\s\\S]*┘".toRegex().find(input)?.value?.trimIndent()} - """.trimMargin() - ) - } +fun TestSuiteOverview.assertTestCountMatches( + total: Int = 0, + errors: Int = 0, + failures: Int = 0, + flakes: Int = 0, + skipped: Int = 0, +) { + assertThat(this.total).isEqualTo(total) + assertThat(this.errors).isEqualTo(errors) + assertThat(this.failures).isEqualTo(failures) + assertThat(this.flakes).isEqualTo(flakes) + assertThat(this.skipped).isEqualTo(skipped) +} + +fun OutputReport.assertCostMatches() { + assertThat(cost?.physical).isEqualToIgnoringScale(BigDecimal.ZERO) + assertThat(cost?.virtual).isGreaterThan(BigDecimal.ZERO) + assertThat(cost?.virtual).isEqualToIgnoringScale(cost?.total) +} fun assertNoOutcomeSummary(input: String) { if ("┌[\\s\\S]*┘".toRegex().matches(input)) throw AssertionError("There should be no outcome table.") diff --git a/integration_tests/src/test/kotlin/utils/LoadJsonResults.kt b/integration_tests/src/test/kotlin/utils/LoadJsonResults.kt new file mode 100644 index 0000000000..9e8caac61e --- /dev/null +++ b/integration_tests/src/test/kotlin/utils/LoadJsonResults.kt @@ -0,0 +1,12 @@ +package utils + +import com.fasterxml.jackson.databind.json.JsonMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.fasterxml.jackson.module.kotlin.readValue +import java.io.File + +private val jsonMapper by lazy { JsonMapper().registerModule(KotlinModule()) } + +fun File.loadJsonResults() = jsonMapper.readValue>(this) + +fun File.json() = readText() diff --git a/integration_tests/src/test/kotlin/utils/OutputHelper.kt b/integration_tests/src/test/kotlin/utils/OutputHelper.kt index cb8b6e9f80..243c81ddc7 100644 --- a/integration_tests/src/test/kotlin/utils/OutputHelper.kt +++ b/integration_tests/src/test/kotlin/utils/OutputHelper.kt @@ -12,6 +12,10 @@ fun String.findTestDirectoryFromOutput() = fun String.toJUnitXmlFile(): File = Paths.get("./", "results", this, "JUnitReport.xml").toFile() +fun String.toOutputReportFile(): File = + if (isEmpty()) Paths.get("outputReport.json").toFile() + else Paths.get("./", "results", this, "outputReport.json").toFile() + fun TestSuites.assertTestResultContainsWebLinks() = testSuites.flatMap { it.testCases }.filter { it.skipped == null }.forEach { assertFalse(it.webLink.isNullOrBlank()) diff --git a/integration_tests/src/test/kotlin/utils/OutputReportParser.kt b/integration_tests/src/test/kotlin/utils/OutputReportParser.kt new file mode 100644 index 0000000000..283b48e0a1 --- /dev/null +++ b/integration_tests/src/test/kotlin/utils/OutputReportParser.kt @@ -0,0 +1,44 @@ +package utils + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.json.JsonMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.fasterxml.jackson.module.kotlin.readValue +import flank.common.OutputReportCostNode + +private val jsonMapper by lazy { JsonMapper().registerModule(KotlinModule()) } + +fun String.asOutputReport() = jsonMapper.readValue(this) + +val OutputReport.firstTestSuiteOverview: TestSuiteOverview + get() = testResults.values.first().testAxises.first().testSuiteOverview + +data class OutputReport( + val args: Any, + val cost: OutputReportCostNode? = null, + val weblinks: List = emptyList(), + @JsonProperty("test_results") val testResults: Map = emptyMap(), + val error: String = "" +) + +data class Matrix( + val app: String = "", + @JsonProperty("test-axises") val testAxises: List +) + +data class TextAxis( + val device: String, + val outcome: String, + val details: String, + val testSuiteOverview: TestSuiteOverview +) + +data class TestSuiteOverview( + val total: Int, + val errors: Int, + val failures: Int, + val flakes: Int, + val skipped: Int, + val elapsedTime: Double, + val overheadTime: Double +) diff --git a/integration_tests/src/test/resources/cases/all_test_filtered_android.yml b/integration_tests/src/test/resources/cases/all_test_filtered_android.yml index 27d3d8cd3b..65f95d4c78 100644 --- a/integration_tests/src/test/resources/cases/all_test_filtered_android.yml +++ b/integration_tests/src/test/resources/cases/all_test_filtered_android.yml @@ -7,3 +7,4 @@ gcloud: flank: disable-sharding: true disable-usage-statistics: true + output-report: json diff --git a/integration_tests/src/test/resources/cases/all_test_filtered_ios.yml b/integration_tests/src/test/resources/cases/all_test_filtered_ios.yml index 571d96f95f..3b48dbc340 100644 --- a/integration_tests/src/test/resources/cases/all_test_filtered_ios.yml +++ b/integration_tests/src/test/resources/cases/all_test_filtered_ios.yml @@ -5,3 +5,4 @@ flank: test-targets: - nonExisting/Class disable-usage-statistics: true + output-report: json diff --git a/integration_tests/src/test/resources/cases/dump_shards_android.yml b/integration_tests/src/test/resources/cases/dump_shards_android.yml index 4f5062f9bd..7dea238766 100644 --- a/integration_tests/src/test/resources/cases/dump_shards_android.yml +++ b/integration_tests/src/test/resources/cases/dump_shards_android.yml @@ -8,3 +8,4 @@ flank: max-test-shards: 2 output-style: single disable-usage-statistics: true + output-report: json diff --git a/integration_tests/src/test/resources/cases/dump_shards_ios.yml b/integration_tests/src/test/resources/cases/dump_shards_ios.yml index c3d3f04e33..0d63e2f0f1 100644 --- a/integration_tests/src/test/resources/cases/dump_shards_ios.yml +++ b/integration_tests/src/test/resources/cases/dump_shards_ios.yml @@ -7,3 +7,4 @@ flank: max-test-shards: 2 output-style: single disable-usage-statistics: true + output-report: json diff --git a/integration_tests/src/test/resources/cases/flank_android_gameloop_success.yml b/integration_tests/src/test/resources/cases/flank_android_gameloop_success.yml index bd8ab86844..67f5bb643b 100644 --- a/integration_tests/src/test/resources/cases/flank_android_gameloop_success.yml +++ b/integration_tests/src/test/resources/cases/flank_android_gameloop_success.yml @@ -8,3 +8,4 @@ gcloud: flank: disable-sharding: true disable-usage-statistics: true + output-report: json diff --git a/integration_tests/src/test/resources/cases/flank_android_ignore_failed.yml b/integration_tests/src/test/resources/cases/flank_android_ignore_failed.yml index 912ff95415..a3df798a05 100644 --- a/integration_tests/src/test/resources/cases/flank_android_ignore_failed.yml +++ b/integration_tests/src/test/resources/cases/flank_android_ignore_failed.yml @@ -6,3 +6,4 @@ flank: disable-results-upload: true ignore-failed-tests: true disable-usage-statistics: true + output-report: json diff --git a/integration_tests/src/test/resources/cases/flank_android_multiple_apk.yml b/integration_tests/src/test/resources/cases/flank_android_multiple_apk.yml index 149cd4a440..d53ee76990 100644 --- a/integration_tests/src/test/resources/cases/flank_android_multiple_apk.yml +++ b/integration_tests/src/test/resources/cases/flank_android_multiple_apk.yml @@ -12,3 +12,4 @@ flank: - test: ../test_runner/src/test/kotlin/ftl/fixtures/tmp/apk/app-multiple-error-debug-androidTest.apk - test: gs://flank-open-source.appspot.com/integration/app-single-success-debug-androidTest.apk disable-usage-statistics: true + output-report: json diff --git a/integration_tests/src/test/resources/cases/flank_android_multiple_devices.yml b/integration_tests/src/test/resources/cases/flank_android_multiple_devices.yml index b83c68ff26..228fa2ecbc 100644 --- a/integration_tests/src/test/resources/cases/flank_android_multiple_devices.yml +++ b/integration_tests/src/test/resources/cases/flank_android_multiple_devices.yml @@ -19,3 +19,4 @@ flank: - test: ../test_runner/src/test/kotlin/ftl/fixtures/tmp/apk/app-multiple-error-debug-androidTest.apk - test: gs://flank-open-source.appspot.com/integration/app-single-success-debug-androidTest.apk disable-usage-statistics: true + output-report: json diff --git a/integration_tests/src/test/resources/cases/flank_android_run_timeout.yml b/integration_tests/src/test/resources/cases/flank_android_run_timeout.yml index 197259cd07..52f3576572 100644 --- a/integration_tests/src/test/resources/cases/flank_android_run_timeout.yml +++ b/integration_tests/src/test/resources/cases/flank_android_run_timeout.yml @@ -7,3 +7,4 @@ flank: disable-sharding: true run-timeout: 1m disable-usage-statistics: true + output-report: json diff --git a/integration_tests/src/test/resources/cases/sanity_robo.yml b/integration_tests/src/test/resources/cases/sanity_robo.yml index 0fa8671016..41be22ebd4 100644 --- a/integration_tests/src/test/resources/cases/sanity_robo.yml +++ b/integration_tests/src/test/resources/cases/sanity_robo.yml @@ -2,3 +2,4 @@ gcloud: app: ../test_runner/src/test/kotlin/ftl/fixtures/tmp/apk/app-debug.apk flank: disable-usage-statistics: true + output-report: json diff --git a/integration_tests/src/test/resources/cases/test_filtering_android.yml b/integration_tests/src/test/resources/cases/test_filtering_android.yml index 388a3609e0..854d2ec627 100644 --- a/integration_tests/src/test/resources/cases/test_filtering_android.yml +++ b/integration_tests/src/test/resources/cases/test_filtering_android.yml @@ -9,3 +9,4 @@ flank: additional-app-test-apks: - test: ../test_runner/src/test/kotlin/ftl/fixtures/tmp/apk/app-multiple-success-debug-androidTest.apk disable-usage-statistics: true + output-report: json diff --git a/test_runner/src/main/kotlin/ftl/reports/output/OutputReport.kt b/test_runner/src/main/kotlin/ftl/reports/output/OutputReport.kt index b26d549a63..e1257674bf 100644 --- a/test_runner/src/main/kotlin/ftl/reports/output/OutputReport.kt +++ b/test_runner/src/main/kotlin/ftl/reports/output/OutputReport.kt @@ -1,6 +1,5 @@ package ftl.reports.output -import flank.common.fileExists import ftl.gc.GcStorage import ftl.run.common.prettyPrint import java.io.File @@ -51,9 +50,11 @@ private fun OutputData.toJson(): String = prettyPrint.toJson(this) private fun String.storeToFile( directoryPath: String, fileName: String -) = if (directoryPath.fileExists()) +) = File(directoryPath).let { directory -> + if (directory.exists().not()) + directory.mkdirs() Paths.get(directoryPath, fileName).toFile().apply { writeText(this@storeToFile) } -else null +} private fun File.uploadToGcloud(resultsBucket: String, resultsDir: String) { GcStorage.upload(absolutePath, resultsBucket, resultsDir) diff --git a/test_runner/src/main/kotlin/ftl/reports/output/OutputReportLoggers.kt b/test_runner/src/main/kotlin/ftl/reports/output/OutputReportLoggers.kt index aa067a3f0b..e16876ed8b 100644 --- a/test_runner/src/main/kotlin/ftl/reports/output/OutputReportLoggers.kt +++ b/test_runner/src/main/kotlin/ftl/reports/output/OutputReportLoggers.kt @@ -1,21 +1,26 @@ package ftl.reports.output +import flank.common.OUTPUT_ARGS +import flank.common.OUTPUT_COST +import flank.common.OUTPUT_TEST_RESULTS +import flank.common.OUTPUT_WEBLINKS +import flank.common.OutputReportCostNode import ftl.args.IArgs import ftl.json.MatrixMap import ftl.json.SavedMatrix import java.math.BigDecimal internal fun OutputReport.log(args: IArgs) { - add("args", args) + add(OUTPUT_ARGS, args) } internal fun OutputReport.log(matrixMap: MatrixMap) { - add("weblinks", matrixMap.map.values.map { it.webLink }) + add(OUTPUT_WEBLINKS, matrixMap.map.values.map { it.webLink }) } internal fun OutputReport.log(matrices: Collection) { add( - "test_results", + OUTPUT_TEST_RESULTS, matrices.map { it.matrixId to mapOf( "app" to it.appFileName, @@ -30,11 +35,5 @@ internal fun OutputReport.log( virtualCost: BigDecimal, totalCost: BigDecimal ) { - add("cost", OutputReportCostNode(physicalCost, virtualCost, totalCost)) + add(OUTPUT_COST, OutputReportCostNode(physicalCost, virtualCost, totalCost)) } - -private data class OutputReportCostNode( - val physical: BigDecimal, - val virtual: BigDecimal, - val total: BigDecimal -) diff --git a/test_runner/src/test/kotlin/ftl/args/AndroidArgsTest.kt b/test_runner/src/test/kotlin/ftl/args/AndroidArgsTest.kt index bc03d4bf03..ae73c67786 100644 --- a/test_runner/src/test/kotlin/ftl/args/AndroidArgsTest.kt +++ b/test_runner/src/test/kotlin/ftl/args/AndroidArgsTest.kt @@ -453,7 +453,7 @@ AndroidArgs disable-results-upload: false default-class-test-time: 240.0 disable-usage-statistics: false - output-report: none + output-report: json skip-config-validation: false """.trimIndent(), args.toString() diff --git a/test_runner/src/test/kotlin/ftl/fixtures/simple-android-flank.yml b/test_runner/src/test/kotlin/ftl/fixtures/simple-android-flank.yml index e486f718ba..e61450156e 100644 --- a/test_runner/src/test/kotlin/ftl/fixtures/simple-android-flank.yml +++ b/test_runner/src/test/kotlin/ftl/fixtures/simple-android-flank.yml @@ -4,3 +4,4 @@ gcloud: results-dir: test_dir flank: legacy-junit-result: true + output-report: json diff --git a/test_runner/src/test/kotlin/ftl/fixtures/test_app_cases/flank-single-success.yml b/test_runner/src/test/kotlin/ftl/fixtures/test_app_cases/flank-single-success.yml index dc81cf8350..a8b941c0b8 100644 --- a/test_runner/src/test/kotlin/ftl/fixtures/test_app_cases/flank-single-success.yml +++ b/test_runner/src/test/kotlin/ftl/fixtures/test_app_cases/flank-single-success.yml @@ -4,3 +4,4 @@ gcloud: flank: disable-sharding: true + output-report: json diff --git a/test_runner/src/test/kotlin/ftl/reports/output/OutputReportTest.kt b/test_runner/src/test/kotlin/ftl/reports/output/OutputReportTest.kt index 2ee2a3a4f2..7b61294b47 100644 --- a/test_runner/src/test/kotlin/ftl/reports/output/OutputReportTest.kt +++ b/test_runner/src/test/kotlin/ftl/reports/output/OutputReportTest.kt @@ -85,26 +85,6 @@ class OutputReportTest { assertThat(expectedPath.toFile().exists()).isFalse() } - @Test - fun `should not generate report if results folder is missing`() { - // given - val configuration = OutputReportConfiguration( - enabled = true, - type = OutputReportType.JSON, - local = OutputReportLocalConfiguration("tmp"), - remote = OutputReportRemoteConfiguration(uploadReport = false) - ) - val expectedPath = Paths.get(configuration.local.storageDirectory, configuration.local.outputFile) - - // when - outputReport.configure(configuration) - outputReport.add("test", "node") - outputReport.generate() - - // then - assertThat(expectedPath.toFile().exists()).isFalse() - } - @Test fun `should generate report if enabled`() { // given