mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
Improve the error messages printed by the shell when a flow c'tor doesn't match.
This commit is contained in:
parent
63ebc394bf
commit
2d39b39e31
@ -47,8 +47,7 @@ import java.io.FileDescriptor
|
|||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.*
|
||||||
import java.lang.reflect.UndeclaredThrowableException
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
@ -300,6 +299,38 @@ object InteractiveShell {
|
|||||||
override fun toString() = (listOf("No applicable constructor for flow. Problems were:") + errors).joinToString(System.lineSeparator())
|
override fun toString() = (listOf("No applicable constructor for flow. Problems were:") + errors).joinToString(System.lineSeparator())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tidies up a possibly generic type name by chopping off the package names of classes in a hard-coded set of
|
||||||
|
* hierarchies that are known to be widely used and recognised, and also not have (m)any ambiguous names in them.
|
||||||
|
*
|
||||||
|
* This is used for printing error messages when something doesn't match.
|
||||||
|
*/
|
||||||
|
private fun maybeAbbreviateGenericType(type: Type, extraRecognisedPackage: String): String {
|
||||||
|
val packagesToAbbreviate = listOf("java.", "net.corda.core.", "kotlin.", extraRecognisedPackage)
|
||||||
|
|
||||||
|
fun shouldAbbreviate(typeName: String) = packagesToAbbreviate.any { typeName.startsWith(it) }
|
||||||
|
fun abbreviated(typeName: String) = if (shouldAbbreviate(typeName)) typeName.split('.').last() else typeName
|
||||||
|
|
||||||
|
fun innerLoop(type: Type): String = when (type) {
|
||||||
|
is ParameterizedType -> {
|
||||||
|
val args: List<String> = type.actualTypeArguments.map(::innerLoop)
|
||||||
|
abbreviated(type.rawType.typeName) + '<' + args.joinToString(", ") + '>'
|
||||||
|
}
|
||||||
|
is GenericArrayType -> {
|
||||||
|
innerLoop(type.genericComponentType) + "[]"
|
||||||
|
}
|
||||||
|
is Class<*> -> {
|
||||||
|
if (type.isArray)
|
||||||
|
abbreviated(type.simpleName)
|
||||||
|
else
|
||||||
|
abbreviated(type.name).replace('$', '.')
|
||||||
|
}
|
||||||
|
else -> type.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return innerLoop(type)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This utility is generally useful and might be better moved to the node class, or an RPC, if we can commit to making it stable API.
|
// TODO: This utility is generally useful and might be better moved to the node class, or an RPC, if we can commit to making it stable API.
|
||||||
/**
|
/**
|
||||||
* Given a [FlowLogic] class and a string in one-line Yaml form, finds an applicable constructor and starts
|
* Given a [FlowLogic] class and a string in one-line Yaml form, finds an applicable constructor and starts
|
||||||
@ -319,10 +350,17 @@ object InteractiveShell {
|
|||||||
// and keep track of the reasons we failed so we can print them out if no constructors are usable.
|
// and keep track of the reasons we failed so we can print them out if no constructors are usable.
|
||||||
val parser = StringToMethodCallParser(clazz, om)
|
val parser = StringToMethodCallParser(clazz, om)
|
||||||
val errors = ArrayList<String>()
|
val errors = ArrayList<String>()
|
||||||
|
|
||||||
|
val classPackage = clazz.packageName
|
||||||
for (ctor in clazz.constructors) {
|
for (ctor in clazz.constructors) {
|
||||||
var paramNamesFromConstructor: List<String>? = null
|
var paramNamesFromConstructor: List<String>? = null
|
||||||
|
|
||||||
fun getPrototype(): List<String> {
|
fun getPrototype(): List<String> {
|
||||||
val argTypes = ctor.genericParameterTypes.map { it.typeName }
|
val argTypes = ctor.genericParameterTypes.map { it: Type ->
|
||||||
|
// If the type name is in the net.corda.core or java namespaces, chop off the package name
|
||||||
|
// because these hierarchies don't have (m)any ambiguous names and the extra detail is just noise.
|
||||||
|
maybeAbbreviateGenericType(it, classPackage)
|
||||||
|
}
|
||||||
return paramNamesFromConstructor!!.zip(argTypes).map { (name, type) -> "$name: $type" }
|
return paramNamesFromConstructor!!.zip(argTypes).map { (name, type) -> "$name: $type" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@ public class InteractiveShellJavaTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public FlowA(Integer b) {
|
public FlowA(int b) {
|
||||||
this(b.toString());
|
this(Integer.valueOf(b).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public FlowA(Integer b, String c) {
|
public FlowA(Integer b, String c) {
|
||||||
@ -111,6 +111,9 @@ public class InteractiveShellJavaTest {
|
|||||||
this.a = a;
|
this.a = a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FlowB(Amount<Currency> amount, int abc) {
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public ProgressTracker getProgressTracker() {
|
public ProgressTracker getProgressTracker() {
|
||||||
@ -142,6 +145,7 @@ public class InteractiveShellJavaTest {
|
|||||||
this.label = label;
|
this.label = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused") // Used via reflection.
|
||||||
public String getLabel() {
|
public String getLabel() {
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
@ -160,17 +164,17 @@ public class InteractiveShellJavaTest {
|
|||||||
|
|
||||||
private void check(String input, String expected, Class<? extends StringFlow> flowClass) throws InteractiveShell.NoApplicableConstructor {
|
private void check(String input, String expected, Class<? extends StringFlow> flowClass) throws InteractiveShell.NoApplicableConstructor {
|
||||||
InteractiveShell.INSTANCE.runFlowFromString((clazz, args) -> {
|
InteractiveShell.INSTANCE.runFlowFromString((clazz, args) -> {
|
||||||
|
|
||||||
StringFlow instance = null;
|
StringFlow instance = null;
|
||||||
try {
|
try {
|
||||||
instance = (StringFlow)clazz.getConstructor(Arrays.stream(args).map(Object::getClass).toArray(Class[]::new)).newInstance(args);
|
instance = (StringFlow)clazz.getConstructor(Arrays.stream(args).map(Object::getClass).toArray(Class[]::new)).newInstance(args);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(e);
|
System.out.println(e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
output = instance.getA();
|
output = instance.getA();
|
||||||
OpenFuture<String> future = CordaFutureImplKt.openFuture();
|
OpenFuture<String> future = CordaFutureImplKt.openFuture();
|
||||||
future.set("ABC");
|
future.set("ABC");
|
||||||
return new FlowProgressHandleImpl(StateMachineRunId.Companion.createRandom(), future, Observable.just("Some string"));
|
return new FlowProgressHandleImpl<String>(StateMachineRunId.Companion.createRandom(), future, Observable.just("Some string"));
|
||||||
}, input, flowClass, om);
|
}, input, flowClass, om);
|
||||||
assertEquals(input, expected, output);
|
assertEquals(input, expected, output);
|
||||||
}
|
}
|
||||||
@ -245,4 +249,14 @@ public class InteractiveShellJavaTest {
|
|||||||
public void unwrapLambda() throws InteractiveShell.NoApplicableConstructor {
|
public void unwrapLambda() throws InteractiveShell.NoApplicableConstructor {
|
||||||
check("party: \"" + megaCorp.getName() + "\", a: Bambam", "Bambam", FlowB.class);
|
check("party: \"" + megaCorp.getName() + "\", a: Bambam", "Bambam", FlowB.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void niceErrors() {
|
||||||
|
// Most cases are checked in the Kotlin test, so we only check raw types here.
|
||||||
|
try {
|
||||||
|
check("amount: $100", "", FlowB.class);
|
||||||
|
} catch (InteractiveShell.NoApplicableConstructor e) {
|
||||||
|
assertEquals("[amount: Amount<Currency>, abc: int]: missing parameter abc", e.getErrors().get(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,13 @@ import net.corda.core.internal.concurrent.openFuture
|
|||||||
import net.corda.core.messaging.FlowProgressHandleImpl
|
import net.corda.core.messaging.FlowProgressHandleImpl
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.node.services.identity.InMemoryIdentityService
|
import net.corda.node.services.identity.InMemoryIdentityService
|
||||||
import net.corda.testing.internal.DEV_ROOT_CA
|
|
||||||
import net.corda.testing.core.TestIdentity
|
import net.corda.testing.core.TestIdentity
|
||||||
|
import net.corda.testing.internal.DEV_ROOT_CA
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
class InteractiveShellTest {
|
class InteractiveShellTest {
|
||||||
companion object {
|
companion object {
|
||||||
@ -28,7 +29,7 @@ class InteractiveShellTest {
|
|||||||
|
|
||||||
@Suppress("UNUSED")
|
@Suppress("UNUSED")
|
||||||
class FlowA(val a: String) : FlowLogic<String>() {
|
class FlowA(val a: String) : FlowLogic<String>() {
|
||||||
constructor(b: Int?) : this(b.toString())
|
constructor(b: Int) : this(b.toString())
|
||||||
constructor(b: Int?, c: String) : this(b.toString() + c)
|
constructor(b: Int?, c: String) : this(b.toString() + c)
|
||||||
constructor(amount: Amount<Currency>) : this(amount.toString())
|
constructor(amount: Amount<Currency>) : this(amount.toString())
|
||||||
constructor(pair: Pair<Amount<Currency>, SecureHash.SHA256>) : this(pair.toString())
|
constructor(pair: Pair<Amount<Currency>, SecureHash.SHA256>) : this(pair.toString())
|
||||||
@ -48,7 +49,6 @@ class InteractiveShellTest {
|
|||||||
private fun check(input: String, expected: String) {
|
private fun check(input: String, expected: String) {
|
||||||
var output: String? = null
|
var output: String? = null
|
||||||
InteractiveShell.runFlowFromString({ clazz, args ->
|
InteractiveShell.runFlowFromString({ clazz, args ->
|
||||||
|
|
||||||
val instance = clazz.getConstructor(*args.map { it!!::class.java }.toTypedArray()).newInstance(*args) as FlowA
|
val instance = clazz.getConstructor(*args.map { it!!::class.java }.toTypedArray()).newInstance(*args) as FlowA
|
||||||
output = instance.a
|
output = instance.a
|
||||||
val future = openFuture<String>()
|
val future = openFuture<String>()
|
||||||
@ -101,6 +101,27 @@ class InteractiveShellTest {
|
|||||||
@Test(expected = InteractiveShell.NoApplicableConstructor::class)
|
@Test(expected = InteractiveShell.NoApplicableConstructor::class)
|
||||||
fun flowTooManyParams() = check("b: 12, c: Yo, d: Bar", "")
|
fun flowTooManyParams() = check("b: 12, c: Yo, d: Bar", "")
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun niceTypeNamesInErrors() {
|
||||||
|
val e = assertFailsWith<InteractiveShell.NoApplicableConstructor> {
|
||||||
|
check("", expected = "")
|
||||||
|
}
|
||||||
|
val correct = setOf(
|
||||||
|
"[amounts: Amount<InteractiveShellTest.UserValue>[]]: missing parameter amounts",
|
||||||
|
"[amount: Amount<Currency>]: missing parameter amount",
|
||||||
|
"[pair: Pair<Amount<Currency>, SecureHash.SHA256>]: missing parameter pair",
|
||||||
|
"[party: Party]: missing parameter party",
|
||||||
|
"[b: Integer, amount: Amount<InteractiveShellTest.UserValue>]: missing parameter b",
|
||||||
|
"[b: String[]]: missing parameter b",
|
||||||
|
"[b: Integer, c: String]: missing parameter b",
|
||||||
|
"[a: String]: missing parameter a",
|
||||||
|
"[b: int]: missing parameter b"
|
||||||
|
)
|
||||||
|
val errors = e.errors.toHashSet()
|
||||||
|
errors.removeAll(correct)
|
||||||
|
assert(errors.isEmpty()) { errors.joinToString(", ") }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun party() = check("party: \"${megaCorp.name}\"", megaCorp.name.toString())
|
fun party() = check("party: \"${megaCorp.name}\"", megaCorp.name.toString())
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user