From 89a5448c39e5d1c42e62d75b5bc7f64c5a9561af Mon Sep 17 00:00:00 2001
From: Andras Slemmer <andras.slemmer@r3cev.com>
Date: Wed, 24 Aug 2016 14:57:52 +0100
Subject: [PATCH] node, core: Add quickcheck generators for basic types,
 transactions

---
 build.gradle                                  |   1 -
 .../r3corda/contracts/testing/Generators.kt   |  87 ++++++++++++++
 core/build.gradle                             |   3 +
 .../com/r3corda/core/testing/Generators.kt    | 111 ++++++++++++++++++
 .../BroadcastTransactionProtocolTest.kt       |  40 +++++++
 node/build.gradle                             |   2 +
 .../com/r3corda/node/JsonSupportTest.kt       |  23 ++++
 .../r3corda/core/testing/JsonSupportTest.kt   |  50 --------
 8 files changed, 266 insertions(+), 51 deletions(-)
 create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/testing/Generators.kt
 create mode 100644 core/src/main/kotlin/com/r3corda/core/testing/Generators.kt
 create mode 100644 core/src/test/kotlin/com/r3corda/core/protocols/BroadcastTransactionProtocolTest.kt
 create mode 100644 node/src/test/kotlin/com/r3corda/node/JsonSupportTest.kt
 delete mode 100644 src/test/kotlin/com/r3corda/core/testing/JsonSupportTest.kt

diff --git a/build.gradle b/build.gradle
index 752a4988d7..5a00ee8697 100644
--- a/build.gradle
+++ b/build.gradle
@@ -115,7 +115,6 @@ dependencies {
     // Unit testing helpers.
     testCompile 'junit:junit:4.12'
     testCompile 'org.assertj:assertj-core:3.4.1'
-    testCompile 'com.pholser:junit-quickcheck-core:0.6'
 
     // Integration test helpers
     integrationTestCompile 'junit:junit:4.12'
diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/testing/Generators.kt b/contracts/src/main/kotlin/com/r3corda/contracts/testing/Generators.kt
new file mode 100644
index 0000000000..50ee1c0406
--- /dev/null
+++ b/contracts/src/main/kotlin/com/r3corda/contracts/testing/Generators.kt
@@ -0,0 +1,87 @@
+package com.r3corda.contracts.testing
+
+import com.pholser.junit.quickcheck.generator.GenerationStatus
+import com.pholser.junit.quickcheck.generator.Generator
+import com.pholser.junit.quickcheck.generator.java.util.ArrayListGenerator
+import com.r3corda.core.contracts.*
+import com.r3corda.core.crypto.DigitalSignature
+import com.r3corda.core.crypto.SecureHash
+import com.r3corda.core.testing.*
+import java.util.*
+
+class ContractStateGenerator : Generator<ContractState>(ContractState::class.java) {
+    override fun generate(random: com.pholser.junit.quickcheck.random.SourceOfRandomness, status: GenerationStatus): ContractState {
+        return com.r3corda.contracts.asset.Cash.State(
+                amount = AmountGenerator(IssuedGenerator(CurrencyGenerator())).generate(random, status),
+                owner = PublicKeyGenerator().generate(random, status)
+        )
+    }
+}
+
+class MoveGenerator : Generator<com.r3corda.contracts.asset.Cash.Commands.Move>(com.r3corda.contracts.asset.Cash.Commands.Move::class.java) {
+    override fun generate(random: com.pholser.junit.quickcheck.random.SourceOfRandomness, status: GenerationStatus): com.r3corda.contracts.asset.Cash.Commands.Move {
+        // TODO generate null as well
+        return com.r3corda.contracts.asset.Cash.Commands.Move(SecureHashGenerator().generate(random, status))
+    }
+}
+
+class IssueGenerator : Generator<com.r3corda.contracts.asset.Cash.Commands.Issue>(com.r3corda.contracts.asset.Cash.Commands.Issue::class.java) {
+    override fun generate(random: com.pholser.junit.quickcheck.random.SourceOfRandomness, status: GenerationStatus): com.r3corda.contracts.asset.Cash.Commands.Issue {
+        return com.r3corda.contracts.asset.Cash.Commands.Issue(random.nextLong())
+    }
+}
+
+class ExitGenerator : Generator<com.r3corda.contracts.asset.Cash.Commands.Exit>(com.r3corda.contracts.asset.Cash.Commands.Exit::class.java) {
+    override fun generate(random: com.pholser.junit.quickcheck.random.SourceOfRandomness, status: GenerationStatus): com.r3corda.contracts.asset.Cash.Commands.Exit {
+        return com.r3corda.contracts.asset.Cash.Commands.Exit(AmountGenerator(IssuedGenerator(CurrencyGenerator())).generate(random, status))
+    }
+}
+
+class CommandDataGenerator : Generator<CommandData>(CommandData::class.java) {
+    override fun generate(random: com.pholser.junit.quickcheck.random.SourceOfRandomness, status: GenerationStatus): CommandData {
+        val generators = listOf(MoveGenerator(), IssueGenerator(), ExitGenerator())
+        return generators[random.nextInt(0, generators.size - 1)].generate(random, status)
+    }
+}
+
+class CommandGenerator : Generator<Command>(Command::class.java) {
+    override fun generate(random: com.pholser.junit.quickcheck.random.SourceOfRandomness, status: GenerationStatus): Command {
+        val signersGenerator = ArrayListGenerator()
+        signersGenerator.addComponentGenerators(listOf(PublicKeyGenerator()))
+        return Command(CommandDataGenerator().generate(random, status), PublicKeyGenerator().generate(random, status))
+    }
+}
+
+class WiredTransactionGenerator: Generator<WireTransaction>(WireTransaction::class.java) {
+    override fun generate(random: com.pholser.junit.quickcheck.random.SourceOfRandomness, status: GenerationStatus): WireTransaction {
+        val inputsGenerator = ArrayListGenerator()
+        inputsGenerator.addComponentGenerators(listOf(StateRefGenerator()))
+        val attachmentsGenerator = ArrayListGenerator()
+        attachmentsGenerator.addComponentGenerators(listOf(SecureHashGenerator()))
+        val outputsGenerator = ArrayListGenerator()
+        outputsGenerator.addComponentGenerators(listOf(TransactionStateGenerator(ContractStateGenerator())))
+        val commandsGenerator = ArrayListGenerator()
+        commandsGenerator.addComponentGenerators(listOf(CommandGenerator()))
+        val commands = commandsGenerator.generate(random, status) as ArrayList<Command> + listOf(CommandGenerator().generate(random, status))
+        return WireTransaction(
+                inputs = inputsGenerator.generate(random, status) as ArrayList<StateRef>,
+                attachments = attachmentsGenerator.generate(random, status) as ArrayList<SecureHash>,
+                outputs = outputsGenerator.generate(random, status) as ArrayList<TransactionState<ContractState>>,
+                commands = commands,
+                notary = PartyGenerator().generate(random, status),
+                signers = commands.flatMap { it.signers },
+                type = TransactionType.General(),
+                timestamp = TimestampGenerator().generate(random, status)
+        )
+    }
+}
+
+class SignedTransactionGenerator: Generator<SignedTransaction>(SignedTransaction::class.java) {
+    override fun generate(random: com.pholser.junit.quickcheck.random.SourceOfRandomness, status: GenerationStatus): SignedTransaction {
+        val wireTransaction = WiredTransactionGenerator().generate(random, status)
+        return SignedTransaction(
+                txBits = wireTransaction.serialized,
+                sigs = wireTransaction.signers.map { DigitalSignature.WithKey(it, random.nextBytes(16)) }
+        )
+    }
+}
diff --git a/core/build.gradle b/core/build.gradle
index 1e27cc0267..2f060971a7 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -56,6 +56,9 @@ dependencies {
     // AssertJ: for fluent assertions for testing
     testCompile "org.assertj:assertj-core:${assertj_version}"
 
+    compile 'com.pholser:junit-quickcheck-core:0.6'
+    compile 'com.pholser:junit-quickcheck-generators:0.6'
+
     // Guava: Google utilities library.
     compile "com.google.guava:guava:19.0"
 
diff --git a/core/src/main/kotlin/com/r3corda/core/testing/Generators.kt b/core/src/main/kotlin/com/r3corda/core/testing/Generators.kt
new file mode 100644
index 0000000000..671a775e07
--- /dev/null
+++ b/core/src/main/kotlin/com/r3corda/core/testing/Generators.kt
@@ -0,0 +1,111 @@
+package com.r3corda.core.testing
+
+import com.pholser.junit.quickcheck.generator.GenerationStatus
+import com.pholser.junit.quickcheck.generator.Generator
+import com.pholser.junit.quickcheck.generator.java.lang.StringGenerator
+import com.pholser.junit.quickcheck.generator.java.util.ArrayListGenerator
+import com.pholser.junit.quickcheck.random.SourceOfRandomness
+import com.r3corda.core.contracts.*
+import com.r3corda.core.crypto.DigitalSignature
+import com.r3corda.core.crypto.Party
+import com.r3corda.core.crypto.SecureHash
+import com.r3corda.core.crypto.ed25519Curve
+import com.r3corda.core.serialization.OpaqueBytes
+import net.i2p.crypto.eddsa.EdDSAPrivateKey
+import net.i2p.crypto.eddsa.EdDSAPublicKey
+import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
+import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
+import java.time.Duration
+import java.time.Instant
+import java.util.*
+
+/**
+ * Generators for quickcheck
+ *
+ * TODO Split this into several files
+ */
+
+class PrivateKeyGenerator: Generator<EdDSAPrivateKey>(EdDSAPrivateKey::class.java) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): EdDSAPrivateKey {
+        val seed = random.nextBytes(32)
+        val privateKeySpec = EdDSAPrivateKeySpec(seed, ed25519Curve)
+        return EdDSAPrivateKey(privateKeySpec)
+    }
+}
+
+class PublicKeyGenerator: Generator<EdDSAPublicKey>(EdDSAPublicKey::class.java) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): EdDSAPublicKey {
+        val privateKey = PrivateKeyGenerator().generate(random, status)
+        return EdDSAPublicKey(EdDSAPublicKeySpec(privateKey.a, ed25519Curve))
+    }
+}
+
+class PartyGenerator: Generator<Party>(Party::class.java) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): Party {
+        return Party(StringGenerator().generate(random, status), PublicKeyGenerator().generate(random, status))
+    }
+}
+
+class PartyAndReferenceGenerator: Generator<PartyAndReference>(PartyAndReference::class.java) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): PartyAndReference {
+        return PartyAndReference(PartyGenerator().generate(random, status), OpaqueBytes(random.nextBytes(16)))
+    }
+}
+
+class SecureHashGenerator: Generator<SecureHash>(SecureHash::class.java) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): SecureHash {
+        return SecureHash.Companion.sha256(random.nextBytes(16))
+    }
+}
+
+class StateRefGenerator: Generator<StateRef>(StateRef::class.java) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): StateRef {
+        return StateRef(SecureHash.Companion.sha256(random.nextBytes(16)), random.nextInt(0, 10))
+    }
+}
+
+class TransactionStateGenerator<T : ContractState>(val stateGenerator: Generator<T>) : Generator<TransactionState<T>>(TransactionState::class.java as Class<TransactionState<T>>) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): TransactionState<T> {
+        return TransactionState(stateGenerator.generate(random, status), PartyGenerator().generate(random, status))
+    }
+}
+
+class IssuedGenerator<T>(val productGenerator: Generator<T>) : Generator<Issued<T>>(Issued::class.java as Class<Issued<T>>) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): Issued<T> {
+        return Issued(PartyAndReferenceGenerator().generate(random, status), productGenerator.generate(random, status))
+    }
+}
+
+class AmountGenerator<T>(val tokenGenerator: Generator<T>) : Generator<Amount<T>>(Amount::class.java as Class<Amount<T>>) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): Amount<T> {
+        return Amount(random.nextLong(0, 1000000), tokenGenerator.generate(random, status))
+    }
+}
+
+class CurrencyGenerator() : Generator<Currency>(Currency::class.java) {
+    companion object {
+        val currencies = Currency.getAvailableCurrencies().toList()
+    }
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): Currency {
+        return currencies[random.nextInt(0, currencies.size - 1)]
+    }
+}
+
+class InstantGenerator : Generator<Instant>(Instant::class.java) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): Instant {
+        return Instant.ofEpochMilli(random.nextLong(0, 1000000))
+    }
+}
+
+class DurationGenerator : Generator<Duration>(Duration::class.java) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): Duration {
+        return Duration.ofMillis(random.nextLong(0, 1000000))
+    }
+}
+
+class TimestampGenerator : Generator<Timestamp>(Timestamp::class.java) {
+    override fun generate(random: SourceOfRandomness, status: GenerationStatus): Timestamp {
+        return Timestamp(InstantGenerator().generate(random, status), DurationGenerator().generate(random, status))
+    }
+}
+
diff --git a/core/src/test/kotlin/com/r3corda/core/protocols/BroadcastTransactionProtocolTest.kt b/core/src/test/kotlin/com/r3corda/core/protocols/BroadcastTransactionProtocolTest.kt
new file mode 100644
index 0000000000..700f2321a0
--- /dev/null
+++ b/core/src/test/kotlin/com/r3corda/core/protocols/BroadcastTransactionProtocolTest.kt
@@ -0,0 +1,40 @@
+package com.r3corda.core.protocols
+
+import com.esotericsoftware.kryo.io.Input
+import com.pholser.junit.quickcheck.From
+import com.pholser.junit.quickcheck.Property
+import com.pholser.junit.quickcheck.generator.GenerationStatus
+import com.pholser.junit.quickcheck.generator.Generator
+import com.pholser.junit.quickcheck.random.SourceOfRandomness
+import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
+import com.r3corda.core.generators.*
+import com.r3corda.core.serialization.createKryo
+import com.r3corda.core.serialization.serialize
+import com.r3corda.core.testing.PartyGenerator
+import com.r3corda.core.testing.SignedTransactionGenerator
+import com.r3corda.protocols.BroadcastTransactionProtocol
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+
+@RunWith(JUnitQuickcheck::class)
+class BroadcastTransactionProtocolTest {
+
+    class NotifyTxRequestMessageGenerator : Generator<BroadcastTransactionProtocol.NotifyTxRequestMessage>(BroadcastTransactionProtocol.NotifyTxRequestMessage::class.java) {
+        override fun generate(random: SourceOfRandomness, status: GenerationStatus): BroadcastTransactionProtocol.NotifyTxRequestMessage {
+            return BroadcastTransactionProtocol.NotifyTxRequestMessage(
+                    tx = SignedTransactionGenerator().generate(random, status),
+                    events = setOf(),
+                    replyToParty = PartyGenerator().generate(random, status),
+                    sessionID = random.nextLong()
+            )
+        }
+    }
+
+    @Property
+    fun serialiseDeserialiseOfNotifyMessageWorks(@From(NotifyTxRequestMessageGenerator::class) message: BroadcastTransactionProtocol.NotifyTxRequestMessage) {
+        val kryo = createKryo()
+        val serialized = message.serialize().bits
+        val deserialized = kryo.readClassAndObject(Input(serialized))
+        assertEquals(deserialized, message)
+    }
+}
diff --git a/node/build.gradle b/node/build.gradle
index 1cfe685209..0a99bcdd5a 100644
--- a/node/build.gradle
+++ b/node/build.gradle
@@ -109,6 +109,8 @@ dependencies {
     testCompile 'junit:junit:4.12'
     testCompile "org.assertj:assertj-core:${assertj_version}"
 
+    testCompile 'com.pholser:junit-quickcheck-core:0.6'
+
     // For H2 database support in persistence
     compile "com.h2database:h2:1.3.176"
 
diff --git a/node/src/test/kotlin/com/r3corda/node/JsonSupportTest.kt b/node/src/test/kotlin/com/r3corda/node/JsonSupportTest.kt
new file mode 100644
index 0000000000..b5621be543
--- /dev/null
+++ b/node/src/test/kotlin/com/r3corda/node/JsonSupportTest.kt
@@ -0,0 +1,23 @@
+package com.r3corda.node
+
+import com.r3corda.core.node.services.testing.MockIdentityService
+import com.r3corda.core.testing.PublicKeyGenerator
+import com.r3corda.node.utilities.JsonSupport
+import net.i2p.crypto.eddsa.EdDSAPublicKey
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+
+@RunWith(com.pholser.junit.quickcheck.runner.JUnitQuickcheck::class)
+class JsonSupportTest {
+
+    companion object {
+        val mapper = JsonSupport.createDefaultMapper(MockIdentityService(mutableListOf()))
+    }
+
+    @com.pholser.junit.quickcheck.Property
+    fun publicKeySerializingWorks(@com.pholser.junit.quickcheck.From(PublicKeyGenerator::class) publicKey: EdDSAPublicKey) {
+        val serialized = mapper.writeValueAsString(publicKey)
+        val parsedKey = mapper.readValue(serialized, EdDSAPublicKey::class.java)
+        assertEquals(publicKey, parsedKey)
+    }
+}
diff --git a/src/test/kotlin/com/r3corda/core/testing/JsonSupportTest.kt b/src/test/kotlin/com/r3corda/core/testing/JsonSupportTest.kt
deleted file mode 100644
index 6593ed8c6e..0000000000
--- a/src/test/kotlin/com/r3corda/core/testing/JsonSupportTest.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.r3corda.core.testing
-
-import com.pholser.junit.quickcheck.From
-import com.pholser.junit.quickcheck.Property
-import com.pholser.junit.quickcheck.generator.GenerationStatus
-import com.pholser.junit.quickcheck.generator.Generator
-import com.pholser.junit.quickcheck.random.SourceOfRandomness
-import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
-import com.r3corda.testing.node.MockIdentityService
-import com.r3corda.node.utilities.JsonSupport
-import net.i2p.crypto.eddsa.EdDSAPrivateKey
-import net.i2p.crypto.eddsa.EdDSAPublicKey
-import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
-import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
-import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
-import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-
-@RunWith(JUnitQuickcheck::class)
-class JsonSupportTest {
-
-    companion object {
-        val mapper = JsonSupport.createDefaultMapper(MockIdentityService(mutableListOf()))
-        val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)
-    }
-
-    /** TODO: factor out generators into a ServiceLoader in order to remove @From annotations.
-     * @See <a href="http://pholser.github.io/junit-quickcheck/site/0.6/usage/other-types.html">pholser.github.io</a>
-     */
-    class PrivateKeyGenerator: Generator<EdDSAPrivateKey>(EdDSAPrivateKey::class.java) {
-        override fun generate(random: SourceOfRandomness, status: GenerationStatus): EdDSAPrivateKey {
-            val seed = random.nextBytes(32)
-            val privateKeySpec = EdDSAPrivateKeySpec(seed, ed25519Curve)
-            return EdDSAPrivateKey(privateKeySpec)
-        }
-    }
-
-    class PublicKeyGenerator: Generator<EdDSAPublicKey>(EdDSAPublicKey::class.java) {
-        override fun generate(random: SourceOfRandomness, status: GenerationStatus): EdDSAPublicKey {
-            val privateKey = PrivateKeyGenerator().generate(random, status)
-            return EdDSAPublicKey(EdDSAPublicKeySpec(privateKey.a, ed25519Curve))
-        }
-    }
-
-    @Property fun publicKeySerializingWorks(@From(PublicKeyGenerator::class) publicKey: EdDSAPublicKey) {
-        val serialized = mapper.writeValueAsString(publicKey)
-        val parsedKey = mapper.readValue(serialized, EdDSAPublicKey::class.java)
-        assertEquals(publicKey, parsedKey)
-    }
-}