Skip to content

Commit

Permalink
Add support for Table in unit test framework
Browse files Browse the repository at this point in the history
The "mock" framework provides a base starting set
of support APIs such as Get/Put/Delete state and
Range for unit testing chaincodes. As tables are used
extensively, this change enhances the framework with
table support.

This addresses https://jira.hyperledger.org/browse/FAB-345

Change-Id: I3bb01e131b370d2e5140d73e022a5a519a4677f2
Signed-off-by: Srinivasan Muralidharan <muralisr@us.ibm.com>
  • Loading branch information
Srinivasan Muralidharan committed Sep 12, 2016
1 parent ce733d2 commit 37837fd
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 27 deletions.
39 changes: 29 additions & 10 deletions core/chaincode/shim/chaincode.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,11 @@ var (

// CreateTable creates a new table given the table name and column definitions
func (stub *ChaincodeStub) CreateTable(name string, columnDefinitions []*ColumnDefinition) error {
return createTableInternal(stub, name, columnDefinitions)
}

_, err := stub.getTable(name)
func createTableInternal(stub ChaincodeStubInterface, name string, columnDefinitions []*ColumnDefinition) error {
_, err := getTable(stub, name)
if err == nil {
return fmt.Errorf("CreateTable operation failed. Table %s already exists.", name)
}
Expand Down Expand Up @@ -490,11 +493,15 @@ func (stub *ChaincodeStub) CreateTable(name string, columnDefinitions []*ColumnD
// GetTable returns the table for the specified table name or ErrTableNotFound
// if the table does not exist.
func (stub *ChaincodeStub) GetTable(tableName string) (*Table, error) {
return stub.getTable(tableName)
return getTable(stub, tableName)
}

// DeleteTable deletes an entire table and all associated rows.
func (stub *ChaincodeStub) DeleteTable(tableName string) error {
return deleteTableInternal(stub, tableName)
}

func deleteTableInternal(stub ChaincodeStubInterface, tableName string) error {
tableNameKey, err := getTableNameKey(tableName)
if err != nil {
return err
Expand Down Expand Up @@ -527,7 +534,7 @@ func (stub *ChaincodeStub) DeleteTable(tableName string) error {
// false and a TableNotFoundError if the specified table name does not exist.
// false and an error if there is an unexpected error condition.
func (stub *ChaincodeStub) InsertRow(tableName string, row Row) (bool, error) {
return stub.insertRowInternal(tableName, row, false)
return insertRowInternal(stub, tableName, row, false)
}

// ReplaceRow updates the row in the specified table.
Expand All @@ -537,11 +544,15 @@ func (stub *ChaincodeStub) InsertRow(tableName string, row Row) (bool, error) {
// flase and a TableNotFoundError if the specified table name does not exist.
// false and an error if there is an unexpected error condition.
func (stub *ChaincodeStub) ReplaceRow(tableName string, row Row) (bool, error) {
return stub.insertRowInternal(tableName, row, true)
return insertRowInternal(stub, tableName, row, true)
}

// GetRow fetches a row from the specified table for the given key.
func (stub *ChaincodeStub) GetRow(tableName string, key []Column) (Row, error) {
return getRowInternal(stub, tableName, key)
}

func getRowInternal(stub ChaincodeStubInterface, tableName string, key []Column) (Row, error) {

var row Row

Expand Down Expand Up @@ -571,13 +582,17 @@ func (stub *ChaincodeStub) GetRow(tableName string, key []Column) (Row, error) {
// also be called with A only to return all rows that have A and any value
// for C and D as their key.
func (stub *ChaincodeStub) GetRows(tableName string, key []Column) (<-chan Row, error) {
return getRowsInternal(stub, tableName, key)
}

func getRowsInternal(stub ChaincodeStubInterface, tableName string, key []Column) (<-chan Row, error) {

keyString, err := buildKeyString(tableName, key)
if err != nil {
return nil, err
}

table, err := stub.getTable(tableName)
table, err := getTable(stub, tableName)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -630,6 +645,10 @@ func (stub *ChaincodeStub) GetRows(tableName string, key []Column) (<-chan Row,

// DeleteRow deletes the row for the given key from the specified table.
func (stub *ChaincodeStub) DeleteRow(tableName string, key []Column) error {
return deleteRowInternal(stub, tableName, key)
}

func deleteRowInternal(stub ChaincodeStubInterface, tableName string, key []Column) error {

keyString, err := buildKeyString(tableName, key)
if err != nil {
Expand Down Expand Up @@ -682,7 +701,7 @@ func (stub *ChaincodeStub) GetTxTimestamp() (*gp.Timestamp, error) {
return stub.securityContext.TxTimestamp, nil
}

func (stub *ChaincodeStub) getTable(tableName string) (*Table, error) {
func getTable(stub ChaincodeStubInterface, tableName string) (*Table, error) {

tableName, err := getTableNameKey(tableName)
if err != nil {
Expand Down Expand Up @@ -808,7 +827,7 @@ func getKeyAndVerifyRow(table Table, row Row) ([]Column, error) {
return keys, nil
}

func (stub *ChaincodeStub) isRowPresent(tableName string, key []Column) (bool, error) {
func isRowPresent(stub ChaincodeStubInterface, tableName string, key []Column) (bool, error) {
keyString, err := buildKeyString(tableName, key)
if err != nil {
return false, err
Expand All @@ -829,9 +848,9 @@ func (stub *ChaincodeStub) isRowPresent(tableName string, key []Column) (bool, e
// false and no error if a row already exists for the given key.
// false and a TableNotFoundError if the specified table name does not exist.
// false and an error if there is an unexpected error condition.
func (stub *ChaincodeStub) insertRowInternal(tableName string, row Row, update bool) (bool, error) {
func insertRowInternal(stub ChaincodeStubInterface, tableName string, row Row, update bool) (bool, error) {

table, err := stub.getTable(tableName)
table, err := getTable(stub, tableName)
if err != nil {
return false, err
}
Expand All @@ -841,7 +860,7 @@ func (stub *ChaincodeStub) insertRowInternal(tableName string, row Row, update b
return false, err
}

present, err := stub.isRowPresent(tableName, key)
present, err := isRowPresent(stub, tableName, key)
if err != nil {
return false, err
}
Expand Down
49 changes: 32 additions & 17 deletions core/chaincode/shim/mockstub.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,45 +196,60 @@ func (stub *MockStub) RangeQueryState(startKey, endKey string) (StateRangeQueryI
return NewMockStateRangeQueryIterator(stub, startKey, endKey), nil
}

// Not implemented
// CreateTable creates a new table given the table name and column definitions
func (stub *MockStub) CreateTable(name string, columnDefinitions []*ColumnDefinition) error {
return nil
return createTableInternal(stub, name, columnDefinitions)
}

// Not implemented
// GetTable returns the table for the specified table name or ErrTableNotFound
// if the table does not exist.
func (stub *MockStub) GetTable(tableName string) (*Table, error) {
return nil, nil
return getTable(stub, tableName)
}

// Not implemented
// DeleteTable deletes an entire table and all associated rows.
func (stub *MockStub) DeleteTable(tableName string) error {
return nil
return deleteTableInternal(stub, tableName)
}

// Not implemented
// InsertRow inserts a new row into the specified table.
// Returns -
// true and no error if the row is successfully inserted.
// false and no error if a row already exists for the given key.
// false and a TableNotFoundError if the specified table name does not exist.
// false and an error if there is an unexpected error condition.
func (stub *MockStub) InsertRow(tableName string, row Row) (bool, error) {
return false, nil
return insertRowInternal(stub, tableName, row, false)
}

// Not implemented
// ReplaceRow updates the row in the specified table.
// Returns -
// true and no error if the row is successfully updated.
// false and no error if a row does not exist the given key.
// flase and a TableNotFoundError if the specified table name does not exist.
// false and an error if there is an unexpected error condition.
func (stub *MockStub) ReplaceRow(tableName string, row Row) (bool, error) {
return false, nil
return insertRowInternal(stub, tableName, row, true)
}

// Not implemented
// GetRow fetches a row from the specified table for the given key.
func (stub *MockStub) GetRow(tableName string, key []Column) (Row, error) {
var r Row
return r, nil
return getRowInternal(stub, tableName, key)
}

// Not implemented
// GetRows returns multiple rows based on a partial key. For example, given table
// | A | B | C | D |
// where A, C and D are keys, GetRows can be called with [A, C] to return
// all rows that have A, C and any value for D as their key. GetRows could
// also be called with A only to return all rows that have A and any value
// for C and D as their key.
func (stub *MockStub) GetRows(tableName string, key []Column) (<-chan Row, error) {
return nil, nil
return getRowsInternal(stub, tableName, key)
}

// Not implemented
// DeleteRow deletes the row for the given key from the specified table.
func (stub *MockStub) DeleteRow(tableName string, key []Column) error {
return nil
return deleteRowInternal(stub, tableName, key)
}

// Invokes a peered chaincode.
Expand Down
124 changes: 124 additions & 0 deletions core/chaincode/shim/mockstub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,88 @@ limitations under the License.
package shim

import (
"errors"
"fmt"
"testing"
)

func createTable(stub ChaincodeStubInterface) error {
// Create table one
var columnDefsTableOne []*ColumnDefinition
columnOneTableOneDef := ColumnDefinition{Name: "colOneTableOne",
Type: ColumnDefinition_STRING, Key: true}
columnTwoTableOneDef := ColumnDefinition{Name: "colTwoTableOne",
Type: ColumnDefinition_INT32, Key: false}
columnThreeTableOneDef := ColumnDefinition{Name: "colThreeTableOne",
Type: ColumnDefinition_INT32, Key: false}
columnDefsTableOne = append(columnDefsTableOne, &columnOneTableOneDef)
columnDefsTableOne = append(columnDefsTableOne, &columnTwoTableOneDef)
columnDefsTableOne = append(columnDefsTableOne, &columnThreeTableOneDef)
return stub.CreateTable("tableOne", columnDefsTableOne)
}

func insertRow(stub ChaincodeStubInterface, col1Val string, col2Val int32, col3Val int32) error {
var columns []*Column
col1 := Column{Value: &Column_String_{String_: col1Val}}
col2 := Column{Value: &Column_Int32{Int32: col2Val}}
col3 := Column{Value: &Column_Int32{Int32: col3Val}}
columns = append(columns, &col1)
columns = append(columns, &col2)
columns = append(columns, &col3)

row := Row{Columns: columns}
ok, err := stub.InsertRow("tableOne", row)
if err != nil {
return fmt.Errorf("insertTableOne operation failed. %s", err)
}
if !ok {
return errors.New("insertTableOne operation failed. Row with given key already exists")
}
return nil
}

func getRow(stub ChaincodeStubInterface, col1Val string) (Row, error) {
var columns []Column
col1 := Column{Value: &Column_String_{String_: col1Val}}
columns = append(columns, col1)

row, err := stub.GetRow("tableOne", columns)
if err != nil {
return row, fmt.Errorf("getRowTableOne operation failed. %s", err)
}

return row, nil
}

func getRows(stub ChaincodeStubInterface, col1Val string) ([]Row, error) {
var columns []Column

col1 := Column{Value: &Column_String_{String_: col1Val}}
columns = append(columns, col1)

rowChannel, err := stub.GetRows("tableOne", columns)
if err != nil {
return nil, fmt.Errorf("getRows operation failed. %s", err)
}

var rows []Row
for {
select {
case row, ok := <-rowChannel:
if !ok {
rowChannel = nil
} else {
rows = append(rows, row)
}
}
if rowChannel == nil {
break
}
}

return rows, nil
}

func TestMockStateRangeQueryIterator(t *testing.T) {
stub := NewMockStub("rangeTest", nil)
stub.MockTransactionStart("init")
Expand Down Expand Up @@ -50,3 +128,49 @@ func TestMockStateRangeQueryIterator(t *testing.T) {
}
}
}

func TestMockTable(t *testing.T) {
stub := NewMockStub("CreateTable", nil)
stub.MockTransactionStart("init")

//create a table
if err := createTable(stub); err != nil {
t.FailNow()
}

type rowType struct {
col1 string
col2 int32
col3 int32
}

//add some rows
rows := []rowType{{"one", 1, 11}, {"two", 2, 22}, {"three", 3, 33}}
for _, r := range rows {
if err := insertRow(stub, r.col1, r.col2, r.col3); err != nil {
t.FailNow()
}
}

//get one row
if r, err := getRow(stub, "one"); err != nil {
t.FailNow()
} else if len(r.Columns) != 3 || r.Columns[0].GetString_() != "one" || r.Columns[1].GetInt32() != 1 || r.Columns[2].GetInt32() != 11 {
t.FailNow()
}

/** we know GetRows is buggy and need to be fixed. Enable this test
* when it is
//get all rows
if rs,err := getRows(stub,"one"); err != nil {
fmt.Printf("getRows err %s\n", err)
t.FailNow()
} else if len(rs) != 1 {
fmt.Printf("getRows returned len %d(expected 1)\n", len(rs))
t.FailNow()
} else if len(rs[0].Columns) != 3 || rs[0].Columns[0].GetString_() != "one" || rs[0].Columns[1].GetInt32() != 1 || rs[0].Columns[2].GetInt32() != 11 {
fmt.Printf("getRows invaid row %v\n", rs[0])
t.FailNow()
}
***/
}

0 comments on commit 37837fd

Please sign in to comment.