Skip to content

Instantly share code, notes, and snippets.

@0x6675636b796f75676974687562
Created July 4, 2023 14:01
Show Gist options
  • Save 0x6675636b796f75676974687562/18778df6a23900f95a458928e0832ad8 to your computer and use it in GitHub Desktop.
Save 0x6675636b796f75676974687562/18778df6a23900f95a458928e0832ad8 to your computer and use it in GitHub Desktop.
package com.example
import org.junit.Assume.assumeTrue
import org.junit.AssumptionViolatedException
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.io.File.pathSeparatorChar
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.div
import kotlin.io.path.isDirectory
import kotlin.io.path.isExecutable
import kotlin.io.path.isRegularFile
import kotlin.io.path.name
/**
* Allows to ignore a JUnit 4 test if a specific executable is not found in
* `PATH`. Include an instance of this class into your test class as a _JUnit
* Rule_:
*
* ```kotlin
* @get:Rule
* val pathLookup = PathLookup()
* ```
*
* Then, within your test method body, invoke [expectInPath] with a name of a
* specific executable (or multiple thereof), e.g.:
*
* ```kotlin
* @Test
* fun test() {
* pathLookup.expectInPath("clang")
* pathLookup.expectInPath("clang++")
*
* // Your test code here.
* }
* ```
*
* The test will be ignored (i.e. throw an [AssumptionViolatedException]) if
* either `clang` or `clang++` is not present in `PATH`.
*
* On Windows, the `PATHEXT` environment variable is processed automatically to
* determine the list of extensions which indicate that a file is executable.
*
* @see expectInPath
*/
class PathLookup : TestRule {
private val expectedExecutables = mutableListOf<Path>()
/**
* Adds [executableName] to the list of executables which are expected to be
* present in `PATH`.
*/
fun expectInPath(executableName: String): Unit =
expectInPath(Path(executableName))
/**
* Adds [executableName] to the list of executables which are expected to be
* present in `PATH`.
*/
fun expectInPath(executableName: Path) {
expectedExecutables.add(executableName)
}
override fun apply(base: Statement, description: Description): Statement =
object : Statement() {
override fun evaluate() {
try {
base.evaluate()
} catch (t: Throwable) {
assumeAllExecutables()
throw t
}
assumeAllExecutables()
}
}
private fun assumeAllExecutables() {
val missingExecutables = expectedExecutables
.asSequence()
.filterNot { executable ->
executable.existsInPath
}
.toList()
assumeTrue("Executables not found in PATH: $missingExecutables", missingExecutables.isEmpty())
}
private fun getenv(name: String, default: String): String =
getenv(name) ?: default
private fun getenv(name: String): String? =
when {
isWindows -> System.getenv()
.asSequence()
.firstOrNull { (key, _) ->
key.equals(name, ignoreCase = true)
}
?.value
else -> System.getenv(name)
}
private val pathEntries: Sequence<Path>
get() =
getenv("PATH")
?.splitToSequence(pathSeparatorChar)
?.filter(String::isNotEmpty)
?.map(::Path)
?.filter(Path::isDirectory)
?: emptySequence()
private val pathExtensions: Sequence<String>
get() =
when {
isWindows -> getenv("PATHEXT", ".com;.exe;.bat;.cmd;")
.splitToSequence(pathSeparatorChar)
.filter(String::isNotEmpty)
else -> emptySequence()
}
private val isWindows: Boolean
get() =
System.getProperty("os.name", "").startsWith("Windows ")
private val Path.existsInPath: Boolean
get() =
pathEntries
.flatMap { pathEntry ->
sequence {
yield(pathEntry / this@existsInPath)
yieldAll(
pathExtensions.map { ext ->
pathEntry / (this@existsInPath.name + ext)
}
)
}
}
.filter(Path::isRegularFile)
.any(Path::isExecutable)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment