From 4dc372e18bd2590b3d61f1394155ab72a2c484e9 Mon Sep 17 00:00:00 2001 From: Jason Yellick Date: Tue, 6 Dec 2016 16:45:36 -0500 Subject: [PATCH] Mock testing infrastructure enhancements One known piece of common mock infrastructure is the blockcutter.Receiver and multichain.ConsenterSupport used in both Solo, and Kafka tests. This changeset factors these structures out of the solo tests and into the mock infrastructure. Because of dependency cycles which were created, it also breaks the mocks package into subpackages per mock type. Change-Id: I924cf18d18ba9e6b1e9853ace105e1c4703ac4aa Signed-off-by: Jason Yellick --- .../common/blockcutter/blockcutter_test.go | 12 +- orderer/mocks/blockcutter/blockcutter.go | 104 ++++++++++++ orderer/mocks/blockcutter/blockcutter_test.go | 27 +++ orderer/mocks/multichain/multichain.go | 59 +++++++ orderer/mocks/multichain/multichain_test.go | 27 +++ .../mocks/{ => sharedconfig}/sharedconfig.go | 19 +-- .../{ => sharedconfig}/sharedconfig_test.go | 4 +- orderer/solo/consensus_test.go | 158 +++++------------- 8 files changed, 273 insertions(+), 137 deletions(-) create mode 100644 orderer/mocks/blockcutter/blockcutter.go create mode 100644 orderer/mocks/blockcutter/blockcutter_test.go create mode 100644 orderer/mocks/multichain/multichain.go create mode 100644 orderer/mocks/multichain/multichain_test.go rename orderer/mocks/{ => sharedconfig}/sharedconfig.go (63%) rename orderer/mocks/{ => sharedconfig}/sharedconfig_test.go (91%) diff --git a/orderer/common/blockcutter/blockcutter_test.go b/orderer/common/blockcutter/blockcutter_test.go index 54cbec70e35..fd8e153cc18 100644 --- a/orderer/common/blockcutter/blockcutter_test.go +++ b/orderer/common/blockcutter/blockcutter_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/hyperledger/fabric/orderer/common/filter" - "github.com/hyperledger/fabric/orderer/mocks" + mocksharedconfig "github.com/hyperledger/fabric/orderer/mocks/sharedconfig" cb "github.com/hyperledger/fabric/protos/common" ) @@ -74,7 +74,7 @@ var unmatchedTx = &cb.Envelope{Payload: []byte("UNMATCHED")} func TestNormalBatch(t *testing.T) { filters := getFilters() batchSize := 2 - r := NewReceiverImpl(&mocks.SharedConfigManager{BatchSizeVal: batchSize}, filters) + r := NewReceiverImpl(&mocksharedconfig.Manager{BatchSizeVal: batchSize}, filters) batches, committers, ok := r.Ordered(goodTx) @@ -101,7 +101,7 @@ func TestNormalBatch(t *testing.T) { func TestBadMessageInBatch(t *testing.T) { filters := getFilters() batchSize := 2 - r := NewReceiverImpl(&mocks.SharedConfigManager{BatchSizeVal: batchSize}, filters) + r := NewReceiverImpl(&mocksharedconfig.Manager{BatchSizeVal: batchSize}, filters) batches, committers, ok := r.Ordered(badTx) @@ -137,7 +137,7 @@ func TestBadMessageInBatch(t *testing.T) { func TestUnmatchedMessageInBatch(t *testing.T) { filters := getFilters() batchSize := 2 - r := NewReceiverImpl(&mocks.SharedConfigManager{BatchSizeVal: batchSize}, filters) + r := NewReceiverImpl(&mocksharedconfig.Manager{BatchSizeVal: batchSize}, filters) batches, committers, ok := r.Ordered(unmatchedTx) @@ -173,7 +173,7 @@ func TestUnmatchedMessageInBatch(t *testing.T) { func TestIsolatedEmptyBatch(t *testing.T) { filters := getFilters() batchSize := 2 - r := NewReceiverImpl(&mocks.SharedConfigManager{BatchSizeVal: batchSize}, filters) + r := NewReceiverImpl(&mocksharedconfig.Manager{BatchSizeVal: batchSize}, filters) batches, committers, ok := r.Ordered(isolatedTx) @@ -197,7 +197,7 @@ func TestIsolatedEmptyBatch(t *testing.T) { func TestIsolatedPartialBatch(t *testing.T) { filters := getFilters() batchSize := 2 - r := NewReceiverImpl(&mocks.SharedConfigManager{BatchSizeVal: batchSize}, filters) + r := NewReceiverImpl(&mocksharedconfig.Manager{BatchSizeVal: batchSize}, filters) batches, committers, ok := r.Ordered(goodTx) diff --git a/orderer/mocks/blockcutter/blockcutter.go b/orderer/mocks/blockcutter/blockcutter.go new file mode 100644 index 00000000000..4435053d479 --- /dev/null +++ b/orderer/mocks/blockcutter/blockcutter.go @@ -0,0 +1,104 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mocks + +import ( + "github.com/hyperledger/fabric/orderer/common/filter" + cb "github.com/hyperledger/fabric/protos/common" +) + +import ( + "github.com/op/go-logging" +) + +var logger = logging.MustGetLogger("orderer/mocks/blockcutter") + +// Receiver mocks the blockcutter.Receiver interface +type Receiver struct { + // QueueNext causes Ordered returns nil false when not set to true + QueueNext bool + + // IsolatedTx causes Ordered returns [][]{curBatch, []{newTx}}, true when set to true + IsolatedTx bool + + // CutNext causes Ordered returns [][]{append(curBatch, newTx)}, true when set to true + CutNext bool + + // CurBatch is the currently outstanding messages in the batch + CurBatch []*cb.Envelope + + // Block is a channel which is read from before returning from Ordered, it is useful for synchronization + // If you do not wish synchronization for whatever reason, simply close the channel + Block chan struct{} +} + +// NewReceiver returns the mock blockcutter.Receiver implemenation +func NewReceiver() *Receiver { + return &Receiver{ + QueueNext: true, + IsolatedTx: false, + CutNext: false, + Block: make(chan struct{}), + } +} + +func noopCommitters(size int) []filter.Committer { + res := make([]filter.Committer, size) + for i := range res { + res[i] = filter.NoopCommitter + } + return res +} + +// Ordered will add or cut the batch according to the state of Receiver, it blocks reading from Block on return +func (mbc *Receiver) Ordered(env *cb.Envelope) ([][]*cb.Envelope, [][]filter.Committer, bool) { + defer func() { + <-mbc.Block + }() + + if !mbc.QueueNext { + logger.Debugf("Not queueing message") + return nil, nil, false + } + + if mbc.IsolatedTx { + logger.Debugf("Receiver: Returning dual batch") + res := [][]*cb.Envelope{mbc.CurBatch, []*cb.Envelope{env}} + mbc.CurBatch = nil + return res, [][]filter.Committer{noopCommitters(len(res[0])), noopCommitters(len(res[1]))}, true + } + + mbc.CurBatch = append(mbc.CurBatch, env) + + if mbc.CutNext { + logger.Debugf("Returning regular batch") + res := [][]*cb.Envelope{mbc.CurBatch} + mbc.CurBatch = nil + return res, [][]filter.Committer{noopCommitters(len(res))}, true + } + + logger.Debugf("Appending to batch") + return nil, nil, true +} + +// Cut terminates the current batch, returning it +func (mbc *Receiver) Cut() ([]*cb.Envelope, []filter.Committer) { + logger.Debugf("Cutting batch") + res := mbc.CurBatch + mbc.CurBatch = nil + return res, noopCommitters(len(res)) +} diff --git a/orderer/mocks/blockcutter/blockcutter_test.go b/orderer/mocks/blockcutter/blockcutter_test.go new file mode 100644 index 00000000000..514e538ec7f --- /dev/null +++ b/orderer/mocks/blockcutter/blockcutter_test.go @@ -0,0 +1,27 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mocks + +import ( + "testing" + + "github.com/hyperledger/fabric/orderer/common/blockcutter" +) + +func TestBlockCutterInterface(t *testing.T) { + _ = blockcutter.Receiver(NewReceiver()) +} diff --git a/orderer/mocks/multichain/multichain.go b/orderer/mocks/multichain/multichain.go new file mode 100644 index 00000000000..11faaef3edc --- /dev/null +++ b/orderer/mocks/multichain/multichain.go @@ -0,0 +1,59 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package multichain + +import ( + "github.com/hyperledger/fabric/orderer/common/blockcutter" + "github.com/hyperledger/fabric/orderer/common/filter" + "github.com/hyperledger/fabric/orderer/common/sharedconfig" + mockblockcutter "github.com/hyperledger/fabric/orderer/mocks/blockcutter" + mocksharedconfig "github.com/hyperledger/fabric/orderer/mocks/sharedconfig" + cb "github.com/hyperledger/fabric/protos/common" + + "github.com/op/go-logging" +) + +var logger = logging.MustGetLogger("orderer/mocks/multichain") + +// ConsenterSupport is used to mock the multichain.ConsenterSupport interface +// Whenever a block is written, it writes to the Batches channel to allow for synchronization +type ConsenterSupport struct { + // SharedConfigVal is the value returned by SharedConfig() + SharedConfigVal *mocksharedconfig.Manager + + // BlockCutterVal is the value returned by BlockCutter() + BlockCutterVal *mockblockcutter.Receiver + + // Batches is the channel which WriteBlock writes data to + Batches chan []*cb.Envelope +} + +// BlockCutter returns BlockCutterVal +func (mcs *ConsenterSupport) BlockCutter() blockcutter.Receiver { + return mcs.BlockCutterVal +} + +// SharedConfig returns SharedConfigVal +func (mcs *ConsenterSupport) SharedConfig() sharedconfig.Manager { + return mcs.SharedConfigVal +} + +// WriteBlock writes data to the Batches channel +func (mcs *ConsenterSupport) WriteBlock(data []*cb.Envelope, metadata [][]byte, committers []filter.Committer) { + logger.Debugf("mockWriter: attempting to write batch") + mcs.Batches <- data +} diff --git a/orderer/mocks/multichain/multichain_test.go b/orderer/mocks/multichain/multichain_test.go new file mode 100644 index 00000000000..c2b14f4beb8 --- /dev/null +++ b/orderer/mocks/multichain/multichain_test.go @@ -0,0 +1,27 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package multichain + +import ( + "testing" + + "github.com/hyperledger/fabric/orderer/multichain" +) + +func TestConsenterSupportInterface(t *testing.T) { + _ = multichain.ConsenterSupport(&ConsenterSupport{}) +} diff --git a/orderer/mocks/sharedconfig.go b/orderer/mocks/sharedconfig/sharedconfig.go similarity index 63% rename from orderer/mocks/sharedconfig.go rename to orderer/mocks/sharedconfig/sharedconfig.go index f23e22d05b6..4349f47bf9e 100644 --- a/orderer/mocks/sharedconfig.go +++ b/orderer/mocks/sharedconfig/sharedconfig.go @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package mocks +package sharedconfig -// MockSharedConfigManager is a mock implementation of sharedconfig.Manager -type SharedConfigManager struct { +// Manager is a mock implementation of sharedconfig.Manager +type Manager struct { // ConsensusTypeVal is returned as the result of ConsensusType() ConsensusTypeVal string // BatchSizeVal is returned as the result of BatchSize() @@ -26,18 +26,17 @@ type SharedConfigManager struct { ChainCreatorsVal []string } -// ConsensusType returns the configured consensus type -func (scm *SharedConfigManager) ConsensusType() string { +// ConsensusType returns the ConsensusTypeVal +func (scm *Manager) ConsensusType() string { return scm.ConsensusTypeVal } -// BatchSize returns the maximum number of messages to include in a block -func (scm *SharedConfigManager) BatchSize() int { +// BatchSize returns the BatchSizeVal +func (scm *Manager) BatchSize() int { return scm.BatchSizeVal } -// ChainCreators returns the policy names which are allowed for chain creation -// This field is only set for the system ordering chain -func (scm *SharedConfigManager) ChainCreators() []string { +// ChainCreators returns the ChainCreatorsVal +func (scm *Manager) ChainCreators() []string { return scm.ChainCreatorsVal } diff --git a/orderer/mocks/sharedconfig_test.go b/orderer/mocks/sharedconfig/sharedconfig_test.go similarity index 91% rename from orderer/mocks/sharedconfig_test.go rename to orderer/mocks/sharedconfig/sharedconfig_test.go index 45ad71ab311..7413945d19d 100644 --- a/orderer/mocks/sharedconfig_test.go +++ b/orderer/mocks/sharedconfig/sharedconfig_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package mocks +package sharedconfig import ( "testing" @@ -23,5 +23,5 @@ import ( ) func TestSharedConfigInterface(t *testing.T) { - _ = sharedconfig.Manager(&SharedConfigManager{}) + _ = sharedconfig.Manager(&Manager{}) } diff --git a/orderer/solo/consensus_test.go b/orderer/solo/consensus_test.go index cbba3e1acc4..1706c9f74e8 100644 --- a/orderer/solo/consensus_test.go +++ b/orderer/solo/consensus_test.go @@ -20,96 +20,16 @@ import ( "testing" "time" - "github.com/hyperledger/fabric/orderer/common/blockcutter" - "github.com/hyperledger/fabric/orderer/common/filter" - "github.com/hyperledger/fabric/orderer/common/sharedconfig" - "github.com/hyperledger/fabric/orderer/multichain" + mockblockcutter "github.com/hyperledger/fabric/orderer/mocks/blockcutter" + mockmultichain "github.com/hyperledger/fabric/orderer/mocks/multichain" cb "github.com/hyperledger/fabric/protos/common" ) -type mockBlockCutter struct { - queueNext bool // Ordered returns nil false when not set to true - isolatedTx bool // Ordered returns [][]{curBatch, []{newTx}}, true when set to true - cutNext bool // Ordered returns [][]{append(curBatch, newTx)}, true when set to true - curBatch []*cb.Envelope - block chan struct{} -} - -func newMockBlockCutter() *mockBlockCutter { - return &mockBlockCutter{ - queueNext: true, - isolatedTx: false, - cutNext: false, - block: make(chan struct{}), - } -} - -func noopCommitters(size int) []filter.Committer { - res := make([]filter.Committer, size) - for i := range res { - res[i] = filter.NoopCommitter - } - return res -} - -func (mbc *mockBlockCutter) Ordered(env *cb.Envelope) ([][]*cb.Envelope, [][]filter.Committer, bool) { - defer func() { - <-mbc.block - }() - - if !mbc.queueNext { - logger.Debugf("mockBlockCutter: Not queueing message") - return nil, nil, false - } - - if mbc.isolatedTx { - logger.Debugf("mockBlockCutter: Returning dual batch") - res := [][]*cb.Envelope{mbc.curBatch, []*cb.Envelope{env}} - mbc.curBatch = nil - return res, [][]filter.Committer{noopCommitters(len(res[0])), noopCommitters(len(res[1]))}, true - } - - mbc.curBatch = append(mbc.curBatch, env) - - if mbc.cutNext { - logger.Debugf("mockBlockCutter: Returning regular batch") - res := [][]*cb.Envelope{mbc.curBatch} - mbc.curBatch = nil - return res, [][]filter.Committer{noopCommitters(len(res))}, true - } - - logger.Debugf("mockBlockCutter: Appending to batch") - return nil, nil, true -} - -func (mbc *mockBlockCutter) Cut() ([]*cb.Envelope, []filter.Committer) { - logger.Debugf("mockBlockCutter: Cutting batch") - res := mbc.curBatch - mbc.curBatch = nil - return res, noopCommitters(len(res)) -} - -type mockConsenterSupport struct { - cutter *mockBlockCutter - batches chan []*cb.Envelope -} - -func (mcs *mockConsenterSupport) BlockCutter() blockcutter.Receiver { - return mcs.cutter -} -func (mcs *mockConsenterSupport) SharedConfig() sharedconfig.Manager { - panic("Unimplemented") -} -func (mcs *mockConsenterSupport) WriteBlock(data []*cb.Envelope, metadata [][]byte, committers []filter.Committer) { - logger.Debugf("mockWriter: attempting to write batch") - mcs.batches <- data -} - var testMessage = &cb.Envelope{Payload: []byte("TEST_MESSAGE")} -func syncQueueMessage(msg *cb.Envelope, chain multichain.Chain, bc *mockBlockCutter) { +func syncQueueMessage(msg *cb.Envelope, chain *chain, bc *mockblockcutter.Receiver) { chain.Enqueue(msg) - bc.block <- struct{}{} + bc.Block <- struct{}{} } type waitableGo struct { @@ -128,74 +48,74 @@ func goWithWait(target func()) *waitableGo { } func TestEmptyBatch(t *testing.T) { - support := &mockConsenterSupport{ - batches: make(chan []*cb.Envelope), - cutter: newMockBlockCutter(), + support := &mockmultichain.ConsenterSupport{ + Batches: make(chan []*cb.Envelope), + BlockCutterVal: mockblockcutter.NewReceiver(), } - defer close(support.cutter.block) + defer close(support.BlockCutterVal.Block) bs := newChain(time.Millisecond, support) wg := goWithWait(bs.main) defer bs.Halt() - syncQueueMessage(testMessage, bs, support.cutter) + syncQueueMessage(testMessage, bs, support.BlockCutterVal) bs.Halt() select { - case <-support.batches: + case <-support.Batches: t.Fatalf("Expected no invocations of Append") case <-wg.done: } } func TestBatchTimer(t *testing.T) { - support := &mockConsenterSupport{ - batches: make(chan []*cb.Envelope), - cutter: newMockBlockCutter(), + support := &mockmultichain.ConsenterSupport{ + Batches: make(chan []*cb.Envelope), + BlockCutterVal: mockblockcutter.NewReceiver(), } - defer close(support.cutter.block) + defer close(support.BlockCutterVal.Block) bs := newChain(time.Millisecond, support) wg := goWithWait(bs.main) defer bs.Halt() - syncQueueMessage(testMessage, bs, support.cutter) + syncQueueMessage(testMessage, bs, support.BlockCutterVal) select { - case <-support.batches: + case <-support.Batches: case <-time.After(time.Second): t.Fatalf("Expected a block to be cut because of batch timer expiration but did not") } - syncQueueMessage(testMessage, bs, support.cutter) + syncQueueMessage(testMessage, bs, support.BlockCutterVal) select { - case <-support.batches: + case <-support.Batches: case <-time.After(time.Second): t.Fatalf("Did not create the second batch, indicating that the timer was not appopriately reset") } bs.Halt() select { - case <-support.batches: + case <-support.Batches: t.Fatalf("Expected no invocations of Append") case <-wg.done: } } func TestBatchTimerHaltOnFilledBatch(t *testing.T) { - support := &mockConsenterSupport{ - batches: make(chan []*cb.Envelope), - cutter: newMockBlockCutter(), + support := &mockmultichain.ConsenterSupport{ + Batches: make(chan []*cb.Envelope), + BlockCutterVal: mockblockcutter.NewReceiver(), } - defer close(support.cutter.block) + defer close(support.BlockCutterVal.Block) bs := newChain(time.Hour, support) wg := goWithWait(bs.main) defer bs.Halt() - syncQueueMessage(testMessage, bs, support.cutter) - support.cutter.cutNext = true - syncQueueMessage(testMessage, bs, support.cutter) + syncQueueMessage(testMessage, bs, support.BlockCutterVal) + support.BlockCutterVal.CutNext = true + syncQueueMessage(testMessage, bs, support.BlockCutterVal) select { - case <-support.batches: + case <-support.Batches: case <-time.After(time.Second): t.Fatalf("Expected a block to be cut because the batch was filled, but did not") } @@ -203,11 +123,11 @@ func TestBatchTimerHaltOnFilledBatch(t *testing.T) { // Change the batch timeout to be near instant, if the timer was not reset, it will still be waiting an hour bs.batchTimeout = time.Millisecond - support.cutter.cutNext = false - syncQueueMessage(testMessage, bs, support.cutter) + support.BlockCutterVal.CutNext = false + syncQueueMessage(testMessage, bs, support.BlockCutterVal) select { - case <-support.batches: + case <-support.Batches: case <-time.After(time.Second): t.Fatalf("Did not create the second batch, indicating that the old timer was still running") } @@ -221,27 +141,27 @@ func TestBatchTimerHaltOnFilledBatch(t *testing.T) { } func TestConfigStyleMultiBatch(t *testing.T) { - support := &mockConsenterSupport{ - batches: make(chan []*cb.Envelope), - cutter: newMockBlockCutter(), + support := &mockmultichain.ConsenterSupport{ + Batches: make(chan []*cb.Envelope), + BlockCutterVal: mockblockcutter.NewReceiver(), } - defer close(support.cutter.block) + defer close(support.BlockCutterVal.Block) bs := newChain(time.Hour, support) wg := goWithWait(bs.main) defer bs.Halt() - syncQueueMessage(testMessage, bs, support.cutter) - support.cutter.isolatedTx = true - syncQueueMessage(testMessage, bs, support.cutter) + syncQueueMessage(testMessage, bs, support.BlockCutterVal) + support.BlockCutterVal.IsolatedTx = true + syncQueueMessage(testMessage, bs, support.BlockCutterVal) select { - case <-support.batches: + case <-support.Batches: case <-time.After(time.Second): t.Fatalf("Expected two blocks to be cut but never got the first") } select { - case <-support.batches: + case <-support.Batches: case <-time.After(time.Second): t.Fatalf("Expected the config type tx to create two blocks, but only go the first") }