|
1 | 1 | import com.github.gradle.node.npm.task.NpmTask |
| 2 | +import kotlinx.coroutines.CoroutineScope |
| 3 | +import kotlinx.coroutines.Dispatchers |
| 4 | +import kotlinx.coroutines.joinAll |
| 5 | +import kotlinx.coroutines.launch |
| 6 | +import kotlinx.coroutines.runBlocking |
2 | 7 | import java.io.FileNotFoundException |
| 8 | +import java.io.Reader |
| 9 | +import kotlin.time.Duration |
3 | 10 | import kotlin.time.Duration.Companion.minutes |
4 | 11 |
|
5 | 12 | plugins { |
@@ -87,23 +94,92 @@ tasks.register("generateTestResources") { |
87 | 94 | outputDir.relativeTo(resources).path, |
88 | 95 | "-t", |
89 | 96 | ) |
90 | | - println("Running: '${cmd.joinToString(" ")}'") |
| 97 | + logger.lifecycle("Running command: ${cmd.joinToString(" ")}") |
91 | 98 | val result = ProcessUtil.run(cmd, timeout = 1.minutes) { |
92 | 99 | directory(resources) |
93 | 100 | } |
94 | 101 | if (result.stdout.isNotBlank()) { |
95 | | - println("[STDOUT]:\n--------\n${result.stdout}\n--------") |
| 102 | + logger.lifecycle("[STDOUT]:\n--------\n${result.stdout}\n--------") |
96 | 103 | } |
97 | 104 | if (result.stderr.isNotBlank()) { |
98 | | - println("[STDERR]:\n--------\n${result.stderr}\n--------") |
| 105 | + logger.lifecycle("[STDERR]:\n--------\n${result.stderr}\n--------") |
99 | 106 | } |
100 | 107 | if (result.isTimeout) { |
101 | | - println("Timeout!") |
| 108 | + logger.warn("Timeout!") |
102 | 109 | } |
103 | 110 | if (result.exitCode != 0) { |
104 | | - println("Exit code: ${result.exitCode}") |
| 111 | + logger.warn("Exit code: ${result.exitCode}") |
105 | 112 | } |
106 | 113 |
|
107 | 114 | println("Done generating test resources in %.1fs".format((System.currentTimeMillis() - startTime) / 1000.0)) |
108 | 115 | } |
109 | 116 | } |
| 117 | + |
| 118 | +object ProcessUtil { |
| 119 | + data class Result( |
| 120 | + val exitCode: Int, |
| 121 | + val stdout: String, |
| 122 | + val stderr: String, |
| 123 | + val isTimeout: Boolean, // true if the process was terminated due to timeout |
| 124 | + ) |
| 125 | + |
| 126 | + fun run( |
| 127 | + command: List<String>, |
| 128 | + input: Reader = "".reader(), |
| 129 | + timeout: Duration? = null, |
| 130 | + builder: ProcessBuilder.() -> Unit = {}, |
| 131 | + ): Result { |
| 132 | + val process = ProcessBuilder(command).apply(builder).start() |
| 133 | + return communicate(process, input, timeout) |
| 134 | + } |
| 135 | + |
| 136 | + private fun communicate( |
| 137 | + process: Process, |
| 138 | + input: Reader, |
| 139 | + timeout: Duration? = null, |
| 140 | + ): Result { |
| 141 | + val stdout = StringBuilder() |
| 142 | + val stderr = StringBuilder() |
| 143 | + |
| 144 | + val scope = CoroutineScope(Dispatchers.IO) |
| 145 | + |
| 146 | + // Handle process input |
| 147 | + val stdinJob = scope.launch { |
| 148 | + process.outputStream.bufferedWriter().use { writer -> |
| 149 | + input.copyTo(writer) |
| 150 | + } |
| 151 | + } |
| 152 | + |
| 153 | + // Launch output capture coroutines |
| 154 | + val stdoutJob = scope.launch { |
| 155 | + process.inputStream.bufferedReader().useLines { lines -> |
| 156 | + lines.forEach { stdout.appendLine(it) } |
| 157 | + } |
| 158 | + } |
| 159 | + val stderrJob = scope.launch { |
| 160 | + process.errorStream.bufferedReader().useLines { lines -> |
| 161 | + lines.forEach { stderr.appendLine(it) } |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + // Wait for completion |
| 166 | + val isTimeout = if (timeout != null) { |
| 167 | + !process.waitFor(timeout.inWholeNanoseconds, TimeUnit.NANOSECONDS) |
| 168 | + } else { |
| 169 | + process.waitFor() |
| 170 | + false |
| 171 | + } |
| 172 | + |
| 173 | + // Wait for all coroutines to finish |
| 174 | + runBlocking { |
| 175 | + joinAll(stdinJob, stdoutJob, stderrJob) |
| 176 | + } |
| 177 | + |
| 178 | + return Result( |
| 179 | + exitCode = process.exitValue(), |
| 180 | + stdout = stdout.toString(), |
| 181 | + stderr = stderr.toString(), |
| 182 | + isTimeout = isTimeout, |
| 183 | + ) |
| 184 | + } |
| 185 | +} |
0 commit comments