-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
386 additions
and
1 deletion.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# Shard dump | ||
|
||
Allows dumping shards as formatted json file. | ||
|
||
## Example | ||
|
||
The example output file structure: | ||
```json | ||
[ | ||
[ | ||
{ | ||
"name": "path/to/app1.apk", | ||
"tests": [ | ||
{ | ||
"name": "path/to/app1-test1.apk", | ||
"cases": [ | ||
{ | ||
"name": "app1.test1.Test#case1", | ||
"duration": 10000 | ||
} | ||
] | ||
} | ||
] | ||
} | ||
], | ||
[ | ||
{ | ||
"name": "path/to/app1.apk", | ||
"tests": [ | ||
{ | ||
"name": "app1-test1.apk", | ||
"cases": [ | ||
{ | ||
"name": "app1.test1.Test#case2", | ||
"duration": 2000 | ||
} | ||
] | ||
} | ||
] | ||
}, | ||
{ | ||
"name": "path/to/app2.apk", | ||
"tests": [ | ||
{ | ||
"name": "path/to/app2-test1.apk", | ||
"cases": [ | ||
{ | ||
"name": "app2.test1.Test2#case1", | ||
"duration": 1000 | ||
} | ||
] | ||
}, | ||
{ | ||
"name": "path/to/app2-test1.apk", | ||
"cases": [ | ||
{ | ||
"name": "app2.test1.Test2#case2", | ||
"duration": 120 | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
] | ||
``` |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | ||
|
||
plugins { | ||
kotlin(Plugins.Kotlin.PLUGIN_JVM) | ||
} | ||
|
||
repositories { | ||
jcenter() | ||
mavenCentral() | ||
maven(url = "https://kotlin.bintray.com/kotlinx") | ||
} | ||
|
||
tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" } | ||
|
||
dependencies { | ||
implementation(Dependencies.KOTLIN_COROUTINES_CORE) | ||
implementation(Dependencies.GSON) | ||
api(project(":corellium:shard")) | ||
|
||
testImplementation(Dependencies.JUNIT) | ||
} |
25 changes: 25 additions & 0 deletions
25
corellium/shard/dump/src/main/kotlin/flank/corellium/shard/Dump.kt
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package flank.corellium.shard | ||
|
||
import flank.corellium.shard.internal.obfuscatePrettyGson | ||
import flank.corellium.shard.internal.prettyGson | ||
import java.nio.file.Files.newBufferedWriter | ||
import java.nio.file.Paths.get | ||
|
||
/** | ||
* Dump shards as formatted json file. | ||
* | ||
* @receiver List of shards to dump. | ||
* @param filePath Relative or absolut path the file. | ||
* @param obfuscate Hash the test cases names. Use for security reasons. | ||
*/ | ||
fun List<InstanceShard>.dumpToFile( | ||
filePath: String, | ||
obfuscate: Boolean = false | ||
) { | ||
val writer = newBufferedWriter(get(filePath)) | ||
when (obfuscate) { | ||
true -> obfuscatePrettyGson | ||
false -> prettyGson | ||
}.toJson(this, writer) | ||
writer.flush() | ||
} |
61 changes: 61 additions & 0 deletions
61
corellium/shard/dump/src/main/kotlin/flank/corellium/shard/internal/GsonPrettyObfuscate.kt
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package flank.corellium.shard.internal | ||
|
||
import com.google.gson.Gson | ||
import com.google.gson.GsonBuilder | ||
import com.google.gson.JsonArray | ||
import com.google.gson.JsonObject | ||
import com.google.gson.JsonSerializationContext | ||
import com.google.gson.JsonSerializer | ||
import com.google.gson.reflect.TypeToken | ||
import java.lang.reflect.Type | ||
|
||
internal val obfuscatePrettyGson: Gson = GsonBuilder() | ||
.registerTypeAdapter(ListOfStringTypeToken.rawType, ObfuscatedAndroidJsonSerializer) | ||
.registerTypeHierarchyAdapter(ListOfStringListTypeToken.rawType, ObfuscatedIosJsonSerializer) | ||
.setPrettyPrinting() | ||
.create() | ||
|
||
private object ListOfStringTypeToken : TypeToken<List<String>>() | ||
|
||
private object ObfuscatedAndroidJsonSerializer : JsonSerializer<List<String>> { | ||
private val obfuscationContext by lazy { mutableMapOf<String, MutableMap<String, String>>() } | ||
|
||
override fun serialize( | ||
src: List<String>, | ||
typeOfSrc: Type, | ||
context: JsonSerializationContext | ||
) = JsonArray().also { jsonArray -> | ||
src.forEach { | ||
val items = it.split(" ") // split for class and test name | ||
jsonArray.add(items.first() + " " + obfuscationContext.obfuscateAndroidTestName(items.last())) | ||
} | ||
} | ||
} | ||
|
||
private object ListOfStringListTypeToken : TypeToken<CustomShardChunks>() | ||
private typealias CustomShardChunks = List<Map<String, List<String>>> | ||
|
||
private object ObfuscatedIosJsonSerializer : JsonSerializer<CustomShardChunks> { | ||
private val obfuscationContext by lazy { mutableMapOf<String, MutableMap<String, String>>() } | ||
|
||
override fun serialize( | ||
src: CustomShardChunks, | ||
typeOfSrc: Type, | ||
context: JsonSerializationContext | ||
) = JsonArray().also { jsonArray -> | ||
src.forEach { xcTestMethodList -> | ||
val xcTestMethodJson = JsonObject() | ||
xcTestMethodList.forEach { entry -> | ||
xcTestMethodJson.add( | ||
obfuscationContext.obfuscateIosTestName(entry.key), | ||
JsonArray().also { nestedJsonArray -> | ||
entry.value.forEach { it -> | ||
nestedJsonArray.add(obfuscationContext.obfuscateIosTestName(it)) | ||
} | ||
} | ||
) | ||
} | ||
jsonArray.add(xcTestMethodJson) | ||
} | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
corellium/shard/dump/src/main/kotlin/flank/corellium/shard/internal/GsonPrettyStandard.kt
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package flank.corellium.shard.internal | ||
|
||
import com.google.gson.Gson | ||
import com.google.gson.GsonBuilder | ||
|
||
internal val prettyGson: Gson = GsonBuilder().setPrettyPrinting().create() |
63 changes: 63 additions & 0 deletions
63
corellium/shard/dump/src/main/kotlin/flank/corellium/shard/internal/Obfuscation.kt
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package flank.corellium.shard.internal | ||
|
||
private const val LOWER_CASE_CHARS = "abcdefghijklmnopqrstuvwxyz" | ||
private const val UPPER_CASE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
private const val ANDROID_TEST_METHOD_SEPARATOR = '#' | ||
private const val ANDROID_PACKAGE_SEPARATOR = '.' | ||
private const val IOS_TEST_METHOD_SEPARATOR = '/' | ||
|
||
internal typealias ObfuscationContext = MutableMap<String, MutableMap<String, String>> | ||
|
||
internal fun ObfuscationContext.obfuscateAndroidTestName(input: String): String { | ||
val obfuscatedPackageNameWithClass = | ||
obfuscateAndroidPackageAndClass(input.split(ANDROID_TEST_METHOD_SEPARATOR).first()) | ||
|
||
return obfuscatedPackageNameWithClass + obfuscateAndroidMethodIfPresent(input, obfuscatedPackageNameWithClass) | ||
} | ||
|
||
private fun ObfuscationContext.obfuscateAndroidPackageAndClass(packageNameWithClass: String) = | ||
packageNameWithClass | ||
.split(ANDROID_PACKAGE_SEPARATOR) | ||
.fold("") { previous, next -> | ||
val classChunk = getOrPut(previous) { linkedMapOf() } | ||
val obfuscatedPart = classChunk.getOrPut(next) { nextSymbol(next, classChunk) } | ||
if (previous.isBlank()) obfuscatedPart else "$previous$ANDROID_PACKAGE_SEPARATOR$obfuscatedPart" | ||
} | ||
|
||
private fun ObfuscationContext.obfuscateAndroidMethodIfPresent( | ||
input: String, | ||
obfuscatedPackageNameWithClass: String | ||
) = if (input.contains(ANDROID_TEST_METHOD_SEPARATOR)) | ||
ANDROID_TEST_METHOD_SEPARATOR + obfuscateMethodName( | ||
methodName = input.split(ANDROID_TEST_METHOD_SEPARATOR).last(), | ||
context = getOrPut(obfuscatedPackageNameWithClass) { mutableMapOf() } | ||
) | ||
else "" | ||
|
||
internal fun ObfuscationContext.obfuscateIosTestName(input: String): String { | ||
val className = input.split(IOS_TEST_METHOD_SEPARATOR).first() | ||
val obfuscatedClassName = getOrPut("") { mutableMapOf() }.run { | ||
getOrPut(className) { nextSymbol(className, this) } | ||
} | ||
return obfuscatedClassName + | ||
IOS_TEST_METHOD_SEPARATOR + | ||
obfuscateMethodName( | ||
methodName = input.split(IOS_TEST_METHOD_SEPARATOR).last(), | ||
context = getOrPut(obfuscatedClassName) { linkedMapOf() } | ||
) | ||
} | ||
|
||
private fun nextSymbol(key: String, context: Map<String, String>): String { | ||
val isLowerCaseKey = key.first().isLowerCase() | ||
val possibleSymbols = if (isLowerCaseKey) LOWER_CASE_CHARS else UPPER_CASE_CHARS | ||
|
||
val currentContextItemCount = context.values.count { | ||
if (isLowerCaseKey) it.first().isLowerCase() else it.first().isUpperCase() | ||
} | ||
val repeatSymbol = currentContextItemCount / possibleSymbols.length + 1 | ||
|
||
return possibleSymbols[currentContextItemCount % possibleSymbols.length].toString().repeat(repeatSymbol) | ||
} | ||
|
||
private fun obfuscateMethodName(methodName: String, context: MutableMap<String, String>) = | ||
context.getOrPut(methodName) { nextSymbol(methodName, context) } |
142 changes: 142 additions & 0 deletions
142
corellium/shard/dump/src/test/kotlin/flank/corellium/shard/DumpTest.kt
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package flank.corellium.shard | ||
|
||
import org.junit.After | ||
import org.junit.Assert | ||
import org.junit.Test | ||
import java.io.File | ||
|
||
class DumpTest { | ||
|
||
private val filePath = "dump-shards.json" | ||
private val file = File(filePath) | ||
|
||
private val shards = listOf( | ||
listOf( | ||
Shard.App( | ||
name = "app1", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app1-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app1-test1#case1", | ||
duration = 10_000, | ||
), | ||
) | ||
), | ||
) | ||
), | ||
), | ||
listOf( | ||
Shard.App( | ||
name = "app1", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app1-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app1-test1#case2", | ||
duration = 2_000, | ||
), | ||
) | ||
), | ||
) | ||
), | ||
Shard.App( | ||
name = "app2", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app2-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app2-test1#case1", | ||
duration = 1_000, | ||
), | ||
) | ||
), | ||
Shard.Test( | ||
name = "app2-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app2-test1#case2", | ||
), | ||
) | ||
), | ||
) | ||
), | ||
) | ||
) | ||
|
||
private val expected = """ | ||
[ | ||
[ | ||
{ | ||
"name": "app1", | ||
"tests": [ | ||
{ | ||
"name": "app1-test1", | ||
"cases": [ | ||
{ | ||
"name": "app1-test1#case1", | ||
"duration": 10000 | ||
} | ||
] | ||
} | ||
] | ||
} | ||
], | ||
[ | ||
{ | ||
"name": "app1", | ||
"tests": [ | ||
{ | ||
"name": "app1-test1", | ||
"cases": [ | ||
{ | ||
"name": "app1-test1#case2", | ||
"duration": 2000 | ||
} | ||
] | ||
} | ||
] | ||
}, | ||
{ | ||
"name": "app2", | ||
"tests": [ | ||
{ | ||
"name": "app2-test1", | ||
"cases": [ | ||
{ | ||
"name": "app2-test1#case1", | ||
"duration": 1000 | ||
} | ||
] | ||
}, | ||
{ | ||
"name": "app2-test1", | ||
"cases": [ | ||
{ | ||
"name": "app2-test1#case2", | ||
"duration": 120 | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
] | ||
""".trimIndent() | ||
|
||
@Test | ||
fun testDumpToFile() { | ||
shards.dumpToFile(filePath) | ||
|
||
Assert.assertTrue(file.exists()) | ||
Assert.assertEquals(expected, file.readText().trim()) | ||
} | ||
|
||
@After | ||
fun cleanUp () { | ||
file.delete() | ||
} | ||
} |
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
Oops, something went wrong.