Last active
June 12, 2017 12:53
-
-
Save akperkins/fec7c0c1ca3de1bfbf661ed896418c27 to your computer and use it in GitHub Desktop.
Example Kotlin script which demonstrates usage gfxinfo to create an automated jank performance test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example | |
import java.io.File | |
import java.io.IOException | |
import kotlin.concurrent.thread | |
import kotlin.collections.* | |
/** | |
* Created by Andre Perkins ([email protected]) on 6/6/17. | |
* | |
* Kotlin script | |
* | |
* A simple script that executes a UI automation tests while, simultaneously, querying the Jank information of | |
* the connected device via gfxinfo and then outputs a subset of the results. | |
*/ | |
fun Map<String, String>.toJson(): String { | |
if ( this.isEmpty() ){ | |
return "{}" | |
} | |
return StringBuilder("") | |
.append("{") | |
.append("\n") | |
.append(this.map { (k, v) -> " \"$k\": \"$v\"" }.reduce {o, n -> "$o,\n$n" }) | |
.append("\n}") | |
.toString() | |
} | |
fun Map<String, String>.toXml(): String { | |
if ( this.isEmpty() ){ | |
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gfxinfo />" | |
} | |
val json: StringBuilder = StringBuilder("") | |
.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gfxinfo>\n") | |
this.forEach { key, entry -> | |
var xmlValidKey = key.replace(" ", "_") | |
if (xmlValidKey.startsWithDigit()){ | |
xmlValidKey = "_" + xmlValidKey | |
} | |
json.append(" <$xmlValidKey>\"$entry\"</$xmlValidKey>\n") | |
} | |
return json.append("</gfxinfo>") | |
.toString() | |
} | |
fun String.startsWithDigit() = Character.isDigit(this[0]) | |
fun String.save(fileName: String): Unit { | |
File(fileName).printWriter().use { out -> | |
out.print(this) | |
} | |
} | |
fun String.extractValues(): Map<String, String> { | |
val mapStats: MutableMap<String, String> = mutableMapOf() | |
val parts: List<String> = split("\\n".toRegex()) | |
parts.forEach { | |
val split: List<String> = it.split(":") | |
if (split.size != 2 || split[0].isBlank() || split[1].isBlank()){ | |
return@forEach | |
} | |
if (split[0] == "Janky frames") { | |
val split_number_data = split[1].split("(") | |
val number_of_janky_frames = split_number_data[0] | |
mapStats["Janky frames"] = number_of_janky_frames.trim() | |
val percentage_of_janky_frames = split_number_data[1].replace(")", "") | |
mapStats["Janky frames Percentage"] = percentage_of_janky_frames.trim() | |
} else { | |
mapStats[split[0]] = split[1].trim() | |
} | |
} | |
return mapStats | |
} | |
private fun extract_gfx_info(apkName: String) = "adb shell dumpsys gfxinfo $apkName".runCommand() | |
private fun app_process_still_alive(current_stats: String) = !current_stats.contains("No process found for:") | |
private fun startTest(startTestRunnerCommand: String) = startTestRunnerCommand.runCommand() | |
private fun String.runCommand(): String { | |
return try { | |
val parts = this.split("\\s".toRegex()) | |
val process = ProcessBuilder(*parts.toTypedArray()) | |
.redirectOutput(ProcessBuilder.Redirect.PIPE) | |
.redirectError(ProcessBuilder.Redirect.PIPE) | |
.start() | |
process.inputStream.bufferedReader().readText() | |
} catch(e: IOException) { | |
throw RuntimeException("Un-able to properly execute system call: $this", e ) | |
} | |
} | |
fun main(args: Array<String>){ | |
print("starting test!") | |
val apkName = args[0] | |
val startTestRunnerCommand = args[1] | |
val instrumentThread: Thread = thread { | |
startTest(startTestRunnerCommand) | |
} | |
var current_stats: String | |
var stats: String = "" | |
while (instrumentThread.isAlive) { | |
current_stats = extract_gfx_info(apkName) | |
if (app_process_still_alive(current_stats)){ | |
stats = current_stats | |
} | |
} | |
stats.save("Output.txt") | |
val extractedValues = stats.extractValues() | |
extractedValues.toJson().save("Output.json") | |
extractedValues.toXml().save("Output.xml") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example | |
import org.junit.Test | |
import com.example.MainKtTest.* | |
import org.junit.Assert | |
/** | |
* Created by Andre Perkins ([email protected]) on 6/6/17. | |
*/ | |
class MainKtTest { | |
@Test | |
fun toJson_emptyMap_emptyObjectIsReturned() { | |
val map = emptyMap<String, String>() | |
val expected = "{}" | |
val actual = map.toJson() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun toJson_regularMap_validJsonIsConstructed() { | |
val map = mapOf(Pair("id", "5"), Pair("name", "Andre")) | |
val expected = "{\n \"id\": \"5\",\n \"name\": \"Andre\"\n}" | |
val actual = map.toJson() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun toXml_emptyMap_emptyObjectIsReturned() { | |
val map = emptyMap<String, String>() | |
val expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gfxinfo />" | |
val actual = map.toXml() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun toXml_regularMap_validXmlIsConstructed() { | |
val map = mapOf(Pair("id", "5"), Pair("name", "Andre")) | |
val expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gfxinfo>\n <id>\"5\"</id>\n <name>\"Andre\"</name>\n</gfxinfo>" | |
val actual = map.toXml() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun toXml_regularMap_andSomeKeyValuesHaveSpaceDelimiter_validXmlIsConstructed_andSpacesAreReplaced() { | |
val map = mapOf(Pair("id", "5"), Pair("zip code", "99999")) | |
val expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gfxinfo>\n <id>\"5\"</id>\n <zip_code>\"99999\"</zip_code>\n</gfxinfo>" | |
val actual = map.toXml() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun toXml_regularMap_andSomeKeyValuesStartWithDigit_validXmlIsConstructed_andKeysThatStartWithDigitPrependWithUnderscore() { | |
val map = mapOf(Pair("id", "5"), Pair("1stzip", "2")) | |
val expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gfxinfo>\n <id>\"5\"</id>\n <_1stzip>\"2\"</_1stzip>\n</gfxinfo>" | |
val actual = map.toXml() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun toXml_regularMap_andSomeKeyValuesStartWithDigitAndHaveSpaceDelimiter_validXmlIsConstructed_andKeysThatStartWithDigitPrependWithUnderscore() { | |
val map = mapOf(Pair("id", "5"), Pair("90th percentile", "2")) | |
val expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gfxinfo>\n <id>\"5\"</id>\n <_90th_percentile>\"2\"</_90th_percentile>\n</gfxinfo>" | |
val actual = map.toXml() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun extractValues_stringIsBlank_emptyMapIsReturned() { | |
val stats = "" | |
val expected = emptyMap<String, String>() | |
val actual = stats.extractValues() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun extractValues_stringContainsEasilyMappableFields_theFieldsArePlacedInTheMap() { | |
val stats = "janky frames: 49" | |
val expected = mapOf("janky frames" to "49") | |
val actual = stats.extractValues() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun extractValues_stringContainsExtraData_theMapIsEmpty() { | |
val stats = "TextureCache 0 / 75497472" | |
val expected = emptyMap<String, String>() | |
val actual = stats.extractValues() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun extractValues_stringContainsEasilyMappableFields_andExtraData_theFieldsArePlacedInTheMap_andTheRandomDataIsIgnored() { | |
val stats = "janky frames: 49\n TextureCache 0 / 75497472" | |
val expected = mapOf("janky frames" to "49") | |
val actual = stats.extractValues() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun extractValues_stringContainsEasilyMappableFields_andExtraDataInTheMiddleOfMappableFields_theFieldsArePlacedInTheMap_andTheRandomDataIsIgnored() { | |
val stats = "janky frames: 49\n TextureCache 0 / 75497472 \nNumber Missed Vsync: 4" | |
val expected = mapOf("janky frames" to "49", "Number Missed Vsync" to "4") | |
val actual = stats.extractValues() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun extractValues_stringContainsJankyFramesAndPercentageOnTheSameLine_splitIntoTwoSeparateFieldsInMap() { | |
val stats = "Janky frames: 4 (80.00%)" | |
val expected = mapOf("Janky frames" to "4", "Janky frames Percentage" to "80.00%") | |
val actual = stats.extractValues() | |
Assert.assertEquals(expected, actual) | |
} | |
@Test | |
fun isDigit_firstCharInStringIsDigit_returnsTrue() { | |
val str = "89thPercentile" | |
Assert.assertTrue(str.startsWithDigit()) | |
} | |
@Test | |
fun isDigit_firstCharInStringIsNotADigit_returnsFalse() { | |
val str = "Jank" | |
Assert.assertFalse(str.startsWithDigit()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment