Skip to content

Commit e899210

Browse files
committed
Add ProcessUtil
Similar to `Util.sh`, but with Kotlin and coroutines
1 parent ddbea5d commit e899210

File tree

1 file changed

+92
-0
lines changed

1 file changed

+92
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.ets.utils
18+
19+
import kotlinx.coroutines.CoroutineScope
20+
import kotlinx.coroutines.Dispatchers
21+
import kotlinx.coroutines.launch
22+
import kotlinx.coroutines.runBlocking
23+
import java.io.Reader
24+
25+
object ProcessUtil {
26+
fun run(command: List<String>, input: String? = null): String {
27+
val reader = input?.reader() ?: "".reader()
28+
return run(command, reader)
29+
}
30+
31+
fun run(command: List<String>, input: Reader): String {
32+
val process = ProcessBuilder(command).start()
33+
return communicate(process, input)
34+
}
35+
36+
private fun communicate(process: Process, input: Reader): String {
37+
val stdout = StringBuilder()
38+
val stderr = StringBuilder()
39+
40+
val scope = CoroutineScope(Dispatchers.IO)
41+
42+
// Handle process input
43+
val stdinJob = scope.launch {
44+
process.outputWriter().use { writer ->
45+
input.copyTo(writer)
46+
}
47+
}
48+
49+
// Launch output capture coroutines
50+
val stdoutJob = scope.launch {
51+
process.inputReader().useLines { lines ->
52+
lines.forEach { stdout.appendLine(it) }
53+
}
54+
}
55+
val stderrJob = scope.launch {
56+
process.errorReader().useLines { lines ->
57+
lines.forEach { stderr.appendLine(it) }
58+
}
59+
}
60+
61+
// Wait for completion
62+
val exitCode = process.waitFor()
63+
runBlocking {
64+
stdinJob.join()
65+
stdoutJob.join()
66+
stderrJob.join()
67+
}
68+
69+
if (exitCode != 0) {
70+
throw ProcessException(
71+
"Process failed with exit code $exitCode",
72+
exitCode,
73+
stderr.toString()
74+
)
75+
}
76+
77+
return stdout.toString()
78+
}
79+
80+
class ProcessException(
81+
message: String,
82+
val exitCode: Int,
83+
val errorOutput: String,
84+
) : RuntimeException(message)
85+
}
86+
87+
fun main() {
88+
// Note: `ls -l /bin/` has big enough output to demonstrate the necessity
89+
// of separate output capture threads/coroutines.
90+
val output = ProcessUtil.run(listOf("ls", "-l", "/bin/"))
91+
println(output)
92+
}

0 commit comments

Comments
 (0)