github.com/onflow/flow-go@v0.33.17/utils/test_matrix/test_matrix.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "strings" 8 9 "golang.org/x/tools/go/packages" 10 ) 11 12 const flowPackagePrefix = "github.com/onflow/flow-go/" 13 const ciMatrixName = "dynamicMatrix" 14 const ciDefaultRunner = "ubuntu-latest" 15 16 // testMatrix represents a single GitHub Actions test matrix combination that consists of a name and a list of flow-go packages associated with that name. 17 type testMatrix struct { 18 Name string `json:"name"` 19 Packages string `json:"packages"` 20 Runner string `json:"runner"` 21 } 22 23 type targets struct { 24 runners map[string]string 25 packages map[string][]string 26 } 27 28 // Generates a list of packages to test that will be passed to GitHub Actions 29 func main() { 30 if len(os.Args) == 1 { 31 fmt.Fprintln(os.Stderr, "must have at least 1 package listed") 32 return 33 } 34 35 allFlowPackages := listAllFlowPackages() 36 37 targetPackages, seenPackages := listTargetPackages(os.Args[1:], allFlowPackages) 38 39 otherPackages := listOtherPackages(allFlowPackages, seenPackages) 40 41 testMatrix := generateTestMatrix(targetPackages, otherPackages) 42 43 // generate JSON output that will be read in by CI matrix 44 // can't use json.MarshalIndent because fromJSON() in CI can’t read JSON with any spaces 45 testMatrixBytes, err := json.Marshal(testMatrix) 46 if err != nil { 47 panic(err) 48 } 49 50 // this string will be read by CI to generate groups of tests to run in separate CI jobs 51 testMatrixStr := "::set-output name=" + ciMatrixName + "::" + string(testMatrixBytes) 52 53 // very important to add newline character at the end of the compacted JSON - otherwise fromJSON() in CI will throw unmarshalling error 54 fmt.Println(testMatrixStr) 55 } 56 57 func generateTestMatrix(targetPackages targets, otherPackages []string) []testMatrix { 58 var testMatrices []testMatrix 59 60 for packageName := range targetPackages.packages { 61 targetTestMatrix := testMatrix{ 62 Name: packageName, 63 Packages: strings.Join(targetPackages.packages[packageName], " "), 64 Runner: targetPackages.runners[packageName], 65 } 66 testMatrices = append(testMatrices, targetTestMatrix) 67 } 68 69 // add the other packages after all target packages added 70 otherTestMatrix := testMatrix{ 71 Name: "others", 72 Packages: strings.Join(otherPackages, " "), 73 Runner: ciDefaultRunner, 74 } 75 76 testMatrices = append(testMatrices, otherTestMatrix) 77 78 return testMatrices 79 } 80 81 // listTargetPackages returns a map-list of target packages to run as separate CI jobs, based on a list of target package prefixes. 82 // It also returns a list of the "seen" packages that can then be used to extract the remaining packages to run (in a separate CI job). 83 func listTargetPackages(targetPackagePrefixes []string, allFlowPackages []string) (targets, map[string]string) { 84 targetPackages := make(map[string][]string) 85 targetRunners := make(map[string]string) 86 87 // Stores list of packages already seen / allocated to other lists. Needed for the last package which will 88 // have all the leftover packages that weren't allocated to a separate list (CI job). 89 // It's a map, not a list, to make it easier to check if a package was seen or not. 90 seenPackages := make(map[string]string) 91 92 // iterate over the target packages to run as separate CI jobs 93 for _, targetPackagePrefix := range targetPackagePrefixes { 94 var targetPackage []string 95 96 // assume package name specified without runner 97 targetPackagePrefixNoRunner := targetPackagePrefix 98 99 // check if specify CI runner to use for the package, otherwise use default 100 colonIndex := strings.Index(targetPackagePrefix, ":") 101 if colonIndex != -1 { 102 targetPackagePrefixNoRunner = targetPackagePrefix[:colonIndex] // strip out runner from package name 103 targetRunners[targetPackagePrefixNoRunner] = targetPackagePrefix[colonIndex+1:] 104 } else { 105 // use default CI runner if didn't specify runner 106 targetRunners[targetPackagePrefix] = ciDefaultRunner 107 } 108 109 // go through all packages to see which ones to pull out 110 for _, allPackage := range allFlowPackages { 111 if strings.HasPrefix(allPackage, flowPackagePrefix+targetPackagePrefixNoRunner) { 112 // if the package was already seen, don't append it to the list 113 // this is to support listing sub-sub packages in a CI job without duplicating those sub-sub packages 114 // in the parent package job 115 _, seen := seenPackages[allPackage] 116 if seen { 117 continue 118 } 119 targetPackage = append(targetPackage, allPackage) 120 seenPackages[allPackage] = allPackage 121 } 122 } 123 if len(targetPackage) == 0 { 124 panic("no packages exist with prefix " + targetPackagePrefixNoRunner) 125 } 126 targetPackages[targetPackagePrefixNoRunner] = targetPackage 127 } 128 return targets{targetRunners, targetPackages}, seenPackages 129 } 130 131 // listOtherPackages compiles the remaining packages that don't match any of the target packages. 132 func listOtherPackages(allFlowPackages []string, seenPackages map[string]string) []string { 133 var otherPackages []string 134 135 for _, allFlowPackage := range allFlowPackages { 136 _, seen := seenPackages[allFlowPackage] 137 if !seen { 138 otherPackages = append(otherPackages, allFlowPackage) 139 } 140 } 141 142 if len(otherPackages) == 0 { 143 panic("other packages list can't be 0") 144 } 145 return otherPackages 146 } 147 148 func listAllFlowPackages() []string { 149 flowPackages, err := packages.Load(&packages.Config{}, "./...") 150 151 if err != nil { 152 panic(err) 153 } 154 var flowPackagesStr []string 155 for _, p := range flowPackages { 156 flowPackagesStr = append(flowPackagesStr, p.PkgPath) 157 } 158 return flowPackagesStr 159 }