Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: Use Sandbox for Testing #360

Merged
merged 17 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .test-env
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Configs for testing repo download:
SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing"
SDK_TESTING_BRANCH="master"
SDK_TESTING_HARNESS="test-harness"

VERBOSE_HARNESS=0

# WARNING: If set to 1, new features will be LOST when downloading the test harness.
# REGARDLESS: modified features are ALWAYS overwritten.
REMOVE_LOCAL_FEATURES=0

# WARNING: Be careful when turning on the next variable.
# In that case you'll need to provide all variables expected by `algorand-sdk-testing`'s `.env`
OVERWRITE_TESTING_ENVIRONMENT=0
25 changes: 21 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
SRCPATH := $(shell pwd)
TEST_SOURCES := $(shell cd $(SRCPATH) && go list ./...)
TEST_SOURCES_NO_CUCUMBER := $(shell cd $(SRCPATH) && go list ./... | grep -v test)
UNIT_TAGS := "$(shell awk '{print $2}' test/unit.tags | paste -s -d, -)"
INTEGRATIONS_TAGS := "$(shell awk '{print $2}' test/integration.tags | paste -s -d, -)"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cucumber tags now reside in:

  • test/unit.tags
  • test/integration.tags

GO_IMAGE := golang:$(subst go,,$(shell go version | cut -d' ' -f 3 | cut -d'.' -f 1,2))-stretch

lint:
golint `go list ./... | grep -v /vendor/`
Expand All @@ -19,13 +22,27 @@ test:

unit:
go test $(TEST_SOURCES_NO_CUCUMBER)
cd test && go test -timeout 0s --godog.strict=true --godog.format=pretty --godog.tags="@unit.sourcemap,@unit.offline,@unit.algod,@unit.indexer,@unit.transactions.keyreg,@unit.rekey,@unit.tealsign,@unit.dryrun,@unit.responses,@unit.applications,@unit.transactions,@unit.indexer.rekey,@unit.responses.messagepack,@unit.responses.231,@unit.responses.messagepack.231,@unit.responses.genesis,@unit.feetest,@unit.indexer.logs,@unit.abijson,@unit.abijson.byname,@unit.transactions.payment,@unit.atomic_transaction_composer,@unit.responses.unlimited_assets,@unit.indexer.ledger_refactoring,@unit.algod.ledger_refactoring,@unit.dryrun.trace.application" --test.v .
cd test && go test -timeout 0s --godog.strict=true --godog.format=pretty --godog.tags=$(UNIT_TAGS) --test.v .

integration:
go test $(TEST_SOURCES_NO_CUCUMBER)
cd test && go test -timeout 0s --godog.strict=true --godog.format=pretty --godog.tags="@algod,@assets,@auction,@kmd,@send,@indexer,@rekey_v1,@send.keyregtxn,@dryrun,@compile,@applications.verified,@indexer.applications,@indexer.231,@abi,@c2c,@compile.sourcemap" --test.v .
cd test && go test -timeout 0s --godog.strict=true --godog.format=pretty --godog.tags=$(INTEGRATIONS_TAGS) --test.v .

display-all-go-steps:
find test -name "*.go" | xargs grep "github.com/cucumber/godog" 2>/dev/null | cut -d: -f1 | sort | uniq | xargs grep -Eo "Step[(].[^\`]+" | awk '{sub(/:Step\(./,":")} 1' | sed -E 's/", [a-zA-Z0-9]+\)//g'

harness:
./test-harness.sh

docker-gosdk-build:
echo "Building docker image from base $(GO_IMAGE)"
docker build -t go-sdk-testing --build-arg GO_IMAGE="$(GO_IMAGE)" -f test/docker/Dockerfile $(shell pwd)

docker-gosdk-run:
docker ps -a
docker run -it --network host go-sdk-testing:latest

docker-test: harness docker-gosdk-build docker-gosdk-run

docker-test:
./test/docker/run_docker.sh

.PHONY: test fmt
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ In `client/v2` the `indexer` package contains a client for the Algorand Indexer

# SDK Development

Run tests with `make docker-test`
Run tests with `make docker-test`. To set up the sandbox-based test harness without standing up the go-algorand docker image use `make harness`.

# Quick Start

Expand Down
62 changes: 62 additions & 0 deletions test-harness.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash

set -euo pipefail

START=$(date "+%s")

THIS=$(basename "$0")
ENV_FILE=".test-env"
TEST_DIR="test"

set -a
source "$ENV_FILE"
set +a

rootdir=$(dirname "$0")
pushd "$rootdir"

## Reset test harness
if [ -d "$SDK_TESTING_HARNESS" ]; then
pushd "$SDK_TESTING_HARNESS"
./scripts/down.sh
popd
rm -rf "$SDK_TESTING_HARNESS"
else
echo "$THIS: directory $SDK_TESTING_HARNESS does not exist - NOOP"
fi

git clone --depth 1 --single-branch --branch "$SDK_TESTING_BRANCH" "$SDK_TESTING_URL" "$SDK_TESTING_HARNESS"


if [[ $OVERWRITE_TESTING_ENVIRONMENT == 1 ]]; then
echo "$THIS: OVERWRITE replaced $SDK_TESTING_HARNESS/.env with $ENV_FILE:"
cp "$ENV_FILE" "$SDK_TESTING_HARNESS"/.env
fi

## Copy feature files into the project resources
if [[ $REMOVE_LOCAL_FEATURES == 1 ]]; then
echo "$THIS: OVERWRITE wipes clean $TEST_DIR/features"
if [[ $VERBOSE_HARNESS == 1 ]]; then
( tree $TEST_DIR/features && echo "$THIS: see the previous for files deleted" ) || true
fi
rm -rf $TEST_DIR/features
fi
mkdir -p $TEST_DIR/features
cp -r "$SDK_TESTING_HARNESS"/features/* $TEST_DIR/features
if [[ $VERBOSE_HARNESS == 1 ]]; then
( tree $TEST_DIR/features && echo "$THIS: see the previous for files copied over" ) || true
fi
echo "$THIS: seconds it took to get to end of cloning and copying: $(($(date "+%s") - START))s"


## Start test harness environment
pushd "$SDK_TESTING_HARNESS"
./scripts/up.sh
popd
echo "$THIS: seconds it took to finish testing sdk's up.sh: $(($(date "+%s") - START))s"
echo ""
echo "--------------------------------------------------------------------------------"
echo "|"
echo "| To run sandbox commands, cd into $SDK_TESTING_HARNESS/.sandbox "
echo "|"
echo "--------------------------------------------------------------------------------"
53 changes: 18 additions & 35 deletions test/algodclientv2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"fmt"

"github.com/algorand/go-algorand-sdk/client/v2/algod"
"github.com/algorand/go-algorand-sdk/client/v2/common/models"
modelsV2 "github.com/algorand/go-algorand-sdk/client/v2/common/models"
"github.com/algorand/go-algorand-sdk/types"

Expand All @@ -20,7 +19,6 @@ func AlgodClientV2Context(s *godog.Suite) {
s.Step(`^we make any Pending Transaction Information call$`, weMakeAnyPendingTransactionInformationCall)
s.Step(`^the parsed Pending Transaction Information response should have sender "([^"]*)"$`, theParsedResponseShouldEqualTheMockResponse)
s.Step(`^we make any Pending Transactions Information call$`, weMakeAnyPendingTransactionsInformationCall)
s.Step(`^the parsed Pending Transactions Information response should have sender "([^"]*)"$`, theParsedResponseShouldEqualTheMockResponse)
s.Step(`^we make any Send Raw Transaction call$`, weMakeAnySendRawTransactionCall)
s.Step(`^the parsed Send Raw Transaction response should have txid "([^"]*)"$`, theParsedResponseShouldEqualTheMockResponse)
s.Step(`^we make any Pending Transactions By Address call$`, weMakeAnyPendingTransactionsByAddressCall)
Expand All @@ -38,11 +36,8 @@ func AlgodClientV2Context(s *godog.Suite) {
s.Step(`^we make any Suggested Transaction Parameters call$`, weMakeAnySuggestedTransactionParametersCall)
s.Step(`^the parsed Suggested Transaction Parameters response should have first round valid of (\d+)$`, theParsedResponseShouldEqualTheMockResponse)
s.Step(`^expect the path used to be "([^"]*)"$`, expectThePathUsedToBe)
s.Step(`^we make a Pending Transaction Information against txid "([^"]*)" with max (\d+)$`, weMakeAPendingTransactionInformationAgainstTxidWithMax)
s.Step(`^we make a Pending Transactions By Address call against account "([^"]*)" and max (\d+)$`, weMakeAPendingTransactionsByAddressCallAgainstAccountAndMax)
s.Step(`^we make a Status after Block call with round (\d+)$`, weMakeAStatusAfterBlockCallWithRound)
s.Step(`^we make an Account Information call against account "([^"]*)"$`, weMakeAnAccountInformationCallAgainstAccount)
s.Step(`^we make a Get Block call against block number (\d+)$`, weMakeAGetBlockCallAgainstBlockNumber)
s.Step(`^the parsed Pending Transactions Information response should contain an array of len (\d+) and element number (\d+) should have sender "([^"]*)"$`, theParsedResponseShouldEqualTheMockResponse)
s.Step(`^we make a Pending Transaction Information against txid "([^"]*)" with format "([^"]*)"$`, weMakeAPendingTransactionInformationAgainstTxidWithFormat)
s.Step(`^we make a Pending Transaction Information with max (\d+) and format "([^"]*)"$`, weMakeAPendingTransactionInformationWithMaxAndFormat)
Expand Down Expand Up @@ -120,24 +115,6 @@ func weMakeAnySuggestedTransactionParametersCall() error {
return weMakeAnyCallTo("algod", "TransactionParams")
}

func weMakeAPendingTransactionInformationAgainstTxidWithMax(txid string, max int) error {
algodClient, err := algod.MakeClient(mockServer.URL, "")
if err != nil {
return err
}
_, _, globalErrForExamination = algodClient.PendingTransactionInformation(txid).Do(context.Background())
return nil
}

func weMakeAPendingTransactionsByAddressCallAgainstAccountAndMax(account string, max int) error {
algodClient, err := algod.MakeClient(mockServer.URL, "")
if err != nil {
return err
}
_, _, globalErrForExamination = algodClient.PendingTransactionsByAddress(account).Max(uint64(max)).Do(context.Background())
return nil
}

func weMakeAStatusAfterBlockCallWithRound(round int) error {
algodClient, err := algod.MakeClient(mockServer.URL, "")
if err != nil {
Expand All @@ -156,22 +133,18 @@ func weMakeAnAccountInformationCallAgainstAccount(account string) error {
return nil
}

func weMakeAGetBlockCallAgainstBlockNumber(blocknum int) error {
func weMakeAPendingTransactionInformationAgainstTxidWithFormat(txid, format string) error {
if format != "msgpack" {
return fmt.Errorf("this sdk does not support format %s", format)
}
algodClient, err := algod.MakeClient(mockServer.URL, "")
if err != nil {
return err
}
_, globalErrForExamination = algodClient.Block(uint64(blocknum)).Do(context.Background())
_, _, globalErrForExamination = algodClient.PendingTransactionInformation(txid).Do(context.Background())
return nil
}

func weMakeAPendingTransactionInformationAgainstTxidWithFormat(txid, format string) error {
if format != "msgpack" {
return fmt.Errorf("this sdk does not support format %s", format)
}
return weMakeAPendingTransactionInformationAgainstTxidWithMax(txid, 0)
}

func weMakeAPendingTransactionInformationWithMaxAndFormat(max int, format string) error {
if format != "msgpack" {
return fmt.Errorf("this sdk does not support format %s", format)
Expand All @@ -188,17 +161,27 @@ func weMakeAPendingTransactionsByAddressCallAgainstAccountAndMaxAndFormat(accoun
if format != "msgpack" {
return fmt.Errorf("this sdk does not support format %s", format)
}
return weMakeAPendingTransactionsByAddressCallAgainstAccountAndMax(account, max)
algodClient, err := algod.MakeClient(mockServer.URL, "")
if err != nil {
return err
}
_, _, globalErrForExamination = algodClient.PendingTransactionsByAddress(account).Max(uint64(max)).Do(context.Background())
return nil
}

func weMakeAGetBlockCallAgainstBlockNumberWithFormat(blocknum int, format string) error {
if format != "msgpack" {
return fmt.Errorf("this sdk does not support format %s", format)
}
return weMakeAGetBlockCallAgainstBlockNumber(blocknum)
algodClient, err := algod.MakeClient(mockServer.URL, "")
if err != nil {
return err
}
_, globalErrForExamination = algodClient.Block(uint64(blocknum)).Do(context.Background())
return nil
}

var dryrunResponse models.DryrunResponse
var dryrunResponse modelsV2.DryrunResponse

func weMakeAnyDryrunCall() (err error) {
algodClient, err := algod.MakeClient(mockServer.URL, "")
Expand Down
120 changes: 2 additions & 118 deletions test/applications_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"regexp"
"strconv"
"strings"
"time"

"github.com/cucumber/godog"

Expand Down Expand Up @@ -211,7 +210,7 @@ func iBuildAnApplicationTransaction(
}

case "call":
tx, err = future.MakeApplicationCallTx(applicationId, args, accs,
tx, _ = future.MakeApplicationCallTx(applicationId, args, accs,
fApp, fAssets, types.NoOpOC, approvalP, clearP, gSchema, lSchema,
suggestedParams, transientAccount.Address, nil, types.Digest{}, [32]byte{}, types.Address{})
case "optin":
Expand Down Expand Up @@ -448,124 +447,11 @@ func theTransientAccountShouldHave(appCreated string, byteSlices, uints int,
}
}
if !found {
fmt.Errorf("Could not find key '%s'", key)
return fmt.Errorf("Could not find key '%s'", key)
}
return nil
}

func theUnconfirmedPendingTransactionByIDShouldHaveNoApplyDataFields() error {
status, _, err := algodV2client.PendingTransactionInformation(txid).Do(context.Background())
if err != nil {
return err
}
if status.ConfirmedRound == 0 {
if len(status.GlobalStateDelta) != 0 {
return fmt.Errorf("unexpected global state delta, there should be none: %v", status.GlobalStateDelta)
}
if len(status.LocalStateDelta) != 0 {
return fmt.Errorf("unexpected local state delta, there should be none: %v", status.LocalStateDelta)
}
}
return nil
}

func getAccountDelta(addr string, data []models.AccountStateDelta) []models.EvalDeltaKeyValue {
for _, v := range data {
if v.Address == addr {
return v.Delta
}
}
return nil
}
func theConfirmedPendingTransactionByIDShouldHaveAStateChangeForToIndexerShouldAlsoConfirmThis(stateLocation, key, newValue string, indexer int) error {
status, _, err := algodV2client.PendingTransactionInformation(txid).Do(context.Background())
if err != nil {
return err
}

c1 := make(chan models.Transaction, 1)

go func() {
for true {
indexerResponse, _ := indexerClients[indexer].SearchForTransactions().TXID(txid).Do(context.Background())
if len(indexerResponse.Transactions) == 1 {
c1 <- indexerResponse.Transactions[0]
}
time.Sleep(time.Second)
}
}()

var indexerTx models.Transaction
select {
case res := <-c1:
indexerTx = res
case <-time.After(5 * time.Second):
return fmt.Errorf("timeout waiting for indexer trasaction")
}

var algodKeyValues []models.EvalDeltaKeyValue
var indexerKeyValues []models.EvalDeltaKeyValue

switch stateLocation {
case "local":
addr := indexerTx.Sender
algodKeyValues = getAccountDelta(addr, status.LocalStateDelta)
indexerKeyValues = getAccountDelta(addr, indexerTx.LocalStateDelta)
case "global":
algodKeyValues = status.GlobalStateDelta
indexerKeyValues = indexerTx.GlobalStateDelta
default:
return fmt.Errorf("unknown location: " + stateLocation)
}

// algod
if len(algodKeyValues) != 1 {
return fmt.Errorf("expected 1 key value, found: %d", len(algodKeyValues))
}
if algodKeyValues[0].Key != key {
return fmt.Errorf("wrong key in algod: %s != %s", algodKeyValues[0].Key, key)
}

// indexer
if len(indexerKeyValues) != 1 {
return fmt.Errorf("expected 1 key value, found: %d", len(indexerKeyValues))
}
if indexerKeyValues[0].Key != key {
return fmt.Errorf("wrong key in indexer: %s != %s", indexerKeyValues[0].Key, key)
}

if indexerKeyValues[0].Value.Action != algodKeyValues[0].Value.Action {
return fmt.Errorf("action mismatch between algod and indexer")
}

switch algodKeyValues[0].Value.Action {
case uint64(1):
// bytes
if algodKeyValues[0].Value.Bytes != newValue {
return fmt.Errorf("algod value mismatch: %s != %s", algodKeyValues[0].Value.Bytes, newValue)
}
if indexerKeyValues[0].Value.Bytes != newValue {
return fmt.Errorf("indexer value mismatch: %s != %s", indexerKeyValues[0].Value.Bytes, newValue)
}
case uint64(2):
// int
newValueInt, err := strconv.ParseUint(newValue, 10, 64)
if err != nil {
return fmt.Errorf("problem parsing new int value: %s", newValue)
}

if algodKeyValues[0].Value.Uint != newValueInt {
return fmt.Errorf("algod value mismatch: %d != %s", algodKeyValues[0].Value.Uint, newValue)
}
if indexerKeyValues[0].Value.Uint != newValueInt {
return fmt.Errorf("indexer value mismatch: %d != %s", indexerKeyValues[0].Value.Uint, newValue)
}
default:
return fmt.Errorf("unexpected action: %d", algodKeyValues[0].Value.Action)
}

return nil
}

func suggestedParamsAlgodV2() error {
var err error
Expand Down Expand Up @@ -883,8 +769,6 @@ func ApplicationsContext(s *godog.Suite) {
s.Step(`^I get the account address for the current application and see that it matches the app id\'s hash$`, iGetTheAccountAddressForTheCurrentApp)
s.Step(`^The transient account should have the created app "([^"]*)" and total schema byte-slices (\d+) and uints (\d+), the application "([^"]*)" state contains key "([^"]*)" with value "([^"]*)"$`,
theTransientAccountShouldHave)
s.Step(`^the unconfirmed pending transaction by ID should have no apply data fields\.$`, theUnconfirmedPendingTransactionByIDShouldHaveNoApplyDataFields)
s.Step(`^the confirmed pending transaction by ID should have a "([^"]*)" state change for "([^"]*)" to "([^"]*)", indexer (\d+) should also confirm this\.$`, theConfirmedPendingTransactionByIDShouldHaveAStateChangeForToIndexerShouldAlsoConfirmThis)
s.Step(`^suggested transaction parameters from the algod v2 client$`, suggestedParamsAlgodV2)
s.Step(`^I add the current transaction with signer to the composer\.$`, iAddTheCurrentTransactionWithSignerToTheComposer)
s.Step(`^I clone the composer\.$`, iCloneTheComposer)
Expand Down
Loading