Skip to content

Commit

Permalink
Merge pull request #196 from m-Peter/testing-framework-move-time-utility
Browse files Browse the repository at this point in the history
[test] Allow setting blockchain's clock
  • Loading branch information
SupunS authored Sep 1, 2023
2 parents 7b09349 + 4145e33 commit c6f5906
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 6 deletions.
27 changes: 27 additions & 0 deletions test/emulator_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"fmt"
"os"
"strings"
"time"

"github.com/onflow/cadence"
"github.com/onflow/cadence/encoding/json"
Expand Down Expand Up @@ -52,6 +53,18 @@ const helperFilePrefix = "\x00helper/"

var _ stdlib.TestFramework = &EmulatorBackend{}

type SystemClock struct {
TimeDelta int64
}

func (sc SystemClock) Now() time.Time {
return time.Now().Add(time.Second * time.Duration(sc.TimeDelta)).UTC()
}

func NewSystemClock() *SystemClock {
return &SystemClock{}
}

// EmulatorBackend is the emulator-backed implementation of the interpreter.TestFramework.
type EmulatorBackend struct {
blockchain *emulator.Blockchain
Expand All @@ -76,6 +89,9 @@ type EmulatorBackend struct {
// logCollection is a hook attached in the server logger, in order
// to aggregate and expose log messages from the blockchain.
logCollection *LogCollectionHook

// clock allows manipulating the blockchain's clock.
clock *SystemClock
}

type keyInfo struct {
Expand Down Expand Up @@ -130,6 +146,8 @@ func NewEmulatorBackend(
} else {
blockchain = newBlockchain(logCollectionHook)
}
clock := NewSystemClock()
blockchain.SetClock(clock)

return &EmulatorBackend{
blockchain: blockchain,
Expand All @@ -139,6 +157,7 @@ func NewEmulatorBackend(
configuration: baseConfiguration(),
stdlibHandler: stdlibHandler,
logCollection: logCollectionHook,
clock: clock,
}
}

Expand Down Expand Up @@ -668,6 +687,14 @@ func (e *EmulatorBackend) Events(
)
}

// Moves the time of the Blockchain's clock, by the
// given time delta, in the form of seconds.
func (e *EmulatorBackend) MoveTime(timeDelta int64) {
e.clock.TimeDelta += timeDelta
e.blockchain.SetClock(e.clock)
e.CommitBlock()
}

// excludeCommonLocations excludes the common contracts from appearing
// in the coverage report, as they skew the coverage metrics.
func excludeCommonLocations(coverageReport *runtime.CoverageReport) {
Expand Down
4 changes: 2 additions & 2 deletions test/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ go 1.18

require (
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/onflow/cadence v0.40.1-0.20230808215334-fcb488b659bf
github.com/onflow/cadence v0.40.1-0.20230828191216-1bbc078efdb3
github.com/onflow/flow-emulator v0.54.0
github.com/onflow/flow-go v0.31.1-0.20230808172820-f074502a67e3
github.com/onflow/flow-go v0.31.1-0.20230901090702-eeeef3a7bd58
github.com/onflow/flow-go-sdk v0.41.10
github.com/rs/zerolog v1.29.0
github.com/stretchr/testify v1.8.4
Expand Down
8 changes: 4 additions & 4 deletions test/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,8 @@ github.com/onflow/atree v0.1.0-beta1.0.20211027184039-559ee654ece9/go.mod h1:+6x
github.com/onflow/atree v0.6.0 h1:j7nQ2r8npznx4NX39zPpBYHmdy45f4xwoi+dm37Jk7c=
github.com/onflow/atree v0.6.0/go.mod h1:gBHU0M05qCbv9NN0kijLWMgC47gHVNBIp4KmsVFi0tc=
github.com/onflow/cadence v0.20.1/go.mod h1:7mzUvPZUIJztIbr9eTvs+fQjWWHTF8veC+yk4ihcNIA=
github.com/onflow/cadence v0.40.1-0.20230808215334-fcb488b659bf h1:XdC0uYL2jjnyGk+XhX9klfNOi0/ewCpjmI8ZwoRVaE0=
github.com/onflow/cadence v0.40.1-0.20230808215334-fcb488b659bf/go.mod h1:2ggmhENvPPZXRnv9SmqN2xiYvluu4wx/96GCpeRh8lU=
github.com/onflow/cadence v0.40.1-0.20230828191216-1bbc078efdb3 h1:Ncadvbf2t4IRY1wJpGgTDs7GEpGO5RgT5HmKSifhoaQ=
github.com/onflow/cadence v0.40.1-0.20230828191216-1bbc078efdb3/go.mod h1:2ggmhENvPPZXRnv9SmqN2xiYvluu4wx/96GCpeRh8lU=
github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d h1:B7PdhdUNkve5MVrekWDuQf84XsGBxNZ/D3x+QQ8XeVs=
github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d/go.mod h1:xAiV/7TKhw863r6iO3CS5RnQ4F+pBY1TxD272BsILlo=
github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 h1:X25A1dNajNUtE+KoV76wQ6BR6qI7G65vuuRXxDDqX7E=
Expand All @@ -529,8 +529,8 @@ github.com/onflow/flow-emulator v0.54.0 h1:GzqMPIjsNweiyBORs8naUXhgs3PhD0X4Ep4j/
github.com/onflow/flow-emulator v0.54.0/go.mod h1:cPKNx2eaxUDtXNHN9nnrt/qydWUHNQRTa/9QnsaCSpo=
github.com/onflow/flow-ft/lib/go/contracts v0.7.0 h1:XEKE6qJUw3luhsYmIOteXP53gtxNxrwTohgxJXCYqBE=
github.com/onflow/flow-ft/lib/go/contracts v0.7.0/go.mod h1:kTMFIySzEJJeupk+7EmXs0EJ6CBWY/MV9fv9iYQk+RU=
github.com/onflow/flow-go v0.31.1-0.20230808172820-f074502a67e3 h1:3iDV59Das0YkeFnjI0UkOZMz+gS1JKpTNZ4oMGH4bDM=
github.com/onflow/flow-go v0.31.1-0.20230808172820-f074502a67e3/go.mod h1:PdmGmlNDu9HOhg31NYAKLrIhmuTvFDgCS56CTs0af9Y=
github.com/onflow/flow-go v0.31.1-0.20230901090702-eeeef3a7bd58 h1:SJS/WqckE6lcXmCOL/7Gp39zY66mCcEhy9xfmyomKrw=
github.com/onflow/flow-go v0.31.1-0.20230901090702-eeeef3a7bd58/go.mod h1:PdmGmlNDu9HOhg31NYAKLrIhmuTvFDgCS56CTs0af9Y=
github.com/onflow/flow-go-sdk v0.24.0/go.mod h1:IoptMLPyFXWvyd9yYA6/4EmSeeozl6nJoIv4FaEMg74=
github.com/onflow/flow-go-sdk v0.41.10 h1:Cio6GJhtx532TUY+cqrqWglD5sZCXkWeM5QvaRha3p4=
github.com/onflow/flow-go-sdk v0.41.10/go.mod h1:0a0LiQFbFt8RW/ptoMUU7YkvW9ArVcbjLE0XS78uz1E=
Expand Down
125 changes: 125 additions & 0 deletions test/test_framework_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4181,3 +4181,128 @@ func TestTestFunctionValidSignature(t *testing.T) {
assert.ErrorContains(t, err, "test functions should have no return values")
})
}

func TestBlockchainMoveTime(t *testing.T) {
t.Parallel()

const contractCode = `
pub contract TimeLocker {
pub let lockPeriod: UFix64
pub let lockedAt: UFix64
init(lockedAt: UFix64) {
self.lockedAt = lockedAt
// Lock period is 30 days, in the form of seconds.
self.lockPeriod = UFix64(30 * 24 * 60 * 60)
}
pub fun isOpen(): Bool {
let currentTime = getCurrentBlock().timestamp
return currentTime > (self.lockedAt + self.lockPeriod)
}
}
`

const scriptCode = `
import "TimeLocker"
pub fun main(): Bool {
return TimeLocker.isOpen()
}
`

const currentBlockTimestamp = `
pub fun main(): UFix64 {
return getCurrentBlock().timestamp
}
`

const testCode = `
import Test
pub let blockchain = Test.newEmulatorBlockchain()
pub let account = blockchain.createAccount()
pub var lockedAt: UFix64 = 0.0
pub fun setup() {
let currentBlockTimestamp = Test.readFile("current_block_timestamp.cdc")
let result = blockchain.executeScript(currentBlockTimestamp, [])
lockedAt = result.returnValue! as! UFix64
let contractCode = Test.readFile("TimeLocker.cdc")
let err = blockchain.deployContract(
name: "TimeLocker",
code: contractCode,
account: account,
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, [])
Test.expect(result, Test.beSucceeded())
Test.assertEqual(false, result.returnValue! as! Bool)
}
pub fun testIsOpen() {
// timeDelta is the representation of 20 days, in seconds
let timeDelta = Fix64(20 * 24 * 60 * 60)
blockchain.moveTime(by: timeDelta)
let isLockerOpen = Test.readFile("is_locker_open.cdc")
var result = blockchain.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)
result = blockchain.executeScript(isLockerOpen, [])
Test.assertEqual(true, result.returnValue! as! Bool)
// We move time backward by 20 days
blockchain.moveTime(by: timeDelta * -1.0)
result = blockchain.executeScript(isLockerOpen, [])
Test.assertEqual(false, result.returnValue! as! Bool)
}
`

fileResolver := func(path string) (string, error) {
switch path {
case "TimeLocker.cdc":
return contractCode, nil
case "is_locker_open.cdc":
return scriptCode, nil
case "current_block_timestamp.cdc":
return currentBlockTimestamp, nil
default:
return "", fmt.Errorf("cannot find import location: %s", path)
}
}

importResolver := func(location common.Location) (string, error) {
return "", nil
}

runner := NewTestRunner().
WithFileResolver(fileResolver).
WithImportResolver(importResolver)

results, err := runner.RunTests(testCode)
require.NoError(t, err)
for _, result := range results {
require.NoError(t, result.Error)
}
}

0 comments on commit c6f5906

Please sign in to comment.