mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
Merged in aslemmer-generators-fixes (pull request #300)
Aslemmer generators fixes
This commit is contained in:
commit
9e993c5b86
@ -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'
|
||||
|
@ -0,0 +1,88 @@
|
||||
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.pholser.junit.quickcheck.random.SourceOfRandomness
|
||||
import com.r3corda.contracts.asset.Cash
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.DigitalSignature
|
||||
import com.r3corda.core.crypto.NullSignature
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.testing.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* This file contains generators for quickcheck style testing. The idea is that we can write random instance generators
|
||||
* for each type we have in the code and test against those instead of predefined mock data. This style of testing can
|
||||
* catch corner case bugs and test algebraic properties of the code, for example deserialize(serialize(generatedThing)) == generatedThing
|
||||
*
|
||||
* TODO add combinators for easier Generator writing
|
||||
*/
|
||||
class ContractStateGenerator : Generator<ContractState>(ContractState::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): ContractState {
|
||||
return Cash.State(
|
||||
amount = AmountGenerator(IssuedGenerator(CurrencyGenerator())).generate(random, status),
|
||||
owner = PublicKeyGenerator().generate(random, status)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class MoveGenerator : Generator<Cash.Commands.Move>(Cash.Commands.Move::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Cash.Commands.Move {
|
||||
return Cash.Commands.Move(SecureHashGenerator().generate(random, status))
|
||||
}
|
||||
}
|
||||
|
||||
class IssueGenerator : Generator<Cash.Commands.Issue>(Cash.Commands.Issue::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Cash.Commands.Issue {
|
||||
return Cash.Commands.Issue(random.nextLong())
|
||||
}
|
||||
}
|
||||
|
||||
class ExitGenerator : Generator<Cash.Commands.Exit>(Cash.Commands.Exit::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Cash.Commands.Exit {
|
||||
return Cash.Commands.Exit(AmountGenerator(IssuedGenerator(CurrencyGenerator())).generate(random, status))
|
||||
}
|
||||
}
|
||||
|
||||
class CommandDataGenerator : Generator<CommandData>(CommandData::class.java) {
|
||||
override fun generate(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: 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: SourceOfRandomness, status: GenerationStatus): WireTransaction {
|
||||
val commands = CommandGenerator().generateList(random, status) + listOf(CommandGenerator().generate(random, status))
|
||||
return WireTransaction(
|
||||
inputs = StateRefGenerator().generateList(random, status),
|
||||
attachments = SecureHashGenerator().generateList(random, status),
|
||||
outputs = TransactionStateGenerator(ContractStateGenerator()).generateList(random, status),
|
||||
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: SourceOfRandomness, status: GenerationStatus): SignedTransaction {
|
||||
val wireTransaction = WiredTransactionGenerator().generate(random, status)
|
||||
return SignedTransaction(
|
||||
txBits = wireTransaction.serialized,
|
||||
sigs = listOf(NullSignature)
|
||||
)
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
||||
|
110
core/src/main/kotlin/com/r3corda/core/testing/Generators.kt
Normal file
110
core/src/main/kotlin/com/r3corda/core/testing/Generators.kt
Normal file
@ -0,0 +1,110 @@
|
||||
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.*
|
||||
import com.r3corda.core.serialization.OpaqueBytes
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Generators for quickcheck
|
||||
*
|
||||
* TODO Split this into several files
|
||||
*/
|
||||
|
||||
fun <A> Generator<A>.generateList(random: SourceOfRandomness, status: GenerationStatus): List<A> {
|
||||
val arrayGenerator = ArrayListGenerator()
|
||||
arrayGenerator.addComponentGenerators(listOf(this))
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return arrayGenerator.generate(random, status) as List<A>
|
||||
}
|
||||
|
||||
class PrivateKeyGenerator: Generator<PrivateKey>(PrivateKey::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): PrivateKey {
|
||||
return entropyToKeyPair(random.nextBigInteger(32)).private
|
||||
}
|
||||
}
|
||||
|
||||
class PublicKeyGenerator: Generator<PublicKey>(PublicKey::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): PublicKey {
|
||||
return entropyToKeyPair(random.nextBigInteger(32)).public
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
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.contracts.testing.SignedTransactionGenerator
|
||||
import com.r3corda.core.serialization.createKryo
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import com.r3corda.core.testing.PartyGenerator
|
||||
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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
||||
|
@ -70,6 +70,9 @@ class ArtemisMessagingClient(directory: Path,
|
||||
var consumer: ClientConsumer? = null
|
||||
var session: ClientSession? = null
|
||||
var clientFactory: ClientSessionFactory? = null
|
||||
|
||||
// TODO: This is not robust and needs to be replaced by more intelligently using the message queue server.
|
||||
var undeliveredMessages = listOf<Message>()
|
||||
}
|
||||
|
||||
/** A registration to handle messages of different types */
|
||||
@ -85,9 +88,6 @@ class ArtemisMessagingClient(directory: Path,
|
||||
private val state = ThreadBox(InnerState())
|
||||
private val handlers = CopyOnWriteArrayList<Handler>()
|
||||
|
||||
// TODO: This is not robust and needs to be replaced by more intelligently using the message queue server.
|
||||
private val undeliveredMessages = CopyOnWriteArrayList<Message>()
|
||||
|
||||
init {
|
||||
require(directory.fileSystem == FileSystems.getDefault()) { "Artemis only uses the default file system" }
|
||||
}
|
||||
@ -214,7 +214,9 @@ class ArtemisMessagingClient(directory: Path,
|
||||
|
||||
// This is a hack; transient messages held in memory isn't crash resistant.
|
||||
// TODO: Use Artemis API more effectively so we don't pop messages off a queue that we aren't ready to use.
|
||||
undeliveredMessages += msg
|
||||
state.locked {
|
||||
undeliveredMessages += msg
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@ -312,7 +314,12 @@ class ArtemisMessagingClient(directory: Path,
|
||||
require(!topicSession.isBlank()) { "Topic must not be blank, as the empty topic is a special case." }
|
||||
val handler = Handler(executor, topicSession, callback)
|
||||
handlers.add(handler)
|
||||
undeliveredMessages.removeIf { deliver(it) }
|
||||
val messagesToRedeliver = state.locked {
|
||||
val messagesToRedeliver = undeliveredMessages
|
||||
undeliveredMessages = listOf()
|
||||
messagesToRedeliver
|
||||
}
|
||||
messagesToRedeliver.forEach { deliver(it) }
|
||||
return handler
|
||||
}
|
||||
|
||||
|
27
node/src/test/kotlin/com/r3corda/node/JsonSupportTest.kt
Normal file
27
node/src/test/kotlin/com/r3corda/node/JsonSupportTest.kt
Normal file
@ -0,0 +1,27 @@
|
||||
package com.r3corda.node
|
||||
|
||||
import com.pholser.junit.quickcheck.From
|
||||
import com.pholser.junit.quickcheck.Property
|
||||
import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
|
||||
import com.r3corda.core.testing.PublicKeyGenerator
|
||||
import com.r3corda.node.utilities.JsonSupport
|
||||
import com.r3corda.testing.node.MockIdentityService
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import org.junit.runner.RunWith
|
||||
import java.security.PublicKey
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@RunWith(JUnitQuickcheck::class)
|
||||
class JsonSupportTest {
|
||||
|
||||
companion object {
|
||||
val mapper = JsonSupport.createDefaultMapper(MockIdentityService(mutableListOf()))
|
||||
}
|
||||
|
||||
@Property
|
||||
fun publicKeySerializingWorks(@From(PublicKeyGenerator::class) publicKey: PublicKey) {
|
||||
val serialized = mapper.writeValueAsString(publicKey)
|
||||
val parsedKey = mapper.readValue(serialized, EdDSAPublicKey::class.java)
|
||||
assertEquals(publicKey, parsedKey)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user