diff --git a/.github/workflows/solana.yml b/.github/workflows/solana.yml index 9d49f47c92..4087b208ea 100644 --- a/.github/workflows/solana.yml +++ b/.github/workflows/solana.yml @@ -134,10 +134,22 @@ jobs: runs-on: ubuntu-latest-8cores-32GB steps: - uses: actions/checkout@v4 - - name: Collect Test Telemetry - uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0 - - name: Free Disk Space - uses: smartcontractkit/.github/actions/free-disk-space@free-disk-space/v1 + + - name: debug info + run: | + echo '=== cgroup memory limits ===' + cat /sys/fs/cgroup/memory.max 2>/dev/null || echo "no memory.max" + cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null || echo "no memory.limit_in_bytes" + + echo '=== /proc/meminfo ===' + cat /proc/meminfo + + echo '=== /tmp mount ===' + mount | grep ' /tmp' || echo "/tmp has no separate mount" + df -h /tmp || echo "df /tmp failed" + echo '=== /tmp listing ===' + ls -la /tmp || echo "ls /tmp failed" + - name: Cache cargo target dir uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: @@ -166,6 +178,23 @@ jobs: run: | sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" # always use latest stable release from solana echo "PATH=$HOME/.local/share/solana/install/active_release/bin:$PATH" >> $GITHUB_ENV + + - name: debug info + run: | + echo '=== cgroup memory limits ===' + cat /sys/fs/cgroup/memory.max 2>/dev/null || echo "no memory.max" + cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null || echo "no memory.limit_in_bytes" + + echo '=== /proc/meminfo ===' + cat /proc/meminfo + + echo '=== /tmp mount ===' + mount | grep ' /tmp' || echo "/tmp has no separate mount" + df -h /tmp || echo "df /tmp failed" + + echo '=== /tmp listing ===' + ls -la /tmp || echo "ls /tmp failed" + - name: build + test run: | set -eoux pipefail @@ -176,8 +205,11 @@ jobs: FORCE_COLOR=1 &&\ cd /solana/contracts &&\ anchor build" + make go-tests + + lint: needs: [ changes, get_anchor_version, build_solana ] if: ${{ needs.changes.outputs.solana_changes == 'true' }} diff --git a/chains/solana/contracts/tests/ccip/ccip_router_test.go b/chains/solana/contracts/tests/ccip/ccip_router_test.go index 9e90e26ba5..30d5b010d5 100644 --- a/chains/solana/contracts/tests/ccip/ccip_router_test.go +++ b/chains/solana/contracts/tests/ccip/ccip_router_test.go @@ -40,8 +40,6 @@ import ( ) func TestCCIPRouter(t *testing.T) { - t.Parallel() - ccip_router.SetProgramID(config.CcipRouterProgram) test_ccip_receiver.SetProgramID(config.CcipLogicReceiver) test_token_pool.SetProgramID(config.CcipTokenPoolProgram) diff --git a/chains/solana/contracts/tests/ccip/tokenpool_test.go b/chains/solana/contracts/tests/ccip/tokenpool_test.go index c0f2b15c11..87fe26e660 100644 --- a/chains/solana/contracts/tests/ccip/tokenpool_test.go +++ b/chains/solana/contracts/tests/ccip/tokenpool_test.go @@ -40,8 +40,6 @@ import ( ) func TestTokenPool(t *testing.T) { - t.Parallel() - rmn_remote.SetProgramID(config.RMNRemoteProgram) test_token_pool.SetProgramID(config.CcipTokenPoolProgram) cctp_message_transmitter.SetProgramID(config.CctpMessageTransmitter) diff --git a/chains/solana/contracts/tests/examples/ping_pong_test.go b/chains/solana/contracts/tests/examples/ping_pong_test.go index 89c799e284..73359656b4 100644 --- a/chains/solana/contracts/tests/examples/ping_pong_test.go +++ b/chains/solana/contracts/tests/examples/ping_pong_test.go @@ -85,6 +85,9 @@ func getReusableAccounts(t *testing.T, linkMint solana.PublicKey) ReusableAccoun // from the ping pong program to itself. func TestPingPong(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() // acting as "dumb" offramp, proxying calls to the receiver that are signed by PDA test_ccip_invalid_receiver.SetProgramID(config.CcipInvalidReceiverProgram) diff --git a/chains/solana/contracts/tests/examples/pools_test.go b/chains/solana/contracts/tests/examples/pools_test.go index 6546c264c4..79cdd9dadb 100644 --- a/chains/solana/contracts/tests/examples/pools_test.go +++ b/chains/solana/contracts/tests/examples/pools_test.go @@ -54,7 +54,10 @@ type ProgramData struct { // more detailed token pool tests are handled by the test-token-pool which is used in the tokenpool_test.go and ccip_router_test.go func TestBaseTokenPoolHappyPath(t *testing.T) { t.Parallel() - + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + // acting as "dumb" onramp & offramp, proxying calls to the pool that are signed by PDA test_ccip_invalid_receiver.SetProgramID(config.CcipInvalidReceiverProgram) rmn_remote.SetProgramID(config.RMNRemoteProgram) diff --git a/chains/solana/contracts/tests/examples/receiver_test.go b/chains/solana/contracts/tests/examples/receiver_test.go index 527b46b224..f7a7d08da8 100644 --- a/chains/solana/contracts/tests/examples/receiver_test.go +++ b/chains/solana/contracts/tests/examples/receiver_test.go @@ -22,6 +22,10 @@ import ( func TestCcipReceiver(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + ctx := tests.Context(t) ccip_router.SetProgramID(config.CcipRouterProgram) diff --git a/chains/solana/contracts/tests/mcms/mcm_multiple_instances_test.go b/chains/solana/contracts/tests/mcms/mcm_multiple_instances_test.go index 5705b2ecf7..a1594bff55 100644 --- a/chains/solana/contracts/tests/mcms/mcm_multiple_instances_test.go +++ b/chains/solana/contracts/tests/mcms/mcm_multiple_instances_test.go @@ -21,6 +21,10 @@ import ( func TestMcmMultipleInstances(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + mcm.SetProgramID(config.McmProgram) ctx := tests.Context(t) diff --git a/chains/solana/contracts/tests/mcms/mcm_set_config_test.go b/chains/solana/contracts/tests/mcms/mcm_set_config_test.go index fc52d3a98e..dd465edce0 100644 --- a/chains/solana/contracts/tests/mcms/mcm_set_config_test.go +++ b/chains/solana/contracts/tests/mcms/mcm_set_config_test.go @@ -23,6 +23,10 @@ import ( func TestMcmSetConfig(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + mcm.SetProgramID(config.McmProgram) ctx := tests.Context(t) diff --git a/chains/solana/contracts/tests/mcms/mcm_set_root_execute_test.go b/chains/solana/contracts/tests/mcms/mcm_set_root_execute_test.go index 7d9c16e135..c394f6529e 100644 --- a/chains/solana/contracts/tests/mcms/mcm_set_root_execute_test.go +++ b/chains/solana/contracts/tests/mcms/mcm_set_root_execute_test.go @@ -38,6 +38,10 @@ type TestMcmOperation struct { func TestMcmSetRootAndExecute(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + mcm.SetProgramID(config.McmProgram) external_program_cpi_stub.SetProgramID(config.ExternalCpiStubProgram) // testing program diff --git a/chains/solana/contracts/tests/mcms/mcm_timelock_test.go b/chains/solana/contracts/tests/mcms/mcm_timelock_test.go index 700ce54bc0..e705c89ba4 100644 --- a/chains/solana/contracts/tests/mcms/mcm_timelock_test.go +++ b/chains/solana/contracts/tests/mcms/mcm_timelock_test.go @@ -30,6 +30,10 @@ import ( func TestMcmWithTimelock(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + ctx := tests.Context(t) mcm.SetProgramID(config.McmProgram) diff --git a/chains/solana/contracts/tests/mcms/mcms_tx_capacity_test.go b/chains/solana/contracts/tests/mcms/mcms_tx_capacity_test.go index 1ccfffb91b..f6a6819fcc 100644 --- a/chains/solana/contracts/tests/mcms/mcms_tx_capacity_test.go +++ b/chains/solana/contracts/tests/mcms/mcms_tx_capacity_test.go @@ -30,6 +30,10 @@ import ( func TestMcmsCapacity(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + ctx := tests.Context(t) mcm.SetProgramID(config.McmProgram) diff --git a/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go b/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go index d0ba7ba95d..f4c781e19e 100644 --- a/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go +++ b/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go @@ -24,6 +24,10 @@ import ( func TestTimelockBypasserExecute(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + ctx := tests.Context(t) timelock.SetProgramID(config.TimelockProgram) diff --git a/chains/solana/contracts/tests/mcms/timelock_multiple_instances_test.go b/chains/solana/contracts/tests/mcms/timelock_multiple_instances_test.go index d4391e701f..9e89b5e6ea 100644 --- a/chains/solana/contracts/tests/mcms/timelock_multiple_instances_test.go +++ b/chains/solana/contracts/tests/mcms/timelock_multiple_instances_test.go @@ -32,6 +32,10 @@ type TimelockInstance struct { func TestTimelockMultipleInstances(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + ctx := tests.Context(t) timelock.SetProgramID(config.TimelockProgram) diff --git a/chains/solana/contracts/tests/mcms/timelock_rbac_test.go b/chains/solana/contracts/tests/mcms/timelock_rbac_test.go index ed507b543d..8e8178dcf1 100644 --- a/chains/solana/contracts/tests/mcms/timelock_rbac_test.go +++ b/chains/solana/contracts/tests/mcms/timelock_rbac_test.go @@ -23,6 +23,10 @@ import ( func TestTimelockRBAC(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + ctx := tests.Context(t) timelock.SetProgramID(config.TimelockProgram) diff --git a/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go b/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go index f31c792353..987b0b2a25 100644 --- a/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go +++ b/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go @@ -27,6 +27,10 @@ import ( func TestTimelockScheduleAndExecute(t *testing.T) { t.Parallel() + g := testutils.GetConcurrencyGroup("solana-test-validator", 2) // max 2 concurrent tests + g.Enter() + defer g.Leave() + ctx := tests.Context(t) timelock.SetProgramID(config.TimelockProgram) diff --git a/chains/solana/contracts/tests/testutils/concurrency_group.go b/chains/solana/contracts/tests/testutils/concurrency_group.go new file mode 100644 index 0000000000..5bc4f0f53d --- /dev/null +++ b/chains/solana/contracts/tests/testutils/concurrency_group.go @@ -0,0 +1,44 @@ +package testutils + +import "sync" + +// ConcurrencyGroup is a counting semaphore. +type ConcurrencyGroup chan struct{} + +// Registry manages named concurrency groups. +type Registry struct { + sync.Mutex + m map[string]ConcurrencyGroup +} + +// NewRegistry creates a new, empty registry. +func newRegistry() *Registry { + return &Registry{m: make(map[string]ConcurrencyGroup)} +} + +// Global registry used by the helper Get. +var defaultRegistry = newRegistry() + +// Get returns the semaphore for id, creating it with the given +// concurrency level if it does not yet exist. +func (r *Registry) get(id string, concurrency int) ConcurrencyGroup { + r.Lock() + defer r.Unlock() + if s, ok := r.m[id]; ok { + return s + } + s := make(ConcurrencyGroup, concurrency) + r.m[id] = s + return s +} + +// GetConcurrencyGroup uses the global registry. +func GetConcurrencyGroup(id string, concurrency int) ConcurrencyGroup { + return defaultRegistry.get(id, concurrency) +} + +// Enter acquires a token (blocks until one is available). +func (s ConcurrencyGroup) Enter() { s <- struct{}{} } + +// Leave releases a token. +func (s ConcurrencyGroup) Leave() { <-s }