Skip to content

Commit

Permalink
Aggregate and expose logs from the Blockchain used for integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
m-Peter committed May 23, 2023
1 parent 4220d31 commit 5e8a74e
Show file tree
Hide file tree
Showing 2 changed files with 286 additions and 7 deletions.
30 changes: 23 additions & 7 deletions test/emulator_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ type EmulatorBackend struct {
configuration *stdlib.Configuration

stdlibHandler stdlib.StandardLibraryHandler

// logCollection is a hook attached in the server logger, in order
// to aggregate and expose log messages from the blockchain.
logCollection *LogCollectionHook
}

type keyInfo struct {
Expand Down Expand Up @@ -102,17 +106,21 @@ func NewEmulatorBackend(
stdlibHandler stdlib.StandardLibraryHandler,
coverageReport *runtime.CoverageReport,
) *EmulatorBackend {
logCollectionHook := &LogCollectionHook{
Logs: make([]string, 0),
}
var blockchain *emulator.Blockchain
if coverageReport != nil {
blockchain = newBlockchain(
logCollectionHook,
emulator.WithCoverageReportingEnabled(true),
)
blockchain.SetCoverageReport(coverageReport)
for _, location := range systemContracts {
coverageReport.ExcludeLocation(location)
}
} else {
blockchain = newBlockchain()
blockchain = newBlockchain(logCollectionHook)
}

return &EmulatorBackend{
Expand All @@ -121,6 +129,7 @@ func NewEmulatorBackend(
accountKeys: map[common.Address]map[string]keyInfo{},
fileResolver: fileResolver,
stdlibHandler: stdlibHandler,
logCollection: logCollectionHook,
}
}

Expand Down Expand Up @@ -419,17 +428,24 @@ func (e *EmulatorBackend) ReadFile(path string) (string, error) {
return e.fileResolver(path)
}

// Logs returns all the log messages from the blockchain.
func (e *EmulatorBackend) Logs() []string {
return e.logCollection.Logs
}

// newBlockchain returns an emulator blockchain for testing.
func newBlockchain(opts ...emulator.Option) *emulator.Blockchain {
func newBlockchain(
hook *LogCollectionHook,
opts ...emulator.Option,
) *emulator.Blockchain {
output := zerolog.ConsoleWriter{Out: os.Stdout}
logger := zerolog.New(output).With().Timestamp().Logger().Hook(hook)

b, err := emulator.NewBlockchain(
append(
[]emulator.Option{
emulator.WithStorageLimitEnabled(false),
emulator.WithServerLogger(
zerolog.New(
zerolog.ConsoleWriter{Out: os.Stdout},
).With().Timestamp().Logger(),
),
emulator.WithServerLogger(logger),
},
opts...,
)...,
Expand Down
263 changes: 263 additions & 0 deletions test/test_framework_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3027,3 +3027,266 @@ func TestRetrieveEmptyLogsFromUnitTests(t *testing.T) {
logs := runner.Logs()
assert.Equal(t, []string{}, logs)
}

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

contractCode := `
pub contract FooContract {
pub let specialNumbers: {Int: String}
init() {
self.specialNumbers = {1729: "Harshad"}
log("init successful")
}
pub fun addSpecialNumber(_ n: Int, _ trait: String) {
self.specialNumbers[n] = trait
log("specialNumbers updated")
}
pub fun getIntegerTrait(_ n: Int): String {
if self.specialNumbers.containsKey(n) {
return self.specialNumbers[n]!
}
return "Unknown"
}
}
`

scriptCode := `
import FooContract from "../contracts/FooContract.cdc"
pub fun main(): Bool {
// Act
let trait = FooContract.getIntegerTrait(1729)
// Assert
assert(trait == "Harshad")
log("getIntegerTrait works")
return true
}
`

testCode := `
import Test
pub let blockchain = Test.newEmulatorBlockchain()
pub let account = blockchain.createAccount()
pub fun setup() {
let contractCode = Test.readFile("../contracts/FooContract.cdc")
let err = blockchain.deployContract(
name: "FooContract",
code: contractCode,
account: account,
arguments: []
)
if err != nil {
panic(err!.message)
}
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, [])
if result.status != Test.ResultStatus.succeeded {
panic(result.error!.message)
}
assert(result.returnValue! as! Bool)
}
pub fun testAddSpecialNumber() {
let code = Test.readFile("../transactions/add_special_number.cdc")
let tx = Test.Transaction(
code: code,
authorizers: [account.address],
signers: [account],
arguments: [78557, "Sierpinski"]
)
let result = blockchain.executeTransaction(tx)
assert(result.status == Test.ResultStatus.succeeded)
}
`

transactionCode := `
import FooContract from "../contracts/FooContract.cdc"
transaction(number: Int, trait: String) {
prepare(acct: AuthAccount) {}
execute {
// Act
FooContract.addSpecialNumber(number, trait)
// Assert
assert(trait == FooContract.getIntegerTrait(number))
log("addSpecialNumber works")
}
}
`

fileResolver := func(path string) (string, error) {
switch path {
case "../contracts/FooContract.cdc":
return contractCode, nil
case "../scripts/get_integer_traits.cdc":
return scriptCode, nil
case "../transactions/add_special_number.cdc":
return transactionCode, nil
default:
return "", fmt.Errorf("cannot find import location: %s", path)
}
}

runner := NewTestRunner().WithFileResolver(fileResolver)

_, err := runner.RunTests(testCode)
require.NoError(t, err)

logs := runner.backend.Logs()
assert.ElementsMatch(
t,
[]string{
"init successful",
"getIntegerTrait works",
"specialNumbers updated",
"addSpecialNumber works",
},
logs,
)
}

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

contractCode := `
pub contract FooContract {
pub let specialNumbers: {Int: String}
init() {
self.specialNumbers = {1729: "Harshad"}
}
pub fun addSpecialNumber(_ n: Int, _ trait: String) {
self.specialNumbers[n] = trait
}
pub fun getIntegerTrait(_ n: Int): String {
if self.specialNumbers.containsKey(n) {
return self.specialNumbers[n]!
}
return "Unknown"
}
}
`

scriptCode := `
import FooContract from "../contracts/FooContract.cdc"
pub fun main(): Bool {
// Act
let trait = FooContract.getIntegerTrait(1729)
// Assert
assert(trait == "Harshad")
return true
}
`

testCode := `
import Test
pub let blockchain = Test.newEmulatorBlockchain()
pub let account = blockchain.createAccount()
pub fun setup() {
let contractCode = Test.readFile("../contracts/FooContract.cdc")
let err = blockchain.deployContract(
name: "FooContract",
code: contractCode,
account: account,
arguments: []
)
if err != nil {
panic(err!.message)
}
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, [])
if result.status != Test.ResultStatus.succeeded {
panic(result.error!.message)
}
assert(result.returnValue! as! Bool)
}
pub fun testAddSpecialNumber() {
let code = Test.readFile("../transactions/add_special_number.cdc")
let tx = Test.Transaction(
code: code,
authorizers: [account.address],
signers: [account],
arguments: [78557, "Sierpinski"]
)
let result = blockchain.executeTransaction(tx)
assert(result.status == Test.ResultStatus.succeeded)
}
`

transactionCode := `
import FooContract from "../contracts/FooContract.cdc"
transaction(number: Int, trait: String) {
prepare(acct: AuthAccount) {}
execute {
// Act
FooContract.addSpecialNumber(number, trait)
// Assert
assert(trait == FooContract.getIntegerTrait(number))
}
}
`

fileResolver := func(path string) (string, error) {
switch path {
case "../contracts/FooContract.cdc":
return contractCode, nil
case "../scripts/get_integer_traits.cdc":
return scriptCode, nil
case "../transactions/add_special_number.cdc":
return transactionCode, nil
default:
return "", fmt.Errorf("cannot find import location: %s", path)
}
}

runner := NewTestRunner().WithFileResolver(fileResolver)

_, err := runner.RunTests(testCode)
require.NoError(t, err)

logs := runner.backend.Logs()
assert.Equal(t, []string{}, logs)
}

0 comments on commit 5e8a74e

Please sign in to comment.