[build][test][environment] improvemnt of test reporting infrostructure

1. reduced number of json file generation
2. Groovy's JsonSlurp replaced with Kotlin Json serialization/deserialization
3. improved naive groovy -> kotlin convertion (46d32c606)
4. some refactoring.
This commit is contained in:
Vasily Levchenko
2019-02-07 17:08:56 +03:00
parent b84559b27a
commit b0f3ee9459
8 changed files with 382 additions and 295 deletions
+19 -15
View File
@@ -177,31 +177,29 @@ task sanity {
dependsOn(tasksOf { it instanceof RunStdlibTest })
}
// Collect results in one json.
// Collect reports in one json.
task resultsTask() {
doLast {
Map<String, Map<String, TestResult>> results = [:]
List<KonanTestGroupReport> reports = []
def statistics = new Statistics()
tasks.withType(RunExternalTestGroup).matching { it.state.executed }.each {
results.put(it.name, it.results)
statistics.add(it.statistics)
reports += new KonanTestGroupReport(it.name, it.testGroupReporter.suiteReports)
statistics.add(it.testGroupReporter.statistics)
}
tasks.withType(RunStdlibTest).matching {it.state.executed }.each {
statistics.add(it.statistics)
}
def output = ["statistics": statistics, "tests": results]
def json = JsonOutput.toJson(output)
def reportFile = new File(testOutputExternal, "results.json")
testOutputExternal.mkdirs()
reportFile.write(JsonOutput.prettyPrint(json))
println("DONE.\n\n" +
"TOTAL: $statistics.total\n" +
"PASSED: $statistics.passed\n" +
"FAILED: $statistics.failed\n" +
"ERROR: $statistics.error\n" +
"SKIPPED: $statistics.skipped")
ExternalReportUtilsKt.saveReport("$testOutputExternal/reports.json", statistics, reports)
use(KonanTestSuiteReportKt) {
project.logger.quiet("DONE.\n\n" +
"TOTAL: $statistics.total\n" +
"PASSED: $statistics.passed\n" +
"FAILED: $statistics.failed\n" +
"ERROR: $statistics.error\n" +
"SKIPPED: $statistics.skipped")
}
}
}
@@ -272,6 +270,12 @@ task slackReport(type:Reporter) {
reportHome = rootProject.file("test.output").absoluteFile
}
task slackReportNightly(type:NightlyReporter) {
externalMacosReport = "external_macos_results.json"
externalLinuxReport = "external_linux_results.json"
externalWindowsReport = "external_windows_results.json"
}
task sum (type:RunKonanTest) {
source = "codegen/function/sum.kt"
}
+2
View File
@@ -28,5 +28,7 @@ allprojects {
maven {
url 'https://cache-redirector.jetbrains.com/maven-central'
}
maven { url "https://kotlin.bintray.com/kotlinx" }
}
}
+8 -8
View File
@@ -20,27 +20,27 @@ buildscript {
ext.rootBuildDirectory = "$rootDir/.."
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
dependencies{
classpath "org.jetbrains.kotlin:kotlin-serialization:$buildKotlinVersion"
}
}
apply plugin: 'kotlin'
apply plugin: 'kotlinx-serialization'
repositories {
maven {
url buildKotlinCompilerRepo
}
maven {
url 'https://cache-redirector.jetbrains.com/maven-central'
}
}
/* don't use repositories: gradle will ignore it anyway, but may confuse gradle build engineer, see outer build.gradle */
dependencies {
compile gradleApi()
compile localGroovy()
compile "org.jetbrains.kotlin:kotlin-stdlib:$buildKotlinVersion"
compile "org.jetbrains.kotlin:kotlin-reflect:$buildKotlinVersion"
compile group: 'com.ullink.slack', name: 'simpleslackapi', version: '0.6.0'
// An artifact from the included build 'shared' cannot be used here due to https://github.com/gradle/gradle/issues/3768
// TODO: Remove this hack when the bug is fixed
compile project(':shared')
compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.10.0"
}
rootProject.dependencies {
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin
import groovy.json.JsonOutput
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecResult
@@ -330,12 +329,6 @@ abstract class KonanTest extends JavaExec {
}
}
class TestFailedException extends RuntimeException {
TestFailedException(String s) {
super(s)
}
}
abstract class ExtKonanTest extends KonanTest {
ExtKonanTest() {
@@ -424,6 +417,9 @@ class BuildKonanTest extends ExtKonanTest {
* Runs test built with Konan's TestRunner
*/
class RunKonanTest extends ExtKonanTest {
/**
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
*/
public def inDevelopersRun = true
public def buildTaskName = 'buildKonanTests'
@@ -473,6 +469,9 @@ class RunKonanTest extends ExtKonanTest {
}
class RunStdlibTest extends RunKonanTest {
/**
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
*/
public def inDevelopersRun = false
public def statistics = new Statistics()
@@ -504,8 +503,10 @@ class RunStdlibTest extends RunKonanTest {
def n = matcher[0][1] as Integer
statistics.fail(n)
}
if (statistics.total == 0) {
statistics.error(testsTotal != 0 ? testsTotal : 1)
use(KonanTestSuiteReportKt) {
if (statistics.total == 0) {
statistics.error(testsTotal != 0 ? testsTotal : 1)
}
}
}
}
@@ -515,6 +516,9 @@ class RunStdlibTest extends RunKonanTest {
* Compiles and executes test as a standalone binary
*/
class RunStandaloneKonanTest extends KonanTest {
/**
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
*/
public def inDevelopersRun = true
void compileTest(List<String> filesToCompile, String exe) {
@@ -527,6 +531,9 @@ class RunStandaloneKonanTest extends KonanTest {
// project.exec + a shell script isolate the jvm
// from IDEA. Use the RunKonanTest instead.
class RunDriverKonanTest extends KonanTest {
/**
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
*/
public def inDevelopersRun = true
RunDriverKonanTest() {
@@ -565,6 +572,9 @@ class RunDriverKonanTest extends KonanTest {
}
class RunInteropKonanTest extends KonanTest {
/**
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
*/
public def inDevelopersRun = true
private String interop
@@ -593,8 +603,10 @@ class RunInteropKonanTest extends KonanTest {
}
class LinkKonanTest extends KonanTest {
/**
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
*/
public def inDevelopersRun = true
protected String lib
void compileTest(List<String> filesToCompile, String exe) {
@@ -607,10 +619,13 @@ class LinkKonanTest extends KonanTest {
}
class DynamicKonanTest extends KonanTest {
protected String cSource
/**
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
*/
public def inDevelopersRun = true
protected String cSource
void compileTest(List<String> filesToCompile, String exe) {
def libname = "testlib"
def dylib = "$outputDirectory/$libname"
@@ -640,13 +655,15 @@ class DynamicKonanTest extends KonanTest {
}
}
class RunExternalTestGroup extends RunStandaloneKonanTest {
def inDevelopersRun = false
/**
* overrides [KonanTest::inDevelopersRun] used in [:backend.native:tests:sanity]
*/
public def inDevelopersRun = false
def groupDirectory = "."
def outputSourceSetName = "testOutputExternal"
String filter = project.findProperty("filter")
Map<String, TestResult> results = [:]
Statistics statistics = new Statistics()
def testGroupReporter = new KonanTestGroupReportEnvironment(project)
RunExternalTestGroup() {
}
@@ -933,79 +950,53 @@ fun runTest() {
}
}
statistics = new Statistics()
def testSuite = createTestSuite(name, statistics)
testSuite.start()
// Build tests in the group
flags = (flags ?: []) + "-tr"
def compileList = []
ktFiles.each {
source = project.relativePath(it)
if (isEnabledForNativeBackend(source)) {
// Create separate output directory for each test in the group.
outputDirectory = outputRootDirectory + "/${it.name}"
project.file(outputDirectory).mkdirs()
parseLanguageFlags()
compileList.addAll(createTestFiles())
}
}
compileList.add(project.file("testUtils.kt").absolutePath)
compileList.add(project.file("helpers.kt").absolutePath)
try {
runCompiler(compileList, buildExePath(), flags)
} catch (Exception ex) {
println("ERROR: Compilation failed for test suite: ${testSuite.name} with exception: ${ex}")
println("The following files were unable to compile:")
ktFiles.each { println it.name }
statistics.error(ktFiles.size())
testSuite.finish()
throw new RuntimeException("Compilation failed", ex)
}
// Run the tests.
def currentResult = null
outputDirectory = outputRootDirectory
arguments = (arguments ?: []) + "--ktest_logger=SILENT"
ktFiles.each {
source = project.relativePath(it)
def savedArgs = arguments
arguments += "--ktest_filter=_${normalize(it.name)}.*"
println("TEST: $it.name (done: $statistics.total/${ktFiles.size()}, passed: $statistics.passed, skipped: $statistics.skipped)")
def testCase = testSuite.createTestCase(it.name)
testCase.start()
if (isEnabledForNativeBackend(source)) {
try {
println(source)
super.executeTest()
currentResult = testCase.pass()
} catch (TestFailedException e) {
currentResult = testCase.fail(e)
} catch (Exception ex) {
ex.printStackTrace()
currentResult = testCase.error(ex)
testGroupReporter.suite(name) { suite ->
// Build tests in the group
flags = (flags ?: []) + "-tr"
def compileList = []
ktFiles.each {
source = project.relativePath(it)
if (isEnabledForNativeBackend(source)) {
// Create separate output directory for each test in the group.
outputDirectory = outputRootDirectory + "/${it.name}"
project.file(outputDirectory).mkdirs()
parseLanguageFlags()
compileList.addAll(createTestFiles())
}
} else {
currentResult = testCase.skip()
}
arguments = savedArgs
println("TEST $currentResult.status\n")
if (currentResult.status == TestStatus.ERROR || currentResult.status == TestStatus.FAILED) {
println("Command to reproduce: ./gradlew $name -Pfilter=${it.name}\n")
compileList.add(project.file("testUtils.kt").absolutePath)
compileList.add(project.file("helpers.kt").absolutePath)
try {
runCompiler(compileList, buildExePath(), flags)
} catch (Exception ex) {
project.logger.quiet("ERROR: Compilation failed for test suite: $name with exception", ex)
project.logger.quiet("The following files were unable to compile:")
ktFiles.each { project.logger.quiet(it.name) }
suite.abort(ex, ktFiles.size())
throw new RuntimeException("Compilation failed", ex)
}
// Run the tests.
def currentResult = null
outputDirectory = outputRootDirectory
arguments = (arguments ?: []) + "--ktest_logger=SILENT"
ktFiles.each { file ->
source = project.relativePath(file)
def savedArgs = arguments
arguments += "--ktest_filter=_${normalize(file.name)}.*"
use(KonanTestSuiteReportKt) {
project.logger.quiet("TEST: $file.name (done: $testGroupReporter.statistics.total/${ktFiles.size()}, passed: $testGroupReporter.statistics.passed, skipped: $testGroupReporter.statistics.skipped)")
}
if (isEnabledForNativeBackend(source)) {
suite.executeTest(file.name) {
project.logger.quiet(source)
super.executeTest()
}
} else {
suite.skipTest(file.name)
}
arguments = savedArgs
}
results.put(it.name, currentResult)
}
testSuite.finish()
// Save the report.
def reportFile = project.file("${outputRootDirectory}/results.json")
def json = JsonOutput.toJson(["statistics" : statistics, "tests" : results])
reportFile.write(JsonOutput.prettyPrint(json))
println("TOTAL PASSED: $statistics.passed/$statistics.total (SKIPPED: $statistics.skipped)")
}
KonanTestSuite createTestSuite(String name, Statistics statistics) {
if (System.getenv("TEAMCITY_BUILD_PROPERTIES_FILE") != null)
return new TeamcityKonanTestSuite(name, statistics)
return new KonanTestSuite(name, statistics)
}
}
@@ -0,0 +1,26 @@
package org.jetbrains.kotlin
import kotlinx.io.PrintWriter
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import java.io.File
import java.io.FileReader
@Serializable
data class ExternalTestReport(val statistics: Statistics, val groups: List<KonanTestGroupReport>)
@ImplicitReflectionSerializer
fun saveReport(reportFileName: String, statistics: Statistics, groups:List<KonanTestGroupReport>){
File(reportFileName).apply {
parentFile.mkdirs()
PrintWriter(this).use {
it.append(Json.stringify(ExternalTestReport(statistics, groups)))
}
}
}
@ImplicitReflectionSerializer
fun loadReport(reportFileName: String) : ExternalTestReport = FileReader(reportFileName).use {
return@use Json.parse<ExternalTestReport>(it.readText())
}
@@ -1,139 +0,0 @@
package org.jetbrains.kotlin
import java.io.PrintWriter
import java.io.StringWriter
enum class TestStatus {
PASSED,
FAILED,
ERROR,
SKIPPED
}
data class TestResult(val status: TestStatus, val comment: String = "")
data class Statistics(
var passed: Int = 0,
var failed: Int = 0,
var error: Int = 0,
var skipped: Int = 0) {
val total: Int
get() = passed + failed + error + skipped
fun pass(count: Int = 1) { passed += count }
fun skip(count: Int = 1) { skipped += count }
fun fail(count: Int = 1) { failed += count }
fun error(count: Int = 1) { error += count }
fun add(other: Statistics) {
passed += other.passed
failed += other.failed
error += other.error
skipped += other.skipped
}
}
open class KonanTestSuite(val name: String, val statistics: Statistics) {
open inner class KonanTestCase(val name: String) {
open fun start() {}
open fun pass(): TestResult {
statistics.pass()
return TestResult(TestStatus.PASSED)
}
open fun fail(e: RuntimeException): TestResult {
statistics.fail()
println(e.message)
return TestResult(TestStatus.FAILED, "Exception: ${e.message}. Cause: ${e.cause?.message}")
}
open fun error(e: Exception): TestResult {
statistics.error()
return TestResult(TestStatus.ERROR, "Exception: ${e.message}. Cause: ${e.cause?.message}")
}
open fun skip(): TestResult {
statistics.skip()
return TestResult(TestStatus.SKIPPED)
}
}
open fun createTestCase(name: String) = KonanTestCase(name)
open fun start() {}
open fun finish() {}
}
class TeamcityKonanTestSuite(name: String, statistics: Statistics) : KonanTestSuite(name, statistics) {
inner class TeamcityKonanTestCase(name: String) : KonanTestCase(name) {
private fun teamcityFinish() {
teamcityReport("testFinished name='$name'")
}
override fun start() {
teamcityReport("testStarted name='$name'")
}
override fun pass(): TestResult {
teamcityFinish()
return super.pass()
}
override fun fail(e: RuntimeException): TestResult {
teamcityReport("testFailed type='comparisonFailure' name='$name' message='${toTeamCityFormat(e.message)}'")
teamcityFinish()
return super.fail(e)
}
override fun error(e: Exception): TestResult {
val writer = StringWriter()
e.printStackTrace(PrintWriter(writer))
val rawString = writer.toString()
teamcityReport("testFailed name='$name' message='${toTeamCityFormat(e.message)}' " +
"details='${toTeamCityFormat(rawString)}'")
teamcityFinish()
return super.error(e)
}
override fun skip(): TestResult {
teamcityReport("testIgnored name='$name'")
teamcityFinish()
return super.skip()
}
}
override fun createTestCase(name: String) = TeamcityKonanTestCase(name)
private fun teamcityReport(msg: String) {
println("##teamcity[$msg]")
}
/**
* Teamcity require escaping some symbols in pipe manner.
* https://github.com/GitTools/GitVersion/issues/94
*/
fun toTeamCityFormat(inStr: String?): String = inStr?.let {
it.replace("\\|", "||")
.replace("\r", "|r")
.replace("\n", "|n")
.replace("'", "|'")
.replace("\\[", "|[")
.replace("]", "|]")
} ?: "null"
override fun start() {
teamcityReport("testSuiteStarted name='$name'")
}
override fun finish() {
teamcityReport("testSuiteFinished name='$name'")
}
}
@@ -0,0 +1,168 @@
package org.jetbrains.kotlin
import kotlinx.io.PrintWriter
import kotlinx.io.StringWriter
import kotlinx.serialization.Optional
import kotlinx.serialization.Serializable
import org.gradle.api.Project
enum class TestStatus {
PASSED,
FAILED,
ERROR,
SKIPPED
}
@Serializable
data class Statistics(
var passed: Int = 0,
var failed: Int = 0,
var error: Int = 0,
var skipped: Int = 0) {
fun pass(count: Int = 1) { passed += count }
fun skip(count: Int = 1) { skipped += count }
fun fail(count: Int = 1) { failed += count }
fun error(count: Int = 1) { error += count }
fun add(other: Statistics) {
passed += other.passed
failed += other.failed
error += other.error
skipped += other.skipped
}
}
val Statistics.total: Int
get() = passed + failed + error + skipped
class TestFailedException(msg:String) : RuntimeException(msg)
@Serializable
data class KonanTestGroupReport(val name:String, val suites:List<KonanTestSuiteReport>)
@Serializable
data class KonanTestSuiteReport(val name: String, val tests: List<KonanTestCaseReport>)
@Serializable
data class KonanTestCaseReport(val name:String, val status: TestStatus, @Optional val comment:String? = null)
class KonanTestSuiteReportEnvironment(val name:String, val project: Project, val statistics: Statistics) {
private val tc = if (Tc.enabled) TeamCityTestPrinter(project) else null
val tests = mutableListOf<KonanTestCaseReport>()
fun executeTest(testName: String, action:() -> Unit) {
var test:KonanTestCaseReport? = null
try {
tc?.startTest(testName)
action()
tc?.passTest(testName)
statistics.pass()
test = KonanTestCaseReport(testName, TestStatus.PASSED)
} catch (e:TestFailedException) {
tc?.failedTest(testName, e)
statistics.fail()
test = KonanTestCaseReport(testName, TestStatus.FAILED, "Exception: ${e.message}. Cause: ${e.cause?.message}")
project.logger.quiet("test: $testName failed")
} catch (e:Exception) {
tc?.errorTest(testName, e)
statistics.error()
test = KonanTestCaseReport(testName, TestStatus.ERROR, "Exception: ${e.message}. Cause: ${e.cause?.message}")
project.logger.quiet("error on test: $testName", e)
}
test!!.apply {
tests += this
if (status == TestStatus.ERROR || status == TestStatus.FAILED) {
project.logger.quiet("Command to reproduce: ./gradlew $name -Pfilter=${test.name}\n")
}
}
}
fun skipTest(name: String) {
tc?.skipTest(name)
statistics.skip()
tests += KonanTestCaseReport(name, TestStatus.SKIPPED)
}
fun abort(throwable: Throwable, count:Int) {
statistics.error(count)
project.logger.quiet("suite `$name` aborted with exception", throwable)
}
internal operator fun invoke(action: (KonanTestSuiteReportEnvironment) -> Unit) {
tc?.suiteStart(name)
action(this)
tc?.suiteFinish(name)
}
}
class KonanTestGroupReportEnvironment(val project:Project) {
val statistics = Statistics()
val suiteReports = mutableListOf<KonanTestSuiteReport>()
fun suite(suiteName:String, action:(KonanTestSuiteReportEnvironment)->Unit) {
val konanTestSuiteEnvironment = KonanTestSuiteReportEnvironment(suiteName, project, statistics)
konanTestSuiteEnvironment {
action(it)
}
suiteReports += KonanTestSuiteReport(suiteName, konanTestSuiteEnvironment.tests)
}
}
private class TeamCityTestPrinter(val project:Project) {
fun suiteStart(name: String) {
teamcityReport("testSuiteStarted name='$name'")
}
fun suiteFinish(name: String) {
teamcityReport("testSuiteFinished name='$name'")
}
fun startTest(name: String) {
teamcityReport("testStarted name='$name'")
}
fun passTest(testName: String) = teamcityFinish(testName)
fun failedTest(testName: String, testFailedException: TestFailedException) {
teamcityReport("testFailed type='comparisonFailure' name='$testName' message='${testFailedException.message.toTeamCityFormat()}'")
teamcityFinish(testName)
}
fun errorTest(testName: String, exception: Exception) {
val writer = StringWriter()
exception.printStackTrace(PrintWriter(writer))
val rawString = writer.toString()
teamcityReport("testFailed name='$testName' message='${exception.message.toTeamCityFormat()}' " +
"details='${rawString.toTeamCityFormat()}'")
teamcityFinish(testName)
}
fun skipTest(testName: String) {
teamcityReport("testIgnored name='$testName'")
teamcityFinish(testName)
}
private fun teamcityFinish(testName:String) {
teamcityReport("testFinished name='$testName'")
}
/**
* Teamcity require escaping some symbols in pipe manner.
* https://github.com/GitTools/GitVersion/issues/94
*/
private fun String?.toTeamCityFormat(): String = this?.also {
it.replace("\\|", "||")
.replace("\r", "|r")
.replace("\n", "|n")
.replace("'", "|'")
.replace("\\[", "|[")
.replace("]", "|]")} ?: "null"
private fun teamcityReport(msg: String) {
project.logger.quiet("##teamcity[$msg]")
}
}
@@ -6,69 +6,104 @@
package org.jetbrains.kotlin
import com.ullink.slack.simpleslackapi.impl.SlackSessionFactory
import groovy.json.JsonSlurper
import kotlinx.serialization.ImplicitReflectionSerializer
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.io.FileInputStream
import java.util.*
internal object Tc {
private val teamcityConfig = System.getenv("TEAMCITY_BUILD_PROPERTIES_FILE")
val enabled:Boolean = (teamcityConfig != null)
private val buildConfig by lazy {
teamcityConfig ?: return@lazy null
val properties = Properties()
properties.load(FileInputStream(teamcityConfig))
properties
}
val buildId = buildConfig?.getProperty("teamcity.build.id")
val buildTypeId = buildConfig?.getProperty("teamcity.buildType.id")
val konanReporterToken = buildConfig?.getProperty("konan-reporter-token")
val konanChannelName = buildConfig?.getProperty("konan-channel-name")
}
private fun buildLogUrlTab(buildId: String?, buildTypeId: String?): String = tabUrl(buildId, buildTypeId, "buildLog")
private fun tabUrl(buildId: String?, buildTypeId: String?, tab: String?): String =
"http://buildserver.labs.intellij.net/viewLog.html?buildId=$buildId&buildTypeId=$buildTypeId&tab=$tab"
private fun testReportUrl(buildId: String?, buildTypeId: String?): String = tabUrl(buildId, buildTypeId, "testsInfo")
private fun sendTextToSlack(report: String) {
with(SlackSessionFactory.createWebSocketSlackSession(Tc.konanReporterToken)) {
connect()
sendMessage(findChannelByName(Tc.konanChannelName),
"Hello, аборигены Котлина!\n текущий статус:\n$report")
disconnect()
}
}
private fun reportEpilogue(): String {
val logUrl = buildLogUrlTab(Tc.buildId, Tc.buildTypeId)
val testReportUrl = testReportUrl(Tc.buildId, Tc.buildTypeId)
return "\nlog url: $logUrl\ntest report url: $testReportUrl"
}
private val Statistics.report
get() = "total: $total\npassed: $passed\nfailed: $failed\nerror: $error\nskipped: $skipped"
private val Statistics.oneLineReport
get() = "(total: $total, passed: $passed, failed: $failed, error: $error, skipped: $skipped)"
open class Reporter : DefaultTask() {
private fun buildLogUrlTab(buildId: String, buildTypeId: String): String = tabUrl(buildId, buildTypeId, "buildLog")
private fun tabUrl(buildId: String, buildTypeId: String, tab: String): String =
"http://buildserver.labs.intellij.net/viewLog.html?buildId=$buildId&buildTypeId=$buildTypeId&tab=$tab"
private fun testReportUrl(buildId: String, buildTypeId: String): String = tabUrl(buildId, buildTypeId, "testsInfo")
@Input
lateinit var reportHome: String
@ImplicitReflectionSerializer
@TaskAction
fun report() {
val teamcityConfig = System.getenv("TEAMCITY_BUILD_PROPERTIES_FILE")
?: throw RuntimeException("Can't load teamcity config")
val buildProperties = Properties()
buildProperties.load(FileInputStream(teamcityConfig))
val buildId = buildProperties.getProperty("teamcity.build.id")
val buildTypeId = buildProperties.getProperty("teamcity.buildType.id")
val logUrl = buildLogUrlTab(buildId, buildTypeId)
val testReportUrl = testReportUrl(buildId, buildTypeId)
var epilogue = "\nlog url: $logUrl\ntest report url: $testReportUrl"
val reportJson = loadReport("$reportHome/external/report.json")
val resultsFile = File(reportHome, "external/results.json")
val report: String = if (resultsFile.exists()) {
@Suppress("UNCHECKED_CAST")
val obj = JsonSlurper().parse(resultsFile) as? AbstractMap<String, Any>
?: throw IllegalStateException("Got incorrect JSON object: ${resultsFile.absolutePath}")
@Suppress("UNCHECKED_CAST")
val statsJSON = obj["statistics"] as? AbstractMap<String, Int>
?: throw IllegalStateException("Got incorrect statistics object in JSON:" +
resultsFile.absolutePath)
val stats = Statistics(
passed = statsJSON["passed"]!!,
failed = statsJSON["failed"]!!,
error = statsJSON["error"]!!,
skipped = statsJSON["skipped"]!!)
val total = statsJSON["total"]!!
if (total != stats.total) {
val message = "WARNING: total amount of tests doesn't match with the sum of failed, passed, error & skipped"
println(message)
epilogue += message
}
"total: ${stats.total}\npassed: ${stats.passed}\nfailed: ${stats.failed}\n" +
"error:${stats.error}\nskipped:${stats.skipped} $epilogue"
} else {
"Unable to get results\nBuild has probably failed$epilogue"
}
println(report)
with(SlackSessionFactory.createWebSocketSlackSession(buildProperties.getProperty("konan-reporter-token"))) {
connect()
sendMessage(findChannelByName(buildProperties.getProperty("konan-channel-name")),
"Hello, аборигены Котлина!\n текущий статус:\n$report")
disconnect()
val report: String =
"${reportJson.statistics.report}\n ${reportEpilogue()}"
project.logger.info(report)
sendTextToSlack(report)
}
}
open class NightlyReporter: DefaultTask() {
@Input
lateinit var externalMacosReport:String
@Input
lateinit var externalLinuxReport:String
@Input
lateinit var externalWindowsReport:String
@ImplicitReflectionSerializer
@TaskAction
fun report() {
val externalMacosJsonReport = loadReport("${project.rootDir.absolutePath}/$externalMacosReport")
val externalLinuxJsonReport = loadReport("${project.rootDir.absolutePath}/$externalLinuxReport")
val externalWindowsJsonReport = loadReport("${project.rootDir.absolutePath}/$externalWindowsReport")
val report = buildString {
append("Mac OS ")
appendln(externalMacosJsonReport.statistics.oneLineReport)
append("Linux ")
appendln(externalLinuxJsonReport.statistics.oneLineReport)
append("Windows ")
appendln(externalWindowsJsonReport.statistics.oneLineReport)
appendln(reportEpilogue())
}
project.logger.info(report)
sendTextToSlack(report)
}
}