github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/gauntlet/gauntlet.go (about) 1 // Package gauntlet enables the framework to interface with the chainlink gauntlet project 2 package gauntlet 3 4 import ( 5 "bufio" 6 "errors" 7 "fmt" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "github.com/avast/retry-go" 15 "github.com/google/uuid" 16 "github.com/rs/zerolog/log" 17 ) 18 19 var ( 20 execDir string 21 ) 22 23 // Gauntlet contains helpful data to run gauntlet commands 24 type Gauntlet struct { 25 exec string 26 Command string 27 Network string 28 NetworkConfig map[string]string 29 } 30 31 // NewGauntlet Sets up a gauntlet struct and checks if the yarn executable exists. 32 func NewGauntlet() (*Gauntlet, error) { 33 yarn, err := exec.LookPath("yarn") 34 if err != nil { 35 return &Gauntlet{}, errors.New("'yarn' is not installed") 36 } 37 log.Debug().Str("PATH", yarn).Msg("Executable Path") 38 os.Setenv("SKIP_PROMPTS", "true") 39 g := &Gauntlet{ 40 exec: yarn, 41 Command: "gauntlet", // Setting gauntlet as the default command 42 NetworkConfig: make(map[string]string), 43 } 44 g.GenerateRandomNetwork() 45 return g, nil 46 } 47 48 // Flag returns a string formatted in the expected gauntlet's flag form 49 func (g *Gauntlet) Flag(flag, value string) string { 50 return fmt.Sprintf("--%s=%s", flag, value) 51 } 52 53 func (g *Gauntlet) SetWorkingDir(wrkDir string) { 54 execDir = wrkDir 55 } 56 57 // GenerateRandomNetwork Creates and sets a random network prepended with test 58 func (g *Gauntlet) GenerateRandomNetwork() { 59 r := uuid.NewString()[0:8] 60 t := time.Now().UnixMilli() 61 g.Network = fmt.Sprintf("test%v%s", t, r) 62 log.Debug().Str("Network", g.Network).Msg("Generated Network Name") 63 } 64 65 type ExecCommandOptions struct { 66 ErrHandling []string 67 CheckErrorsInRead bool 68 RetryCount int 69 RetryDelay time.Duration 70 } 71 72 // ExecCommand Executes a gauntlet or yarn command with the provided arguments. 73 // 74 // It will also check for any errors you specify in the output via the errHandling slice. 75 func (g *Gauntlet) ExecCommand(args []string, options ExecCommandOptions) (string, error) { 76 output := "" 77 var updatedArgs []string 78 if g.Command == "gauntlet" { 79 updatedArgs = append([]string{g.Command}, args...) 80 // Appending network to the gauntlet command 81 updatedArgs = insertArg(updatedArgs, 2, g.Flag("network", g.Network)) 82 } else { 83 updatedArgs = args 84 } 85 86 printArgs(updatedArgs) 87 88 cmd := exec.Command(g.exec, updatedArgs...) // #nosec G204 89 if execDir != "" { 90 cmd.Dir = execDir 91 } 92 stdout, _ := cmd.StdoutPipe() 93 stderr, _ := cmd.StderrPipe() 94 if err := cmd.Start(); err != nil { 95 return output, err 96 } 97 98 reader := bufio.NewReader(stdout) 99 line, err := reader.ReadString('\n') 100 for err == nil { 101 log.Info().Str("stdout", line).Msg(g.Command) 102 output = fmt.Sprintf("%s%s", output, line) 103 if options.CheckErrorsInRead { 104 rerr := checkForErrors(options.ErrHandling, output) 105 if rerr != nil { 106 return output, rerr 107 } 108 } 109 line, err = reader.ReadString('\n') 110 } 111 112 reader = bufio.NewReader(stderr) 113 line, err = reader.ReadString('\n') 114 for err == nil { 115 log.Info().Str("stderr", line).Msg(g.Command) 116 output = fmt.Sprintf("%s%s", output, line) 117 if options.CheckErrorsInRead { 118 rerr := checkForErrors(options.ErrHandling, output) 119 if rerr != nil { 120 return output, rerr 121 } 122 } 123 line, err = reader.ReadString('\n') 124 } 125 126 rerr := checkForErrors(options.ErrHandling, output) 127 if rerr != nil { 128 return output, rerr 129 } 130 131 if strings.Compare("EOF", err.Error()) > 0 { 132 return output, err 133 } 134 135 // catch any exit codes 136 err = cmd.Wait() 137 138 log.Debug().Str("Command", g.Command).Msg("command Completed") 139 return output, err 140 } 141 142 // ExecCommandWithRetries Some commands are safe to retry and in ci this can be even more so needed. 143 func (g *Gauntlet) ExecCommandWithRetries(args []string, options ExecCommandOptions) (string, error) { 144 var output string 145 var err error 146 if options.RetryDelay == 0 { 147 // default to 5 seconds 148 options.RetryDelay = time.Second * 5 149 } 150 err = retry.Do( 151 func() error { 152 output, err = g.ExecCommand(args, options) 153 return err 154 }, 155 retry.Delay(options.RetryDelay), 156 retry.MaxDelay(options.RetryDelay), 157 retry.Attempts(uint(options.RetryCount)), 158 ) 159 160 return output, err 161 } 162 163 // WriteNetworkConfigMap write a network config file for gauntlet testing. 164 func (g *Gauntlet) WriteNetworkConfigMap(networkDirPath string) error { 165 file := filepath.Join(networkDirPath, fmt.Sprintf(".env.%s", g.Network)) 166 f, err := os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 167 if err != nil { 168 return err 169 } 170 defer f.Close() 171 for k, v := range g.NetworkConfig { 172 log.Debug().Str(k, v).Msg("Gauntlet .env config value:") 173 _, err = f.WriteString(fmt.Sprintf("\n%s=%s", k, v)) 174 if err != nil { 175 return err 176 } 177 } 178 return nil 179 } 180 181 // checkForErrors Loops through provided err slice to see if the error exists in the output. 182 func checkForErrors(errHandling []string, line string) error { 183 for _, e := range errHandling { 184 if strings.Contains(line, e) { 185 log.Debug().Str("Error", line).Msg("Gauntlet Error Found") 186 return fmt.Errorf("found a gauntlet error") 187 } 188 } 189 return nil 190 } 191 192 // insertArg inserts an argument into the args slice 193 func insertArg(args []string, index int, valueToInsert string) []string { 194 if len(args) <= index { // nil or empty slice or after last element 195 return append(args, valueToInsert) 196 } 197 args = append(args[:index+1], args[index:]...) // index < len(a) 198 args[index] = valueToInsert 199 return args 200 } 201 202 // printArgs prints all the gauntlet args being used in a call to gauntlet 203 func printArgs(args []string) { 204 out := "yarn" 205 for _, arg := range args { 206 out = fmt.Sprintf("%s %s", out, arg) 207 208 } 209 log.Info().Str("Command", out).Msg("Gauntlet") 210 } 211 212 func (g *Gauntlet) AddNetworkConfigVar(k string, v string) { 213 g.NetworkConfig[k] = v 214 }