com.rubensousa.carioca.android.allure.gradle.AllureReportGenerator.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-allure-gradle-plugin Show documentation
Show all versions of android-allure-gradle-plugin Show documentation
Plugin that generates instrumented test reports for allure
The newest version!
/*
* Copyright 2024 Rúben Sousa
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.rubensousa.carioca.android.allure.gradle
import com.google.common.io.Files
import com.rubensousa.carioca.report.json.JsonAttachment
import com.rubensousa.carioca.report.json.JsonExecutionReport
import com.rubensousa.carioca.report.json.JsonExecutionStatus
import com.rubensousa.carioca.report.json.JsonReportParser
import com.rubensousa.carioca.report.json.JsonStage
import com.rubensousa.carioca.report.json.JsonTestReport
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToStream
import java.io.File
import java.util.UUID
@OptIn(ExperimentalSerializationApi::class)
class AllureReportGenerator(
private val logcatFinder: LogcatFileFinder,
private val parser: JsonReportParser,
) {
private val stageValue = "finished"
private val brokenStatus = "broken"
fun generateReport(
testResultDir: File,
logcatOutputDir: File,
outputDir: File,
keepLogcatOnSuccess: Boolean,
deleteOriginalReports: Boolean
) {
val reportDir = parser.findReportDir(testResultDir) ?: return
val testReportFiles = parser.parseTestReports(reportDir)
val logcatFiles = logcatFinder.find(logcatOutputDir)
outputDir.mkdirs()
testReportFiles.forEach { testReportFile ->
val originalReport = createTestReport(testReportFile.report)
val logcatFile = if (keepLogcatOnSuccess || originalReport.status == brokenStatus) {
logcatFiles[originalReport.fullName]
} else {
null
}
moveTestReport(
report = originalReport,
inputDir = reportDir,
outputDir = outputDir,
logcatFile = logcatFile
)
if (deleteOriginalReports) {
testReportFile.file.delete()
}
createContainerReport(testReportFile.report)?.let {
moveContainerReport(it, testResultDir, outputDir)
}
}
}
private fun moveTestReport(
report: AllureTestReport,
inputDir: File,
outputDir: File,
logcatFile: File?,
) {
val reportFile = File(outputDir, "${report.uuid}-result.json")
var newReport = moveTestAttachments(report, inputDir, outputDir)
if (logcatFile != null) {
val newFile = moveAttachment(
src = logcatFile,
outputDir = outputDir,
fileId = UUID.randomUUID().toString()
)
newReport = newReport.copy(
attachments = newReport.attachments + listOf(
AllureAttachment(
name = "Logcat",
source = newFile.name,
type = "text/plain"
)
)
)
}
writeToFile(newReport, reportFile)
}
private fun moveTestAttachments(
report: AllureTestReport,
inputDir: File,
outputDir: File,
): AllureTestReport {
return report.copy(
attachments = moveAttachments(report.attachments, inputDir, outputDir),
steps = moveStepAttachments(report.steps, inputDir, outputDir)
)
}
private fun moveStepAttachments(
steps: List,
inputDir: File,
outputDir: File,
): List {
val newSteps = mutableListOf()
steps.forEach { step ->
newSteps.add(
step.copy(
steps = moveStepAttachments(
steps = step.steps,
inputDir = inputDir,
outputDir = outputDir
),
attachments = moveAttachments(
attachments = step.attachments,
inputDir = inputDir,
outputDir = outputDir
)
)
)
}
return newSteps
}
/**
* Ensures all attachments are also renamed to match the allure spec of having "-attachment" as suffix
*/
private fun moveAttachments(
attachments: List,
inputDir: File,
outputDir: File,
): List {
val newAttachments = mutableListOf()
attachments.forEach { attachment ->
val attachmentFile = File(inputDir, attachment.source)
if (attachmentFile.exists()) {
val newFile = moveAttachment(
src = attachmentFile,
outputDir = outputDir,
fileId = attachmentFile.nameWithoutExtension
)
newAttachments.add(
attachment.copy(
source = newFile.name
)
)
}
}
return newAttachments
}
private fun moveAttachment(
src: File,
outputDir: File,
fileId: String,
): File {
val newFile = File(outputDir, fileId + "-attachment.${src.extension}")
Files.move(src, newFile)
return newFile
}
private fun moveContainerReport(report: AllureContainerReport, inputDir: File, outputDir: File) {
val reportFile = File(outputDir, "${report.uuid}-container.json")
val newReport = report.copy(
befores = moveStepAttachments(report.befores, inputDir, outputDir),
afters = moveStepAttachments(report.afters, inputDir, outputDir)
)
writeToFile(newReport, reportFile)
}
private inline fun writeToFile(content: T, file: File) {
try {
if (!file.exists()) {
file.createNewFile()
}
file.outputStream().use {
Json.encodeToStream(content, it)
}
} catch (exception: Exception) {
println(exception.stackTraceToString())
}
}
private fun createTestReport(test: JsonTestReport): AllureTestReport {
val execution = test.execution
return AllureTestReport(
uuid = execution.id,
historyId = test.id,
testCaseId = test.id,
fullName = test.fullName,
links = createLinks(test),
labels = createLabels(test),
name = test.title,
description = test.description,
status = getStatus(execution.status),
statusDetails = getStatusDetail(execution),
stage = stageValue,
steps = test.stages.map { mapStage(it) },
attachments = mapAttachments(test.attachments),
start = execution.startTime,
stop = execution.endTime
)
}
private fun createContainerReport(test: JsonTestReport): AllureContainerReport? {
if (test.beforeStages.isEmpty() && test.afterStages.isEmpty()) {
return null
}
return AllureContainerReport(
uuid = UUID.randomUUID().toString(),
name = test.title,
children = listOf(test.execution.id),
befores = test.beforeStages.map { mapStage(it) },
afters = test.afterStages.map { mapStage(it) },
start = test.execution.startTime,
stop = test.execution.endTime
)
}
private fun mapStage(stage: JsonStage): AllureStep {
val execution = stage.execution
return AllureStep(
name = stage.name,
status = getStatus(execution.status),
statusDetails = getStatusDetail(execution),
stage = stageValue,
attachments = mapAttachments(stage.attachments),
start = execution.startTime,
stop = execution.endTime,
steps = stage.stages.map { step ->
mapStage(step)
},
parameters = stage.parameters.map {
AllureParameter(
name = it.key,
value = it.value
)
}
)
}
private fun mapAttachments(list: List): List {
return list.map { attachment ->
AllureAttachment(
name = attachment.description,
source = attachment.path,
type = attachment.mimeType
)
}
}
private fun getStatus(reportStatus: JsonExecutionStatus): String {
return when (reportStatus) {
JsonExecutionStatus.PASSED -> "passed"
JsonExecutionStatus.IGNORED -> "skipped"
else -> brokenStatus
}
}
private fun createLabels(test: JsonTestReport): List {
return listOf(
AllureLabel(
name = "package",
value = test.packageName
),
AllureLabel(
name = "testClass",
value = test.className
),
AllureLabel(
name = "testMethod",
value = test.methodName
),
AllureLabel(
name = "suite",
value = test.className
),
AllureLabel(
name = "framework",
value = "junit4"
),
AllureLabel(
name = "language",
value = "kotlin"
),
)
}
private fun createLinks(test: JsonTestReport): List {
return test.links.map { link ->
AllureLink(
name = link,
url = link,
type = "general"
)
}
}
private fun getStatusDetail(report: JsonExecutionReport): AllureStatusDetail? {
val failureMessage = report.failureMessage
val failureStacktrace = report.failureStacktrace.orEmpty()
return if (failureMessage == null) {
null
} else {
AllureStatusDetail(
known = false,
muted = false,
flaky = false,
message = failureMessage,
trace = failureStacktrace
)
}
}
}