mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
EG-464 Corda returns incorrect exit code in case if node is started with unknown/missing option (#6010)
* EG-464 Corda returns incorrect exit code in case if node is started with unknown/missing option * fixed empty else block * We should not use the parent's exception handler as it will quit and we have our own one * SampleCordaCliWrapper implemented and tests to verify error handling. * addressing code review comments
This commit is contained in:
parent
9a406839fa
commit
c3a59f5293
@ -0,0 +1,50 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.matchesPattern
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.util.stream.Collectors
|
||||
|
||||
|
||||
@RunWith(value = Parameterized::class)
|
||||
class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputRegexPattern: String) {
|
||||
|
||||
companion object {
|
||||
val className = "net.corda.testing.node.internal.SampleCordaCliWrapper"
|
||||
|
||||
private val stackTraceRegex = "^.+Exception[^\\n]++(\\s+at .++)+[\\s\\S]*"
|
||||
private val exceptionWithoutStackTraceRegex ="${className}(\\s+.+)"
|
||||
private val emptyStringRegex = "^$"
|
||||
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters
|
||||
fun data() = listOf(
|
||||
arrayOf(listOf("--throw-exception", "--verbose"), stackTraceRegex),
|
||||
arrayOf(listOf("--throw-exception"), exceptionWithoutStackTraceRegex),
|
||||
arrayOf(listOf("--sample-command"), emptyStringRegex)
|
||||
)
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Run CordaCliWrapper sample app with arguments and check error output matches regExp`() {
|
||||
|
||||
val process = ProcessUtilities.startJavaProcess(
|
||||
className = className,
|
||||
arguments = arguments,
|
||||
inheritIO = false)
|
||||
|
||||
process.waitFor()
|
||||
|
||||
val processErrorOutput = BufferedReader(
|
||||
InputStreamReader(process.errorStream))
|
||||
.lines()
|
||||
.collect(Collectors.joining("\n"))
|
||||
.toString()
|
||||
|
||||
assertThat(processErrorOutput, matchesPattern(outputRegexPattern))
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
|
||||
import net.corda.cliutils.ExitCodes
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
import kotlin.test.assertEquals
|
||||
@RunWith(value = Parameterized::class)
|
||||
class RunCordaNodeReturnCodeTests(val argument: String, val exitCode: Int){
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters
|
||||
fun data() = listOf(
|
||||
arrayOf("--nonExistingOption", ExitCodes.FAILURE),
|
||||
arrayOf("--help", ExitCodes.SUCCESS),
|
||||
arrayOf("validate-configuration", ExitCodes.FAILURE),//Should fail as there is no node.conf
|
||||
arrayOf("initial-registration", ExitCodes.FAILURE) //Missing required option
|
||||
)
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun runCordaWithArgumentAndAssertExitCode() {
|
||||
|
||||
val process = ProcessUtilities.startJavaProcess(
|
||||
className = "net.corda.node.Corda",
|
||||
arguments = listOf(argument)
|
||||
)
|
||||
process.waitFor()
|
||||
assertEquals(exitCode, process.exitValue())
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.cliutils.CordaCliWrapper
|
||||
import net.corda.cliutils.ExitCodes
|
||||
import net.corda.cliutils.start
|
||||
import picocli.CommandLine
|
||||
|
||||
class SampleCordaCliWrapperException(message: String) : Exception(message)
|
||||
class SampleCordaCliWrapper: CordaCliWrapper("sampleCliWrapper", "Sample corda cliWrapper app") {
|
||||
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
SampleCordaCliWrapper().start(args)
|
||||
}
|
||||
}
|
||||
|
||||
@CommandLine.Option(names = ["--sample-command"],
|
||||
description = [ "Sample command. Prints a message to the console."])
|
||||
var sampleCommand: Boolean? = null
|
||||
|
||||
@CommandLine.Option(names = ["--throw-exception"], description = ["Specify this to throw an exception"])
|
||||
var throwException: Boolean? = null
|
||||
|
||||
override fun runProgram(): Int {
|
||||
|
||||
|
||||
if (throwException!=null) {
|
||||
throw SampleCordaCliWrapperException("net.corda.testing.node.internal.SampleCordaCliWrapper test exception")
|
||||
}
|
||||
if (sampleCommand!=null) {
|
||||
System.out.println("Sample command invoked.")
|
||||
}
|
||||
return ExitCodes.SUCCESS
|
||||
}
|
||||
|
||||
}
|
@ -37,7 +37,8 @@ object ProcessUtilities {
|
||||
extraJvmArguments: List<String> = emptyList(),
|
||||
maximumHeapSize: String? = null,
|
||||
identifier: String = "",
|
||||
environmentVariables: Map<String,String> = emptyMap()
|
||||
environmentVariables: Map<String,String> = emptyMap(),
|
||||
inheritIO: Boolean = true
|
||||
): Process {
|
||||
val command = mutableListOf<String>().apply {
|
||||
add(javaPath)
|
||||
@ -49,7 +50,7 @@ object ProcessUtilities {
|
||||
addAll(arguments)
|
||||
}
|
||||
return ProcessBuilder(command).apply {
|
||||
inheritIO()
|
||||
if (inheritIO) inheritIO()
|
||||
environment().putAll(environmentVariables)
|
||||
environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator)
|
||||
if (workingDirectory != null) {
|
||||
|
@ -64,15 +64,33 @@ fun CordaCliWrapper.start(args: Array<String>) {
|
||||
// This line makes sure ANSI escapes work on Windows, where they aren't supported out of the box.
|
||||
AnsiConsole.systemInstall()
|
||||
|
||||
try {
|
||||
val defaultAnsiMode = if (CordaSystemUtils.isOsWindows()) {
|
||||
Help.Ansi.ON
|
||||
} else {
|
||||
Help.Ansi.AUTO
|
||||
}
|
||||
|
||||
val exceptionHandler = object : DefaultExceptionHandler<List<Any>>() {
|
||||
|
||||
override fun handleParseException(ex: ParameterException?, args: Array<out String>?): List<Any> {
|
||||
super.handleParseException(ex, args)
|
||||
return listOf(ExitCodes.FAILURE)
|
||||
}
|
||||
override fun handleExecutionException(ex: ExecutionException, parseResult: ParseResult?): List<Any> {
|
||||
|
||||
val throwable = ex.cause ?: ex
|
||||
if (this@start.verbose || this@start.subCommands.any { it.verbose }) {
|
||||
throwable.printStackTrace()
|
||||
}
|
||||
printError(throwable.rootMessage ?: "Use --verbose for more details")
|
||||
return listOf(ExitCodes.FAILURE)
|
||||
}
|
||||
}
|
||||
@Suppress("SpreadOperator")
|
||||
val results = cmd.parseWithHandlers(RunLast().useOut(System.out).useAnsi(defaultAnsiMode),
|
||||
DefaultExceptionHandler<List<Any>>().useErr(System.err).useAnsi(defaultAnsiMode), *args)
|
||||
exceptionHandler.useErr(System.err).useAnsi(defaultAnsiMode), *args)
|
||||
|
||||
|
||||
// If an error code has been returned, use this and exit
|
||||
results?.firstOrNull()?.let {
|
||||
if (it is Int) {
|
||||
@ -81,17 +99,10 @@ fun CordaCliWrapper.start(args: Array<String>) {
|
||||
exitProcess(ExitCodes.FAILURE)
|
||||
}
|
||||
}
|
||||
|
||||
// If no results returned, picocli ran something without invoking the main program, e.g. --help or --version, so exit successfully
|
||||
exitProcess(ExitCodes.SUCCESS)
|
||||
} catch (e: ExecutionException) {
|
||||
val throwable = e.cause ?: e
|
||||
if (this.verbose || this.subCommands.any { it.verbose }) {
|
||||
throwable.printStackTrace()
|
||||
} else {
|
||||
}
|
||||
printError(throwable.rootMessage ?: "Use --verbose for more details")
|
||||
exitProcess(ExitCodes.FAILURE)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Command(mixinStandardHelpOptions = true,
|
||||
|
Loading…
Reference in New Issue
Block a user