Skip to content

Commit

Permalink
Merge pull request #2505 from Build-Squad/enrich-test-framework-inter…
Browse files Browse the repository at this point in the history
…face
  • Loading branch information
turbolent authored May 30, 2023
2 parents b24a708 + 44abe28 commit 4cb64ae
Show file tree
Hide file tree
Showing 3 changed files with 262 additions and 1 deletion.
50 changes: 50 additions & 0 deletions runtime/stdlib/contracts/test.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,38 @@ pub contract Test {
pub fun useConfiguration(_ configuration: Configuration) {
self.backend.useConfiguration(configuration)
}

/// Returns all the logs from the blockchain, up to the calling point.
///
pub fun logs(): [String] {
return self.backend.logs()
}

/// Returns the service account of the blockchain. Can be used to sign
/// transactions with this account.
///
pub fun serviceAccount(): Account {
return self.backend.serviceAccount()
}

/// Returns all events emitted from the blockchain.
///
pub fun events(): [AnyStruct] {
return self.backend.events(nil)
}

/// Returns all events emitted from the blockchain,
/// filtered by type.
///
pub fun eventsOfType(_ type: Type): [AnyStruct] {
return self.backend.events(type)
}

/// Resets the state of the blockchain.
///
pub fun reset() {
self.backend.reset()
}
}

pub struct Matcher {
Expand Down Expand Up @@ -258,6 +290,24 @@ pub contract Test {
/// Overrides any existing configuration.
///
pub fun useConfiguration(_ configuration: Configuration)

/// Returns all the logs from the blockchain, up to the calling point.
///
pub fun logs(): [String]

/// Returns the service account of the blockchain. Can be used to sign
/// transactions with this account.
///
pub fun serviceAccount(): Account

/// Returns all events emitted from the blockchain, optionally filtered
/// by type.
///
pub fun events(_ type: Type?): [AnyStruct]

/// Resets the state of the blockchain.
///
pub fun reset()
}

/// Returns a new matcher that negates the test of the given matcher.
Expand Down
16 changes: 15 additions & 1 deletion runtime/stdlib/test-framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ import (
// Cadence standard library talks to test providers via this interface.
// This is used as a way to inject test provider dependencies dynamically.
type TestFramework interface {
RunScript(inter *interpreter.Interpreter, code string, arguments []interpreter.Value) *ScriptResult
RunScript(
inter *interpreter.Interpreter,
code string, arguments []interpreter.Value,
) *ScriptResult

CreateAccount() (*Account, error)

Expand Down Expand Up @@ -57,6 +60,17 @@ type TestFramework interface {
UseConfiguration(configuration *Configuration)

StandardLibraryHandler() StandardLibraryHandler

Logs() []string

ServiceAccount() (*Account, error)

Events(
inter *interpreter.Interpreter,
eventType interpreter.StaticType,
) interpreter.Value

Reset()
}

type ScriptResult struct {
Expand Down
197 changes: 197 additions & 0 deletions runtime/stdlib/test_emulatorbackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ type testEmulatorBackendType struct {
commitBlockFunctionType *sema.FunctionType
deployContractFunctionType *sema.FunctionType
useConfigFunctionType *sema.FunctionType
logsFunctionType *sema.FunctionType
serviceAccountFunctionType *sema.FunctionType
eventsFunctionType *sema.FunctionType
resetFunctionType *sema.FunctionType
}

func newTestEmulatorBackendType(blockchainBackendInterfaceType *sema.InterfaceType) *testEmulatorBackendType {
Expand Down Expand Up @@ -80,6 +84,26 @@ func newTestEmulatorBackendType(blockchainBackendInterfaceType *sema.InterfaceTy
testEmulatorBackendTypeUseConfigFunctionName,
)

logsFunctionType := interfaceFunctionType(
blockchainBackendInterfaceType,
testEmulatorBackendTypeLogsFunctionName,
)

serviceAccountFunctionType := interfaceFunctionType(
blockchainBackendInterfaceType,
testEmulatorBackendTypeServiceAccountFunctionName,
)

eventsFunctionType := interfaceFunctionType(
blockchainBackendInterfaceType,
testEmulatorBackendTypeEventsFunctionName,
)

resetFunctionType := interfaceFunctionType(
blockchainBackendInterfaceType,
testEmulatorBackendTypeResetFunctionName,
)

compositeType := &sema.CompositeType{
Identifier: testEmulatorBackendTypeName,
Kind: common.CompositeKindStructure,
Expand Down Expand Up @@ -132,6 +156,30 @@ func newTestEmulatorBackendType(blockchainBackendInterfaceType *sema.InterfaceTy
useConfigFunctionType,
testEmulatorBackendTypeUseConfigFunctionDocString,
),
sema.NewUnmeteredPublicFunctionMember(
compositeType,
testEmulatorBackendTypeLogsFunctionName,
logsFunctionType,
testEmulatorBackendTypeLogsFunctionDocString,
),
sema.NewUnmeteredPublicFunctionMember(
compositeType,
testEmulatorBackendTypeServiceAccountFunctionName,
serviceAccountFunctionType,
testEmulatorBackendTypeServiceAccountFunctionDocString,
),
sema.NewUnmeteredPublicFunctionMember(
compositeType,
testEmulatorBackendTypeEventsFunctionName,
eventsFunctionType,
testEmulatorBackendTypeEventsFunctionDocString,
),
sema.NewUnmeteredPublicFunctionMember(
compositeType,
testEmulatorBackendTypeResetFunctionName,
resetFunctionType,
testEmulatorBackendTypeResetFunctionDocString,
),
}

compositeType.Members = sema.MembersAsMap(members)
Expand All @@ -146,6 +194,10 @@ func newTestEmulatorBackendType(blockchainBackendInterfaceType *sema.InterfaceTy
commitBlockFunctionType: commitBlockFunctionType,
deployContractFunctionType: deployContractFunctionType,
useConfigFunctionType: useConfigFunctionType,
logsFunctionType: logsFunctionType,
serviceAccountFunctionType: serviceAccountFunctionType,
eventsFunctionType: eventsFunctionType,
resetFunctionType: resetFunctionType,
}
}

Expand Down Expand Up @@ -494,6 +546,135 @@ func (t *testEmulatorBackendType) newUseConfigFunction(testFramework TestFramewo
)
}

// 'EmulatorBackend.logs' function

const testEmulatorBackendTypeLogsFunctionName = "logs"

const testEmulatorBackendTypeLogsFunctionDocString = `
Returns all the logs from the blockchain, up to the calling point.
`

func (t *testEmulatorBackendType) newLogsFunction(
testFramework TestFramework,
) *interpreter.HostFunctionValue {
return interpreter.NewUnmeteredHostFunctionValue(
t.logsFunctionType,
func(invocation interpreter.Invocation) interpreter.Value {
logs := testFramework.Logs()
inter := invocation.Interpreter

arrayType := interpreter.NewVariableSizedStaticType(
inter,
interpreter.NewPrimitiveStaticType(
inter,
interpreter.PrimitiveStaticTypeString,
),
)

values := make([]interpreter.Value, len(logs))
for i, log := range logs {
memoryUsage := common.NewStringMemoryUsage(len(log))
values[i] = interpreter.NewStringValue(
inter,
memoryUsage,
func() string {
return log
},
)
}

return interpreter.NewArrayValue(
inter,
invocation.LocationRange,
arrayType,
common.ZeroAddress,
values...,
)
},
)
}

// 'EmulatorBackend.serviceAccount' function

const testEmulatorBackendTypeServiceAccountFunctionName = "serviceAccount"

const testEmulatorBackendTypeServiceAccountFunctionDocString = `
Returns the service account of the blockchain. Can be used to sign
transactions with this account.
`

func (t *testEmulatorBackendType) newServiceAccountFunction(
testFramework TestFramework,
) *interpreter.HostFunctionValue {
return interpreter.NewUnmeteredHostFunctionValue(
t.serviceAccountFunctionType,
func(invocation interpreter.Invocation) interpreter.Value {
serviceAccount, err := testFramework.ServiceAccount()
if err != nil {
panic(err)
}

return newTestAccountValue(
testFramework,
invocation.Interpreter,
invocation.LocationRange,
serviceAccount,
)
},
)
}

// 'EmulatorBackend.events' function

const testEmulatorBackendTypeEventsFunctionName = "events"

const testEmulatorBackendTypeEventsFunctionDocString = `
Returns all events emitted from the blockchain,
optionally filtered by event type.
`

func (t *testEmulatorBackendType) newEventsFunction(
testFramework TestFramework,
) *interpreter.HostFunctionValue {
return interpreter.NewUnmeteredHostFunctionValue(
t.eventsFunctionType,
func(invocation interpreter.Invocation) interpreter.Value {
value, ok := invocation.Arguments[0].(interpreter.OptionalValue)
if !ok {
panic(errors.NewUnreachableError())
}

var eventType interpreter.StaticType = nil
_, isNilValue := value.(interpreter.NilValue)
if !isNilValue {
eventType = value.StaticType(invocation.Interpreter)
}

return testFramework.Events(invocation.Interpreter, eventType)
},
)
}

// 'EmulatorBackend.reset' function

const testEmulatorBackendTypeResetFunctionName = "reset"

const testEmulatorBackendTypeResetFunctionDocString = `
Resets the state of the blockchain.
`

func (t *testEmulatorBackendType) newResetFunction(
testFramework TestFramework,
) *interpreter.HostFunctionValue {
return interpreter.NewUnmeteredHostFunctionValue(
t.eventsFunctionType,
func(invocation interpreter.Invocation) interpreter.Value {
testFramework.Reset()
return interpreter.Void
},
)
}

func (t *testEmulatorBackendType) newEmulatorBackend(
inter *interpreter.Interpreter,
testFramework TestFramework,
Expand Down Expand Up @@ -527,6 +708,22 @@ func (t *testEmulatorBackendType) newEmulatorBackend(
Name: testEmulatorBackendTypeUseConfigFunctionName,
Value: t.newUseConfigFunction(testFramework),
},
{
Name: testEmulatorBackendTypeLogsFunctionName,
Value: t.newLogsFunction(testFramework),
},
{
Name: testEmulatorBackendTypeServiceAccountFunctionName,
Value: t.newServiceAccountFunction(testFramework),
},
{
Name: testEmulatorBackendTypeEventsFunctionName,
Value: t.newEventsFunction(testFramework),
},
{
Name: testEmulatorBackendTypeResetFunctionName,
Value: t.newResetFunction(testFramework),
},
}

// TODO: Use SimpleCompositeValue
Expand Down

0 comments on commit 4cb64ae

Please sign in to comment.