Skip to content

Commit 7ea96db

Browse files
committed
fix: test flakyness for solana program loading
1 parent 628cfb7 commit 7ea96db

2 files changed

Lines changed: 42 additions & 82 deletions

File tree

legacy/pkg/family/solana/testutils/artifacts.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,8 @@ package soltestutils
33
import (
44
"os"
55
"path/filepath"
6-
"sync"
7-
"testing"
8-
9-
"github.com/stretchr/testify/require"
10-
11-
"github.com/smartcontractkit/cld-changesets/legacy/pkg/family/solana/solutils"
126
)
137

14-
var (
15-
onceCCIP = &sync.Once{}
16-
)
17-
18-
type downloadFunc func(t *testing.T) string
19-
20-
// downloadChainlinkCCIPProgramArtifacts downloads CCIP Solana artifacts (includes MCMS programs).
21-
func downloadChainlinkCCIPProgramArtifacts(t *testing.T) string {
22-
t.Helper()
23-
24-
cachePath := programsCacheDir()
25-
26-
onceCCIP.Do(func() {
27-
err := solutils.DownloadChainlinkCCIPProgramArtifacts(t.Context(), cachePath, "", nil)
28-
require.NoError(t, err)
29-
})
30-
31-
return cachePath
32-
}
33-
348
// programsCacheDir returns where to store downloaded .so files. Leaf dir is solana_programs
359
// (under UserCacheDir/TempDir, so "cache" is implied; avoids read-only pkg/mod paths).
3610
func programsCacheDir() string {
Lines changed: 42 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package soltestutils
22

33
import (
4-
"io"
5-
"os"
64
"path/filepath"
5+
"sync"
76
"testing"
87

98
"github.com/stretchr/testify/require"
@@ -13,71 +12,58 @@ import (
1312
"github.com/smartcontractkit/cld-changesets/legacy/pkg/family/solana/solutils"
1413
)
1514

16-
// LoadMCMSPrograms loads the MCMS program artifacts into the given directory.
17-
//
18-
// Returns the path to the temporary test directory and a map of program names to IDs.
19-
func LoadMCMSPrograms(t *testing.T, dir string) (string, map[string]string) {
15+
// programIDMu serializes Solana integration tests that mutate global gobinding
16+
// program IDs via SetProgramID. solana-go bindings use process-wide state, so
17+
// parallel package tests otherwise race and fail with "Program is not deployed".
18+
var programIDMu sync.Mutex
19+
20+
var (
21+
mcmsProgramsOnce sync.Once
22+
mcmsProgramsPath string
23+
mcmsProgramIDs map[string]string
24+
)
25+
26+
// sharedMCMSPrograms downloads MCMS Solana program artifacts once per test process
27+
// and returns the shared cache directory plus program IDs.
28+
func sharedMCMSPrograms(t *testing.T) (string, map[string]string) {
2029
t.Helper()
2130

22-
progIDs := loadProgramArtifacts(t,
23-
solutils.MCMSProgramNames, downloadChainlinkCCIPProgramArtifacts, dir,
24-
)
31+
mcmsProgramsOnce.Do(func() {
32+
mcmsProgramsPath = programsCacheDir()
33+
err := solutils.DownloadChainlinkCCIPProgramArtifacts(t.Context(), mcmsProgramsPath, "", nil)
34+
require.NoError(t, err)
35+
36+
mcmsProgramIDs = make(map[string]string, len(solutils.MCMSProgramNames))
37+
for _, name := range solutils.MCMSProgramNames {
38+
id := solutils.GetProgramID(name)
39+
require.NotEmpty(t, id, "program id not found for program name: %s", name)
40+
require.FileExists(t, filepath.Join(mcmsProgramsPath, name+".so"))
41+
mcmsProgramIDs[name] = id
42+
}
43+
})
44+
45+
return mcmsProgramsPath, copyProgramIDs(mcmsProgramIDs)
46+
}
47+
48+
func copyProgramIDs(src map[string]string) map[string]string {
49+
dst := make(map[string]string, len(src))
50+
for name, id := range src {
51+
dst[name] = id
52+
}
2553

26-
return dir, progIDs
54+
return dst
2755
}
2856

2957
// PreloadMCMS provides a convenience function to preload the MCMS program artifacts and address
3058
// book for a given selector.
3159
func PreloadMCMS(t *testing.T, selector uint64) (string, map[string]string, *cldf.AddressBookMap) {
3260
t.Helper()
3361

34-
dir := t.TempDir()
35-
36-
_, programIDs := LoadMCMSPrograms(t, dir)
62+
programIDMu.Lock()
63+
t.Cleanup(programIDMu.Unlock)
3764

65+
programsPath, programIDs := sharedMCMSPrograms(t)
3866
ab := PreloadAddressBookWithMCMSPrograms(t, selector)
3967

40-
return dir, programIDs, ab
41-
}
42-
43-
// loadProgramArtifacts is a helper function that loads program artifacts into a temporary test directory.
44-
// It downloads artifacts using the provided download function and copies the specified programs.
45-
//
46-
// Returns the map of program names to IDs.
47-
func loadProgramArtifacts(t *testing.T, programNames []string, downloadFn downloadFunc, targetDir string) map[string]string {
48-
t.Helper()
49-
50-
// Download the program artifacts using the provided download function
51-
cachePath := downloadFn(t)
52-
53-
progIDs := make(map[string]string, len(programNames))
54-
55-
// Copy the specific artifacts to the target directory and add the program ID to the map
56-
for _, name := range programNames {
57-
id := solutils.GetProgramID(name)
58-
require.NotEmpty(t, id, "program id not found for program name: %s", name)
59-
60-
src := filepath.Join(cachePath, name+".so")
61-
dst := filepath.Join(targetDir, name+".so")
62-
63-
func() {
64-
srcFile, err := os.Open(src)
65-
require.NoError(t, err)
66-
defer srcFile.Close()
67-
68-
dstFile, err := os.Create(dst)
69-
require.NoError(t, err)
70-
defer dstFile.Close()
71-
72-
_, err = io.Copy(dstFile, srcFile)
73-
require.NoError(t, err)
74-
}()
75-
76-
// Add the program ID to the map
77-
progIDs[name] = id
78-
t.Logf("copied solana program %s to %s", name, dst)
79-
}
80-
81-
// Return the path to the cached artifacts and the map of program IDs
82-
return progIDs
68+
return programsPath, programIDs, ab
8369
}

0 commit comments

Comments
 (0)