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::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::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::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::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::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::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::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 + listOf(CommandGenerator().generate(random, status)) + return WireTransaction( + inputs = inputsGenerator.generate(random, status) as ArrayList, + attachments = attachmentsGenerator.generate(random, status) as ArrayList, + outputs = outputsGenerator.generate(random, status) as ArrayList>, + commands = commands, + notary = PartyGenerator().generate(random, status), + signers = commands.flatMap { it.signers }, + type = TransactionType.General(), + timestamp = TimestampGenerator().generate(random, status) + ) + } +} + +class SignedTransactionGenerator: Generator(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::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::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::class.java) { + override fun generate(random: SourceOfRandomness, status: GenerationStatus): Party { + return Party(StringGenerator().generate(random, status), PublicKeyGenerator().generate(random, status)) + } +} + +class PartyAndReferenceGenerator: Generator(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::class.java) { + override fun generate(random: SourceOfRandomness, status: GenerationStatus): SecureHash { + return SecureHash.Companion.sha256(random.nextBytes(16)) + } +} + +class StateRefGenerator: Generator(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(val stateGenerator: Generator) : Generator>(TransactionState::class.java as Class>) { + override fun generate(random: SourceOfRandomness, status: GenerationStatus): TransactionState { + return TransactionState(stateGenerator.generate(random, status), PartyGenerator().generate(random, status)) + } +} + +class IssuedGenerator(val productGenerator: Generator) : Generator>(Issued::class.java as Class>) { + override fun generate(random: SourceOfRandomness, status: GenerationStatus): Issued { + return Issued(PartyAndReferenceGenerator().generate(random, status), productGenerator.generate(random, status)) + } +} + +class AmountGenerator(val tokenGenerator: Generator) : Generator>(Amount::class.java as Class>) { + override fun generate(random: SourceOfRandomness, status: GenerationStatus): Amount { + return Amount(random.nextLong(0, 1000000), tokenGenerator.generate(random, status)) + } +} + +class CurrencyGenerator() : Generator(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::class.java) { + override fun generate(random: SourceOfRandomness, status: GenerationStatus): Instant { + return Instant.ofEpochMilli(random.nextLong(0, 1000000)) + } +} + +class DurationGenerator : Generator(Duration::class.java) { + override fun generate(random: SourceOfRandomness, status: GenerationStatus): Duration { + return Duration.ofMillis(random.nextLong(0, 1000000)) + } +} + +class TimestampGenerator : Generator(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::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 pholser.github.io - */ - class PrivateKeyGenerator: Generator(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::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) - } -}