diff --git a/test/blockchain_helpers.cdc b/test/blockchain_helpers.cdc index 3e44b2dc..c6d74369 100644 --- a/test/blockchain_helpers.cdc +++ b/test/blockchain_helpers.cdc @@ -1,80 +1,76 @@ import Test -pub struct BlockchainHelpers { - pub let blockchain: Test.Blockchain +/// Returns the current block height of the blockchain. +/// +access(all) +fun getCurrentBlockHeight(): UInt64 { + let script = readFile("get_current_block_height.cdc") + let scriptResult = Test.executeScript(script, []) - init(blockchain: Test.Blockchain) { - self.blockchain = blockchain - } - - /// Returns the current block height of the blockchain. - /// - pub fun getCurrentBlockHeight(): UInt64 { - let script = self.readFile("get_current_block_height.cdc") - let scriptResult = self.blockchain.executeScript(script, []) - - if scriptResult.status == Test.ResultStatus.failed { - panic(scriptResult.error!.message) - } - return scriptResult.returnValue! as! UInt64 + if scriptResult.status == Test.ResultStatus.failed { + panic(scriptResult.error!.message) } + return scriptResult.returnValue! as! UInt64 +} - /// Returns the Flow token balance for the given account. - /// - pub fun getFlowBalance(for account: Test.Account): UFix64 { - let script = self.readFile("get_flow_balance.cdc") - let scriptResult = self.blockchain.executeScript(script, [account.address]) +/// Returns the Flow token balance for the given account. +/// +access(all) +fun getFlowBalance(for account: Test.Account): UFix64 { + let script = readFile("get_flow_balance.cdc") + let scriptResult = Test.executeScript(script, [account.address]) - if scriptResult.status == Test.ResultStatus.failed { - panic(scriptResult.error!.message) - } - return scriptResult.returnValue! as! UFix64 + if scriptResult.status == Test.ResultStatus.failed { + panic(scriptResult.error!.message) } + return scriptResult.returnValue! as! UFix64 +} - /// Mints the given amount of Flow tokens to a specified test account. - /// The transaction is authorized and signed by the service account. - /// Returns the result of the transaction. - /// - pub fun mintFlow( - to receiver: Test.Account, - amount: UFix64 - ): Test.TransactionResult { - let code = self.readFile("mint_flow.cdc") - let tx = Test.Transaction( - code: code, - authorizers: [self.blockchain.serviceAccount().address], - signers: [], - arguments: [receiver.address, amount] - ) +/// Mints the given amount of Flow tokens to a specified test account. +/// The transaction is authorized and signed by the service account. +/// Returns the result of the transaction. +/// +access(all) +fun mintFlow( + to receiver: Test.Account, + amount: UFix64 +): Test.TransactionResult { + let code = readFile("mint_flow.cdc") + let tx = Test.Transaction( + code: code, + authorizers: [Test.serviceAccount().address], + signers: [], + arguments: [receiver.address, amount] + ) - return self.blockchain.executeTransaction(tx) - } + return Test.executeTransaction(tx) +} - /// Burns the specified amount of Flow tokens for the given - /// test account. Returns the result of the transaction. - /// - pub fun burnFlow( - from account: Test.Account, - amount: UFix64 - ): Test.TransactionResult { - let code = self.readFile("burn_flow.cdc") - let tx = Test.Transaction( - code: code, - authorizers: [account.address], - signers: [account], - arguments: [amount] - ) +/// Burns the specified amount of Flow tokens for the given +/// test account. Returns the result of the transaction. +/// +access(all) +fun burnFlow( + from account: Test.Account, + amount: UFix64 +): Test.TransactionResult { + let code = readFile("burn_flow.cdc") + let tx = Test.Transaction( + code: code, + authorizers: [account.address], + signers: [account], + arguments: [amount] + ) - return self.blockchain.executeTransaction(tx) - } + return Test.executeTransaction(tx) +} - /// Reads the code for the script/transaction with the given - /// file name and returns its content as a String. - /// - access(self) - fun readFile(_ name: String): String { - // The "\u{0}helper/" prefix is used in order to prevent - // conflicts with user-defined scripts/transactions. - return Test.readFile("\u{0}helper/".concat(name)) - } +/// Reads the code for the script/transaction with the given +/// file name and returns its content as a String. +/// +access(self) +fun readFile(_ name: String): String { + // The "\u{0}helper/" prefix is used in order to prevent + // conflicts with user-defined scripts/transactions. + return Test.readFile("\u{0}helper/".concat(name)) } diff --git a/test/emulator_backend.go b/test/emulator_backend.go index ef304e2e..39eacdce 100644 --- a/test/emulator_backend.go +++ b/test/emulator_backend.go @@ -52,6 +52,10 @@ import ( // conflicts with user-defined scripts/transactions. const helperFilePrefix = "\x00helper/" +// The number of predefined accounts that are created +// upon initialization of EmulatorBackend. +const initialAccountsNumber = 25 + var _ stdlib.Blockchain = &EmulatorBackend{} type systemClock struct { @@ -66,11 +70,17 @@ func newSystemClock() *systemClock { return &systemClock{} } +// This type holds the necessary information for each contract deployment. +// These are consumed in InterpreterConfig.ContractValueHandler function, +// in order to construct the contract value for imported contracts in +// test scripts. type deployedContractConstructorInvocation struct { ConstructorArguments []interpreter.Value ArgumentTypes []sema.Type + Address common.Address } +// This slice records all contract deployments in test scripts. var contractInvocations = make( map[string]deployedContractConstructorInvocation, 0, @@ -88,10 +98,6 @@ type EmulatorBackend struct { // accountKeys is a mapping of account addresses with their keys. accountKeys map[common.Address]map[string]keyInfo - // A property bag to pass various configurations to the backend. - // Currently, supports passing address mapping for contracts. - configuration *stdlib.Configuration - stdlibHandler stdlib.StandardLibraryHandler // logCollection is a hook attached in the server logger, in order @@ -100,6 +106,18 @@ type EmulatorBackend struct { // clock allows manipulating the blockchain's clock. clock *systemClock + + // accounts is a mapping of account addresses to the underlying + // stdlib.Account value. + accounts map[common.Address]*stdlib.Account + + // fileResolver is used to retrieve the Cadence source code, + // given a relative path. + fileResolver FileResolver + + // contracts is a mapping of contract identifiers to their + // deployed account address. + contracts map[string]common.Address } type keyInfo struct { @@ -160,15 +178,19 @@ func NewEmulatorBackend( clock := newSystemClock() blockchain.SetClock(clock) - return &EmulatorBackend{ + emulatorBackend := &EmulatorBackend{ blockchain: blockchain, blockOffset: 0, accountKeys: map[common.Address]map[string]keyInfo{}, - configuration: baseConfiguration(), stdlibHandler: stdlibHandler, logCollection: logCollectionHook, clock: clock, + contracts: map[string]common.Address{}, + accounts: map[common.Address]*stdlib.Account{}, } + emulatorBackend.bootstrapAccounts() + + return emulatorBackend } func (e *EmulatorBackend) RunScript( @@ -293,13 +315,26 @@ func (e *EmulatorBackend) CreateAccount() (*stdlib.Account, error) { }, } - return &stdlib.Account{ + account := &stdlib.Account{ Address: common.Address(address), PublicKey: &stdlib.PublicKey{ PublicKey: publicKey, SignAlgo: fvmCrypto.CryptoToRuntimeSigningAlgorithm(accountKey.PublicKey.Algorithm()), }, - }, nil + } + e.accounts[account.Address] = account + + return account, nil +} + +func (e *EmulatorBackend) GetAccount( + address interpreter.AddressValue, +) (*stdlib.Account, error) { + account, ok := e.accounts[address.ToAddress()] + if !ok { + return nil, fmt.Errorf("account with address: %s not found", address.Hex()) + } + return account, nil } func (e *EmulatorBackend) AddTransaction( @@ -343,58 +378,6 @@ func (e *EmulatorBackend) AddTransaction( return nil } -func (e *EmulatorBackend) newTransaction(code string, authorizers []common.Address) *sdk.Transaction { - serviceKey := e.blockchain.ServiceKey() - - sequenceNumber := serviceKey.SequenceNumber + e.blockOffset - - tx := sdk.NewTransaction(). - SetScript([]byte(code)). - SetProposalKey(serviceKey.Address, serviceKey.Index, sequenceNumber). - SetPayer(serviceKey.Address) - - for _, authorizer := range authorizers { - tx = tx.AddAuthorizer(sdk.Address(authorizer)) - } - - return tx -} - -func (e *EmulatorBackend) signTransaction( - tx *sdk.Transaction, - signerAccounts []*stdlib.Account, -) error { - - // Sign transaction with each signer - // Note: Following logic is borrowed from the flow-ft. - - for i := len(signerAccounts) - 1; i >= 0; i-- { - signerAccount := signerAccounts[i] - - publicKey := signerAccount.PublicKey.PublicKey - accountKeys := e.accountKeys[signerAccount.Address] - keyInfo := accountKeys[string(publicKey)] - - err := tx.SignPayload(sdk.Address(signerAccount.Address), 0, keyInfo.signer) - if err != nil { - return err - } - } - - serviceKey := e.blockchain.ServiceKey() - serviceSigner, err := serviceKey.Signer() - if err != nil { - return err - } - - err = tx.SignEnvelope(serviceKey.Address, 0, serviceSigner) - if err != nil { - return err - } - - return nil -} - func (e *EmulatorBackend) ExecuteNextTransaction() *stdlib.TransactionResult { result, err := e.blockchain.ExecuteNextTransaction() @@ -431,18 +414,23 @@ func (e *EmulatorBackend) CommitBlock() error { func (e *EmulatorBackend) DeployContract( inter *interpreter.Interpreter, name string, - code string, - account *stdlib.Account, + path string, args []interpreter.Value, ) error { const deployContractTransactionTemplate = ` - transaction(%s) { - prepare(signer: AuthAccount) { - signer.contracts.add(name: "%s", code: "%s".decodeHex()%s) - } - }` - + transaction(%s) { + prepare(signer: AuthAccount) { + signer.contracts.add(name: "%s", code: "%s".decodeHex()%s) + } + } + ` + + // Retrieve the contract source code, by using the given path. + code, err := e.fileResolver(path) + if err != nil { + panic(err) + } code = e.replaceImports(code) hexEncodedCode := hex.EncodeToString([]byte(code)) @@ -475,6 +463,14 @@ func (e *EmulatorBackend) DeployContract( addArgsBuilder.String(), ) + address, ok := e.contracts[name] + if !ok { + return fmt.Errorf("could not find the address of contract: %s", name) + } + account, ok := e.accounts[address] + if !ok { + return fmt.Errorf("could not find an account with address: %s", address) + } tx := e.newTransaction(script, []common.Address{account.Address}) for _, arg := range cadenceArgs { @@ -484,7 +480,7 @@ func (e *EmulatorBackend) DeployContract( } } - err := e.signTransaction(tx, []*stdlib.Account{account}) + err = e.signTransaction(tx, []*stdlib.Account{account}) if err != nil { return err } @@ -512,9 +508,13 @@ func (e *EmulatorBackend) DeployContract( } argTypes = append(argTypes, argType) } + // We record the successful contract deployment, along with any + // supplied arguments, so that we can reconstruct a contract value + // in test scripts. contractInvocations[name] = deployedContractConstructorInvocation{ ConstructorArguments: args, ArgumentTypes: argTypes, + Address: account.Address, } return e.CommitBlock() @@ -525,93 +525,6 @@ func (e *EmulatorBackend) Logs() []string { return e.logCollection.Logs } -// newBlockchain returns an emulator blockchain for testing. -func newBlockchain( - hook *logCollectionHook, - opts ...emulator.Option, -) *emulator.Blockchain { - output := zerolog.ConsoleWriter{Out: os.Stdout} - logger := zerolog.New(output).With().Timestamp(). - Logger().Hook(hook).Level(zerolog.InfoLevel) - - b, err := emulator.New( - append( - []emulator.Option{ - emulator.WithStorageLimitEnabled(false), - emulator.WithServerLogger(logger), - emulator.Contracts(commonContracts), - emulator.WithChainID(chain.ChainID()), - }, - opts..., - )..., - ) - if err != nil { - panic(err) - } - - return b -} - -func (e *EmulatorBackend) UseConfiguration(configuration *stdlib.Configuration) { - for contract, address := range configuration.Addresses { - // We do not want to override the base configuration, - // which includes the mapping for system/common contracts. - e.configuration.Addresses[contract] = address - } -} - -func (e *EmulatorBackend) replaceImports(code string) string { - if e.configuration == nil { - return code - } - - program, err := parser.ParseProgram(nil, []byte(code), parser.Config{}) - if err != nil { - panic(err) - } - - sb := strings.Builder{} - importDeclEnd := 0 - - for _, importDeclaration := range program.ImportDeclarations() { - prevImportDeclEnd := importDeclEnd - importDeclEnd = importDeclaration.EndPos.Offset + 1 - - location, ok := importDeclaration.Location.(common.StringLocation) - if !ok { - // keep the import statement it as-is - sb.WriteString(code[prevImportDeclEnd:importDeclEnd]) - continue - } - - address, ok := e.configuration.Addresses[location.String()] - if !ok { - // keep import statement it as-is - sb.WriteString(code[prevImportDeclEnd:importDeclEnd]) - continue - } - - var addressStr string - if strings.Contains(importDeclaration.String(), "from") { - addressStr = fmt.Sprintf("0x%s", address) - } else { - // Imports of the form `import "FungibleToken"` should be - // expanded to `import FungibleToken from 0xee82856bf20e2aa6` - addressStr = fmt.Sprintf("%s from 0x%s", location, address) - } - - locationStart := importDeclaration.LocationPos.Offset - - sb.WriteString(code[prevImportDeclEnd:locationStart]) - sb.WriteString(addressStr) - - } - - sb.WriteString(code[importDeclEnd:]) - - return sb.String() -} - func (e *EmulatorBackend) StandardLibraryHandler() stdlib.StandardLibraryHandler { return e.stdlibHandler } @@ -709,6 +622,147 @@ func (e *EmulatorBackend) LoadSnapshot(name string) error { return e.blockchain.LoadSnapshot(name) } +// Creates the number of predefined accounts that will be used +// for deploying the contracts under testing. +func (e *EmulatorBackend) bootstrapAccounts() { + for i := 0; i < initialAccountsNumber; i++ { + _, err := e.CreateAccount() + if err != nil { + panic(err) + } + } +} + +func (e *EmulatorBackend) newTransaction(code string, authorizers []common.Address) *sdk.Transaction { + serviceKey := e.blockchain.ServiceKey() + + sequenceNumber := serviceKey.SequenceNumber + e.blockOffset + + tx := sdk.NewTransaction(). + SetScript([]byte(code)). + SetProposalKey(serviceKey.Address, serviceKey.Index, sequenceNumber). + SetPayer(serviceKey.Address) + + for _, authorizer := range authorizers { + tx = tx.AddAuthorizer(sdk.Address(authorizer)) + } + + return tx +} + +func (e *EmulatorBackend) signTransaction( + tx *sdk.Transaction, + signerAccounts []*stdlib.Account, +) error { + + // Sign transaction with each signer + // Note: Following logic is borrowed from the flow-ft. + + for i := len(signerAccounts) - 1; i >= 0; i-- { + signerAccount := signerAccounts[i] + + publicKey := signerAccount.PublicKey.PublicKey + accountKeys := e.accountKeys[signerAccount.Address] + keyInfo := accountKeys[string(publicKey)] + + err := tx.SignPayload(sdk.Address(signerAccount.Address), 0, keyInfo.signer) + if err != nil { + return err + } + } + + serviceKey := e.blockchain.ServiceKey() + serviceSigner, err := serviceKey.Signer() + if err != nil { + return err + } + + err = tx.SignEnvelope(serviceKey.Address, 0, serviceSigner) + if err != nil { + return err + } + + return nil +} + +func (e *EmulatorBackend) replaceImports(code string) string { + program, err := parser.ParseProgram(nil, []byte(code), parser.Config{}) + if err != nil { + panic(err) + } + + sb := strings.Builder{} + importDeclEnd := 0 + for _, importDeclaration := range program.ImportDeclarations() { + prevImportDeclEnd := importDeclEnd + importDeclEnd = importDeclaration.EndPos.Offset + 1 + + location, ok := importDeclaration.Location.(common.StringLocation) + if !ok { + // keep the import statement it as-is + sb.WriteString(code[prevImportDeclEnd:importDeclEnd]) + continue + } + + var address common.Address + if len(importDeclaration.Identifiers) > 0 { + address, ok = e.contracts[importDeclaration.Identifiers[0].Identifier] + if !ok { + // keep import statement it as-is + sb.WriteString(code[prevImportDeclEnd:importDeclEnd]) + continue + } + } else { + address = e.contracts[location.String()] + } + + var importStr string + if strings.Contains(importDeclaration.String(), "from") { + importStr = fmt.Sprintf("0x%s", address) + } else { + // Imports of the form `import "FungibleToken"` should be + // expanded to `import FungibleToken from 0xee82856bf20e2aa6` + importStr = fmt.Sprintf("%s from 0x%s", location, address) + } + + locationStart := importDeclaration.LocationPos.Offset + + sb.WriteString(code[prevImportDeclEnd:locationStart]) + sb.WriteString(importStr) + } + + sb.WriteString(code[importDeclEnd:]) + + return sb.String() +} + +// newBlockchain returns an emulator blockchain for testing. +func newBlockchain( + hook *logCollectionHook, + opts ...emulator.Option, +) *emulator.Blockchain { + output := zerolog.ConsoleWriter{Out: os.Stdout} + logger := zerolog.New(output).With().Timestamp(). + Logger().Hook(hook).Level(zerolog.InfoLevel) + + b, err := emulator.New( + append( + []emulator.Option{ + emulator.WithStorageLimitEnabled(false), + emulator.WithServerLogger(logger), + emulator.Contracts(commonContracts), + emulator.WithChainID(chain.ChainID()), + }, + opts..., + )..., + ) + if err != nil { + panic(err) + } + + return b +} + // excludeCommonLocations excludes the common contracts from appearing // in the coverage report, as they skew the coverage metrics. func excludeCommonLocations(coverageReport *runtime.CoverageReport) { @@ -724,27 +778,3 @@ func excludeCommonLocations(coverageReport *runtime.CoverageReport) { coverageReport.ExcludeLocation(location) } } - -// baseConfiguration returns an *stdlib.Configuration with contract to -// address mappings for system/common contracts. -func baseConfiguration() *stdlib.Configuration { - addresses := make(map[string]common.Address, 0) - serviceAddress := common.Address(chain.ServiceAddress()) - addresses["NonFungibleToken"] = serviceAddress - addresses["MetadataViews"] = serviceAddress - addresses["ViewResolver"] = serviceAddress - for _, addressLocation := range systemContracts { - contract := addressLocation.Name - address := common.Address(addressLocation.Address) - addresses[contract] = address - } - for _, contractDescription := range commonContracts { - contract := contractDescription.Name - address := common.Address(contractDescription.Address) - addresses[contract] = address - } - - return &stdlib.Configuration{ - Addresses: addresses, - } -} diff --git a/test/test_framework_provider.go b/test/test_framework_provider.go index 85378908..5091e4fe 100644 --- a/test/test_framework_provider.go +++ b/test/test_framework_provider.go @@ -34,6 +34,8 @@ type TestFrameworkProvider struct { stdlibHandler stdlib.StandardLibraryHandler coverageReport *runtime.CoverageReport + + EmulatorBackend *EmulatorBackend } func (tf *TestFrameworkProvider) ReadFile(path string) (string, error) { @@ -61,11 +63,7 @@ func (tf *TestFrameworkProvider) ReadFile(path string) (string, error) { } func (tf *TestFrameworkProvider) NewEmulatorBackend() stdlib.Blockchain { - return NewEmulatorBackend( - tf.fileResolver, - tf.stdlibHandler, - tf.coverageReport, - ) + return tf.EmulatorBackend } func NewTestFrameworkProvider( @@ -73,9 +71,16 @@ func NewTestFrameworkProvider( stdlibHandler stdlib.StandardLibraryHandler, coverageReport *runtime.CoverageReport, ) stdlib.TestFramework { - return &TestFrameworkProvider{ + provider := &TestFrameworkProvider{ fileResolver: fileResolver, stdlibHandler: stdlibHandler, coverageReport: coverageReport, } + provider.EmulatorBackend = NewEmulatorBackend( + fileResolver, + stdlibHandler, + coverageReport, + ) + + return provider } diff --git a/test/test_framework_test.go b/test/test_framework_test.go index 149043b1..6ca76ebe 100644 --- a/test/test_framework_test.go +++ b/test/test_framework_test.go @@ -144,8 +144,7 @@ func TestExecuteScript(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let result = blockchain.executeScript( + let result = Test.executeScript( "pub fun main(): Int { return 2 + 3 }", [] ) @@ -168,8 +167,7 @@ func TestExecuteScript(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let result = blockchain.executeScript( + let result = Test.executeScript( "pub fun main(a: Int, b: Int): Int { return a + b }", [2, 3] ) @@ -188,14 +186,13 @@ func TestExecuteScript(t *testing.T) { t.Run("non-empty array returns", func(t *testing.T) { t.Parallel() - const code = `pub fun main(): [UInt64] { return [1, 2, 3]}` + const code = `pub fun main(): [UInt64] { return [1, 2, 3] }` testScript := fmt.Sprintf(` import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let result = blockchain.executeScript("%s", []) + let result = Test.executeScript("%s", []) Test.expect(result, Test.beSucceeded()) @@ -203,8 +200,7 @@ func TestExecuteScript(t *testing.T) { let resultArray = result.returnValue! as! [UInt64] Test.assertEqual(expected, resultArray) } - `, code, - ) + `, code) runner := NewTestRunner() result, err := runner.RunTest(testScript, "test") @@ -221,8 +217,7 @@ func TestExecuteScript(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let result = blockchain.executeScript("%s", []) + let result = Test.executeScript("%s", []) Test.expect(result, Test.beSucceeded()) @@ -230,8 +225,7 @@ func TestExecuteScript(t *testing.T) { let resultArray = result.returnValue! as! [UInt64] Test.assertEqual(expected, resultArray) } - `, code, - ) + `, code) runner := NewTestRunner() result, err := runner.RunTest(testScript, "test") @@ -242,14 +236,13 @@ func TestExecuteScript(t *testing.T) { t.Run("non-empty dictionary returns", func(t *testing.T) { t.Parallel() - const code = `pub fun main(): {String: Int} { return {\"foo\": 5, \"bar\": 10}}` + const code = `pub fun main(): {String: Int} { return {\"foo\": 5, \"bar\": 10} }` testScript := fmt.Sprintf(` import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let result = blockchain.executeScript("%s", []) + let result = Test.executeScript("%s", []) Test.expect(result, Test.beSucceeded()) @@ -257,8 +250,7 @@ func TestExecuteScript(t *testing.T) { let resultDict = result.returnValue! as! {String: Int} Test.assertEqual(expected, resultDict) } - `, code, - ) + `, code) runner := NewTestRunner() result, err := runner.RunTest(testScript, "test") @@ -275,8 +267,7 @@ func TestExecuteScript(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let result = blockchain.executeScript("%s", []) + let result = Test.executeScript("%s", []) Test.expect(result, Test.beSucceeded()) @@ -284,8 +275,7 @@ func TestExecuteScript(t *testing.T) { let resultDict = result.returnValue! as! {String: Int} Test.assertEqual(expected, resultDict) } - `, code, - ) + `, code) runner := NewTestRunner() result, err := runner.RunTest(testScript, "test") @@ -297,16 +287,25 @@ func TestExecuteScript(t *testing.T) { func TestImportContract(t *testing.T) { t.Parallel() - t.Run("init no params", func(t *testing.T) { + t.Run("contract with no init params", func(t *testing.T) { t.Parallel() const code = ` import Test - import FooContract from "./FooContract" + import "FooContract" + + pub fun setup() { + let err = Test.deployContract( + name: "FooContract", + path: "./FooContract", + arguments: [] + ) + + Test.expect(err, Test.beNil()) + } pub fun test() { - let foo = FooContract() - Test.assertEqual("hello from Foo", foo.sayHello()) + Test.assertEqual("hello from Foo", FooContract.sayHello()) } ` @@ -320,27 +319,63 @@ func TestImportContract(t *testing.T) { } ` + fileResolver := func(path string) (string, error) { + switch path { + case "./FooContract": + return fooContract, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) + } + } + importResolver := func(location common.Location) (string, error) { - return fooContract, nil + switch location := location.(type) { + case common.AddressLocation: + if location.Name == "FooContract" { + return fooContract, nil + } + case common.StringLocation: + if location == "FooContract" { + return fooContract, nil + } + } + + return "", fmt.Errorf("cannot find import location: %s", location.ID()) } - runner := NewTestRunner().WithImportResolver(importResolver) + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithImportResolver(importResolver). + WithFileResolver(fileResolver). + WithContracts(contracts) result, err := runner.RunTest(code, "test") require.NoError(t, err) require.NoError(t, result.Error) }) - t.Run("init with params", func(t *testing.T) { + t.Run("contract with init params", func(t *testing.T) { t.Parallel() const code = ` import Test import FooContract from "./FooContract" + pub fun setup() { + let err = Test.deployContract( + name: "FooContract", + path: "./FooContract", + arguments: ["hello from Foo"] + ) + + Test.expect(err, Test.beNil()) + } + pub fun test() { - let foo = FooContract(greeting: "hello from Foo") - Test.assertEqual("hello from Foo", foo.sayHello()) + Test.assertEqual("hello from Foo", FooContract.sayHello()) } ` @@ -359,11 +394,38 @@ func TestImportContract(t *testing.T) { } ` + fileResolver := func(path string) (string, error) { + switch path { + case "./FooContract": + return fooContract, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) + } + } + importResolver := func(location common.Location) (string, error) { - return fooContract, nil + switch location := location.(type) { + case common.AddressLocation: + if location.Name == "FooContract" { + return fooContract, nil + } + case common.StringLocation: + if location == "./FooContract" { + return fooContract, nil + } + } + + return "", fmt.Errorf("cannot find import location: %s", location.ID()) } - runner := NewTestRunner().WithImportResolver(importResolver) + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithImportResolver(importResolver). + WithFileResolver(fileResolver). + WithContracts(contracts) result, err := runner.RunTest(code, "test") require.NoError(t, err) @@ -377,12 +439,12 @@ func TestImportContract(t *testing.T) { import FooContract from "./FooContract" pub fun test() { - let foo = FooContract() + let message = FooContract.sayHello() } ` importResolver := func(location common.Location) (string, error) { - return "", errors.New("cannot load file") + return "", errors.New("cannot import location") } runner := NewTestRunner().WithImportResolver(importResolver) @@ -394,7 +456,7 @@ func TestImportContract(t *testing.T) { importedProgramError := &sema.ImportedProgramError{} assert.ErrorAs(t, errs[0], &importedProgramError) - assert.Contains(t, importedProgramError.Err.Error(), "cannot load file") + assert.Contains(t, importedProgramError.Err.Error(), "cannot import location") assert.IsType(t, &sema.NotDeclaredError{}, errs[1]) }) @@ -406,7 +468,7 @@ func TestImportContract(t *testing.T) { import FooContract from "./FooContract" pub fun test() { - let foo = FooContract() + let message = FooContract.sayHello() } ` @@ -426,15 +488,32 @@ func TestImportContract(t *testing.T) { t.Run("nested imports", func(t *testing.T) { t.Parallel() - testLocation := common.AddressLocation{ - Address: common.MustBytesToAddress([]byte{0x1}), - Name: "BarContract", - } - const code = ` + import Test + import BarContract from "./BarContract" import FooContract from "./FooContract" - pub fun test() {} + pub let account = Test.getAccount(0x0000000000000005) + + pub fun setup() { + var err = Test.deployContract( + name: "BarContract", + path: "./BarContract", + arguments: [] + ) + Test.expect(err, Test.beNil()) + + err = Test.deployContract( + name: "FooContract", + path: "./FooContract", + arguments: [] + ) + Test.expect(err, Test.beNil()) + } + + pub fun test() { + Test.assertEqual(1, 1) + } ` const fooContract = ` @@ -453,12 +532,18 @@ func TestImportContract(t *testing.T) { importResolver := func(location common.Location) (string, error) { switch location := location.(type) { + case common.AddressLocation: + if location.Name == "FooContract" { + return fooContract, nil + } + if location.Name == "BarContract" { + return barContract, nil + } case common.StringLocation: if location == "./FooContract" { return fooContract, nil } - case common.AddressLocation: - if location == testLocation { + if location == "./BarContract" { return barContract, nil } } @@ -466,28 +551,39 @@ func TestImportContract(t *testing.T) { return "", fmt.Errorf("unsupported import %s", location) } - runner := NewTestRunner().WithImportResolver(importResolver) + fileResolver := func(path string) (string, error) { + switch path { + case "./FooContract": + return fooContract, nil + case "./BarContract": + return barContract, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) + } + } + + contracts := map[string]common.Address{ + "BarContract": {0, 0, 0, 0, 0, 0, 0, 5}, + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithImportResolver(importResolver). + WithContracts(contracts). + WithFileResolver(fileResolver) _, err := runner.RunTest(code, "test") - require.Error(t, err) - assert.Contains(t, err.Error(), "nested imports are not supported") + require.NoError(t, err) }) } func TestImportBuiltinContracts(t *testing.T) { t.Parallel() - testCode := ` + const testCode = ` import Test - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() - - pub fun setup() { - blockchain.useConfiguration(Test.Configuration({ - "FooContract": account.address - })) - } + pub let account = Test.createAccount() pub fun testSetupExampleNFTCollection() { let code = Test.readFile("../transactions/setup_example_nft_collection.cdc") @@ -498,20 +594,20 @@ func TestImportBuiltinContracts(t *testing.T) { arguments: [] ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beSucceeded()) } pub fun testGetIntegerTrait() { let script = Test.readFile("../scripts/import_common_contracts.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) Test.assertEqual(true, result.returnValue! as! Bool) } ` - transactionCode := ` + const transactionCode = ` import "NonFungibleToken" import "ExampleNFT" import "MetadataViews" @@ -539,7 +635,7 @@ func TestImportBuiltinContracts(t *testing.T) { } ` - scriptCode := ` + const scriptCode = ` import "FungibleToken" import "FlowToken" import "NonFungibleToken" @@ -561,7 +657,7 @@ func TestImportBuiltinContracts(t *testing.T) { case "../scripts/import_common_contracts.cdc": return scriptCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } @@ -625,11 +721,20 @@ func TestUsingEnv(t *testing.T) { const code = ` import Test - import FooContract from "./FooContract" + import "FooContract" + + pub fun setup() { + let err = Test.deployContract( + name: "FooContract", + path: "./FooContract", + arguments: [] + ) + + Test.expect(err, Test.beNil()) + } pub fun test() { - let foo = FooContract() - Test.assertEqual(0.0, foo.getBalance()) + Test.assertEqual(0.0, FooContract.getBalance()) } ` @@ -644,11 +749,38 @@ func TestUsingEnv(t *testing.T) { } ` + fileResolver := func(path string) (string, error) { + switch path { + case "./FooContract": + return fooContract, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) + } + } + importResolver := func(location common.Location) (string, error) { - return fooContract, nil + switch location := location.(type) { + case common.AddressLocation: + if location.Name == "FooContract" { + return fooContract, nil + } + case common.StringLocation: + if location == "FooContract" { + return fooContract, nil + } + } + + return "", fmt.Errorf("cannot find import location: %s", location.ID()) } - runner := NewTestRunner().WithImportResolver(importResolver) + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithImportResolver(importResolver). + WithFileResolver(fileResolver). + WithContracts(contracts) result, err := runner.RunTest(code, "test") require.NoError(t, err) @@ -662,8 +794,7 @@ func TestUsingEnv(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() // just checking the invocation of verify function Test.assert(!account.publicKey.verify( @@ -688,12 +819,11 @@ func TestUsingEnv(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let script = Test.readFile("./sample/script.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) @@ -743,12 +873,11 @@ func TestCreateAccount(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let typ = CompositeType("flow.AccountCreated")! - let events = blockchain.eventsOfType(typ) - Test.assertEqual(1, events.length) + let events = Test.eventsOfType(typ) + Test.expect(events.length, Test.beGreaterThan(1)) } ` @@ -762,6 +891,45 @@ func TestCreateAccount(t *testing.T) { require.NoError(t, result.Error) } +func TestGetAccount(t *testing.T) { + t.Parallel() + + const code = ` + import Test + + pub fun testMissingAccount() { + let account = Test.getAccount(0x0000000000000095) + + Test.assertEqual(0x0000000000000005 as Address, account.address) + } + + pub fun testExistingAccount() { + let admin = Test.createAccount() + let account = Test.getAccount(admin.address) + + Test.assertEqual(account.address, admin.address) + Test.assertEqual(account.publicKey.publicKey, admin.publicKey.publicKey) + } + ` + + importResolver := func(location common.Location) (string, error) { + return "", nil + } + + runner := NewTestRunner().WithImportResolver(importResolver) + result, err := runner.RunTest(code, "testMissingAccount") + require.NoError(t, err) + require.ErrorContains( + t, + result.Error, + "account with address: 0x0000000000000095 was not found", + ) + + result, err = runner.RunTest(code, "testExistingAccount") + require.NoError(t, err) + assert.NoError(t, result.Error) +} + func TestExecutingTransactions(t *testing.T) { t.Parallel() @@ -772,8 +940,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx = Test.Transaction( code: "transaction { execute{ assert(false) } }", @@ -782,7 +949,7 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - blockchain.addTransaction(tx) + Test.addTransaction(tx) } ` @@ -799,8 +966,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx = Test.Transaction( code: "transaction { execute{ assert(true) } }", @@ -809,9 +975,9 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - blockchain.addTransaction(tx) + Test.addTransaction(tx) - let result = blockchain.executeNextTransaction()! + let result = Test.executeNextTransaction()! Test.expect(result, Test.beSucceeded()) } ` @@ -829,8 +995,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx = Test.Transaction( code: "transaction { prepare(acct: AuthAccount) {} execute{ assert(true) } }", @@ -839,9 +1004,9 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - blockchain.addTransaction(tx) + Test.addTransaction(tx) - let result = blockchain.executeNextTransaction()! + let result = Test.executeNextTransaction()! Test.expect(result, Test.beSucceeded()) } ` @@ -859,8 +1024,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx = Test.Transaction( code: "transaction { execute{ assert(false) } }", @@ -869,9 +1033,9 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - blockchain.addTransaction(tx) + Test.addTransaction(tx) - let result = blockchain.executeNextTransaction()! + let result = Test.executeNextTransaction()! Test.expect(result, Test.beFailed()) } ` @@ -889,8 +1053,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let result = blockchain.executeNextTransaction() + let result = Test.executeNextTransaction() Test.expect(result, Test.beNil()) } ` @@ -908,8 +1071,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - blockchain.commitBlock() + Test.commitBlock() } ` @@ -926,8 +1088,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx = Test.Transaction( code: "transaction { execute{ assert(false) } }", @@ -936,9 +1097,9 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - blockchain.addTransaction(tx) + Test.addTransaction(tx) - blockchain.commitBlock() + Test.commitBlock() } ` @@ -957,8 +1118,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx = Test.Transaction( code: "transaction { execute{ assert(false) } }", @@ -968,14 +1128,14 @@ func TestExecutingTransactions(t *testing.T) { ) // Add two transactions - blockchain.addTransaction(tx) - blockchain.addTransaction(tx) + Test.addTransaction(tx) + Test.addTransaction(tx) // But execute only one - blockchain.executeNextTransaction() + Test.executeNextTransaction() // Then try to commit - blockchain.commitBlock() + Test.commitBlock() } ` @@ -994,9 +1154,8 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - blockchain.commitBlock() - blockchain.commitBlock() + Test.commitBlock() + Test.commitBlock() } ` @@ -1013,8 +1172,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx = Test.Transaction( code: "transaction { execute{ assert(true) } }", @@ -1023,7 +1181,7 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beSucceeded()) } ` @@ -1041,8 +1199,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx = Test.Transaction( code: "transaction(a: Int, b: Int) { execute{ assert(a == b) } }", @@ -1051,7 +1208,7 @@ func TestExecutingTransactions(t *testing.T) { arguments: [4, 4], ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beSucceeded()) } ` @@ -1069,9 +1226,8 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account1 = blockchain.createAccount() - let account2 = blockchain.createAccount() + let account1 = Test.createAccount() + let account2 = Test.createAccount() let tx = Test.Transaction( code: "transaction() { prepare(acct1: AuthAccount, acct2: AuthAccount) {} }", @@ -1080,7 +1236,7 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beSucceeded()) } ` @@ -1098,8 +1254,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx = Test.Transaction( code: "transaction { execute{ assert(fail) } }", @@ -1108,7 +1263,7 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beFailed()) } ` @@ -1126,8 +1281,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx1 = Test.Transaction( code: "transaction { execute{ assert(true) } }", @@ -1150,7 +1304,7 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - let firstResults = blockchain.executeTransactions([tx1, tx2, tx3]) + let firstResults = Test.executeTransactions([tx1, tx2, tx3]) Test.assertEqual(3, firstResults.length) Test.expect(firstResults[0], Test.beSucceeded()) @@ -1159,7 +1313,7 @@ func TestExecutingTransactions(t *testing.T) { // Execute them again: To verify the proper increment/reset of sequence numbers. - let secondResults = blockchain.executeTransactions([tx1, tx2, tx3]) + let secondResults = Test.executeTransactions([tx1, tx2, tx3]) Test.assertEqual(3, secondResults.length) Test.expect(secondResults[0], Test.beSucceeded()) @@ -1181,10 +1335,9 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() - let result = blockchain.executeTransactions([]) + let result = Test.executeTransactions([]) Test.assertEqual(0, result.length) } ` @@ -1202,8 +1355,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx1 = Test.Transaction( code: "transaction { execute{ assert(true) } }", @@ -1212,7 +1364,7 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - blockchain.addTransaction(tx1) + Test.addTransaction(tx1) let tx2 = Test.Transaction( code: "transaction { execute{ assert(true) } }", @@ -1221,7 +1373,7 @@ func TestExecutingTransactions(t *testing.T) { arguments: [], ) - let result = blockchain.executeTransaction(tx2) + let result = Test.executeTransaction(tx2) Test.expect(result, Test.beSucceeded()) } ` @@ -1241,8 +1393,7 @@ func TestExecutingTransactions(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx = Test.Transaction( code: "transaction(a: [Int]) { execute{ assert(a[0] == a[1]) } }", @@ -1251,7 +1402,7 @@ func TestExecutingTransactions(t *testing.T) { arguments: [[4, 4]], ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beSucceeded()) } ` @@ -1271,15 +1422,13 @@ func TestSetupAndTearDown(t *testing.T) { const code = ` import Test - pub(set) var setupRan = false pub fun setup() { - Test.assert(!setupRan) - setupRan = true + log("setup is running!") } pub fun testFunc() { - Test.assert(setupRan) + Test.assert(true) } ` @@ -1291,6 +1440,8 @@ func TestSetupAndTearDown(t *testing.T) { result := results[0] assert.Equal(t, result.TestName, "testFunc") require.NoError(t, result.Error) + + assert.ElementsMatch(t, []string{"setup is running!"}, runner.Logs()) }) t.Run("setup failed", func(t *testing.T) { @@ -1320,14 +1471,12 @@ func TestSetupAndTearDown(t *testing.T) { const code = ` import Test - pub(set) var tearDownRan = false - pub fun testFunc() { - Test.assert(!tearDownRan) + Test.assert(true) } pub fun tearDown() { - Test.assert(true) + log("tearDown is running!") } ` @@ -1339,13 +1488,15 @@ func TestSetupAndTearDown(t *testing.T) { result := results[0] assert.Equal(t, result.TestName, "testFunc") require.NoError(t, result.Error) + + assert.ElementsMatch(t, []string{"tearDown is running!"}, runner.Logs()) }) t.Run("teardown failed", func(t *testing.T) { t.Parallel() const code = ` - import Test + import Test pub(set) var tearDownRan = false @@ -1378,7 +1529,7 @@ func TestBeforeAndAfterEach(t *testing.T) { t.Run("beforeEach", func(t *testing.T) { t.Parallel() - code := ` + const code = ` import Test pub(set) var counter = 0 @@ -1410,7 +1561,7 @@ func TestBeforeAndAfterEach(t *testing.T) { t.Run("beforeEach failed", func(t *testing.T) { t.Parallel() - code := ` + const code = ` import Test pub fun beforeEach() { @@ -1431,7 +1582,7 @@ func TestBeforeAndAfterEach(t *testing.T) { t.Run("afterEach", func(t *testing.T) { t.Parallel() - code := ` + const code = ` import Test pub(set) var counter = 2 @@ -1467,7 +1618,7 @@ func TestBeforeAndAfterEach(t *testing.T) { t.Run("afterEach failed", func(t *testing.T) { t.Parallel() - code := ` + const code = ` import Test pub(set) var tearDownRan = false @@ -1546,12 +1697,11 @@ func TestLoadingProgramsFromLocalFile(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let script = Test.readFile("./sample/script.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) Test.assertEqual(5, result.returnValue! as! Int) @@ -1588,12 +1738,11 @@ func TestLoadingProgramsFromLocalFile(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let script = Test.readFile("./sample/script.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) Test.assertEqual(5, result.returnValue! as! Int) @@ -1625,8 +1774,7 @@ func TestLoadingProgramsFromLocalFile(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let script = Test.readFile("./sample/script.cdc") } @@ -1647,28 +1795,41 @@ func TestDeployingContracts(t *testing.T) { t.Run("no args", func(t *testing.T) { t.Parallel() + const contract = ` + pub contract Foo { + init() {} + + pub fun sayHello(): String { + return "hello from Foo" + } + } + ` + + const script = ` + import Foo from "Foo.cdc" + + pub fun main(): String { + return Foo.sayHello() + } + ` + const code = ` import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() - - let contractCode = "pub contract Foo{ init(){} pub fun sayHello(): String { return \"hello from Foo\"} }" + let account = Test.getAccount(0x0000000000000005) - let err = blockchain.deployContract( + let err = Test.deployContract( name: "Foo", - code: contractCode, - account: account, + path: "Foo.cdc", arguments: [], ) Test.expect(err, Test.beNil()) - var script = "import Foo from ".concat(account.address.toString()).concat("\n") - script = script.concat("pub fun main(): String { return Foo.sayHello() }") + let script = Test.readFile("say_hello.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) @@ -1677,7 +1838,24 @@ func TestDeployingContracts(t *testing.T) { } ` - runner := NewTestRunner() + fileResolver := func(path string) (string, error) { + switch path { + case "Foo.cdc": + return contract, nil + case "say_hello.cdc": + return script, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) + } + } + + contracts := map[string]common.Address{ + "Foo": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithFileResolver(fileResolver). + WithContracts(contracts) result, err := runner.RunTest(code, "test") require.NoError(t, err) require.NoError(t, result.Error) @@ -1686,28 +1864,45 @@ func TestDeployingContracts(t *testing.T) { t.Run("with args", func(t *testing.T) { t.Parallel() + const contract = ` + pub contract Foo { + pub let msg: String + + init(_ msg: String) { + self.msg = msg + } + + pub fun sayHello(): String { + return self.msg + } + } + ` + + const script = ` + import Foo from "Foo.cdc" + + pub fun main(): String { + return Foo.sayHello() + } + ` + const code = ` import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.getAccount(0x0000000000000005) - let contractCode = "pub contract Foo{ pub let msg: String; init(_ msg: String){ self.msg = msg } pub fun sayHello(): String { return self.msg } }" - - let err = blockchain.deployContract( + let err = Test.deployContract( name: "Foo", - code: contractCode, - account: account, + path: "Foo.cdc", arguments: ["hello from args"], ) Test.expect(err, Test.beNil()) - var script = "import Foo from ".concat(account.address.toString()).concat("\n") - script = script.concat("pub fun main(): String { return Foo.sayHello() }") + let script = Test.readFile("say_hello.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) @@ -1716,7 +1911,24 @@ func TestDeployingContracts(t *testing.T) { } ` - runner := NewTestRunner() + fileResolver := func(path string) (string, error) { + switch path { + case "Foo.cdc": + return contract, nil + case "say_hello.cdc": + return script, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) + } + } + + contracts := map[string]common.Address{ + "Foo": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithFileResolver(fileResolver). + WithContracts(contracts) result, err := runner.RunTest(code, "test") require.NoError(t, err) require.NoError(t, result.Error) @@ -1729,19 +1941,23 @@ func TestErrors(t *testing.T) { t.Run("contract deployment error", func(t *testing.T) { t.Parallel() + const contract = ` + pub contract Foo { + init() {} + + pub fun sayHello() { + return 0 + } + } + ` + const code = ` import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() - - let contractCode = "pub contract Foo{ init(){} pub fun sayHello() { return 0 } }" - - let err = blockchain.deployContract( + let err = Test.deployContract( name: "Foo", - code: contractCode, - account: account, + path: "Foo.cdc", arguments: [], ) @@ -1751,7 +1967,22 @@ func TestErrors(t *testing.T) { } ` - runner := NewTestRunner() + fileResolver := func(path string) (string, error) { + switch path { + case "Foo.cdc": + return contract, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) + } + } + + contracts := map[string]common.Address{ + "Foo": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithFileResolver(fileResolver). + WithContracts(contracts) result, err := runner.RunTest(code, "test") require.NoError(t, err) require.Error(t, result.Error) @@ -1765,11 +1996,8 @@ func TestErrors(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() - let script = "import Foo from 0x01; pub fun main() {}" - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) if result.status == Test.ResultStatus.failed { panic(result.error!.message) @@ -1795,8 +2023,7 @@ func TestErrors(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let account = blockchain.createAccount() + let account = Test.createAccount() let tx2 = Test.Transaction( code: "transaction { execute{ panic(\"some error\") } }", @@ -1805,7 +2032,7 @@ func TestErrors(t *testing.T) { arguments: [], ) - let result = blockchain.executeTransaction(tx2)! + let result = Test.executeTransaction(tx2)! Test.assertError(result, errorMessage: "some error") if result.status == Test.ResultStatus.failed { @@ -2497,44 +2724,34 @@ func TestReplacingImports(t *testing.T) { const code = ` import Test - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.getAccount(0x0000000000000005) pub fun setup() { // Deploy the contract - let contractCode = Test.readFile("./sample/contract.cdc") - - let err = blockchain.deployContract( + let err = Test.deployContract( name: "Foo", - code: contractCode, - account: account, + path: "./sample/contract.cdc", arguments: [], ) Test.expect(err, Test.beNil()) - - // Set the configurations to use the address of the deployed contract. - - blockchain.useConfiguration(Test.Configuration({ - "./FooContract": account.address - })) - } + } pub fun test() { let script = Test.readFile("./sample/script.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) Test.assertEqual("hello from Foo", result.returnValue! as! String) } - ` + ` const contractCode = ` - pub contract Foo{ + pub contract Foo { init() {} pub fun sayHello(): String { - return "hello from Foo" + return "hello from Foo" } } ` @@ -2554,11 +2771,17 @@ func TestReplacingImports(t *testing.T) { case "./sample/contract.cdc": return contractCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } - runner := NewTestRunner().WithFileResolver(fileResolver) + contracts := map[string]common.Address{ + "Foo": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithFileResolver(fileResolver). + WithContracts(contracts) result, err := runner.RunTest(code, "test") require.NoError(t, err) @@ -2571,31 +2794,21 @@ func TestReplacingImports(t *testing.T) { const code = ` import Test - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.getAccount(0x0000000000000005) pub fun setup() { - let contractCode = Test.readFile("./sample/contract.cdc") - - let err = blockchain.deployContract( + let err = Test.deployContract( name: "Foo", - code: contractCode, - account: account, + path: "./sample/contract.cdc", arguments: [], ) Test.expect(err, Test.beNil()) - - // Address locations are not replaceable! - - blockchain.useConfiguration(Test.Configuration({ - "0x01": account.address - })) } pub fun test() { let script = Test.readFile("./sample/script.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beFailed()) if result.status == Test.ResultStatus.failed { @@ -2606,11 +2819,11 @@ func TestReplacingImports(t *testing.T) { ` const contractCode = ` - pub contract Foo{ - init(){} + pub contract Foo { + init() {} pub fun sayHello(): String { - return "hello from Foo" + return "hello from Foo" } } ` @@ -2630,11 +2843,17 @@ func TestReplacingImports(t *testing.T) { case "./sample/contract.cdc": return contractCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } - runner := NewTestRunner().WithFileResolver(fileResolver) + contracts := map[string]common.Address{ + "Foo": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithFileResolver(fileResolver). + WithContracts(contracts) result, err := runner.RunTest(code, "test") require.NoError(t, err) @@ -2652,16 +2871,12 @@ func TestReplacingImports(t *testing.T) { const code = ` import Test - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.createAccount() pub fun setup() { - let contractCode = Test.readFile("./sample/contract.cdc") - - let err = blockchain.deployContract( + let err = Test.deployContract( name: "Foo", - code: contractCode, - account: account, + path: "./sample/contract.cdc", arguments: [], ) @@ -2672,9 +2887,9 @@ func TestReplacingImports(t *testing.T) { pub fun test() { let script = Test.readFile("./sample/script.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) - Test.expect(result, Test.beFailed()) + Test.expect(result, Test.beSucceeded()) if result.status == Test.ResultStatus.failed { panic(result.error!.message) } @@ -2683,11 +2898,11 @@ func TestReplacingImports(t *testing.T) { ` const contractCode = ` - pub contract Foo{ + pub contract Foo { init() {} pub fun sayHello(): String { - return "hello from Foo" + return "hello from Foo" } } ` @@ -2707,20 +2922,21 @@ func TestReplacingImports(t *testing.T) { case "./sample/contract.cdc": return contractCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } - runner := NewTestRunner().WithFileResolver(fileResolver) + contracts := map[string]common.Address{ + "Foo": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithFileResolver(fileResolver). + WithContracts(contracts) result, err := runner.RunTest(code, "test") require.NoError(t, err) - require.Error(t, result.Error) - assert.Contains( - t, - result.Error.Error(), - "expecting an AddressLocation, but other location types are passed", - ) + require.NoError(t, result.Error) }) t.Run("config with missing imports", func(t *testing.T) { @@ -2729,19 +2945,21 @@ func TestReplacingImports(t *testing.T) { const code = ` import Test - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.getAccount(0x0000000000000005) pub fun setup() { - // Configurations provided, but some imports are missing. - blockchain.useConfiguration(Test.Configuration({ - "./FooContract": account.address - })) + let err = Test.deployContract( + name: "Foo", + path: "./FooContract", + arguments: [], + ) + + Test.expect(err, Test.beNil()) } pub fun test() { let script = Test.readFile("./sample/script.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beFailed()) if result.status == Test.ResultStatus.failed { @@ -2753,23 +2971,41 @@ func TestReplacingImports(t *testing.T) { const scriptCode = ` import Foo from "./FooContract" - import Foo from "./BarContract" // This is missing in configs + import Bar from "./BarContract" // This is missing in configs pub fun main(): String { return Foo.sayHello() } ` + const contractCode = ` + pub contract Foo { + init() {} + + pub fun sayHello(): String { + return "hello from Foo" + } + } + ` + fileResolver := func(path string) (string, error) { switch path { case "./sample/script.cdc": return scriptCode, nil + case "./FooContract": + return contractCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } - runner := NewTestRunner().WithFileResolver(fileResolver) + contracts := map[string]common.Address{ + "Foo": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithFileResolver(fileResolver). + WithContracts(contracts) result, err := runner.RunTest(code, "test") require.NoError(t, err) @@ -2786,26 +3022,26 @@ func TestReplaceImports(t *testing.T) { t.Parallel() emulatorBackend := NewEmulatorBackend(nil, nil, nil) - emulatorBackend.UseConfiguration(&stdlib.Configuration{ - Addresses: map[string]common.Address{ - "./sample/contract1.cdc": {0x1}, - "./sample/contract2.cdc": {0x2}, - "./sample/contract3.cdc": {0x3}, - }, - }) + emulatorBackend.contracts = map[string]common.Address{ + "C1": {0, 0, 0, 0, 0, 0, 0, 1}, + "C2": {0, 0, 0, 0, 0, 0, 0, 2}, + "C3": {0, 0, 0, 0, 0, 0, 0, 1}, + } const code = ` import C1 from "./sample/contract1.cdc" - import C2 from "./sample/contract2.cdc" - import C3 from "./sample/contract3.cdc" + import C2 from "C2" + import "C3" + import C4 from 0x0000000000000009 pub fun main() {} ` const expected = ` - import C1 from 0x0100000000000000 - import C2 from 0x0200000000000000 - import C3 from 0x0300000000000000 + import C1 from 0x0000000000000001 + import C2 from 0x0000000000000002 + import C3 from 0x0000000000000001 + import C4 from 0x0000000000000009 pub fun main() {} ` @@ -2822,15 +3058,12 @@ func TestGetAccountFlowBalance(t *testing.T) { import Test import BlockchainHelpers - pub let blockchain = Test.newEmulatorBlockchain() - pub let helpers = BlockchainHelpers(blockchain: blockchain) - pub fun testGetFlowBalance() { // Arrange - let account = blockchain.serviceAccount() + let account = Test.serviceAccount() // Act - let balance = helpers.getFlowBalance(for: account) + let balance = getFlowBalance(for: account) // Assert Test.assertEqual(1000000000.0, balance) @@ -2851,19 +3084,16 @@ func TestGetCurrentBlockHeight(t *testing.T) { import Test import BlockchainHelpers - pub let blockchain = Test.newEmulatorBlockchain() - pub let helpers = BlockchainHelpers(blockchain: blockchain) - pub fun testGetCurrentBlockHeight() { // Act - let height = helpers.getCurrentBlockHeight() + let height = getCurrentBlockHeight() // Assert Test.expect(height, Test.beGreaterThan(1 as UInt64)) // Act - blockchain.commitBlock() - let newHeight = helpers.getCurrentBlockHeight() + Test.commitBlock() + let newHeight = getCurrentBlockHeight() // Assert Test.assertEqual(newHeight, height + 1) @@ -2884,18 +3114,15 @@ func TestMintFlow(t *testing.T) { import Test import BlockchainHelpers - pub let blockchain = Test.newEmulatorBlockchain() - pub let helpers = BlockchainHelpers(blockchain: blockchain) - pub fun testMintFlow() { // Arrange - let account = blockchain.createAccount() + let account = Test.createAccount() // Act - helpers.mintFlow(to: account, amount: 1500.0) + mintFlow(to: account, amount: 1500.0) // Assert - let balance = helpers.getFlowBalance(for: account) + let balance = getFlowBalance(for: account) Test.assertEqual(1500.0, balance) } ` @@ -2914,25 +3141,22 @@ func TestBurnFlow(t *testing.T) { import Test import BlockchainHelpers - pub let blockchain = Test.newEmulatorBlockchain() - pub let helpers = BlockchainHelpers(blockchain: blockchain) - pub fun testBurnFlow() { // Arrange - let account = blockchain.createAccount() + let account = Test.createAccount() // Act - helpers.mintFlow(to: account, amount: 1500.0) + mintFlow(to: account, amount: 1500.0) // Assert - var balance = helpers.getFlowBalance(for: account) + var balance = getFlowBalance(for: account) Test.assertEqual(1500.0, balance) // Act - helpers.burnFlow(from: account, amount: 500.0) + burnFlow(from: account, amount: 500.0) // Assert - balance = helpers.getFlowBalance(for: account) + balance = getFlowBalance(for: account) Test.assertEqual(1000.0, balance) } ` @@ -2968,11 +3192,9 @@ func TestServiceAccount(t *testing.T) { const testCode = ` import Test - pub let blockchain = Test.newEmulatorBlockchain() - pub fun testGetServiceAccount() { // Act - let account = blockchain.serviceAccount() + let account = Test.serviceAccount() // Assert Test.assertEqual(Type
(), account.address.getType()) @@ -2995,15 +3217,12 @@ func TestServiceAccount(t *testing.T) { import Test import BlockchainHelpers - pub let blockchain = Test.newEmulatorBlockchain() - pub let helpers = BlockchainHelpers(blockchain: blockchain) - pub fun testGetServiceAccountBalance() { // Arrange - let account = blockchain.serviceAccount() + let account = Test.serviceAccount() // Act - let balance = helpers.getFlowBalance(for: account) + let balance = getFlowBalance(for: account) // Assert Test.assertEqual(1000000000.0, balance) @@ -3011,8 +3230,8 @@ func TestServiceAccount(t *testing.T) { pub fun testTransferFlowTokens() { // Arrange - let account = blockchain.serviceAccount() - let receiver = blockchain.createAccount() + let account = Test.serviceAccount() + let receiver = Test.createAccount() let code = Test.readFile("../transactions/transfer_flow_tokens.cdc") let tx = Test.Transaction( @@ -3023,11 +3242,11 @@ func TestServiceAccount(t *testing.T) { ) // Act - let txResult = blockchain.executeTransaction(tx) + let txResult = Test.executeTransaction(tx) Test.expect(txResult, Test.beSucceeded()) // Assert - let balance = helpers.getFlowBalance(for: receiver) + let balance = getFlowBalance(for: receiver) Test.assertEqual(1500.0, balance) } ` @@ -3058,7 +3277,7 @@ func TestServiceAccount(t *testing.T) { case "../transactions/transfer_flow_tokens.cdc": return transactionCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } @@ -3117,9 +3336,17 @@ func TestCoverageReportForUnitTests(t *testing.T) { const code = ` import Test - import FooContract from "FooContract.cdc" + import FooContract from "../contracts/FooContract.cdc" - pub let foo = FooContract() + pub fun setup() { + let err = Test.deployContract( + name: "FooContract", + path: "../contracts/FooContract.cdc", + arguments: [] + ) + + Test.expect(err, Test.beNil()) + } pub fun testGetIntegerTrait() { // Arrange @@ -3137,7 +3364,7 @@ func TestCoverageReportForUnitTests(t *testing.T) { for input in testInputs.keys { // Act - let result = foo.getIntegerTrait(input) + let result = FooContract.getIntegerTrait(input) // Assert Test.assertEqual(result, testInputs[input]!) @@ -3146,40 +3373,68 @@ func TestCoverageReportForUnitTests(t *testing.T) { pub fun testAddSpecialNumber() { // Act - foo.addSpecialNumber(78557, "Sierpinski") + FooContract.addSpecialNumber(78557, "Sierpinski") // Assert - Test.assertEqual("Sierpinski", foo.getIntegerTrait(78557)) + Test.assertEqual("Sierpinski", FooContract.getIntegerTrait(78557)) } ` - importResolver := func(location common.Location) (string, error) { - if location == common.StringLocation("FooContract.cdc") { + fileResolver := func(path string) (string, error) { + switch path { + case "../contracts/FooContract.cdc": return fooContract, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) } + } - return "", fmt.Errorf("unsupported import %s", location) + importResolver := func(location common.Location) (string, error) { + switch location := location.(type) { + case common.AddressLocation: + if location.Name == "FooContract" { + return fooContract, nil + } + case common.StringLocation: + if location == "../contracts/FooContract.cdc" { + return fooContract, nil + } + } + + return "", fmt.Errorf("cannot find import location: %s", location.ID()) + } + + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 9}, } coverageReport := runtime.NewCoverageReport() + coverageReport.WithLocationFilter(func(location common.Location) bool { + _, addressLoc := location.(common.AddressLocation) + _, stringLoc := location.(common.StringLocation) + // We only allow inspection of AddressLocation or StringLocation + return addressLoc || stringLoc + }) runner := NewTestRunner(). + WithFileResolver(fileResolver). WithImportResolver(importResolver). - WithCoverageReport(coverageReport) + WithCoverageReport(coverageReport). + WithContracts(contracts) results, err := runner.RunTests(code) - require.NoError(t, err) + require.NoError(t, err) require.Len(t, results, 2) + for _, result := range results { + assert.NoError(t, result.Error) + } - result1 := results[0] - assert.Equal(t, result1.TestName, "testGetIntegerTrait") - assert.NoError(t, result1.Error) - - result2 := results[1] - assert.Equal(t, result2.TestName, "testAddSpecialNumber") - require.NoError(t, result2.Error) - - location := common.StringLocation("FooContract.cdc") + address, err := common.HexToAddress("0x0000000000000009") + require.NoError(t, err) + location := common.AddressLocation{ + Address: address, + Name: "FooContract", + } coverage := coverageReport.Coverage[location] assert.Equal(t, []int{}, coverage.MissedLines()) @@ -3188,7 +3443,7 @@ func TestCoverageReportForUnitTests(t *testing.T) { assert.EqualValues( t, map[int]int{ - 6: 1, 14: 1, 18: 10, 19: 1, 20: 9, 21: 1, 22: 8, 23: 1, + 6: 2, 14: 1, 18: 10, 19: 1, 20: 9, 21: 1, 22: 8, 23: 1, 24: 7, 25: 1, 26: 6, 27: 1, 30: 5, 31: 4, 34: 1, }, coverage.LineHits, @@ -3197,9 +3452,25 @@ func TestCoverageReportForUnitTests(t *testing.T) { assert.ElementsMatch( t, []string{ - "s.7465737400000000000000000000000000000000000000000000000000000000", - "I.Crypto", + "A.0000000000000001.FlowClusterQC", + "A.0000000000000001.NFTStorefront", + "A.0000000000000002.FungibleToken", + "A.0000000000000001.NodeVersionBeacon", + "A.0000000000000003.FlowToken", + "A.0000000000000001.FlowEpoch", + "A.0000000000000001.FlowIDTableStaking", + "A.0000000000000001.NFTStorefrontV2", + "A.0000000000000001.FlowStakingCollection", + "A.0000000000000001.FlowServiceAccount", + "A.0000000000000001.FlowStorageFees", + "A.0000000000000001.LockedTokens", + "A.0000000000000001.FlowDKG", + "A.0000000000000004.FlowFees", + "A.0000000000000001.ExampleNFT", + "A.0000000000000001.StakingProxy", "I.Test", + "I.Crypto", + "s.7465737400000000000000000000000000000000000000000000000000000000", }, coverageReport.ExcludedLocationIDs(), ) @@ -3283,28 +3554,21 @@ func TestCoverageReportForIntegrationTests(t *testing.T) { const testCode = ` import Test - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.getAccount(0x0000000000000009) pub fun setup() { - let contractCode = Test.readFile("../contracts/FooContract.cdc") - let err = blockchain.deployContract( + let err = Test.deployContract( name: "FooContract", - code: contractCode, - account: account, + path: "../contracts/FooContract.cdc", arguments: [] ) Test.expect(err, Test.beNil()) - - blockchain.useConfiguration(Test.Configuration({ - "../contracts/FooContract.cdc": account.address - })) } pub fun testGetIntegerTrait() { let script = Test.readFile("../scripts/get_integer_traits.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) Test.assert(result.returnValue! as! Bool) @@ -3319,7 +3583,7 @@ func TestCoverageReportForIntegrationTests(t *testing.T) { arguments: [78557, "Sierpinski"] ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beSucceeded()) } ` @@ -3349,10 +3613,14 @@ func TestCoverageReportForIntegrationTests(t *testing.T) { case "../transactions/add_special_number.cdc": return transactionCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 9}, + } + coverageReport := runtime.NewCoverageReport() coverageReport.WithLocationFilter(func(location common.Location) bool { _, addressLoc := location.(common.AddressLocation) @@ -3362,7 +3630,8 @@ func TestCoverageReportForIntegrationTests(t *testing.T) { }) runner := NewTestRunner(). WithFileResolver(fileResolver). - WithCoverageReport(coverageReport) + WithCoverageReport(coverageReport). + WithContracts(contracts) results, err := runner.RunTests(testCode) require.NoError(t, err) @@ -3377,7 +3646,7 @@ func TestCoverageReportForIntegrationTests(t *testing.T) { assert.Equal(t, result2.TestName, "testAddSpecialNumber") require.NoError(t, result2.Error) - address, err := common.HexToAddress("0x0000000000000005") + address, err := common.HexToAddress("0x0000000000000009") require.NoError(t, err) location := common.AddressLocation{ Address: address, @@ -3461,15 +3730,21 @@ func TestRetrieveLogsFromUnitTests(t *testing.T) { import Test import FooContract from "FooContract.cdc" - pub let foo = FooContract() - pub fun setup() { + let err = Test.deployContract( + name: "FooContract", + path: "FooContract.cdc", + arguments: [] + ) + + Test.expect(err, Test.beNil()) + log("setup successful") } pub fun testGetIntegerTrait() { // Act - let result = foo.getIntegerTrait(1729) + let result = FooContract.getIntegerTrait(1729) // Assert Test.assertEqual("Harshad", result) @@ -3478,23 +3753,46 @@ func TestRetrieveLogsFromUnitTests(t *testing.T) { pub fun testAddSpecialNumber() { // Act - foo.addSpecialNumber(78557, "Sierpinski") + FooContract.addSpecialNumber(78557, "Sierpinski") // Assert - Test.assertEqual("Sierpinski", foo.getIntegerTrait(78557)) + Test.assertEqual("Sierpinski", FooContract.getIntegerTrait(78557)) log("addSpecialNumber works") } ` - importResolver := func(location common.Location) (string, error) { - if location == common.StringLocation("FooContract.cdc") { + fileResolver := func(path string) (string, error) { + switch path { + case "FooContract.cdc": return fooContract, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) + } + } + + importResolver := func(location common.Location) (string, error) { + switch location := location.(type) { + case common.AddressLocation: + if location.Name == "FooContract" { + return fooContract, nil + } + case common.StringLocation: + if location == "FooContract.cdc" { + return fooContract, nil + } } return "", fmt.Errorf("unsupported import %s", location) } - runner := NewTestRunner().WithImportResolver(importResolver) + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithImportResolver(importResolver). + WithFileResolver(fileResolver). + WithContracts(contracts) results, err := runner.RunTests(code) require.NoError(t, err) @@ -3545,11 +3843,19 @@ func TestRetrieveEmptyLogsFromUnitTests(t *testing.T) { import Test import FooContract from "FooContract.cdc" - pub let foo = FooContract() + pub fun setup() { + let err = Test.deployContract( + name: "FooContract", + path: "FooContract.cdc", + arguments: [] + ) + + Test.expect(err, Test.beNil()) + } pub fun testGetIntegerTrait() { - // Act - let result = foo.getIntegerTrait(1729) + // Act + let result = FooContract.getIntegerTrait(1729) // Assert Test.assertEqual("Harshad", result) @@ -3557,22 +3863,45 @@ func TestRetrieveEmptyLogsFromUnitTests(t *testing.T) { pub fun testAddSpecialNumber() { // Act - foo.addSpecialNumber(78557, "Sierpinski") + FooContract.addSpecialNumber(78557, "Sierpinski") // Assert - Test.assertEqual("Sierpinski", foo.getIntegerTrait(78557)) + Test.assertEqual("Sierpinski", FooContract.getIntegerTrait(78557)) } ` - importResolver := func(location common.Location) (string, error) { - if location == common.StringLocation("FooContract.cdc") { + fileResolver := func(path string) (string, error) { + switch path { + case "FooContract.cdc": return fooContract, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) + } + } + + importResolver := func(location common.Location) (string, error) { + switch location := location.(type) { + case common.AddressLocation: + if location.Name == "FooContract" { + return fooContract, nil + } + case common.StringLocation: + if location == "FooContract.cdc" { + return fooContract, nil + } } return "", fmt.Errorf("unsupported import %s", location) } - runner := NewTestRunner().WithImportResolver(importResolver) + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithImportResolver(importResolver). + WithFileResolver(fileResolver). + WithContracts(contracts) results, err := runner.RunTests(code) require.NoError(t, err) @@ -3629,28 +3958,21 @@ func TestRetrieveLogsFromIntegrationTests(t *testing.T) { const testCode = ` import Test - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.getAccount(0x0000000000000005) pub fun setup() { - let contractCode = Test.readFile("../contracts/FooContract.cdc") - let err = blockchain.deployContract( + let err = Test.deployContract( name: "FooContract", - code: contractCode, - account: account, + path: "../contracts/FooContract.cdc", arguments: [] ) Test.expect(err, Test.beNil()) - - blockchain.useConfiguration(Test.Configuration({ - "../contracts/FooContract.cdc": account.address - })) } pub fun testGetIntegerTrait() { let script = Test.readFile("../scripts/get_integer_traits.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) Test.assert(result.returnValue! as! Bool) @@ -3665,7 +3987,7 @@ func TestRetrieveLogsFromIntegrationTests(t *testing.T) { arguments: [78557, "Sierpinski"] ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beSucceeded()) } @@ -3676,7 +3998,7 @@ func TestRetrieveLogsFromIntegrationTests(t *testing.T) { "specialNumbers updated", "addSpecialNumber works" ] - Test.assertEqual(expectedLogs, blockchain.logs()) + Test.assertEqual(expectedLogs, Test.logs()) } ` @@ -3706,11 +4028,17 @@ func TestRetrieveLogsFromIntegrationTests(t *testing.T) { case "../transactions/add_special_number.cdc": return transactionCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } - runner := NewTestRunner().WithFileResolver(fileResolver) + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithFileResolver(fileResolver). + WithContracts(contracts) results, err := runner.RunTests(testCode) require.NoError(t, err) @@ -3761,28 +4089,21 @@ func TestRetrieveEmptyLogsFromIntegrationTests(t *testing.T) { const testCode = ` import Test - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.getAccount(0x0000000000000005) pub fun setup() { - let contractCode = Test.readFile("../contracts/FooContract.cdc") - let err = blockchain.deployContract( + let err = Test.deployContract( name: "FooContract", - code: contractCode, - account: account, + path: "../contracts/FooContract.cdc", arguments: [] ) Test.expect(err, Test.beNil()) - - blockchain.useConfiguration(Test.Configuration({ - "../contracts/FooContract.cdc": account.address - })) } pub fun testGetIntegerTrait() { let script = Test.readFile("../scripts/get_integer_traits.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) Test.assert(result.returnValue! as! Bool) @@ -3797,12 +4118,12 @@ func TestRetrieveEmptyLogsFromIntegrationTests(t *testing.T) { arguments: [78557, "Sierpinski"] ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beSucceeded()) } pub fun tearDown() { - Test.assertEqual([] as [String], blockchain.logs() ) + Test.assertEqual([] as [String], Test.logs() ) } ` @@ -3831,11 +4152,17 @@ func TestRetrieveEmptyLogsFromIntegrationTests(t *testing.T) { case "../transactions/add_special_number.cdc": return transactionCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } - runner := NewTestRunner().WithFileResolver(fileResolver) + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + + runner := NewTestRunner(). + WithFileResolver(fileResolver). + WithContracts(contracts) results, err := runner.RunTests(testCode) require.NoError(t, err) @@ -3889,36 +4216,29 @@ func TestGetEventsFromIntegrationTests(t *testing.T) { const testCode = ` import Test - import FooContract from 0x0000000000000005 + import FooContract from "../contracts/FooContract.cdc" - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.getAccount(0x0000000000000005) pub fun setup() { - let contractCode = Test.readFile("../contracts/FooContract.cdc") - let err = blockchain.deployContract( + let err = Test.deployContract( name: "FooContract", - code: contractCode, - account: account, + path: "../contracts/FooContract.cdc", arguments: [] ) Test.expect(err, Test.beNil()) - - blockchain.useConfiguration(Test.Configuration({ - "../contracts/FooContract.cdc": account.address - })) } pub fun testGetIntegerTrait() { let script = Test.readFile("../scripts/get_integer_traits.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) Test.assert(result.returnValue! as! Bool) let typ = Type() - let events = blockchain.eventsOfType(typ) + let events = Test.eventsOfType(typ) Test.assertEqual(1, events.length) } @@ -3931,18 +4251,18 @@ func TestGetEventsFromIntegrationTests(t *testing.T) { arguments: [78557, "Sierpinski"] ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beSucceeded()) let typ = Type() - let events = blockchain.eventsOfType(typ) + let events = Test.eventsOfType(typ) Test.assertEqual(1, events.length) let event = events[0] as! FooContract.NumberAdded Test.assertEqual(78557, event.n) Test.assertEqual("Sierpinski", event.trait) - let evts = blockchain.events() + let evts = Test.events() Test.expect(evts.length, Test.beGreaterThan(1)) } ` @@ -3972,7 +4292,7 @@ func TestGetEventsFromIntegrationTests(t *testing.T) { case "../transactions/add_special_number.cdc": return transactionCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } @@ -3982,14 +4302,23 @@ func TestGetEventsFromIntegrationTests(t *testing.T) { if location.Name == "FooContract" { return contractCode, nil } + case common.StringLocation: + if location == "../contracts/FooContract.cdc" { + return contractCode, nil + } } return "", fmt.Errorf("cannot find import location: %s", location.ID()) } + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + runner := NewTestRunner(). WithFileResolver(fileResolver). - WithImportResolver(importResolver) + WithImportResolver(importResolver). + WithContracts(contracts) results, err := runner.RunTests(testCode) require.NoError(t, err) @@ -4032,8 +4361,7 @@ func TestImportingHelperFile(t *testing.T) { import Test import "test_helpers.cdc" - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.createAccount() pub fun testRunTransaction() { let tx = createTransaction( @@ -4042,7 +4370,7 @@ func TestImportingHelperFile(t *testing.T) { args: [] ) - let result = blockchain.executeTransaction(tx) + let result = Test.executeTransaction(tx) Test.expect(result, Test.beSucceeded()) } ` @@ -4052,7 +4380,7 @@ func TestImportingHelperFile(t *testing.T) { case "../transactions/add_special_number.cdc": return transactionCode, nil default: - return "", fmt.Errorf("cannot find file: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } @@ -4085,30 +4413,27 @@ func TestBlockchainReset(t *testing.T) { import Test import BlockchainHelpers - pub let blockchain = Test.newEmulatorBlockchain() - pub let helpers = BlockchainHelpers(blockchain: blockchain) - pub fun testBlockchainReset() { // Arrange - let account = blockchain.createAccount() - var balance = helpers.getFlowBalance(for: account) + let account = Test.createAccount() + var balance = getFlowBalance(for: account) Test.assertEqual(0.0, balance) - let height = helpers.getCurrentBlockHeight() + let height = getCurrentBlockHeight() - helpers.mintFlow(to: account, amount: 1500.0) + mintFlow(to: account, amount: 1500.0) - balance = helpers.getFlowBalance(for: account) + balance = getFlowBalance(for: account) Test.assertEqual(1500.0, balance) - Test.assertEqual(helpers.getCurrentBlockHeight(), height + 1) + Test.assertEqual(getCurrentBlockHeight(), height + 1) // Act - blockchain.reset(to: height) + Test.reset(to: height) // Assert - balance = helpers.getFlowBalance(for: account) + balance = getFlowBalance(for: account) Test.assertEqual(0.0, balance) - Test.assertEqual(helpers.getCurrentBlockHeight(), height) + Test.assertEqual(getCurrentBlockHeight(), height) } ` @@ -4213,33 +4538,26 @@ func TestBlockchainMoveTime(t *testing.T) { const testCode = ` import Test - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.getAccount(0x0000000000000005) pub var lockedAt: UFix64 = 0.0 pub fun setup() { let currentBlockTimestamp = Test.readFile("current_block_timestamp.cdc") - let result = blockchain.executeScript(currentBlockTimestamp, []) + let result = Test.executeScript(currentBlockTimestamp, []) lockedAt = result.returnValue! as! UFix64 - let contractCode = Test.readFile("TimeLocker.cdc") - let err = blockchain.deployContract( + let err = Test.deployContract( name: "TimeLocker", - code: contractCode, - account: account, + path: "TimeLocker.cdc", arguments: [lockedAt] ) Test.expect(err, Test.beNil()) - - blockchain.useConfiguration(Test.Configuration({ - "TimeLocker": account.address - })) } pub fun testIsNotOpen() { let isLockerOpen = Test.readFile("is_locker_open.cdc") - let result = blockchain.executeScript(isLockerOpen, []) + let result = Test.executeScript(isLockerOpen, []) Test.expect(result, Test.beSucceeded()) Test.assertEqual(false, result.returnValue! as! Bool) @@ -4248,25 +4566,25 @@ func TestBlockchainMoveTime(t *testing.T) { pub fun testIsOpen() { // timeDelta is the representation of 20 days, in seconds let timeDelta = Fix64(20 * 24 * 60 * 60) - blockchain.moveTime(by: timeDelta) + Test.moveTime(by: timeDelta) let isLockerOpen = Test.readFile("is_locker_open.cdc") - var result = blockchain.executeScript(isLockerOpen, []) + var result = Test.executeScript(isLockerOpen, []) Test.expect(result, Test.beSucceeded()) Test.assertEqual(false, result.returnValue! as! Bool) // We move time forward by another 20 days - blockchain.moveTime(by: timeDelta) + Test.moveTime(by: timeDelta) - result = blockchain.executeScript(isLockerOpen, []) + result = Test.executeScript(isLockerOpen, []) Test.assertEqual(true, result.returnValue! as! Bool) // We move time backward by 20 days - blockchain.moveTime(by: timeDelta * -1.0) + Test.moveTime(by: timeDelta * -1.0) - result = blockchain.executeScript(isLockerOpen, []) + result = Test.executeScript(isLockerOpen, []) Test.assertEqual(false, result.returnValue! as! Bool) } @@ -4281,17 +4599,33 @@ func TestBlockchainMoveTime(t *testing.T) { case "current_block_timestamp.cdc": return currentBlockTimestamp, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } importResolver := func(location common.Location) (string, error) { - return "", nil + switch location := location.(type) { + case common.AddressLocation: + if location.Name == "TimeLocker" { + return contractCode, nil + } + case common.StringLocation: + if location == "TimeLocker.cdc" { + return contractCode, nil + } + } + + return "", fmt.Errorf("cannot find import location: %s", location.ID()) + } + + contracts := map[string]common.Address{ + "TimeLocker": {0, 0, 0, 0, 0, 0, 0, 5}, } runner := NewTestRunner(). WithFileResolver(fileResolver). - WithImportResolver(importResolver) + WithImportResolver(importResolver). + WithContracts(contracts) results, err := runner.RunTests(testCode) require.NoError(t, err) @@ -4339,44 +4673,6 @@ func TestRandomizedTestExecution(t *testing.T) { assert.Equal(t, expected, resultsStr) } -func TestNewEmulatorBlockchainCleanState(t *testing.T) { - t.Parallel() - - const code = ` - import Test - import BlockchainHelpers - - pub fun test() { - let blockchain = Test.newEmulatorBlockchain() - let helpers = BlockchainHelpers(blockchain: blockchain) - let account = blockchain.createAccount() - - let typ = CompositeType("flow.AccountCreated")! - let events = blockchain.eventsOfType(typ) - Test.assertEqual(1, events.length) - - let blockchain2 = Test.newEmulatorBlockchain() - let helpers2 = BlockchainHelpers(blockchain: blockchain2) - - let events2 = blockchain2.eventsOfType(typ) - Test.assertEqual(0, events2.length) - - Test.assert( - helpers.getCurrentBlockHeight() > helpers2.getCurrentBlockHeight() - ) - } - ` - - importResolver := func(location common.Location) (string, error) { - return "", nil - } - - runner := NewTestRunner().WithImportResolver(importResolver) - result, err := runner.RunTest(code, "test") - require.NoError(t, err) - require.NoError(t, result.Error) -} - func TestReferenceDeployedContractTypes(t *testing.T) { t.Parallel() @@ -4425,29 +4721,22 @@ func TestReferenceDeployedContractTypes(t *testing.T) { const testCode = ` import Test - import FooContract from 0x0000000000000005 + import FooContract from "../contracts/FooContract.cdc" - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.getAccount(0x0000000000000005) pub fun setup() { - let contractCode = Test.readFile("../contracts/FooContract.cdc") - let err = blockchain.deployContract( + let err = Test.deployContract( name: "FooContract", - code: contractCode, - account: account, + path: "../contracts/FooContract.cdc", arguments: [] ) Test.expect(err, Test.beNil()) - - blockchain.useConfiguration(Test.Configuration({ - "../contracts/FooContract.cdc": account.address - })) } pub fun testGetSpecialNumber() { let script = Test.readFile("../scripts/get_special_number.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) let specialNumbers = result.returnValue! as! [FooContract.SpecialNumber] @@ -4468,7 +4757,7 @@ func TestReferenceDeployedContractTypes(t *testing.T) { case "../scripts/get_special_number.cdc": return scriptCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } @@ -4478,14 +4767,23 @@ func TestReferenceDeployedContractTypes(t *testing.T) { if location.Name == "FooContract" { return contractCode, nil } + case common.StringLocation: + if location == "../contracts/FooContract.cdc" { + return contractCode, nil + } } return "", fmt.Errorf("cannot find import location: %s", location.ID()) } + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + runner := NewTestRunner(). WithFileResolver(fileResolver). - WithImportResolver(importResolver) + WithImportResolver(importResolver). + WithContracts(contracts) results, err := runner.RunTests(testCode) require.NoError(t, err) @@ -4548,29 +4846,22 @@ func TestReferenceDeployedContractTypes(t *testing.T) { const testCode = ` import Test - import FooContract from 0x0000000000000005 + import FooContract from "../contracts/FooContract.cdc" - pub let blockchain = Test.newEmulatorBlockchain() - pub let account = blockchain.createAccount() + pub let account = Test.getAccount(0x0000000000000005) pub fun setup() { - let contractCode = Test.readFile("../contracts/FooContract.cdc") - let err = blockchain.deployContract( + let err = Test.deployContract( name: "FooContract", - code: contractCode, - account: account, + path: "../contracts/FooContract.cdc", arguments: [{1729: "Harshad"}] ) Test.expect(err, Test.beNil()) - - blockchain.useConfiguration(Test.Configuration({ - "../contracts/FooContract.cdc": account.address - })) } pub fun testGetSpecialNumber() { let script = Test.readFile("../scripts/get_special_number.cdc") - let result = blockchain.executeScript(script, []) + let result = Test.executeScript(script, []) Test.expect(result, Test.beSucceeded()) let specialNumbers = result.returnValue! as! [FooContract.SpecialNumber] @@ -4582,31 +4873,6 @@ func TestReferenceDeployedContractTypes(t *testing.T) { Test.assertEqual(1729, specialNumber.n) Test.assertEqual("Harshad", specialNumber.trait) } - - pub fun testNewDeploymentWithEmptyArgs() { - let contractCode = Test.readFile("../contracts/FooContract.cdc") - let blockchain2 = Test.newEmulatorBlockchain() - let account2 = blockchain2.createAccount() - let args: {Int: String} = {} - let err = blockchain2.deployContract( - name: "FooContract", - code: contractCode, - account: account2, - arguments: [args] - ) - Test.expect(err, Test.beNil()) - - blockchain2.useConfiguration(Test.Configuration({ - "../contracts/FooContract.cdc": account2.address - })) - - let script = Test.readFile("../scripts/get_special_number.cdc") - let result = blockchain2.executeScript(script, []) - Test.expect(result, Test.beSucceeded()) - - let specialNumbers = result.returnValue! as! [FooContract.SpecialNumber] - Test.expect(specialNumbers, Test.beEmpty()) - } ` fileResolver := func(path string) (string, error) { @@ -4616,7 +4882,7 @@ func TestReferenceDeployedContractTypes(t *testing.T) { case "../scripts/get_special_number.cdc": return scriptCode, nil default: - return "", fmt.Errorf("cannot find import location: %s", path) + return "", fmt.Errorf("cannot find file path: %s", path) } } @@ -4626,14 +4892,23 @@ func TestReferenceDeployedContractTypes(t *testing.T) { if location.Name == "FooContract" { return contractCode, nil } + case common.StringLocation: + if location == "../contracts/FooContract.cdc" { + return contractCode, nil + } } return "", fmt.Errorf("cannot find import location: %s", location.ID()) } + contracts := map[string]common.Address{ + "FooContract": {0, 0, 0, 0, 0, 0, 0, 5}, + } + runner := NewTestRunner(). WithFileResolver(fileResolver). - WithImportResolver(importResolver) + WithImportResolver(importResolver). + WithContracts(contracts) results, err := runner.RunTests(testCode) require.NoError(t, err) @@ -4650,27 +4925,24 @@ func TestEmulatorBlockchainSnapshotting(t *testing.T) { import Test import BlockchainHelpers - pub let blockchain = Test.newEmulatorBlockchain() - pub let helpers = BlockchainHelpers(blockchain: blockchain) - pub fun test() { - let admin = blockchain.createAccount() - blockchain.createSnapshot(name: "adminCreated") + let admin = Test.createAccount() + Test.createSnapshot(name: "adminCreated") - helpers.mintFlow(to: admin, amount: 1000.0) - blockchain.createSnapshot(name: "adminFunded") + mintFlow(to: admin, amount: 1000.0) + Test.createSnapshot(name: "adminFunded") - var balance = helpers.getFlowBalance(for: admin) + var balance = getFlowBalance(for: admin) Test.assertEqual(1000.0, balance) - blockchain.loadSnapshot(name: "adminCreated") + Test.loadSnapshot(name: "adminCreated") - balance = helpers.getFlowBalance(for: admin) + balance = getFlowBalance(for: admin) Test.assertEqual(0.0, balance) - blockchain.loadSnapshot(name: "adminFunded") + Test.loadSnapshot(name: "adminFunded") - balance = helpers.getFlowBalance(for: admin) + balance = getFlowBalance(for: admin) Test.assertEqual(1000.0, balance) } ` diff --git a/test/test_runner.go b/test/test_runner.go index ab2b94cb..363ccdd8 100644 --- a/test/test_runner.go +++ b/test/test_runner.go @@ -28,7 +28,6 @@ import ( "github.com/logrusorgru/aurora" "github.com/rs/zerolog" - "github.com/onflow/flow-emulator/emulator" "github.com/onflow/flow-go/engine/execution/testutil" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/environment" @@ -130,7 +129,6 @@ type TestRunner struct { importResolver ImportResolver // fileResolver is used to resolve local files. - // fileResolver FileResolver testRuntime runtime.Runtime @@ -149,9 +147,9 @@ type TestRunner struct { // randomSeed is used for randomized test case execution. randomSeed int64 - // blockchain is mainly used to obtain system-defined - // contracts & their exposed types - blockchain *emulator.Blockchain + contracts map[string]common.Address + + backend *EmulatorBackend } func NewTestRunner() *TestRunner { @@ -167,23 +165,35 @@ func NewTestRunner() *TestRunner { ) } logger := zerolog.New(output).With().Timestamp().Logger().Hook(logCollectionHook) - blockchain, err := emulator.New( - emulator.WithStorageLimitEnabled(false), - emulator.Contracts(commonContracts), - emulator.WithChainID(chain.ChainID()), - ) - if err != nil { - panic(err) - } return &TestRunner{ testRuntime: runtime.NewInterpreterRuntime(runtime.Config{}), logCollection: logCollectionHook, logger: logger, - blockchain: blockchain, + contracts: baseContracts(), } } +func baseContracts() map[string]common.Address { + contracts := make(map[string]common.Address, 0) + serviceAddress := common.Address(chain.ServiceAddress()) + contracts["NonFungibleToken"] = serviceAddress + contracts["MetadataViews"] = serviceAddress + contracts["ViewResolver"] = serviceAddress + for _, addressLocation := range systemContracts { + contract := addressLocation.Name + address := common.Address(addressLocation.Address) + contracts[contract] = address + } + for _, contractDescription := range commonContracts { + contract := contractDescription.Name + address := common.Address(contractDescription.Address) + contracts[contract] = address + } + + return contracts +} + func (r *TestRunner) WithImportResolver(importResolver ImportResolver) *TestRunner { r.importResolver = importResolver return r @@ -204,6 +214,15 @@ func (r *TestRunner) WithRandomSeed(seed int64) *TestRunner { return r } +func (r *TestRunner) WithContracts(contracts map[string]common.Address) *TestRunner { + for contract, address := range contracts { + // We do not want to override the base configuration, + // which includes the mapping for system/common contracts. + r.contracts[contract] = address + } + return r +} + // RunTest runs a single test in the provided test script. func (r *TestRunner) RunTest(script string, funcName string) (result *Result, err error) { defer func() { @@ -312,6 +331,54 @@ func (r *TestRunner) RunTests(script string) (results Results, err error) { return results, err } +func (r *TestRunner) replaceImports(program *ast.Program, code string) string { + sb := strings.Builder{} + importDeclEnd := 0 + + for _, importDeclaration := range program.ImportDeclarations() { + prevImportDeclEnd := importDeclEnd + importDeclEnd = importDeclaration.EndPos.Offset + 1 + + location, ok := importDeclaration.Location.(common.StringLocation) + if !ok { + // keep the import statement it as-is + sb.WriteString(code[prevImportDeclEnd:importDeclEnd]) + continue + } + + var address common.Address + var found bool + if len(importDeclaration.Identifiers) > 0 { + address, found = r.contracts[importDeclaration.Identifiers[0].Identifier] + } else { + address, found = r.contracts[location.String()] + } + if !found { + // keep import statement it as-is + sb.WriteString(code[prevImportDeclEnd:importDeclEnd]) + continue + } + + var importStr string + if strings.Contains(importDeclaration.String(), "from") { + importStr = fmt.Sprintf("0x%s", address) + } else { + // Imports of the form `import "FungibleToken"` should be + // expanded to `import FungibleToken from 0xee82856bf20e2aa6` + importStr = fmt.Sprintf("%s from 0x%s", location, address) + } + + locationStart := importDeclaration.LocationPos.Offset + + sb.WriteString(code[prevImportDeclEnd:locationStart]) + sb.WriteString(importStr) + } + + sb.WriteString(code[importDeclEnd:]) + + return sb.String() +} + func (r *TestRunner) runTestSetup(inter *interpreter.Interpreter) error { if !hasSetup(inter) { return nil @@ -403,12 +470,6 @@ func (r *TestRunner) parseCheckAndInterpret(script string) (*interpreter.Program Location: testScriptLocation, Environment: env, } - if r.coverageReport != nil { - r.coverageReport.ExcludeLocation(stdlib.CryptoCheckerLocation) - r.coverageReport.ExcludeLocation(stdlib.TestContractLocation) - r.coverageReport.ExcludeLocation(testScriptLocation) - ctx.CoverageReport = r.coverageReport - } // Checker configs env.CheckerConfig.ImportHandler = r.checkerImportHandler(ctx) @@ -451,8 +512,28 @@ func (r *TestRunner) parseCheckAndInterpret(script string) (*interpreter.Program // Set the storage after checking, because `ParseAndCheckProgram` clears the storage. env.InterpreterConfig.Storage = runtime.NewStorage(ctx.Interface, nil) + script = r.replaceImports(program.Program, script) + newCtx := runtime.Context{ + Interface: newScriptEnvironment(r.logger), + Location: testScriptLocation, + Environment: env, + } + if r.coverageReport != nil { + r.coverageReport.ExcludeLocation(stdlib.CryptoCheckerLocation) + r.coverageReport.ExcludeLocation(stdlib.TestContractLocation) + r.coverageReport.ExcludeLocation(testScriptLocation) + newCtx.CoverageReport = r.coverageReport + } + program, err = r.testRuntime.ParseAndCheckProgram([]byte(script), newCtx) + if err != nil { + return nil, nil, err + } + + // Set the storage after checking, because `ParseAndCheckProgram` clears the storage. + env.InterpreterConfig.Storage = runtime.NewStorage(newCtx.Interface, nil) + _, inter, err := env.Interpret( - ctx.Location, + newCtx.Location, program, nil, ) @@ -504,29 +585,16 @@ func contractValueHandler( declaration *ast.CompositeDeclaration, compositeType *sema.CompositeType, ) sema.ValueDeclaration { - constructorType, constructorArgumentLabels := sema.CompositeLikeConstructorType( + _, constructorArgumentLabels := sema.CompositeLikeConstructorType( checker.Elaboration, declaration, compositeType, ) - // In unit tests, contracts are imported with string locations, e.g - // import FooContract from "../contracts/FooContract.cdc" - if _, ok := compositeType.Location.(common.StringLocation); ok { - return stdlib.StandardLibraryValue{ - Name: declaration.Identifier.Identifier, - Type: constructorType, - DocString: declaration.DocString, - Kind: declaration.DeclarationKind(), - Position: &declaration.Identifier.Pos, - ArgumentLabels: constructorArgumentLabels, - } - } - // For composite types (e.g. contracts) that are deployed on // EmulatorBackend's blockchain, we have to declare the - // define the value declaration as a composite. This is needed - // for nested types that are defined in the composite type, + // value declaration as a composite. This is needed to access + // nested types that are defined in the composite type, // e.g events / structs / resources / enums etc. return stdlib.StandardLibraryValue{ Name: declaration.Identifier.Identifier, @@ -566,6 +634,13 @@ func (r *TestRunner) interpreterContractValueHandler( stdlibHandler, r.coverageReport, ) + backend, ok := testFramework.NewEmulatorBackend().(*EmulatorBackend) + if !ok { + panic(fmt.Errorf("failed to retrieve EmulatorBackend")) + } + backend.fileResolver = r.fileResolver + backend.contracts = r.contracts + r.backend = backend contract, err := stdlib.GetTestContractType(). NewTestContract( inter, @@ -579,33 +654,27 @@ func (r *TestRunner) interpreterContractValueHandler( return contract default: - if _, ok := compositeType.Location.(common.AddressLocation); ok { - invocation, found := contractInvocations[compositeType.Identifier] - if !found { - panic(fmt.Errorf("contract invocation not found")) - } - parameterTypes := make([]sema.Type, len(compositeType.ConstructorParameters)) - for i, constructorParameter := range compositeType.ConstructorParameters { - parameterTypes[i] = constructorParameter.TypeAnnotation.Type - } - - value, err := inter.InvokeFunctionValue( - constructorGenerator(common.Address{}), - invocation.ConstructorArguments, - invocation.ArgumentTypes, - parameterTypes, - invocationRange, - ) - if err != nil { - panic(err) - } + invocation, found := contractInvocations[compositeType.Identifier] + if !found { + panic(fmt.Errorf("contract invocation not found")) + } + parameterTypes := make([]sema.Type, len(compositeType.ConstructorParameters)) + for i, constructorParameter := range compositeType.ConstructorParameters { + parameterTypes[i] = constructorParameter.TypeAnnotation.Type + } - return value.(*interpreter.CompositeValue) + value, err := inter.InvokeFunctionValue( + constructorGenerator(common.Address{}), + invocation.ConstructorArguments, + invocation.ArgumentTypes, + parameterTypes, + invocationRange, + ) + if err != nil { + panic(err) } - // During tests, imported contracts can be constructed using the constructor, - // similar to structs. Therefore, generate a constructor function. - return constructorGenerator(common.Address{}) + return value.(*interpreter.CompositeValue) } } } @@ -685,7 +754,7 @@ func (r *TestRunner) parseAndCheckImport( if ok { // System-defined contracts are obtained from // the blockchain. - account, err := r.blockchain.GetAccount( + account, err := r.backend.blockchain.GetAccount( flow.Address(addressLocation.Address), ) if err != nil { @@ -725,7 +794,7 @@ func (r *TestRunner) parseAndCheckImport( if ok { // System-defined contracts are obtained from // the blockchain. - account, err := r.blockchain.GetAccount( + account, err := r.backend.blockchain.GetAccount( flow.Address(addressLoc.Address), ) if err != nil { @@ -742,9 +811,27 @@ func (r *TestRunner) parseAndCheckImport( return sema.ElaborationImport{ Elaboration: program.Elaboration, }, nil - } else { - return nil, fmt.Errorf("nested imports are not supported") } + + stringLocation, ok := importedLocation.(common.StringLocation) + if ok { + code, err := r.importResolver(stringLocation) + if err != nil { + return nil, err + } + program, err := env.ParseAndCheckProgram( + []byte(code), stringLocation, true, + ) + if err != nil { + return nil, err + } + + return sema.ElaborationImport{ + Elaboration: program.Elaboration, + }, nil + } + + return nil, fmt.Errorf("unable to import location: %s", importedLocation) } }