Skip to content

Instantly share code, notes, and snippets.

@eakurnikov
Created March 11, 2021 15:00
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 eakurnikov/abe8c7a8ce1e96078ce1bf189eac5522 to your computer and use it in GitHub Desktop.
Save eakurnikov/abe8c7a8ce1e96078ce1bf189eac5522 to your computer and use it in GitHub Desktop.
class TestResultsFilesProvider(
private val testResultsDirsProvider: TestResultsDirsProvider,
private val addTimestamps: Boolean
) {
private val logcatRootDir = File("logcat")
private val screenshotsRootDir = File("screenshots")
private val videoRootDir = File("video")
private val viewHierarchy = File("view_hierarchy")
fun provideLogcatFile(tag: String): File =
testResultsDirsProvider
.provideCleared(logcatRootDir)
.resolve(getFileName(tag, FileExtension.TXT))
fun provideScreenshotFile(tag: String): File =
testResultsDirsProvider
.provideCleared(screenshotsRootDir)
.resolve(getFileName(tag, FileExtension.PNG))
fun provideVideoFile(tag: String): File =
testResultsDirsProvider
.provideCleared(videoRootDir)
.resolve(getFileName(tag, FileExtension.MP4))
fun provideViewHierarchyFile(tag: String): File =
testResultsDirsProvider
.provideCleared(viewHierarchy)
.resolve(getFileName(tag, FileExtension.XML))
private fun getFileName(tag: String, ext: FileExtension): String =
"${if (addTimestamps) "${System.currentTimeMillis()}_" else ""}$tag$ext"
}
class TestResultsDirsProvider(
private val groupByRunNumbers: Boolean
) {
private val dirProvider = DirProvider()
private val testRunsCount = mutableMapOf<TestMethod, Int>()
fun provideCleared(path: File, subDir: String? = null): File =
dirProvider.provideCleared(provide(path, subDir))
fun provide(path: File, subDir: String? = null): File =
getDir(dirProvider.provide(path), subDir)
fun onNewTestRun() {
val testMethod = Thread.currentThread().stackTrace.findTestMethod()
testRunsCount[testMethod] = (testRunsCount[testMethod] ?: 0) + 1
}
private fun getDir(rootDir: File, subDir: String? = null): File {
val testMethod: TestMethod = Thread.currentThread().stackTrace.findTestMethod()
val runNumber: Int = testRunsCount[testMethod] ?: 1
val dirName: String = getDirName(testMethod, runNumber)
return if (subDir != null) {
rootDir.resolve(subDir).resolve(dirName)
} else {
rootDir.resolve(dirName)
}
}
private fun getDirName(testMethod: TestMethod, runNumber: Int): String {
val clearedClassName: String =
testMethod.className.replace("[^A-Za-z0-9._-]".toRegex(), "_")
val directDirName = "$clearedClassName${File.separator}${testMethod.methodName}"
return "${if (groupByRunNumbers) "run_$runNumber${File.separator}" else ""}$directDirName"
}
}
class DirProvider {
private val clearedDirs = HashSet<File>()
fun provide(path: File): File {
val dir: File = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Environment.getExternalStorageDirectory().resolve(path)
} else {
TestEnvironment.targetContext.applicationContext.getDir(
path.canonicalPath,
Context.MODE_WORLD_READABLE
)
}
if (!dir.exists()) {
dir.createDirectoryRWX()
}
return dir
}
fun provideCleared(dir: File): File {
if (!clearedDirs.contains(dir)) {
clearDir(path = dir, inclusive = false)
clearedDirs.add(dir)
}
if (!dir.exists()) {
dir.createDirectoryRWX()
}
return dir
}
private fun clearDir(path: File, inclusive: Boolean) {
if (path.isDirectory) {
path.listFiles()?.run {
forEach { child -> clearDir(path = child, inclusive = true) }
}
}
if (inclusive) {
path.delete()
}
}
private fun File.createDirectoryRWX() {
mkdirs()
setReadable(true, false)
setWritable(true, false)
setExecutable(true, false)
}
}
private const val TEST_CASE_CLASS_JUNIT_4 = "org.junit.runners.model.FrameworkMethod$1"
private const val TEST_CASE_METHOD_JUNIT_4 = "runReflectiveCall"
fun Array<StackTraceElement>.findTestMethod(): TestMethod =
findTestClassTraceElement().let { TestMethod(it.className, it.methodName) }
fun Array<StackTraceElement>.findTestClassTraceElement(): StackTraceElement =
this.withIndex().reversed()
.find { (_, element) -> element.isJunit4() }
?.let { (i, _) -> extractStackElement(i) }
?: throw IllegalArgumentException(
"Could not find test class! Trace: ${this.map { it.toString() }}"
)
fun StackTraceElement.isJunit4(): Boolean =
TEST_CASE_CLASS_JUNIT_4 == className && TEST_CASE_METHOD_JUNIT_4 == methodName
@Suppress("MagicNumber")
fun Array<StackTraceElement>.extractStackElement(i: Int): StackTraceElement =
this[if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) i - 2 else i - 3]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment