From 22132d852ca83b09aafd30eb429cf45c1029e1f8 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 27 Oct 2025 16:51:06 -0700 Subject: [PATCH 1/6] Add fork testing support to Cadence Test Framework --- go.mod | 4 +- go.sum | 8 ++-- internal/test/test.go | 50 +++++++++++++++++++++- internal/test/test_test.go | 88 ++++++++++++++++++++++++++++++++++++-- internal/util/util.go | 35 +++++++++++++++ 5 files changed, 174 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index ff3492474..11c5760f1 100644 --- a/go.mod +++ b/go.mod @@ -15,11 +15,11 @@ require ( github.com/onflow/cadence v1.8.1 github.com/onflow/cadence-tools/languageserver v1.7.0 github.com/onflow/cadence-tools/lint v1.6.0 - github.com/onflow/cadence-tools/test v1.7.0 + github.com/onflow/cadence-tools/test v1.7.1-0.20251024154941-cbcc3082a8d9 github.com/onflow/fcl-dev-wallet v0.8.0 github.com/onflow/flixkit-go/v2 v2.6.0 github.com/onflow/flow-core-contracts/lib/go/templates v1.9.1 - github.com/onflow/flow-emulator v1.10.0 + github.com/onflow/flow-emulator v1.10.1 github.com/onflow/flow-evm-gateway v1.3.5 github.com/onflow/flow-go v0.43.3-0.20251021182938-b0fef2c5ca47 github.com/onflow/flow-go-sdk v1.9.0 diff --git a/go.sum b/go.sum index b59acd689..c391b83d5 100644 --- a/go.sum +++ b/go.sum @@ -779,8 +779,8 @@ github.com/onflow/cadence-tools/languageserver v1.7.0 h1:Bf8Ef6oSxlkwr34UAUzUwrO github.com/onflow/cadence-tools/languageserver v1.7.0/go.mod h1:uIKKHJNKR02BmTMKsE8+UW84db+RfpoBD0xXpTzrcSM= github.com/onflow/cadence-tools/lint v1.6.0 h1:xtgVUzQQWIVGe0tvJNov9zc9o1t2kE3eBtPsIEKZwDY= github.com/onflow/cadence-tools/lint v1.6.0/go.mod h1:SpTwSUwZuWl5Gdl6tn97kD/qVAMp8u3xPLjbR3GJ8ZE= -github.com/onflow/cadence-tools/test v1.7.0 h1:TeomK+uVFwmvYdU0RLvRNgwbYgeb5j8QNv0Z9amhxtE= -github.com/onflow/cadence-tools/test v1.7.0/go.mod h1:9gfshvyBMkb1Kut8j5XdVA874L7NWpEaH+REwMp9URY= +github.com/onflow/cadence-tools/test v1.7.1-0.20251024154941-cbcc3082a8d9 h1:0mtib01RICP/RvJbQImYDv3Td4O/3jRp+ctc5juHvwE= +github.com/onflow/cadence-tools/test v1.7.1-0.20251024154941-cbcc3082a8d9/go.mod h1:FRfS8/qX12UOSBzORc9+SgOVOK8Sg5nkxVJbdfiNPbY= github.com/onflow/crypto v0.25.3 h1:XQ3HtLsw8h1+pBN+NQ1JYM9mS2mVXTyg55OldaAIF7U= github.com/onflow/crypto v0.25.3/go.mod h1:+1igaXiK6Tjm9wQOBD1EGwW7bYWMUGKtwKJ/2QL/OWs= github.com/onflow/fcl-dev-wallet v0.8.0 h1:8TWHhJBWrzS6RCZI3eVjRT+SaUBqO6eygUNDaJV/B7s= @@ -793,8 +793,8 @@ github.com/onflow/flow-core-contracts/lib/go/contracts v1.9.1 h1:u6am8NzuWOIKkSk github.com/onflow/flow-core-contracts/lib/go/contracts v1.9.1/go.mod h1:jBDqVep0ICzhXky56YlyO4aiV2Jl/5r7wnqUPpvi7zE= github.com/onflow/flow-core-contracts/lib/go/templates v1.9.1 h1:ebyynXy74ZcfW+JpPwI+aaY0ezlxxA0cUgUrjhJonWg= github.com/onflow/flow-core-contracts/lib/go/templates v1.9.1/go.mod h1:twSVyUt3rNrgzAmxtBX+1Gw64QlPemy17cyvnXYy1Ug= -github.com/onflow/flow-emulator v1.10.0 h1:zrAlCP6yEFmlDg80fja55AqwVtD00OmrVGzeBf+gvcg= -github.com/onflow/flow-emulator v1.10.0/go.mod h1:t4mJAxj+czpJz6y/Jz4POw5ylBDXPrXFYejm2Env9Ak= +github.com/onflow/flow-emulator v1.10.1 h1:c/wtpXDI0o+n/icDUzSgCvT/4mT6WYW+nxaeiggmdGY= +github.com/onflow/flow-emulator v1.10.1/go.mod h1:+PbfGuya48rdW80en3msv2CLH8XM+7YEZYFHNIDNpeo= github.com/onflow/flow-evm-bridge v0.1.0 h1:7X2osvo4NnQgHj8aERUmbYtv9FateX8liotoLnPL9nM= github.com/onflow/flow-evm-bridge v0.1.0/go.mod h1:5UYwsnu6WcBNrwitGFxphCl5yq7fbWYGYuiCSTVF6pk= github.com/onflow/flow-evm-gateway v1.3.5 h1:2Nx5eCYwUsVBVOMNOMPab66PNKj8784t+SPgAckw2zk= diff --git a/internal/test/test.go b/internal/test/test.go index 2a91c1a8d..e8136d369 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -31,6 +31,7 @@ import ( cdcTests "github.com/onflow/cadence-tools/test" "github.com/onflow/cadence/common" "github.com/onflow/cadence/runtime" + flowgo "github.com/onflow/flow-go/model/flow" "github.com/rs/zerolog" "github.com/spf13/cobra" @@ -74,6 +75,11 @@ type flagsTests struct { Random bool `default:"false" flag:"random" info:"Use the random flag to execute test cases randomly"` Seed int64 `default:"0" flag:"seed" info:"Use the seed flag to manipulate random execution of test cases"` Name string `default:"" flag:"name" info:"Use the name flag to run only tests that match the given name"` + + // Fork mode flags + Fork string `default:"" info:"Fork tests from a remote network defined in flow.json (typically mainnet or testnet). If provided without a value, defaults to mainnet."` + ForkHost string `default:"" flag:"fork-host" info:"Run tests against a fork of a remote network. Provide the GRPC Access host (host:port)."` + ForkHeight uint64 `default:"0" flag:"fork-height" info:"Optional block height to pin the fork (if supported)."` } var testFlags = flagsTests{} @@ -94,6 +100,13 @@ flow test test1.cdc test2.cdc`, RunS: run, } +func init() { + // add default value to --fork flag + if f := TestCommand.Cmd.Flags().Lookup("fork"); f != nil { + f.NoOptDefVal = "mainnet" + } +} + func run( args []string, _ command.GlobalFlags, @@ -171,6 +184,36 @@ func testCode( logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger() runner := cdcTests.NewTestRunner().WithLogger(logger) + // Configure fork mode if requested + effectiveForkHost := strings.TrimSpace(flags.ForkHost) + var forkChainID flowgo.ChainID + + if effectiveForkHost == "" && strings.TrimSpace(flags.Fork) != "" { + // Resolve network endpoint from flow.json + network, err := state.Networks().ByName(strings.ToLower(flags.Fork)) + if err != nil { + return nil, fmt.Errorf("network %q not found in flow.json", flags.Fork) + } + effectiveForkHost = network.Host + if effectiveForkHost == "" { + return nil, fmt.Errorf("network %q has no host configured", flags.Fork) + } + + // Detect chain ID from the network + forkChainID, err = util.GetNetworkChainID(state, strings.ToLower(flags.Fork)) + if err != nil { + return nil, err + } + } + + if effectiveForkHost != "" { + runner = runner.WithFork(cdcTests.ForkConfig{ + ForkHost: effectiveForkHost, + ChainID: forkChainID, + ForkHeight: flags.ForkHeight, + }) + } + var coverageReport *runtime.CoverageReport if flags.Cover { coverageReport = state.CreateCoverageReport("testing") @@ -199,8 +242,13 @@ func testCode( contractsConfig := *state.Contracts() contracts := make(map[string]common.Address, len(contractsConfig)) + // Choose alias network: default to "testing", but in fork mode use selected chain (mainnet/testnet) + aliasNetwork := "testing" + if flags.Fork != "" { + aliasNetwork = flags.Fork + } for _, contract := range contractsConfig { - alias := contract.Aliases.ByNetwork("testing") + alias := contract.Aliases.ByNetwork(aliasNetwork) if alias != nil { contracts[contract.Name] = common.Address(alias.Address) } diff --git a/internal/test/test_test.go b/internal/test/test_test.go index 447356eb8..9b8659960 100644 --- a/internal/test/test_test.go +++ b/internal/test/test_test.go @@ -76,8 +76,7 @@ func TestExecutingTests(t *testing.T) { err = result.Results[script.Filename][0].Error require.Error(t, err) - var assertionErr *stdlib.AssertionError - assert.ErrorAs(t, err, &assertionErr) + assert.ErrorAs(t, err, &stdlib.AssertionError{}) }) t.Run("with import", func(t *testing.T) { @@ -712,8 +711,7 @@ Seed: 1521 assert.Len(t, result.Results, 2) assert.NoError(t, result.Results[scriptPassing.Filename][0].Error) assert.Error(t, result.Results[scriptFailing.Filename][0].Error) - var assertionErr *stdlib.AssertionError - assert.ErrorAs(t, result.Results[scriptFailing.Filename][0].Error, &assertionErr) + assert.ErrorAs(t, result.Results[scriptFailing.Filename][0].Error, &stdlib.AssertionError{}) assert.Contains( t, @@ -755,3 +753,85 @@ Seed: 1521 ) }) } + +func TestForkMode_UsesMainnetAliases(t *testing.T) { + t.Parallel() + + _, state, _ := util.TestMocks(t) + + // Provide only mainnet alias; no testing alias on purpose + mainnetAliases := config.Aliases{{ + Network: "mainnet", + Address: flowsdk.HexToAddress("0x0000000000000007"), + }} + c := config.Contract{ + Name: tests.ContractHelloString.Name, + Location: tests.ContractHelloString.Filename, + Aliases: mainnetAliases, + } + state.Contracts().AddOrUpdate(c) + + script := tests.TestScriptWithImport + testFiles := map[string][]byte{ + script.Filename: script.Source, + } + + flags := flagsTests{ + ForkHost: "access.mainnet.nodes.onflow.org:9000", + Fork: "mainnet", + } + + result, err := testCode(testFiles, state, flags) + + require.NoError(t, err) + require.Len(t, result.Results, 1) + assert.NoError(t, result.Results[script.Filename][0].Error) +} + +func TestForkMode_UsesTestnetAliasesExplicit(t *testing.T) { + t.Parallel() + + _, state, _ := util.TestMocks(t) + + testnetAliases := config.Aliases{{ + Network: "testnet", + Address: flowsdk.HexToAddress("0x0000000000000007"), + }} + c := config.Contract{ + Name: tests.ContractHelloString.Name, + Location: tests.ContractHelloString.Filename, + Aliases: testnetAliases, + } + state.Contracts().AddOrUpdate(c) + + script := tests.TestScriptWithImport + testFiles := map[string][]byte{ + script.Filename: script.Source, + } + + flags := flagsTests{ + ForkHost: "access.testnet.nodes.onflow.org:9000", + Fork: "testnet", + } + + result, err := testCode(testFiles, state, flags) + + require.NoError(t, err) + require.Len(t, result.Results, 1) + assert.NoError(t, result.Results[script.Filename][0].Error) +} + +func TestForkMode_AutodetectFailureRequiresExplicitNetwork(t *testing.T) { + t.Parallel() + + _, state, _ := util.TestMocks(t) + + // No network hints in URL; expect early error + flags := flagsTests{ + ForkHost: "rpc.foobar.org:9000", + } + + _, err := testCode(map[string][]byte{}, state, flags) + require.Error(t, err) + assert.ErrorContains(t, err, "could not auto-detect fork network") +} diff --git a/internal/util/util.go b/internal/util/util.go index 9391218bd..8a44bd025 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -20,6 +20,7 @@ package util import ( "bytes" + "context" "encoding/hex" "fmt" "net" @@ -34,6 +35,9 @@ import ( "github.com/onflow/flow-go-sdk/crypto" "github.com/onflow/flow-go/fvm/systemcontracts" flowGo "github.com/onflow/flow-go/model/flow" + flowaccess "github.com/onflow/flow/protobuf/go/flow/access" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" "github.com/onflow/flowkit/v2" "github.com/onflow/flowkit/v2/config" @@ -238,6 +242,37 @@ func NetworkToChainID(network string) (flow.ChainID, error) { } } +// GetNetworkChainID resolves a network name from flow.json and returns its chain ID. +// It queries the network's access node via GetNetworkParameters to detect the chain ID. +func GetNetworkChainID(state *flowkit.State, networkName string) (flowGo.ChainID, error) { + network, err := state.Networks().ByName(networkName) + if err != nil { + return "", fmt.Errorf("network %q not found in flow.json", networkName) + } + + host := network.Host + if host == "" { + return "", fmt.Errorf("network %q has no host configured", networkName) + } + + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + conn, err := grpc.NewClient(host, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return "", fmt.Errorf("failed to connect to %s: %w", host, err) + } + defer conn.Close() + + client := flowaccess.NewAccessAPIClient(conn) + resp, err := client.GetNetworkParameters(ctx, &flowaccess.GetNetworkParametersRequest{}) + if err != nil { + return "", fmt.Errorf("failed to get network parameters from %s: %w", host, err) + } + + return flowGo.ChainID(resp.GetChainId()), nil +} + func CreateTabWriter(b *bytes.Buffer) *tabwriter.Writer { return tabwriter.NewWriter(b, 0, 8, 1, '\t', tabwriter.AlignRight) } From e15b9b97934a809226b098eecc8e67b92c868a88 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 27 Oct 2025 19:05:25 -0700 Subject: [PATCH 2/6] fix: fork mode tests and util --- internal/test/test.go | 26 ++++++----- internal/test/test_test.go | 91 ++++++++++++++++++++++++++++++++------ internal/util/util.go | 30 ++++++++----- 3 files changed, 109 insertions(+), 38 deletions(-) diff --git a/internal/test/test.go b/internal/test/test.go index e8136d369..da847393e 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -31,7 +31,6 @@ import ( cdcTests "github.com/onflow/cadence-tools/test" "github.com/onflow/cadence/common" "github.com/onflow/cadence/runtime" - flowgo "github.com/onflow/flow-go/model/flow" "github.com/rs/zerolog" "github.com/spf13/cobra" @@ -185,12 +184,15 @@ func testCode( runner := cdcTests.NewTestRunner().WithLogger(logger) // Configure fork mode if requested - effectiveForkHost := strings.TrimSpace(flags.ForkHost) - var forkChainID flowgo.ChainID - - if effectiveForkHost == "" && strings.TrimSpace(flags.Fork) != "" { - // Resolve network endpoint from flow.json - network, err := state.Networks().ByName(strings.ToLower(flags.Fork)) + var effectiveForkHost string + + // Determine the fork host + if flags.ForkHost != "" { + effectiveForkHost = strings.TrimSpace(flags.ForkHost) + } else if flags.Fork != "" { + // Look up network in flow.json + forkNetwork := strings.ToLower(flags.Fork) + network, err := state.Networks().ByName(forkNetwork) if err != nil { return nil, fmt.Errorf("network %q not found in flow.json", flags.Fork) } @@ -198,15 +200,15 @@ func testCode( if effectiveForkHost == "" { return nil, fmt.Errorf("network %q has no host configured", flags.Fork) } + } - // Detect chain ID from the network - forkChainID, err = util.GetNetworkChainID(state, strings.ToLower(flags.Fork)) + // If fork mode is enabled, query the host to get chain ID + if effectiveForkHost != "" { + forkChainID, err := util.GetChainIDFromHost(effectiveForkHost) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get chain ID from fork host %q: %w", effectiveForkHost, err) } - } - if effectiveForkHost != "" { runner = runner.WithFork(cdcTests.ForkConfig{ ForkHost: effectiveForkHost, ChainID: forkChainID, diff --git a/internal/test/test_test.go b/internal/test/test_test.go index 9b8659960..ce6719523 100644 --- a/internal/test/test_test.go +++ b/internal/test/test_test.go @@ -759,21 +759,52 @@ func TestForkMode_UsesMainnetAliases(t *testing.T) { _, state, _ := util.TestMocks(t) - // Provide only mainnet alias; no testing alias on purpose + // Use a real mainnet address (FlowToken system contract) + // This verifies fork mode correctly resolves mainnet aliases mainnetAliases := config.Aliases{{ Network: "mainnet", - Address: flowsdk.HexToAddress("0x0000000000000007"), + Address: flowsdk.HexToAddress("0x1654653399040a61"), // FlowToken on mainnet }} + + // Create a simple test contract to deploy + testContractSource := []byte(` + access(all) contract TestContract { + access(all) var value: Int + init() { self.value = 42 } + access(all) fun getValue(): Int { return self.value } + } + `) + _ = state.ReaderWriter().WriteFile("TestContract.cdc", testContractSource, 0644) + c := config.Contract{ - Name: tests.ContractHelloString.Name, - Location: tests.ContractHelloString.Filename, + Name: "TestContract", + Location: "TestContract.cdc", Aliases: mainnetAliases, } state.Contracts().AddOrUpdate(c) - script := tests.TestScriptWithImport + // Test script that deploys and uses the contract + testScript := []byte(` + import Test + + access(all) fun testDeployAndUse() { + let err = Test.deployContract( + name: "TestContract", + path: "TestContract.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) + + // Verify the contract deployed and works + let script = "import TestContract from 0x1654653399040a61\naccess(all) fun main(): Int { return TestContract.getValue() }" + let result = Test.executeScript(script, []) + Test.expect(result, Test.beSucceeded()) + Test.assertEqual(42, result.returnValue! as! Int) + } + `) + testFiles := map[string][]byte{ - script.Filename: script.Source, + "test_mainnet_fork.cdc": testScript, } flags := flagsTests{ @@ -785,7 +816,7 @@ func TestForkMode_UsesMainnetAliases(t *testing.T) { require.NoError(t, err) require.Len(t, result.Results, 1) - assert.NoError(t, result.Results[script.Filename][0].Error) + assert.NoError(t, result.Results["test_mainnet_fork.cdc"][0].Error) } func TestForkMode_UsesTestnetAliasesExplicit(t *testing.T) { @@ -793,20 +824,52 @@ func TestForkMode_UsesTestnetAliasesExplicit(t *testing.T) { _, state, _ := util.TestMocks(t) + // Use a real testnet address (FlowToken system contract testnet address) + // This verifies fork mode correctly resolves testnet aliases testnetAliases := config.Aliases{{ Network: "testnet", - Address: flowsdk.HexToAddress("0x0000000000000007"), + Address: flowsdk.HexToAddress("0x7e60df042a9c0868"), // FlowToken on testnet }} + + // Create a simple test contract to deploy + testContractSource := []byte(` + access(all) contract TestContract { + access(all) var value: String + init() { self.value = "testnet" } + access(all) fun getValue(): String { return self.value } + } + `) + _ = state.ReaderWriter().WriteFile("TestContract.cdc", testContractSource, 0644) + c := config.Contract{ - Name: tests.ContractHelloString.Name, - Location: tests.ContractHelloString.Filename, + Name: "TestContract", + Location: "TestContract.cdc", Aliases: testnetAliases, } state.Contracts().AddOrUpdate(c) - script := tests.TestScriptWithImport + // Test script that deploys and uses the contract + testScript := []byte(` + import Test + + access(all) fun testDeployAndUse() { + let err = Test.deployContract( + name: "TestContract", + path: "TestContract.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) + + // Verify the contract deployed and works + let script = "import TestContract from 0x7e60df042a9c0868\naccess(all) fun main(): String { return TestContract.getValue() }" + let result = Test.executeScript(script, []) + Test.expect(result, Test.beSucceeded()) + Test.assertEqual("testnet", result.returnValue! as! String) + } + `) + testFiles := map[string][]byte{ - script.Filename: script.Source, + "test_testnet_fork.cdc": testScript, } flags := flagsTests{ @@ -818,7 +881,7 @@ func TestForkMode_UsesTestnetAliasesExplicit(t *testing.T) { require.NoError(t, err) require.Len(t, result.Results, 1) - assert.NoError(t, result.Results[script.Filename][0].Error) + assert.NoError(t, result.Results["test_testnet_fork.cdc"][0].Error) } func TestForkMode_AutodetectFailureRequiresExplicitNetwork(t *testing.T) { @@ -833,5 +896,5 @@ func TestForkMode_AutodetectFailureRequiresExplicitNetwork(t *testing.T) { _, err := testCode(map[string][]byte{}, state, flags) require.Error(t, err) - assert.ErrorContains(t, err, "could not auto-detect fork network") + assert.ErrorContains(t, err, "failed to get chain ID from fork host") } diff --git a/internal/util/util.go b/internal/util/util.go index 8a44bd025..2161add28 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -242,18 +242,8 @@ func NetworkToChainID(network string) (flow.ChainID, error) { } } -// GetNetworkChainID resolves a network name from flow.json and returns its chain ID. -// It queries the network's access node via GetNetworkParameters to detect the chain ID. -func GetNetworkChainID(state *flowkit.State, networkName string) (flowGo.ChainID, error) { - network, err := state.Networks().ByName(networkName) - if err != nil { - return "", fmt.Errorf("network %q not found in flow.json", networkName) - } - - host := network.Host - if host == "" { - return "", fmt.Errorf("network %q has no host configured", networkName) - } +// GetChainIDFromHost queries the given host directly to get its chain ID. +func GetChainIDFromHost(host string) (flowGo.ChainID, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -273,6 +263,22 @@ func GetNetworkChainID(state *flowkit.State, networkName string) (flowGo.ChainID return flowGo.ChainID(resp.GetChainId()), nil } +// GetNetworkChainID resolves a network name from flow.json and returns its chain ID. +// It queries the network's access node via GetNetworkParameters to detect the chain ID. +func GetNetworkChainID(state *flowkit.State, networkName string) (flowGo.ChainID, error) { + network, err := state.Networks().ByName(networkName) + if err != nil { + return "", fmt.Errorf("network %q not found in flow.json", networkName) + } + + host := network.Host + if host == "" { + return "", fmt.Errorf("network %q has no host configured", networkName) + } + + return GetChainIDFromHost(host) +} + func CreateTabWriter(b *bytes.Buffer) *tabwriter.Writer { return tabwriter.NewWriter(b, 0, 8, 1, '\t', tabwriter.AlignRight) } From 9a6d0588a21a93777102d9bcc02d955c71593ac5 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 27 Oct 2025 19:08:46 -0700 Subject: [PATCH 3/6] Address feedback --- internal/test/test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/test/test.go b/internal/test/test.go index da847393e..70b6b044b 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -76,7 +76,7 @@ type flagsTests struct { Name string `default:"" flag:"name" info:"Use the name flag to run only tests that match the given name"` // Fork mode flags - Fork string `default:"" info:"Fork tests from a remote network defined in flow.json (typically mainnet or testnet). If provided without a value, defaults to mainnet."` + Fork string `default:"" flag:"fork" info:"Fork tests from a remote network defined in flow.json (typically mainnet or testnet). If provided without a value, defaults to mainnet."` ForkHost string `default:"" flag:"fork-host" info:"Run tests against a fork of a remote network. Provide the GRPC Access host (host:port)."` ForkHeight uint64 `default:"0" flag:"fork-height" info:"Optional block height to pin the fork (if supported)."` } @@ -246,8 +246,8 @@ func testCode( contracts := make(map[string]common.Address, len(contractsConfig)) // Choose alias network: default to "testing", but in fork mode use selected chain (mainnet/testnet) aliasNetwork := "testing" - if flags.Fork != "" { - aliasNetwork = flags.Fork + if strings.TrimSpace(flags.Fork) != "" { + aliasNetwork = strings.ToLower(flags.Fork) } for _, contract := range contractsConfig { alias := contract.Aliases.ByNetwork(aliasNetwork) From e29a595d4eaf9ccc0e468a52c6b64a685e941ed6 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 27 Oct 2025 20:23:44 -0700 Subject: [PATCH 4/6] fix tests --- internal/test/test_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/test/test_test.go b/internal/test/test_test.go index ce6719523..9084d0478 100644 --- a/internal/test/test_test.go +++ b/internal/test/test_test.go @@ -76,7 +76,8 @@ func TestExecutingTests(t *testing.T) { err = result.Results[script.Filename][0].Error require.Error(t, err) - assert.ErrorAs(t, err, &stdlib.AssertionError{}) + var assertionErr *stdlib.AssertionError + assert.ErrorAs(t, err, &assertionErr) }) t.Run("with import", func(t *testing.T) { @@ -375,7 +376,7 @@ func TestExecutingTests(t *testing.T) { coverageReport.ExcludedLocationIDs(), ) - expected := "Coverage: 93.9% of statements" + expected := "Coverage: 93.7% of statements" assert.Equal( t, @@ -711,7 +712,8 @@ Seed: 1521 assert.Len(t, result.Results, 2) assert.NoError(t, result.Results[scriptPassing.Filename][0].Error) assert.Error(t, result.Results[scriptFailing.Filename][0].Error) - assert.ErrorAs(t, result.Results[scriptFailing.Filename][0].Error, &stdlib.AssertionError{}) + var assertionErr2 *stdlib.AssertionError + assert.ErrorAs(t, result.Results[scriptFailing.Filename][0].Error, &assertionErr2) assert.Contains( t, From bf58a81b7d3ff963aedaae59762fb56b25a87cf7 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Tue, 28 Oct 2025 05:24:58 -0700 Subject: [PATCH 5/6] update test framework --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 11c5760f1..b1452b70f 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/onflow/cadence v1.8.1 github.com/onflow/cadence-tools/languageserver v1.7.0 github.com/onflow/cadence-tools/lint v1.6.0 - github.com/onflow/cadence-tools/test v1.7.1-0.20251024154941-cbcc3082a8d9 + github.com/onflow/cadence-tools/test v1.8.0 github.com/onflow/fcl-dev-wallet v0.8.0 github.com/onflow/flixkit-go/v2 v2.6.0 github.com/onflow/flow-core-contracts/lib/go/templates v1.9.1 diff --git a/go.sum b/go.sum index c391b83d5..fb0c5571e 100644 --- a/go.sum +++ b/go.sum @@ -779,8 +779,8 @@ github.com/onflow/cadence-tools/languageserver v1.7.0 h1:Bf8Ef6oSxlkwr34UAUzUwrO github.com/onflow/cadence-tools/languageserver v1.7.0/go.mod h1:uIKKHJNKR02BmTMKsE8+UW84db+RfpoBD0xXpTzrcSM= github.com/onflow/cadence-tools/lint v1.6.0 h1:xtgVUzQQWIVGe0tvJNov9zc9o1t2kE3eBtPsIEKZwDY= github.com/onflow/cadence-tools/lint v1.6.0/go.mod h1:SpTwSUwZuWl5Gdl6tn97kD/qVAMp8u3xPLjbR3GJ8ZE= -github.com/onflow/cadence-tools/test v1.7.1-0.20251024154941-cbcc3082a8d9 h1:0mtib01RICP/RvJbQImYDv3Td4O/3jRp+ctc5juHvwE= -github.com/onflow/cadence-tools/test v1.7.1-0.20251024154941-cbcc3082a8d9/go.mod h1:FRfS8/qX12UOSBzORc9+SgOVOK8Sg5nkxVJbdfiNPbY= +github.com/onflow/cadence-tools/test v1.8.0 h1:V/dux1JuUHllxBPPAzEk5au3AZeYcpGQmDi9HUJwLk8= +github.com/onflow/cadence-tools/test v1.8.0/go.mod h1:FRfS8/qX12UOSBzORc9+SgOVOK8Sg5nkxVJbdfiNPbY= github.com/onflow/crypto v0.25.3 h1:XQ3HtLsw8h1+pBN+NQ1JYM9mS2mVXTyg55OldaAIF7U= github.com/onflow/crypto v0.25.3/go.mod h1:+1igaXiK6Tjm9wQOBD1EGwW7bYWMUGKtwKJ/2QL/OWs= github.com/onflow/fcl-dev-wallet v0.8.0 h1:8TWHhJBWrzS6RCZI3eVjRT+SaUBqO6eygUNDaJV/B7s= From 36a98c641b084dcd8d2604cb46db81f74bc676ef Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Tue, 28 Oct 2025 09:09:10 -0700 Subject: [PATCH 6/6] fix flag default --- internal/test/test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/test/test.go b/internal/test/test.go index 70b6b044b..be5e3c232 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -76,7 +76,7 @@ type flagsTests struct { Name string `default:"" flag:"name" info:"Use the name flag to run only tests that match the given name"` // Fork mode flags - Fork string `default:"" flag:"fork" info:"Fork tests from a remote network defined in flow.json (typically mainnet or testnet). If provided without a value, defaults to mainnet."` + Fork string // Use definition in init() ForkHost string `default:"" flag:"fork-host" info:"Run tests against a fork of a remote network. Provide the GRPC Access host (host:port)."` ForkHeight uint64 `default:"0" flag:"fork-height" info:"Optional block height to pin the fork (if supported)."` } @@ -100,7 +100,9 @@ flow test test1.cdc test2.cdc`, } func init() { - // add default value to --fork flag + // Add default value to --fork flag + // workaround because config schema via struct tags doesn't support default values + TestCommand.Cmd.Flags().StringVar(&testFlags.Fork, "fork", "mainnet", "Fork tests from a remote network. If provided without a value, defaults to mainnet") if f := TestCommand.Cmd.Flags().Lookup("fork"); f != nil { f.NoOptDefVal = "mainnet" }