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  }