BuildMetricsServiceTest.kt (8018B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import groovy.json.JsonSlurper 6 import org.gradle.tooling.events.task.* 7 import org.junit.jupiter.api.Assertions.* 8 import org.junit.jupiter.api.Test 9 import org.junit.jupiter.api.io.TempDir 10 import org.mockito.Mockito.* 11 import java.io.File 12 13 class BuildMetricsServiceTest { 14 @TempDir 15 lateinit var tempDir: File 16 17 @Test 18 fun `service records different task statuses correctly`() { 19 val suffix = "task-status" 20 val service = createService(tempDir, suffix) 21 22 service.onFinish(createTaskEvent(":app:compileJava", upToDate = true)) 23 service.onFinish(createTaskEvent(":lib:test", fromCache = true)) 24 service.onFinish(createTaskEvent(":app:build")) 25 service.onFinish(createTaskEvent(":app:failedTask", failed = true)) 26 service.onFinish(createTaskEvent(":app:skippedTask", skipped = true)) 27 28 service.close() 29 30 val metricsFile = File(tempDir, "gradle/build/metrics/build-metrics-$suffix.json") 31 assertTrue(metricsFile.exists()) 32 33 val metrics = JsonSlurper().parseText(metricsFile.readText()) as Map<*, *> 34 val tasks = metrics["tasks"] as List<*> 35 assertEquals(5, tasks.size) 36 37 val taskMap = tasks.associate { task -> 38 val t = task as Map<*, *> 39 t["path"] to t["status"] 40 } 41 42 assertEquals("UP-TO-DATE", taskMap[":app:compileJava"]) 43 assertEquals("FROM-CACHE", taskMap[":lib:test"]) 44 assertEquals("EXECUTED", taskMap[":app:build"]) 45 assertEquals("FAILED", taskMap[":app:failedTask"]) 46 assertEquals("SKIPPED", taskMap[":app:skippedTask"]) 47 } 48 49 @Test 50 fun `service creates metrics file with correct structure`() { 51 val suffix = "structure" 52 val service = createService(tempDir, suffix) 53 54 service.onFinish(createTaskEvent(":task1")) 55 service.onFinish(createTaskEvent(":task2", upToDate = true)) 56 57 service.close() 58 59 val metricsFile = File(tempDir, "gradle/build/metrics/build-metrics-$suffix.json") 60 val metrics = JsonSlurper().parseText(metricsFile.readText()) as Map<*, *> 61 62 assertNotNull(metrics["invocation"]) 63 assertNotNull(metrics["configPhase"]) 64 assertNotNull(metrics["tasks"]) 65 66 val tasks = metrics["tasks"] as List<*> 67 assertEquals(2, tasks.size) 68 } 69 70 @Test 71 fun `service uses custom file suffix`() { 72 val suffix = "custom-suffix" 73 val service = createService(tempDir, suffix) 74 75 service.onFinish(createTaskEvent(":task1")) 76 service.close() 77 78 val metricsFile = File(tempDir, "gradle/build/metrics/build-metrics-$suffix.json") 79 assertTrue(metricsFile.exists()) 80 } 81 82 @Test 83 fun `tasks appear in execution order`() { 84 val suffix = "order" 85 val service = createService(tempDir, suffix) 86 87 service.onFinish(createTaskEvent(":first")) 88 service.onFinish(createTaskEvent(":second")) 89 service.onFinish(createTaskEvent(":third")) 90 91 service.close() 92 93 val metricsFile = File(tempDir, "gradle/build/metrics/build-metrics-$suffix.json") 94 val metrics = JsonSlurper().parseText(metricsFile.readText()) as Map<*, *> 95 val tasks = metrics["tasks"] as List<*> 96 97 assertEquals(":first", (tasks[0] as Map<*, *>)["path"]) 98 assertEquals(":second", (tasks[1] as Map<*, *>)["path"]) 99 assertEquals(":third", (tasks[2] as Map<*, *>)["path"]) 100 } 101 102 @Test 103 fun `task timing fields are present in JSON`() { 104 val suffix = "timing" 105 val service = createService(tempDir, suffix) 106 107 service.onFinish(createTaskEvent(":testTask")) 108 service.close() 109 110 val metricsFile = File(tempDir, "gradle/build/metrics/build-metrics-$suffix.json") 111 val metrics = JsonSlurper().parseText(metricsFile.readText()) as Map<*, *> 112 val tasks = metrics["tasks"] as List<*> 113 val task = tasks[0] as Map<*, *> 114 115 assertNotNull(task["start"]) 116 assertNotNull(task["stop"]) 117 assertNotNull(task["duration"]) 118 } 119 120 @Test 121 fun `service handles different task path formats`() { 122 val suffix = "paths" 123 val service = createService(tempDir, suffix) 124 125 service.onFinish(createTaskEvent(":rootTask")) 126 service.onFinish(createTaskEvent(":project:subTask")) 127 service.onFinish(createTaskEvent(":deep:nested:project:deepTask")) 128 129 service.close() 130 131 val metricsFile = File(tempDir, "gradle/build/metrics/build-metrics-$suffix.json") 132 val metrics = JsonSlurper().parseText(metricsFile.readText()) as Map<*, *> 133 val tasks = metrics["tasks"] as List<*> 134 135 val taskPaths = tasks.map { (it as Map<*, *>)["path"] } 136 assertTrue(taskPaths.contains(":rootTask")) 137 assertTrue(taskPaths.contains(":project:subTask")) 138 assertTrue(taskPaths.contains(":deep:nested:project:deepTask")) 139 } 140 141 @Test 142 fun `service creates metrics directory if it does not exist`() { 143 val suffix = "newdir" 144 val newTempDir = File(tempDir, "nonexistent") 145 val service = createService(newTempDir, suffix) 146 147 service.onFinish(createTaskEvent(":task")) 148 service.close() 149 150 val metricsDir = File(newTempDir, "gradle/build/metrics") 151 assertTrue(metricsDir.exists()) 152 assertTrue(metricsDir.isDirectory) 153 154 val metricsFile = File(metricsDir, "build-metrics-$suffix.json") 155 assertTrue(metricsFile.exists()) 156 } 157 158 @Suppress("UNCHECKED_CAST") 159 private fun createService(topobjdir: File, fileSuffix: String): BuildMetricsService { 160 val topojdirProperty = mock(org.gradle.api.provider.Property::class.java) as org.gradle.api.provider.Property<String> 161 `when`(topojdirProperty.get()).thenReturn(topobjdir.absolutePath) 162 163 val fileSuffixProperty = mock(org.gradle.api.provider.Property::class.java) as org.gradle.api.provider.Property<String> 164 `when`(fileSuffixProperty.get()).thenReturn(fileSuffix) 165 166 val parameters = object : BuildMetricsServiceParameters { 167 override val topobjdir = topojdirProperty 168 override val fileSuffix = fileSuffixProperty 169 } 170 return TestBuildMetricsService(parameters).apply { 171 invocationStart = System.currentTimeMillis() 172 configStart = invocationStart + 100 173 configEnd = configStart + 200 174 } 175 } 176 177 private fun createTaskEvent(taskPath: String, upToDate: Boolean = false, fromCache: Boolean = false, failed: Boolean = false, skipped: Boolean = false): TaskFinishEvent { 178 val result = if (failed) mockFailedResult() else if (skipped) mockSkippedResult() else mockSuccessResult(upToDate, fromCache) 179 val descriptor = mock(TaskOperationDescriptor::class.java).apply { `when`(getTaskPath()).thenReturn(taskPath) } 180 return mock(TaskFinishEvent::class.java).apply { `when`(getDescriptor()).thenReturn(descriptor); `when`(getResult()).thenReturn(result) } 181 } 182 183 private fun mockSuccessResult(upToDate: Boolean, fromCache: Boolean) = mock(TaskSuccessResult::class.java).apply { 184 `when`(getStartTime()).thenReturn(1000L); `when`(getEndTime()).thenReturn(2000L) 185 `when`(isUpToDate()).thenReturn(upToDate); `when`(isFromCache()).thenReturn(fromCache) 186 } 187 188 private fun mockFailedResult() = mock(TaskFailureResult::class.java).apply { 189 `when`(getStartTime()).thenReturn(1000L); `when`(getEndTime()).thenReturn(2000L) 190 } 191 192 private fun mockSkippedResult() = mock(TaskSkippedResult::class.java).apply { 193 `when`(getStartTime()).thenReturn(1000L); `when`(getEndTime()).thenReturn(2000L) 194 } 195 196 class TestBuildMetricsService(private val testParameters: BuildMetricsServiceParameters) : BuildMetricsService(testParameters) { 197 override fun getParameters() = testParameters 198 } 199 }