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(),
|
extraJvmArguments: List<String> = emptyList(),
|
||||||
maximumHeapSize: String? = null,
|
maximumHeapSize: String? = null,
|
||||||
identifier: String = "",
|
identifier: String = "",
|
||||||
environmentVariables: Map<String,String> = emptyMap()
|
environmentVariables: Map<String,String> = emptyMap(),
|
||||||
|
inheritIO: Boolean = true
|
||||||
): Process {
|
): Process {
|
||||||
val command = mutableListOf<String>().apply {
|
val command = mutableListOf<String>().apply {
|
||||||
add(javaPath)
|
add(javaPath)
|
||||||
@ -49,7 +50,7 @@ object ProcessUtilities {
|
|||||||
addAll(arguments)
|
addAll(arguments)
|
||||||
}
|
}
|
||||||
return ProcessBuilder(command).apply {
|
return ProcessBuilder(command).apply {
|
||||||
inheritIO()
|
if (inheritIO) inheritIO()
|
||||||
environment().putAll(environmentVariables)
|
environment().putAll(environmentVariables)
|
||||||
environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator)
|
environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator)
|
||||||
if (workingDirectory != null) {
|
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.
|
// This line makes sure ANSI escapes work on Windows, where they aren't supported out of the box.
|
||||||
AnsiConsole.systemInstall()
|
AnsiConsole.systemInstall()
|
||||||
|
|
||||||
try {
|
|
||||||
val defaultAnsiMode = if (CordaSystemUtils.isOsWindows()) {
|
val defaultAnsiMode = if (CordaSystemUtils.isOsWindows()) {
|
||||||
Help.Ansi.ON
|
Help.Ansi.ON
|
||||||
} else {
|
} else {
|
||||||
Help.Ansi.AUTO
|
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")
|
@Suppress("SpreadOperator")
|
||||||
val results = cmd.parseWithHandlers(RunLast().useOut(System.out).useAnsi(defaultAnsiMode),
|
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
|
// If an error code has been returned, use this and exit
|
||||||
results?.firstOrNull()?.let {
|
results?.firstOrNull()?.let {
|
||||||
if (it is Int) {
|
if (it is Int) {
|
||||||
@ -81,17 +99,10 @@ fun CordaCliWrapper.start(args: Array<String>) {
|
|||||||
exitProcess(ExitCodes.FAILURE)
|
exitProcess(ExitCodes.FAILURE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no results returned, picocli ran something without invoking the main program, e.g. --help or --version, so exit successfully
|
// If no results returned, picocli ran something without invoking the main program, e.g. --help or --version, so exit successfully
|
||||||
exitProcess(ExitCodes.SUCCESS)
|
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,
|
@Command(mixinStandardHelpOptions = true,
|
||||||
|
Loading…
Reference in New Issue
Block a user