Skip to content

Commit

Permalink
Fix storage errors for unit testing
Browse files Browse the repository at this point in the history
In `InterpreterConfig.ContractValueHandler` function, we would override
the interpreter's shared state with a new storage based on emulator's
ledger.
This proved to be problematic, as the new storage would not contain
the composite values needed for testing (e.g. I.Test and I.Crypto).
To fix this, we simply store every slab from the new storage, to the
current environment's storage.
  • Loading branch information
m-Peter committed Oct 23, 2023
1 parent cf23796 commit ca130a0
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 13 deletions.
2 changes: 1 addition & 1 deletion test/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/onflow/cadence-tools/test
go 1.18

require (
github.com/onflow/atree v0.6.0
github.com/onflow/cadence v0.42.0
github.com/onflow/flow-emulator v0.56.0
github.com/onflow/flow-go v0.32.2-0.20231017202518-0b275f42906c
Expand Down Expand Up @@ -84,7 +85,6 @@ require (
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-multistream v0.4.1 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/onflow/atree v0.6.0 // indirect
github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d // indirect
github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 // indirect
github.com/onflow/flow-ft/lib/go/contracts v0.7.0 // indirect
Expand Down
111 changes: 105 additions & 6 deletions test/test_framework_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,10 +589,17 @@ func TestImportContract(t *testing.T) {

const code = `
import Test
import FooContract from "./FooContract"
import BlockchainHelpers
import Crypto
import "FlowToken"
import BarContract from "./BarContract"
import FooContract from "./FooContract"
import BazContract from "./BazContract"
pub fun setup() {
access(all) let admin = Test.getAccount(0x0000000000000008)
access(all)
fun setup() {
var err = Test.deployContract(
name: "BarContract",
path: "./BarContract",
Expand All @@ -606,12 +613,75 @@ func TestImportContract(t *testing.T) {
arguments: []
)
Test.expect(err, Test.beNil())
err = Test.deployContract(
name: "BazContract",
path: "./BazContract",
arguments: []
)
Test.expect(err, Test.beNil())
}
pub fun test() {
Test.assertEqual("Hi from FooContract", FooContract.sayHi())
access(all)
fun test() {
var scriptResult = Test.executeScript(
"access(all) fun main(): Int { return 5 }",
[]
)
var value = scriptResult.returnValue! as! Int
Test.assertEqual(5, value)
// Test access of methods & fields from built-in contract
let keyList = Crypto.KeyList()
Test.assert(keyList.get(keyIndex: 0) == nil)
let hash = Crypto.hash([1, 2, 3], algorithm: HashAlgorithm.SHA3_256)
Test.assertEqual(32, hash.length)
let totalSupply = FlowToken.totalSupply
Test.assertEqual(1000000000.0, totalSupply)
// Test access of methods & fields from deployed contract
Test.assertEqual("Hi from BarContract", BarContract.sayHi())
Test.assertEqual(3, FooContract.numbers.length)
Test.assertEqual(/public/BarContractPublicPath, BarContract.publicPath)
Test.assertEqual(
["one", "two", "three"],
BarContract.proposals
)
// Test access of methods & fields from deployed contract
Test.assertEqual("Hi from FooContract", FooContract.sayHi())
Test.assertEqual(
{1: "one", 2: "two", 3: "three"},
FooContract.numbers
)
// Test access of methods & fields from deployed contract
Test.assertEqual("Hi from BazContract", BazContract.sayHi())
Test.assertEqual(/public/BazContractPublicPath, BazContract.publicPath)
// Test that we can still create accounts, execute scripts
// and transactions.
scriptResult = Test.executeScript(
"access(all) fun main(): Int { return 15 }",
[]
)
value = scriptResult.returnValue! as! Int
Test.assertEqual(15, value)
let account = Test.createAccount()
let tx = Test.Transaction(
code: "transaction { execute{ assert(true) } }",
authorizers: [],
signers: [account],
arguments: [],
)
let result = Test.executeTransaction(tx)
Test.expect(result, Test.beSucceeded())
let blockHeight = getCurrentBlockHeight()
Test.assert(blockHeight > 1)
}
`

Expand All @@ -631,14 +701,34 @@ func TestImportContract(t *testing.T) {

const barContract = `
pub contract BarContract {
init() {}
pub let publicPath: PublicPath
pub let proposals: [String]
init() {
self.publicPath = /public/BarContractPublicPath
self.proposals = ["one", "two", "three"]
}
pub fun sayHi(): String {
return "Hi from BarContract"
}
}
`

const bazContract = `
pub contract BazContract {
pub let publicPath: PublicPath
init() {
self.publicPath = /public/BazContractPublicPath
}
pub fun sayHi(): String {
return "Hi from BazContract"
}
}
`

importResolver := func(location common.Location) (string, error) {
switch location := location.(type) {
case common.AddressLocation:
Expand All @@ -648,13 +738,19 @@ func TestImportContract(t *testing.T) {
if location.Name == "BarContract" {
return barContract, nil
}
if location.Name == "BazContract" {
return bazContract, nil
}
case common.StringLocation:
if location == "./FooContract" {
return fooContract, nil
}
if location == "./BarContract" {
return barContract, nil
}
if location == "./BazContract" {
return bazContract, nil
}
}

return "", fmt.Errorf("unsupported import %s", location)
Expand All @@ -666,12 +762,15 @@ func TestImportContract(t *testing.T) {
return fooContract, nil
case "./BarContract":
return barContract, nil
case "./BazContract":
return bazContract, nil
default:
return "", fmt.Errorf("cannot find file path: %s", path)
}
}

contracts := map[string]common.Address{
"BazContract": {0, 0, 0, 0, 0, 0, 0, 8},
"BarContract": {0, 0, 0, 0, 0, 0, 0, 5},
"FooContract": {0, 0, 0, 0, 0, 0, 0, 5},
}
Expand Down
39 changes: 33 additions & 6 deletions test/test_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

"github.com/rs/zerolog"

"github.com/onflow/atree"
"github.com/onflow/flow-go/model/flow"

"github.com/onflow/cadence/runtime"
Expand Down Expand Up @@ -63,6 +64,8 @@ const BlockchainHelpersLocation = common.IdentifierLocation("BlockchainHelpers")

var quotedLog = regexp.MustCompile("\"(.*)\"")

var StorageIDUndefined = atree.StorageID{}

type Results []Result

type Result struct {
Expand Down Expand Up @@ -581,14 +584,14 @@ func (r *TestRunner) interpreterContractValueHandler(

switch location := compositeType.Location.(type) {
case common.AddressLocation:
storage := runtime.NewStorage(
// All contracts are deployed on EmulatorBackend's
// blockchain, so we construct a storage based on
// its ledge.
blockchainStorage := runtime.NewStorage(
r.backend.blockchain.NewScriptEnvironment(),
nil,
inter,
)
// Update the storage to reflect the changes
// from deployments in setup() function.
inter.SharedState.Config.Storage = storage
storageMap := storage.GetStorageMap(
storageMap := blockchainStorage.GetStorageMap(
location.Address,
runtime.StorageDomainContract,
false,
Expand All @@ -599,6 +602,30 @@ func (r *TestRunner) interpreterContractValueHandler(
interpreter.StringStorageMapKey(location.Name),
)
}

// We need to store every slab of `blockchainStorage`
// to the current environment's storage, so that
// we can access fields & types.
iterator, err := blockchainStorage.SlabIterator()
if err != nil {
panic(err)
}
storage := inter.Storage().(*runtime.Storage)

for {
id, slab := iterator()
if id == StorageIDUndefined {
break
}
err := storage.Store(id, slab)
if err != nil {
panic(err)
}
err = storage.Commit(inter, true)
if err != nil {
panic(err)
}
}
}

if storedValue == nil {
Expand Down

0 comments on commit ca130a0

Please sign in to comment.