From eecc40a74f47768f2114e9a6fb8d8da0fa01ec02 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Thu, 14 Sep 2023 14:57:44 +0300 Subject: [PATCH] Refine API of the testing framework These changes will allow testing providers to unify unit & integration tests. --- runtime/stdlib/contracts/test.cdc | 19 +- runtime/stdlib/test-framework.go | 5 +- runtime/stdlib/test_contract.go | 48 ++++ runtime/stdlib/test_emulatorbackend.go | 77 +++++- runtime/stdlib/test_test.go | 340 ++++++++++++++++++++----- 5 files changed, 399 insertions(+), 90 deletions(-) diff --git a/runtime/stdlib/contracts/test.cdc b/runtime/stdlib/contracts/test.cdc index b6041f7b9e..458e138db7 100644 --- a/runtime/stdlib/contracts/test.cdc +++ b/runtime/stdlib/contracts/test.cdc @@ -27,6 +27,12 @@ pub contract Test { return self.backend.createAccount() } + /// Returns the account for the given address. + /// + pub fun getAccount(_ address: Address): Account { + return self.backend.getAccount(address) + } + /// Add a transaction to the current block. /// pub fun addTransaction(_ tx: Transaction) { @@ -77,14 +83,12 @@ pub contract Test { /// pub fun deployContract( name: String, - code: String, - account: Account, + path: String, arguments: [AnyStruct] ): Error? { return self.backend.deployContract( name: name, - code: code, - account: account, + path: path, arguments: arguments ) } @@ -297,6 +301,10 @@ pub contract Test { /// pub fun createAccount(): Account + /// Returns the account for the given address. + /// + pub fun getAccount(_ address: Address): Account + /// Add a transaction to the current block. /// pub fun addTransaction(_ tx: Transaction) @@ -315,8 +323,7 @@ pub contract Test { /// pub fun deployContract( name: String, - code: String, - account: Account, + path: String, arguments: [AnyStruct] ): Error? diff --git a/runtime/stdlib/test-framework.go b/runtime/stdlib/test-framework.go index 3e8d0523c2..facb02145c 100644 --- a/runtime/stdlib/test-framework.go +++ b/runtime/stdlib/test-framework.go @@ -43,6 +43,8 @@ type Blockchain interface { CreateAccount() (*Account, error) + GetAccount(interpreter.AddressValue) (*Account, error) + AddTransaction( inter *interpreter.Interpreter, code string, @@ -58,8 +60,7 @@ type Blockchain interface { DeployContract( inter *interpreter.Interpreter, name string, - code string, - account *Account, + path string, arguments []interpreter.Value, ) error diff --git a/runtime/stdlib/test_contract.go b/runtime/stdlib/test_contract.go index 2f3baf41e4..f4faa2b7c0 100644 --- a/runtime/stdlib/test_contract.go +++ b/runtime/stdlib/test_contract.go @@ -48,6 +48,14 @@ type TestContractType struct { expectFailureFunction interpreter.FunctionValue } +// 'Test.blockchain' public field + +const testTypeBlockchainFieldName = "blockchain" + +const testTypeBlockchainFieldDocString = ` +An emulator-backed blockchain. +` + // 'Test.assert' function const testTypeAssertFunctionDocString = ` @@ -1072,6 +1080,16 @@ func newTestContractType() *TestContractType { blockchainType := ty.blockchainType() + compositeType.Fields = []string{testTypeBlockchainFieldName} + blockchainField := sema.NewPublicConstantFieldMember( + nil, + compositeType, + testTypeBlockchainFieldName, + blockchainType, + testTypeBlockchainFieldDocString, + ) + compositeType.Members.Set(testTypeBlockchainFieldName, blockchainField) + // Test.assert() compositeType.Members.Set( testTypeAssertFunctionName, @@ -1368,5 +1386,35 @@ func (t *TestContractType) NewTestContract( compositeValue.Functions[testTypeBeLessThanFunctionName] = t.beLessThanFunction compositeValue.Functions[testExpectFailureFunctionName] = t.expectFailureFunction + // Create an `EmulatorBackend` + emulatorBackend := t.emulatorBackendType.newEmulatorBackend( + inter, + testFramework.NewEmulatorBackend(), + interpreter.EmptyLocationRange, + ) + + // Create a 'Blockchain' struct value, that wraps the emulator backend, + // by calling the constructor of 'Blockchain'. + blockchainConstructor := getNestedTypeConstructorValue( + compositeValue, + testBlockchainTypeName, + ) + blockchain, err := inter.InvokeExternally( + blockchainConstructor, + blockchainConstructor.Type, + []interpreter.Value{ + emulatorBackend, + }, + ) + if err != nil { + return nil, err + } + compositeValue.SetMember( + inter, + interpreter.EmptyLocationRange, + testTypeBlockchainFieldName, + blockchain, + ) + return compositeValue, nil } diff --git a/runtime/stdlib/test_emulatorbackend.go b/runtime/stdlib/test_emulatorbackend.go index aca8706e56..876e6c6858 100644 --- a/runtime/stdlib/test_emulatorbackend.go +++ b/runtime/stdlib/test_emulatorbackend.go @@ -20,6 +20,8 @@ package stdlib import ( + "fmt" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" @@ -49,6 +51,7 @@ type testEmulatorBackendType struct { moveTimeFunctionType *sema.FunctionType createSnapshotFunctionType *sema.FunctionType loadSnapshotFunctionType *sema.FunctionType + getAccountFunctionType *sema.FunctionType } func newTestEmulatorBackendType( @@ -124,6 +127,11 @@ func newTestEmulatorBackendType( testEmulatorBackendTypeLoadSnapshotFunctionName, ) + getAccountFunctionType := interfaceFunctionType( + blockchainBackendInterfaceType, + testEmulatorBackendTypeGetAccountFunctionName, + ) + compositeType := &sema.CompositeType{ Identifier: testEmulatorBackendTypeName, Kind: common.CompositeKindStructure, @@ -218,6 +226,12 @@ func newTestEmulatorBackendType( loadSnapshotFunctionType, testEmulatorBackendTypeLoadSnapshotFunctionDocString, ), + sema.NewUnmeteredPublicFunctionMember( + compositeType, + testEmulatorBackendTypeGetAccountFunctionName, + getAccountFunctionType, + testEmulatorBackendTypeGetAccountFunctionDocString, + ), } compositeType.Members = sema.MembersAsMap(members) @@ -239,6 +253,7 @@ func newTestEmulatorBackendType( moveTimeFunctionType: moveTimeFunctionType, createSnapshotFunctionType: createSnapshotFunctionType, loadSnapshotFunctionType: loadSnapshotFunctionType, + getAccountFunctionType: getAccountFunctionType, } } @@ -348,6 +363,47 @@ func newTestAccountValue( return accountValue } +// 'EmulatorBackend.getAccount' function + +const testEmulatorBackendTypeGetAccountFunctionName = "getAccount" + +const testEmulatorBackendTypeGetAccountFunctionDocString = ` +Returns the account for the given address. +` + +func (t *testEmulatorBackendType) newGetAccountFunction( + blockchain Blockchain, +) *interpreter.HostFunctionValue { + return interpreter.NewUnmeteredHostFunctionValue( + t.getAccountFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + address, ok := invocation.Arguments[0].(interpreter.AddressValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + account, err := blockchain.GetAccount(address) + if err != nil { + msg := fmt.Sprintf("account with address: %s was not found", address) + panic(PanicError{ + Message: msg, + LocationRange: invocation.LocationRange, + }) + } + + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + return newTestAccountValue( + blockchain, + inter, + locationRange, + account, + ) + }, + ) +} + // 'EmulatorBackend.addTransaction' function const testEmulatorBackendTypeAddTransactionFunctionName = "addTransaction" @@ -509,22 +565,14 @@ func (t *testEmulatorBackendType) newDeployContractFunction( panic(errors.NewUnreachableError()) } - // Contract code - code, ok := invocation.Arguments[1].(*interpreter.StringValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - // authorizer - accountValue, ok := invocation.Arguments[2].(interpreter.MemberAccessibleValue) + // Contract file path + path, ok := invocation.Arguments[1].(*interpreter.StringValue) if !ok { panic(errors.NewUnreachableError()) } - account := accountFromValue(inter, accountValue, invocation.LocationRange) - // Contract init arguments - args, err := arrayValueToSlice(inter, invocation.Arguments[3]) + args, err := arrayValueToSlice(inter, invocation.Arguments[2]) if err != nil { panic(err) } @@ -532,8 +580,7 @@ func (t *testEmulatorBackendType) newDeployContractFunction( err = blockchain.DeployContract( inter, name.Str, - code.Str, - account, + path.Str, args, ) @@ -878,6 +925,10 @@ func (t *testEmulatorBackendType) newEmulatorBackend( Name: testEmulatorBackendTypeLoadSnapshotFunctionName, Value: t.newLoadSnapshotFunction(blockchain), }, + { + Name: testEmulatorBackendTypeGetAccountFunctionName, + Value: t.newGetAccountFunction(blockchain), + }, } // TODO: Use SimpleCompositeValue diff --git a/runtime/stdlib/test_test.go b/runtime/stdlib/test_test.go index beaa3a2f68..17a3eb95bd 100644 --- a/runtime/stdlib/test_test.go +++ b/runtime/stdlib/test_test.go @@ -16,7 +16,8 @@ * limitations under the License. */ -package stdlib +// This is in order to avoid cyclic import errors with runtime package +package stdlib_test import ( "errors" @@ -26,6 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/activations" "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" @@ -33,18 +35,24 @@ import ( "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/parser" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" "github.com/onflow/cadence/runtime/tests/checker" "github.com/onflow/cadence/runtime/tests/utils" ) func newTestContractInterpreter(t *testing.T, code string) (*interpreter.Interpreter, error) { - return newTestContractInterpreterWithTestFramework(t, code, nil) + testFramework := &mockedTestFramework{ + newEmulatorBackend: func() stdlib.Blockchain { + return &mockedBlockchain{} + }, + } + return newTestContractInterpreterWithTestFramework(t, code, testFramework) } func newTestContractInterpreterWithTestFramework( t *testing.T, code string, - testFramework TestFramework, + testFramework stdlib.TestFramework, ) (*interpreter.Interpreter, error) { program, err := parser.ParseProgram( nil, @@ -54,8 +62,8 @@ func newTestContractInterpreterWithTestFramework( require.NoError(t, err) activation := sema.NewVariableActivation(sema.BaseValueActivation) - activation.DeclareValue(AssertFunction) - activation.DeclareValue(PanicFunction) + activation.DeclareValue(stdlib.AssertFunction) + activation.DeclareValue(stdlib.PanicFunction) checker, err := sema.NewChecker( program, @@ -72,15 +80,15 @@ func newTestContractInterpreterWithTestFramework( sema.Import, error, ) { - if importedLocation == TestContractLocation { + if importedLocation == stdlib.TestContractLocation { return sema.ElaborationImport{ - Elaboration: GetTestContractType().Checker.Elaboration, + Elaboration: stdlib.GetTestContractType().Checker.Elaboration, }, nil } return nil, errors.New("invalid import") }, - ContractValueHandler: TestCheckerContractValueHandler, + ContractValueHandler: stdlib.TestCheckerContractValueHandler, }, ) require.NoError(t, err) @@ -90,13 +98,13 @@ func newTestContractInterpreterWithTestFramework( return nil, err } - storage := newUnmeteredInMemoryStorage() + storage := interpreter.NewInMemoryStorage(nil) var uuid uint64 = 0 baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) - interpreter.Declare(baseActivation, AssertFunction) - interpreter.Declare(baseActivation, PanicFunction) + interpreter.Declare(baseActivation, stdlib.AssertFunction) + interpreter.Declare(baseActivation, stdlib.PanicFunction) inter, err := interpreter.NewInterpreter( interpreter.ProgramFromChecker(checker), @@ -105,8 +113,8 @@ func newTestContractInterpreterWithTestFramework( Storage: storage, BaseActivation: baseActivation, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { - if location == TestContractLocation { - program := interpreter.ProgramFromChecker(GetTestContractType().Checker) + if location == stdlib.TestContractLocation { + program := interpreter.ProgramFromChecker(stdlib.GetTestContractType().Checker) subInterpreter, err := inter.NewSubInterpreter(program, location) if err != nil { panic(err) @@ -118,7 +126,7 @@ func newTestContractInterpreterWithTestFramework( return nil }, - ContractValueHandler: NewTestInterpreterContractValueHandler(testFramework), + ContractValueHandler: stdlib.NewTestInterpreterContractValueHandler(testFramework), UUIDHandler: func() (uint64, error) { uuid++ return uuid, nil @@ -690,7 +698,7 @@ func TestAssertEqual(t *testing.T) { _, err = inter.Invoke("test") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, @@ -714,7 +722,7 @@ func TestAssertEqual(t *testing.T) { _, err = inter.Invoke("test") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, @@ -749,7 +757,7 @@ func TestAssertEqual(t *testing.T) { _, err = inter.Invoke("testNotEqual") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, @@ -792,7 +800,7 @@ func TestAssertEqual(t *testing.T) { _, err = inter.Invoke("testNotEqual") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, @@ -827,7 +835,7 @@ func TestAssertEqual(t *testing.T) { _, err = inter.Invoke("testNotEqual") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, @@ -862,11 +870,11 @@ func TestAssertEqual(t *testing.T) { _, err = inter.Invoke("testNotEqual") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) assert.ErrorContains( t, err, - "not equal: expected: {2: false, 1: true}, actual: {2: true, 1: true}", + "not equal: expected: {2: false, 1: true}, actual: {1: true, 2: true}", ) }) @@ -1721,7 +1729,7 @@ func TestTestExpect(t *testing.T) { _, err = inter.Invoke("test") require.Error(t, err) - assertionErr := &AssertionError{} + assertionErr := &stdlib.AssertionError{} assert.ErrorAs(t, err, assertionErr) assert.Equal(t, "given value is: \"this string\"", assertionErr.Message) assert.Equal(t, "test", assertionErr.LocationRange.Location.String()) @@ -1744,7 +1752,7 @@ func TestTestExpect(t *testing.T) { _, err = inter.Invoke("test") require.Error(t, err) - assert.ErrorAs(t, err, &AssertionError{}) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) }) t.Run("with explicit types", func(t *testing.T) { @@ -2053,7 +2061,7 @@ func TestBlockchain(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain let events = blockchain.events() Test.expect(events, Test.beEmpty()) @@ -2063,7 +2071,7 @@ func TestBlockchain(t *testing.T) { eventsInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ events: func(inter *interpreter.Interpreter, eventType interpreter.StaticType) interpreter.Value { eventsInvoked = true @@ -2097,7 +2105,7 @@ func TestBlockchain(t *testing.T) { pub struct Foo {} pub fun test() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain // 'Foo' is not an event-type. // But we just need to test the API, so it doesn't really matter. @@ -2112,7 +2120,7 @@ func TestBlockchain(t *testing.T) { eventsInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ events: func(inter *interpreter.Interpreter, eventType interpreter.StaticType) interpreter.Value { eventsInvoked = true @@ -2149,7 +2157,7 @@ func TestBlockchain(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain blockchain.reset(to: 5) } ` @@ -2157,7 +2165,7 @@ func TestBlockchain(t *testing.T) { resetInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ reset: func(height uint64) { resetInvoked = true @@ -2183,7 +2191,7 @@ func TestBlockchain(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain blockchain.reset(to: 5.5) } ` @@ -2191,7 +2199,7 @@ func TestBlockchain(t *testing.T) { resetInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ reset: func(height uint64) { resetInvoked = true @@ -2213,7 +2221,7 @@ func TestBlockchain(t *testing.T) { import Test pub fun testMoveForward() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain // timeDelta is the representation of 35 days, // in the form of seconds. let timeDelta = Fix64(35 * 24 * 60 * 60) @@ -2224,7 +2232,7 @@ func TestBlockchain(t *testing.T) { moveTimeInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ moveTime: func(timeDelta int64) { moveTimeInvoked = true @@ -2250,7 +2258,7 @@ func TestBlockchain(t *testing.T) { import Test pub fun testMoveBackward() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain // timeDelta is the representation of 35 days, // in the form of seconds. let timeDelta = Fix64(35 * 24 * 60 * 60) * -1.0 @@ -2261,7 +2269,7 @@ func TestBlockchain(t *testing.T) { moveTimeInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ moveTime: func(timeDelta int64) { moveTimeInvoked = true @@ -2287,7 +2295,7 @@ func TestBlockchain(t *testing.T) { import Test pub fun testMoveTime() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain blockchain.moveTime(by: 3000) } ` @@ -2295,7 +2303,7 @@ func TestBlockchain(t *testing.T) { moveTimeInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ moveTime: func(timeDelta int64) { moveTimeInvoked = true @@ -2310,14 +2318,14 @@ func TestBlockchain(t *testing.T) { assert.False(t, moveTimeInvoked) }) - t.Run("newEmulatorBackend", func(t *testing.T) { + t.Run("blockchain", func(t *testing.T) { t.Parallel() const script = ` import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain Test.assertEqual(Type(), blockchain.getType()) } ` @@ -2325,7 +2333,7 @@ func TestBlockchain(t *testing.T) { newEmulatorBackendInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { newEmulatorBackendInvoked = true return &mockedBlockchain{} }, @@ -2347,7 +2355,7 @@ func TestBlockchain(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain blockchain.createSnapshot(name: "adminCreated") } ` @@ -2355,7 +2363,7 @@ func TestBlockchain(t *testing.T) { createSnapshotInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ createSnapshot: func(name string) error { createSnapshotInvoked = true @@ -2383,7 +2391,7 @@ func TestBlockchain(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain blockchain.createSnapshot(name: "adminCreated") } ` @@ -2391,7 +2399,7 @@ func TestBlockchain(t *testing.T) { createSnapshotInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ createSnapshot: func(name string) error { createSnapshotInvoked = true @@ -2419,7 +2427,7 @@ func TestBlockchain(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain blockchain.createSnapshot(name: "adminCreated") blockchain.loadSnapshot(name: "adminCreated") } @@ -2428,7 +2436,7 @@ func TestBlockchain(t *testing.T) { loadSnapshotInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ createSnapshot: func(name string) error { assert.Equal(t, "adminCreated", name) @@ -2461,7 +2469,7 @@ func TestBlockchain(t *testing.T) { import Test pub fun test() { - let blockchain = Test.newEmulatorBlockchain() + let blockchain = Test.blockchain blockchain.createSnapshot(name: "adminCreated") blockchain.loadSnapshot(name: "contractDeployed") } @@ -2470,7 +2478,7 @@ func TestBlockchain(t *testing.T) { loadSnapshotInvoked := false testFramework := &mockedTestFramework{ - newEmulatorBackend: func() Blockchain { + newEmulatorBackend: func() stdlib.Blockchain { return &mockedBlockchain{ createSnapshot: func(name string) error { assert.Equal(t, "adminCreated", name) @@ -2496,17 +2504,203 @@ func TestBlockchain(t *testing.T) { assert.True(t, loadSnapshotInvoked) }) + t.Run("deployContract", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun test() { + let blockchain = Test.blockchain + let err = blockchain.deployContract( + name: "FooContract", + path: "./contracts/FooContract.cdc", + arguments: ["Hey, there!"] + ) + + Test.expect(err, Test.beNil()) + } + ` + + deployContractInvoked := false + + testFramework := &mockedTestFramework{ + newEmulatorBackend: func() stdlib.Blockchain { + return &mockedBlockchain{ + deployContract: func( + inter *interpreter.Interpreter, + name string, + path string, + arguments []interpreter.Value, + ) error { + deployContractInvoked = true + assert.Equal(t, "FooContract", name) + assert.Equal(t, "./contracts/FooContract.cdc", path) + assert.Equal(t, 1, len(arguments)) + argument := arguments[0].(*interpreter.StringValue) + assert.Equal(t, "Hey, there!", argument.Str) + + return nil + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.True(t, deployContractInvoked) + }) + + t.Run("deployContract with failure", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun test() { + let blockchain = Test.blockchain + let err = blockchain.deployContract( + name: "FooContract", + path: "./contracts/FooContract.cdc", + arguments: ["Hey, there!"] + ) + + Test.assertEqual( + "failed to deploy contract: FooContract", + err!.message + ) + } + ` + + deployContractInvoked := false + + testFramework := &mockedTestFramework{ + newEmulatorBackend: func() stdlib.Blockchain { + return &mockedBlockchain{ + deployContract: func( + inter *interpreter.Interpreter, + name string, + path string, + arguments []interpreter.Value, + ) error { + deployContractInvoked = true + + return fmt.Errorf("failed to deploy contract: %s", name) + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.True(t, deployContractInvoked) + }) + + t.Run("getAccount", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun test() { + let blockchain = Test.blockchain + let account = blockchain.getAccount(0x0000000000000009) + } + ` + + getAccountInvoked := false + + testFramework := &mockedTestFramework{ + newEmulatorBackend: func() stdlib.Blockchain { + return &mockedBlockchain{ + getAccount: func(address interpreter.AddressValue) (*stdlib.Account, error) { + getAccountInvoked = true + assert.Equal(t, "0000000000000009", address.Hex()) + addr := common.Address(address) + + return &stdlib.Account{ + Address: addr, + PublicKey: &stdlib.PublicKey{ + PublicKey: []byte{1, 2, 3}, + SignAlgo: sema.SignatureAlgorithmECDSA_P256, + }, + }, nil + }, + stdlibHandler: func() stdlib.StandardLibraryHandler { + return runtime.NewBaseInterpreterEnvironment(runtime.Config{}) + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.True(t, getAccountInvoked) + }) + + t.Run("getAccount with failure", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun test() { + let blockchain = Test.blockchain + let account = blockchain.getAccount(0x0000000000000009) + } + ` + + getAccountInvoked := false + + testFramework := &mockedTestFramework{ + newEmulatorBackend: func() stdlib.Blockchain { + return &mockedBlockchain{ + getAccount: func(address interpreter.AddressValue) (*stdlib.Account, error) { + getAccountInvoked = true + assert.Equal(t, "0000000000000009", address.Hex()) + + return nil, fmt.Errorf("failed to retrieve account with address: %s", address) + }, + stdlibHandler: func() stdlib.StandardLibraryHandler { + return runtime.NewBaseInterpreterEnvironment(runtime.Config{}) + }, + } + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.Error(t, err) + assert.ErrorContains(t, err, "account with address: 0x0000000000000009 was not found") + + assert.True(t, getAccountInvoked) + }) + // TODO: Add more tests for the remaining functions. } type mockedTestFramework struct { - newEmulatorBackend func() Blockchain + newEmulatorBackend func() stdlib.Blockchain readFile func(s string) (string, error) } -var _ TestFramework = &mockedTestFramework{} +var _ stdlib.TestFramework = &mockedTestFramework{} -func (m mockedTestFramework) NewEmulatorBackend() Blockchain { +func (m mockedTestFramework) NewEmulatorBackend() stdlib.Blockchain { if m.newEmulatorBackend == nil { panic("'NewEmulatorBackend' is not implemented") } @@ -2524,15 +2718,16 @@ func (m mockedTestFramework) ReadFile(fileName string) (string, error) { type mockedBlockchain struct { runScript func(inter *interpreter.Interpreter, code string, arguments []interpreter.Value) - createAccount func() (*Account, error) - addTransaction func(inter *interpreter.Interpreter, code string, authorizers []common.Address, signers []*Account, arguments []interpreter.Value) error - executeTransaction func() *TransactionResult + createAccount func() (*stdlib.Account, error) + getAccount func(interpreter.AddressValue) (*stdlib.Account, error) + addTransaction func(inter *interpreter.Interpreter, code string, authorizers []common.Address, signers []*stdlib.Account, arguments []interpreter.Value) error + executeTransaction func() *stdlib.TransactionResult commitBlock func() error - deployContract func(inter *interpreter.Interpreter, name string, code string, account *Account, arguments []interpreter.Value) error - useConfiguration func(configuration *Configuration) - stdlibHandler func() StandardLibraryHandler + deployContract func(inter *interpreter.Interpreter, name string, path string, arguments []interpreter.Value) error + useConfiguration func(configuration *stdlib.Configuration) + stdlibHandler func() stdlib.StandardLibraryHandler logs func() []string - serviceAccount func() (*Account, error) + serviceAccount func() (*stdlib.Account, error) events func(inter *interpreter.Interpreter, eventType interpreter.StaticType) interpreter.Value reset func(uint64) moveTime func(int64) @@ -2540,13 +2735,13 @@ type mockedBlockchain struct { loadSnapshot func(string) error } -var _ Blockchain = &mockedBlockchain{} +var _ stdlib.Blockchain = &mockedBlockchain{} func (m mockedBlockchain) RunScript( inter *interpreter.Interpreter, code string, arguments []interpreter.Value, -) *ScriptResult { +) *stdlib.ScriptResult { if m.runScript == nil { panic("'RunScript' is not implemented") } @@ -2554,7 +2749,7 @@ func (m mockedBlockchain) RunScript( return m.RunScript(inter, code, arguments) } -func (m mockedBlockchain) CreateAccount() (*Account, error) { +func (m mockedBlockchain) CreateAccount() (*stdlib.Account, error) { if m.createAccount == nil { panic("'CreateAccount' is not implemented") } @@ -2562,11 +2757,19 @@ func (m mockedBlockchain) CreateAccount() (*Account, error) { return m.createAccount() } +func (m mockedBlockchain) GetAccount(address interpreter.AddressValue) (*stdlib.Account, error) { + if m.getAccount == nil { + panic("'getAccount' is not implemented") + } + + return m.getAccount(address) +} + func (m mockedBlockchain) AddTransaction( inter *interpreter.Interpreter, code string, authorizers []common.Address, - signers []*Account, + signers []*stdlib.Account, arguments []interpreter.Value, ) error { if m.addTransaction == nil { @@ -2576,7 +2779,7 @@ func (m mockedBlockchain) AddTransaction( return m.addTransaction(inter, code, authorizers, signers, arguments) } -func (m mockedBlockchain) ExecuteNextTransaction() *TransactionResult { +func (m mockedBlockchain) ExecuteNextTransaction() *stdlib.TransactionResult { if m.executeTransaction == nil { panic("'ExecuteNextTransaction' is not implemented") } @@ -2595,18 +2798,17 @@ func (m mockedBlockchain) CommitBlock() error { func (m mockedBlockchain) DeployContract( inter *interpreter.Interpreter, name string, - code string, - account *Account, + path string, arguments []interpreter.Value, ) error { if m.deployContract == nil { panic("'DeployContract' is not implemented") } - return m.deployContract(inter, name, code, account, arguments) + return m.deployContract(inter, name, path, arguments) } -func (m mockedBlockchain) UseConfiguration(configuration *Configuration) { +func (m mockedBlockchain) UseConfiguration(configuration *stdlib.Configuration) { if m.useConfiguration == nil { panic("'UseConfiguration' is not implemented") } @@ -2614,7 +2816,7 @@ func (m mockedBlockchain) UseConfiguration(configuration *Configuration) { m.useConfiguration(configuration) } -func (m mockedBlockchain) StandardLibraryHandler() StandardLibraryHandler { +func (m mockedBlockchain) StandardLibraryHandler() stdlib.StandardLibraryHandler { if m.stdlibHandler == nil { panic("'StandardLibraryHandler' is not implemented") } @@ -2630,7 +2832,7 @@ func (m mockedBlockchain) Logs() []string { return m.logs() } -func (m mockedBlockchain) ServiceAccount() (*Account, error) { +func (m mockedBlockchain) ServiceAccount() (*stdlib.Account, error) { if m.serviceAccount == nil { panic("'ServiceAccount' is not implemented") }