github.com/dfklegend/cell2/utils@v0.0.0-20240402033734-a0a9f3d9335d/helpers/helpers.go (about) 1 package helpers 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "os/exec" 10 "path/filepath" 11 "reflect" 12 "strings" 13 "testing" 14 "time" 15 ) 16 17 // 18 19 // GetFreePort returns a free port 20 func GetFreePort(t testing.TB) int { 21 t.Helper() 22 addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 23 if err != nil { 24 t.Fatal(err) 25 } 26 27 l, err := net.ListenTCP("tcp", addr) 28 if err != nil { 29 t.Fatal(err) 30 } 31 defer l.Close() 32 return l.Addr().(*net.TCPAddr).Port 33 } 34 35 // GetMapKeys returns a string slice with the map keys 36 func GetMapKeys(t *testing.T, m interface{}) []string { 37 if reflect.ValueOf(m).Kind() != reflect.Map { 38 t.Fatal(errors.New("GetMapKeys should receive a map")) 39 } 40 if reflect.TypeOf(m).Key() != reflect.TypeOf("bla") { 41 t.Fatal(errors.New("GetMapKeys should receive a map with string keys")) 42 } 43 t.Helper() 44 res := make([]string, 0) 45 for _, k := range reflect.ValueOf(m).MapKeys() { 46 res = append(res, k.String()) 47 } 48 return res 49 } 50 51 // WriteFile test helper 52 func WriteFile(t *testing.T, filepath string, bytes []byte) { 53 t.Helper() 54 if err := ioutil.WriteFile(filepath, bytes, 0644); err != nil { 55 t.Fatalf("failed writing file: %s", err) 56 } 57 } 58 59 // ReadFile test helper 60 func ReadFile(t *testing.T, filepath string) []byte { 61 t.Helper() 62 b, err := ioutil.ReadFile(filepath) 63 if err != nil { 64 t.Fatalf("failed reading file: %s", err) 65 } 66 return b 67 } 68 69 // StartProcess starts a process 70 func StartProcess(t testing.TB, program string, args ...string) *exec.Cmd { 71 t.Helper() 72 return exec.Command(program, args...) 73 } 74 75 func waitForServerToBeReady(t testing.TB, out *bufio.Reader) { 76 t.Helper() 77 ShouldEventuallyReturn(t, func() bool { 78 line, _, err := out.ReadLine() 79 if err != nil { 80 t.Fatal(err) 81 } 82 return strings.Contains(string(line), "all modules started!") 83 }, true, 100*time.Millisecond, 30*time.Second) 84 } 85 86 // FixtureGoldenFileName returns the golden file name on fixtures path 87 func FixtureGoldenFileName(t *testing.T, name string) string { 88 t.Helper() 89 return filepath.Join("fixtures", name+".golden") 90 } 91 92 func vetExtras(extras []interface{}) (bool, string) { 93 for i, extra := range extras { 94 if extra != nil { 95 zeroValue := reflect.Zero(reflect.TypeOf(extra)).Interface() 96 if !reflect.DeepEqual(zeroValue, extra) { 97 message := fmt.Sprintf("unexpected non-nil/non-zero extra argument at index %d:\n\t<%T>: %#v", i+1, extra, extra) 98 return false, message 99 } 100 } 101 } 102 return true, "" 103 } 104 105 func pollFuncReturn(f interface{}) (interface{}, error) { 106 values := reflect.ValueOf(f).Call([]reflect.Value{}) 107 108 extras := []interface{}{} 109 for _, value := range values[1:] { 110 extras = append(extras, value.Interface()) 111 } 112 113 success, message := vetExtras(extras) 114 115 if !success { 116 return nil, errors.New(message) 117 } 118 119 return values[0].Interface(), nil 120 } 121 122 // ShouldEventuallyReceive should asserts that eventually channel c receives a value 123 func ShouldEventuallyReceive(t testing.TB, c interface{}, timeouts ...time.Duration) interface{} { 124 t.Helper() 125 if !isChan(c) { 126 t.Fatal("ShouldEventuallyReceive c argument should be a channel") 127 } 128 v := reflect.ValueOf(c) 129 130 timeout := time.After(500 * time.Millisecond) 131 132 if len(timeouts) > 0 { 133 timeout = time.After(timeouts[0]) 134 } 135 136 recvChan := make(chan reflect.Value) 137 138 go func() { 139 v, ok := v.Recv() 140 if ok { 141 recvChan <- v 142 } 143 }() 144 145 select { 146 case <-timeout: 147 t.Fatal(errors.New("timed out waiting for channel to receive")) 148 case a := <-recvChan: 149 return a.Interface() 150 } 151 152 return nil 153 } 154 155 // ShouldAlwaysReturn asserts that the return of f should always be v, timeouts: 0 - evaluation interval, 1 - timeout 156 func ShouldAlwaysReturn(t testing.TB, f interface{}, v interface{}, timeouts ...time.Duration) { 157 t.Helper() 158 interval := 10 * time.Millisecond 159 timeout := time.After(50 * time.Millisecond) 160 switch len(timeouts) { 161 case 1: 162 interval = timeouts[0] 163 break 164 case 2: 165 interval = timeouts[0] 166 timeout = time.After(timeouts[1]) 167 } 168 ticker := time.NewTicker(interval) 169 defer ticker.Stop() 170 171 if isFunction(f) { 172 for { 173 select { 174 case <-timeout: 175 return 176 case <-ticker.C: 177 val, err := pollFuncReturn(f) 178 if err != nil { 179 t.Fatal(err) 180 } 181 if v != val { 182 t.Fatalf("function f returned wrong value %s", val) 183 } 184 } 185 } 186 } else { 187 t.Fatal("ShouldAlwaysReturn should receive a function with no args and more than 0 outs") 188 return 189 } 190 } 191 192 // ShouldEventuallyReturn asserts that eventually the return of f should be v, timeouts: 0 - evaluation interval, 1 - timeout 193 func ShouldEventuallyReturn(t testing.TB, f interface{}, v interface{}, timeouts ...time.Duration) { 194 t.Helper() 195 interval := 10 * time.Millisecond 196 timeout := time.After(500 * time.Millisecond) 197 switch len(timeouts) { 198 case 1: 199 interval = timeouts[0] 200 break 201 case 2: 202 interval = timeouts[0] 203 timeout = time.After(timeouts[1]) 204 } 205 ticker := time.NewTicker(interval) 206 defer ticker.Stop() 207 208 if isFunction(f) { 209 for { 210 select { 211 case <-timeout: 212 t.Fatalf("function f never returned value %s", v) 213 case <-ticker.C: 214 val, err := pollFuncReturn(f) 215 if err != nil { 216 t.Fatal(err) 217 } 218 if v == val { 219 return 220 } 221 } 222 } 223 } else { 224 t.Fatal("ShouldEventuallyEqual should receive a function with no args and more than 0 outs") 225 return 226 } 227 }