Skip to content

Instantly share code, notes, and snippets.

@akperkins
Last active June 12, 2017 12:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akperkins/fec7c0c1ca3de1bfbf661ed896418c27 to your computer and use it in GitHub Desktop.
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
package com.example
import java.io.File
import java.io.IOException
import kotlin.concurrent.thread
import kotlin.collections.*
/**
* Created by Andre Perkins (akperkins1@gmail.com) 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")
}
package com.example
import org.junit.Test
import com.example.MainKtTest.*
import org.junit.Assert
/**
* Created by Andre Perkins (akperkins1@gmail.com) 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