-
Notifications
You must be signed in to change notification settings - Fork 8.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ported BDD implementations to golang using Godog.
Godog is the official Cucumber BDD framework for Golang, which is the standard that our current behave (python) tests are implemented. In this manner, we reuse our existing feature files and can implement BDD tests in both python and golang. The Jira issue is in https://jira.hyperledger.org/browse/FAB-448 Change-Id: If95051670a0f4357f56b12216cd42f31b1402148 Signed-off-by: jeffgarratt <garratt.jeff@gmail.com>
- Loading branch information
1 parent
6d78968
commit ea9f840
Showing
60 changed files
with
10,932 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,6 @@ tags=~@issue_767 | |
~@issue_1565 | ||
~@issue_RBAC_TCERT_With_Attributes | ||
~@sdk | ||
~@preV1 | ||
|
||
~@FAB-314 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
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 bddtests | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/golang/protobuf/proto" | ||
"github.com/hyperledger/fabric/core/util" | ||
pb "github.com/hyperledger/fabric/protos" | ||
) | ||
|
||
func createChaincodeSpec(ccType string, path string, args [][]byte) *pb.ChaincodeSpec { | ||
// make chaincode spec for chaincode to be deployed | ||
ccSpec := &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value[ccType]), | ||
ChaincodeID: &pb.ChaincodeID{Path: path}, | ||
CtorMsg: &pb.ChaincodeInput{Args: args}} | ||
return ccSpec | ||
|
||
} | ||
|
||
func createPropsalID() string { | ||
return util.GenerateUUID() | ||
} | ||
|
||
// createChaincodeDeploymentSpec Returns a deployment proposal of chaincode type | ||
func createProposalForChaincode(ccChaincodeDeploymentSpec *pb.ChaincodeDeploymentSpec) (proposal *pb.Proposal, err error) { | ||
var ccDeploymentSpecBytes []byte | ||
if ccDeploymentSpecBytes, err = proto.Marshal(ccChaincodeDeploymentSpec); err != nil { | ||
return nil, fmt.Errorf("Error creating proposal from ChaincodeDeploymentSpec: %s", err) | ||
} | ||
lcChaincodeSpec := &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_GOLANG, | ||
ChaincodeID: &pb.ChaincodeID{Name: "lccc"}, | ||
CtorMsg: &pb.ChaincodeInput{Args: [][]byte{[]byte("deploy"), []byte("default"), ccDeploymentSpecBytes}}} | ||
lcChaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: lcChaincodeSpec} | ||
var ccLifecycleChaincodeInvocationSpecBytes []byte | ||
if ccLifecycleChaincodeInvocationSpecBytes, err = proto.Marshal(lcChaincodeInvocationSpec); err != nil { | ||
return nil, fmt.Errorf("Error creating proposal from ChaincodeDeploymentSpec: %s", err) | ||
} | ||
// make proposal | ||
proposal = &pb.Proposal{Type: pb.Proposal_CHAINCODE, Id: createPropsalID(), Payload: ccLifecycleChaincodeInvocationSpecBytes} | ||
return proposal, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
/* | ||
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 bddtests | ||
|
||
import ( | ||
"fmt" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/fsouza/go-dockerclient" | ||
) | ||
|
||
const dockerComposeCommand = "docker-compose" | ||
|
||
// Composition represents a docker-compose execution and management | ||
type Composition struct { | ||
endpoint string | ||
dockerClient *docker.Client | ||
apiContainers []docker.APIContainers | ||
|
||
composeFilesYaml string | ||
projectName string | ||
dockerHelper DockerHelper | ||
} | ||
|
||
// NewComposition create a new Composition specifying the project name (for isolation) and the compose files. | ||
func NewComposition(projectName string, composeFilesYaml string) (composition *Composition, err error) { | ||
errRetFunc := func() error { | ||
return fmt.Errorf("Error creating new composition '%s' using compose yaml '%s': %s", projectName, composeFilesYaml, err) | ||
} | ||
|
||
endpoint := "unix:///var/run/docker.sock" | ||
composition = &Composition{composeFilesYaml: composeFilesYaml, projectName: projectName} | ||
if composition.dockerClient, err = docker.NewClient(endpoint); err != nil { | ||
return nil, errRetFunc() | ||
} | ||
if _, err = composition.issueCommand([]string{"up", "--force-recreate", "-d"}); err != nil { | ||
return nil, errRetFunc() | ||
} | ||
if composition.dockerHelper, err = NewDockerCmdlineHelper(); err != nil { | ||
return nil, errRetFunc() | ||
} | ||
// Now parse the current system | ||
return composition, nil | ||
} | ||
|
||
func parseComposeFilesArg(composeFileArgs string) []string { | ||
var args []string | ||
for _, f := range strings.Fields(composeFileArgs) { | ||
args = append(args, []string{"-f", f}...) | ||
} | ||
return args | ||
} | ||
|
||
func (c *Composition) getFileArgs() []string { | ||
return parseComposeFilesArg(c.composeFilesYaml) | ||
} | ||
|
||
// GetContainerIDs returns the container IDs for the composition (NOTE: does NOT include those defined outside composition, eg. chaincode containers) | ||
func (c *Composition) GetContainerIDs() (containerIDs []string, err error) { | ||
var cmdOutput string | ||
if cmdOutput, err = c.issueCommand([]string{"ps", "-q"}); err != nil { | ||
return nil, fmt.Errorf("Error getting container IDs for project '%s': %s", c.projectName, err) | ||
} | ||
containerIDs = splitDockerCommandResults(cmdOutput) | ||
return containerIDs, err | ||
} | ||
|
||
func (c *Composition) getEnv() []string { | ||
return []string{"COMPOSE_PROJECT_NAME=" + c.projectName, "CORE_PEER_NETWORKID=" + c.projectName} | ||
} | ||
|
||
func (c *Composition) refreshContainerList() (err error) { | ||
var allAPIContainers []docker.APIContainers | ||
var thisProjectsContainers []docker.APIContainers | ||
if thisProjectsContainers, err = c.dockerClient.ListContainers(docker.ListContainersOptions{All: true, Filters: map[string][]string{"name": {c.projectName}}}); err != nil { | ||
return fmt.Errorf("Error refreshing container list for project '%s': %s", c.projectName, err) | ||
} | ||
//if allApiContainers, err = c.dockerClient.ListContainers(docker.ListContainersOptions{All: true}); err != nil { | ||
// return fmt.Errorf("Error refreshing container list for project '%s': %s", c.projectName, err) | ||
//} | ||
for _, apiContainer := range allAPIContainers { | ||
if composeService, ok := apiContainer.Labels["com.docker.compose.service"]; ok == true { | ||
fmt.Println(fmt.Sprintf("Container name: %s, composeService: %s, IPAddress: %s", apiContainer.Names[0], composeService, apiContainer.Networks.Networks["bridge"].IPAddress)) | ||
} | ||
} | ||
c.apiContainers = thisProjectsContainers | ||
return err | ||
} | ||
|
||
func (c *Composition) issueCommand(args []string) (_ string, err error) { | ||
errRetFunc := func() error { | ||
return fmt.Errorf("Error issuing command to docker-compose with args '%s': %s", args, err) | ||
} | ||
var cmdArgs []string | ||
cmdArgs = append(cmdArgs, c.getFileArgs()...) | ||
cmdArgs = append(cmdArgs, args...) | ||
var cmdOut []byte | ||
cmd := exec.Command(dockerComposeCommand, cmdArgs...) | ||
cmd.Env = append(cmd.Env, c.getEnv()...) | ||
if cmdOut, err = cmd.CombinedOutput(); err != nil { | ||
return string(cmdOut), errRetFunc() | ||
} | ||
|
||
// Reparse Container list | ||
if err = c.refreshContainerList(); err != nil { | ||
return "", errRetFunc() | ||
} | ||
return string(cmdOut), err | ||
} | ||
|
||
// Decompose decompose the composition. Will also remove any containers with the same projectName prefix (eg. chaincode containers) | ||
func (c *Composition) Decompose() (output string, err error) { | ||
//var containers []string | ||
output, err = c.issueCommand([]string{"stop"}) | ||
output, err = c.issueCommand([]string{"rm", "-f"}) | ||
// Now remove associated chaincode containers if any | ||
c.dockerHelper.RemoveContainersWithNamePrefix(c.projectName) | ||
return output, err | ||
} | ||
|
||
// parseComposition parses the current docker-compose project from ps command | ||
func (c *Composition) parseComposition() (err error) { | ||
//c.issueCommand() | ||
return nil | ||
} | ||
|
||
// GetAPIContainerForComposeService return the docker.APIContainers with the supplied composeService name. | ||
func (c *Composition) GetAPIContainerForComposeService(composeService string) (apiContainer *docker.APIContainers, err error) { | ||
for _, apiContainer := range c.apiContainers { | ||
if currComposeService, ok := apiContainer.Labels["com.docker.compose.service"]; ok == true { | ||
if currComposeService == composeService { | ||
return &apiContainer, nil | ||
} | ||
} | ||
} | ||
return nil, fmt.Errorf("Could not find container with compose service '%s'", composeService) | ||
} | ||
|
||
// GetIPAddressForComposeService returns the IPAddress of the container with the supplied composeService name. | ||
func (c *Composition) GetIPAddressForComposeService(composeService string) (ipAddress string, err error) { | ||
errRetFunc := func() error { | ||
return fmt.Errorf("Error getting IPAddress for compose service '%s': %s", composeService, err) | ||
} | ||
var apiContainer *docker.APIContainers | ||
if apiContainer, err = c.GetAPIContainerForComposeService(composeService); err != nil { | ||
return "", errRetFunc() | ||
} | ||
// Now get the IPAddress | ||
return apiContainer.Networks.Networks["bridge"].IPAddress, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
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 bddtests | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hyperledger/fabric/core/comm" | ||
"google.golang.org/grpc" | ||
) | ||
|
||
// NewGrpcClient return a new GRPC client connection for the specified peer address | ||
func NewGrpcClient(peerAddress string) (*grpc.ClientConn, error) { | ||
var tmpConn *grpc.ClientConn | ||
var err error | ||
if comm.TLSEnabled() { | ||
tmpConn, err = comm.NewClientConnectionWithAddress(peerAddress, true, true, comm.InitTLSForPeer()) | ||
} | ||
tmpConn, err = comm.NewClientConnectionWithAddress(peerAddress, true, false, nil) | ||
if err != nil { | ||
fmt.Printf("error connection to server at host:port = %s\n", peerAddress) | ||
} | ||
return tmpConn, err | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
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 bddtests | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/DATA-DOG/godog" | ||
"github.com/DATA-DOG/godog/gherkin" | ||
) | ||
|
||
// BDDContext represents the current context for the executing scenario. Commensurate concept of 'context' from behave testing. | ||
type BDDContext struct { | ||
grpcClientPort int | ||
composition *Composition | ||
godogSuite *godog.Suite | ||
scenarioOrScenarioOutline interface{} | ||
users map[string]*UserRegistration | ||
} | ||
|
||
func (b *BDDContext) getScenarioDefinition() *gherkin.ScenarioDefinition { | ||
if b.scenarioOrScenarioOutline == nil { | ||
return nil | ||
} | ||
switch t := b.scenarioOrScenarioOutline.(type) { | ||
case *gherkin.Scenario: | ||
return &(t.ScenarioDefinition) | ||
case *gherkin.ScenarioOutline: | ||
return &(t.ScenarioDefinition) | ||
} | ||
return nil | ||
} | ||
|
||
func (b *BDDContext) hasTag(tagName string) bool { | ||
if b.scenarioOrScenarioOutline == nil { | ||
return false | ||
} | ||
hasTagInner := func(tags []*gherkin.Tag) bool { | ||
for _, t := range tags { | ||
if t.Name == tagName { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
switch t := b.scenarioOrScenarioOutline.(type) { | ||
case *gherkin.Scenario: | ||
return hasTagInner(t.Tags) | ||
case *gherkin.ScenarioOutline: | ||
return hasTagInner(t.Tags) | ||
} | ||
return false | ||
} | ||
|
||
// GetArgsForUser will return an arg slice of string allowing for replacement of parameterized values based upon tags for userRegistration | ||
func (b *BDDContext) GetArgsForUser(cells []*gherkin.TableCell, userRegistration *UserRegistration) (args []string, err error) { | ||
regExp := regexp.MustCompile("\\{(.*?)\\}+") | ||
// Loop through cells and replace with user tag values if found | ||
for _, cell := range cells { | ||
var arg = cell.Value | ||
for _, tagNameToFind := range regExp.FindAllStringSubmatch(cell.Value, -1) { | ||
println("toFind = ", tagNameToFind[0], " to replace = ", tagNameToFind[1]) | ||
var tagValue interface{} | ||
tagValue, err = userRegistration.GetTagValue(tagNameToFind[1]) | ||
if err != nil { | ||
return nil, fmt.Errorf("Error getting args for user '%s': %s", userRegistration.enrollID, err) | ||
} | ||
arg = strings.Replace(arg, tagNameToFind[0], fmt.Sprintf("%v", tagValue), 1) | ||
} | ||
args = append(args, arg) | ||
} | ||
return args, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.