Skip to content
This repository has been archived by the owner on Dec 7, 2019. It is now read-only.

Commit

Permalink
Integrate HTML report with Composer! (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
artem-zinnatullin authored Jun 7, 2017
1 parent 270c1c1 commit 5448983
Show file tree
Hide file tree
Showing 11 changed files with 267 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ coverage/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
composer/src/main/resources/html-report
7 changes: 5 additions & 2 deletions ci/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fi

docker build -t composer:latest ci/docker

BUILD_COMMAND=""
BUILD_COMMAND="set -e && "

BUILD_COMMAND+="echo 'Java version:' && java -version && "
BUILD_COMMAND+="echo 'Node.js version:' && node --version && "
Expand All @@ -28,7 +28,10 @@ BUILD_COMMAND+="cd /opt/project/html-report && "
BUILD_COMMAND+="rm -rf node_modules && "
BUILD_COMMAND+="npm install && "
BUILD_COMMAND+="npm run build && "
BUILD_COMMAND+="cd - && "
BUILD_COMMAND+="cd /opt/project && "
BUILD_COMMAND+="rm -rf composer/src/main/resources/html-report/ && "
BUILD_COMMAND+="mkdir -p composer/src/main/resources/html-report/ && "
BUILD_COMMAND+="cp -R html-report/build/* composer/src/main/resources/html-report/ && "

# Build Composer.
BUILD_COMMAND+="echo 'Building Composer...' && "
Expand Down
3 changes: 2 additions & 1 deletion composer/src/main/kotlin/com/gojuno/composer/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.google.gson.Gson
import rx.Observable
import rx.schedulers.Schedulers
import java.io.File
import java.util.*

sealed class Exit(val code: Int, val message: String?) {
object Ok : Exit(code = 0, message = null)
Expand Down Expand Up @@ -113,7 +114,7 @@ fun main(rawArgs: Array<String>) {
}
}
.flatMap { suites ->
writeHtmlReport(gson, suites, File(args.outputDirectory, "html-report"))
writeHtmlReport(gson, suites, File(args.outputDirectory, "html-report"), Date())
.andThen(Observable.just(suites))
}
.toBlocking()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ data class HtmlFullSuite(

fun Suite.toHtmlFullSuite(id: String, htmlReportDir: File) = HtmlFullSuite(
id = id,
tests = tests.map { it.toHtmlFullTest(htmlReportDir).toHtmlShortTest() },
tests = tests.map { it.toHtmlFullTest(suiteId = id, htmlReportDir = htmlReportDir).toHtmlShortTest() },
passedCount = passedCount,
ignoredCount = ignoredCount,
failedCount = failedCount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import java.util.concurrent.TimeUnit.NANOSECONDS

data class HtmlFullTest(

@SerializedName("suite_id")
val suiteId: String,

@SerializedName("package_name")
val packageName: String,

Expand Down Expand Up @@ -56,7 +59,8 @@ data class HtmlFullTest(
}
}

fun AdbDeviceTest.toHtmlFullTest(htmlReportDir: File) = HtmlFullTest(
fun AdbDeviceTest.toHtmlFullTest(suiteId: String, htmlReportDir: File) = HtmlFullTest(
suiteId = suiteId,
packageName = className.substringBeforeLast("."),
className = className.substringAfterLast("."),
name = testName,
Expand Down
86 changes: 81 additions & 5 deletions composer/src/main/kotlin/com/gojuno/composer/html/HtmlReport.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ package com.gojuno.composer.html

import com.gojuno.composer.Suite
import com.google.gson.Gson
import org.apache.commons.lang3.StringEscapeUtils
import rx.Completable
import java.io.File
import java.io.InputStream
import java.text.SimpleDateFormat
import java.util.*

/**
* Following file tree structure will be created:
* - index.json
* - suites/suiteId.json
* - suites/deviceId/testId.json
*/
fun writeHtmlReport(gson: Gson, suites: List<Suite>, outputDir: File): Completable = Completable.fromCallable {
fun writeHtmlReport(gson: Gson, suites: List<Suite>, outputDir: File, date: Date): Completable = Completable.fromCallable {
outputDir.mkdirs()

val htmlIndexJson = gson.toJson(
Expand All @@ -20,18 +24,62 @@ fun writeHtmlReport(gson: Gson, suites: List<Suite>, outputDir: File): Completab
)
)

File(outputDir, "index.json").writeText(htmlIndexJson)

val date = SimpleDateFormat("HH:mm:ss z, MMM d yyyy").apply { timeZone = TimeZone.getTimeZone("UTC") }.format(date)

val appJs = File(outputDir, "app.min.js")
inputStreamFromResources("html-report/app.min.js").copyTo(appJs.outputStream())

val appCss = File(outputDir, "app.min.css")
inputStreamFromResources("html-report/app.min.css").copyTo(appCss.outputStream())

// index.html is a page that can render all kinds of inner pages: Index, Suite, Test.
val indexHtml = inputStreamFromResources("html-report/index.html").reader().readText()

val indexHtmlFile = File(outputDir, "index.html")

fun File.relativePathToHtmlDir(): String = outputDir.relativePathTo(this.parentFile).let { relativePath ->
when (relativePath) {
"" -> relativePath
else -> "$relativePath/"
}
}

indexHtmlFile.writeText(indexHtml
.replace("\${relative_path}", indexHtmlFile.relativePathToHtmlDir())
.replace("\${data_json}", "window.mainData = $htmlIndexJson")
.replace("\${date}", date)
.replace("\${log}", "")
)

val suitesDir = File(outputDir, "suites").apply { mkdirs() }

suites.mapIndexed { suiteId, suite ->
File(suitesDir, "$suiteId.json").writeText(gson.toJson(suite.toHtmlFullSuite(id = "$suiteId", htmlReportDir = suitesDir)))
val suiteJson = gson.toJson(suite.toHtmlFullSuite(id = "$suiteId", htmlReportDir = suitesDir))
val suiteHtmlFile = File(suitesDir, "$suiteId.html")

suiteHtmlFile.writeText(indexHtml
.replace("\${relative_path}", suiteHtmlFile.relativePathToHtmlDir())
.replace("\${data_json}", "window.suite = $suiteJson")
.replace("\${date}", date)
.replace("\${log}", "")
)

suite
.tests
.map { it to File(File(suitesDir, "$suiteId"), it.adbDevice.id).apply { mkdirs() } }
.map { (test, testDir) -> test.toHtmlFullTest(htmlReportDir = testDir) to testDir }
.forEach { (htmlFullTest, testDir) -> File(testDir, "${htmlFullTest.id}.json").writeText(gson.toJson(htmlFullTest)) }
.map { (test, testDir) -> Triple(test, test.toHtmlFullTest(suiteId = "$suiteId", htmlReportDir = testDir), testDir) }
.forEach { (test, htmlTest, testDir) ->
val testJson = gson.toJson(htmlTest)
val testHtmlFile = File(testDir, "${htmlTest.id}.html")

testHtmlFile.writeText(indexHtml
.replace("\${relative_path}", testHtmlFile.relativePathToHtmlDir())
.replace("\${data_json}", "window.test = $testJson")
.replace("\${date}", date)
.replace("\${log}", generateLogcatHtml(test.logcat))
)
}
}
}

Expand All @@ -40,3 +88,31 @@ fun writeHtmlReport(gson: Gson, suites: List<Suite>, outputDir: File): Completab
* See https://youtrack.jetbrains.com/issue/KT-14056
*/
fun File.relativePathTo(base: File): String = absoluteFile.toRelativeString(base.absoluteFile)

fun inputStreamFromResources(path: String): InputStream = Suite::class.java.classLoader.getResourceAsStream(path)

fun generateLogcatHtml(logcatOutput: File): String = when (logcatOutput.exists()) {
false -> ""
true -> logcatOutput
.readLines()
.map { line -> """<div class="log__${cssClassForLogcatLine(line)}">${StringEscapeUtils.escapeXml11(line)}</div>""" }
.fold(StringBuilder("""<div class="content"><div class="card log">""")) { stringBuilder, line ->
stringBuilder.appendln(line)
}
.let { it.appendln("""</div></div>""") }
.let { it.toString() }
}

fun cssClassForLogcatLine(logcatLine: String): String {
// Logcat line example: `06-07 16:55:14.490 2100 2100 I MicroDetectionWorker: #onError(false)`
// First letter is Logcat level.
return when (logcatLine.firstOrNull { it.isLetter() }) {
'V' -> "verbose"
'D' -> "debug"
'I' -> "info"
'W' -> "warning"
'E' -> "error"
'A' -> "assert"
else -> "default"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class HtmlFullSuiteSpec : Spek({
failedCount = suite.failedCount,
durationMillis = NANOSECONDS.toMillis(suite.durationNanos),
devices = suite.devices.map { it.toHtmlDevice(htmlReportDir = testFile()) },
tests = suite.tests.map { it.toHtmlFullTest(htmlReportDir = testFile()).toHtmlShortTest() }
tests = suite.tests.map { it.toHtmlFullTest(suiteId = "testSuite", htmlReportDir = testFile()).toHtmlShortTest() }
))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ class HtmlFullTestSpec : Spek({
screenshots = listOf(testFile(), testFile())
)

val htmlTest = adbDeviceTest.toHtmlFullTest(htmlReportDir = testFile().parentFile)
val htmlTest = adbDeviceTest.toHtmlFullTest(suiteId = "testSuite", htmlReportDir = testFile().parentFile)

it("converts AdbDeviceTest to HtmlFullTest") {
assertThat(htmlTest).isEqualTo(HtmlFullTest(
suiteId = "testSuite",
packageName = "com.gojuno.example",
className = "TestClass",
name = adbDeviceTest.testName,
Expand Down
Loading

0 comments on commit 5448983

Please sign in to comment.