diff --git a/README.md b/README.md index a84a4a8..ebebe4e 100644 --- a/README.md +++ b/README.md @@ -105,11 +105,11 @@ rendered: ([info](https://github.blog/2022-02-14-include-diagrams-markdown-files ```mermaid gantt -dateFormat YYYY-MM-DDTHH:mm:ss.SSSZZ -axisFormat %H:%M:%S.%L -section Test1Spec -test 2 - 120 ms :active, 2022-11-08T20:46:17.854+0100, 2022-11-08T20:46:17.974+0100 -test 1 - 213 ms :active, 2022-11-08T20:46:17.854+0100, 2022-11-08T20:46:18.067+0100 + dateFormat YYYY-MM-DDTHH:mm:ss.SSSZZ + axisFormat %H:%M:%S.%L + section Test1Spec + test 2 - 120 ms: active, 2022-11-08T20:46:17.854+0100, 2022-11-08T20:46:17.974+0100 + test 1 - 213 ms: active, 2022-11-08T20:46:17.854+0100, 2022-11-08T20:46:18.067+0100 ``` # Configuration @@ -125,6 +125,8 @@ Options: | `formats.json.enabled` | boolean | Generate report in json format | `true` | | `formats.mermaid.enabled` | boolean | Generate report in mermaid text format | `true` | | `shiftTimestampsToStartOfDay` | boolean | Adjust the earliest timestamp to the start of the day | `false` | +| `marks.totalTimeOfAllTests.enabled` | boolean | Enable mark showing total time of all tests | `false` | +| `marks.totalTimeOfAllTests.name` | string | Label used for mark | `total time of all tests` | `build.gradle.kts`: @@ -149,6 +151,12 @@ configure = objectFactory.property(Boolean::class.java).convention(false) open fun formats(action: Action) { action.execute(getFormats()) } + + open fun marks(action: Action) { + action.execute(getMarks()) + } } diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/CreateTestsExecutionReportTask.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/CreateTestsExecutionReportTask.kt index bc0b292..d234495 100644 --- a/src/main/kotlin/io/github/platan/tests_execution_chart/CreateTestsExecutionReportTask.kt +++ b/src/main/kotlin/io/github/platan/tests_execution_chart/CreateTestsExecutionReportTask.kt @@ -1,6 +1,7 @@ package io.github.platan.tests_execution_chart import io.github.platan.tests_execution_chart.config.Formats +import io.github.platan.tests_execution_chart.config.Marks import io.github.platan.tests_execution_chart.report.TestExecutionScheduleReport import io.github.platan.tests_execution_chart.reporters.Logger import io.github.platan.tests_execution_chart.reporters.html.HtmlGanttDiagramReporter @@ -37,6 +38,9 @@ abstract class CreateTestsExecutionReportTask @Inject constructor(objectFactory: @Nested abstract fun getFormats(): Formats + @Nested + abstract fun getMarks(): Marks + @get:Input val shiftTimestampsToStartOfDay: Property = objectFactory.property(Boolean::class.java).convention(false) @@ -57,7 +61,11 @@ abstract class CreateTestsExecutionReportTask @Inject constructor(objectFactory: ) } if (getFormats().getJson().enabled.get()) { - JsonReporter(getFormats().getJson(), customLogger).report(adjustedResults, task.project.buildDir, task.name) + JsonReporter(getFormats().getJson(), customLogger).report( + adjustedResults, + task.project.buildDir, + task.name + ) } if (getFormats().getHtml().enabled.get()) { HtmlGanttDiagramReporter(getFormats().getHtml().toHtmlConfig(), customLogger).report( @@ -71,10 +79,13 @@ abstract class CreateTestsExecutionReportTask @Inject constructor(objectFactory: } private fun adjustResults(results: TestExecutionScheduleReport): TestExecutionScheduleReport { - return if (shiftTimestampsToStartOfDay.get()) { - results.timestampsShiftedToStartOfDay(ZoneId.systemDefault()) - } else { - results + var adjusted = results + if (shiftTimestampsToStartOfDay.get()) { + adjusted = results.timestampsShiftedToStartOfDay(ZoneId.systemDefault()) + } + if (getMarks().getTotalTimeOfAllTests().enabled.get()) { + adjusted = adjusted.addTotalTimeOfAllTestsMark(getMarks().getTotalTimeOfAllTests().name.get()) } + return adjusted } } diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/TestsExecutionReportPlugin.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/TestsExecutionReportPlugin.kt index 71f2f66..78b1910 100644 --- a/src/main/kotlin/io/github/platan/tests_execution_chart/TestsExecutionReportPlugin.kt +++ b/src/main/kotlin/io/github/platan/tests_execution_chart/TestsExecutionReportPlugin.kt @@ -51,5 +51,11 @@ class TestsExecutionReportPlugin : Plugin { .set(createTestsExecutionReportExtension.getFormats().getMermaid().outputLocation) task.shiftTimestampsToStartOfDay.set(createTestsExecutionReportExtension.shiftTimestampsToStartOfDay) + task.getMarks().getTotalTimeOfAllTests().enabled.set( + createTestsExecutionReportExtension.getMarks().getTotalTimeOfAllTests().enabled + ) + val name = createTestsExecutionReportExtension.getMarks().getTotalTimeOfAllTests().name.get() + require(name.isNotBlank()) { "marks.totalTimeOfAllTests.name cannot be blank" } + task.getMarks().getTotalTimeOfAllTests().name.set(name) } } diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/config/Mark.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/config/Mark.kt new file mode 100644 index 0000000..41c5dcd --- /dev/null +++ b/src/main/kotlin/io/github/platan/tests_execution_chart/config/Mark.kt @@ -0,0 +1,15 @@ +package io.github.platan.tests_execution_chart.config + +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import javax.inject.Inject + +abstract class Mark @Inject constructor(objectFactory: ObjectFactory, name: String) { + + @get:Input + val enabled: Property = objectFactory.property(Boolean::class.java).convention(false) + + @get:Input + val name: Property = objectFactory.property(String::class.java).convention(name) +} diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/config/Marks.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/config/Marks.kt new file mode 100644 index 0000000..0b076a2 --- /dev/null +++ b/src/main/kotlin/io/github/platan/tests_execution_chart/config/Marks.kt @@ -0,0 +1,14 @@ +package io.github.platan.tests_execution_chart.config + +import org.gradle.api.Action +import org.gradle.api.tasks.Nested + +abstract class Marks { + + @Nested + abstract fun getTotalTimeOfAllTests(): TotalTimeOfAllTestsMark + + open fun totalTimeOfAllTests(action: Action) { + action.execute(getTotalTimeOfAllTests()) + } +} diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/config/TotalTimeOfAllTestsMark.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/config/TotalTimeOfAllTestsMark.kt new file mode 100644 index 0000000..a107967 --- /dev/null +++ b/src/main/kotlin/io/github/platan/tests_execution_chart/config/TotalTimeOfAllTestsMark.kt @@ -0,0 +1,7 @@ +package io.github.platan.tests_execution_chart.config + +import org.gradle.api.model.ObjectFactory +import javax.inject.Inject + +abstract class TotalTimeOfAllTestsMark @Inject constructor(objectFactory: ObjectFactory) : + Mark(objectFactory, "total time of all tests") diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/report/Mark.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/report/Mark.kt new file mode 100644 index 0000000..8cad591 --- /dev/null +++ b/src/main/kotlin/io/github/platan/tests_execution_chart/report/Mark.kt @@ -0,0 +1,14 @@ +package io.github.platan.tests_execution_chart.report + +import kotlinx.serialization.Serializable +import java.time.Duration + +@Serializable +data class Mark( + var name: String, + var timestamp: Long +) { + fun shiftTimestamps(timeShift: Duration): Mark { + return this.copy(timestamp = this.timestamp + timeShift.toMillis()) + } +} diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReport.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReport.kt index 62629eb..184bfa2 100644 --- a/src/main/kotlin/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReport.kt +++ b/src/main/kotlin/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReport.kt @@ -7,7 +7,10 @@ import java.time.ZoneId import java.time.temporal.ChronoUnit @Serializable -data class TestExecutionScheduleReport(val results: List) { +data class TestExecutionScheduleReport(val results: List, val marks: List) { + + constructor(results: List) : this(results, emptyList()) + fun timestampsShiftedToStartOfDay(zoneId: ZoneId): TestExecutionScheduleReport { return when { results.isEmpty() -> this @@ -15,8 +18,18 @@ data class TestExecutionScheduleReport(val results: List) { val minStartTime = Instant.ofEpochMilli(results.minBy { it.startTime }.startTime) val targetMinStartTime = minStartTime.atZone(zoneId).truncatedTo(ChronoUnit.DAYS).toInstant() val timeShift = Duration.between(minStartTime, targetMinStartTime) - return TestExecutionScheduleReport(results.map { it.shiftTimestamps(timeShift) }) + return TestExecutionScheduleReport( + results.map { it.shiftTimestamps(timeShift) }, + marks.map { it.shiftTimestamps(timeShift) } + ) } } } + + fun addTotalTimeOfAllTestsMark(markName: String): TestExecutionScheduleReport { + val minStartTime = results.minBy { it.startTime }.startTime + val totalDurationOfAllTestsMs = results.sumOf { it.durationMs } + val totalTimeOfAllTestsMark = minStartTime + totalDurationOfAllTestsMs + return this.copy(marks = listOf(Mark(markName, totalTimeOfAllTestsMark))) + } } diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/MermaidGanttDiagram.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/MermaidGanttDiagram.kt deleted file mode 100644 index d50d214..0000000 --- a/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/MermaidGanttDiagram.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.platan.tests_execution_chart.reporters.mermaid - -data class MermaidGanttDiagram(val dateFormat: String, val axisFormat: String, val sections: List
) { - data class Section(val name: String, val rows: List) { - data class Row(val name: String, val type: String?, val start: Long, val end: Long) - } - - class MermaidGanttDiagramBuilder { - private val sectionsMap: MutableMap> = mutableMapOf() - fun add(sectionName: String, rowName: String, type: String?, start: Long, end: Long) { - val section: MutableList = - sectionsMap.getOrPut(sectionName) { mutableListOf() } - section.add(Section.Row(rowName, type, start, end)) - } - - fun build(dateFormat: String, axisFormat: String): MermaidGanttDiagram { - return MermaidGanttDiagram( - dateFormat, - axisFormat, - sectionsMap.entries.map { sectionEntry -> - Section( - sectionEntry.key, - sectionEntry.value.map { row -> - Section.Row( - row.name, - row.type, - row.start, - row.end - ) - } - ) - } - ) - } - } -} diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/MermaidGanttDiagramFormatter.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/MermaidGanttDiagramFormatter.kt deleted file mode 100644 index fd60e6b..0000000 --- a/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/MermaidGanttDiagramFormatter.kt +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.platan.tests_execution_chart.reporters.mermaid - -import java.text.SimpleDateFormat -import java.util.Date - -internal class MermaidGanttDiagramFormatter { - - fun format(diagram: MermaidGanttDiagram, dateFormat: String): String { - val ganttDiagram = StringBuilder() - ganttDiagram.append("gantt\n") - ganttDiagram.append("dateFormat ${diagram.dateFormat}\n") - ganttDiagram.append("axisFormat ${diagram.axisFormat}\n") - val format = SimpleDateFormat(dateFormat) - diagram.sections.forEach { section -> - ganttDiagram.append("section ${escape(section.name)}\n") - section.rows.forEach { row -> - var status = "" - if (row.type != null) { - status = "${row.type}, " - } - val end = format.format(Date(row.end)) - val start = format.format(Date(row.start)) - ganttDiagram.append("${escape(row.name)} :${status}$start, $end\n") - } - } - return ganttDiagram.toString() - } - - private fun escape(str: String): String { - return str - // remove # before replacing other characters with entity containing # - .replace("#", "") - // replace ; before replacing other characters with entity containing ; - .replace(";", "#semi;") - .replace(":", "#colon;") // https://github.com/mermaid-js/mermaid/issues/742 - } -} diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/TestExecutionMermaidDiagramFormatter.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/TestExecutionMermaidDiagramFormatter.kt index 098a471..3104140 100644 --- a/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/TestExecutionMermaidDiagramFormatter.kt +++ b/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/TestExecutionMermaidDiagramFormatter.kt @@ -1,6 +1,8 @@ package io.github.platan.tests_execution_chart.reporters.mermaid import io.github.platan.tests_execution_chart.report.TestExecutionScheduleReport +import io.github.platan.tests_execution_chart.reporters.mermaid.core.MermaidGanttDiagram +import io.github.platan.tests_execution_chart.reporters.mermaid.core.MermaidGanttDiagramFormatter class TestExecutionMermaidDiagramFormatter { @@ -10,9 +12,20 @@ class TestExecutionMermaidDiagramFormatter { fun format(report: TestExecutionScheduleReport): String { val diagramBuilder = MermaidGanttDiagram.MermaidGanttDiagramBuilder() - report.results.forEach { - val testNameWithDuration = "${it.testName} - ${it.endTime.minus(it.startTime)} ms" - diagramBuilder.add(it.className!!, testNameWithDuration, types[it.resultType], it.startTime, it.endTime) + report.results.groupBy { result -> result.className }.forEach { (className, results) -> + diagramBuilder.addSection(className.orEmpty()) + results.forEach { + val testNameWithDuration = "${it.testName} - ${it.endTime.minus(it.startTime)} ms" + diagramBuilder.addTask( + testNameWithDuration, + types[it.resultType], + it.startTime, + it.endTime + ) + } + } + report.marks.forEach { mark -> + diagramBuilder.addMilestone(mark.name, mark.timestamp) } val diagram = diagramBuilder.build("YYYY-MM-DDTHH:mm:ss.SSSZZ", "%H:%M:%S.%L") return MermaidGanttDiagramFormatter().format(diagram, "yyyy-MM-dd'T'HH:mm:ss.SSSZ") diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagram.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagram.kt new file mode 100644 index 0000000..a3233ac --- /dev/null +++ b/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagram.kt @@ -0,0 +1,50 @@ +package io.github.platan.tests_execution_chart.reporters.mermaid.core + +data class MermaidGanttDiagram( + val dateFormat: String, + val axisFormat: String, + val entries: List +) { + sealed interface Entry + data class Section(val name: String) : Entry { + init { + require(name.isNotBlank()) { "Section name cannot be blank" } + } + } + + data class Task(val name: String, val type: String?, val start: Long, val end: Long) : Entry { + init { + require(name.isNotBlank()) { "Task name cannot be blank" } + } + } + + data class Milestone(val name: String, val timestamp: Long) : Entry { + init { + require(name.isNotBlank()) { "Mark name cannot be blank" } + } + } + + class MermaidGanttDiagramBuilder { + private val entries: MutableList = mutableListOf() + + fun addTask(name: String, type: String?, start: Long, end: Long) { + entries.add(Task(name, type, start, end)) + } + + fun addSection(name: String) { + entries.add(Section(name)) + } + + fun addMilestone(name: String, timestamp: Long) { + entries.add(Milestone(name, timestamp)) + } + + fun build(dateFormat: String, axisFormat: String): MermaidGanttDiagram { + return MermaidGanttDiagram( + dateFormat, + axisFormat, + entries.toList() + ) + } + } +} diff --git a/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagramFormatter.kt b/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagramFormatter.kt new file mode 100644 index 0000000..fe561b3 --- /dev/null +++ b/src/main/kotlin/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagramFormatter.kt @@ -0,0 +1,49 @@ +package io.github.platan.tests_execution_chart.reporters.mermaid.core + +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter + +internal class MermaidGanttDiagramFormatter { + + fun format(diagram: MermaidGanttDiagram, dateFormat: String): String { + val ganttDiagram = StringBuilder() + ganttDiagram.append("gantt\n") + ganttDiagram.append("dateFormat ${diagram.dateFormat}\n") + ganttDiagram.append("axisFormat ${diagram.axisFormat}\n") + val format = DateTimeFormatter.ofPattern(dateFormat).withZone(ZoneId.systemDefault()) + diagram.entries.forEach { entry -> + when (entry) { + is MermaidGanttDiagram.Section -> { + ganttDiagram.append("section ${escape(entry.name)}\n") + } + + is MermaidGanttDiagram.Task -> { + var status = "" + if (entry.type != null) { + status = "${entry.type}, " + } + val end = format.format(Instant.ofEpochMilli(entry.end)) + val start = format.format(Instant.ofEpochMilli(entry.start)) + ganttDiagram.append("${escape(entry.name)} :${status}$start, $end\n") + } + + is MermaidGanttDiagram.Milestone -> { + val timestamp = format.format(Instant.ofEpochMilli(entry.timestamp)) + ganttDiagram.append("${escape(entry.name)} : milestone, $timestamp, 0\n") + } + } + } + return ganttDiagram.toString() + } + + private fun escape(str: String): String { + return str + // warning: order of replacements below matters + // replace ; before replacing other characters with entity containing ; + .replace(";", "#semi;") + // replace # but not in #semi; + .replace(Regex("#(?!semi;)"), "#35;") + .replace(":", "#colon;") // https://github.com/mermaid-js/mermaid/issues/742 + } +} diff --git a/src/test/groovy/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReportMarksTest.groovy b/src/test/groovy/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReportMarksTest.groovy new file mode 100644 index 0000000..6bd7e60 --- /dev/null +++ b/src/test/groovy/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReportMarksTest.groovy @@ -0,0 +1,28 @@ +package io.github.platan.tests_execution_chart.report + +import spock.lang.Specification + +import java.time.Instant + +class TestExecutionScheduleReportMarksTest extends Specification { + + def "add mark with total time of all tests when report has many entries"() { + given: + def report = new TestExecutionScheduleReport([ + new TimedTestResult('class', 'test', toEpochMilli('2023-03-10T19:00:02Z'), toEpochMilli('2023-03-10T19:00:06Z'), 'passed'), + new TimedTestResult('class', 'test', toEpochMilli('2023-03-10T19:00:03Z'), toEpochMilli('2023-03-10T19:00:07Z'), 'passed'), + ]) + + when: + def resultWithMarks = report.addTotalTimeOfAllTestsMark('total time of all tests') + + then: + resultWithMarks.results == report.results + resultWithMarks.marks == [new Mark('total time of all tests', toEpochMilli('2023-03-10T19:00:10Z'))] + } + + + private static long toEpochMilli(String instant) { + Instant.parse(instant).toEpochMilli() + } +} diff --git a/src/test/groovy/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReportTest.groovy b/src/test/groovy/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReportTest.groovy index 3764533..d62dc52 100644 --- a/src/test/groovy/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReportTest.groovy +++ b/src/test/groovy/io/github/platan/tests_execution_chart/report/TestExecutionScheduleReportTest.groovy @@ -12,7 +12,7 @@ class TestExecutionScheduleReportTest extends Specification { def report = new TestExecutionScheduleReport([ new TimedTestResult('class', 'test', toEpochMilli('2023-03-10T19:00:02Z'), toEpochMilli('2023-03-10T19:00:05Z'), 'passed'), new TimedTestResult('class', 'test', toEpochMilli('2023-03-10T19:00:03Z'), toEpochMilli('2023-03-10T19:00:08Z'), 'passed'), - ]) + ], [new Mark('mark1', toEpochMilli('2023-03-10T19:00:05Z'))]) when: def shiftedResult = report.timestampsShiftedToStartOfDay(ZoneOffset.ofHours(0)) @@ -21,7 +21,7 @@ class TestExecutionScheduleReportTest extends Specification { shiftedResult == new TestExecutionScheduleReport([ new TimedTestResult('class', 'test', toEpochMilli('2023-03-10T00:00:00Z'), toEpochMilli('2023-03-10T00:00:03Z'), 'passed'), new TimedTestResult('class', 'test', toEpochMilli('2023-03-10T00:00:01Z'), toEpochMilli('2023-03-10T00:00:06Z'), 'passed'), - ]) + ], [new Mark('mark1', toEpochMilli('2023-03-10T00:00:03Z'))]) } def "shift timestamps when report has one entry"() { diff --git a/src/test/groovy/io/github/platan/tests_execution_chart/reporters/mermaid/TestExecutionMermaidDiagramFormatterSpec.groovy b/src/test/groovy/io/github/platan/tests_execution_chart/reporters/mermaid/TestExecutionMermaidDiagramFormatterSpec.groovy new file mode 100644 index 0000000..7e1622d --- /dev/null +++ b/src/test/groovy/io/github/platan/tests_execution_chart/reporters/mermaid/TestExecutionMermaidDiagramFormatterSpec.groovy @@ -0,0 +1,72 @@ +package io.github.platan.tests_execution_chart.reporters.mermaid + +import io.github.platan.tests_execution_chart.report.Mark +import io.github.platan.tests_execution_chart.report.TestExecutionScheduleReportBuilder +import spock.lang.Specification + +import java.time.ZoneOffset + +class TestExecutionMermaidDiagramFormatterSpec extends Specification { + + def "should format report with test results"() { + given: + def getDefault = TimeZone.getDefault() + TimeZone.setDefault(SimpleTimeZone.getTimeZone(ZoneOffset.ofHours(2))) + def reportBuilder = new TestExecutionScheduleReportBuilder() + reportBuilder.add('Test1', 'test1', 1681402397000, 1681402397100, 'SUCCESS') + reportBuilder.add('Test1', 'test2', 1681402397100, 1681402397300, 'SUCCESS') + reportBuilder.add('Test2', 'test1', 1681402397000, 1681402397100, 'SUCCESS') + def report = reportBuilder.getResults() + + when: + def mermaidReport = new TestExecutionMermaidDiagramFormatter().format(report) + + + def indent = """gantt +dateFormat YYYY-MM-DDTHH:mm:ss.SSSZZ +axisFormat %H:%M:%S.%L +section Test1 +test1 - 100 ms :active, 2023-04-13T18:13:17.000+0200, 2023-04-13T18:13:17.100+0200 +test2 - 200 ms :active, 2023-04-13T18:13:17.100+0200, 2023-04-13T18:13:17.300+0200 +section Test2 +test1 - 100 ms :active, 2023-04-13T18:13:17.000+0200, 2023-04-13T18:13:17.100+0200 +""" + then: + mermaidReport == indent + + cleanup: + TimeZone.setDefault(getDefault) + } + + def "should format report with test results and marks"() { + given: + def getDefault = TimeZone.getDefault() + TimeZone.setDefault(SimpleTimeZone.getTimeZone(ZoneOffset.ofHours(2))) + def reportBuilder = new TestExecutionScheduleReportBuilder() + reportBuilder.add('Test1', 'test1', 1681402397000, 1681402397100, 'SUCCESS') + reportBuilder.add('Test1', 'test2', 1681402397100, 1681402397300, 'SUCCESS') + reportBuilder.add('Test2', 'test1', 1681402397000, 1681402397100, 'SUCCESS') + def results = reportBuilder.getResults() + def report = results.copy(results.results, [new Mark('mark1', 1681402397400)]) + + when: + def mermaidReport = new TestExecutionMermaidDiagramFormatter().format(report) + + + def indent = """gantt +dateFormat YYYY-MM-DDTHH:mm:ss.SSSZZ +axisFormat %H:%M:%S.%L +section Test1 +test1 - 100 ms :active, 2023-04-13T18:13:17.000+0200, 2023-04-13T18:13:17.100+0200 +test2 - 200 ms :active, 2023-04-13T18:13:17.100+0200, 2023-04-13T18:13:17.300+0200 +section Test2 +test1 - 100 ms :active, 2023-04-13T18:13:17.000+0200, 2023-04-13T18:13:17.100+0200 +mark1 : milestone, 2023-04-13T18:13:17.400+0200, 0 +""" + then: + mermaidReport == indent + + cleanup: + TimeZone.setDefault(getDefault) + } +} diff --git a/src/test/groovy/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagramBuilderTest.groovy b/src/test/groovy/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagramBuilderTest.groovy new file mode 100644 index 0000000..d5f39d0 --- /dev/null +++ b/src/test/groovy/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagramBuilderTest.groovy @@ -0,0 +1,42 @@ +package io.github.platan.tests_execution_chart.reporters.mermaid.core + +import spock.lang.Specification + +class MermaidGanttDiagramBuilderTest extends Specification { + + def "should return error for blank mark name"() { + when: + new MermaidGanttDiagram.MermaidGanttDiagramBuilder().addMilestone(name, 0) + + then: + def e = thrown(IllegalArgumentException) + e.message == 'Mark name cannot be blank' + + where: + name << ['', ' '] + } + + def "should return error for blank task name"() { + when: + new MermaidGanttDiagram.MermaidGanttDiagramBuilder().addTask(name, 'active', 0, 1) + + then: + def e = thrown(IllegalArgumentException) + e.message == 'Task name cannot be blank' + + where: + name << ['', ' '] + } + + def "should return error for blank section name"() { + when: + new MermaidGanttDiagram.MermaidGanttDiagramBuilder().addSection(name) + + then: + def e = thrown(IllegalArgumentException) + e.message == 'Section name cannot be blank' + + where: + name << ['', ' '] + } +} diff --git a/src/test/groovy/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagramFormatterTest.groovy b/src/test/groovy/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagramFormatterTest.groovy new file mode 100644 index 0000000..e29e33d --- /dev/null +++ b/src/test/groovy/io/github/platan/tests_execution_chart/reporters/mermaid/core/MermaidGanttDiagramFormatterTest.groovy @@ -0,0 +1,75 @@ +package io.github.platan.tests_execution_chart.reporters.mermaid.core + + +import spock.lang.Specification + +import java.time.ZoneOffset + +class MermaidGanttDiagramFormatterTest extends Specification { + + def "should format report"() { + given: + def getDefault = TimeZone.getDefault() + TimeZone.setDefault(SimpleTimeZone.getTimeZone(ZoneOffset.ofHours(2))) + def diagramBuilder = new MermaidGanttDiagram.MermaidGanttDiagramBuilder() + diagramBuilder.addSection('Test1') + diagramBuilder.addTask('test1 - 100 ms', 'active', 1681402397000, 1681402397100) + diagramBuilder.addTask('test2 - 200 ms', 'active', 1681402397100, 1681402397300) + diagramBuilder.addMilestone('milestone1', 1681402397100) + diagramBuilder.addSection('Test2') + diagramBuilder.addTask('test1 - 100 ms', 'active', 1681402397000, 1681402397100) + diagramBuilder.addMilestone('milestone2', 1681402397400) + + MermaidGanttDiagram diagram = diagramBuilder.build("YYYY-MM-DDTHH:mm:ss.SSSZZ", "%H:%M:%S.%L") + + when: + def mermaidReport = new MermaidGanttDiagramFormatter().format(diagram, "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + + + def indent = """gantt +dateFormat YYYY-MM-DDTHH:mm:ss.SSSZZ +axisFormat %H:%M:%S.%L +section Test1 +test1 - 100 ms :active, 2023-04-13T18:13:17.000+0200, 2023-04-13T18:13:17.100+0200 +test2 - 200 ms :active, 2023-04-13T18:13:17.100+0200, 2023-04-13T18:13:17.300+0200 +milestone1 : milestone, 2023-04-13T18:13:17.100+0200, 0 +section Test2 +test1 - 100 ms :active, 2023-04-13T18:13:17.000+0200, 2023-04-13T18:13:17.100+0200 +milestone2 : milestone, 2023-04-13T18:13:17.400+0200, 0 +""" + then: + mermaidReport == indent + + cleanup: + TimeZone.setDefault(getDefault) + } + + def "should format report with special characters"() { + given: + def getDefault = TimeZone.getDefault() + TimeZone.setDefault(SimpleTimeZone.getTimeZone(ZoneOffset.ofHours(2))) + def diagramBuilder = new MermaidGanttDiagram.MermaidGanttDiagramBuilder() + diagramBuilder.addSection('Test # ; : 1') + diagramBuilder.addTask('test # ; : 1', 'active', 1681402397000, 1681402397100) + diagramBuilder.addMilestone('milestone # ; : 1', 1681402397100) + + MermaidGanttDiagram diagram = diagramBuilder.build("YYYY-MM-DDTHH:mm:ss.SSSZZ", "%H:%M:%S.%L") + + when: + def mermaidReport = new MermaidGanttDiagramFormatter().format(diagram, "yyyy-MM-dd'T'HH:mm:ss.SSSZ") + + + def indent = """gantt +dateFormat YYYY-MM-DDTHH:mm:ss.SSSZZ +axisFormat %H:%M:%S.%L +section Test #35; #semi; #colon; 1 +test #35; #semi; #colon; 1 :active, 2023-04-13T18:13:17.000+0200, 2023-04-13T18:13:17.100+0200 +milestone #35; #semi; #colon; 1 : milestone, 2023-04-13T18:13:17.100+0200, 0 +""" + then: + mermaidReport == indent + + cleanup: + TimeZone.setDefault(getDefault) + } +} diff --git a/test-projects/spock-single-module/build.gradle b/test-projects/spock-single-module/build.gradle index 5bceea1..0f16a23 100644 --- a/test-projects/spock-single-module/build.gradle +++ b/test-projects/spock-single-module/build.gradle @@ -26,7 +26,7 @@ createTestsExecutionReport { enabled = true script { config { - maxTextSize = 990 + maxTextSize = 2000 } src = 'https://cdn.jsdelivr.net/npm/mermaid@8.13.3/dist/mermaid.js' } @@ -34,4 +34,10 @@ createTestsExecutionReport { json { enabled = true } mermaid { enabled = true } } + marks { + totalTimeOfAllTests { + enabled = true + name = 'total time of all tests' + } + } }