From e940e2d6ca8f879fa5342365bc29ac12a699060d Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Fri, 4 Aug 2023 09:32:26 +0300 Subject: [PATCH 1/3] Update Blockchain.reset() function to require a block height --- runtime/stdlib/contracts/test.cdc | 10 ++--- runtime/stdlib/test-framework.go | 2 +- runtime/stdlib/test_emulatorbackend.go | 10 +++-- runtime/stdlib/test_test.go | 62 ++++++++++++++++++++++++-- 4 files changed, 72 insertions(+), 12 deletions(-) diff --git a/runtime/stdlib/contracts/test.cdc b/runtime/stdlib/contracts/test.cdc index d8ec4381d2..7c7ceca335 100644 --- a/runtime/stdlib/contracts/test.cdc +++ b/runtime/stdlib/contracts/test.cdc @@ -122,10 +122,10 @@ pub contract Test { return self.backend.events(type) } - /// Resets the state of the blockchain. + /// Resets the state of the blockchain at the given height. /// - pub fun reset() { - self.backend.reset() + pub fun reset(to height: UInt64) { + self.backend.reset(to: height) } } @@ -305,9 +305,9 @@ pub contract Test { /// pub fun events(_ type: Type?): [AnyStruct] - /// Resets the state of the blockchain. + /// Resets the state of the blockchain at the given height. /// - pub fun reset() + pub fun reset(to height: UInt64) } /// Returns a new matcher that negates the test of the given matcher. diff --git a/runtime/stdlib/test-framework.go b/runtime/stdlib/test-framework.go index dc1b770d15..6c8664c206 100644 --- a/runtime/stdlib/test-framework.go +++ b/runtime/stdlib/test-framework.go @@ -70,7 +70,7 @@ type TestFramework interface { eventType interpreter.StaticType, ) interpreter.Value - Reset() + Reset(uint64) } type ScriptResult struct { diff --git a/runtime/stdlib/test_emulatorbackend.go b/runtime/stdlib/test_emulatorbackend.go index 86e9db2038..ecf2ef3cbb 100644 --- a/runtime/stdlib/test_emulatorbackend.go +++ b/runtime/stdlib/test_emulatorbackend.go @@ -666,16 +666,20 @@ func (t *testEmulatorBackendType) newEventsFunction( const testEmulatorBackendTypeResetFunctionName = "reset" const testEmulatorBackendTypeResetFunctionDocString = ` -Resets the state of the blockchain. +Resets the state of the blockchain at the given height. ` func (t *testEmulatorBackendType) newResetFunction( testFramework TestFramework, ) *interpreter.HostFunctionValue { return interpreter.NewUnmeteredHostFunctionValue( - t.eventsFunctionType, + t.resetFunctionType, func(invocation interpreter.Invocation) interpreter.Value { - testFramework.Reset() + height, ok := invocation.Arguments[0].(interpreter.UInt64Value) + if !ok { + panic(errors.NewUnreachableError()) + } + testFramework.Reset(uint64(height)) return interpreter.Void }, ) diff --git a/runtime/stdlib/test_test.go b/runtime/stdlib/test_test.go index fd4b1f8a64..a1ccdf9eb6 100644 --- a/runtime/stdlib/test_test.go +++ b/runtime/stdlib/test_test.go @@ -2023,6 +2023,62 @@ func TestBlockchain(t *testing.T) { assert.True(t, eventsInvoked) }) + t.Run("reset", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun test() { + let blockchain = Test.newEmulatorBlockchain() + blockchain.reset(to: 5) + } + ` + + resetInvoked := false + + testFramework := &mockedTestFramework{ + reset: func(height uint64) { + resetInvoked = true + assert.Equal(t, uint64(5), height) + }, + } + + inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + assert.True(t, resetInvoked) + }) + + t.Run("reset with type mismatch for height", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun test() { + let blockchain = Test.newEmulatorBlockchain() + blockchain.reset(to: 5.5) + } + ` + + resetInvoked := false + + testFramework := &mockedTestFramework{ + reset: func(height uint64) { + resetInvoked = true + }, + } + + _, err := newTestContractInterpreterWithTestFramework(t, script, testFramework) + errs := checker.RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.False(t, resetInvoked) + }) + // TODO: Add more tests for the remaining functions. } @@ -2039,7 +2095,7 @@ type mockedTestFramework struct { logs func() []string serviceAccount func() (*Account, error) events func(inter *interpreter.Interpreter, eventType interpreter.StaticType) interpreter.Value - reset func() + reset func(uint64) } var _ TestFramework = &mockedTestFramework{} @@ -2159,10 +2215,10 @@ func (m mockedTestFramework) Events( return m.events(inter, eventType) } -func (m mockedTestFramework) Reset() { +func (m mockedTestFramework) Reset(height uint64) { if m.reset == nil { panic("'Reset' is not implemented") } - m.reset() + m.reset(height) } From c5a76415234e00e7908c854299f8b8dfae3f9c0e Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Fri, 4 Aug 2023 10:50:30 +0300 Subject: [PATCH 2/3] Add the Test.assertError() helper function This matcher function asserts that the result of an executed operation, such as scripts and transactions, resulted in an error that contains a given sub-string. --- runtime/stdlib/contracts/test.cdc | 28 ++++++++ runtime/stdlib/test_test.go | 106 ++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/runtime/stdlib/contracts/test.cdc b/runtime/stdlib/contracts/test.cdc index 7c7ceca335..6888f740c2 100644 --- a/runtime/stdlib/contracts/test.cdc +++ b/runtime/stdlib/contracts/test.cdc @@ -171,6 +171,10 @@ pub contract Test { /// The resulted status of an executed operation. /// pub let status: ResultStatus + + /// The optionally resulted error of an executed operation. + /// + pub let error: Error? } /// The result of a transaction execution. @@ -346,4 +350,28 @@ pub contract Test { }) } + /// Asserts that the result of an executed operation, such as + /// scripts & transactions, has errored and the error message + /// contains the given sub-string. + /// + pub fun assertError(_ result: {Result}, errorMessage: String) { + pre { + result.status == ResultStatus.failed: "no error was found" + } + + var found = false + let msg = result.error!.message + let msgLength = msg.length - errorMessage.length + 1 + var i = 0 + while i < msgLength { + if msg.slice(from: i, upTo: i + errorMessage.length) == errorMessage { + found = true + break + } + i = i + 1 + } + + assert(found, message: "the error message did not contain the given sub-string") + } + } diff --git a/runtime/stdlib/test_test.go b/runtime/stdlib/test_test.go index a1ccdf9eb6..b241598093 100644 --- a/runtime/stdlib/test_test.go +++ b/runtime/stdlib/test_test.go @@ -1154,6 +1154,112 @@ func TestTestBeFailedMatcher(t *testing.T) { }) } +func TestTestAssertErrorMatcher(t *testing.T) { + + t.Parallel() + + t.Run("with ScriptResult", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun testMatch() { + let result = Test.ScriptResult( + status: Test.ResultStatus.failed, + returnValue: nil, + error: Test.Error("computation exceeding limit") + ) + + Test.assertError(result, errorMessage: "exceeding limit") + } + + pub fun testNoMatch() { + let result = Test.ScriptResult( + status: Test.ResultStatus.failed, + returnValue: nil, + error: Test.Error("computation exceeding memory") + ) + + Test.assertError(result, errorMessage: "exceeding limit") + } + + pub fun testNoError() { + let result = Test.ScriptResult( + status: Test.ResultStatus.succeeded, + returnValue: 42, + error: nil + ) + + Test.assertError(result, errorMessage: "exceeding limit") + } + ` + + inter, err := newTestContractInterpreter(t, script) + require.NoError(t, err) + + _, err = inter.Invoke("testMatch") + require.NoError(t, err) + + _, err = inter.Invoke("testNoMatch") + require.Error(t, err) + assert.ErrorContains(t, err, "the error message did not contain the given sub-string") + + _, err = inter.Invoke("testNoError") + require.Error(t, err) + assert.ErrorContains(t, err, "no error was found") + }) + + t.Run("with TransactionResult", func(t *testing.T) { + t.Parallel() + + const script = ` + import Test + + pub fun testMatch() { + let result = Test.TransactionResult( + status: Test.ResultStatus.failed, + error: Test.Error("computation exceeding limit") + ) + + Test.assertError(result, errorMessage: "exceeding limit") + } + + pub fun testNoMatch() { + let result = Test.TransactionResult( + status: Test.ResultStatus.failed, + error: Test.Error("computation exceeding memory") + ) + + Test.assertError(result, errorMessage: "exceeding limit") + } + + pub fun testNoError() { + let result = Test.TransactionResult( + status: Test.ResultStatus.succeeded, + error: nil + ) + + Test.assertError(result, errorMessage: "exceeding limit") + } + ` + + inter, err := newTestContractInterpreter(t, script) + require.NoError(t, err) + + _, err = inter.Invoke("testMatch") + require.NoError(t, err) + + _, err = inter.Invoke("testNoMatch") + require.Error(t, err) + assert.ErrorContains(t, err, "the error message did not contain the given sub-string") + + _, err = inter.Invoke("testNoError") + require.Error(t, err) + assert.ErrorContains(t, err, "no error was found") + }) +} + func TestTestBeNilMatcher(t *testing.T) { t.Parallel() From ff06b1dc9ca26dad1395ee5f698d61d7702e74c2 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 7 Aug 2023 19:33:15 +0300 Subject: [PATCH 3/3] Improve docstring wordings for Test contract --- runtime/stdlib/contracts/test.cdc | 14 +++++++------- runtime/stdlib/test_emulatorbackend.go | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/runtime/stdlib/contracts/test.cdc b/runtime/stdlib/contracts/test.cdc index 6888f740c2..cbfc3ed774 100644 --- a/runtime/stdlib/contracts/test.cdc +++ b/runtime/stdlib/contracts/test.cdc @@ -122,7 +122,7 @@ pub contract Test { return self.backend.events(type) } - /// Resets the state of the blockchain at the given height. + /// Resets the state of the blockchain to the given height. /// pub fun reset(to height: UInt64) { self.backend.reset(to: height) @@ -168,11 +168,11 @@ pub contract Test { /// operations, such as transactions and scripts. /// pub struct interface Result { - /// The resulted status of an executed operation. + /// The result status of an executed operation. /// pub let status: ResultStatus - /// The optionally resulted error of an executed operation. + /// The optional error of an executed operation. /// pub let error: Error? } @@ -309,7 +309,7 @@ pub contract Test { /// pub fun events(_ type: Type?): [AnyStruct] - /// Resets the state of the blockchain at the given height. + /// Resets the state of the blockchain to the given height. /// pub fun reset(to height: UInt64) } @@ -350,9 +350,9 @@ pub contract Test { }) } - /// Asserts that the result of an executed operation, such as - /// scripts & transactions, has errored and the error message - /// contains the given sub-string. + /// Asserts that the result status of an executed operation, such as + /// a script or transaction, has failed and contains the given error + /// message. /// pub fun assertError(_ result: {Result}, errorMessage: String) { pre { diff --git a/runtime/stdlib/test_emulatorbackend.go b/runtime/stdlib/test_emulatorbackend.go index ecf2ef3cbb..cde1d1456c 100644 --- a/runtime/stdlib/test_emulatorbackend.go +++ b/runtime/stdlib/test_emulatorbackend.go @@ -666,7 +666,7 @@ func (t *testEmulatorBackendType) newEventsFunction( const testEmulatorBackendTypeResetFunctionName = "reset" const testEmulatorBackendTypeResetFunctionDocString = ` -Resets the state of the blockchain at the given height. +Resets the state of the blockchain to the given height. ` func (t *testEmulatorBackendType) newResetFunction(