-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathktest.kts
executable file
·124 lines (112 loc) · 4.56 KB
/
ktest.kts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import java.io.BufferedReader
import java.io.File
import java.util.concurrent.TimeUnit
import kotlin.system.exitProcess
/**
* This script takes the name of another kts script as an argument, builds that script with gradle, and runs
* its tests via "gradle clean test".
*/
val scriptPath = args.firstOrNull() ?: error("First argument must be a path to a script")
val failOnFailFailure: Boolean = args.getOrNull(1)?.let { it.toBoolean() } ?: true
// Checks for a global path or a relative path
val scriptFile = listOf(File(scriptPath), File("", scriptPath))
.firstOrNull { it.exists() }
?: error("Could not find kts file at $scriptPath")
// This leverages the kscript --idea command to create a project containing all the dependencies for the script,
// which we can then build and test.
// We don't want kscript to actually open Intellij, so we create a local "idea" file that does nothing.
// It is added to the PATH immediately before executing the kscript command since the PATH change
// is only applicable for the life of that process.
// It is prepended to take precedence over the real idea executable.
val mockIdeaFile = File("idea").apply {
check(createNewFile()) { "idea file already exists in this directory" }
setExecutable(true)
deleteOnExit()
}
val ideaPath = mockIdeaFile.canonicalPath.substringBeforeLast("/")
val projectLocation = "export PATH=\"$ideaPath:\$PATH\"; kscript --idea ${scriptFile.canonicalPath}"
.execute()
// We capture the output of the --idea command since it contains the location of the generated project.
// kscript prints this info to err instead of stdout
.stdErr
.lineSequence()
.map {
// Output is like "[kscript] Project set up at /Users/your_name/.kscript/kscript_tmp_project__MyScript.kts_1574748399761"
it.substringAfter("Project set up at ", "")
}
.firstOrNull { it.isNotEmpty() }
?: error("Project output not found")
// The generated project does not automatically include a ./gradlew wrapper so we have to test using a global gradle installation
assertInPath("gradle")
println("\nTesting ${scriptFile.canonicalPath}...\n")
"""
gradle -p $projectLocation clean test
""".trimIndent()
.execute(
// Inherit is used so that gradle test output is shown in console to the user
stdoutRedirectBehavior = ProcessBuilder.Redirect.INHERIT,
stderrRedirectBehavior = ProcessBuilder.Redirect.INHERIT
).let { result ->
if (failOnFailFailure) {
exitProcess(result.exitCode)
} else {
copyTestResultBackToProject()
}
}
fun copyTestResultBackToProject() {
"""
mkdir build
mv $projectLocation/build/* build/
""".trimIndent()
.execute()
}
fun assertInPath(executableName: String) {
"which $executableName"
.execute()
.stdOut
.readLine()
.let {
checkOrExit("not found" !in it) {
"$executableName was not found in PATH. Make sure it is installed globally."
}
}
}
fun checkOrExit(condition: Boolean, msg: () -> String) {
if (!condition) {
println("Error: ${msg()}")
exitProcess(1)
}
}
fun String.execute(
workingDir: File = File("."),
timeoutAmount: Long = 120,
timeoutUnit: TimeUnit = TimeUnit.SECONDS,
stdoutRedirectBehavior: ProcessBuilder.Redirect = ProcessBuilder.Redirect.PIPE,
stderrRedirectBehavior: ProcessBuilder.Redirect = ProcessBuilder.Redirect.PIPE
): ProcessResult {
val processBuilder = ProcessBuilder("/bin/sh", "-c", this)
.directory(workingDir)
.redirectOutput(stdoutRedirectBehavior)
.redirectError(stderrRedirectBehavior)
return processBuilder.start()
.apply {
waitFor(timeoutAmount, timeoutUnit)
if (isAlive) {
destroyForcibly()
println("Command timed out after ${timeoutUnit.toSeconds(timeoutAmount)} seconds: '$this'")
exitProcess(1)
}
}
.let { process ->
val stdOut = processBuilder.redirectOutput()?.file()?.bufferedReader() ?: process.inputStream.bufferedReader()
val stdErr = processBuilder.redirectError()?.file()?.bufferedReader() ?: process.errorStream.bufferedReader()
ProcessResult(process.exitValue(), stdOut, stdErr)
}
}
data class ProcessResult(val exitCode: Int, val stdOut: BufferedReader, val stdErr: BufferedReader) {
companion object {
const val SUCCESS_EXIT_CODE = 0
}
val succeeded: Boolean = exitCode == SUCCESS_EXIT_CODE
val failed: Boolean = !succeeded
}