[POC] Introducing property based testing to Core #8469

Open
wants to merge 11 commits into
from
View
@@ -654,6 +654,7 @@ if test x$use_upnp != xno; then
)
fi
+
BITCOIN_QT_INIT
dnl sets $bitcoin_enable_qt, $bitcoin_enable_qt_test, $bitcoin_enable_qt_dbus
@@ -741,6 +742,14 @@ if test x$use_tests = xyes; then
fi
fi
+if test x$use_tests = xyes; then
+ dnl check for rapidcheck
+ AC_CHECK_HEADERS(
+ [rapidcheck.h],
+ [AC_CHECK_LIB([rapidcheck], [main],[], [])],
+ [])
+fi
+
if test x$use_boost = xyes; then
BOOST_LIBS="$BOOST_LDFLAGS $BOOST_SYSTEM_LIB $BOOST_FILESYSTEM_LIB $BOOST_PROGRAM_OPTIONS_LIB $BOOST_THREAD_LIB $BOOST_CHRONO_LIB"
@@ -1,4 +1,4 @@
-packages:=boost openssl libevent zeromq
+packages:=boost openssl libevent zeromq rapidcheck
native_packages := native_ccache
qt_native_packages = native_protobuf
@@ -0,0 +1,13 @@
+package=rapidcheck
+$(package)_version=f5d3afa
+$(package)_download_path=https://bitcoin-10596.firebaseapp.com/depends
+$(package)_file_name=$(package)-$($(package)_version).tar.gz
+$(package)_sha256_hash=78cdb8d0185b602e32e66f4e5d1a6ceec1f801dd9641b8a9456c386b1eaaf0e5
+
+define $(package)_config_cmds
+ cmake -DCMAKE_INSTALL_PREFIX=$(build_prefix)
+endef
+
+define $(package)_build_cmds
+ $(MAKE)
+endef
View
@@ -84,7 +84,20 @@ BITCOIN_TESTS =\
test/versionbits_tests.cpp \
test/uint256_tests.cpp \
test/univalue_tests.cpp \
- test/util_tests.cpp
+ test/util_tests.cpp \
+ test/gen/crypto_gen.h \
+ test/gen/script_gen.h \
+ test/gen/transaction_gen.h \
+ test/gen/block_gen.h \
+ test/gen/merkleblock_gen.h \
+ test/gen/bloom_gen.cpp \
+ test/gen/bloom_gen.h \
+ test/key_properties.cpp \
+ test/script_properties.cpp \
+ test/block_properties.cpp \
+ test/merkleblock_properties.cpp \
+ test/bloom_properties.cpp \
+ test/transaction_properties.cpp
if ENABLE_WALLET
BITCOIN_TESTS += \
@@ -0,0 +1,32 @@
+#include <boost/test/unit_test.hpp>
+#include <rapidcheck/boost_test.h>
+#include <rapidcheck/gen/Arbitrary.h>
+#include <rapidcheck/Gen.h>
+
+#include "test/test_bitcoin.h"
+#include "primitives/block.h"
+#include "test/gen/block_gen.h"
+
+
+BOOST_FIXTURE_TEST_SUITE(block_properties, BasicTestingSetup)
+
+RC_BOOST_PROP(blockheader_serialization_symmetry, (CBlockHeader header)) {
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ ss << header;
+ CBlockHeader header2;
+ ss >> header2;
+ CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION);
+ ss << header;
+ ss1 << header2;
+ RC_ASSERT(ss.str() == ss1.str());
+}
+
+RC_BOOST_PROP(block_serialization_symmetry, (CBlock block)) {
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ ss << block;
+ CBlock block2;
+ ss >> block2;
+ RC_ASSERT(block.GetHash() == block2.GetHash());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
@@ -0,0 +1,33 @@
+// Copyright (c) 2012-2016 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include "key.h"
+
+#include "test/test_bitcoin.h"
+
+#include <boost/test/unit_test.hpp>
+#include <rapidcheck/boost_test.h>
+#include <rapidcheck/gen/Arbitrary.h>
+#include <rapidcheck/Gen.h>
+
+#include "test/gen/bloom_gen.h"
+#include "test/gen/crypto_gen.h"
+
+BOOST_FIXTURE_TEST_SUITE(bloom_properties, BasicTestingSetup)
+
+RC_BOOST_PROP(no_false_negatives, (CBloomFilter bloomFilter, uint256 hash)) {
+ bloomFilter.insert(hash);
+ RC_ASSERT(bloomFilter.contains(hash));
+}
+
+RC_BOOST_PROP(serialization_symmetry, (CBloomFilter bloomFilter)) {
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ ss << bloomFilter;
+ CBloomFilter bloomFilter2;
+ ss >> bloomFilter2;
+ CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION);
+ ss << bloomFilter;
+ ss1 << bloomFilter2;
+ RC_ASSERT(ss.str() == ss1.str());
+}
+BOOST_AUTO_TEST_SUITE_END()
View
@@ -0,0 +1,57 @@
+#ifndef BITCOIN_TEST_GEN_BLOCK_GEN_H
+#define BITCOIN_TEST_GEN_BLOCK_GEN_H
+
+#include "test/gen/crypto_gen.h"
+#include "test/gen/transaction_gen.h"
+#include "uint256.h"
+#include "primitives/block.h"
+
+#include <rapidcheck/gen/Arbitrary.h>
+#include <rapidcheck/Gen.h>
+
+/** Generator for the primitives of a block header */
+inline rc::Gen<std::tuple<int32_t, uint256, uint256, uint32_t, uint32_t, uint32_t>> blockHeaderPrimitives() {
+ return rc::gen::tuple(rc::gen::arbitrary<int32_t>(),
+ rc::gen::arbitrary<uint256>(), rc::gen::arbitrary<uint256>(),
+ rc::gen::arbitrary<uint32_t>(), rc::gen::arbitrary<uint32_t>(), rc::gen::arbitrary<uint32_t>());
+}
+
+namespace rc {
+
+ /** Generator for a new CBlockHeader */
+ template<>
+ struct Arbitrary<CBlockHeader> {
+ static Gen<CBlockHeader> arbitrary() {
+ return gen::map(blockHeaderPrimitives(), [](std::tuple<int32_t, uint256, uint256, uint32_t, uint32_t, uint32_t> headerPrimitives) {
+ int32_t nVersion;
+ uint256 hashPrevBlock;
+ uint256 hashMerkleRoot;
+ uint32_t nTime;
+ uint32_t nBits;
+ uint32_t nNonce;
+ std::tie(nVersion,hashPrevBlock, hashMerkleRoot, nTime,nBits,nNonce) = headerPrimitives;
+ CBlockHeader header;
+ header.nVersion = nVersion;
+ header.hashPrevBlock = hashPrevBlock;
+ header.hashMerkleRoot = hashMerkleRoot;
+ header.nTime = nTime;
+ header.nBits = nBits;
+ header.nNonce = nNonce;
+ return header;
+ });
+ };
+ };
+
+ /** Generator for a new CBlock */
+ template<>
+ struct Arbitrary<CBlock> {
+ static Gen<CBlock> arbitrary() {
+ return gen::map(gen::nonEmpty<std::vector<CTransactionRef>>(), [](std::vector<CTransactionRef> txRefs) {
+ CBlock block;
+ block.vtx = txRefs;
+ return block;
+ });
+ }
+ };
+}
+#endif
View
@@ -0,0 +1,43 @@
+#ifndef BITCOIN_TEST_GEN_BLOOM_GEN_H
+#define BITCOIN_TEST_GEN_BLOOM_GEN_H
+
+#include "bloom.h"
+#include "merkleblock.h"
+
+#include <math.h>
+
+#include <rapidcheck/Gen.h>
+#include <rapidcheck/gen/Arbitrary.h>
+
+/** Generates a double between 0,1 exclusive */
+rc::Gen<double> BetweenZeroAndOne();
+
+rc::Gen<std::tuple<unsigned int, double, unsigned int, unsigned int>> BloomFilterPrimitives();
+
+namespace rc {
+
+
+ /** Generator for a new CBloomFilter*/
+ template<>
+ struct Arbitrary<CBloomFilter> {
+ static Gen<CBloomFilter> arbitrary() {
+ return gen::map(BloomFilterPrimitives(), [](std::tuple<unsigned int, double, unsigned int, unsigned int> filterPrimitives) {
+ unsigned int numElements;
+ double fpRate;
+ unsigned int nTweakIn;
+ unsigned int bloomFlag;
+ std::tie(numElements, fpRate, nTweakIn, bloomFlag) = filterPrimitives;
+ return CBloomFilter(numElements,fpRate,nTweakIn,bloomFlag);
+ });
+ };
+ };
+}
+
+/** Returns a bloom filter loaded with the given uint256s */
+rc::Gen<std::tuple<CBloomFilter, std::vector<uint256>>> LoadedBloomFilter();
+
+/** Loads an arbitrary bloom filter with the given hashes */
+rc::Gen<std::tuple<CBloomFilter, std::vector<uint256>>> LoadBloomFilter(std::vector<uint256>& hashes);
+
+
+#endif
View
@@ -0,0 +1,53 @@
+#ifndef BITCOIN_TEST_GEN_CRYPTO_GEN_H
+#define BITCOIN_TEST_GEN_CRYPTO_GEN_H
+
+#include "key.h"
+#include "random.h"
+#include "uint256.h"
+#include <rapidcheck/gen/Arbitrary.h>
+#include <rapidcheck/Gen.h>
+#include <rapidcheck/gen/Create.h>
+#include <rapidcheck/gen/Numeric.h>
+namespace rc {
+
+ /** Generator for a new CKey */
+ template<>
+ struct Arbitrary<CKey> {
+ static Gen<CKey> arbitrary() {
+ return rc::gen::map<int>([](int x) {
+ CKey key;
+ key.MakeNewKey(true);
+ return key;
+ });
+ };
+ };
+
+ /** Generator for a CPrivKey */
+ template<>
+ struct Arbitrary<CPrivKey> {
+ static Gen<CPrivKey> arbitrary() {
+ return gen::map(gen::arbitrary<CKey>(), [](CKey key) {
+ return key.GetPrivKey();
+ });
+ };
+ };
+
+ /** Generator for a new CPubKey */
+ template<>
+ struct Arbitrary<CPubKey> {
+ static Gen<CPubKey> arbitrary() {
+ return gen::map(gen::arbitrary<CKey>(), [](CKey key) {
+ return key.GetPubKey();
+ });
+ };
+ };
+
+ /** Generates a arbitrary uint256 */
+ template<>
+ struct Arbitrary<uint256> {
+ static Gen<uint256> arbitrary() {
+ return rc::gen::just(GetRandHash());
+ };
+ };
+}
+#endif
@@ -0,0 +1,89 @@
+#ifndef BITCOIN_TEST_GEN_MERKLEBLOCK_GEN_H
+#define BITCOIN_TEST_GEN_MERKLEBLOCK_GEN_H
+
+#include <rapidcheck/gen/Arbitrary.h>
+#include <rapidcheck/Gen.h>
+
+#include "merkleblock.h"
+#include "test/gen/block_gen.h"
+
+#include <iostream>
+namespace rc {
+ /** Returns a CMerkleblock with the hashes that match inside of the CPartialMerkleTree */
+ template<>
+ struct Arbitrary<std::pair<CMerkleBlock, std::set<uint256>>> {
+ static Gen<std::pair<CMerkleBlock,std::set<uint256>>> arbitrary() {
+ return gen::map(gen::arbitrary<CBlock>(), [](CBlock block) {
+ std::set<uint256> hashes;
+ for(unsigned int i = 0; i < block.vtx.size(); i++) {
+ //pretty naive to include every other txid in the merkle block
+ //but this will work for now.
+ if (i % 2 == 0) {
+ hashes.insert(block.vtx[i]->GetHash());
+ }
+ }
+ return std::make_pair(CMerkleBlock(block,hashes),hashes);
+ });
+ };
+ };
+
+
+ Gen<std::vector<uint256>> betweenZeroAnd100 = gen::suchThat<std::vector<uint256>>([](std::vector<uint256> hashes) {
+ return hashes.size() <= 100;
+ });
+
+ /** Returns an arbitrary CMerkleBlock */
+ template<>
+ struct Arbitrary<CMerkleBlock> {
+ static Gen<CMerkleBlock> arbitrary() {
+ return gen::map(gen::arbitrary<std::pair<CMerkleBlock, std::set<uint256>>>(),
+ [](std::pair<CMerkleBlock, std::set<uint256>> merkleBlockWithTxIds) {
+ CMerkleBlock merkleBlock = merkleBlockWithTxIds.first;
+ std::set<uint256> insertedHashes = merkleBlockWithTxIds.second;
+ return merkleBlock;
+ });
+ };
+ };
+
+
+
+ /** Generates a CPartialMerkleTree and returns the PartialMerkleTree along
+ * with the txids that should be matched inside of it */
+ template<>
+ struct Arbitrary<std::pair<CPartialMerkleTree, std::vector<uint256>>> {
+ static Gen<std::pair<CPartialMerkleTree, std::vector<uint256>>> arbitrary() {
+ return gen::map(gen::arbitrary<std::vector<uint256>>(), [](std::vector<uint256> txids) {
+ //note this use of 'gen::nonEmpty' above, if we have an empty vector of txids
+ //we will get a memory access violation when calling the CPartialMerkleTree
+ //constructor below. On one hand we shouldn't every have CPartialMerkleTree
+ //with no txids, but on the other hand, it seems we should call
+ //CPartialMerkleTree() inside of CPartialMerkleTree(txids,matches)
+ //if we have zero txids.
+ //Some one who knows more than me will have to elaborate if the memory access violation
+ //is a desirable failure mode or not...
+ std::vector<bool> matches;
+ std::vector<uint256> matchedTxs;
+ for(unsigned int i = 0; i < txids.size(); i++) {
+ //pretty naive to include every other txid in the merkle block
+ //but this will work for now.
+ matches.push_back(i % 2 == 1);
+ if (i % 2 == 1) {
+ matchedTxs.push_back(txids[i]);
+ }
+ }
+ return std::make_pair(CPartialMerkleTree(txids,matches),matchedTxs);
+ });
+ };
+ };
+
+ template<>
+ struct Arbitrary<CPartialMerkleTree> {
+ static Gen<CPartialMerkleTree> arbitrary() {
+ return gen::map(gen::arbitrary<std::pair<CPartialMerkleTree,std::vector<uint256>>>(),
+ [](std::pair<CPartialMerkleTree,std::vector<uint256>> p) {
+ return p.first;
+ });
+ };
+ };
+}
+#endif
View
@@ -0,0 +1,20 @@
+#ifndef BITCOIN_TEST_GEN_SCRIPT_GEN_H
+#define BITCOIN_TEST_GEN_SCRIPT_GEN_H
+
+#include "script/script.h"
+#include <rapidcheck/gen/Arbitrary.h>
+#include <rapidcheck/Gen.h>
+#include <rapidcheck/gen/Numeric.h>
+#include <rapidcheck/gen/Container.h>
+namespace rc {
+
+ template<>
+ struct Arbitrary<CScript> {
+ static Gen<CScript> arbitrary() {
+ return gen::map(gen::arbitrary<std::vector<unsigned char>>(), [](std::vector<unsigned char> script) {
+ return CScript(script);
+ });
+ };
+ };
+}
+#endif
Oops, something went wrong.