Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added mordant lib, prettified terminal output #944

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ object Versions {
val influxDbClient = "2.24"
val influxDb2Client = "6.12.0"
val clikt = "4.2.2"
val mordant = "2.6.0"
val mordantCoroutines = "2.6.0"
val jacksonDatabind = "2.15.3"
val jacksonKotlin = jacksonDatabind
val jacksonYaml = jacksonDatabind
Expand Down Expand Up @@ -76,6 +78,8 @@ object Libraries {
val influxDbClient = "org.influxdb:influxdb-java:${Versions.influxDbClient}"
val influxDb2Client = "com.influxdb:influxdb-client-java:${Versions.influxDb2Client}"
val clikt = "com.github.ajalt.clikt:clikt:${Versions.clikt}"
val mordant = "com.github.ajalt.mordant:mordant:${Versions.mordant}"
val mordantCoroutines = "com.github.ajalt.mordant:mordant-coroutines:${Versions.mordantCoroutines}"
val jacksonDatabind = "com.fasterxml.jackson.core:jackson-databind:${Versions.jacksonDatabind}"
val jacksonAnnotations = "com.fasterxml.jackson.core:jackson-annotations:${Versions.jacksonAnnotations}"
val jacksonKotlin = "com.fasterxml.jackson.module:jackson-module-kotlin:${Versions.jacksonKotlin}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ data class Configuration private constructor(
val testBatchTimeoutMillis: Long,
val testOutputTimeoutMillis: Long,
val debug: Boolean,
val prettyTerminalOutput: Boolean,

val screenRecordingPolicy: ScreenRecordingPolicy,

Expand Down Expand Up @@ -72,6 +73,7 @@ data class Configuration private constructor(
"testBatchTimeoutMillis" to testBatchTimeoutMillis.toString(),
"testOutputTimeoutMillis" to testOutputTimeoutMillis.toString(),
"debug" to debug.toString(),
"prettyTerminalOutput" to prettyTerminalOutput.toString(),
"screenRecordingPolicy" to screenRecordingPolicy.toString(),
"vendorConfiguration" to vendorConfiguration.toString(),
"deviceInitializationTimeoutMillis" to deviceInitializationTimeoutMillis.toString()
Expand Down Expand Up @@ -103,6 +105,7 @@ data class Configuration private constructor(
if (testBatchTimeoutMillis != other.testBatchTimeoutMillis) return false
if (testOutputTimeoutMillis != other.testOutputTimeoutMillis) return false
if (debug != other.debug) return false
if (prettyTerminalOutput != other.prettyTerminalOutput) return false
if (screenRecordingPolicy != other.screenRecordingPolicy) return false
if (vendorConfiguration != other.vendorConfiguration) return false
if (analyticsTracking != other.analyticsTracking) return false
Expand Down Expand Up @@ -133,6 +136,7 @@ data class Configuration private constructor(
result = 31 * result + testBatchTimeoutMillis.hashCode()
result = 31 * result + testOutputTimeoutMillis.hashCode()
result = 31 * result + debug.hashCode()
result = 31 * result + prettyTerminalOutput.hashCode()
result = 31 * result + screenRecordingPolicy.hashCode()
result = 31 * result + vendorConfiguration.hashCode()
result = 31 * result + analyticsTracking.hashCode()
Expand Down Expand Up @@ -164,6 +168,7 @@ data class Configuration private constructor(
var testBatchTimeoutMillis: Long = DEFAULT_BATCH_EXECUTION_TIMEOUT_MILLIS,
var testOutputTimeoutMillis: Long = DEFAULT_OUTPUT_TIMEOUT_MILLIS,
var debug: Boolean = true,
var prettyTerminalOutput: Boolean = false,

var screenRecordingPolicy: ScreenRecordingPolicy = ScreenRecordingPolicy.ON_FAILURE,

Expand Down Expand Up @@ -196,6 +201,7 @@ data class Configuration private constructor(
testBatchTimeoutMillis = testBatchTimeoutMillis,
testOutputTimeoutMillis = testOutputTimeoutMillis,
debug = debug,
prettyTerminalOutput = prettyTerminalOutput,
screenRecordingPolicy = screenRecordingPolicy,
vendorConfiguration = vendorConfiguration,
analyticsTracking = analyticsTracking,
Expand Down
2 changes: 2 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ dependencies {
implementation(Libraries.kotlinCoroutines)
implementation(Libraries.kotlinLogging)
implementation(Libraries.logbackClassic)
implementation(Libraries.mordant)
implementation(Libraries.mordantCoroutines)
implementation(Libraries.influxDbClient)
implementation(Libraries.influxDb2Client)
implementation(Libraries.scalr)
Expand Down
1 change: 1 addition & 0 deletions core/src/main/kotlin/com/malinskiy/marathon/Marathon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class Marathon(

private fun configureLogging() {
MarathonLogging.debug = configuration.debug
MarathonLogging.prettyTerminalOutput = configuration.prettyTerminalOutput
logConfigurator.configure()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.malinskiy.marathon.execution.bundle.TestBundleIdentifier
import com.malinskiy.marathon.execution.progress.PoolProgressAccumulator
import com.malinskiy.marathon.extension.toPoolingStrategy
import com.malinskiy.marathon.log.MarathonLogging
import com.malinskiy.marathon.log.TerminalPrettyOutput
import com.malinskiy.marathon.time.Timer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
Expand Down Expand Up @@ -53,6 +54,7 @@ class Scheduler(
*/
suspend fun execute() : Boolean {
subscribeOnDevices(job)
TerminalPrettyOutput.launchAnimation()
try {
withTimeout(configuration.deviceInitializationTimeoutMillis) {
while (pools.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.malinskiy.marathon.execution.progress

import com.github.ajalt.mordant.rendering.TextColors
import com.malinskiy.marathon.actor.StateMachine
import com.malinskiy.marathon.analytics.internal.pub.Track
import com.malinskiy.marathon.config.Configuration
Expand All @@ -13,6 +14,8 @@ import com.malinskiy.marathon.execution.queue.TestAction
import com.malinskiy.marathon.execution.queue.TestEvent
import com.malinskiy.marathon.execution.queue.TestState
import com.malinskiy.marathon.log.MarathonLogging
import com.malinskiy.marathon.log.TerminalPrettyOutput
import com.malinskiy.marathon.log.TerminalPrettyOutput.terminal
import com.malinskiy.marathon.test.Test
import com.malinskiy.marathon.test.toTestName
import kotlin.math.roundToInt
Expand Down Expand Up @@ -261,6 +264,8 @@ class PoolProgressAccumulator(
}.also {
tests.putAll(it)
}
TerminalPrettyOutput.addProgressBar(poolId)
TerminalPrettyOutput.updateProgressBar(poolId, tests.size.toLong())
}

fun testStarted(device: DeviceInfo, test: Test) {
Expand All @@ -274,28 +279,31 @@ class PoolProgressAccumulator(
fun testEnded(device: DeviceInfo, testResult: TestResult, final: Boolean = false): TestAction? {
return when (testResult.status) {
TestStatus.FAILURE -> {
println("${toPercent(progress())} | [${poolId.name}]-[${device.serialNumber}] ${testResult.test.toTestName()} failed")
terminal.println("[${poolId.name}]-[${device.serialNumber}] ${testResult.test.toTestName()} ${TextColors.brightRed("failed")}")
TerminalPrettyOutput.advanceProgressBar(poolId)
transition(testResult.test, TestEvent.Failed(device, testResult)).sideffect()
}

TestStatus.PASSED -> {
println("${toPercent(progress())} | [${poolId.name}]-[${device.serialNumber}] ${testResult.test.toTestName()} passed")
terminal.println("[${poolId.name}]-[${device.serialNumber}] ${testResult.test.toTestName()} ${TextColors.brightGreen("passed")}")
TerminalPrettyOutput.advanceProgressBar(poolId)
transition(testResult.test, TestEvent.Passed(device, testResult)).sideffect()
}

TestStatus.IGNORED, TestStatus.ASSUMPTION_FAILURE -> {
println("${toPercent(progress())} | [${poolId.name}]-[${device.serialNumber}] ${testResult.test.toTestName()} ignored")
terminal.println("[${poolId.name}]-[${device.serialNumber}] ${testResult.test.toTestName()} ${TextColors.brightYellow("ignored")}")
TerminalPrettyOutput.advanceProgressBar(poolId)
transition(testResult.test, TestEvent.Passed(device, testResult)).sideffect()
}

TestStatus.INCOMPLETE -> {
println("${toPercent(progress())} | [${poolId.name}]-[${device.serialNumber}] ${testResult.test.toTestName()} incomplete")
terminal.println("[${poolId.name}]-[${device.serialNumber}] ${testResult.test.toTestName()} ${TextColors.brightBlue("incomplete")}")
TerminalPrettyOutput.advanceProgressBar(poolId)
transition(testResult.test, TestEvent.Incomplete(device, testResult, final)).sideffect()
}
}
}


/**
* Should always be called before testEnded, otherwise the FSM might transition into a terminal state prematurely
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import mu.KotlinLogging

object MarathonLogging {
var debug = true
var prettyTerminalOutput = false
private var warningPrinted = false

fun logger(func: () -> Unit): KLogger {
return logger(level = null, func = func)
return if (prettyTerminalOutput) { MordantLogger(KotlinLogging.logger(func)) }
else { logger(level = null, func = func) }
}

fun logger(name: String): KLogger {
return logger(level = null, name = name)
return if (prettyTerminalOutput) { MordantLogger(KotlinLogging.logger(name)) }
else { logger(level = null, name = name) }
}

fun logger(level: Level?, func: () -> Unit): KLogger {
Expand Down
104 changes: 104 additions & 0 deletions core/src/main/kotlin/com/malinskiy/marathon/log/MordantLogger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.malinskiy.marathon.log

import com.github.ajalt.mordant.rendering.TextColors
import com.malinskiy.marathon.log.TerminalPrettyOutput.terminal
import mu.KLogger
import mu.Marker

class MordantLogger(override val underlyingLogger: KLogger) : KLogger by underlyingLogger {

override fun trace(msg: () -> Any?) {
// terminal.println(TextColors.blue(msg.toStringSafe()))
}
override fun trace(t: Throwable?, msg: () -> Any?) {}
override fun trace(marker: mu.Marker?, msg: () -> Any?) {}
override fun trace(marker: mu.Marker?, t: Throwable?, msg: () -> Any?) {}
override fun trace(p0: String?) {}
override fun trace(p0: String?, p1: Any?) {}
override fun trace(p0: String?, p1: Any?, p2: Any?) {}
override fun trace(p0: String?, vararg p1: Any?) {}
override fun trace(p0: String?, p1: Throwable?) {}
override fun trace(p0: Marker?, p1: String?) {}
override fun trace(p0: Marker?, p1: String?, p2: Any?) {}
override fun trace(p0: Marker?, p1: String?, p2: Any?, p3: Any?) {}
override fun trace(p0: Marker?, p1: String?, vararg p2: Any?) {}
override fun trace(p0: Marker?, p1: String?, p2: Throwable?) {}

override fun debug(msg: () -> Any?) {
// terminal.println(TextColors.white(msg.toStringSafe()))
}
override fun debug(t: Throwable?, msg: () -> Any?) {}
override fun debug(marker: mu.Marker?, msg: () -> Any?) {}
override fun debug(marker: mu.Marker?, t: Throwable?, msg: () -> Any?) {}
override fun debug(format: String, vararg arguments: Any) {}
override fun debug(marker: Marker?, format: String?, vararg arguments: Any?) {}
override fun debug(marker: Marker?, format: String?, arg: Any?) {}
override fun debug(format: String?, arg: Any?) {}
override fun debug(marker: Marker?, msg: String?) {}
override fun debug(format: String?, arg1: Any?, arg2: Any?) {}
override fun debug(msg: String?, t: Throwable?) {}
override fun debug(marker: Marker?, msg: String?, t: Throwable?) {}
override fun entry(vararg argArray: Any?) {}
override fun debug(msg: String?) {}
override fun debug(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {}

override fun info(msg: () -> Any?) {
// terminal.println(TextColors.green(msg.toStringSafe()))
}
override fun info(t: Throwable?, msg: () -> Any?) {}
override fun info(marker: mu.Marker?, msg: () -> Any?) {}
override fun info(marker: mu.Marker?, t: Throwable?, msg: () -> Any?) {}
override fun info(p0: String?) {}
override fun info(p0: String?, p1: Any?) {}
override fun info(p0: String?, p1: Any?, p2: Any?) {}
override fun info(p0: String?, vararg p1: Any?) {}
override fun info(p0: String?, p1: Throwable?) {}
override fun info(p0: Marker?, p1: String?) {}
override fun info(p0: Marker?, p1: String?, p2: Any?) {}
override fun info(p0: Marker?, p1: String?, p2: Any?, p3: Any?) {}
override fun info(p0: Marker?, p1: String?, vararg p2: Any?) {}
override fun info(p0: Marker?, p1: String?, p2: Throwable?) {}

override fun warn(msg: () -> Any?) {
terminal.println(TextColors.yellow(msg.toStringSafe()))
}
override fun warn(t: Throwable?, msg: () -> Any?) {}
override fun warn(marker: mu.Marker?, msg: () -> Any?) {}
override fun warn(marker: mu.Marker?, t: Throwable?, msg: () -> Any?) {}
override fun warn(p0: String?) {}
override fun warn(p0: String?, p1: Any?) {}
override fun warn(p0: String?, vararg p1: Any?) {}
override fun warn(p0: String?, p1: Any?, p2: Any?) {}
override fun warn(p0: String?, p1: Throwable?) {}
override fun warn(p0: Marker?, p1: String?) {}
override fun warn(p0: Marker?, p1: String?, p2: Any?) {}
override fun warn(p0: Marker?, p1: String?, p2: Any?, p3: Any?) {}
override fun warn(p0: Marker?, p1: String?, vararg p2: Any?) {}
override fun warn(p0: Marker?, p1: String?, p2: Throwable?) {}

override fun error(msg: () -> Any?) {
terminal.println(TextColors.brightRed(msg.toStringSafe()))
}
override fun error(t: Throwable?, msg: () -> Any?) {}
override fun error(marker: mu.Marker?, msg: () -> Any?) {}
override fun error(marker: mu.Marker?, t: Throwable?, msg: () -> Any?) {}
override fun error(p0: String?) {}
override fun error(p0: String?, p1: Any?) {}
override fun error(p0: String?, p1: Any?, p2: Any?) {}
override fun error(p0: String?, vararg p1: Any?) {}
override fun error(p0: String?, p1: Throwable?) {}
override fun error(p0: Marker?, p1: String?) {}
override fun error(p0: Marker?, p1: String?, p2: Any?) {}
override fun error(p0: Marker?, p1: String?, p2: Any?, p3: Any?) {}
override fun error(p0: Marker?, p1: String?, vararg p2: Any?) {}
override fun error(p0: Marker?, p1: String?, p2: Throwable?) {}

@Suppress("NOTHING_TO_INLINE")
private inline fun (() -> Any?).toStringSafe(): String {
return try {
invoke().toString()
} catch (e: Exception) {
return "Log message invocation failed: $e"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.malinskiy.marathon.log

import com.github.ajalt.mordant.animation.coroutines.animateInCoroutine
import com.github.ajalt.mordant.animation.progress.MultiProgressBarAnimation
import com.github.ajalt.mordant.animation.progress.ProgressTask
import com.github.ajalt.mordant.animation.progress.addTask
import com.github.ajalt.mordant.animation.progress.advance
import com.github.ajalt.mordant.rendering.TextAlign
import com.github.ajalt.mordant.rendering.TextStyles
import com.github.ajalt.mordant.terminal.Terminal
import com.github.ajalt.mordant.widgets.Spinner
import com.github.ajalt.mordant.widgets.progress.completed
import com.github.ajalt.mordant.widgets.progress.percentage
import com.github.ajalt.mordant.widgets.progress.progressBar
import com.github.ajalt.mordant.widgets.progress.progressBarContextLayout
import com.github.ajalt.mordant.widgets.progress.progressBarLayout
import com.github.ajalt.mordant.widgets.progress.spinner
import com.github.ajalt.mordant.widgets.progress.text
import com.github.ajalt.mordant.widgets.progress.timeElapsed
import com.malinskiy.marathon.device.DevicePoolId
import java.util.concurrent.ConcurrentHashMap
import kotlin.time.ExperimentalTime


@OptIn(ExperimentalTime::class)
object TerminalPrettyOutput {
private val logger = MarathonLogging.logger("TerminalPrettyOutput")

val terminal = Terminal()
private val animation = MultiProgressBarAnimation(terminal).animateInCoroutine()
private val overallLayout = progressBarLayout(alignColumns = false) {
text { (terminal.theme.success + TextStyles.bold)("Overall") }
completed(style = terminal.theme.success)
spinner(Spinner.Dots())
timeElapsed(style = terminal.theme.warning, compact = false)
}
private val overall = animation.addTask(overallLayout, total = 0)

private val poolLayout = progressBarContextLayout<String> {
text(fps = animationFps, align = TextAlign.LEFT) { "Pool: $context" }
progressBar(width = 40)
percentage()
completed(style = terminal.theme.success)
spinner(Spinner.Dots())
timeElapsed(style = terminal.theme.info)
}
private val progressBars = ConcurrentHashMap<DevicePoolId, ProgressTask<String>>()

suspend fun launchAnimation() {
animation.execute()
}

fun addProgressBar(poolId: DevicePoolId) {
progressBars.computeIfAbsent(poolId) { id ->
animation.addTask(poolLayout, id.name)}
overall.update { total = total?.plus(1) }
}

fun updateProgressBar(poolId: DevicePoolId, t: Long) {
val pb = progressBars[poolId]?.update { total = t }
if (pb == null) logger.debug { "Progress bar ${poolId.name} not registered in animation" }
}

fun advanceProgressBar(poolId: DevicePoolId) {
val pb = progressBars[poolId]
if (pb != null) {
pb.advance()
if (pb.total == pb.completed) {
overall.advance()
}
} else {
logger.debug { "Progress bar ${poolId.name} not registered in animation" }
}
}
}
Loading
Loading