github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/trace/trace_test.go (about)

     1  package trace
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"testing"
     7  )
     8  
     9  // Stub is a helper function that stubs all functional fields of x with given f.
    10  func Stub(x interface{}, f func(name string, args ...interface{})) {
    11  	(FieldStubber{
    12  		OnCall: f,
    13  	}).Stub(reflect.ValueOf(x))
    14  }
    15  
    16  func ClearContext(x interface{}) interface{} {
    17  	p := reflect.ValueOf(x).Index(0)
    18  	t := p.Elem().Type()
    19  	f, has := t.FieldByName("Context")
    20  	if has && f.Type.Kind() == reflect.Interface {
    21  		x := reflect.New(t)
    22  		x.Elem().Set(p.Elem())
    23  		c := x.Elem().FieldByName(f.Name)
    24  		c.Set(reflect.Zero(c.Type()))
    25  		p.Set(x)
    26  	}
    27  
    28  	return p.Interface()
    29  }
    30  
    31  // FieldStubber contains options of filling all struct functional fields.
    32  // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals
    33  type FieldStubber struct {
    34  	// OnStub is an optional callback that is called when field getting
    35  	// stubbed.
    36  	// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals
    37  	OnStub func(name string)
    38  
    39  	// OnCall is an optional callback that will be called for each stubbed
    40  	// field getting called.
    41  	// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals
    42  	OnCall func(name string, args ...interface{})
    43  }
    44  
    45  // Stub fills in given x struct.
    46  // Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals
    47  func (f FieldStubber) Stub(x reflect.Value) {
    48  	var (
    49  		v = x.Elem()
    50  		t = v.Type()
    51  	)
    52  	for i := 0; i < t.NumField(); i++ {
    53  		var (
    54  			fx = v.Field(i)
    55  			ft = fx.Type()
    56  		)
    57  		if ft.Kind() != reflect.Func {
    58  			continue
    59  		}
    60  		name := t.Field(i).Name
    61  		if f.OnStub != nil {
    62  			f.OnStub(name)
    63  		}
    64  		out := []reflect.Value{}
    65  		for i := 0; i < ft.NumOut(); i++ {
    66  			ti := reflect.New(ft.Out(i)).Elem()
    67  			out = append(out, ti)
    68  		}
    69  		fn := reflect.MakeFunc(ft, func(args []reflect.Value) []reflect.Value {
    70  			if f.OnCall == nil {
    71  				return out
    72  			}
    73  			params := make([]interface{}, len(args))
    74  			for i, arg := range args {
    75  				params[i] = arg.Interface()
    76  			}
    77  			f.OnCall(name, params...)
    78  
    79  			return out
    80  		})
    81  		fx.Set(fn)
    82  	}
    83  }
    84  
    85  func TestTable(t *testing.T) {
    86  	testSingleTrace(t, &Table{}, "Table")
    87  }
    88  
    89  func TestDriver(t *testing.T) {
    90  	testSingleTrace(t, &Driver{}, "Driver")
    91  }
    92  
    93  func TestRetry(t *testing.T) {
    94  	testSingleTrace(t, &Retry{}, "Retry")
    95  }
    96  
    97  func TestCoordination(t *testing.T) {
    98  	testSingleTrace(t, &Table{}, "Coordination")
    99  }
   100  
   101  func TestRatelimiter(t *testing.T) {
   102  	testSingleTrace(t, &Ratelimiter{}, "Ratelimiter")
   103  }
   104  
   105  func TestTopic(t *testing.T) {
   106  	testSingleTrace(t, &Topic{}, "Topic")
   107  }
   108  
   109  func TestDiscovery(t *testing.T) {
   110  	testSingleTrace(t, &Discovery{}, "Discovery")
   111  }
   112  
   113  func TestDatabaseSQL(t *testing.T) {
   114  	testSingleTrace(t, &DatabaseSQL{}, "DatabaseSQL")
   115  }
   116  
   117  func testSingleTrace(t *testing.T, x interface{}, traceName string) {
   118  	t.Helper()
   119  	v := reflect.ValueOf(x)
   120  	m := v.MethodByName("Compose")
   121  	m.Call(
   122  		[]reflect.Value{reflect.New(reflect.ValueOf(x).Elem().Type())},
   123  	)
   124  	a := reflect.New(reflect.TypeOf(x).Elem())
   125  	defer assertCalled(t, traceName, stubEachFunc(a))
   126  	callEachFunc(a.Elem())
   127  }
   128  
   129  func assertCalled(t *testing.T, prefix string, called map[string]bool) {
   130  	t.Helper()
   131  	for name, called := range called {
   132  		if !called {
   133  			t.Error(prefix, fmt.Sprintf("%s field is not called", name))
   134  		}
   135  	}
   136  }
   137  
   138  func stubEachFunc(x reflect.Value) map[string]bool {
   139  	fs := make(map[string]bool)
   140  	(FieldStubber{
   141  		OnStub: func(name string) {
   142  			fs[name] = false
   143  		},
   144  		OnCall: func(name string, _ ...interface{}) {
   145  			fs[name] = true
   146  		},
   147  	}).Stub(x)
   148  
   149  	return fs
   150  }
   151  
   152  func callFunc(f reflect.Value, ft reflect.Type) {
   153  	if ft.Kind() != reflect.Func {
   154  		return
   155  	}
   156  	if f.IsNil() {
   157  		return
   158  	}
   159  	args := make([]reflect.Value, ft.NumIn())
   160  	for j := range args {
   161  		args[j] = reflect.New(ft.In(j)).Elem()
   162  	}
   163  	for _, xx := range f.Call(args) {
   164  		switch xx.Type().Kind() {
   165  		case reflect.Struct:
   166  			callEachFunc(xx)
   167  		case reflect.Func:
   168  			callFunc(xx, xx.Type())
   169  		default:
   170  			_ = xx
   171  		}
   172  	}
   173  }
   174  
   175  func callEachFunc(x reflect.Value) {
   176  	t := x.Type()
   177  	for i := 0; i < t.NumField(); i++ {
   178  		var (
   179  			f  = x.Field(i)
   180  			ft = f.Type()
   181  		)
   182  		callFunc(f, ft)
   183  	}
   184  }