Skip to content

Commit

Permalink
Add native function declarations for all revelent Test contract funct…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
m-Peter committed Nov 6, 2023
1 parent 1458dcf commit e4bba03
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 7 deletions.
83 changes: 83 additions & 0 deletions runtime/stdlib/contracts/test.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -437,4 +437,87 @@ access(all) contract Test {
assert(found, message: "the error message did not contain the given sub-string")
}

/// Creates a matcher with a test function.
/// The test function is of type '((T): Bool)',
/// where 'T' is bound to 'AnyStruct'.
///
access(all)
native fun newMatcher<T: AnyStruct>(_ test: ((T): Bool)): Test.Matcher {}

/// Wraps a function call in a closure, and expects it to fail with
/// an error message that contains the given error message portion.
///
access(all)
native fun expectFailure(
_ functionWrapper: ((): Void),
errorMessageSubstring: String
) {}

/// Expect function tests a value against a matcher
/// and fails the test if it's not a match.
///
access(all)
native fun expect<T: AnyStruct>(_ value: T, _ matcher: Test.Matcher) {}

/// Returns a matcher that succeeds if the tested
/// value is equal to the given value.
///
access(all)
native fun equal<T: AnyStruct>(_ value: T): Test.Matcher {}

/// Fails the test-case if the given values are not equal, and
/// reports a message which explains how the two values differ.
///
access(all)
native fun assertEqual(_ expected: AnyStruct, _ actual: AnyStruct) {}

/// Returns a matcher that succeeds if the tested value is
/// an array or dictionary and the tested value contains
/// no elements.
///
access(all)
native fun beEmpty(): Test.Matcher {}

/// Returns a matcher that succeeds if the tested value is
/// an array or dictionary and has the given number of elements.
///
access(all)
native fun haveElementCount(_ count: Int): Test.Matcher {}

/// Returns a matcher that succeeds if the tested value is
/// an array that contains a value that is equal to the given
/// value, or the tested value is a dictionary that contains
/// an entry where the key is equal to the given value.
///
access(all)
native fun contain(_ element: AnyStruct): Test.Matcher {}

/// Returns a matcher that succeeds if the tested value
/// is a number and greater than the given number.
///
access(all)
native fun beGreaterThan(_ value: Number): Test.Matcher {}

/// Returns a matcher that succeeds if the tested value
/// is a number and less than the given number.
///
access(all)
native fun beLessThan(_ value: Number): Test.Matcher {}

/// Read a local file, and return the content as a string.
///
access(all)
native fun readFile(_ path: String): String {}

/// Fails the test-case if the given condition is false,
/// and reports a message which explains how the condition is false.
///
access(all)
native fun assert(_ condition: Bool, message: String): Void {}

/// Fails the test-case with a message.
///
access(all)
native fun fail(message: String): Void {}

}
10 changes: 7 additions & 3 deletions runtime/stdlib/test_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,10 @@ func newTestContractType() *TestContractType {
program, err := parser.ParseProgram(
nil,
contracts.TestContract,
parser.Config{},
parser.Config{
NativeModifierEnabled: true,
TypeParametersEnabled: true,
},
)
if err != nil {
panic(err)
Expand All @@ -965,8 +968,9 @@ func newTestContractType() *TestContractType {
TestContractLocation,
nil,
&sema.Config{
BaseValueActivation: activation,
AccessCheckMode: sema.AccessCheckModeStrict,
BaseValueActivation: activation,
AccessCheckMode: sema.AccessCheckModeStrict,
AllowNativeDeclarations: true,
},
)
if err != nil {
Expand Down
151 changes: 147 additions & 4 deletions runtime/stdlib/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ func newTestContractInterpreterWithTestFramework(
program, err := parser.ParseProgram(
nil,
[]byte(code),
parser.Config{},
parser.Config{
NativeModifierEnabled: true,
TypeParametersEnabled: true,
},
)
require.NoError(t, err)

Expand All @@ -70,8 +73,9 @@ func newTestContractInterpreterWithTestFramework(
utils.TestLocation,
nil,
&sema.Config{
BaseValueActivation: activation,
AccessCheckMode: sema.AccessCheckModeStrict,
BaseValueActivation: activation,
AccessCheckMode: sema.AccessCheckModeStrict,
AllowNativeDeclarations: true,
ImportHandler: func(
checker *sema.Checker,
importedLocation common.Location,
Expand Down Expand Up @@ -660,6 +664,74 @@ func TestTestEqualMatcher(t *testing.T) {
})
}

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

const script = `
import Test
pub fun testAssertWithNoArgs() {
Test.assert(true)
}
pub fun testAssertWithNoArgsFail() {
Test.assert(false)
}
pub fun testAssertWithMessage() {
Test.assert(true, message: "some reason")
}
pub fun testAssertWithMessageFail() {
Test.assert(false, message: "some reason")
}
`

inter, err := newTestContractInterpreter(t, script)
require.NoError(t, err)

_, err = inter.Invoke("testAssertWithNoArgs")
require.NoError(t, err)

_, err = inter.Invoke("testAssertWithNoArgsFail")
require.Error(t, err)
assert.ErrorContains(t, err, "assertion failed")

_, err = inter.Invoke("testAssertWithMessage")
require.NoError(t, err)

_, err = inter.Invoke("testAssertWithMessageFail")
require.Error(t, err)
require.ErrorContains(t, err, "assertion failed: some reason")
}

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

const script = `
import Test
pub fun testFailWithoutMessage() {
Test.fail()
}
pub fun testFailWithMessage() {
Test.fail(message: "some error")
}
`

inter, err := newTestContractInterpreter(t, script)
require.NoError(t, err)

_, err = inter.Invoke("testFailWithoutMessage")
require.Error(t, err)
require.ErrorContains(t, err, "assertion failed")

_, err = inter.Invoke("testFailWithMessage")
require.Error(t, err)
require.ErrorContains(t, err, "assertion failed: some error")
}

func TestAssertEqual(t *testing.T) {

t.Parallel()
Expand Down Expand Up @@ -931,7 +1003,7 @@ func TestAssertEqual(t *testing.T) {
pub fun test() {
let foo = Foo()
let bar <- create Bar()
Test.expect(foo, Test.equal(<-bar))
Test.assertEqual(foo, <-bar)
}
pub struct Foo {}
Expand Down Expand Up @@ -2645,6 +2717,77 @@ func TestBlockchain(t *testing.T) {
assert.True(t, getAccountInvoked)
})

t.Run("readFile", func(t *testing.T) {
t.Parallel()

const script = `
import Test
pub fun test() {
let content = Test.readFile("some_file.cdc")
Test.assertEqual("Hey there!", content)
}
`

readFileInvoked := false

testFramework := &mockedTestFramework{
emulatorBackend: func() stdlib.Blockchain {
return &mockedBlockchain{}
},
readFile: func(path string) (string, error) {
readFileInvoked = true
assert.Equal(t, "some_file.cdc", path)

return "Hey there!", nil
},
}

inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework)
require.NoError(t, err)

_, err = inter.Invoke("test")
require.NoError(t, err)

assert.True(t, readFileInvoked)
})

t.Run("readFile with failure", func(t *testing.T) {
t.Parallel()

const script = `
import Test
pub fun test() {
let content = Test.readFile("some_file.cdc")
Test.assertEqual("Hey there!", content)
}
`

readFileInvoked := false

testFramework := &mockedTestFramework{
emulatorBackend: func() stdlib.Blockchain {
return &mockedBlockchain{}
},
readFile: func(path string) (string, error) {
readFileInvoked = true
assert.Equal(t, "some_file.cdc", path)

return "", fmt.Errorf("could not read file: %s", path)
},
}

inter, err := newTestContractInterpreterWithTestFramework(t, script, testFramework)
require.NoError(t, err)

_, err = inter.Invoke("test")
require.Error(t, err)
assert.ErrorContains(t, err, "could not read file: some_file.cdc")

assert.True(t, readFileInvoked)
})

// TODO: Add more tests for the remaining functions.
}

Expand Down

0 comments on commit e4bba03

Please sign in to comment.