node, core: Add quickcheck generators for basic types, transactions

This commit is contained in:
Andras Slemmer 2016-08-24 14:57:52 +01:00
parent f3b4379e06
commit 89a5448c39
8 changed files with 266 additions and 51 deletions

View File

@ -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'

View File

@ -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)) }
)
}
}

View File

@ -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"

View File

@ -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))
}
}

View File

@ -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)
}
}

View File

@ -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"

View File

@ -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)
}
}

View File

@ -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)
}
}