From 889ba34c2c40b81faff3fa76cbf2ef601e5dfb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20G=C3=B3ral?= Date: Wed, 16 Jun 2021 12:44:39 +0200 Subject: [PATCH 1/9] Add structural logging --- .../adapter/ExecuteAndroidTestPlan.kt | 10 +-- .../corellium/adapter/InstallAndroidApps.kt | 26 ++++-- .../corellium/adapter/InvokeAndroidDevices.kt | 33 +++---- .../kotlin/flank/corellium/api/AndroidApps.kt | 16 +++- .../flank/corellium/api/AndroidInstance.kt | 13 ++- .../flank/corellium/api/AndroidTestPlan.kt | 2 +- corellium/cli/build.gradle.kts | 1 + .../cli/RunTestCorelliumAndroidCommand.kt | 73 +++++++++++++++- .../cli/RunTestCorelliumAndroidCommandTest.kt | 85 ++++++++++++++++++- corellium/domain/build.gradle.kts | 17 ++-- .../domain/RunTestCorelliumAndroid.kt | 61 +++++++++++-- .../domain/run/test/android/step/Authorize.kt | 5 +- .../run/test/android/step/CleanUpInstances.kt | 7 +- .../run/test/android/step/CreateOutputDir.kt | 13 +-- .../run/test/android/step/DumpShards.kt | 7 +- .../run/test/android/step/ExecuteTests.kt | 20 ++--- .../domain/run/test/android/step/Finish.kt | 7 +- .../run/test/android/step/GenerateReport.kt | 7 +- .../run/test/android/step/InstallApks.kt | 8 +- .../run/test/android/step/InvokeDevices.kt | 15 +++- .../android/step/LoadPreviousDurations.kt | 20 ++--- .../run/test/android/step/ParseApkInfo.kt | 5 +- .../run/test/android/step/ParseTestCases.kt | 5 +- .../run/test/android/step/PrepareShards.kt | 5 +- .../flank/corellium/domain/util/Transform.kt | 22 ++++- .../domain/RunTestAndroidCorelliumExample.kt | 2 + ...nTestAndroidCorelliumTestMockApiAndroid.kt | 11 ++- ...nTestAndroidCorelliumTestParsingAndroid.kt | 11 ++- 28 files changed, 394 insertions(+), 113 deletions(-) diff --git a/corellium/adapter/src/main/kotlin/flank/corellium/adapter/ExecuteAndroidTestPlan.kt b/corellium/adapter/src/main/kotlin/flank/corellium/adapter/ExecuteAndroidTestPlan.kt index cd85e6abd3..a635bfe022 100644 --- a/corellium/adapter/src/main/kotlin/flank/corellium/adapter/ExecuteAndroidTestPlan.kt +++ b/corellium/adapter/src/main/kotlin/flank/corellium/adapter/ExecuteAndroidTestPlan.kt @@ -12,16 +12,10 @@ import kotlinx.coroutines.launch val executeAndroidTestPlan = AndroidTestPlan.Execute { config -> config.instances.map { (instanceId, commands: List) -> - channelFlow { - println("Getting console $instanceId") + instanceId to channelFlow { corellium.connectConsole(instanceId).apply { clear() - launch { - commands.forEach { string -> - println("Sending command: $string") - sendCommand(string) - } - } + launch { commands.forEach { string -> sendCommand(string) } } launch { flowLogs().collect(channel::send) } waitForIdle(10_000) } diff --git a/corellium/adapter/src/main/kotlin/flank/corellium/adapter/InstallAndroidApps.kt b/corellium/adapter/src/main/kotlin/flank/corellium/adapter/InstallAndroidApps.kt index 73d73f8855..91ab6ce1fb 100644 --- a/corellium/adapter/src/main/kotlin/flank/corellium/adapter/InstallAndroidApps.kt +++ b/corellium/adapter/src/main/kotlin/flank/corellium/adapter/InstallAndroidApps.kt @@ -1,6 +1,8 @@ package flank.corellium.adapter import flank.corellium.api.AndroidApps +import flank.corellium.api.AndroidApps.Event.Apk +import flank.corellium.api.AndroidApps.Event.Connecting import flank.corellium.client.agent.disconnect import flank.corellium.client.agent.uploadFile import flank.corellium.client.console.close @@ -9,6 +11,9 @@ import flank.corellium.client.core.connectAgent import flank.corellium.client.core.connectConsole import flank.corellium.client.core.getAllProjects import flank.corellium.client.core.getProjectInstancesList +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch @@ -19,20 +24,30 @@ private const val PATH_TO_UPLOAD = "/sdcard" fun installAndroidApps( projectName: String ) = AndroidApps.Install { apps -> + channelFlow { + install(projectName, apps).join() + } +} + +private suspend fun SendChannel.install( + projectName: String, + appsList: List +): Job = corellium.launch { val projectId = corellium.getAllProjects().first { it.name == projectName }.id val instances = corellium.getProjectInstancesList(projectId).associateBy { it.id } - apps.forEach { apps -> + appsList.forEach { apps -> val instance = instances.getValue(apps.instanceId) - println("Connecting agent for ${apps.instanceId}") + send(Connecting.Agent(apps.instanceId)) + val agentInfo = requireNotNull(instance.agent?.info) { "Cannot connect to the agent, no agent info for instance ${instance.name} with id: ${instance.id}" } val agent = corellium.connectAgent(agentInfo) - println("Connecting console for ${apps.instanceId}") + send(Connecting.Console(apps.instanceId)) val console = corellium.connectConsole(instance.id) // Disable system logging @@ -42,10 +57,10 @@ fun installAndroidApps( val file = File(localPath) val remotePath = "$PATH_TO_UPLOAD/${file.name}" - println("Uploading apk $localPath") + send(Apk.Uploading(localPath)) agent.uploadFile(remotePath, file.readBytes()) - println("Installing apk $localPath") + send(Apk.Installing(localPath)) console.sendCommand( // Current solution is enough for the MVP. // Fixme: Find better solution for recognizing test apk. @@ -59,4 +74,3 @@ fun installAndroidApps( agent.disconnect() } } -} diff --git a/corellium/adapter/src/main/kotlin/flank/corellium/adapter/InvokeAndroidDevices.kt b/corellium/adapter/src/main/kotlin/flank/corellium/adapter/InvokeAndroidDevices.kt index 93b5660b8d..905d817861 100644 --- a/corellium/adapter/src/main/kotlin/flank/corellium/adapter/InvokeAndroidDevices.kt +++ b/corellium/adapter/src/main/kotlin/flank/corellium/adapter/InvokeAndroidDevices.kt @@ -1,6 +1,7 @@ package flank.corellium.adapter import flank.corellium.api.AndroidInstance +import flank.corellium.api.AndroidInstance.Event import flank.corellium.client.core.createNewInstance import flank.corellium.client.core.getAllProjects import flank.corellium.client.core.getProjectInstancesList @@ -22,7 +23,7 @@ private const val SCREEN = "720x1280:280" fun invokeAndroidDevices( projectName: String, ) = AndroidInstance.Invoke { config -> - channelFlow { + channelFlow { val projectId = getProjectId(projectName) val instances = getCreatedInstances(projectId, config.amount) startNotRunningInstances(instances) @@ -42,7 +43,7 @@ fun invokeAndroidDevices( ) } - waitForInstances(channel, ids) + waitForInstances(ids) } } @@ -50,6 +51,8 @@ fun invokeAndroidDevices( // Try to keep the private methods not dependent on each other. // Otherwise the implementation will go complicated. +private typealias OutputChannel = SendChannel + /** * @return The project id for given project name. */ @@ -60,39 +63,39 @@ private suspend fun getProjectId(name: String) = * Get all instances that was already created for flank. * @return [List] of [Instance] where [List.size] <= [amount] */ -private suspend fun getCreatedInstances( +private suspend fun OutputChannel.getCreatedInstances( projectId: String, amount: Int ): List = corellium - .also { println("Getting instances already created by flank.") } + .also { send(Event.GettingAlreadyCreated) } .getProjectInstancesList(projectId) .filter { it.name.startsWith(FLANK_INSTANCE_NAME_PREFIX) } .filter { it.state !in Instance.State.unavailable } .take(amount) - .apply { println("Obtained $size already created devices") } + .apply { send(Event.Obtained(size)) } /** * Start all given instances with status different than "on". */ -private suspend fun startNotRunningInstances( +private suspend fun OutputChannel.startNotRunningInstances( instances: List ): Unit = instances .filter { it.state != Instance.State.ON } - .apply { if (isNotEmpty()) println("Starting not running $size instances.") } + .apply { if (isNotEmpty()) send(Event.Starting(size)) } .forEach { instance -> corellium.startInstance(instance.id) - println(instance) + send(Event.Started(instance.id, instance.name)) } /** * Create new instances basing on given indexes. */ -private suspend fun createInstances( +private suspend fun OutputChannel.createInstances( projectId: String, indexes: List, gpuAcceleration: Boolean, ) = indexes - .apply { println("Creating additional ${indexes.size} instances. Connecting to the agents may take longer.") } + .apply { send(Event.Creating(size)) } .map { index -> corellium.createNewInstance( Instance( @@ -131,17 +134,15 @@ private val Instance.index get() = name.removePrefix(FLANK_INSTANCE_NAME_PREFIX) /** * Block the execution and wait until each instance change the status to "on". */ -private suspend fun waitForInstances( - channel: SendChannel, +private suspend fun OutputChannel.waitForInstances( ids: List ) = coroutineScope { - println("Wait until all instances are ready...") + send(Event.Waiting) ids.map { id -> launch { corellium.waitUntilInstanceIsReady(id) - println("ready: $id") - channel.send(id) + send(Event.Ready(id)) } }.joinAll() - println("All instances invoked and ready to use.") + send(Event.AllReady) } diff --git a/corellium/api/src/main/kotlin/flank/corellium/api/AndroidApps.kt b/corellium/api/src/main/kotlin/flank/corellium/api/AndroidApps.kt index d2712b4fdc..c80cc49881 100644 --- a/corellium/api/src/main/kotlin/flank/corellium/api/AndroidApps.kt +++ b/corellium/api/src/main/kotlin/flank/corellium/api/AndroidApps.kt @@ -1,6 +1,6 @@ package flank.corellium.api -import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow /** * [AndroidApps] represents a bunch of apk files related to the testing instance. @@ -16,5 +16,17 @@ data class AndroidApps( /** * Install android apps on the specified instances. */ - fun interface Install : (List) -> Job + fun interface Install : (List) -> Flow + + sealed class Event { + object Connecting { + data class Agent(val instanceId: String) : Event() + data class Console(val instanceId: String) : Event() + } + + object Apk { + data class Uploading(val path: String) : Event() + data class Installing(val path: String) : Event() + } + } } diff --git a/corellium/api/src/main/kotlin/flank/corellium/api/AndroidInstance.kt b/corellium/api/src/main/kotlin/flank/corellium/api/AndroidInstance.kt index b21e6fcb5e..384c130554 100644 --- a/corellium/api/src/main/kotlin/flank/corellium/api/AndroidInstance.kt +++ b/corellium/api/src/main/kotlin/flank/corellium/api/AndroidInstance.kt @@ -27,5 +27,16 @@ object AndroidInstance { * * @return List of invoked device ids. */ - fun interface Invoke : (Config) -> Flow + fun interface Invoke : (Config) -> Flow + + sealed class Event { + object GettingAlreadyCreated : Event() + class Obtained(val size: Int) : Event() + class Starting(val size: Int) : Event() + class Started(val id: String, val name: String) : Event() + class Creating(val size: Int) : Event() + object Waiting : Event() + class Ready(val id: String) : Event() + object AllReady : Event() + } } diff --git a/corellium/api/src/main/kotlin/flank/corellium/api/AndroidTestPlan.kt b/corellium/api/src/main/kotlin/flank/corellium/api/AndroidTestPlan.kt index 6066bfcd6c..e346f2cf81 100644 --- a/corellium/api/src/main/kotlin/flank/corellium/api/AndroidTestPlan.kt +++ b/corellium/api/src/main/kotlin/flank/corellium/api/AndroidTestPlan.kt @@ -19,7 +19,7 @@ object AndroidTestPlan { /** * Execute tests on android instances using specified configuration. */ - fun interface Execute : (Config) -> List> + fun interface Execute : (Config) -> List>> } private typealias InstanceId = String diff --git a/corellium/cli/build.gradle.kts b/corellium/cli/build.gradle.kts index 5b4c0d5c10..c46dbc28aa 100644 --- a/corellium/cli/build.gradle.kts +++ b/corellium/cli/build.gradle.kts @@ -24,4 +24,5 @@ dependencies { implementation(Dependencies.JACKSON_YAML) implementation(Dependencies.JACKSON_XML) testImplementation(Dependencies.JUNIT) + testImplementation(Dependencies.MOCKK) } diff --git a/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt b/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt index 3f1e215623..2466521a3b 100644 --- a/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt +++ b/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonProperty import flank.apk.Apk +import flank.corellium.api.AndroidApps +import flank.corellium.api.AndroidInstance import flank.corellium.cli.RunTestCorelliumAndroidCommand.Config import flank.corellium.cli.util.ConfigMap import flank.corellium.cli.util.emptyConfigMap @@ -12,7 +14,24 @@ import flank.corellium.cli.util.merge import flank.corellium.corelliumApi import flank.corellium.domain.RunTestCorelliumAndroid import flank.corellium.domain.RunTestCorelliumAndroid.Args +import flank.corellium.domain.RunTestCorelliumAndroid.Authorize +import flank.corellium.domain.RunTestCorelliumAndroid.CleanUp +import flank.corellium.domain.RunTestCorelliumAndroid.CompleteTests +import flank.corellium.domain.RunTestCorelliumAndroid.DumpShards +import flank.corellium.domain.RunTestCorelliumAndroid.ExecuteTests +import flank.corellium.domain.RunTestCorelliumAndroid.GenerateReport +import flank.corellium.domain.RunTestCorelliumAndroid.InstallApks +import flank.corellium.domain.RunTestCorelliumAndroid.InvokeDevices +import flank.corellium.domain.RunTestCorelliumAndroid.LoadPreviousDurations +import flank.corellium.domain.RunTestCorelliumAndroid.OutputDir +import flank.corellium.domain.RunTestCorelliumAndroid.ParseApkInfo +import flank.corellium.domain.RunTestCorelliumAndroid.ParseTestCases +import flank.corellium.domain.RunTestCorelliumAndroid.PrepareShards import flank.corellium.domain.invoke +import flank.log.Event.Start +import flank.log.buildFormatter +import flank.log.output +import flank.instrument.log.Instrument import flank.junit.JUnit import picocli.CommandLine @@ -134,12 +153,14 @@ class RunTestCorelliumAndroidCommand : override val api by lazy { corelliumApi(config.project!!) } - override val apk = Apk.Api() + override val apk by lazy { Apk.Api() } - override val junit = JUnit.Api() + override val junit by lazy { JUnit.Api() } override val args by lazy { createArgs() } + override val out by lazy { format.output } + override fun run() = invoke() } @@ -166,3 +187,51 @@ private fun RunTestCorelliumAndroidCommand.createArgs() = Args( gpuAcceleration = config.gpuAcceleration!!, scanPreviousDurations = config.scanPreviousDurations!!, ) + +internal val format = buildFormatter { + + Start(Authorize) { "* Authorizing" } + Start(CleanUp) { "* Cleaning instances" } + Start(OutputDir) { "* Preparing output directory" } + Start(DumpShards) { "* Dumping shards" } + Start(ExecuteTests) { "* Executing tests" } + Start(CompleteTests) { "* Finish" } + Start(GenerateReport) { "* Generating report" } + Start(InstallApks) { "* Installing apks" } + Start(InvokeDevices) { "* Invoking devices" } + Start(LoadPreviousDurations) { "* Obtaining previous test cases durations" } + Start(ParseApkInfo) { "* Parsing apk info" } + Start(ParseTestCases) { "* Parsing test cases" } + Start(PrepareShards) { "* Calculating shards" } + + LoadPreviousDurations.Searching { "Searching in $this JUnitReport.xml files..." } + LoadPreviousDurations.Summary::class { "For $required test cases, found $matching matching and $unknown unknown" } + InstallApks.Status { + when (this) { + is AndroidApps.Event.Connecting.Agent -> "Connecting agent for $instanceId" + is AndroidApps.Event.Connecting.Console -> "Connecting console for $instanceId" + is AndroidApps.Event.Apk.Uploading -> "Uploading apk $path" + is AndroidApps.Event.Apk.Installing -> "Installing apk $path" + } + } + InvokeDevices.Status { + when (this) { + is AndroidInstance.Event.GettingAlreadyCreated -> "Getting instances already created by flank." + is AndroidInstance.Event.Obtained -> "Obtained $size already created devices" + is AndroidInstance.Event.Starting -> "Starting not running $size instances." + is AndroidInstance.Event.Started -> "$id - $name" + is AndroidInstance.Event.Creating -> "Creating additional $size instances. Connecting to the agents may take longer." + is AndroidInstance.Event.Waiting -> "Wait until all instances are ready..." + is AndroidInstance.Event.Ready -> "ready: $id" + is AndroidInstance.Event.AllReady -> "All instances invoked and ready to use." + } + } + ExecuteTests.Status::class { + when (val status = status) { + is Instrument.Status -> "$id: " + status.details.run { "$className#$testName" } + " - " + status.code + else -> null + } + } + RunTestCorelliumAndroid.Created { "Created $path" } + RunTestCorelliumAndroid.AlreadyExist { "Already exist $path" } +} diff --git a/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt b/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt index 57c9ad5351..c18175222b 100644 --- a/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt +++ b/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt @@ -1,9 +1,28 @@ package flank.corellium.cli +import flank.corellium.api.AndroidApps +import flank.corellium.api.AndroidInstance import flank.corellium.api.Authorization +import flank.corellium.domain.RunTestCorelliumAndroid import flank.corellium.domain.RunTestCorelliumAndroid.Args -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotEquals +import flank.corellium.domain.RunTestCorelliumAndroid.Authorize +import flank.corellium.domain.RunTestCorelliumAndroid.CleanUp +import flank.corellium.domain.RunTestCorelliumAndroid.CompleteTests +import flank.corellium.domain.RunTestCorelliumAndroid.DumpShards +import flank.corellium.domain.RunTestCorelliumAndroid.ExecuteTests +import flank.corellium.domain.RunTestCorelliumAndroid.GenerateReport +import flank.corellium.domain.RunTestCorelliumAndroid.InstallApks +import flank.corellium.domain.RunTestCorelliumAndroid.InvokeDevices +import flank.corellium.domain.RunTestCorelliumAndroid.LoadPreviousDurations +import flank.corellium.domain.RunTestCorelliumAndroid.OutputDir +import flank.corellium.domain.RunTestCorelliumAndroid.ParseApkInfo +import flank.corellium.domain.RunTestCorelliumAndroid.ParseTestCases +import flank.corellium.domain.RunTestCorelliumAndroid.PrepareShards +import flank.log.Event +import flank.log.event +import flank.log.invoke +import flank.instrument.log.Instrument +import org.junit.Assert.* import org.junit.Test import picocli.CommandLine import java.io.File @@ -172,4 +191,66 @@ password: $password assertEquals(expected, actual) } + + /** + * Test is checking if all specified events have registered dedicated formatters. + */ + @Test + fun outputTest() { + // ======================== GIVEN ======================== + + val events = listOf( + Authorize event Event.Start, + CleanUp event Event.Start, + OutputDir event Event.Start, + DumpShards event Event.Start, + ExecuteTests event Event.Start, + CompleteTests event Event.Start, + GenerateReport event Event.Start, + InstallApks event Event.Start, + InvokeDevices event Event.Start, + LoadPreviousDurations event Event.Start, + ParseApkInfo event Event.Start, + ParseTestCases event Event.Start, + PrepareShards event Event.Start, + Unit event LoadPreviousDurations.Searching(5), + Unit event LoadPreviousDurations.Summary(1, 2, 3), + Unit event InstallApks.Status(AndroidApps.Event.Connecting.Agent("123456")), + Unit event InstallApks.Status(AndroidApps.Event.Connecting.Console("123456")), + Unit event InstallApks.Status(AndroidApps.Event.Apk.Uploading("path/to/apk.apk")), + Unit event InstallApks.Status(AndroidApps.Event.Apk.Installing("path/to/apk.apk")), + Unit event InvokeDevices.Status(AndroidInstance.Event.GettingAlreadyCreated), + Unit event InvokeDevices.Status(AndroidInstance.Event.Obtained(5)), + Unit event InvokeDevices.Status(AndroidInstance.Event.Starting(6)), + Unit event InvokeDevices.Status(AndroidInstance.Event.Started("123456", "AndroidDevice")), + Unit event InvokeDevices.Status(AndroidInstance.Event.Creating(7)), + Unit event InvokeDevices.Status(AndroidInstance.Event.Waiting), + Unit event InvokeDevices.Status(AndroidInstance.Event.Ready("123456")), + Unit event ExecuteTests.Status( + id = "123456", + status = Instrument.Status( + code = 0, + startTime = 1, + endTime = 2, + details = Instrument.Status.Details(emptyMap(), "Class", "Test", null) + ) + ), + Unit event RunTestCorelliumAndroid.Created(File("path/to/apk.apk")), + Unit event RunTestCorelliumAndroid.AlreadyExist(File("path/to/apk.apk")), + ) + + // ======================== WHEN ======================== + + val nulls = events + .associateWith { format(it) } + .onEach { (_, string) -> println(string) } + .filterValues { it == null } + + // ======================== THEN ======================== + + assertTrue( + nulls.keys.joinToString("\n", "Missing formatters for:\n"), + nulls.isEmpty() + ) + } } diff --git a/corellium/domain/build.gradle.kts b/corellium/domain/build.gradle.kts index 1bd6db1c50..05510c20de 100644 --- a/corellium/domain/build.gradle.kts +++ b/corellium/domain/build.gradle.kts @@ -15,14 +15,15 @@ tasks.withType { kotlinOptions.jvmTarget = "1.8" } dependencies { implementation(Dependencies.KOTLIN_COROUTINES_CORE) - implementation(project(":corellium:api")) - implementation(project(":tool:apk")) - implementation(project(":tool:shard:calculate")) - implementation(project(":tool:shard:obfuscate")) - implementation(project(":tool:shard:dump")) - implementation(project(":tool:instrument:command")) - implementation(project(":tool:instrument:log")) - implementation(project(":tool:junit")) + api(project(":corellium:api")) + api(project(":tool:apk")) + api(project(":tool:shard:calculate")) + api(project(":tool:shard:obfuscate")) + api(project(":tool:shard:dump")) + api(project(":tool:instrument:command")) + api(project(":tool:instrument:log")) + api(project(":tool:junit")) + api(project(":tool:log")) testImplementation(Dependencies.JUNIT) testImplementation(project(":corellium:adapter")) diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/RunTestCorelliumAndroid.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/RunTestCorelliumAndroid.kt index 59beaf6a9f..603c1a3f49 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/RunTestCorelliumAndroid.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/RunTestCorelliumAndroid.kt @@ -1,8 +1,12 @@ package flank.corellium.domain import flank.apk.Apk +import flank.corellium.api.AndroidApps +import flank.corellium.api.AndroidInstance import flank.corellium.api.Authorization import flank.corellium.api.CorelliumApi +import flank.corellium.domain.RunTestCorelliumAndroid.Args.DefaultOutputDir +import flank.corellium.domain.RunTestCorelliumAndroid.Args.DefaultOutputDir.new import flank.corellium.domain.RunTestCorelliumAndroid.Context import flank.corellium.domain.RunTestCorelliumAndroid.State import flank.corellium.domain.run.test.android.step.authorize @@ -18,13 +22,18 @@ import flank.corellium.domain.run.test.android.step.loadPreviousDurations import flank.corellium.domain.run.test.android.step.parseApksInfo import flank.corellium.domain.run.test.android.step.parseTestCasesFromApks import flank.corellium.domain.run.test.android.step.prepareShards -import flank.corellium.domain.util.CreateTransformation +import flank.corellium.domain.util.Transform import flank.corellium.domain.util.execute +import flank.corellium.domain.util.injectLogger import flank.instrument.log.Instrument import flank.junit.JUnit +import flank.log.Event +import flank.log.Logger +import flank.log.Output import flank.shard.Shard import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking +import java.io.File import java.lang.System.currentTimeMillis import java.text.SimpleDateFormat @@ -37,7 +46,7 @@ object RunTestCorelliumAndroid { * The context of android test execution on corellium. * Is providing all necessary data and operations for [Context.invoke]. */ - interface Context { + interface Context : Logger { val api: CorelliumApi val apk: Apk.Api val junit: JUnit.Api @@ -127,13 +136,51 @@ object RunTestCorelliumAndroid { private val defaultPreviousDurations = emptyMap().withDefault { DEFAULT_TEST_CASE_DURATION } - /** - * The reference to the step factory. - * Invoke it to generate new execution step. - */ - internal val step = CreateTransformation() + // Types + + object Authorize + object CleanUp + object OutputDir + object DumpShards + object ExecuteTests { + data class Status(val id: String, val status: Instrument) : Event.Data + } + + object CompleteTests + object GenerateReport + object InstallApks { + object Status : Event.Type + } + + object InvokeDevices { + object Status : Event.Type + } + + object LoadPreviousDurations { + object Searching : Event.Type + data class Summary(val unknown: Int, val matching: Int, val required: Int) : Event.Data + } + + object ParseApkInfo + object ParseTestCases + object PrepareShards + + // Common Events + + object Created : Event.Type + object AlreadyExist : Event.Type } +/** + * The reference to the step factory. + * Invoke it to generate new execution step. + */ +internal fun Context.step( + type: Any = Unit, + transform: suspend State.(Output) -> State +): Transform = + injectLogger(type, transform) + operator fun Context.invoke(): Unit = runBlocking { State() execute flowOf( authorize(), diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/Authorize.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/Authorize.kt index 1445ceaa76..977c2cc2c1 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/Authorize.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/Authorize.kt @@ -1,12 +1,13 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.Authorize +import flank.corellium.domain.step /** * The Initial step required to perform further remote calls to Corellium API. */ -internal fun RunTestCorelliumAndroid.Context.authorize() = RunTestCorelliumAndroid.step { - println("* Authorizing") +internal fun RunTestCorelliumAndroid.Context.authorize() = step(Authorize) { api.authorize(args.credentials).join() this } diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/CleanUpInstances.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/CleanUpInstances.kt index f3a20b1882..de32f597ac 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/CleanUpInstances.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/CleanUpInstances.kt @@ -1,12 +1,13 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.CleanUp +import flank.corellium.domain.step /** * The step is cleaning instances from changes applied during test execution. */ -internal fun RunTestCorelliumAndroid.Context.cleanUpInstances() = RunTestCorelliumAndroid.step { - println("* Cleaning instances") - println("TODO") // TODO +internal fun RunTestCorelliumAndroid.Context.cleanUpInstances() = step(CleanUp) { out -> + out("TODO") this } diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/CreateOutputDir.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/CreateOutputDir.kt index eddb2b592d..b5585d8804 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/CreateOutputDir.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/CreateOutputDir.kt @@ -1,25 +1,28 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.AlreadyExist +import flank.corellium.domain.RunTestCorelliumAndroid.Created +import flank.corellium.domain.RunTestCorelliumAndroid.OutputDir +import flank.corellium.domain.step import java.io.File /** * The step is creating the output directory for execution results. */ -internal fun RunTestCorelliumAndroid.Context.createOutputDir() = RunTestCorelliumAndroid.step { - println("* Preparing output directory") +internal fun RunTestCorelliumAndroid.Context.createOutputDir() = step(OutputDir) { out -> require(args.outputDir.isNotEmpty()) val dir = File(args.outputDir) when { !dir.exists() -> { dir.mkdirs() - println("Created ${dir.absolutePath}") + Created(dir).out() } !dir.isDirectory -> { - throw IllegalStateException("Cannot create output directory, file ${dir.absolutePath} already exist") + throw IllegalStateException("Cannot create output directory, file ${dir.path} already exist") } else -> { - println(println("Already exist ${dir.absolutePath}")) + AlreadyExist(dir).out() } } this diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/DumpShards.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/DumpShards.kt index 2e20a1b1a0..8f62b1e6df 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/DumpShards.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/DumpShards.kt @@ -1,6 +1,8 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.DumpShards +import flank.corellium.domain.step import flank.shard.dumpTo import flank.shard.obfuscate import java.io.File @@ -13,14 +15,13 @@ import java.io.File * * [RunTestCorelliumAndroid.Context.createOutputDir] */ -internal fun RunTestCorelliumAndroid.Context.dumpShards() = RunTestCorelliumAndroid.step { - println("* Dumping shards") +internal fun RunTestCorelliumAndroid.Context.dumpShards() = step(DumpShards) { out -> val file = File(args.outputDir, ANDROID_SHARD_FILENAME) when (args.obfuscateDumpShards) { true -> obfuscate(shards) else -> shards } dumpTo file.writer() - println("Created ${file.absolutePath}") + RunTestCorelliumAndroid.Created(file).out() this } diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ExecuteTests.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ExecuteTests.kt index f94f8e5d4b..96b39ed7b9 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ExecuteTests.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ExecuteTests.kt @@ -2,8 +2,9 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.api.AndroidTestPlan import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.ExecuteTests +import flank.corellium.domain.step import flank.instrument.command.formatAmInstrumentCommand -import flank.instrument.log.Instrument import flank.instrument.log.parseAdbInstrumentLog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -27,22 +28,15 @@ import kotlinx.coroutines.flow.toList * updates: * * [RunTestCorelliumAndroid.State.shards] */ -internal fun RunTestCorelliumAndroid.Context.executeTests() = RunTestCorelliumAndroid.step { - println("* Executing tests") +internal fun RunTestCorelliumAndroid.Context.executeTests() = step(ExecuteTests) { out -> val testPlan: AndroidTestPlan.Config = prepareTestPlan() val list = coroutineScope { - api.executeTest(testPlan).mapIndexed { index, flow -> + api.executeTest(testPlan).map { (id, flow) -> async { - flow - .flowOn(Dispatchers.IO) - .dropWhile { !it.startsWith("INSTRUMENTATION_STATUS") } + flow.flowOn(Dispatchers.IO) + .dropWhile { line -> !line.startsWith("INSTRUMENTATION_STATUS") } .parseAdbInstrumentLog() - .onEach { status -> - if (status is Instrument.Status) { - val line = "$index: " + status.details.run { "$className#$testName" } + " - " + status.code - println(line) - } - } + .onEach { status -> ExecuteTests.Status(id, status).out() } .toList() } }.awaitAll() diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/Finish.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/Finish.kt index 8bbae24646..c66f0c4e74 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/Finish.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/Finish.kt @@ -1,11 +1,10 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.CompleteTests +import flank.corellium.domain.step /** * The final step, notifies that execution completes without exceptions. */ -internal fun finish() = RunTestCorelliumAndroid.step { - println("* Finish") - this -} +internal fun RunTestCorelliumAndroid.Context.finish() = step(CompleteTests) { this } diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/GenerateReport.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/GenerateReport.kt index 400bffcd43..ed0f52277f 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/GenerateReport.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/GenerateReport.kt @@ -1,6 +1,8 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.GenerateReport +import flank.corellium.domain.step import flank.instrument.log.Instrument import flank.junit.JUnit import flank.junit.generateJUnitReport @@ -16,14 +18,13 @@ import java.io.File * * [RunTestCorelliumAndroid.Context.createOutputDir] * */ -internal fun RunTestCorelliumAndroid.Context.generateReport() = RunTestCorelliumAndroid.step { - println("* Generating report") +internal fun RunTestCorelliumAndroid.Context.generateReport() = step(GenerateReport) { out -> val file = File(args.outputDir, JUnit.REPORT_FILE_NAME) testResult .prepareInputForJUnit() .generateJUnitReport() .writeAsXml(file.bufferedWriter()) - println("Created ${file.absolutePath}") + RunTestCorelliumAndroid.Created(file).out() this } diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InstallApks.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InstallApks.kt index 0ceeff3bc5..400b9e5827 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InstallApks.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InstallApks.kt @@ -2,8 +2,11 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.api.AndroidApps import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.InstallApks +import flank.corellium.domain.step import flank.shard.Shard import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect /** * The step is installing required software on android instances. @@ -13,11 +16,10 @@ import kotlinx.coroutines.delay * * [RunTestCorelliumAndroid.Context.prepareShards] * * [RunTestCorelliumAndroid.Context.invokeDevices] */ -internal fun RunTestCorelliumAndroid.Context.installApks() = RunTestCorelliumAndroid.step { - println("* Installing apks") +internal fun RunTestCorelliumAndroid.Context.installApks() = step(InstallApks) { out -> require(shards.size <= ids.size) { "Not enough instances, required ${shards.size} but was $ids.size" } val apks = prepareApkToInstall() - api.installAndroidApps(apks).join() + api.installAndroidApps(apks).collect { event -> event.out() } // If tests will be executed too fast just after the // app installed, the instrumentation will fail delay(8_000) diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt index 3a59ea6040..e93521fd22 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt @@ -2,6 +2,11 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.api.AndroidInstance import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.InvokeDevices +import flank.corellium.domain.step +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.toList /** @@ -14,11 +19,15 @@ import kotlinx.coroutines.flow.toList * updates: * * [RunTestCorelliumAndroid.State.ids] */ -internal fun RunTestCorelliumAndroid.Context.invokeDevices() = RunTestCorelliumAndroid.step { - println("* Invoking devices") +internal fun RunTestCorelliumAndroid.Context.invokeDevices() = step(InvokeDevices) { out -> val config = AndroidInstance.Config( amount = shards.size, gpuAcceleration = args.gpuAcceleration ) - copy(ids = api.invokeAndroidDevices(config).toList()) + copy(ids = api.invokeAndroidDevices(config) + .onEach { event -> out(event) } + .filterIsInstance() + .map { instance -> instance.id } + .toList() + ) } diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/LoadPreviousDurations.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/LoadPreviousDurations.kt index 7e8cdba77a..be8ea976a2 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/LoadPreviousDurations.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/LoadPreviousDurations.kt @@ -2,6 +2,8 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.domain.RunTestCorelliumAndroid import flank.corellium.domain.RunTestCorelliumAndroid.Args.DefaultOutputDir +import flank.corellium.domain.RunTestCorelliumAndroid.LoadPreviousDurations +import flank.corellium.domain.step import flank.junit.calculateTestCaseDurations /** @@ -14,9 +16,7 @@ import flank.junit.calculateTestCaseDurations * updates: * * [RunTestCorelliumAndroid.State.previousDurations] */ -internal fun RunTestCorelliumAndroid.Context.loadPreviousDurations() = RunTestCorelliumAndroid.step { - println("* Obtaining previous test cases durations") - +internal fun RunTestCorelliumAndroid.Context.loadPreviousDurations() = step(LoadPreviousDurations) { out -> val directoryToScan: String = if (args.outputDir.startsWith(DefaultOutputDir.ROOT)) DefaultOutputDir.ROOT else args.outputDir @@ -24,19 +24,19 @@ internal fun RunTestCorelliumAndroid.Context.loadPreviousDurations() = RunTestCo copy( previousDurations = junit.parseTestResults(directoryToScan) .take(args.scanPreviousDurations).toList() - .apply { println("Searching in $size JUnitReport.xml files...") } + .apply { LoadPreviousDurations.Searching(size).out() } .flatten() .calculateTestCaseDurations() .withDefault { previousDurations.getValue(it) } - .also { durations -> printStats(durations.keys) } + .also { durations -> printStats(durations.keys).out() } ) } -private fun RunTestCorelliumAndroid.State.printStats(obtainedDurations: Set) { +private fun RunTestCorelliumAndroid.State.printStats(obtainedDurations: Set): LoadPreviousDurations.Summary { val testCasesNames = testCases.flatMap { (_, cases) -> cases }.toSet() - val unknown = (obtainedDurations - testCasesNames).size - val matching = obtainedDurations.size - unknown - val required = testCasesNames.size + val unknown: Int = (obtainedDurations - testCasesNames).size + val matching: Int = obtainedDurations.size - unknown + val required: Int = testCasesNames.size - println("For $required test cases, found $matching matching and $unknown unknown") + return LoadPreviousDurations.Summary(unknown, matching, required) } diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ParseApkInfo.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ParseApkInfo.kt index f308064913..084d8ae009 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ParseApkInfo.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ParseApkInfo.kt @@ -1,6 +1,8 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.ParseApkInfo +import flank.corellium.domain.step /** * The step is parsing information from app and test apk files. @@ -9,8 +11,7 @@ import flank.corellium.domain.RunTestCorelliumAndroid * * [RunTestCorelliumAndroid.State.packageNames] * * [RunTestCorelliumAndroid.State.testRunners] */ -internal fun RunTestCorelliumAndroid.Context.parseApksInfo() = RunTestCorelliumAndroid.step { - println("* Parsing apk info") +internal fun RunTestCorelliumAndroid.Context.parseApksInfo() = step(ParseApkInfo) { val packageNames = mutableMapOf() val testRunners = mutableMapOf() args.apks.map { app -> diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ParseTestCases.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ParseTestCases.kt index d0b325d41a..1bf2f93dd5 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ParseTestCases.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/ParseTestCases.kt @@ -1,6 +1,8 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.ParseTestCases +import flank.corellium.domain.step /** * The step is parsing the test methods from each test apk. @@ -8,8 +10,7 @@ import flank.corellium.domain.RunTestCorelliumAndroid * updates: * * [RunTestCorelliumAndroid.State.testCases] */ -internal fun RunTestCorelliumAndroid.Context.parseTestCasesFromApks() = RunTestCorelliumAndroid.step { - println("* Parsing test cases") +internal fun RunTestCorelliumAndroid.Context.parseTestCasesFromApks() = step(ParseTestCases) { copy( testCases = args.apks // Get the list of paths to test apks diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/PrepareShards.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/PrepareShards.kt index b16cfb90c3..4377dd4173 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/PrepareShards.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/PrepareShards.kt @@ -1,6 +1,8 @@ package flank.corellium.domain.run.test.android.step import flank.corellium.domain.RunTestCorelliumAndroid +import flank.corellium.domain.RunTestCorelliumAndroid.PrepareShards +import flank.corellium.domain.step import flank.shard.Shard import flank.shard.calculateShards @@ -13,8 +15,7 @@ import flank.shard.calculateShards * updates: * * [RunTestCorelliumAndroid.State.shards] */ -internal fun RunTestCorelliumAndroid.Context.prepareShards() = RunTestCorelliumAndroid.step { - println("* Calculating shards") +internal fun RunTestCorelliumAndroid.Context.prepareShards() = step(PrepareShards) { copy( shards = calculateShards( apps = prepareDataForSharding( diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt index e24822ffb3..311e80dd2e 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt @@ -1,5 +1,9 @@ package flank.corellium.domain.util +import flank.log.Event +import flank.log.Logger +import flank.log.Output +import flank.log.event import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.fold @@ -20,4 +24,20 @@ internal class CreateTransformation : (Transform) -> Transform by { it /** * Type-alias for suspendable transforming operation */ -private typealias Transform = suspend S.() -> S +typealias Transform = suspend S.() -> S + +/** + * Inject log output to the transform function. + */ +internal fun Logger.injectLogger( + type: Any = Unit, + transform: suspend S.(Output) -> S +): Transform { + val out: Output = { type.event(this).out() } + return { + Event.Start.out() + val result = transform(this, out) + Event.Stop.out() + result + } +} diff --git a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumExample.kt b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumExample.kt index a61ccde09b..01a3ff2ba7 100644 --- a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumExample.kt +++ b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumExample.kt @@ -2,6 +2,7 @@ package flank.corellium.domain import flank.apk.Apk import flank.corellium.corelliumApi +import flank.log.Output import flank.junit.JUnit object RunTestAndroidCorelliumExample : RunTestCorelliumAndroid.Context { @@ -13,6 +14,7 @@ object RunTestAndroidCorelliumExample : RunTestCorelliumAndroid.Context { apks = fewTestArtifactsApks(APK_PATH_MAIN), maxShardsCount = 3 ) + override val out: Output = { println(this) } } fun main() = RunTestAndroidCorelliumExample() diff --git a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt index 13fe57dedb..5d819ff949 100644 --- a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt +++ b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt @@ -1,10 +1,13 @@ package flank.corellium.domain import flank.apk.Apk +import flank.corellium.api.AndroidInstance import flank.corellium.api.CorelliumApi import flank.corellium.domain.RunTestCorelliumAndroid.Args +import flank.log.Output import flank.junit.JUnit import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map import org.junit.After import org.junit.Assert.assertEquals @@ -51,12 +54,14 @@ class RunTestAndroidCorelliumTestMockApiAndroid : RunTestCorelliumAndroid.Contex }, invokeAndroidDevices = { (amount) -> assertEquals(expectedShardsCount, amount) - (1..amount).asFlow().map(Int::toString) + (1..amount).asFlow().map { + AndroidInstance.Event.Ready(it.toString()) + } }, installAndroidApps = { list -> println(list) assertEquals(expectedShardsCount, list.size) - completeJob + emptyFlow() }, executeTest = { (instances) -> println(instances) @@ -85,6 +90,8 @@ class RunTestAndroidCorelliumTestMockApiAndroid : RunTestCorelliumAndroid.Contex }, ) + override val out: Output = { } + override val junit = JUnit.Api() @Test diff --git a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestParsingAndroid.kt b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestParsingAndroid.kt index 45464dc40e..e042877692 100644 --- a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestParsingAndroid.kt +++ b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestParsingAndroid.kt @@ -1,9 +1,12 @@ package flank.corellium.domain import flank.apk.Apk +import flank.corellium.api.AndroidInstance import flank.corellium.api.CorelliumApi +import flank.log.Output import flank.junit.JUnit import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map import org.junit.After import org.junit.Assert.assertEquals @@ -32,7 +35,9 @@ class RunTestAndroidCorelliumTestParsingAndroid : RunTestCorelliumAndroid.Contex invokeAndroidDevices = { (amount) -> println(amount) assertEquals(expectedShardsCount, amount) - (1..amount).asFlow().map(Int::toString) + (1..amount).asFlow().map { + AndroidInstance.Event.Ready(it.toString()) + } }, installAndroidApps = { apps -> apps.forEach { (key, value) -> @@ -42,7 +47,7 @@ class RunTestAndroidCorelliumTestParsingAndroid : RunTestCorelliumAndroid.Contex } } assertEquals(expectedShardsCount, apps.size) - completeJob + emptyFlow() }, executeTest = { (instances) -> instances.forEach { (key, value) -> @@ -56,6 +61,8 @@ class RunTestAndroidCorelliumTestParsingAndroid : RunTestCorelliumAndroid.Contex }, ) + override val out: Output = { } + override val junit = JUnit.Api() override val apk = Apk.Api() From 39b2b287f9db60593f7227c4dae18c5299397385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20G=C3=B3ral?= Date: Wed, 16 Jun 2021 14:14:28 +0200 Subject: [PATCH 2/9] Add test --- .../run/test/android/step/InstallApks.kt | 2 +- .../run/test/android/step/InvokeDevices.kt | 2 +- .../flank/corellium/domain/util/Transform.kt | 4 +- tool/log/src/main/kotlin/flank/log/Builder.kt | 42 +++++++++++++++++++ .../src/main/kotlin/flank/log/Formatter.kt | 24 +++++++++++ .../src/test/kotlin/flank/log/LoggingTest.kt | 40 ++++++++++++++++++ 6 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 tool/log/src/main/kotlin/flank/log/Builder.kt create mode 100644 tool/log/src/main/kotlin/flank/log/Formatter.kt create mode 100644 tool/log/src/test/kotlin/flank/log/LoggingTest.kt diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InstallApks.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InstallApks.kt index 400b9e5827..67b4d6077c 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InstallApks.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InstallApks.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.collect internal fun RunTestCorelliumAndroid.Context.installApks() = step(InstallApks) { out -> require(shards.size <= ids.size) { "Not enough instances, required ${shards.size} but was $ids.size" } val apks = prepareApkToInstall() - api.installAndroidApps(apks).collect { event -> event.out() } + api.installAndroidApps(apks).collect { event -> InstallApks.Status(event).out() } // If tests will be executed too fast just after the // app installed, the instrumentation will fail delay(8_000) diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt index e93521fd22..a11cd1fc03 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt @@ -25,7 +25,7 @@ internal fun RunTestCorelliumAndroid.Context.invokeDevices() = step(InvokeDevice gpuAcceleration = args.gpuAcceleration ) copy(ids = api.invokeAndroidDevices(config) - .onEach { event -> out(event) } + .onEach { event -> InvokeDevices.Status(event).out() } .filterIsInstance() .map { instance -> instance.id } .toList() diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt index 311e80dd2e..b7c43ed3e2 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt @@ -3,7 +3,7 @@ package flank.corellium.domain.util import flank.log.Event import flank.log.Logger import flank.log.Output -import flank.log.event +import flank.log.wrapEvents import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.fold @@ -33,7 +33,7 @@ internal fun Logger.injectLogger( type: Any = Unit, transform: suspend S.(Output) -> S ): Transform { - val out: Output = { type.event(this).out() } + val out: Output = out.wrapEvents(type) return { Event.Start.out() val result = transform(this, out) diff --git a/tool/log/src/main/kotlin/flank/log/Builder.kt b/tool/log/src/main/kotlin/flank/log/Builder.kt new file mode 100644 index 0000000000..3bb6b2860e --- /dev/null +++ b/tool/log/src/main/kotlin/flank/log/Builder.kt @@ -0,0 +1,42 @@ +package flank.log + +import kotlin.reflect.KClass + +fun buildFormatter(build: Builder.() -> Unit): Formatter = + Builder().apply(build).run { + @Suppress("UNCHECKED_CAST") + Formatter( + static = static as Map>, + dynamic = dynamic as Map>, + ) + } + +class Builder internal constructor() { + + internal val static = mutableMapOf>() + internal val dynamic = mutableMapOf>() + + operator fun KClass.invoke( + context: Any? = null, + toString: ToString, + ) { + static += listOfNotNull(context, java) to toString + } + + operator fun Event.Type.invoke( + context: Any? = null, + toString: ToString + ) { + static += listOfNotNull(context, this) to toString + } + + fun match(f: (Any.(Any) -> V?)) = f + + infix fun (Any.(Any) -> V?).to( + toString: ToString + ) { + val wrap: DynamicMatcher = { invoke(this, it) != null } + dynamic += wrap to toString + } + +} diff --git a/tool/log/src/main/kotlin/flank/log/Formatter.kt b/tool/log/src/main/kotlin/flank/log/Formatter.kt new file mode 100644 index 0000000000..345666a339 --- /dev/null +++ b/tool/log/src/main/kotlin/flank/log/Formatter.kt @@ -0,0 +1,24 @@ +package flank.log + +class Formatter internal constructor( + val static: Map>, + val dynamic: Map>, +) + +internal typealias StaticMatcher = List +internal typealias DynamicMatcher = Any.(Any) -> Boolean +internal typealias ToString = V.(Any) -> String? + + +operator fun Formatter.invoke(event: Event<*>): String? = event.run { + val format = static[listOf(context, type)] + ?: static[listOf(type)] + ?: dynamic.toList().firstOrNull { (match, _) -> match.invoke(context, value) }?.second + + format?.invoke(value, context) +} + +val Formatter.output: Output get() = output(::println) + +fun Formatter.output(log: (String) -> Unit): Output = { (this as? Event<*>)?.let { invoke(it)?.let(log) } } + diff --git a/tool/log/src/test/kotlin/flank/log/LoggingTest.kt b/tool/log/src/test/kotlin/flank/log/LoggingTest.kt new file mode 100644 index 0000000000..1adaac899e --- /dev/null +++ b/tool/log/src/test/kotlin/flank/log/LoggingTest.kt @@ -0,0 +1,40 @@ +package flank.log + +import org.junit.Assert.assertEquals +import org.junit.Test + +private const val STATIC = "static" +private const val VALUE = "value" +private const val CLASS = "class" +private const val STRING = "string" + +class LoggingTest { + + private object Singleton : Event.Type + private object Value : Event.Type + private data class Class(val value: String) : Event.Data + + @Test + fun test() { + // Given + val formatter = buildFormatter { + Singleton { STATIC } + Value { this } + Class::class { value } + match { it as? String } to { this } + } + + val expected = listOf(STATIC, VALUE, CLASS, STRING) + val actual = mutableListOf() + val out = formatter.output { actual += it }.wrapEvents() + + // When + Singleton.out() + Value(VALUE).out() + Class(CLASS).out() + STRING.out() + + // Then + assertEquals(expected, actual) + } +} From 5570a5154e70f04873ab526a4c02c6dfa221652c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20G=C3=B3ral?= Date: Wed, 16 Jun 2021 16:37:07 +0200 Subject: [PATCH 3/9] Fix --- .../flank/corellium/cli/RunTestCorelliumAndroidCommand.kt | 4 ++-- .../corellium/cli/RunTestCorelliumAndroidCommandTest.kt | 6 ++++-- tool/log/src/main/kotlin/flank/log/Builder.kt | 1 - tool/log/src/main/kotlin/flank/log/Formatter.kt | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt b/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt index 2466521a3b..9cb6251d97 100644 --- a/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt +++ b/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt @@ -28,11 +28,11 @@ import flank.corellium.domain.RunTestCorelliumAndroid.ParseApkInfo import flank.corellium.domain.RunTestCorelliumAndroid.ParseTestCases import flank.corellium.domain.RunTestCorelliumAndroid.PrepareShards import flank.corellium.domain.invoke +import flank.instrument.log.Instrument +import flank.junit.JUnit import flank.log.Event.Start import flank.log.buildFormatter import flank.log.output -import flank.instrument.log.Instrument -import flank.junit.JUnit import picocli.CommandLine @CommandLine.Command( diff --git a/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt b/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt index c18175222b..dcf46ef5ab 100644 --- a/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt +++ b/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt @@ -18,11 +18,13 @@ import flank.corellium.domain.RunTestCorelliumAndroid.OutputDir import flank.corellium.domain.RunTestCorelliumAndroid.ParseApkInfo import flank.corellium.domain.RunTestCorelliumAndroid.ParseTestCases import flank.corellium.domain.RunTestCorelliumAndroid.PrepareShards +import flank.instrument.log.Instrument import flank.log.Event import flank.log.event import flank.log.invoke -import flank.instrument.log.Instrument -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertTrue import org.junit.Test import picocli.CommandLine import java.io.File diff --git a/tool/log/src/main/kotlin/flank/log/Builder.kt b/tool/log/src/main/kotlin/flank/log/Builder.kt index 3bb6b2860e..351612e4d6 100644 --- a/tool/log/src/main/kotlin/flank/log/Builder.kt +++ b/tool/log/src/main/kotlin/flank/log/Builder.kt @@ -38,5 +38,4 @@ class Builder internal constructor() { val wrap: DynamicMatcher = { invoke(this, it) != null } dynamic += wrap to toString } - } diff --git a/tool/log/src/main/kotlin/flank/log/Formatter.kt b/tool/log/src/main/kotlin/flank/log/Formatter.kt index 345666a339..b7f1e1335a 100644 --- a/tool/log/src/main/kotlin/flank/log/Formatter.kt +++ b/tool/log/src/main/kotlin/flank/log/Formatter.kt @@ -21,4 +21,3 @@ operator fun Formatter.invoke(event: Event<*>): String? = event.run { val Formatter.output: Output get() = output(::println) fun Formatter.output(log: (String) -> Unit): Output = { (this as? Event<*>)?.let { invoke(it)?.let(log) } } - From a244b122d406e8786f1c7d93e7cea27ef78d915f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20G=C3=B3ral?= Date: Thu, 17 Jun 2021 09:55:28 +0200 Subject: [PATCH 4/9] Update log doc --- tool/log/src/main/kotlin/flank/log/Builder.kt | 29 +++++++++++++++++++ .../src/main/kotlin/flank/log/Formatter.kt | 15 +++++++++- .../src/test/kotlin/flank/log/LoggingTest.kt | 10 +++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/tool/log/src/main/kotlin/flank/log/Builder.kt b/tool/log/src/main/kotlin/flank/log/Builder.kt index 351612e4d6..c8c7f0e9a2 100644 --- a/tool/log/src/main/kotlin/flank/log/Builder.kt +++ b/tool/log/src/main/kotlin/flank/log/Builder.kt @@ -2,6 +2,9 @@ package flank.log import kotlin.reflect.KClass +/** + * Factory method for building and creating formatter. + */ fun buildFormatter(build: Builder.() -> Unit): Formatter = Builder().apply(build).run { @Suppress("UNCHECKED_CAST") @@ -11,11 +14,21 @@ fun buildFormatter(build: Builder.() -> Unit): Formatter = ) } +/** + * Log formatters builder. + */ class Builder internal constructor() { internal val static = mutableMapOf>() internal val dynamic = mutableMapOf>() + /** + * Registers [V] formatter with [KClass] based static matcher. + * + * @receiver [KClass] as a identifier. + * @param context Optional context. + * @param toString Reference to formatting function. + */ operator fun KClass.invoke( context: Any? = null, toString: ToString, @@ -23,6 +36,13 @@ class Builder internal constructor() { static += listOfNotNull(context, java) to toString } + /** + * Registers [V] formatter with [Event.Type] based static matcher. + * + * @receiver [Event.Type] as a identifier. + * @param context Optional context. + * @param toString Reference to formatting function. + */ operator fun Event.Type.invoke( context: Any? = null, toString: ToString @@ -30,8 +50,17 @@ class Builder internal constructor() { static += listOfNotNull(context, this) to toString } + /** + * Creates matcher function. + */ fun match(f: (Any.(Any) -> V?)) = f + /** + * Registers [V] formatter with dynamic matcher function. + * + * @receiver Matching event function. The receiver is a context where + * @param toString Reference to formatting function. + */ infix fun (Any.(Any) -> V?).to( toString: ToString ) { diff --git a/tool/log/src/main/kotlin/flank/log/Formatter.kt b/tool/log/src/main/kotlin/flank/log/Formatter.kt index b7f1e1335a..7e1745a8ce 100644 --- a/tool/log/src/main/kotlin/flank/log/Formatter.kt +++ b/tool/log/src/main/kotlin/flank/log/Formatter.kt @@ -1,5 +1,8 @@ package flank.log +/** + * Structural logs formatter + */ class Formatter internal constructor( val static: Map>, val dynamic: Map>, @@ -9,7 +12,9 @@ internal typealias StaticMatcher = List internal typealias DynamicMatcher = Any.(Any) -> Boolean internal typealias ToString = V.(Any) -> String? - +/** + * Format given event into string. + */ operator fun Formatter.invoke(event: Event<*>): String? = event.run { val format = static[listOf(context, type)] ?: static[listOf(type)] @@ -18,6 +23,14 @@ operator fun Formatter.invoke(event: Event<*>): String? = event.run { format?.invoke(value, context) } +/** + * Creates [println] log [Output] from a given [Formatter]. + */ val Formatter.output: Output get() = output(::println) +/** + * Creates [log] [Output] from a given [Formatter]. + * + * Conditionally invoking given [log] with formatted values. + */ fun Formatter.output(log: (String) -> Unit): Output = { (this as? Event<*>)?.let { invoke(it)?.let(log) } } diff --git a/tool/log/src/test/kotlin/flank/log/LoggingTest.kt b/tool/log/src/test/kotlin/flank/log/LoggingTest.kt index 1adaac899e..2e55f0f302 100644 --- a/tool/log/src/test/kotlin/flank/log/LoggingTest.kt +++ b/tool/log/src/test/kotlin/flank/log/LoggingTest.kt @@ -8,12 +8,22 @@ private const val VALUE = "value" private const val CLASS = "class" private const val STRING = "string" +/** + * Complete logger API test. + */ class LoggingTest { private object Singleton : Event.Type private object Value : Event.Type private data class Class(val value: String) : Event.Data + /** + * Test logging API. + * + * * Build [Formatter] with different types of formatting functions. + * * Create output with events wrapper. + * * Pass various objects to logger output. + */ @Test fun test() { // Given From 48dd354adb9d79a2aa1c87eaa2122919aa6accc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20G=C3=B3ral?= Date: Thu, 17 Jun 2021 11:30:56 +0200 Subject: [PATCH 5/9] Extract logging related tools to dedicated modules --- corellium/cli/build.gradle.kts | 1 + .../cli/RunTestCorelliumAndroidCommand.kt | 2 +- .../flank/corellium/domain/util/Transform.kt | 5 +- tool/log/src/main/kotlin/flank/log/Builder.kt | 70 ------------------- .../src/main/kotlin/flank/log/Formatter.kt | 36 ---------- .../src/test/kotlin/flank/log/LoggingTest.kt | 50 ------------- 6 files changed, 5 insertions(+), 159 deletions(-) delete mode 100644 tool/log/src/main/kotlin/flank/log/Builder.kt delete mode 100644 tool/log/src/main/kotlin/flank/log/Formatter.kt delete mode 100644 tool/log/src/test/kotlin/flank/log/LoggingTest.kt diff --git a/corellium/cli/build.gradle.kts b/corellium/cli/build.gradle.kts index c46dbc28aa..00f2a1fef0 100644 --- a/corellium/cli/build.gradle.kts +++ b/corellium/cli/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(project(":corellium:adapter")) implementation(project(":tool:apk")) implementation(project(":tool:junit")) + implementation(project(":tool:log:format")) implementation(Dependencies.JACKSON_KOTLIN) implementation(Dependencies.JACKSON_YAML) implementation(Dependencies.JACKSON_XML) diff --git a/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt b/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt index 9cb6251d97..e72360abf2 100644 --- a/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt +++ b/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt @@ -188,7 +188,7 @@ private fun RunTestCorelliumAndroidCommand.createArgs() = Args( scanPreviousDurations = config.scanPreviousDurations!!, ) -internal val format = buildFormatter { +internal val format = buildFormatter { Start(Authorize) { "* Authorizing" } Start(CleanUp) { "* Cleaning instances" } diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt index b7c43ed3e2..2649d725ec 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/util/Transform.kt @@ -3,7 +3,8 @@ package flank.corellium.domain.util import flank.log.Event import flank.log.Logger import flank.log.Output -import flank.log.wrapEvents +import flank.log.event +import flank.log.normalize import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.fold @@ -33,7 +34,7 @@ internal fun Logger.injectLogger( type: Any = Unit, transform: suspend S.(Output) -> S ): Transform { - val out: Output = out.wrapEvents(type) + val out: Output = out.normalize { type event it } return { Event.Start.out() val result = transform(this, out) diff --git a/tool/log/src/main/kotlin/flank/log/Builder.kt b/tool/log/src/main/kotlin/flank/log/Builder.kt deleted file mode 100644 index c8c7f0e9a2..0000000000 --- a/tool/log/src/main/kotlin/flank/log/Builder.kt +++ /dev/null @@ -1,70 +0,0 @@ -package flank.log - -import kotlin.reflect.KClass - -/** - * Factory method for building and creating formatter. - */ -fun buildFormatter(build: Builder.() -> Unit): Formatter = - Builder().apply(build).run { - @Suppress("UNCHECKED_CAST") - Formatter( - static = static as Map>, - dynamic = dynamic as Map>, - ) - } - -/** - * Log formatters builder. - */ -class Builder internal constructor() { - - internal val static = mutableMapOf>() - internal val dynamic = mutableMapOf>() - - /** - * Registers [V] formatter with [KClass] based static matcher. - * - * @receiver [KClass] as a identifier. - * @param context Optional context. - * @param toString Reference to formatting function. - */ - operator fun KClass.invoke( - context: Any? = null, - toString: ToString, - ) { - static += listOfNotNull(context, java) to toString - } - - /** - * Registers [V] formatter with [Event.Type] based static matcher. - * - * @receiver [Event.Type] as a identifier. - * @param context Optional context. - * @param toString Reference to formatting function. - */ - operator fun Event.Type.invoke( - context: Any? = null, - toString: ToString - ) { - static += listOfNotNull(context, this) to toString - } - - /** - * Creates matcher function. - */ - fun match(f: (Any.(Any) -> V?)) = f - - /** - * Registers [V] formatter with dynamic matcher function. - * - * @receiver Matching event function. The receiver is a context where - * @param toString Reference to formatting function. - */ - infix fun (Any.(Any) -> V?).to( - toString: ToString - ) { - val wrap: DynamicMatcher = { invoke(this, it) != null } - dynamic += wrap to toString - } -} diff --git a/tool/log/src/main/kotlin/flank/log/Formatter.kt b/tool/log/src/main/kotlin/flank/log/Formatter.kt deleted file mode 100644 index 7e1745a8ce..0000000000 --- a/tool/log/src/main/kotlin/flank/log/Formatter.kt +++ /dev/null @@ -1,36 +0,0 @@ -package flank.log - -/** - * Structural logs formatter - */ -class Formatter internal constructor( - val static: Map>, - val dynamic: Map>, -) - -internal typealias StaticMatcher = List -internal typealias DynamicMatcher = Any.(Any) -> Boolean -internal typealias ToString = V.(Any) -> String? - -/** - * Format given event into string. - */ -operator fun Formatter.invoke(event: Event<*>): String? = event.run { - val format = static[listOf(context, type)] - ?: static[listOf(type)] - ?: dynamic.toList().firstOrNull { (match, _) -> match.invoke(context, value) }?.second - - format?.invoke(value, context) -} - -/** - * Creates [println] log [Output] from a given [Formatter]. - */ -val Formatter.output: Output get() = output(::println) - -/** - * Creates [log] [Output] from a given [Formatter]. - * - * Conditionally invoking given [log] with formatted values. - */ -fun Formatter.output(log: (String) -> Unit): Output = { (this as? Event<*>)?.let { invoke(it)?.let(log) } } diff --git a/tool/log/src/test/kotlin/flank/log/LoggingTest.kt b/tool/log/src/test/kotlin/flank/log/LoggingTest.kt deleted file mode 100644 index 2e55f0f302..0000000000 --- a/tool/log/src/test/kotlin/flank/log/LoggingTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -package flank.log - -import org.junit.Assert.assertEquals -import org.junit.Test - -private const val STATIC = "static" -private const val VALUE = "value" -private const val CLASS = "class" -private const val STRING = "string" - -/** - * Complete logger API test. - */ -class LoggingTest { - - private object Singleton : Event.Type - private object Value : Event.Type - private data class Class(val value: String) : Event.Data - - /** - * Test logging API. - * - * * Build [Formatter] with different types of formatting functions. - * * Create output with events wrapper. - * * Pass various objects to logger output. - */ - @Test - fun test() { - // Given - val formatter = buildFormatter { - Singleton { STATIC } - Value { this } - Class::class { value } - match { it as? String } to { this } - } - - val expected = listOf(STATIC, VALUE, CLASS, STRING) - val actual = mutableListOf() - val out = formatter.output { actual += it }.wrapEvents() - - // When - Singleton.out() - Value(VALUE).out() - Class(CLASS).out() - STRING.out() - - // Then - assertEquals(expected, actual) - } -} From ccf9ec5cc43dc2ee6ba99a9c3b4810e3cb2c05d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20G=C3=B3ral?= Date: Mon, 21 Jun 2021 10:47:00 +0200 Subject: [PATCH 6/9] Fix kotlin reflect warnings --- corellium/cli/build.gradle.kts | 1 + tool/junit/build.gradle.kts | 1 + 2 files changed, 2 insertions(+) diff --git a/corellium/cli/build.gradle.kts b/corellium/cli/build.gradle.kts index 00f2a1fef0..373783e2b3 100644 --- a/corellium/cli/build.gradle.kts +++ b/corellium/cli/build.gradle.kts @@ -14,6 +14,7 @@ tasks.withType { kotlinOptions.jvmTarget = "1.8" } dependencies { implementation(Dependencies.KOTLIN_COROUTINES_CORE) + implementation(Dependencies.KOTLIN_REFLECT) implementation(Dependencies.PICOCLI) implementation(project(":corellium:api")) implementation(project(":corellium:domain")) diff --git a/tool/junit/build.gradle.kts b/tool/junit/build.gradle.kts index 932dbae2a0..fdcf31c217 100644 --- a/tool/junit/build.gradle.kts +++ b/tool/junit/build.gradle.kts @@ -14,6 +14,7 @@ tasks.withType { kotlinOptions.jvmTarget = "1.8" } dependencies { implementation(Dependencies.KOTLIN_COROUTINES_CORE) + implementation(Dependencies.KOTLIN_REFLECT) implementation(Dependencies.JACKSON_KOTLIN) implementation(Dependencies.JACKSON_YAML) From ef97de42c44bac0887a8e7cbbfe569a90c93784a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20G=C3=B3ral?= Date: Mon, 21 Jun 2021 10:49:38 +0200 Subject: [PATCH 7/9] Fix lint issue --- .../domain/run/test/android/step/InvokeDevices.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt index a11cd1fc03..68c93b34e2 100644 --- a/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt +++ b/corellium/domain/src/main/kotlin/flank/corellium/domain/run/test/android/step/InvokeDevices.kt @@ -24,10 +24,11 @@ internal fun RunTestCorelliumAndroid.Context.invokeDevices() = step(InvokeDevice amount = shards.size, gpuAcceleration = args.gpuAcceleration ) - copy(ids = api.invokeAndroidDevices(config) - .onEach { event -> InvokeDevices.Status(event).out() } - .filterIsInstance() - .map { instance -> instance.id } - .toList() + copy( + ids = api.invokeAndroidDevices(config) + .onEach { event -> InvokeDevices.Status(event).out() } + .filterIsInstance() + .map { instance -> instance.id } + .toList() ) } From dfcdc9b059df8479d9763350560fbd1bc9270a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20G=C3=B3ral?= Date: Mon, 21 Jun 2021 11:04:03 +0200 Subject: [PATCH 8/9] Fix imports order --- .../flank/corellium/domain/RunTestAndroidCorelliumExample.kt | 2 +- .../domain/RunTestAndroidCorelliumTestMockApiAndroid.kt | 2 +- .../domain/RunTestAndroidCorelliumTestParsingAndroid.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumExample.kt b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumExample.kt index 01a3ff2ba7..9b32fcf77d 100644 --- a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumExample.kt +++ b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumExample.kt @@ -2,8 +2,8 @@ package flank.corellium.domain import flank.apk.Apk import flank.corellium.corelliumApi -import flank.log.Output import flank.junit.JUnit +import flank.log.Output object RunTestAndroidCorelliumExample : RunTestCorelliumAndroid.Context { override val api = corelliumApi("Default Project") diff --git a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt index 5d819ff949..f423685390 100644 --- a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt +++ b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt @@ -4,8 +4,8 @@ import flank.apk.Apk import flank.corellium.api.AndroidInstance import flank.corellium.api.CorelliumApi import flank.corellium.domain.RunTestCorelliumAndroid.Args -import flank.log.Output import flank.junit.JUnit +import flank.log.Output import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map diff --git a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestParsingAndroid.kt b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestParsingAndroid.kt index e042877692..8954b6c5cb 100644 --- a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestParsingAndroid.kt +++ b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestParsingAndroid.kt @@ -3,8 +3,8 @@ package flank.corellium.domain import flank.apk.Apk import flank.corellium.api.AndroidInstance import flank.corellium.api.CorelliumApi -import flank.log.Output import flank.junit.JUnit +import flank.log.Output import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map From 39c35a917c97ad58e796f2fea771f1476c87f8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20G=C3=B3ral?= Date: Thu, 24 Jun 2021 10:57:45 +0200 Subject: [PATCH 9/9] Fix log printing --- corellium/cli/build.gradle.kts | 3 --- .../flank/corellium/cli/RunTestCorelliumAndroidCommand.kt | 2 ++ .../corellium/cli/RunTestCorelliumAndroidCommandTest.kt | 6 +++++- .../domain/RunTestAndroidCorelliumTestMockApiAndroid.kt | 2 +- tool/instrument/log/build.gradle.kts | 5 ++++- tool/log/src/main/kotlin/flank/log/Logger.kt | 2 +- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/corellium/cli/build.gradle.kts b/corellium/cli/build.gradle.kts index 373783e2b3..4364672bd9 100644 --- a/corellium/cli/build.gradle.kts +++ b/corellium/cli/build.gradle.kts @@ -16,11 +16,8 @@ dependencies { implementation(Dependencies.KOTLIN_COROUTINES_CORE) implementation(Dependencies.KOTLIN_REFLECT) implementation(Dependencies.PICOCLI) - implementation(project(":corellium:api")) implementation(project(":corellium:domain")) implementation(project(":corellium:adapter")) - implementation(project(":tool:apk")) - implementation(project(":tool:junit")) implementation(project(":tool:log:format")) implementation(Dependencies.JACKSON_KOTLIN) implementation(Dependencies.JACKSON_YAML) diff --git a/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt b/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt index e72360abf2..71ff9131f4 100644 --- a/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt +++ b/corellium/cli/src/main/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommand.kt @@ -234,4 +234,6 @@ internal val format = buildFormatter { } RunTestCorelliumAndroid.Created { "Created $path" } RunTestCorelliumAndroid.AlreadyExist { "Already exist $path" } + + match { it as? String } to { this } } diff --git a/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt b/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt index dcf46ef5ab..0def003b92 100644 --- a/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt +++ b/corellium/cli/src/test/kotlin/flank/corellium/cli/RunTestCorelliumAndroidCommandTest.kt @@ -22,6 +22,7 @@ import flank.instrument.log.Instrument import flank.log.Event import flank.log.event import flank.log.invoke +import flank.log.output import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue @@ -196,6 +197,7 @@ password: $password /** * Test is checking if all specified events have registered dedicated formatters. + * Also, the specified events should be formatted and printed to console output. */ @Test fun outputTest() { @@ -241,11 +243,13 @@ password: $password Unit event RunTestCorelliumAndroid.AlreadyExist(File("path/to/apk.apk")), ) + val printLog = format.output + // ======================== WHEN ======================== val nulls = events + .onEach(printLog) .associateWith { format(it) } - .onEach { (_, string) -> println(string) } .filterValues { it == null } // ======================== THEN ======================== diff --git a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt index f423685390..4f0aef1338 100644 --- a/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt +++ b/corellium/domain/src/test/kotlin/flank/corellium/domain/RunTestAndroidCorelliumTestMockApiAndroid.kt @@ -90,7 +90,7 @@ class RunTestAndroidCorelliumTestMockApiAndroid : RunTestCorelliumAndroid.Contex }, ) - override val out: Output = { } + override val out: Output = ::println override val junit = JUnit.Api() diff --git a/tool/instrument/log/build.gradle.kts b/tool/instrument/log/build.gradle.kts index 93570d09cd..63789cd805 100644 --- a/tool/instrument/log/build.gradle.kts +++ b/tool/instrument/log/build.gradle.kts @@ -10,7 +10,10 @@ repositories { maven(url = "https://kotlin.bintray.com/kotlinx") } -tasks.withType { kotlinOptions.jvmTarget = "1.8" } +tasks { + withType { kotlinOptions.jvmTarget = "1.8" } + withType { archiveBaseName.set("instrument_log") } +} dependencies { implementation(Dependencies.KOTLIN_COROUTINES_CORE) diff --git a/tool/log/src/main/kotlin/flank/log/Logger.kt b/tool/log/src/main/kotlin/flank/log/Logger.kt index 321a216481..33a5aff828 100644 --- a/tool/log/src/main/kotlin/flank/log/Logger.kt +++ b/tool/log/src/main/kotlin/flank/log/Logger.kt @@ -27,4 +27,4 @@ fun GenericOutput.normalize(transform: (Any) -> T?): Output = * Normalizes [GenericOutput] into [Output] by filtering values of type [T] */ inline fun GenericOutput.filter(): Output = - normalize { this as? T } + normalize { it as? T }