core/*: Encapsulate blockchain data storage

This introduces a blockchain data storage package, `chaindb`. `chaindb.ChainDB`
serves as a layer between the blockchain (and headerchain and lightchain) and
the underlying key-value storage. It can be best thought of as the blockchain
data counterpart to `core/state/statedb` and ultimately a replacement for
`rawdb`*, whose code would be moved where it is currently being called within
`chaindb.ChainDB` whose use is a 1:1 mapping from before.**

The goals of this work are twofold:
1. Consolidate code interacting with storage.
2. With (1) in place continue the freezer work initiated #17814 inside.

Follow up work from this PR would be:
1. Use `chaindb.ChainDB` in the remaining places where `rawdb` is being used.
2. Migrate the various caches used between `ethdb.Database` inside the storage
   and also expose tuning options to set them.
3. Continue with the freezer work.

This also introduces the `github.com/google/go-cmp/cmp/cmpopts` package,
which is used for the tests added for this work.

* The only exception is `WritePreimages` which is really a `StateDB`
concept, however it is included as it is currently stored/retrieved
through methods in `rawdb`.
** The only exception is `WriteCanonicalHash` whose method parameter order
was swapped as the `number` is the key and the `hash` is the value being
mapped to it, making it consistent with the "key" then "value" convention
the remaining methods use.
This commit is contained in:
Matthew Halpern 2019-03-02 15:50:07 -08:00
parent f9aa1cd21f
commit 3cf22e77b4
30 changed files with 4219 additions and 92 deletions

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/chaindb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader"
@ -390,7 +391,7 @@ func copyDb(ctx *cli.Context) error {
if err != nil {
return err
}
hc, err := core.NewHeaderChain(db, chain.Config(), chain.Engine(), func() bool { return false })
hc, err := core.NewHeaderChain(chaindb.Wrap(db), chain.Config(), chain.Engine(), func() bool { return false })
if err != nil {
return err
}

View file

@ -31,7 +31,7 @@ import (
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/chaindb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@ -102,6 +102,7 @@ type BlockChain struct {
cacheConfig *CacheConfig // Cache configuration for pruning
db ethdb.Database // Low level persistent database to store final content in
chainDB *chaindb.ChainDB
triegc *prque.Prque // Priority queue mapping block numbers to tries to gc
gcproc time.Duration // Accumulates canonical block processing for trie dumping
@ -162,10 +163,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
futureBlocks, _ := lru.New(maxFutureBlocks)
badBlocks, _ := lru.New(badBlockLimit)
chainDB := chaindb.Wrap(db)
bc := &BlockChain{
chainConfig: chainConfig,
cacheConfig: cacheConfig,
db: db,
chainDB: chainDB,
triegc: prque.New(nil),
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
quit: make(chan struct{}),
@ -183,7 +186,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
bc.SetProcessor(NewStateProcessor(chainConfig, bc, engine))
var err error
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.getProcInterrupt)
bc.hc, err = NewHeaderChain(chainDB, chainConfig, engine, bc.getProcInterrupt)
if err != nil {
return nil, err
}
@ -225,7 +228,7 @@ func (bc *BlockChain) GetVMConfig() *vm.Config {
// assumes that the chain manager mutex is held.
func (bc *BlockChain) loadLastState() error {
// Restore the last known head block
head := rawdb.ReadHeadBlockHash(bc.db)
head := bc.chainDB.ReadHeadBlockHash()
if head == (common.Hash{}) {
// Corrupt or empty database, init from scratch
log.Warn("Empty database, resetting chain")
@ -251,7 +254,7 @@ func (bc *BlockChain) loadLastState() error {
// Restore the last known head header
currentHeader := currentBlock.Header()
if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) {
if head := bc.chainDB.ReadHeadHeaderHash(); head != (common.Hash{}) {
if header := bc.GetHeaderByHash(head); header != nil {
currentHeader = header
}
@ -260,7 +263,7 @@ func (bc *BlockChain) loadLastState() error {
// Restore the last known head fast block
bc.currentFastBlock.Store(currentBlock)
if head := rawdb.ReadHeadFastBlockHash(bc.db); head != (common.Hash{}) {
if head := bc.chainDB.ReadHeadFastBlockHash(); head != (common.Hash{}) {
if block := bc.GetBlockByHash(head); block != nil {
bc.currentFastBlock.Store(block)
}
@ -291,8 +294,8 @@ func (bc *BlockChain) SetHead(head uint64) error {
defer bc.chainmu.Unlock()
// Rewind the header chain, deleting all block bodies until then
delFn := func(db rawdb.DatabaseDeleter, hash common.Hash, num uint64) {
rawdb.DeleteBody(db, hash, num)
delFn := func(batch *chaindb.Batch, hash common.Hash, num uint64) {
batch.DeleteBody(hash, num)
}
bc.hc.SetHead(head, delFn)
currentHeader := bc.hc.CurrentHeader()
@ -328,8 +331,8 @@ func (bc *BlockChain) SetHead(head uint64) error {
currentBlock := bc.CurrentBlock()
currentFastBlock := bc.CurrentFastBlock()
rawdb.WriteHeadBlockHash(bc.db, currentBlock.Hash())
rawdb.WriteHeadFastBlockHash(bc.db, currentFastBlock.Hash())
bc.chainDB.WriteHeadBlockHash(currentBlock.Hash())
bc.chainDB.WriteHeadFastBlockHash(currentFastBlock.Hash())
return bc.loadLastState()
}
@ -433,7 +436,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
if err := bc.hc.WriteTd(genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()); err != nil {
log.Crit("Failed to write genesis block TD", "err", err)
}
rawdb.WriteBlock(bc.db, genesis)
bc.chainDB.WriteBlock(genesis)
bc.genesisBlock = genesis
bc.insert(bc.genesisBlock)
@ -507,18 +510,18 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
// Note, this function assumes that the `mu` mutex is held!
func (bc *BlockChain) insert(block *types.Block) {
// If the block is on a side chain or an unknown one, force other heads onto it too
updateHeads := rawdb.ReadCanonicalHash(bc.db, block.NumberU64()) != block.Hash()
updateHeads := bc.chainDB.ReadCanonicalHash(block.NumberU64()) != block.Hash()
// Add the block to the canonical chain number scheme and mark as the head
rawdb.WriteCanonicalHash(bc.db, block.Hash(), block.NumberU64())
rawdb.WriteHeadBlockHash(bc.db, block.Hash())
bc.chainDB.WriteCanonicalHash(block.NumberU64(), block.Hash())
bc.chainDB.WriteHeadBlockHash(block.Hash())
bc.currentBlock.Store(block)
// If the block is better than our head or is on a different chain, force update heads
if updateHeads {
bc.hc.SetCurrentHeader(block.Header())
rawdb.WriteHeadFastBlockHash(bc.db, block.Hash())
bc.chainDB.WriteHeadFastBlockHash(block.Hash())
bc.currentFastBlock.Store(block)
}
@ -541,7 +544,7 @@ func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {
if number == nil {
return nil
}
body := rawdb.ReadBody(bc.db, hash, *number)
body := bc.chainDB.ReadBody(hash, *number)
if body == nil {
return nil
}
@ -561,7 +564,7 @@ func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue {
if number == nil {
return nil
}
body := rawdb.ReadBodyRLP(bc.db, hash, *number)
body := bc.chainDB.ReadBodyRLP(hash, *number)
if len(body) == 0 {
return nil
}
@ -575,7 +578,7 @@ func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool {
if bc.blockCache.Contains(hash) {
return true
}
return rawdb.HasBody(bc.db, hash, number)
return bc.chainDB.HasBody(hash, number)
}
// HasFastBlock checks if a fast block is fully present in the database or not.
@ -586,7 +589,7 @@ func (bc *BlockChain) HasFastBlock(hash common.Hash, number uint64) bool {
if bc.receiptsCache.Contains(hash) {
return true
}
return rawdb.HasReceipts(bc.db, hash, number)
return bc.chainDB.HasReceipts(hash, number)
}
// HasState checks if state trie is fully present in the database or not.
@ -613,7 +616,7 @@ func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
if block, ok := bc.blockCache.Get(hash); ok {
return block.(*types.Block)
}
block := rawdb.ReadBlock(bc.db, hash, number)
block := bc.chainDB.ReadBlock(hash, number)
if block == nil {
return nil
}
@ -634,7 +637,7 @@ func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block {
// GetBlockByNumber retrieves a block from the database by number, caching it
// (associated with its hash) if found.
func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block {
hash := rawdb.ReadCanonicalHash(bc.db, number)
hash := bc.chainDB.ReadCanonicalHash(number)
if hash == (common.Hash{}) {
return nil
}
@ -646,11 +649,11 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
if receipts, ok := bc.receiptsCache.Get(hash); ok {
return receipts.(types.Receipts)
}
number := rawdb.ReadHeaderNumber(bc.db, hash)
number := bc.chainDB.ReadHeaderNumber(hash)
if number == nil {
return nil
}
receipts := rawdb.ReadReceipts(bc.db, hash, *number)
receipts := bc.chainDB.ReadReceipts(hash, *number)
if receipts == nil {
return nil
}
@ -777,12 +780,12 @@ func (bc *BlockChain) Rollback(chain []common.Hash) {
if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock.Hash() == hash {
newFastBlock := bc.GetBlock(currentFastBlock.ParentHash(), currentFastBlock.NumberU64()-1)
bc.currentFastBlock.Store(newFastBlock)
rawdb.WriteHeadFastBlockHash(bc.db, newFastBlock.Hash())
bc.chainDB.WriteHeadFastBlockHash(newFastBlock.Hash())
}
if currentBlock := bc.CurrentBlock(); currentBlock.Hash() == hash {
newBlock := bc.GetBlock(currentBlock.ParentHash(), currentBlock.NumberU64()-1)
bc.currentBlock.Store(newBlock)
rawdb.WriteHeadBlockHash(bc.db, newBlock.Hash())
bc.chainDB.WriteHeadBlockHash(newBlock.Hash())
}
}
}
@ -845,7 +848,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
stats = struct{ processed, ignored int32 }{}
start = time.Now()
bytes = 0
batch = bc.db.NewBatch()
batch = bc.chainDB.NewBatch()
)
for i, block := range blockChain {
receipts := receiptChain[i]
@ -867,9 +870,9 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
return i, fmt.Errorf("failed to set receipts data: %v", err)
}
// Write all the data out into the database
rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body())
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
rawdb.WriteTxLookupEntries(batch, block)
batch.WriteBody(block.Hash(), block.NumberU64(), block.Body())
batch.WriteReceipts(block.Hash(), block.NumberU64(), receipts)
batch.WriteTxLookupEntries(block)
stats.processed++
@ -894,7 +897,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
if td := bc.GetTd(head.Hash(), head.NumberU64()); td != nil { // Rewind may have occurred, skip in that case
currentFastBlock := bc.CurrentFastBlock()
if bc.GetTd(currentFastBlock.Hash(), currentFastBlock.NumberU64()).Cmp(td) < 0 {
rawdb.WriteHeadFastBlockHash(bc.db, head.Hash())
bc.chainDB.WriteHeadFastBlockHash(head.Hash())
bc.currentFastBlock.Store(head)
}
}
@ -925,7 +928,7 @@ func (bc *BlockChain) WriteBlockWithoutState(block *types.Block, td *big.Int) (e
if err := bc.hc.WriteTd(block.Hash(), block.NumberU64(), td); err != nil {
return err
}
rawdb.WriteBlock(bc.db, block)
bc.chainDB.WriteBlock(block)
return nil
}
@ -958,7 +961,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
if err := bc.hc.WriteTd(block.Hash(), block.NumberU64(), externTd); err != nil {
return NonStatTy, err
}
rawdb.WriteBlock(bc.db, block)
bc.chainDB.WriteBlock(block)
root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number()))
if err != nil {
@ -1020,8 +1023,8 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
}
// Write other block data using a batch.
batch := bc.db.NewBatch()
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
batch := bc.chainDB.NewBatch()
batch.WriteReceipts(block.Hash(), block.NumberU64(), receipts)
// If the total difficulty is higher than our known, add it to the canonical chain
// Second clause in the if statement reduces the vulnerability to selfish mining.
@ -1049,8 +1052,8 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
}
}
// Write the positional metadata for transaction/receipt lookups and preimages
rawdb.WriteTxLookupEntries(batch, block)
rawdb.WritePreimages(batch, state.Preimages())
batch.WriteTxLookupEntries(block)
batch.WritePreimages(state.Preimages())
status = CanonStatTy
} else {
@ -1441,7 +1444,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
if number == nil {
return
}
receipts := rawdb.ReadReceipts(bc.db, hash, *number)
receipts := bc.chainDB.ReadReceipts(hash, *number)
for _, receipt := range receipts {
for _, log := range receipt.Logs {
del := *log
@ -1510,16 +1513,16 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
// insert the block in the canonical way, re-writing history
bc.insert(newChain[i])
// write lookup entries for hash based transaction/receipt searches
rawdb.WriteTxLookupEntries(bc.db, newChain[i])
bc.chainDB.WriteTxLookupEntries(newChain[i])
addedTxs = append(addedTxs, newChain[i].Transactions()...)
}
// calculate the difference between deleted and added transactions
diff := types.TxDifference(deletedTxs, addedTxs)
// When transactions get deleted from the database that means the
// receipts that were created in the fork must also be deleted
batch := bc.db.NewBatch()
batch := bc.chainDB.NewBatch()
for _, tx := range diff {
rawdb.DeleteTxLookupEntry(batch, tx.Hash())
batch.DeleteTxLookupEntry(tx.Hash())
}
batch.Write()

293
core/chaindb/chaindb.go Normal file
View file

@ -0,0 +1,293 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package chaindb
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
)
// ChainDB is the blockchain data storage layer. It provides the ability
// to retrieve, store, and delete the various constructs (i.e. headers,
// receipts) and metadata (i.e. header hash mappings, transaction locations).
type ChainDB struct {
writer
db ethdb.Database
}
// Batch consolidates writes and deletions to the ChainDB it originated
// from. The changes will be committed when Write is called.
type Batch struct {
writer
batch ethdb.Batch
}
// Consolidates shared writing and deleting code used by both ChainDB and Batch.
// Note that this considers deleting to be a form of writing unlike ethdb.
type writer struct {
db ethdbPutterAndDeleter
}
// Composite type to enable both putting and deleting.
type ethdbPutterAndDeleter interface {
ethdb.Putter
ethdb.Deleter
}
// Wrap creates a new ChainDB that uses the specified database as its
// underlying data store.
func Wrap(db ethdb.Database) *ChainDB {
return &ChainDB{writer{db}, db}
}
// Returns a Batch associated with the ChainDB.
func (c *ChainDB) NewBatch() *Batch {
return newBatch(c.db.NewBatch())
}
func newBatch(batch ethdb.Batch) *Batch {
return &Batch{writer{batch}, batch}
}
// Write commits the changes within the batch to its associated ChainDB.
func (b *Batch) Write() error {
return b.batch.Write()
}
// Reset drops all pending changes contained in the batch.
func (b *Batch) Reset() {
b.batch.Reset()
}
// Value size returns the number of changes pending in the batch.
func (b *Batch) ValueSize() int {
return b.batch.ValueSize()
}
// ReadHeadHeaderHash retrieves the current canonical head header hash.
func (c *ChainDB) ReadHeadHeaderHash() common.Hash {
return rawdb.ReadHeadHeaderHash(c.db)
}
// WriteHeadHeaderHash stores the current canonical head header hash.
func (w *writer) WriteHeadHeaderHash(hash common.Hash) {
rawdb.WriteHeadHeaderHash(w.db, hash)
}
// ReadHeadBlockHash retrieves the current canonical head block hash.
//
// Note that this is different from the canonical head header hash as
// this corresponds to all block data and not just the header.
func (c *ChainDB) ReadHeadBlockHash() common.Hash {
return rawdb.ReadHeadBlockHash(c.db)
}
// WriteHeadBlockHash stores the current fast-sync head block hash.
func (w *writer) WriteHeadBlockHash(hash common.Hash) {
rawdb.WriteHeadBlockHash(w.db, hash)
}
// ReadHeadFastBlockHash retrieves the current fast-sync head block hash.
func (c *ChainDB) ReadHeadFastBlockHash() common.Hash {
return rawdb.ReadHeadFastBlockHash(c.db)
}
// WriteHeadFastBlockHash stores the current head block hash.
//
// Note that this is different from the canonical head header hash as
// this corresponds to all block data and not just the header.
func (w *writer) WriteHeadFastBlockHash(hash common.Hash) {
rawdb.WriteHeadFastBlockHash(w.db, hash)
}
// ReadCanonicalHash retrieves the canonical hash for a block number,
// returning a common.Hash{} if it does not exist.
func (c *ChainDB) ReadCanonicalHash(number uint64) common.Hash {
return rawdb.ReadCanonicalHash(c.db, number)
}
// WriteCanonicalHash stores the canoical hash assigned to a block number.
func (w *writer) WriteCanonicalHash(number uint64, hash common.Hash) {
rawdb.WriteCanonicalHash(w.db, hash, number)
}
// DeleteCanonicalHash removes the number to canonical hash mapping.
func (w *writer) DeleteCanonicalHash(number uint64) {
rawdb.DeleteCanonicalHash(w.db, number)
}
// ReadHeaderNumber returns the header number assigned to a hash.
func (c *ChainDB) ReadHeaderNumber(hash common.Hash) *uint64 {
return rawdb.ReadHeaderNumber(c.db, hash)
}
// writeHeaderNumber associates the block number with the hash.
func (w *writer) writeHeaderNumber(hash common.Hash, number uint64) {
rawdb.WriteHeaderNumber(w.db, hash, number)
}
// HasHeader returns whether the block header associated with the
// specified hash and number pair is currently stored.
func (c *ChainDB) HasHeader(hash common.Hash, number uint64) bool {
return rawdb.HasHeader(c.db, hash, number)
}
// ReadHeader returns the block header associated with the specified
// hash and number pair.
func (c *ChainDB) ReadHeader(hash common.Hash, number uint64) *types.Header {
return rawdb.ReadHeader(c.db, hash, number)
}
// WriteHeader stores the block header, including its associated
// hash-to-number mapping.
func (w *writer) WriteHeader(header *types.Header) {
rawdb.WriteHeader(w.db, header)
}
// DeleteHeader removes the block header and its associated
// hash-to-number mapping.
func (w *writer) DeleteHeader(hash common.Hash, number uint64) {
rawdb.DeleteHeader(w.db, hash, number)
}
// HasBody returns whether the block body associated with the
// specified hash and number pair is currently stored.
func (c *ChainDB) HasBody(hash common.Hash, number uint64) bool {
return rawdb.HasBody(c.db, hash, number)
}
// ReadBody returns the block body associated with the specified
// hash and number pair.
func (c *ChainDB) ReadBody(hash common.Hash, number uint64) *types.Body {
return rawdb.ReadBody(c.db, hash, number)
}
// WriteBody stores the block body associated with hash and number.
func (w *writer) WriteBody(hash common.Hash, number uint64, body *types.Body) {
rawdb.WriteBody(w.db, hash, number, body)
}
// DeleteBody removes the block body associated with the specified hash
// and number pair.
func (w *writer) DeleteBody(hash common.Hash, number uint64) {
rawdb.DeleteBody(w.db, hash, number)
}
// ReadBodyRLP returns the RLP-encoded block body associated with the specified
// hash and number pair.
func (c *ChainDB) ReadBodyRLP(hash common.Hash, number uint64) rlp.RawValue {
return rawdb.ReadBodyRLP(c.db, hash, number)
}
// ReadBlock returns the block associated with the specified hash and
// number pair.
func (c *ChainDB) ReadBlock(hash common.Hash, number uint64) *types.Block {
return rawdb.ReadBlock(c.db, hash, number)
}
// WriteBlock stores the block (header and body), including its associated
// hash-to-number mapping.
func (w *writer) WriteBlock(block *types.Block) {
rawdb.WriteBlock(w.db, block)
}
// DeleteBlock removes the block, receipts and other metadata associated
// with the specified hash and number pair.
func (w *writer) DeleteBlock(hash common.Hash, number uint64) {
rawdb.DeleteBody(w.db, hash, number)
}
// HasHeader returns whether the transaction receipts associated with
// the specified hash and number pair is currently stored.
func (c *ChainDB) HasReceipts(hash common.Hash, number uint64) bool {
return rawdb.HasReceipts(c.db, hash, number)
}
// ReadReceipts returns the transaction receipts in the block with the
// specified hash and number pair.
func (c *ChainDB) ReadReceipts(hash common.Hash, number uint64) types.Receipts {
return rawdb.ReadReceipts(c.db, hash, number)
}
// WriteReceipts stores transaction receipts associated with the hash and
// number pair.
func (w *writer) WriteReceipts(hash common.Hash, number uint64, receipts types.Receipts) {
rawdb.WriteReceipts(w.db, hash, number, receipts)
}
// DeleteReceipts removes transaction receipts associated with the hash and
// number pair.
func (w *writer) DeleteReceipts(hash common.Hash, number uint64) {
rawdb.DeleteReceipts(w.db, hash, number)
}
// ReadTD returns the total difficulty associated with the specified
// specified block header hash and number pair.
func (c *ChainDB) ReadTD(hash common.Hash, number uint64) *big.Int {
return rawdb.ReadTd(c.db, hash, number)
}
// WriteTd stores the total difficulty associated with the specified
// hash and number pair.
func (w *writer) WriteTD(hash common.Hash, number uint64, td *big.Int) {
rawdb.WriteTd(w.db, hash, number, td)
}
// DeleteTd removes the total difficulty data associated with the specified
// hash and number pair.
func (w *writer) DeleteTD(hash common.Hash, number uint64) {
rawdb.DeleteTd(w.db, hash, number)
}
// ReadTxLookupEntry retrieves the block hash the transaction is located in.
func (c *ChainDB) ReadTxLookupEntry(hash common.Hash) common.Hash {
return rawdb.ReadTxLookupEntry(c.db, hash)
}
// WriteTxLookupEntries stores the block hash each of the transactions
// within the block are stored in, enabling hash based transaction and receipt lookups.
// Note that the block hash-to-number mapping and block body is assumed to have been stored
// already by WriteBlock.
func (w *writer) WriteTxLookupEntries(block *types.Block) {
rawdb.WriteTxLookupEntries(w.db, block)
}
// DeleteTxLookupEntry removes the lookup metadat for transaction hash.
func (w *writer) DeleteTxLookupEntry(hash common.Hash) {
rawdb.DeleteTxLookupEntry(w.db, hash)
}
func (c *ChainDB) ReadTransaction(hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) {
return rawdb.ReadTransaction(c.db, hash)
}
// ReadPreimage retrieves a the preimage of the hash.
func (c *ChainDB) ReadPreimage(hash common.Hash) []byte {
return rawdb.ReadPreimage(c.db, hash)
}
// WritePreimages stores the provided preimage mappings.
func (w *writer) WritePreimages(preimages map[common.Hash][]byte) {
rawdb.WritePreimages(w.db, preimages)
}

View file

@ -0,0 +1,579 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package chaindb
import (
"bytes"
"encoding/hex"
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
type testWriter interface {
WriteHeadHeaderHash(common.Hash)
WriteHeadBlockHash(common.Hash)
WriteHeadFastBlockHash(common.Hash)
WriteCanonicalHash(uint64, common.Hash)
DeleteCanonicalHash(uint64)
writeHeaderNumber(common.Hash, uint64)
WriteHeader(*types.Header)
DeleteHeader(common.Hash, uint64)
WriteBody(common.Hash, uint64, *types.Body)
DeleteBody(common.Hash, uint64)
WriteBlock(*types.Block)
DeleteBlock(common.Hash, uint64)
WriteReceipts(common.Hash, uint64, types.Receipts)
DeleteReceipts(common.Hash, uint64)
WriteTD(common.Hash, uint64, *big.Int)
DeleteTD(common.Hash, uint64)
WriteTxLookupEntries(*types.Block)
DeleteTxLookupEntry(common.Hash)
WritePreimages(preimages map[common.Hash][]byte)
}
var tests = []struct {
name string
createWrapper func(*ChainDB) *testWriterWrapper
}{
{
"ChainDB",
chainDBWrapper,
},
{
"Batch",
batchWrapper,
},
}
func chainDBWrapper(chainDB *ChainDB) *testWriterWrapper {
return &testWriterWrapper{
chainDB,
func() error {
return nil
},
}
}
func batchWrapper(chainDB *ChainDB) *testWriterWrapper {
batch := chainDB.NewBatch()
return &testWriterWrapper{
batch,
func() error {
return batch.Write()
},
}
}
type testWriterWrapper struct {
writer testWriter
done func() error
}
func TestChainDB_ReadWriteHeadHeaderHash(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
want := common.HexToHash("0x1")
if got := chainDB.ReadHeadHeaderHash(); got != (common.Hash{}) {
t.Fatalf("chainDB.ReadHeadHeaderHash() = %v, want %v", got, common.Hash{})
}
wrapper.writer.WriteHeadHeaderHash(want)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadHeadHeaderHash(); got != want {
t.Fatalf("chainDB.ReadHeadHeaderHash() = %v, want %v", got, want)
}
})
}
}
func TestChainDB_ReadWriteHeadBlockHash(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
want := common.HexToHash("0x1")
if got := chainDB.ReadHeadBlockHash(); got != (common.Hash{}) {
t.Fatalf("chainDB.ReadHeadBlockHash() = %v, want %v", got, common.Hash{})
}
wrapper.writer.WriteHeadBlockHash(want)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadHeadBlockHash(); got != want {
t.Fatalf("chainDB.ReadHeadBlockHash() = %v, want %v", got, want)
}
})
}
}
func TestChainDB_ReadWriteHeadFastBlockHash(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
want := common.HexToHash("0x1")
if got := chainDB.ReadHeadFastBlockHash(); got != (common.Hash{}) {
t.Fatalf("chainDB.ReadHeadFastBlockHash() = %v, want %v", got, common.Hash{})
}
wrapper.writer.WriteHeadFastBlockHash(want)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadHeadFastBlockHash(); got != want {
t.Fatalf("chainDB.ReadFastHeadBlockHash() = %v, want %v", got, want)
}
})
}
}
func TestChainDB_ReadWriteDeleteCanonicalHash(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
number := uint64(22)
want := common.HexToHash("0x1")
if got := chainDB.ReadCanonicalHash(number); got != (common.Hash{}) {
t.Fatalf("chainDB.ReadCanonicalHash(%d) = %v, want %v", number, got, common.Hash{})
}
wrapper.writer.WriteCanonicalHash(number, want)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadCanonicalHash(number); got != want {
t.Fatalf("chainDB.ReadCanonicalHash(%d) = %+v, want %+v", number, got, want)
}
wrapper.writer.DeleteCanonicalHash(number)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadCanonicalHash(number); got != (common.Hash{}) {
t.Fatalf("chainDB.ReadCanonicalHash(%d) = %v, want %v", number, got, common.Hash{})
}
})
}
}
func TestChainDB_ReadWriteHeaderNumber(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
hash := common.HexToHash("0x1")
want := uint64(1)
if got := chainDB.ReadHeaderNumber(hash); got != nil {
t.Fatalf("chainDB.ReadHeaderNumber(%q) = %d, want <nil>", hash.String(), *got)
}
wrapper.writer.writeHeaderNumber(hash, want)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadHeaderNumber(hash); got == nil {
t.Fatalf("chainDB.ReadHeaderNumber(%q) = <nil>, want %d", hash.String(), want)
} else if *got != want {
t.Fatalf("chainDB.ReadHeaderNumber(%q) = %d, want %d", hash.String(), *got, want)
}
})
}
}
func TestChainDB_HasReadWriteDeleteHeader(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
want := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(2), Time: big.NewInt(3), Extra: []byte("test header")}
if present := chainDB.HasHeader(want.Hash(), want.Number.Uint64()); present {
t.Fatalf("chainDB.HasHeader(%q, %d) = true, want false", want.Hash().String(), want.Number.Uint64())
}
if got := chainDB.ReadHeader(want.Hash(), want.Number.Uint64()); got != nil {
t.Fatalf("chainDB.ReadHeader(%q, %d) = %+v, want <nil>", want.Hash().String(), want.Number.Uint64(), got)
}
wrapper.writer.WriteHeader(want)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if present := chainDB.HasHeader(want.Hash(), want.Number.Uint64()); !present {
t.Fatalf("chainDB.HasHeader(%q, %d) = false, want true", want.Hash().String(), want.Number.Uint64())
}
got := chainDB.ReadHeader(want.Hash(), want.Number.Uint64())
if diff := cmp.Diff(want, got, cmpopts.IgnoreUnexported(big.Int{})); diff != "" {
t.Fatalf("chainDB.ReadHeader(%q, %d) headers differ (-want +got):\n%s", want.Hash().String(), want.Number.Uint64(), diff)
}
wrapper.writer.DeleteHeader(want.Hash(), want.Number.Uint64())
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if present := chainDB.HasHeader(want.Hash(), want.Number.Uint64()); present {
t.Fatalf("chainDB.HasHeader(%q, %d) = true, want false", want.Hash().String(), want.Number.Uint64())
}
if got := chainDB.ReadHeader(want.Hash(), want.Number.Uint64()); got != nil {
t.Fatalf("chainDB.ReadHeader(%q, %d) = %+v, want <nil>", want.Hash().String(), want.Number.Uint64(), got)
}
})
}
}
func TestChainDB_HasReadWriteDeleteBody(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
hash := common.HexToHash("0x1")
number := uint64(22)
tx := types.NewTransaction(1, common.HexToAddress("0x2"), big.NewInt(333), 4444, big.NewInt(55555), []byte{66, 66, 66})
want := &types.Body{Transactions: []*types.Transaction{tx}, Uncles: []*types.Header{{Number: big.NewInt(1), Difficulty: big.NewInt(2), Time: big.NewInt(3), Extra: []byte("test ommer header")}}}
if present := chainDB.HasBody(hash, number); present {
t.Fatalf("chainDB.HasBody(%q, %d) = true, want false", hash.String(), number)
}
if got := chainDB.ReadBody(hash, number); got != nil {
t.Fatalf("chainDB.ReadBody(%q, %d) = %+v, want <nil>", hash.String(), number, got)
}
wrapper.writer.WriteBody(hash, number, want)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if present := chainDB.HasBody(hash, number); !present {
t.Fatalf("chainDB.HasBody(%q, %d) = false, want true", hash.String(), number)
}
got := chainDB.ReadBody(hash, number)
if diff := cmp.Diff(want, got, cmpopts.IgnoreUnexported(big.Int{}, types.Transaction{})); diff != "" {
t.Fatalf("chainDB.ReadBody(%q, %d) bodies differ (-want +got):\n%s", hash.String(), number, diff)
}
wrapper.writer.DeleteBody(hash, number)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if present := chainDB.HasBody(hash, number); present {
t.Fatalf("chainDB.HasBody(%q, %d) = true, want false", hash, number)
}
if got := chainDB.ReadBody(hash, number); got != nil {
t.Fatalf("chainDB.ReadBody(%q, %d) = %+v, want <nil>", hash.String(), number, got)
}
})
}
}
func TestChainDB_ReadWriteDeleteBlock(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(2), Time: big.NewInt(3), Extra: []byte("test header")}
tx := types.NewTransaction(1, common.HexToAddress("0x2"), big.NewInt(333), 4444, big.NewInt(55555), []byte{66, 66, 66})
ommerHeader := &types.Header{Number: big.NewInt(4), Difficulty: big.NewInt(5), Time: big.NewInt(6), Extra: []byte("test ommer header")}
want := types.NewBlock(header, []*types.Transaction{tx}, []*types.Header{ommerHeader}, nil)
if got := chainDB.ReadBlock(want.Hash(), want.NumberU64()); got != nil {
t.Fatalf("chainDB.ReadBlock(%q, %d) = %+v, want <nil>", want.Hash().String(), want.NumberU64(), got)
}
wrapper.writer.WriteBlock(want)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
got := chainDB.ReadBlock(want.Hash(), want.NumberU64())
if diff := cmp.Diff(want, got, cmpopts.IgnoreUnexported(big.Int{}, types.Block{}, types.Transaction{})); diff != "" {
t.Fatalf("chainDB.ReadBlock(%q, %d) bodies differ (-want +got):\n%s", want.Hash().String(), want.NumberU64(), diff)
}
wrapper.writer.DeleteBlock(want.Hash(), want.NumberU64())
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadBlock(want.Hash(), want.NumberU64()); got != nil {
t.Fatalf("chainDB.ReadBlock(%q, %d) = %+v, want <nil>", want.Hash().String(), want.NumberU64(), got)
}
})
}
}
func TestChainDB_HasReadWriteDeleteReceipts(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
hash := common.HexToHash("0x1")
number := uint64(22)
want := types.Receipts{types.NewReceipt(nil, false, 333)}
if present := chainDB.HasReceipts(hash, number); present {
t.Fatalf("chainDB.HasReceipts(%q, %d) = true, want false", hash.String(), number)
}
if got := chainDB.ReadReceipts(hash, number); got != nil {
t.Fatalf("chainDB.ReadReceipts(%q, %d) = %+v, want <nil>", hash.String(), number, got)
}
wrapper.writer.WriteReceipts(hash, number, want)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if present := chainDB.HasReceipts(hash, number); !present {
t.Fatalf("chainDB.HasReceipts(%q, %d) = false, want true", hash.String(), number)
}
got := chainDB.ReadReceipts(hash, number)
if diff := cmp.Diff(want, got, cmpopts.EquateEmpty()); diff != "" {
t.Fatalf("chainDB.ReadReceipts(%q, %d) bodies differ (-want +got):\n%s", hash.String(), number, diff)
}
wrapper.writer.DeleteReceipts(hash, number)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if present := chainDB.HasReceipts(hash, number); present {
t.Fatalf("chainDB.HasReceipts(%q, %d) = true, want false", hash, number)
}
if got := chainDB.ReadReceipts(hash, number); got != nil {
t.Fatalf("chainDB.ReadReceipts(%q, %d) = %+v, want <nil>", hash.String(), number, got)
}
})
}
}
func cmpBigInt(a, b *big.Int) bool {
fmt.Println("a", a)
fmt.Println("b", b)
return a.Cmp(b) == 0
}
func TestChainDB_ReadWriteDeleteTxLookupEntries(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
header := &types.Header{}
tx := types.NewTransaction(1, common.HexToAddress("0x2"), big.NewInt(333), 4444, big.NewInt(55555), []byte{66, 66, 66})
txs := []*types.Transaction{tx}
block := types.NewBlock(header, txs, []*types.Header{}, nil)
if got := chainDB.ReadTxLookupEntry(tx.Hash()); got != (common.Hash{}) {
t.Fatalf("chainDB.ReadTxLookupEntry(%q) = %s, want %s", tx.Hash(), got.String(), (common.Hash{}).String())
}
if got, blockHash, blockNumber, txIndex := chainDB.ReadTransaction(tx.Hash()); got != nil {
t.Fatalf("chainDB.ReadTransaction(%q) = %+v, %s, %d, %d, want <nil>, %s, 0, 0", tx.Hash(), got, blockHash.String(), blockNumber, txIndex, tx.Hash().String())
}
// WriteBlock ensures the hash-to-number mapping and block bodies for transaction retrieval are present.
wrapper.writer.WriteBlock(block)
wrapper.writer.WriteTxLookupEntries(block)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadTxLookupEntry(tx.Hash()); got != block.Hash() {
t.Fatalf("chainDB.ReadTxLookupEntry(%q) = %s, want %s", tx.Hash(), got.String(), block.Hash().String())
}
got, blockHash, blockNumber, txIndex := chainDB.ReadTransaction(tx.Hash())
if diff := cmp.Diff(tx, got, cmpopts.IgnoreUnexported(types.Transaction{})); diff != "" {
t.Fatalf("chainDB.ReadTransaction(%q) differ (-want +got):\n%s", tx.Hash().String(), diff)
}
if blockHash != block.Hash() || blockNumber != block.NumberU64() || txIndex != 0 {
t.Fatalf("chainDB.ReadTransaction(%q) = ..., %s, %d, %d, want ..., %s, %d, %d", tx.Hash().String(), blockHash.String(), blockNumber, txIndex, block.Hash().String(), block.NumberU64(), 0)
}
wrapper.writer.DeleteTxLookupEntry(tx.Hash())
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadTxLookupEntry(tx.Hash()); got != (common.Hash{}) {
t.Fatalf("chainDB.ReadTxLookupEntry(%q) = %s, want %s", tx.Hash(), got.String(), (common.Hash{}).String())
}
if got, blockHash, blockNumber, txIndex := chainDB.ReadTransaction(tx.Hash()); got != nil {
t.Fatalf("chainDB.ReadTransaction(%q) = %+v, %s, %d, %d, want <nil>, %s, 0, 0", tx.Hash(), got, blockHash.String(), blockNumber, txIndex, tx.Hash().String())
}
})
}
}
func TestChainDB_ReadWriteDeleteTD(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
hash := common.HexToHash("0x1")
number := uint64(22)
want := big.NewInt(3333)
if got := chainDB.ReadTD(hash, number); got != nil {
t.Fatalf("chainDB.ReadTD(%q, %d) = %+v, want <nil>", hash.String(), number, got)
}
wrapper.writer.WriteTD(hash, number, want)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadTD(hash, number); got == nil || got.Cmp(want) != 0 {
t.Fatalf("chainDB.ReadTD(%q, %d) = %+v, want %+v", hash.String(), number, got, want)
}
wrapper.writer.DeleteTD(hash, number)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
if got := chainDB.ReadTD(hash, number); got != nil {
t.Fatalf("chainDB.ReadTD(%q, %d) = %+v, want <nil>", hash.String(), number, got)
}
})
}
}
func TestChainDB_ReadWritePreimages(t *testing.T) {
for _, tc := range tests {
tc := tc // Capture test case.
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
db := ethdb.NewMemDatabase()
chainDB := Wrap(db)
wrapper := tc.createWrapper(chainDB)
preimages := map[common.Hash][]byte{
common.HexToHash("0x1"): common.Hex2Bytes("0xa"),
common.HexToHash("0x2"): common.Hex2Bytes("0xb"),
common.HexToHash("0x3"): common.Hex2Bytes("0xc"),
}
for preimage := range preimages {
if got := chainDB.ReadPreimage(preimage); got != nil {
t.Fatalf("chainDB.ReadPreimage(%q) = %s, want <nil>", preimage, hex.EncodeToString(got))
}
}
wrapper.writer.WritePreimages(preimages)
if err := wrapper.done(); err != nil {
t.Fatalf("wrapper.done() = %v, want <nil>", err)
}
for preimage, want := range preimages {
if got := chainDB.ReadPreimage(preimage); !bytes.Equal(got, want) {
t.Fatalf("chainDB.ReadPreimage(%q) = %s, want %s", preimage, hex.EncodeToString(got), hex.EncodeToString(want))
}
}
})
}
}

View file

@ -28,9 +28,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/chaindb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/hashicorp/golang-lru"
@ -50,7 +49,7 @@ const (
type HeaderChain struct {
config *params.ChainConfig
chainDb ethdb.Database
chainDB *chaindb.ChainDB
genesisHeader *types.Header
currentHeader atomic.Value // Current head of the header chain (may be above the block chain!)
@ -70,7 +69,7 @@ type HeaderChain struct {
// getValidator should return the parent's validator
// procInterrupt points to the parent's interrupt semaphore
// wg points to the parent's shutdown wait group
func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine consensus.Engine, procInterrupt func() bool) (*HeaderChain, error) {
func NewHeaderChain(chainDB *chaindb.ChainDB, config *params.ChainConfig, engine consensus.Engine, procInterrupt func() bool) (*HeaderChain, error) {
headerCache, _ := lru.New(headerCacheLimit)
tdCache, _ := lru.New(tdCacheLimit)
numberCache, _ := lru.New(numberCacheLimit)
@ -83,7 +82,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
hc := &HeaderChain{
config: config,
chainDb: chainDb,
chainDB: chainDB,
headerCache: headerCache,
tdCache: tdCache,
numberCache: numberCache,
@ -98,7 +97,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
}
hc.currentHeader.Store(hc.genesisHeader)
if head := rawdb.ReadHeadBlockHash(chainDb); head != (common.Hash{}) {
if head := hc.chainDB.ReadHeadBlockHash(); head != (common.Hash{}) {
if chead := hc.GetHeaderByHash(head); chead != nil {
hc.currentHeader.Store(chead)
}
@ -115,7 +114,7 @@ func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
number := cached.(uint64)
return &number
}
number := rawdb.ReadHeaderNumber(hc.chainDb, hash)
number := hc.chainDB.ReadHeaderNumber(hash)
if number != nil {
hc.numberCache.Add(hash, *number)
}
@ -149,20 +148,20 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
if err := hc.WriteTd(hash, number, externTd); err != nil {
log.Crit("Failed to write header total difficulty", "err", err)
}
rawdb.WriteHeader(hc.chainDb, header)
hc.chainDB.WriteHeader(header)
// If the total difficulty is higher than our known, add it to the canonical chain
// Second clause in the if statement reduces the vulnerability to selfish mining.
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
if externTd.Cmp(localTd) > 0 || (externTd.Cmp(localTd) == 0 && mrand.Float64() < 0.5) {
// Delete any canonical number assignments above the new head
batch := hc.chainDb.NewBatch()
batch := hc.chainDB.NewBatch()
for i := number + 1; ; i++ {
hash := rawdb.ReadCanonicalHash(hc.chainDb, i)
hash := hc.chainDB.ReadCanonicalHash(i)
if hash == (common.Hash{}) {
break
}
rawdb.DeleteCanonicalHash(batch, i)
batch.DeleteCanonicalHash(i)
}
batch.Write()
@ -172,16 +171,16 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
headNumber = header.Number.Uint64() - 1
headHeader = hc.GetHeader(headHash, headNumber)
)
for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash {
rawdb.WriteCanonicalHash(hc.chainDb, headHash, headNumber)
for hc.chainDB.ReadCanonicalHash(headNumber) != headHash {
hc.chainDB.WriteCanonicalHash(headNumber, headHash)
headHash = headHeader.ParentHash
headNumber = headHeader.Number.Uint64() - 1
headHeader = hc.GetHeader(headHash, headNumber)
}
// Extend the canonical chain with the new header
rawdb.WriteCanonicalHash(hc.chainDb, hash, number)
rawdb.WriteHeadHeaderHash(hc.chainDb, hash)
hc.chainDB.WriteCanonicalHash(number, hash)
hc.chainDB.WriteHeadHeaderHash(hash)
hc.currentHeaderHash = hash
hc.currentHeader.Store(types.CopyHeader(header))
@ -342,9 +341,9 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma
}
}
for ancestor != 0 {
if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash {
if hc.chainDB.ReadCanonicalHash(number) == hash {
number -= ancestor
return rawdb.ReadCanonicalHash(hc.chainDb, number), number
return hc.chainDB.ReadCanonicalHash(number), number
}
if *maxNonCanonical == 0 {
return common.Hash{}, 0
@ -368,7 +367,7 @@ func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
if cached, ok := hc.tdCache.Get(hash); ok {
return cached.(*big.Int)
}
td := rawdb.ReadTd(hc.chainDb, hash, number)
td := hc.chainDB.ReadTD(hash, number)
if td == nil {
return nil
}
@ -390,7 +389,7 @@ func (hc *HeaderChain) GetTdByHash(hash common.Hash) *big.Int {
// WriteTd stores a block's total difficulty into the database, also caching it
// along the way.
func (hc *HeaderChain) WriteTd(hash common.Hash, number uint64, td *big.Int) error {
rawdb.WriteTd(hc.chainDb, hash, number, td)
hc.chainDB.WriteTD(hash, number, td)
hc.tdCache.Add(hash, new(big.Int).Set(td))
return nil
}
@ -402,7 +401,7 @@ func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header
if header, ok := hc.headerCache.Get(hash); ok {
return header.(*types.Header)
}
header := rawdb.ReadHeader(hc.chainDb, hash, number)
header := hc.chainDB.ReadHeader(hash, number)
if header == nil {
return nil
}
@ -426,13 +425,13 @@ func (hc *HeaderChain) HasHeader(hash common.Hash, number uint64) bool {
if hc.numberCache.Contains(hash) || hc.headerCache.Contains(hash) {
return true
}
return rawdb.HasHeader(hc.chainDb, hash, number)
return hc.chainDB.HasHeader(hash, number)
}
// GetHeaderByNumber retrieves a block header from the database by number,
// caching it (associated with its hash) if found.
func (hc *HeaderChain) GetHeaderByNumber(number uint64) *types.Header {
hash := rawdb.ReadCanonicalHash(hc.chainDb, number)
hash := hc.chainDB.ReadCanonicalHash(number)
if hash == (common.Hash{}) {
return nil
}
@ -447,7 +446,7 @@ func (hc *HeaderChain) CurrentHeader() *types.Header {
// SetCurrentHeader sets the current head header of the canonical chain.
func (hc *HeaderChain) SetCurrentHeader(head *types.Header) {
rawdb.WriteHeadHeaderHash(hc.chainDb, head.Hash())
hc.chainDB.WriteHeadHeaderHash(head.Hash())
hc.currentHeader.Store(head)
hc.currentHeaderHash = head.Hash()
@ -455,7 +454,7 @@ func (hc *HeaderChain) SetCurrentHeader(head *types.Header) {
// DeleteCallback is a callback function that is called by SetHead before
// each header is deleted.
type DeleteCallback func(rawdb.DatabaseDeleter, common.Hash, uint64)
type DeleteCallback func(*chaindb.Batch, common.Hash, uint64)
// SetHead rewinds the local chain to a new head. Everything above the new head
// will be deleted and the new one set.
@ -465,21 +464,21 @@ func (hc *HeaderChain) SetHead(head uint64, delFn DeleteCallback) {
if hdr := hc.CurrentHeader(); hdr != nil {
height = hdr.Number.Uint64()
}
batch := hc.chainDb.NewBatch()
batch := hc.chainDB.NewBatch()
for hdr := hc.CurrentHeader(); hdr != nil && hdr.Number.Uint64() > head; hdr = hc.CurrentHeader() {
hash := hdr.Hash()
num := hdr.Number.Uint64()
if delFn != nil {
delFn(batch, hash, num)
}
rawdb.DeleteHeader(batch, hash, num)
rawdb.DeleteTd(batch, hash, num)
batch.DeleteHeader(hash, num)
batch.DeleteTD(hash, num)
hc.currentHeader.Store(hc.GetHeader(hdr.ParentHash, hdr.Number.Uint64()-1))
}
// Roll back the canonical chain numbering
for i := height; i > head; i-- {
rawdb.DeleteCanonicalHash(batch, i)
batch.DeleteCanonicalHash(i)
}
batch.Write()
@ -493,7 +492,7 @@ func (hc *HeaderChain) SetHead(head uint64, delFn DeleteCallback) {
}
hc.currentHeaderHash = hc.CurrentHeader().Hash()
rawdb.WriteHeadHeaderHash(hc.chainDb, hc.currentHeaderHash)
hc.chainDB.WriteHeadHeaderHash(hc.currentHeaderHash)
}
// SetGenesis sets a new genesis block header for the chain

View file

@ -60,6 +60,14 @@ func ReadHeaderNumber(db DatabaseReader, hash common.Hash) *uint64 {
return &number
}
// WriteHeaderNumber stores the header number assigned to a hash.
func WriteHeaderNumber(db DatabaseWriter, hash common.Hash, number uint64) {
key := headerNumberKey(hash)
if err := db.Put(key, encodeBlockNumber(number)); err != nil {
log.Crit("Failed to store hash to number mapping", "err", err)
}
}
// ReadHeadHeaderHash retrieves the hash of the current canonical head header.
func ReadHeadHeaderHash(db DatabaseReader) common.Hash {
data, _ := db.Get(headHeaderKey)
@ -161,18 +169,14 @@ func WriteHeader(db DatabaseWriter, header *types.Header) {
var (
hash = header.Hash()
number = header.Number.Uint64()
encoded = encodeBlockNumber(number)
)
key := headerNumberKey(hash)
if err := db.Put(key, encoded); err != nil {
log.Crit("Failed to store hash to number mapping", "err", err)
}
WriteHeaderNumber(db, hash, number)
// Write the encoded header
data, err := rlp.EncodeToBytes(header)
if err != nil {
log.Crit("Failed to RLP encode header", "err", err)
}
key = headerKey(number, hash)
key := headerKey(number, hash)
if err := db.Put(key, data); err != nil {
log.Crit("Failed to store header", "err", err)
}

View file

@ -64,20 +64,24 @@ func ReadTransaction(db DatabaseReader, hash common.Hash) (*types.Transaction, c
if blockHash == (common.Hash{}) {
return nil, common.Hash{}, 0, 0
}
blockNumber := ReadHeaderNumber(db, blockHash)
if blockNumber == nil {
return nil, common.Hash{}, 0, 0
}
body := ReadBody(db, blockHash, *blockNumber)
if body == nil {
log.Error("Transaction referenced missing", "number", blockNumber, "hash", blockHash)
return nil, common.Hash{}, 0, 0
}
for txIndex, tx := range body.Transactions {
if tx.Hash() == hash {
return tx, blockHash, *blockNumber, uint64(txIndex)
}
}
log.Error("Transaction not found", "number", blockNumber, "hash", blockHash, "txhash", hash)
return nil, common.Hash{}, 0, 0
}

View file

@ -29,7 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/chaindb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
@ -51,7 +51,8 @@ var (
type LightChain struct {
hc *core.HeaderChain
indexerConfig *IndexerConfig
chainDb ethdb.Database
db ethdb.Database
chainDB *chaindb.ChainDB
engine consensus.Engine
odr OdrBackend
chainFeed event.Feed
@ -82,8 +83,11 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
bodyRLPCache, _ := lru.New(bodyCacheLimit)
blockCache, _ := lru.New(blockCacheLimit)
db := odr.Database()
chainDB := chaindb.Wrap(db)
bc := &LightChain{
chainDb: odr.Database(),
db: db,
chainDB: chainDB,
indexerConfig: odr.IndexerConfig(),
odr: odr,
quit: make(chan struct{}),
@ -93,7 +97,7 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
engine: engine,
}
var err error
bc.hc, err = core.NewHeaderChain(odr.Database(), config, bc.engine, bc.getProcInterrupt)
bc.hc, err = core.NewHeaderChain(chainDB, config, bc.engine, bc.getProcInterrupt)
if err != nil {
return nil, err
}
@ -121,11 +125,11 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
// addTrustedCheckpoint adds a trusted checkpoint to the blockchain
func (lc *LightChain) addTrustedCheckpoint(cp *params.TrustedCheckpoint) {
if lc.odr.ChtIndexer() != nil {
StoreChtRoot(lc.chainDb, cp.SectionIndex, cp.SectionHead, cp.CHTRoot)
StoreChtRoot(lc.db, cp.SectionIndex, cp.SectionHead, cp.CHTRoot)
lc.odr.ChtIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead)
}
if lc.odr.BloomTrieIndexer() != nil {
StoreBloomTrieRoot(lc.chainDb, cp.SectionIndex, cp.SectionHead, cp.BloomRoot)
StoreBloomTrieRoot(lc.db, cp.SectionIndex, cp.SectionHead, cp.BloomRoot)
lc.odr.BloomTrieIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead)
}
if lc.odr.BloomIndexer() != nil {
@ -146,7 +150,7 @@ func (lc *LightChain) Odr() OdrBackend {
// loadLastState loads the last known chain state from the database. This method
// assumes that the chain manager mutex is held.
func (lc *LightChain) loadLastState() error {
if head := rawdb.ReadHeadHeaderHash(lc.chainDb); head == (common.Hash{}) {
if head := lc.chainDB.ReadHeadHeaderHash(); head == (common.Hash{}) {
// Corrupt or empty database, init from scratch
lc.Reset()
} else {
@ -193,8 +197,8 @@ func (lc *LightChain) ResetWithGenesisBlock(genesis *types.Block) {
defer lc.chainmu.Unlock()
// Prepare the genesis block and reinitialise the chain
rawdb.WriteTd(lc.chainDb, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty())
rawdb.WriteBlock(lc.chainDb, genesis)
lc.chainDB.WriteTD(genesis.Hash(), genesis.NumberU64(), genesis.Difficulty())
lc.chainDB.WriteBlock(genesis)
lc.genesisBlock = genesis
lc.hc.SetGenesis(lc.genesisBlock.Header())

View file

@ -123,8 +123,8 @@ func testHeaderChainImport(chain []*types.Header, lightchain *LightChain) error
}
// Manually insert the header into the database, but don't reorganize (allows subsequent testing)
lightchain.chainmu.Lock()
rawdb.WriteTd(lightchain.chainDb, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, lightchain.GetTdByHash(header.ParentHash)))
rawdb.WriteHeader(lightchain.chainDb, header)
rawdb.WriteTd(lightchain.db, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, lightchain.GetTdByHash(header.ParentHash)))
rawdb.WriteHeader(lightchain.db, header)
lightchain.chainmu.Unlock()
}
return nil
@ -344,7 +344,7 @@ func TestReorgBadHeaderHashes(t *testing.T) {
defer func() { delete(core.BadHashes, headers[3].Hash()) }()
// Create a new LightChain and check that it rolled back the state.
ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker())
ncm, err := NewLightChain(&dummyOdr{db: bc.db}, params.TestChainConfig, ethash.NewFaker())
if err != nil {
t.Fatalf("failed to create new chain manager: %v", err)
}

27
vendor/github.com/google/go-cmp/LICENSE generated vendored Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2017 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

89
vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go generated vendored Normal file
View file

@ -0,0 +1,89 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// Package cmpopts provides common options for the cmp package.
package cmpopts
import (
"math"
"reflect"
"github.com/google/go-cmp/cmp"
)
func equateAlways(_, _ interface{}) bool { return true }
// EquateEmpty returns a Comparer option that determines all maps and slices
// with a length of zero to be equal, regardless of whether they are nil.
//
// EquateEmpty can be used in conjunction with SortSlices and SortMaps.
func EquateEmpty() cmp.Option {
return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways))
}
func isEmpty(x, y interface{}) bool {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
return (x != nil && y != nil && vx.Type() == vy.Type()) &&
(vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
(vx.Len() == 0 && vy.Len() == 0)
}
// EquateApprox returns a Comparer option that determines float32 or float64
// values to be equal if they are within a relative fraction or absolute margin.
// This option is not used when either x or y is NaN or infinite.
//
// The fraction determines that the difference of two values must be within the
// smaller fraction of the two values, while the margin determines that the two
// values must be within some absolute margin.
// To express only a fraction or only a margin, use 0 for the other parameter.
// The fraction and margin must be non-negative.
//
// The mathematical expression used is equivalent to:
// |x-y| ≤ max(fraction*min(|x|, |y|), margin)
//
// EquateApprox can be used in conjunction with EquateNaNs.
func EquateApprox(fraction, margin float64) cmp.Option {
if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) {
panic("margin or fraction must be a non-negative number")
}
a := approximator{fraction, margin}
return cmp.Options{
cmp.FilterValues(areRealF64s, cmp.Comparer(a.compareF64)),
cmp.FilterValues(areRealF32s, cmp.Comparer(a.compareF32)),
}
}
type approximator struct{ frac, marg float64 }
func areRealF64s(x, y float64) bool {
return !math.IsNaN(x) && !math.IsNaN(y) && !math.IsInf(x, 0) && !math.IsInf(y, 0)
}
func areRealF32s(x, y float32) bool {
return areRealF64s(float64(x), float64(y))
}
func (a approximator) compareF64(x, y float64) bool {
relMarg := a.frac * math.Min(math.Abs(x), math.Abs(y))
return math.Abs(x-y) <= math.Max(a.marg, relMarg)
}
func (a approximator) compareF32(x, y float32) bool {
return a.compareF64(float64(x), float64(y))
}
// EquateNaNs returns a Comparer option that determines float32 and float64
// NaN values to be equal.
//
// EquateNaNs can be used in conjunction with EquateApprox.
func EquateNaNs() cmp.Option {
return cmp.Options{
cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)),
cmp.FilterValues(areNaNsF32s, cmp.Comparer(equateAlways)),
}
}
func areNaNsF64s(x, y float64) bool {
return math.IsNaN(x) && math.IsNaN(y)
}
func areNaNsF32s(x, y float32) bool {
return areNaNsF64s(float64(x), float64(y))
}

149
vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go generated vendored Normal file
View file

@ -0,0 +1,149 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmpopts
import (
"fmt"
"reflect"
"unicode"
"unicode/utf8"
"github.com/google/go-cmp/cmp"
)
// IgnoreFields returns an Option that ignores exported fields of the
// given names on a single struct type.
// The struct type is specified by passing in a value of that type.
//
// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
// specific sub-field that is embedded or nested within the parent struct.
//
// This does not handle unexported fields; use IgnoreUnexported instead.
func IgnoreFields(typ interface{}, names ...string) cmp.Option {
sf := newStructFilter(typ, names...)
return cmp.FilterPath(sf.filter, cmp.Ignore())
}
// IgnoreTypes returns an Option that ignores all values assignable to
// certain types, which are specified by passing in a value of each type.
func IgnoreTypes(typs ...interface{}) cmp.Option {
tf := newTypeFilter(typs...)
return cmp.FilterPath(tf.filter, cmp.Ignore())
}
type typeFilter []reflect.Type
func newTypeFilter(typs ...interface{}) (tf typeFilter) {
for _, typ := range typs {
t := reflect.TypeOf(typ)
if t == nil {
// This occurs if someone tries to pass in sync.Locker(nil)
panic("cannot determine type; consider using IgnoreInterfaces")
}
tf = append(tf, t)
}
return tf
}
func (tf typeFilter) filter(p cmp.Path) bool {
if len(p) < 1 {
return false
}
t := p.Last().Type()
for _, ti := range tf {
if t.AssignableTo(ti) {
return true
}
}
return false
}
// IgnoreInterfaces returns an Option that ignores all values or references of
// values assignable to certain interface types. These interfaces are specified
// by passing in an anonymous struct with the interface types embedded in it.
// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}.
func IgnoreInterfaces(ifaces interface{}) cmp.Option {
tf := newIfaceFilter(ifaces)
return cmp.FilterPath(tf.filter, cmp.Ignore())
}
type ifaceFilter []reflect.Type
func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) {
t := reflect.TypeOf(ifaces)
if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct {
panic("input must be an anonymous struct")
}
for i := 0; i < t.NumField(); i++ {
fi := t.Field(i)
switch {
case !fi.Anonymous:
panic("struct cannot have named fields")
case fi.Type.Kind() != reflect.Interface:
panic("embedded field must be an interface type")
case fi.Type.NumMethod() == 0:
// This matches everything; why would you ever want this?
panic("cannot ignore empty interface")
default:
tf = append(tf, fi.Type)
}
}
return tf
}
func (tf ifaceFilter) filter(p cmp.Path) bool {
if len(p) < 1 {
return false
}
t := p.Last().Type()
for _, ti := range tf {
if t.AssignableTo(ti) {
return true
}
if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) {
return true
}
}
return false
}
// IgnoreUnexported returns an Option that only ignores the immediate unexported
// fields of a struct, including anonymous fields of unexported types.
// In particular, unexported fields within the struct's exported fields
// of struct types, including anonymous fields, will not be ignored unless the
// type of the field itself is also passed to IgnoreUnexported.
//
// Avoid ignoring unexported fields of a type which you do not control (i.e. a
// type from another repository), as changes to the implementation of such types
// may change how the comparison behaves. Prefer a custom Comparer instead.
func IgnoreUnexported(typs ...interface{}) cmp.Option {
ux := newUnexportedFilter(typs...)
return cmp.FilterPath(ux.filter, cmp.Ignore())
}
type unexportedFilter struct{ m map[reflect.Type]bool }
func newUnexportedFilter(typs ...interface{}) unexportedFilter {
ux := unexportedFilter{m: make(map[reflect.Type]bool)}
for _, typ := range typs {
t := reflect.TypeOf(typ)
if t == nil || t.Kind() != reflect.Struct {
panic(fmt.Sprintf("invalid struct type: %T", typ))
}
ux.m[t] = true
}
return ux
}
func (xf unexportedFilter) filter(p cmp.Path) bool {
sf, ok := p.Index(-1).(cmp.StructField)
if !ok {
return false
}
return xf.m[p.Index(-2).Type()] && !isExported(sf.Name())
}
// isExported reports whether the identifier is exported.
func isExported(id string) bool {
r, _ := utf8.DecodeRuneInString(id)
return unicode.IsUpper(r)
}

147
vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go generated vendored Normal file
View file

@ -0,0 +1,147 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmpopts
import (
"fmt"
"reflect"
"sort"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/internal/function"
)
// SortSlices returns a Transformer option that sorts all []V.
// The less function must be of the form "func(T, T) bool" which is used to
// sort any slice with element type V that is assignable to T.
//
// The less function must be:
// • Deterministic: less(x, y) == less(x, y)
// • Irreflexive: !less(x, x)
// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
//
// The less function does not have to be "total". That is, if !less(x, y) and
// !less(y, x) for two elements x and y, their relative order is maintained.
//
// SortSlices can be used in conjunction with EquateEmpty.
func SortSlices(less interface{}) cmp.Option {
vf := reflect.ValueOf(less)
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
panic(fmt.Sprintf("invalid less function: %T", less))
}
ss := sliceSorter{vf.Type().In(0), vf}
return cmp.FilterValues(ss.filter, cmp.Transformer("cmpopts.SortSlices", ss.sort))
}
type sliceSorter struct {
in reflect.Type // T
fnc reflect.Value // func(T, T) bool
}
func (ss sliceSorter) filter(x, y interface{}) bool {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
if !(x != nil && y != nil && vx.Type() == vy.Type()) ||
!(vx.Kind() == reflect.Slice && vx.Type().Elem().AssignableTo(ss.in)) ||
(vx.Len() <= 1 && vy.Len() <= 1) {
return false
}
// Check whether the slices are already sorted to avoid an infinite
// recursion cycle applying the same transform to itself.
ok1 := sort.SliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) })
ok2 := sort.SliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) })
return !ok1 || !ok2
}
func (ss sliceSorter) sort(x interface{}) interface{} {
src := reflect.ValueOf(x)
dst := reflect.MakeSlice(src.Type(), src.Len(), src.Len())
for i := 0; i < src.Len(); i++ {
dst.Index(i).Set(src.Index(i))
}
sort.SliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) })
ss.checkSort(dst)
return dst.Interface()
}
func (ss sliceSorter) checkSort(v reflect.Value) {
start := -1 // Start of a sequence of equal elements.
for i := 1; i < v.Len(); i++ {
if ss.less(v, i-1, i) {
// Check that first and last elements in v[start:i] are equal.
if start >= 0 && (ss.less(v, start, i-1) || ss.less(v, i-1, start)) {
panic(fmt.Sprintf("incomparable values detected: want equal elements: %v", v.Slice(start, i)))
}
start = -1
} else if start == -1 {
start = i
}
}
}
func (ss sliceSorter) less(v reflect.Value, i, j int) bool {
vx, vy := v.Index(i), v.Index(j)
return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
}
// SortMaps returns a Transformer option that flattens map[K]V types to be a
// sorted []struct{K, V}. The less function must be of the form
// "func(T, T) bool" which is used to sort any map with key K that is
// assignable to T.
//
// Flattening the map into a slice has the property that cmp.Equal is able to
// use Comparers on K or the K.Equal method if it exists.
//
// The less function must be:
// • Deterministic: less(x, y) == less(x, y)
// • Irreflexive: !less(x, x)
// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
// • Total: if x != y, then either less(x, y) or less(y, x)
//
// SortMaps can be used in conjunction with EquateEmpty.
func SortMaps(less interface{}) cmp.Option {
vf := reflect.ValueOf(less)
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
panic(fmt.Sprintf("invalid less function: %T", less))
}
ms := mapSorter{vf.Type().In(0), vf}
return cmp.FilterValues(ms.filter, cmp.Transformer("cmpopts.SortMaps", ms.sort))
}
type mapSorter struct {
in reflect.Type // T
fnc reflect.Value // func(T, T) bool
}
func (ms mapSorter) filter(x, y interface{}) bool {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
return (x != nil && y != nil && vx.Type() == vy.Type()) &&
(vx.Kind() == reflect.Map && vx.Type().Key().AssignableTo(ms.in)) &&
(vx.Len() != 0 || vy.Len() != 0)
}
func (ms mapSorter) sort(x interface{}) interface{} {
src := reflect.ValueOf(x)
outType := reflect.StructOf([]reflect.StructField{
{Name: "K", Type: src.Type().Key()},
{Name: "V", Type: src.Type().Elem()},
})
dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len())
for i, k := range src.MapKeys() {
v := reflect.New(outType).Elem()
v.Field(0).Set(k)
v.Field(1).Set(src.MapIndex(k))
dst.Index(i).Set(v)
}
sort.Slice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) })
ms.checkSort(dst)
return dst.Interface()
}
func (ms mapSorter) checkSort(v reflect.Value) {
for i := 1; i < v.Len(); i++ {
if !ms.less(v, i-1, i) {
panic(fmt.Sprintf("partial order detected: want %v < %v", v.Index(i-1), v.Index(i)))
}
}
}
func (ms mapSorter) less(v reflect.Value, i, j int) bool {
vx, vy := v.Index(i).Field(0), v.Index(j).Field(0)
return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
}

View file

@ -0,0 +1,182 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmpopts
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp"
)
// filterField returns a new Option where opt is only evaluated on paths that
// include a specific exported field on a single struct type.
// The struct type is specified by passing in a value of that type.
//
// The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
// specific sub-field that is embedded or nested within the parent struct.
func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option {
// TODO: This is currently unexported over concerns of how helper filters
// can be composed together easily.
// TODO: Add tests for FilterField.
sf := newStructFilter(typ, name)
return cmp.FilterPath(sf.filter, opt)
}
type structFilter struct {
t reflect.Type // The root struct type to match on
ft fieldTree // Tree of fields to match on
}
func newStructFilter(typ interface{}, names ...string) structFilter {
// TODO: Perhaps allow * as a special identifier to allow ignoring any
// number of path steps until the next field match?
// This could be useful when a concrete struct gets transformed into
// an anonymous struct where it is not possible to specify that by type,
// but the transformer happens to provide guarantees about the names of
// the transformed fields.
t := reflect.TypeOf(typ)
if t == nil || t.Kind() != reflect.Struct {
panic(fmt.Sprintf("%T must be a struct", typ))
}
var ft fieldTree
for _, name := range names {
cname, err := canonicalName(t, name)
if err != nil {
panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err))
}
ft.insert(cname)
}
return structFilter{t, ft}
}
func (sf structFilter) filter(p cmp.Path) bool {
for i, ps := range p {
if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) {
return true
}
}
return false
}
// fieldTree represents a set of dot-separated identifiers.
//
// For example, inserting the following selectors:
// Foo
// Foo.Bar.Baz
// Foo.Buzz
// Nuka.Cola.Quantum
//
// Results in a tree of the form:
// {sub: {
// "Foo": {ok: true, sub: {
// "Bar": {sub: {
// "Baz": {ok: true},
// }},
// "Buzz": {ok: true},
// }},
// "Nuka": {sub: {
// "Cola": {sub: {
// "Quantum": {ok: true},
// }},
// }},
// }}
type fieldTree struct {
ok bool // Whether this is a specified node
sub map[string]fieldTree // The sub-tree of fields under this node
}
// insert inserts a sequence of field accesses into the tree.
func (ft *fieldTree) insert(cname []string) {
if ft.sub == nil {
ft.sub = make(map[string]fieldTree)
}
if len(cname) == 0 {
ft.ok = true
return
}
sub := ft.sub[cname[0]]
sub.insert(cname[1:])
ft.sub[cname[0]] = sub
}
// matchPrefix reports whether any selector in the fieldTree matches
// the start of path p.
func (ft fieldTree) matchPrefix(p cmp.Path) bool {
for _, ps := range p {
switch ps := ps.(type) {
case cmp.StructField:
ft = ft.sub[ps.Name()]
if ft.ok {
return true
}
if len(ft.sub) == 0 {
return false
}
case cmp.Indirect:
default:
return false
}
}
return false
}
// canonicalName returns a list of identifiers where any struct field access
// through an embedded field is expanded to include the names of the embedded
// types themselves.
//
// For example, suppose field "Foo" is not directly in the parent struct,
// but actually from an embedded struct of type "Bar". Then, the canonical name
// of "Foo" is actually "Bar.Foo".
//
// Suppose field "Foo" is not directly in the parent struct, but actually
// a field in two different embedded structs of types "Bar" and "Baz".
// Then the selector "Foo" causes a panic since it is ambiguous which one it
// refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
func canonicalName(t reflect.Type, sel string) ([]string, error) {
var name string
sel = strings.TrimPrefix(sel, ".")
if sel == "" {
return nil, fmt.Errorf("name must not be empty")
}
if i := strings.IndexByte(sel, '.'); i < 0 {
name, sel = sel, ""
} else {
name, sel = sel[:i], sel[i:]
}
// Type must be a struct or pointer to struct.
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("%v must be a struct", t)
}
// Find the canonical name for this current field name.
// If the field exists in an embedded struct, then it will be expanded.
if !isExported(name) {
// Disallow unexported fields:
// * To discourage people from actually touching unexported fields
// * FieldByName is buggy (https://golang.org/issue/4876)
return []string{name}, fmt.Errorf("name must be exported")
}
sf, ok := t.FieldByName(name)
if !ok {
return []string{name}, fmt.Errorf("does not exist")
}
var ss []string
for i := range sf.Index {
ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name)
}
if sel == "" {
return ss, nil
}
ssPost, err := canonicalName(sf.Type, sel)
return append(ss, ssPost...), err
}

35
vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go generated vendored Normal file
View file

@ -0,0 +1,35 @@
// Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmpopts
import (
"github.com/google/go-cmp/cmp"
)
type xformFilter struct{ xform cmp.Option }
func (xf xformFilter) filter(p cmp.Path) bool {
for _, ps := range p {
if t, ok := ps.(cmp.Transform); ok && t.Option() == xf.xform {
return false
}
}
return true
}
// AcyclicTransformer returns a Transformer with a filter applied that ensures
// that the transformer cannot be recursively applied upon its own output.
//
// An example use case is a transformer that splits a string by lines:
// AcyclicTransformer("SplitLines", func(s string) []string{
// return strings.Split(s, "\n")
// })
//
// Had this been an unfiltered Transformer instead, this would result in an
// infinite cycle converting a string to []string to [][]string and so on.
func AcyclicTransformer(name string, f interface{}) cmp.Option {
xf := xformFilter{cmp.Transformer(name, f)}
return cmp.FilterPath(xf.filter, xf.xform)
}

612
vendor/github.com/google/go-cmp/cmp/compare.go generated vendored Normal file
View file

@ -0,0 +1,612 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// Package cmp determines equality of values.
//
// This package is intended to be a more powerful and safer alternative to
// reflect.DeepEqual for comparing whether two values are semantically equal.
//
// The primary features of cmp are:
//
// • When the default behavior of equality does not suit the needs of the test,
// custom equality functions can override the equality operation.
// For example, an equality function may report floats as equal so long as they
// are within some tolerance of each other.
//
// • Types that have an Equal method may use that method to determine equality.
// This allows package authors to determine the equality operation for the types
// that they define.
//
// • If no custom equality functions are used and no Equal method is defined,
// equality is determined by recursively comparing the primitive kinds on both
// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
// fields are not compared by default; they result in panics unless suppressed
// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
// using the AllowUnexported option.
package cmp
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp/internal/diff"
"github.com/google/go-cmp/cmp/internal/function"
"github.com/google/go-cmp/cmp/internal/value"
)
// Equal reports whether x and y are equal by recursively applying the
// following rules in the given order to x and y and all of their sub-values:
//
// • Let S be the set of all Ignore, Transformer, and Comparer options that
// remain after applying all path filters, value filters, and type filters.
// If at least one Ignore exists in S, then the comparison is ignored.
// If the number of Transformer and Comparer options in S is greater than one,
// then Equal panics because it is ambiguous which option to use.
// If S contains a single Transformer, then use that to transform the current
// values and recursively call Equal on the output values.
// If S contains a single Comparer, then use that to compare the current values.
// Otherwise, evaluation proceeds to the next rule.
//
// • If the values have an Equal method of the form "(T) Equal(T) bool" or
// "(T) Equal(I) bool" where T is assignable to I, then use the result of
// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
// evaluation proceeds to the next rule.
//
// • Lastly, try to compare x and y based on their basic kinds.
// Simple kinds like booleans, integers, floats, complex numbers, strings, and
// channels are compared using the equivalent of the == operator in Go.
// Functions are only equal if they are both nil, otherwise they are unequal.
//
// Structs are equal if recursively calling Equal on all fields report equal.
// If a struct contains unexported fields, Equal panics unless an Ignore option
// (e.g., cmpopts.IgnoreUnexported) ignores that field or the AllowUnexported
// option explicitly permits comparing the unexported field.
//
// Slices are equal if they are both nil or both non-nil, where recursively
// calling Equal on all non-ignored slice or array elements report equal.
// Empty non-nil slices and nil slices are not equal; to equate empty slices,
// consider using cmpopts.EquateEmpty.
//
// Maps are equal if they are both nil or both non-nil, where recursively
// calling Equal on all non-ignored map entries report equal.
// Map keys are equal according to the == operator.
// To use custom comparisons for map keys, consider using cmpopts.SortMaps.
// Empty non-nil maps and nil maps are not equal; to equate empty maps,
// consider using cmpopts.EquateEmpty.
//
// Pointers and interfaces are equal if they are both nil or both non-nil,
// where they have the same underlying concrete type and recursively
// calling Equal on the underlying values reports equal.
func Equal(x, y interface{}, opts ...Option) bool {
vx := reflect.ValueOf(x)
vy := reflect.ValueOf(y)
// If the inputs are different types, auto-wrap them in an empty interface
// so that they have the same parent type.
var t reflect.Type
if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() {
t = reflect.TypeOf((*interface{})(nil)).Elem()
if vx.IsValid() {
vvx := reflect.New(t).Elem()
vvx.Set(vx)
vx = vvx
}
if vy.IsValid() {
vvy := reflect.New(t).Elem()
vvy.Set(vy)
vy = vvy
}
} else {
t = vx.Type()
}
s := newState(opts)
s.compareAny(&pathStep{t, vx, vy})
return s.result.Equal()
}
// Diff returns a human-readable report of the differences between two values.
// It returns an empty string if and only if Equal returns true for the same
// input values and options. The output string will use the "-" symbol to
// indicate elements removed from x, and the "+" symbol to indicate elements
// added to y.
//
// Do not depend on this output being stable.
func Diff(x, y interface{}, opts ...Option) string {
r := new(defaultReporter)
opts = Options{Options(opts), reporter(r)}
eq := Equal(x, y, opts...)
d := r.String()
if (d == "") != eq {
panic("inconsistent difference and equality results")
}
return d
}
type state struct {
// These fields represent the "comparison state".
// Calling statelessCompare must not result in observable changes to these.
result diff.Result // The current result of comparison
curPath Path // The current path in the value tree
reporters []reporterOption // Optional reporters
// recChecker checks for infinite cycles applying the same set of
// transformers upon the output of itself.
recChecker recChecker
// dynChecker triggers pseudo-random checks for option correctness.
// It is safe for statelessCompare to mutate this value.
dynChecker dynChecker
// These fields, once set by processOption, will not change.
exporters map[reflect.Type]bool // Set of structs with unexported field visibility
opts Options // List of all fundamental and filter options
}
func newState(opts []Option) *state {
// Always ensure a validator option exists to validate the inputs.
s := &state{opts: Options{validator{}}}
for _, opt := range opts {
s.processOption(opt)
}
return s
}
func (s *state) processOption(opt Option) {
switch opt := opt.(type) {
case nil:
case Options:
for _, o := range opt {
s.processOption(o)
}
case coreOption:
type filtered interface {
isFiltered() bool
}
if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() {
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
}
s.opts = append(s.opts, opt)
case visibleStructs:
if s.exporters == nil {
s.exporters = make(map[reflect.Type]bool)
}
for t := range opt {
s.exporters[t] = true
}
case reporterOption:
s.reporters = append(s.reporters, opt)
default:
panic(fmt.Sprintf("unknown option %T", opt))
}
}
// statelessCompare compares two values and returns the result.
// This function is stateless in that it does not alter the current result,
// or output to any registered reporters.
func (s *state) statelessCompare(step PathStep) diff.Result {
// We do not save and restore the curPath because all of the compareX
// methods should properly push and pop from the path.
// It is an implementation bug if the contents of curPath differs from
// when calling this function to when returning from it.
oldResult, oldReporters := s.result, s.reporters
s.result = diff.Result{} // Reset result
s.reporters = nil // Remove reporters to avoid spurious printouts
s.compareAny(step)
res := s.result
s.result, s.reporters = oldResult, oldReporters
return res
}
func (s *state) compareAny(step PathStep) {
// TODO: Support cyclic data structures.
// Update the path stack.
s.curPath.push(step)
defer s.curPath.pop()
for _, r := range s.reporters {
r.PushStep(step)
defer r.PopStep()
}
s.recChecker.Check(s.curPath)
// Obtain the current type and values.
t := step.Type()
vx, vy := step.Values()
// Rule 1: Check whether an option applies on this node in the value tree.
if s.tryOptions(t, vx, vy) {
return
}
// Rule 2: Check whether the type has a valid Equal method.
if s.tryMethod(t, vx, vy) {
return
}
// Rule 3: Recursively descend into each value's underlying kind.
switch t.Kind() {
case reflect.Bool:
s.report(vx.Bool() == vy.Bool(), 0)
return
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s.report(vx.Int() == vy.Int(), 0)
return
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
s.report(vx.Uint() == vy.Uint(), 0)
return
case reflect.Float32, reflect.Float64:
s.report(vx.Float() == vy.Float(), 0)
return
case reflect.Complex64, reflect.Complex128:
s.report(vx.Complex() == vy.Complex(), 0)
return
case reflect.String:
s.report(vx.String() == vy.String(), 0)
return
case reflect.Chan, reflect.UnsafePointer:
s.report(vx.Pointer() == vy.Pointer(), 0)
return
case reflect.Func:
s.report(vx.IsNil() && vy.IsNil(), 0)
return
case reflect.Struct:
s.compareStruct(t, vx, vy)
return
case reflect.Slice:
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
fallthrough
case reflect.Array:
s.compareSlice(t, vx, vy)
return
case reflect.Map:
s.compareMap(t, vx, vy)
return
case reflect.Ptr:
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
vx, vy = vx.Elem(), vy.Elem()
s.compareAny(&indirect{pathStep{t.Elem(), vx, vy}})
return
case reflect.Interface:
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
vx, vy = vx.Elem(), vy.Elem()
if vx.Type() != vy.Type() {
s.report(false, 0)
return
}
s.compareAny(&typeAssertion{pathStep{vx.Type(), vx, vy}})
return
default:
panic(fmt.Sprintf("%v kind not handled", t.Kind()))
}
}
func (s *state) tryOptions(t reflect.Type, vx, vy reflect.Value) bool {
// Evaluate all filters and apply the remaining options.
if opt := s.opts.filter(s, t, vx, vy); opt != nil {
opt.apply(s, vx, vy)
return true
}
return false
}
func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool {
// Check if this type even has an Equal method.
m, ok := t.MethodByName("Equal")
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
return false
}
eq := s.callTTBFunc(m.Func, vx, vy)
s.report(eq, reportByMethod)
return true
}
func (s *state) callTRFunc(f, v reflect.Value, step *transform) reflect.Value {
v = sanitizeValue(v, f.Type().In(0))
if !s.dynChecker.Next() {
return f.Call([]reflect.Value{v})[0]
}
// Run the function twice and ensure that we get the same results back.
// We run in goroutines so that the race detector (if enabled) can detect
// unsafe mutations to the input.
c := make(chan reflect.Value)
go detectRaces(c, f, v)
got := <-c
want := f.Call([]reflect.Value{v})[0]
if step.vx, step.vy = got, want; !s.statelessCompare(step).Equal() {
// To avoid false-positives with non-reflexive equality operations,
// we sanity check whether a value is equal to itself.
if step.vx, step.vy = want, want; !s.statelessCompare(step).Equal() {
return want
}
panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f)))
}
return want
}
func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
x = sanitizeValue(x, f.Type().In(0))
y = sanitizeValue(y, f.Type().In(1))
if !s.dynChecker.Next() {
return f.Call([]reflect.Value{x, y})[0].Bool()
}
// Swapping the input arguments is sufficient to check that
// f is symmetric and deterministic.
// We run in goroutines so that the race detector (if enabled) can detect
// unsafe mutations to the input.
c := make(chan reflect.Value)
go detectRaces(c, f, y, x)
got := <-c
want := f.Call([]reflect.Value{x, y})[0].Bool()
if !got.IsValid() || got.Bool() != want {
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f)))
}
return want
}
func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
var ret reflect.Value
defer func() {
recover() // Ignore panics, let the other call to f panic instead
c <- ret
}()
ret = f.Call(vs)[0]
}
// sanitizeValue converts nil interfaces of type T to those of type R,
// assuming that T is assignable to R.
// Otherwise, it returns the input value as is.
func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
// TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/22143).
// The upstream fix landed in Go1.10, so we can remove this when drop support
// for Go1.9 and below.
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
return reflect.New(t).Elem()
}
return v
}
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
var vax, vay reflect.Value // Addressable versions of vx and vy
step := &structField{}
for i := 0; i < t.NumField(); i++ {
step.typ = t.Field(i).Type
step.vx = vx.Field(i)
step.vy = vy.Field(i)
step.name = t.Field(i).Name
step.idx = i
step.unexported = !isExported(step.name)
if step.unexported {
if step.name == "_" {
continue
}
// Defer checking of unexported fields until later to give an
// Ignore a chance to ignore the field.
if !vax.IsValid() || !vay.IsValid() {
// For retrieveUnexportedField to work, the parent struct must
// be addressable. Create a new copy of the values if
// necessary to make them addressable.
vax = makeAddressable(vx)
vay = makeAddressable(vy)
}
step.mayForce = s.exporters[t]
step.pvx = vax
step.pvy = vay
step.field = t.Field(i)
}
s.compareAny(step)
}
}
func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) {
step := &sliceIndex{pathStep: pathStep{typ: t.Elem()}}
withIndexes := func(ix, iy int) *sliceIndex {
if ix >= 0 {
step.vx, step.xkey = vx.Index(ix), ix
} else {
step.vx, step.xkey = reflect.Value{}, -1
}
if iy >= 0 {
step.vy, step.ykey = vy.Index(iy), iy
} else {
step.vy, step.ykey = reflect.Value{}, -1
}
return step
}
// Ignore options are able to ignore missing elements in a slice.
// However, detecting these reliably requires an optimal differencing
// algorithm, for which diff.Difference is not.
//
// Instead, we first iterate through both slices to detect which elements
// would be ignored if standing alone. The index of non-discarded elements
// are stored in a separate slice, which diffing is then performed on.
var indexesX, indexesY []int
var ignoredX, ignoredY []bool
for ix := 0; ix < vx.Len(); ix++ {
ignored := s.statelessCompare(withIndexes(ix, -1)).NumDiff == 0
if !ignored {
indexesX = append(indexesX, ix)
}
ignoredX = append(ignoredX, ignored)
}
for iy := 0; iy < vy.Len(); iy++ {
ignored := s.statelessCompare(withIndexes(-1, iy)).NumDiff == 0
if !ignored {
indexesY = append(indexesY, iy)
}
ignoredY = append(ignoredY, ignored)
}
// Compute an edit-script for slices vx and vy (excluding ignored elements).
edits := diff.Difference(len(indexesX), len(indexesY), func(ix, iy int) diff.Result {
return s.statelessCompare(withIndexes(indexesX[ix], indexesY[iy]))
})
// Replay the ignore-scripts and the edit-script.
var ix, iy int
for ix < vx.Len() || iy < vy.Len() {
var e diff.EditType
switch {
case ix < len(ignoredX) && ignoredX[ix]:
e = diff.UniqueX
case iy < len(ignoredY) && ignoredY[iy]:
e = diff.UniqueY
default:
e, edits = edits[0], edits[1:]
}
switch e {
case diff.UniqueX:
s.compareAny(withIndexes(ix, -1))
ix++
case diff.UniqueY:
s.compareAny(withIndexes(-1, iy))
iy++
default:
s.compareAny(withIndexes(ix, iy))
ix++
iy++
}
}
return
}
func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) {
if vx.IsNil() || vy.IsNil() {
s.report(vx.IsNil() && vy.IsNil(), 0)
return
}
// We combine and sort the two map keys so that we can perform the
// comparisons in a deterministic order.
step := &mapIndex{pathStep: pathStep{typ: t.Elem()}}
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
step.vx = vx.MapIndex(k)
step.vy = vy.MapIndex(k)
step.key = k
if !step.vx.IsValid() && !step.vy.IsValid() {
// It is possible for both vx and vy to be invalid if the
// key contained a NaN value in it.
//
// Even with the ability to retrieve NaN keys in Go 1.12,
// there still isn't a sensible way to compare the values since
// a NaN key may map to multiple unordered values.
// The most reasonable way to compare NaNs would be to compare the
// set of values. However, this is impossible to do efficiently
// since set equality is provably an O(n^2) operation given only
// an Equal function. If we had a Less function or Hash function,
// this could be done in O(n*log(n)) or O(n), respectively.
//
// Rather than adding complex logic to deal with NaNs, make it
// the user's responsibility to compare such obscure maps.
const help = "consider providing a Comparer to compare the map"
panic(fmt.Sprintf("%#v has map key with NaNs\n%s", s.curPath, help))
}
s.compareAny(step)
}
}
func (s *state) report(eq bool, rf reportFlags) {
if rf&reportIgnored == 0 {
if eq {
s.result.NumSame++
rf |= reportEqual
} else {
s.result.NumDiff++
rf |= reportUnequal
}
}
for _, r := range s.reporters {
r.Report(rf)
}
}
// recChecker tracks the state needed to periodically perform checks that
// user provided transformers are not stuck in an infinitely recursive cycle.
type recChecker struct{ next int }
// Check scans the Path for any recursive transformers and panics when any
// recursive transformers are detected. Note that the presence of a
// recursive Transformer does not necessarily imply an infinite cycle.
// As such, this check only activates after some minimal number of path steps.
func (rc *recChecker) Check(p Path) {
const minLen = 1 << 16
if rc.next == 0 {
rc.next = minLen
}
if len(p) < rc.next {
return
}
rc.next <<= 1
// Check whether the same transformer has appeared at least twice.
var ss []string
m := map[Option]int{}
for _, ps := range p {
if t, ok := ps.(Transform); ok {
t := t.Option()
if m[t] == 1 { // Transformer was used exactly once before
tf := t.(*transformer).fnc.Type()
ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0)))
}
m[t]++
}
}
if len(ss) > 0 {
const warning = "recursive set of Transformers detected"
const help = "consider using cmpopts.AcyclicTransformer"
set := strings.Join(ss, "\n\t")
panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help))
}
}
// dynChecker tracks the state needed to periodically perform checks that
// user provided functions are symmetric and deterministic.
// The zero value is safe for immediate use.
type dynChecker struct{ curr, next int }
// Next increments the state and reports whether a check should be performed.
//
// Checks occur every Nth function call, where N is a triangular number:
// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
// See https://en.wikipedia.org/wiki/Triangular_number
//
// This sequence ensures that the cost of checks drops significantly as
// the number of functions calls grows larger.
func (dc *dynChecker) Next() bool {
ok := dc.curr == dc.next
if ok {
dc.curr = 0
dc.next++
}
dc.curr++
return ok
}
// makeAddressable returns a value that is always addressable.
// It returns the input verbatim if it is already addressable,
// otherwise it creates a new value and returns an addressable copy.
func makeAddressable(v reflect.Value) reflect.Value {
if v.CanAddr() {
return v
}
vc := reflect.New(v.Type()).Elem()
vc.Set(v)
return vc
}

15
vendor/github.com/google/go-cmp/cmp/export_panic.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build purego
package cmp
import "reflect"
const supportAllowUnexported = false
func retrieveUnexportedField(reflect.Value, reflect.StructField) reflect.Value {
panic("retrieveUnexportedField is not implemented")
}

23
vendor/github.com/google/go-cmp/cmp/export_unsafe.go generated vendored Normal file
View file

@ -0,0 +1,23 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build !purego
package cmp
import (
"reflect"
"unsafe"
)
const supportAllowUnexported = true
// retrieveUnexportedField uses unsafe to forcibly retrieve any field from
// a struct such that the value has read-write permissions.
//
// The parent struct, v, must be addressable, while f must be a StructField
// describing the field to retrieve.
func retrieveUnexportedField(v reflect.Value, f reflect.StructField) reflect.Value {
return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem()
}

View file

@ -0,0 +1,17 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build !cmp_debug
package diff
var debug debugger
type debugger struct{}
func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
return f
}
func (debugger) Update() {}
func (debugger) Finish() {}

View file

@ -0,0 +1,122 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build cmp_debug
package diff
import (
"fmt"
"strings"
"sync"
"time"
)
// The algorithm can be seen running in real-time by enabling debugging:
// go test -tags=cmp_debug -v
//
// Example output:
// === RUN TestDifference/#34
// ┌───────────────────────────────┐
// │ \ · · · · · · · · · · · · · · │
// │ · # · · · · · · · · · · · · · │
// │ · \ · · · · · · · · · · · · · │
// │ · · \ · · · · · · · · · · · · │
// │ · · · X # · · · · · · · · · · │
// │ · · · # \ · · · · · · · · · · │
// │ · · · · · # # · · · · · · · · │
// │ · · · · · # \ · · · · · · · · │
// │ · · · · · · · \ · · · · · · · │
// │ · · · · · · · · \ · · · · · · │
// │ · · · · · · · · · \ · · · · · │
// │ · · · · · · · · · · \ · · # · │
// │ · · · · · · · · · · · \ # # · │
// │ · · · · · · · · · · · # # # · │
// │ · · · · · · · · · · # # # # · │
// │ · · · · · · · · · # # # # # · │
// │ · · · · · · · · · · · · · · \ │
// └───────────────────────────────┘
// [.Y..M.XY......YXYXY.|]
//
// The grid represents the edit-graph where the horizontal axis represents
// list X and the vertical axis represents list Y. The start of the two lists
// is the top-left, while the ends are the bottom-right. The '·' represents
// an unexplored node in the graph. The '\' indicates that the two symbols
// from list X and Y are equal. The 'X' indicates that two symbols are similar
// (but not exactly equal) to each other. The '#' indicates that the two symbols
// are different (and not similar). The algorithm traverses this graph trying to
// make the paths starting in the top-left and the bottom-right connect.
//
// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
// the currently established path from the forward and reverse searches,
// separated by a '|' character.
const (
updateDelay = 100 * time.Millisecond
finishDelay = 500 * time.Millisecond
ansiTerminal = true // ANSI escape codes used to move terminal cursor
)
var debug debugger
type debugger struct {
sync.Mutex
p1, p2 EditScript
fwdPath, revPath *EditScript
grid []byte
lines int
}
func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
dbg.Lock()
dbg.fwdPath, dbg.revPath = p1, p2
top := "┌─" + strings.Repeat("──", nx) + "┐\n"
row := "│ " + strings.Repeat("· ", nx) + "│\n"
btm := "└─" + strings.Repeat("──", nx) + "┘\n"
dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
dbg.lines = strings.Count(dbg.String(), "\n")
fmt.Print(dbg)
// Wrap the EqualFunc so that we can intercept each result.
return func(ix, iy int) (r Result) {
cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
for i := range cell {
cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
}
switch r = f(ix, iy); {
case r.Equal():
cell[0] = '\\'
case r.Similar():
cell[0] = 'X'
default:
cell[0] = '#'
}
return
}
}
func (dbg *debugger) Update() {
dbg.print(updateDelay)
}
func (dbg *debugger) Finish() {
dbg.print(finishDelay)
dbg.Unlock()
}
func (dbg *debugger) String() string {
dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
for i := len(*dbg.revPath) - 1; i >= 0; i-- {
dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
}
return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
}
func (dbg *debugger) print(d time.Duration) {
if ansiTerminal {
fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
}
fmt.Print(dbg)
time.Sleep(d)
}

View file

@ -0,0 +1,363 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// Package diff implements an algorithm for producing edit-scripts.
// The edit-script is a sequence of operations needed to transform one list
// of symbols into another (or vice-versa). The edits allowed are insertions,
// deletions, and modifications. The summation of all edits is called the
// Levenshtein distance as this problem is well-known in computer science.
//
// This package prioritizes performance over accuracy. That is, the run time
// is more important than obtaining a minimal Levenshtein distance.
package diff
// EditType represents a single operation within an edit-script.
type EditType uint8
const (
// Identity indicates that a symbol pair is identical in both list X and Y.
Identity EditType = iota
// UniqueX indicates that a symbol only exists in X and not Y.
UniqueX
// UniqueY indicates that a symbol only exists in Y and not X.
UniqueY
// Modified indicates that a symbol pair is a modification of each other.
Modified
)
// EditScript represents the series of differences between two lists.
type EditScript []EditType
// String returns a human-readable string representing the edit-script where
// Identity, UniqueX, UniqueY, and Modified are represented by the
// '.', 'X', 'Y', and 'M' characters, respectively.
func (es EditScript) String() string {
b := make([]byte, len(es))
for i, e := range es {
switch e {
case Identity:
b[i] = '.'
case UniqueX:
b[i] = 'X'
case UniqueY:
b[i] = 'Y'
case Modified:
b[i] = 'M'
default:
panic("invalid edit-type")
}
}
return string(b)
}
// stats returns a histogram of the number of each type of edit operation.
func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
for _, e := range es {
switch e {
case Identity:
s.NI++
case UniqueX:
s.NX++
case UniqueY:
s.NY++
case Modified:
s.NM++
default:
panic("invalid edit-type")
}
}
return
}
// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
// lists X and Y are equal.
func (es EditScript) Dist() int { return len(es) - es.stats().NI }
// LenX is the length of the X list.
func (es EditScript) LenX() int { return len(es) - es.stats().NY }
// LenY is the length of the Y list.
func (es EditScript) LenY() int { return len(es) - es.stats().NX }
// EqualFunc reports whether the symbols at indexes ix and iy are equal.
// When called by Difference, the index is guaranteed to be within nx and ny.
type EqualFunc func(ix int, iy int) Result
// Result is the result of comparison.
// NumSame is the number of sub-elements that are equal.
// NumDiff is the number of sub-elements that are not equal.
type Result struct{ NumSame, NumDiff int }
// Equal indicates whether the symbols are equal. Two symbols are equal
// if and only if NumDiff == 0. If Equal, then they are also Similar.
func (r Result) Equal() bool { return r.NumDiff == 0 }
// Similar indicates whether two symbols are similar and may be represented
// by using the Modified type. As a special case, we consider binary comparisons
// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
//
// The exact ratio of NumSame to NumDiff to determine similarity may change.
func (r Result) Similar() bool {
// Use NumSame+1 to offset NumSame so that binary comparisons are similar.
return r.NumSame+1 >= r.NumDiff
}
// Difference reports whether two lists of lengths nx and ny are equal
// given the definition of equality provided as f.
//
// This function returns an edit-script, which is a sequence of operations
// needed to convert one list into the other. The following invariants for
// the edit-script are maintained:
// • eq == (es.Dist()==0)
// • nx == es.LenX()
// • ny == es.LenY()
//
// This algorithm is not guaranteed to be an optimal solution (i.e., one that
// produces an edit-script with a minimal Levenshtein distance). This algorithm
// favors performance over optimality. The exact output is not guaranteed to
// be stable and may change over time.
func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// This algorithm is based on traversing what is known as an "edit-graph".
// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
// by Eugene W. Myers. Since D can be as large as N itself, this is
// effectively O(N^2). Unlike the algorithm from that paper, we are not
// interested in the optimal path, but at least some "decent" path.
//
// For example, let X and Y be lists of symbols:
// X = [A B C A B B A]
// Y = [C B A B A C]
//
// The edit-graph can be drawn as the following:
// A B C A B B A
// ┌─────────────┐
// C │_|_|\|_|_|_|_│ 0
// B │_|\|_|_|\|\|_│ 1
// A │\|_|_|\|_|_|\│ 2
// B │_|\|_|_|\|\|_│ 3
// A │\|_|_|\|_|_|\│ 4
// C │ | |\| | | | │ 5
// └─────────────┘ 6
// 0 1 2 3 4 5 6 7
//
// List X is written along the horizontal axis, while list Y is written
// along the vertical axis. At any point on this grid, if the symbol in
// list X matches the corresponding symbol in list Y, then a '\' is drawn.
// The goal of any minimal edit-script algorithm is to find a path from the
// top-left corner to the bottom-right corner, while traveling through the
// fewest horizontal or vertical edges.
// A horizontal edge is equivalent to inserting a symbol from list X.
// A vertical edge is equivalent to inserting a symbol from list Y.
// A diagonal edge is equivalent to a matching symbol between both X and Y.
// Invariants:
// • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
// • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
//
// In general:
// • fwdFrontier.X < revFrontier.X
// • fwdFrontier.Y < revFrontier.Y
// Unless, it is time for the algorithm to terminate.
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
fwdFrontier := fwdPath.point // Forward search frontier
revFrontier := revPath.point // Reverse search frontier
// Search budget bounds the cost of searching for better paths.
// The longest sequence of non-matching symbols that can be tolerated is
// approximately the square-root of the search budget.
searchBudget := 4 * (nx + ny) // O(n)
// The algorithm below is a greedy, meet-in-the-middle algorithm for
// computing sub-optimal edit-scripts between two lists.
//
// The algorithm is approximately as follows:
// • Searching for differences switches back-and-forth between
// a search that starts at the beginning (the top-left corner), and
// a search that starts at the end (the bottom-right corner). The goal of
// the search is connect with the search from the opposite corner.
// • As we search, we build a path in a greedy manner, where the first
// match seen is added to the path (this is sub-optimal, but provides a
// decent result in practice). When matches are found, we try the next pair
// of symbols in the lists and follow all matches as far as possible.
// • When searching for matches, we search along a diagonal going through
// through the "frontier" point. If no matches are found, we advance the
// frontier towards the opposite corner.
// • This algorithm terminates when either the X coordinates or the
// Y coordinates of the forward and reverse frontier points ever intersect.
//
// This algorithm is correct even if searching only in the forward direction
// or in the reverse direction. We do both because it is commonly observed
// that two lists commonly differ because elements were added to the front
// or end of the other list.
//
// Running the tests with the "cmp_debug" build tag prints a visualization
// of the algorithm running in real-time. This is educational for
// understanding how the algorithm works. See debug_enable.go.
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
for {
// Forward search from the beginning.
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
break
}
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
// Search in a diagonal pattern for a match.
z := zigzag(i)
p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
switch {
case p.X >= revPath.X || p.Y < fwdPath.Y:
stop1 = true // Hit top-right corner
case p.Y >= revPath.Y || p.X < fwdPath.X:
stop2 = true // Hit bottom-left corner
case f(p.X, p.Y).Equal():
// Match found, so connect the path to this point.
fwdPath.connect(p, f)
fwdPath.append(Identity)
// Follow sequence of matches as far as possible.
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
if !f(fwdPath.X, fwdPath.Y).Equal() {
break
}
fwdPath.append(Identity)
}
fwdFrontier = fwdPath.point
stop1, stop2 = true, true
default:
searchBudget-- // Match not found
}
debug.Update()
}
// Advance the frontier towards reverse point.
if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
fwdFrontier.X++
} else {
fwdFrontier.Y++
}
// Reverse search from the end.
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
break
}
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
// Search in a diagonal pattern for a match.
z := zigzag(i)
p := point{revFrontier.X - z, revFrontier.Y + z}
switch {
case fwdPath.X >= p.X || revPath.Y < p.Y:
stop1 = true // Hit bottom-left corner
case fwdPath.Y >= p.Y || revPath.X < p.X:
stop2 = true // Hit top-right corner
case f(p.X-1, p.Y-1).Equal():
// Match found, so connect the path to this point.
revPath.connect(p, f)
revPath.append(Identity)
// Follow sequence of matches as far as possible.
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
if !f(revPath.X-1, revPath.Y-1).Equal() {
break
}
revPath.append(Identity)
}
revFrontier = revPath.point
stop1, stop2 = true, true
default:
searchBudget-- // Match not found
}
debug.Update()
}
// Advance the frontier towards forward point.
if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
revFrontier.X--
} else {
revFrontier.Y--
}
}
// Join the forward and reverse paths and then append the reverse path.
fwdPath.connect(revPath.point, f)
for i := len(revPath.es) - 1; i >= 0; i-- {
t := revPath.es[i]
revPath.es = revPath.es[:i]
fwdPath.append(t)
}
debug.Finish()
return fwdPath.es
}
type path struct {
dir int // +1 if forward, -1 if reverse
point // Leading point of the EditScript path
es EditScript
}
// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
// to the edit-script to connect p.point to dst.
func (p *path) connect(dst point, f EqualFunc) {
if p.dir > 0 {
// Connect in forward direction.
for dst.X > p.X && dst.Y > p.Y {
switch r := f(p.X, p.Y); {
case r.Equal():
p.append(Identity)
case r.Similar():
p.append(Modified)
case dst.X-p.X >= dst.Y-p.Y:
p.append(UniqueX)
default:
p.append(UniqueY)
}
}
for dst.X > p.X {
p.append(UniqueX)
}
for dst.Y > p.Y {
p.append(UniqueY)
}
} else {
// Connect in reverse direction.
for p.X > dst.X && p.Y > dst.Y {
switch r := f(p.X-1, p.Y-1); {
case r.Equal():
p.append(Identity)
case r.Similar():
p.append(Modified)
case p.Y-dst.Y >= p.X-dst.X:
p.append(UniqueY)
default:
p.append(UniqueX)
}
}
for p.X > dst.X {
p.append(UniqueX)
}
for p.Y > dst.Y {
p.append(UniqueY)
}
}
}
func (p *path) append(t EditType) {
p.es = append(p.es, t)
switch t {
case Identity, Modified:
p.add(p.dir, p.dir)
case UniqueX:
p.add(p.dir, 0)
case UniqueY:
p.add(0, p.dir)
}
debug.Update()
}
type point struct{ X, Y int }
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
func zigzag(x int) int {
if x&1 != 0 {
x = ^x
}
return x >> 1
}

View file

@ -0,0 +1,87 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// Package function provides functionality for identifying function types.
package function
import (
"reflect"
"regexp"
"runtime"
"strings"
)
type funcType int
const (
_ funcType = iota
ttbFunc // func(T, T) bool
tibFunc // func(T, I) bool
trFunc // func(T) R
Equal = ttbFunc // func(T, T) bool
EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
Transformer = trFunc // func(T) R
ValueFilter = ttbFunc // func(T, T) bool
Less = ttbFunc // func(T, T) bool
)
var boolType = reflect.TypeOf(true)
// IsType reports whether the reflect.Type is of the specified function type.
func IsType(t reflect.Type, ft funcType) bool {
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
return false
}
ni, no := t.NumIn(), t.NumOut()
switch ft {
case ttbFunc: // func(T, T) bool
if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
return true
}
case tibFunc: // func(T, I) bool
if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
return true
}
case trFunc: // func(T) R
if ni == 1 && no == 1 {
return true
}
}
return false
}
var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`)
// NameOf returns the name of the function value.
func NameOf(v reflect.Value) string {
fnc := runtime.FuncForPC(v.Pointer())
if fnc == nil {
return "<unknown>"
}
fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm"
// Method closures have a "-fm" suffix.
fullName = strings.TrimSuffix(fullName, "-fm")
var name string
for len(fullName) > 0 {
inParen := strings.HasSuffix(fullName, ")")
fullName = strings.TrimSuffix(fullName, ")")
s := lastIdentRx.FindString(fullName)
if s == "" {
break
}
name = s + "." + name
fullName = strings.TrimSuffix(fullName, s)
if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 {
fullName = fullName[:i]
}
fullName = strings.TrimSuffix(fullName, ".")
}
return strings.TrimSuffix(name, ".")
}

View file

@ -0,0 +1,280 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// Package value provides functionality for reflect.Value types.
package value
import (
"fmt"
"reflect"
"strconv"
"strings"
"unicode"
)
var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
// Format formats the value v as a string.
//
// This is similar to fmt.Sprintf("%+v", v) except this:
// * Prints the type unless it can be elided
// * Avoids printing struct fields that are zero
// * Prints a nil-slice as being nil, not empty
// * Prints map entries in deterministic order
func Format(v reflect.Value, conf FormatConfig) string {
conf.printType = true
conf.followPointers = true
conf.realPointers = true
return formatAny(v, conf, visited{})
}
type FormatConfig struct {
UseStringer bool // Should the String method be used if available?
printType bool // Should we print the type before the value?
PrintPrimitiveType bool // Should we print the type of primitives?
followPointers bool // Should we recursively follow pointers?
realPointers bool // Should we print the real address of pointers?
}
func formatAny(v reflect.Value, conf FormatConfig, m visited) string {
// TODO: Should this be a multi-line printout in certain situations?
if !v.IsValid() {
return "<non-existent>"
}
if conf.UseStringer && v.Type().Implements(stringerIface) && v.CanInterface() {
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
return "<nil>"
}
const stringerPrefix = "s" // Indicates that the String method was used
s := v.Interface().(fmt.Stringer).String()
return stringerPrefix + formatString(s)
}
switch v.Kind() {
case reflect.Bool:
return formatPrimitive(v.Type(), v.Bool(), conf)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return formatPrimitive(v.Type(), v.Int(), conf)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
// Unnamed uints are usually bytes or words, so use hexadecimal.
return formatPrimitive(v.Type(), formatHex(v.Uint()), conf)
}
return formatPrimitive(v.Type(), v.Uint(), conf)
case reflect.Float32, reflect.Float64:
return formatPrimitive(v.Type(), v.Float(), conf)
case reflect.Complex64, reflect.Complex128:
return formatPrimitive(v.Type(), v.Complex(), conf)
case reflect.String:
return formatPrimitive(v.Type(), formatString(v.String()), conf)
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
return formatPointer(v, conf)
case reflect.Ptr:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("(%v)(nil)", v.Type())
}
return "<nil>"
}
if m.Visit(v) || !conf.followPointers {
return formatPointer(v, conf)
}
return "&" + formatAny(v.Elem(), conf, m)
case reflect.Interface:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("%v(nil)", v.Type())
}
return "<nil>"
}
return formatAny(v.Elem(), conf, m)
case reflect.Slice:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("%v(nil)", v.Type())
}
return "<nil>"
}
fallthrough
case reflect.Array:
var ss []string
subConf := conf
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
for i := 0; i < v.Len(); i++ {
vi := v.Index(i)
if vi.CanAddr() { // Check for recursive elements
p := vi.Addr()
if m.Visit(p) {
subConf := conf
subConf.printType = true
ss = append(ss, "*"+formatPointer(p, subConf))
continue
}
}
ss = append(ss, formatAny(vi, subConf, m))
}
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
if conf.printType {
return v.Type().String() + s
}
return s
case reflect.Map:
if v.IsNil() {
if conf.printType {
return fmt.Sprintf("%v(nil)", v.Type())
}
return "<nil>"
}
if m.Visit(v) {
return formatPointer(v, conf)
}
var ss []string
keyConf, valConf := conf, conf
keyConf.printType = v.Type().Key().Kind() == reflect.Interface
keyConf.followPointers = false
valConf.printType = v.Type().Elem().Kind() == reflect.Interface
for _, k := range SortKeys(v.MapKeys()) {
sk := formatAny(k, keyConf, m)
sv := formatAny(v.MapIndex(k), valConf, m)
ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
}
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
if conf.printType {
return v.Type().String() + s
}
return s
case reflect.Struct:
var ss []string
subConf := conf
subConf.printType = true
for i := 0; i < v.NumField(); i++ {
vv := v.Field(i)
if isZero(vv) {
continue // Elide zero value fields
}
name := v.Type().Field(i).Name
subConf.UseStringer = conf.UseStringer
s := formatAny(vv, subConf, m)
ss = append(ss, fmt.Sprintf("%s: %s", name, s))
}
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
if conf.printType {
return v.Type().String() + s
}
return s
default:
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
}
}
func formatString(s string) string {
// Use quoted string if it the same length as a raw string literal.
// Otherwise, attempt to use the raw string form.
qs := strconv.Quote(s)
if len(qs) == 1+len(s)+1 {
return qs
}
// Disallow newlines to ensure output is a single line.
// Only allow printable runes for readability purposes.
rawInvalid := func(r rune) bool {
return r == '`' || r == '\n' || !unicode.IsPrint(r)
}
if strings.IndexFunc(s, rawInvalid) < 0 {
return "`" + s + "`"
}
return qs
}
func formatPrimitive(t reflect.Type, v interface{}, conf FormatConfig) string {
if conf.printType && (conf.PrintPrimitiveType || t.PkgPath() != "") {
return fmt.Sprintf("%v(%v)", t, v)
}
return fmt.Sprintf("%v", v)
}
func formatPointer(v reflect.Value, conf FormatConfig) string {
p := v.Pointer()
if !conf.realPointers {
p = 0 // For deterministic printing purposes
}
s := formatHex(uint64(p))
if conf.printType {
return fmt.Sprintf("(%v)(%s)", v.Type(), s)
}
return s
}
func formatHex(u uint64) string {
var f string
switch {
case u <= 0xff:
f = "0x%02x"
case u <= 0xffff:
f = "0x%04x"
case u <= 0xffffff:
f = "0x%06x"
case u <= 0xffffffff:
f = "0x%08x"
case u <= 0xffffffffff:
f = "0x%010x"
case u <= 0xffffffffffff:
f = "0x%012x"
case u <= 0xffffffffffffff:
f = "0x%014x"
case u <= 0xffffffffffffffff:
f = "0x%016x"
}
return fmt.Sprintf(f, u)
}
// isZero reports whether v is the zero value.
// This does not rely on Interface and so can be used on unexported fields.
func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Bool:
return v.Bool() == false
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Complex64, reflect.Complex128:
return v.Complex() == 0
case reflect.String:
return v.String() == ""
case reflect.UnsafePointer:
return v.Pointer() == 0
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
return v.IsNil()
case reflect.Array:
for i := 0; i < v.Len(); i++ {
if !isZero(v.Index(i)) {
return false
}
}
return true
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if !isZero(v.Field(i)) {
return false
}
}
return true
}
return false
}
type visited map[Pointer]bool
func (m visited) Visit(v reflect.Value) bool {
p := PointerOf(v)
visited := m[p]
m[p] = true
return visited
}

View file

@ -0,0 +1,23 @@
// Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build purego
package value
import "reflect"
// Pointer is an opaque typed pointer and is guaranteed to be comparable.
type Pointer struct {
p uintptr
t reflect.Type
}
// PointerOf returns a Pointer from v, which must be a
// reflect.Ptr, reflect.Slice, or reflect.Map.
func PointerOf(v reflect.Value) Pointer {
// NOTE: Storing a pointer as an uintptr is technically incorrect as it
// assumes that the GC implementation does not use a moving collector.
return Pointer{v.Pointer(), v.Type()}
}

View file

@ -0,0 +1,26 @@
// Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// +build !purego
package value
import (
"reflect"
"unsafe"
)
// Pointer is an opaque typed pointer and is guaranteed to be comparable.
type Pointer struct {
p unsafe.Pointer
t reflect.Type
}
// PointerOf returns a Pointer from v, which must be a
// reflect.Ptr, reflect.Slice, or reflect.Map.
func PointerOf(v reflect.Value) Pointer {
// The proper representation of a pointer is unsafe.Pointer,
// which is necessary if the GC ever uses a moving collector.
return Pointer{unsafe.Pointer(v.Pointer()), v.Type()}
}

View file

@ -0,0 +1,104 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package value
import (
"fmt"
"math"
"reflect"
"sort"
)
// SortKeys sorts a list of map keys, deduplicating keys if necessary.
// The type of each value must be comparable.
func SortKeys(vs []reflect.Value) []reflect.Value {
if len(vs) == 0 {
return vs
}
// Sort the map keys.
sort.Slice(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) })
// Deduplicate keys (fails for NaNs).
vs2 := vs[:1]
for _, v := range vs[1:] {
if isLess(vs2[len(vs2)-1], v) {
vs2 = append(vs2, v)
}
}
return vs2
}
// isLess is a generic function for sorting arbitrary map keys.
// The inputs must be of the same type and must be comparable.
func isLess(x, y reflect.Value) bool {
switch x.Type().Kind() {
case reflect.Bool:
return !x.Bool() && y.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return x.Int() < y.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return x.Uint() < y.Uint()
case reflect.Float32, reflect.Float64:
fx, fy := x.Float(), y.Float()
return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
case reflect.Complex64, reflect.Complex128:
cx, cy := x.Complex(), y.Complex()
rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
}
return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
return x.Pointer() < y.Pointer()
case reflect.String:
return x.String() < y.String()
case reflect.Array:
for i := 0; i < x.Len(); i++ {
if isLess(x.Index(i), y.Index(i)) {
return true
}
if isLess(y.Index(i), x.Index(i)) {
return false
}
}
return false
case reflect.Struct:
for i := 0; i < x.NumField(); i++ {
if isLess(x.Field(i), y.Field(i)) {
return true
}
if isLess(y.Field(i), x.Field(i)) {
return false
}
}
return false
case reflect.Interface:
vx, vy := x.Elem(), y.Elem()
if !vx.IsValid() || !vy.IsValid() {
return !vx.IsValid() && vy.IsValid()
}
tx, ty := vx.Type(), vy.Type()
if tx == ty {
return isLess(x.Elem(), y.Elem())
}
if tx.Kind() != ty.Kind() {
return vx.Kind() < vy.Kind()
}
if tx.String() != ty.String() {
return tx.String() < ty.String()
}
if tx.PkgPath() != ty.PkgPath() {
return tx.PkgPath() < ty.PkgPath()
}
// This can happen in rare situations, so we fallback to just comparing
// the unique pointer for a reflect.Type. This guarantees deterministic
// ordering within a program, but it is obviously not stable.
return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
default:
// Must be Func, Map, or Slice; which are not comparable.
panic(fmt.Sprintf("%T is not comparable", x.Type()))
}
}

505
vendor/github.com/google/go-cmp/cmp/options.go generated vendored Normal file
View file

@ -0,0 +1,505 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp
import (
"fmt"
"reflect"
"regexp"
"strings"
"github.com/google/go-cmp/cmp/internal/function"
)
// Option configures for specific behavior of Equal and Diff. In particular,
// the fundamental Option functions (Ignore, Transformer, and Comparer),
// configure how equality is determined.
//
// The fundamental options may be composed with filters (FilterPath and
// FilterValues) to control the scope over which they are applied.
//
// The cmp/cmpopts package provides helper functions for creating options that
// may be used with Equal and Diff.
type Option interface {
// filter applies all filters and returns the option that remains.
// Each option may only read s.curPath and call s.callTTBFunc.
//
// An Options is returned only if multiple comparers or transformers
// can apply simultaneously and will only contain values of those types
// or sub-Options containing values of those types.
filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption
}
// applicableOption represents the following types:
// Fundamental: ignore | validator | *comparer | *transformer
// Grouping: Options
type applicableOption interface {
Option
// apply executes the option, which may mutate s or panic.
apply(s *state, vx, vy reflect.Value)
}
// coreOption represents the following types:
// Fundamental: ignore | validator | *comparer | *transformer
// Filters: *pathFilter | *valuesFilter
type coreOption interface {
Option
isCore()
}
type core struct{}
func (core) isCore() {}
// Options is a list of Option values that also satisfies the Option interface.
// Helper comparison packages may return an Options value when packing multiple
// Option values into a single Option. When this package processes an Options,
// it will be implicitly expanded into a flat list.
//
// Applying a filter on an Options is equivalent to applying that same filter
// on all individual options held within.
type Options []Option
func (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) {
for _, opt := range opts {
switch opt := opt.filter(s, t, vx, vy); opt.(type) {
case ignore:
return ignore{} // Only ignore can short-circuit evaluation
case validator:
out = validator{} // Takes precedence over comparer or transformer
case *comparer, *transformer, Options:
switch out.(type) {
case nil:
out = opt
case validator:
// Keep validator
case *comparer, *transformer, Options:
out = Options{out, opt} // Conflicting comparers or transformers
}
}
}
return out
}
func (opts Options) apply(s *state, _, _ reflect.Value) {
const warning = "ambiguous set of applicable options"
const help = "consider using filters to ensure at most one Comparer or Transformer may apply"
var ss []string
for _, opt := range flattenOptions(nil, opts) {
ss = append(ss, fmt.Sprint(opt))
}
set := strings.Join(ss, "\n\t")
panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help))
}
func (opts Options) String() string {
var ss []string
for _, opt := range opts {
ss = append(ss, fmt.Sprint(opt))
}
return fmt.Sprintf("Options{%s}", strings.Join(ss, ", "))
}
// FilterPath returns a new Option where opt is only evaluated if filter f
// returns true for the current Path in the value tree.
//
// This filter is called even if a slice element or map entry is missing and
// provides an opportunity to ignore such cases. The filter function must be
// symmetric such that the filter result is identical regardless of whether the
// missing value is from x or y.
//
// The option passed in may be an Ignore, Transformer, Comparer, Options, or
// a previously filtered Option.
func FilterPath(f func(Path) bool, opt Option) Option {
if f == nil {
panic("invalid path filter function")
}
if opt := normalizeOption(opt); opt != nil {
return &pathFilter{fnc: f, opt: opt}
}
return nil
}
type pathFilter struct {
core
fnc func(Path) bool
opt Option
}
func (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
if f.fnc(s.curPath) {
return f.opt.filter(s, t, vx, vy)
}
return nil
}
func (f pathFilter) String() string {
return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt)
}
// FilterValues returns a new Option where opt is only evaluated if filter f,
// which is a function of the form "func(T, T) bool", returns true for the
// current pair of values being compared. If either value is invalid or
// the type of the values is not assignable to T, then this filter implicitly
// returns false.
//
// The filter function must be
// symmetric (i.e., agnostic to the order of the inputs) and
// deterministic (i.e., produces the same result when given the same inputs).
// If T is an interface, it is possible that f is called with two values with
// different concrete types that both implement T.
//
// The option passed in may be an Ignore, Transformer, Comparer, Options, or
// a previously filtered Option.
func FilterValues(f interface{}, opt Option) Option {
v := reflect.ValueOf(f)
if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() {
panic(fmt.Sprintf("invalid values filter function: %T", f))
}
if opt := normalizeOption(opt); opt != nil {
vf := &valuesFilter{fnc: v, opt: opt}
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
vf.typ = ti
}
return vf
}
return nil
}
type valuesFilter struct {
core
typ reflect.Type // T
fnc reflect.Value // func(T, T) bool
opt Option
}
func (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() {
return nil
}
if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {
return f.opt.filter(s, t, vx, vy)
}
return nil
}
func (f valuesFilter) String() string {
return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt)
}
// Ignore is an Option that causes all comparisons to be ignored.
// This value is intended to be combined with FilterPath or FilterValues.
// It is an error to pass an unfiltered Ignore option to Equal.
func Ignore() Option { return ignore{} }
type ignore struct{ core }
func (ignore) isFiltered() bool { return false }
func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} }
func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportIgnored) }
func (ignore) String() string { return "Ignore()" }
// validator is a sentinel Option type to indicate that some options could not
// be evaluated due to unexported fields, missing slice elements, or
// missing map entries. Both values are validator only for unexported fields.
type validator struct{ core }
func (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption {
if !vx.IsValid() || !vy.IsValid() {
return validator{}
}
if !vx.CanInterface() || !vy.CanInterface() {
return validator{}
}
return nil
}
func (validator) apply(s *state, vx, vy reflect.Value) {
// Implies missing slice element or map entry.
if !vx.IsValid() || !vy.IsValid() {
s.report(vx.IsValid() == vy.IsValid(), 0)
return
}
// Unable to Interface implies unexported field without visibility access.
if !vx.CanInterface() || !vy.CanInterface() {
const help = "consider using a custom Comparer; if you control the implementation of type, you can also consider AllowUnexported or cmpopts.IgnoreUnexported"
panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
}
panic("not reachable")
}
// identRx represents a valid identifier according to the Go specification.
const identRx = `[_\p{L}][_\p{L}\p{N}]*`
var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`)
// Transformer returns an Option that applies a transformation function that
// converts values of a certain type into that of another.
//
// The transformer f must be a function "func(T) R" that converts values of
// type T to those of type R and is implicitly filtered to input values
// assignable to T. The transformer must not mutate T in any way.
//
// To help prevent some cases of infinite recursive cycles applying the
// same transform to the output of itself (e.g., in the case where the
// input and output types are the same), an implicit filter is added such that
// a transformer is applicable only if that exact transformer is not already
// in the tail of the Path since the last non-Transform step.
// For situations where the implicit filter is still insufficient,
// consider using cmpopts.AcyclicTransformer, which adds a filter
// to prevent the transformer from being recursively applied upon itself.
//
// The name is a user provided label that is used as the Transform.Name in the
// transformation PathStep (and eventually shown in the Diff output).
// The name must be a valid identifier or qualified identifier in Go syntax.
// If empty, an arbitrary name is used.
func Transformer(name string, f interface{}) Option {
v := reflect.ValueOf(f)
if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {
panic(fmt.Sprintf("invalid transformer function: %T", f))
}
if name == "" {
name = function.NameOf(v)
if !identsRx.MatchString(name) {
name = "λ" // Lambda-symbol as placeholder name
}
} else if !identsRx.MatchString(name) {
panic(fmt.Sprintf("invalid name: %q", name))
}
tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
tr.typ = ti
}
return tr
}
type transformer struct {
core
name string
typ reflect.Type // T
fnc reflect.Value // func(T) R
}
func (tr *transformer) isFiltered() bool { return tr.typ != nil }
func (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption {
for i := len(s.curPath) - 1; i >= 0; i-- {
if t, ok := s.curPath[i].(*transform); !ok {
break // Hit most recent non-Transform step
} else if tr == t.trans {
return nil // Cannot directly use same Transform
}
}
if tr.typ == nil || t.AssignableTo(tr.typ) {
return tr
}
return nil
}
func (tr *transformer) apply(s *state, vx, vy reflect.Value) {
step := &transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}
vvx := s.callTRFunc(tr.fnc, vx, step)
vvy := s.callTRFunc(tr.fnc, vy, step)
step.vx, step.vy = vvx, vvy
s.compareAny(step)
}
func (tr transformer) String() string {
return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc))
}
// Comparer returns an Option that determines whether two values are equal
// to each other.
//
// The comparer f must be a function "func(T, T) bool" and is implicitly
// filtered to input values assignable to T. If T is an interface, it is
// possible that f is called with two values of different concrete types that
// both implement T.
//
// The equality function must be:
// • Symmetric: equal(x, y) == equal(y, x)
// • Deterministic: equal(x, y) == equal(x, y)
// • Pure: equal(x, y) does not modify x or y
func Comparer(f interface{}) Option {
v := reflect.ValueOf(f)
if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
panic(fmt.Sprintf("invalid comparer function: %T", f))
}
cm := &comparer{fnc: v}
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
cm.typ = ti
}
return cm
}
type comparer struct {
core
typ reflect.Type // T
fnc reflect.Value // func(T, T) bool
}
func (cm *comparer) isFiltered() bool { return cm.typ != nil }
func (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption {
if cm.typ == nil || t.AssignableTo(cm.typ) {
return cm
}
return nil
}
func (cm *comparer) apply(s *state, vx, vy reflect.Value) {
eq := s.callTTBFunc(cm.fnc, vx, vy)
s.report(eq, reportByFunc)
}
func (cm comparer) String() string {
return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc))
}
// AllowUnexported returns an Option that forcibly allows operations on
// unexported fields in certain structs, which are specified by passing in a
// value of each struct type.
//
// Users of this option must understand that comparing on unexported fields
// from external packages is not safe since changes in the internal
// implementation of some external package may cause the result of Equal
// to unexpectedly change. However, it may be valid to use this option on types
// defined in an internal package where the semantic meaning of an unexported
// field is in the control of the user.
//
// In many cases, a custom Comparer should be used instead that defines
// equality as a function of the public API of a type rather than the underlying
// unexported implementation.
//
// For example, the reflect.Type documentation defines equality to be determined
// by the == operator on the interface (essentially performing a shallow pointer
// comparison) and most attempts to compare *regexp.Regexp types are interested
// in only checking that the regular expression strings are equal.
// Both of these are accomplished using Comparers:
//
// Comparer(func(x, y reflect.Type) bool { return x == y })
// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
//
// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
// all unexported fields on specified struct types.
func AllowUnexported(types ...interface{}) Option {
if !supportAllowUnexported {
panic("AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS")
}
m := make(map[reflect.Type]bool)
for _, typ := range types {
t := reflect.TypeOf(typ)
if t.Kind() != reflect.Struct {
panic(fmt.Sprintf("invalid struct type: %T", typ))
}
m[t] = true
}
return visibleStructs(m)
}
type visibleStructs map[reflect.Type]bool
func (visibleStructs) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
panic("not implemented")
}
// reportFlags is a bit-set representing how a comparison was determined.
type reportFlags uint
const (
_ reportFlags = (1 << iota) / 2
// reportEqual reports whether the node is equal.
// This may be ORed with reportByMethod or reportByFunc.
reportEqual
// reportUnequal reports whether the node is not equal.
// This may be ORed with reportByMethod or reportByFunc.
reportUnequal
// reportIgnored reports whether the node was ignored.
reportIgnored
// reportByMethod reports whether equality was determined by calling the
// Equal method. This may be ORed with reportEqual or reportUnequal.
reportByMethod
// reportByFunc reports whether equality was determined by calling a custom
// Comparer function. This may be ORed with reportEqual or reportUnequal.
reportByFunc
)
// reporter is an Option that can be passed to Equal. When Equal traverses
// the value trees, it calls PushStep as it descends into each node in the
// tree and PopStep as it ascend out of the node. The leaves of the tree are
// either compared (determined to be equal or not equal) or ignored and reported
// as such by calling the Report method.
func reporter(r interface {
// TODO: Export this option.
// PushStep is called when a tree-traversal operation is performed.
// The PathStep itself is only valid until the step is popped.
// The PathStep.Values are valid for the duration of the entire traversal.
//
// Equal always call PushStep at the start to provide an operation-less
// PathStep used to report the root values.
//
// The entries of a map are iterated through in an unspecified order.
PushStep(PathStep)
// Report is called at exactly once on leaf nodes to report whether the
// comparison identified the node as equal, unequal, or ignored.
// A leaf node is one that is immediately preceded by and followed by
// a pair of PushStep and PopStep calls.
Report(reportFlags)
// PopStep ascends back up the value tree.
// There is always a matching pop call for every push call.
PopStep()
}) Option {
return reporterOption{r}
}
type reporterOption struct{ reporterIface }
type reporterIface interface {
PushStep(PathStep)
Report(reportFlags)
PopStep()
}
func (reporterOption) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
panic("not implemented")
}
// normalizeOption normalizes the input options such that all Options groups
// are flattened and groups with a single element are reduced to that element.
// Only coreOptions and Options containing coreOptions are allowed.
func normalizeOption(src Option) Option {
switch opts := flattenOptions(nil, Options{src}); len(opts) {
case 0:
return nil
case 1:
return opts[0]
default:
return opts
}
}
// flattenOptions copies all options in src to dst as a flat list.
// Only coreOptions and Options containing coreOptions are allowed.
func flattenOptions(dst, src Options) Options {
for _, opt := range src {
switch opt := opt.(type) {
case nil:
continue
case Options:
dst = flattenOptions(dst, opt)
case coreOption:
dst = append(dst, opt)
default:
panic(fmt.Sprintf("invalid option type: %T", opt))
}
}
return dst
}

342
vendor/github.com/google/go-cmp/cmp/path.go generated vendored Normal file
View file

@ -0,0 +1,342 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp
import (
"fmt"
"reflect"
"strings"
"unicode"
"unicode/utf8"
)
type (
// Path is a list of PathSteps describing the sequence of operations to get
// from some root type to the current position in the value tree.
// The first Path element is always an operation-less PathStep that exists
// simply to identify the initial type.
//
// When traversing structs with embedded structs, the embedded struct will
// always be accessed as a field before traversing the fields of the
// embedded struct themselves. That is, an exported field from the
// embedded struct will never be accessed directly from the parent struct.
Path []PathStep
// PathStep is a union-type for specific operations to traverse
// a value's tree structure. Users of this package never need to implement
// these types as values of this type will be returned by this package.
PathStep interface {
String() string
// Type is the resulting type after performing the path step.
Type() reflect.Type
// Values is the resulting values after performing the path step.
// The type of each valid value is guaranteed to be identical to Type.
//
// In some cases, one or both may be invalid or have restrictions:
// • For StructField, both are not interface-able if the current field
// is unexported and the struct type is not explicitly permitted by
// AllowUnexported to traverse unexported fields.
// • For SliceIndex, one may be invalid if an element is missing from
// either the x or y slice.
// • For MapIndex, one may be invalid if an entry is missing from
// either the x or y map.
//
// The provided values must not be mutated.
Values() (vx, vy reflect.Value)
}
// StructField represents a struct field access on a field called Name.
StructField interface {
PathStep
// Name is the field name.
Name() string
// Index is the index of the field in the parent struct type.
// See reflect.Type.Field.
Index() int
isStructField()
}
// SliceIndex is an index operation on a slice or array at some index Key.
SliceIndex interface {
PathStep
// Key is the index key; it may return -1 if in a split state
Key() int
// SplitKeys are the indexes for indexing into slices in the
// x and y values, respectively. These indexes may differ due to the
// insertion or removal of an element in one of the slices, causing
// all of the indexes to be shifted. If an index is -1, then that
// indicates that the element does not exist in the associated slice.
//
// Key is guaranteed to return -1 if and only if the indexes returned
// by SplitKeys are not the same. SplitKeys will never return -1 for
// both indexes.
SplitKeys() (ix, iy int)
isSliceIndex()
}
// MapIndex is an index operation on a map at some index Key.
MapIndex interface {
PathStep
// Key is the value of the map key.
Key() reflect.Value
isMapIndex()
}
// Indirect represents pointer indirection on the parent type.
Indirect interface {
PathStep
isIndirect()
}
// TypeAssertion represents a type assertion on an interface.
TypeAssertion interface {
PathStep
isTypeAssertion()
}
// Transform is a transformation from the parent type to the current type.
Transform interface {
PathStep
// Name is the name of the Transformer.
Name() string
// Func is the function pointer to the transformer function.
Func() reflect.Value
// Option returns the originally constructed Transformer option.
// The == operator can be used to detect the exact option used.
Option() Option
isTransform()
}
)
func (pa *Path) push(s PathStep) {
*pa = append(*pa, s)
}
func (pa *Path) pop() {
*pa = (*pa)[:len(*pa)-1]
}
// Last returns the last PathStep in the Path.
// If the path is empty, this returns a non-nil PathStep that reports a nil Type.
func (pa Path) Last() PathStep {
return pa.Index(-1)
}
// Index returns the ith step in the Path and supports negative indexing.
// A negative index starts counting from the tail of the Path such that -1
// refers to the last step, -2 refers to the second-to-last step, and so on.
// If index is invalid, this returns a non-nil PathStep that reports a nil Type.
func (pa Path) Index(i int) PathStep {
if i < 0 {
i = len(pa) + i
}
if i < 0 || i >= len(pa) {
return pathStep{}
}
return pa[i]
}
// String returns the simplified path to a node.
// The simplified path only contains struct field accesses.
//
// For example:
// MyMap.MySlices.MyField
func (pa Path) String() string {
var ss []string
for _, s := range pa {
if _, ok := s.(*structField); ok {
ss = append(ss, s.String())
}
}
return strings.TrimPrefix(strings.Join(ss, ""), ".")
}
// GoString returns the path to a specific node using Go syntax.
//
// For example:
// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
func (pa Path) GoString() string {
var ssPre, ssPost []string
var numIndirect int
for i, s := range pa {
var nextStep PathStep
if i+1 < len(pa) {
nextStep = pa[i+1]
}
switch s := s.(type) {
case *indirect:
numIndirect++
pPre, pPost := "(", ")"
switch nextStep.(type) {
case *indirect:
continue // Next step is indirection, so let them batch up
case *structField:
numIndirect-- // Automatic indirection on struct fields
case nil:
pPre, pPost = "", "" // Last step; no need for parenthesis
}
if numIndirect > 0 {
ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect))
ssPost = append(ssPost, pPost)
}
numIndirect = 0
continue
case *transform:
ssPre = append(ssPre, s.trans.name+"(")
ssPost = append(ssPost, ")")
continue
case *typeAssertion:
// As a special-case, elide type assertions on anonymous types
// since they are typically generated dynamically and can be very
// verbose. For example, some transforms return interface{} because
// of Go's lack of generics, but typically take in and return the
// exact same concrete type.
if s.Type().PkgPath() == "" {
continue
}
}
ssPost = append(ssPost, s.String())
}
for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {
ssPre[i], ssPre[j] = ssPre[j], ssPre[i]
}
return strings.Join(ssPre, "") + strings.Join(ssPost, "")
}
type (
pathStep struct {
typ reflect.Type
vx, vy reflect.Value
}
structField struct {
pathStep
name string
idx int
// These fields are used for forcibly accessing an unexported field.
// pvx, pvy, and field are only valid if unexported is true.
unexported bool
mayForce bool // Forcibly allow visibility
pvx, pvy reflect.Value // Parent values
field reflect.StructField // Field information
}
sliceIndex struct {
pathStep
xkey, ykey int
}
mapIndex struct {
pathStep
key reflect.Value
}
indirect struct {
pathStep
}
typeAssertion struct {
pathStep
}
transform struct {
pathStep
trans *transformer
}
)
func (ps pathStep) Type() reflect.Type { return ps.typ }
func (ps pathStep) Values() (vx, vy reflect.Value) { return ps.vx, ps.vy }
func (ps pathStep) String() string {
if ps.typ == nil {
return "<nil>"
}
s := ps.typ.String()
if s == "" || strings.ContainsAny(s, "{}\n") {
return "root" // Type too simple or complex to print
}
return fmt.Sprintf("{%s}", s)
}
func (ps pathStep) isPathStep() {}
func (sf structField) Values() (vx, vy reflect.Value) {
if !sf.unexported {
return sf.vx, sf.vy // CanInterface reports true
}
// Forcibly obtain read-write access to an unexported struct field.
if sf.mayForce {
vx = retrieveUnexportedField(sf.pvx, sf.field)
vy = retrieveUnexportedField(sf.pvy, sf.field)
return vx, vy // CanInterface reports true
}
return sf.vx, sf.vy // CanInterface reports false
}
func (sf structField) String() string { return fmt.Sprintf(".%s", sf.name) }
func (sf structField) Name() string { return sf.name }
func (sf structField) Index() int { return sf.idx }
func (sf structField) isStructField() {}
func (si sliceIndex) String() string {
switch {
case si.xkey == si.ykey:
return fmt.Sprintf("[%d]", si.xkey)
case si.ykey == -1:
// [5->?] means "I don't know where X[5] went"
return fmt.Sprintf("[%d->?]", si.xkey)
case si.xkey == -1:
// [?->3] means "I don't know where Y[3] came from"
return fmt.Sprintf("[?->%d]", si.ykey)
default:
// [5->3] means "X[5] moved to Y[3]"
return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)
}
}
func (si sliceIndex) Key() int {
if si.xkey != si.ykey {
return -1
}
return si.xkey
}
func (si sliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey }
func (si sliceIndex) isSliceIndex() {}
func (mi mapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
func (mi mapIndex) Key() reflect.Value { return mi.key }
func (mi mapIndex) isMapIndex() {}
func (in indirect) String() string { return "*" }
func (in indirect) isIndirect() {}
func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
func (ta typeAssertion) isTypeAssertion() {}
func (tf transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
func (tf transform) Name() string { return tf.trans.name }
func (tf transform) Func() reflect.Value { return tf.trans.fnc }
func (tf transform) Option() Option { return tf.trans }
func (tf transform) isTransform() {}
var (
_ PathStep = StructField(structField{})
_ PathStep = SliceIndex(sliceIndex{})
_ PathStep = MapIndex(mapIndex{})
_ PathStep = Indirect(indirect{})
_ PathStep = TypeAssertion(typeAssertion{})
_ PathStep = Transform(transform{})
)
// isExported reports whether the identifier is exported.
func isExported(id string) bool {
r, _ := utf8.DecodeRuneInString(id)
return unicode.IsUpper(r)
}

62
vendor/github.com/google/go-cmp/cmp/report.go generated vendored Normal file
View file

@ -0,0 +1,62 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp/internal/value"
)
type defaultReporter struct {
curPath Path
diffs []string // List of differences, possibly truncated
ndiffs int // Total number of differences
nbytes int // Number of bytes in diffs
nlines int // Number of lines in diffs
}
func (r *defaultReporter) PushStep(ps PathStep) {
r.curPath.push(ps)
}
func (r *defaultReporter) Report(f reportFlags) {
if f&reportUnequal > 0 {
vx, vy := r.curPath.Last().Values()
r.report(vx, vy, r.curPath)
}
}
func (r *defaultReporter) PopStep() {
r.curPath.pop()
}
func (r *defaultReporter) report(x, y reflect.Value, p Path) {
const maxBytes = 4096
const maxLines = 256
r.ndiffs++
if r.nbytes < maxBytes && r.nlines < maxLines {
sx := value.Format(x, value.FormatConfig{UseStringer: true})
sy := value.Format(y, value.FormatConfig{UseStringer: true})
if sx == sy {
// Unhelpful output, so use more exact formatting.
sx = value.Format(x, value.FormatConfig{PrintPrimitiveType: true})
sy = value.Format(y, value.FormatConfig{PrintPrimitiveType: true})
}
s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
r.diffs = append(r.diffs, s)
r.nbytes += len(s)
r.nlines += strings.Count(s, "\n")
}
}
func (r *defaultReporter) String() string {
s := strings.Join(r.diffs, "")
if r.ndiffs == len(r.diffs) {
return s
}
return fmt.Sprintf("%s... %d more differences ...", s, r.ndiffs-len(r.diffs))
}

30
vendor/vendor.json vendored
View file

@ -176,6 +176,36 @@
"revision": "553a641470496b2327abcac10b36396bd98e45c9",
"revisionTime": "2017-02-15T23:32:05Z"
},
{
"checksumSHA1": "hgZvwSpZoZ+nzOJxP/4ivVe6rCE=",
"path": "github.com/google/go-cmp/cmp",
"revision": "c81281657ad99ba22e14fda7c4dfaaf2974c454e",
"revisionTime": "2019-02-28T02:41:37Z"
},
{
"checksumSHA1": "2b4rwcqKeRFkd9iT7+vQgBNVFM8=",
"path": "github.com/google/go-cmp/cmp/cmpopts",
"revision": "c81281657ad99ba22e14fda7c4dfaaf2974c454e",
"revisionTime": "2019-02-28T02:41:37Z"
},
{
"checksumSHA1": "ElnaXKknoOmCtSOuZ7mY3piHlRI=",
"path": "github.com/google/go-cmp/cmp/internal/diff",
"revision": "c81281657ad99ba22e14fda7c4dfaaf2974c454e",
"revisionTime": "2019-02-28T02:41:37Z"
},
{
"checksumSHA1": "aWCtRt+7QihHSosvSFse9RsjAGY=",
"path": "github.com/google/go-cmp/cmp/internal/function",
"revision": "c81281657ad99ba22e14fda7c4dfaaf2974c454e",
"revisionTime": "2019-02-28T02:41:37Z"
},
{
"checksumSHA1": "FLgIRNmxNGpYuHemKzoSzaPrqiQ=",
"path": "github.com/google/go-cmp/cmp/internal/value",
"revision": "c81281657ad99ba22e14fda7c4dfaaf2974c454e",
"revisionTime": "2019-02-28T02:41:37Z"
},
{
"checksumSHA1": "d9PxF1XQGLMJZRct2R8qVM/eYlE=",
"path": "github.com/hashicorp/golang-lru",