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  }