github.com/cilium/ebpf@v0.10.0/btf/core_test.go (about)

     1  package btf
     2  
     3  import (
     4  	"errors"
     5  	"math/rand"
     6  	"os"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/cilium/ebpf/internal/testutils"
    11  	"github.com/google/go-cmp/cmp"
    12  
    13  	qt "github.com/frankban/quicktest"
    14  )
    15  
    16  func TestCOREAreTypesCompatible(t *testing.T) {
    17  	tests := []struct {
    18  		a, b       Type
    19  		compatible bool
    20  	}{
    21  		{&Void{}, &Void{}, true},
    22  		{&Struct{Name: "a"}, &Struct{Name: "b"}, true},
    23  		{&Union{Name: "a"}, &Union{Name: "b"}, true},
    24  		{&Union{Name: "a"}, &Struct{Name: "b"}, false},
    25  		{&Enum{Name: "a"}, &Enum{Name: "b"}, true},
    26  		{&Fwd{Name: "a"}, &Fwd{Name: "b"}, true},
    27  		{&Int{Name: "a", Size: 2}, &Int{Name: "b", Size: 4}, true},
    28  		{&Pointer{Target: &Void{}}, &Pointer{Target: &Void{}}, true},
    29  		{&Pointer{Target: &Void{}}, &Void{}, false},
    30  		{&Array{Index: &Void{}, Type: &Void{}}, &Array{Index: &Void{}, Type: &Void{}}, true},
    31  		{&Array{Index: &Void{}, Type: &Int{}}, &Array{Index: &Void{}, Type: &Void{}}, false},
    32  		{&FuncProto{Return: &Int{}}, &FuncProto{Return: &Void{}}, false},
    33  		{
    34  			&FuncProto{Return: &Void{}, Params: []FuncParam{{Name: "a", Type: &Void{}}}},
    35  			&FuncProto{Return: &Void{}, Params: []FuncParam{{Name: "b", Type: &Void{}}}},
    36  			true,
    37  		},
    38  		{
    39  			&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}},
    40  			&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Int{}}}},
    41  			false,
    42  		},
    43  		{
    44  			&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}, {Type: &Void{}}}},
    45  			&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}},
    46  			false,
    47  		},
    48  	}
    49  
    50  	for _, test := range tests {
    51  		err := coreAreTypesCompatible(test.a, test.b)
    52  		if test.compatible {
    53  			if err != nil {
    54  				t.Errorf("Expected types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
    55  				continue
    56  			}
    57  		} else {
    58  			if !errors.Is(err, errImpossibleRelocation) {
    59  				t.Errorf("Expected types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
    60  				continue
    61  			}
    62  		}
    63  
    64  		err = coreAreTypesCompatible(test.b, test.a)
    65  		if test.compatible {
    66  			if err != nil {
    67  				t.Errorf("Expected reversed types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
    68  			}
    69  		} else {
    70  			if !errors.Is(err, errImpossibleRelocation) {
    71  				t.Errorf("Expected reversed types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
    72  			}
    73  		}
    74  	}
    75  
    76  	for _, invalid := range []Type{&Var{}, &Datasec{}} {
    77  		err := coreAreTypesCompatible(invalid, invalid)
    78  		if errors.Is(err, errImpossibleRelocation) {
    79  			t.Errorf("Expected an error for %T, not errImpossibleRelocation", invalid)
    80  		} else if err == nil {
    81  			t.Errorf("Expected an error for %T", invalid)
    82  		}
    83  	}
    84  }
    85  
    86  func TestCOREAreMembersCompatible(t *testing.T) {
    87  	tests := []struct {
    88  		a, b       Type
    89  		compatible bool
    90  	}{
    91  		{&Struct{Name: "a"}, &Struct{Name: "b"}, true},
    92  		{&Union{Name: "a"}, &Union{Name: "b"}, true},
    93  		{&Union{Name: "a"}, &Struct{Name: "b"}, true},
    94  		{&Enum{Name: "a"}, &Enum{Name: "b"}, false},
    95  		{&Enum{Name: "a"}, &Enum{Name: "a___foo"}, true},
    96  		{&Enum{Name: "a"}, &Enum{Name: ""}, true},
    97  		{&Fwd{Name: "a"}, &Fwd{Name: "b"}, false},
    98  		{&Fwd{Name: "a"}, &Fwd{Name: "a___foo"}, true},
    99  		{&Fwd{Name: "a"}, &Fwd{Name: ""}, true},
   100  		{&Int{Name: "a", Size: 2}, &Int{Name: "b", Size: 4}, true},
   101  		{&Pointer{Target: &Void{}}, &Pointer{Target: &Void{}}, true},
   102  		{&Pointer{Target: &Void{}}, &Void{}, false},
   103  		{&Array{Type: &Int{Size: 1}}, &Array{Type: &Int{Encoding: Signed}}, true},
   104  		{&Float{Size: 2}, &Float{Size: 4}, true},
   105  	}
   106  
   107  	for _, test := range tests {
   108  		err := coreAreMembersCompatible(test.a, test.b)
   109  		if test.compatible {
   110  			if err != nil {
   111  				t.Errorf("Expected members to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
   112  				continue
   113  			}
   114  		} else {
   115  			if !errors.Is(err, errImpossibleRelocation) {
   116  				t.Errorf("Expected members to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
   117  				continue
   118  			}
   119  		}
   120  
   121  		err = coreAreMembersCompatible(test.b, test.a)
   122  		if test.compatible {
   123  			if err != nil {
   124  				t.Errorf("Expected reversed members to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
   125  			}
   126  		} else {
   127  			if !errors.Is(err, errImpossibleRelocation) {
   128  				t.Errorf("Expected reversed members to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
   129  			}
   130  		}
   131  	}
   132  
   133  	for _, invalid := range []Type{&Void{}, &FuncProto{}, &Var{}, &Datasec{}} {
   134  		err := coreAreMembersCompatible(invalid, invalid)
   135  		if errors.Is(err, errImpossibleRelocation) {
   136  			t.Errorf("Expected an error for %T, not errImpossibleRelocation", invalid)
   137  		} else if err == nil {
   138  			t.Errorf("Expected an error for %T", invalid)
   139  		}
   140  	}
   141  }
   142  
   143  func TestCOREAccessor(t *testing.T) {
   144  	for _, valid := range []string{
   145  		"0",
   146  		"1:0",
   147  		"1:0:3:34:10:1",
   148  	} {
   149  		_, err := parseCOREAccessor(valid)
   150  		if err != nil {
   151  			t.Errorf("Parse %q: %s", valid, err)
   152  		}
   153  	}
   154  
   155  	for _, invalid := range []string{
   156  		"",
   157  		"-1",
   158  		":",
   159  		"0:",
   160  		":12",
   161  		"4294967296",
   162  	} {
   163  		_, err := parseCOREAccessor(invalid)
   164  		if err == nil {
   165  			t.Errorf("Accepted invalid accessor %q", invalid)
   166  		}
   167  	}
   168  }
   169  
   170  func TestCOREFindEnumValue(t *testing.T) {
   171  	a := &Enum{Values: []EnumValue{{"foo", 23}, {"bar", 42}}}
   172  	b := &Enum{Values: []EnumValue{
   173  		{"foo___flavour", 0},
   174  		{"bar", 123},
   175  		{"garbage", 3},
   176  	}}
   177  
   178  	invalid := []struct {
   179  		name   string
   180  		local  Type
   181  		target Type
   182  		acc    coreAccessor
   183  		err    error
   184  	}{
   185  		{"o-o-b accessor", a, b, coreAccessor{len(a.Values)}, nil},
   186  		{"long accessor", a, b, coreAccessor{0, 1}, nil},
   187  		{"wrong target", a, &Void{}, coreAccessor{0, 1}, nil},
   188  		{
   189  			"no matching value",
   190  			b, a,
   191  			coreAccessor{2},
   192  			errImpossibleRelocation,
   193  		},
   194  	}
   195  
   196  	for _, test := range invalid {
   197  		t.Run(test.name, func(t *testing.T) {
   198  			_, _, err := coreFindEnumValue(test.local, test.acc, test.target)
   199  			if test.err != nil && !errors.Is(err, test.err) {
   200  				t.Fatalf("Expected %s, got %s", test.err, err)
   201  			}
   202  			if err == nil {
   203  				t.Fatal("Accepted invalid case")
   204  			}
   205  		})
   206  	}
   207  
   208  	valid := []struct {
   209  		name                    string
   210  		local, target           Type
   211  		acc                     coreAccessor
   212  		localValue, targetValue uint64
   213  	}{
   214  		{"a to b", a, b, coreAccessor{0}, 23, 0},
   215  		{"b to a", b, a, coreAccessor{1}, 123, 42},
   216  	}
   217  
   218  	for _, test := range valid {
   219  		t.Run(test.name, func(t *testing.T) {
   220  			local, target, err := coreFindEnumValue(test.local, test.acc, test.target)
   221  			qt.Assert(t, err, qt.IsNil)
   222  			qt.Check(t, local.Value, qt.Equals, test.localValue)
   223  			qt.Check(t, target.Value, qt.Equals, test.targetValue)
   224  		})
   225  	}
   226  }
   227  
   228  func TestCOREFindField(t *testing.T) {
   229  	ptr := &Pointer{}
   230  	u16 := &Int{Size: 2}
   231  	u32 := &Int{Size: 4}
   232  	aFields := []Member{
   233  		{Name: "foo", Type: ptr, Offset: 8},
   234  		{Name: "bar", Type: u16, Offset: 16},
   235  		{Name: "baz", Type: u32, Offset: 32, BitfieldSize: 3},
   236  		{Name: "quux", Type: u32, Offset: 35, BitfieldSize: 10},
   237  		{Name: "quuz", Type: u32, Offset: 45, BitfieldSize: 8},
   238  	}
   239  	bFields := []Member{
   240  		{Name: "foo", Type: ptr, Offset: 16},
   241  		{Name: "bar", Type: u32, Offset: 8},
   242  		{Name: "other", Offset: 4},
   243  		// baz is separated out from the other bitfields
   244  		{Name: "baz", Type: u32, Offset: 64, BitfieldSize: 3},
   245  		// quux's type changes u32->u16
   246  		{Name: "quux", Type: u16, Offset: 96, BitfieldSize: 10},
   247  		// quuz becomes a normal field
   248  		{Name: "quuz", Type: u16, Offset: 112},
   249  	}
   250  
   251  	aStruct := &Struct{Members: aFields, Size: 48}
   252  	bStruct := &Struct{Members: bFields, Size: 80}
   253  	aArray := &Array{Nelems: 4, Type: u16}
   254  	bArray := &Array{Nelems: 3, Type: u32}
   255  
   256  	invalid := []struct {
   257  		name          string
   258  		local, target Type
   259  		acc           coreAccessor
   260  		err           error
   261  	}{
   262  		{
   263  			"unsupported type",
   264  			&Void{}, &Void{},
   265  			coreAccessor{0, 0},
   266  			ErrNotSupported,
   267  		},
   268  		{
   269  			"different types",
   270  			&Union{}, &Array{Type: u16},
   271  			coreAccessor{0},
   272  			errImpossibleRelocation,
   273  		},
   274  		{
   275  			"invalid composite accessor",
   276  			aStruct, aStruct,
   277  			coreAccessor{0, len(aStruct.Members)},
   278  			nil,
   279  		},
   280  		{
   281  			"invalid array accessor",
   282  			aArray, aArray,
   283  			coreAccessor{0, int(aArray.Nelems)},
   284  			nil,
   285  		},
   286  		{
   287  			"o-o-b array accessor",
   288  			aArray, bArray,
   289  			coreAccessor{0, int(bArray.Nelems)},
   290  			errImpossibleRelocation,
   291  		},
   292  		{
   293  			"no match",
   294  			bStruct, aStruct,
   295  			coreAccessor{0, 2},
   296  			errImpossibleRelocation,
   297  		},
   298  		{
   299  			"incompatible match",
   300  			&Union{Members: []Member{{Name: "foo", Type: &Pointer{}}}},
   301  			&Union{Members: []Member{{Name: "foo", Type: &Int{}}}},
   302  			coreAccessor{0, 0},
   303  			errImpossibleRelocation,
   304  		},
   305  		{
   306  			"unsized type",
   307  			bStruct, &Func{},
   308  			// non-zero accessor to force calculating the offset.
   309  			coreAccessor{1},
   310  			errImpossibleRelocation,
   311  		},
   312  	}
   313  
   314  	for _, test := range invalid {
   315  		t.Run(test.name, func(t *testing.T) {
   316  			_, _, err := coreFindField(test.local, test.acc, test.target)
   317  			if test.err != nil && !errors.Is(err, test.err) {
   318  				t.Fatalf("Expected %s, got %s", test.err, err)
   319  			}
   320  			if err == nil {
   321  				t.Fatal("Accepted invalid case")
   322  			}
   323  			t.Log(err)
   324  		})
   325  	}
   326  
   327  	bytes := func(typ Type) uint32 {
   328  		sz, err := Sizeof(typ)
   329  		if err != nil {
   330  			t.Fatal(err)
   331  		}
   332  		return uint32(sz)
   333  	}
   334  
   335  	anon := func(t Type, offset Bits) []Member {
   336  		return []Member{{Type: t, Offset: offset}}
   337  	}
   338  
   339  	anonStruct := func(m ...Member) Member {
   340  		return Member{Type: &Struct{Members: m}}
   341  	}
   342  
   343  	anonUnion := func(m ...Member) Member {
   344  		return Member{Type: &Union{Members: m}}
   345  	}
   346  
   347  	valid := []struct {
   348  		name                    string
   349  		local                   Type
   350  		target                  Type
   351  		acc                     coreAccessor
   352  		localField, targetField coreField
   353  	}{
   354  		{
   355  			"array[0]",
   356  			aArray,
   357  			bArray,
   358  			coreAccessor{0, 0},
   359  			coreField{u16, 0, 0, 0},
   360  			coreField{u32, 0, 0, 0},
   361  		},
   362  		{
   363  			"array[1]",
   364  			aArray,
   365  			bArray,
   366  			coreAccessor{0, 1},
   367  			coreField{u16, bytes(aArray.Type), 0, 0},
   368  			coreField{u32, bytes(bArray.Type), 0, 0},
   369  		},
   370  		{
   371  			"array[0] with base offset",
   372  			aArray,
   373  			bArray,
   374  			coreAccessor{1, 0},
   375  			coreField{u16, bytes(aArray), 0, 0},
   376  			coreField{u32, bytes(bArray), 0, 0},
   377  		},
   378  		{
   379  			"array[2] with base offset",
   380  			aArray,
   381  			bArray,
   382  			coreAccessor{1, 2},
   383  			coreField{u16, bytes(aArray) + 2*bytes(aArray.Type), 0, 0},
   384  			coreField{u32, bytes(bArray) + 2*bytes(bArray.Type), 0, 0},
   385  		},
   386  		{
   387  			"flex array",
   388  			&Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u16}}}},
   389  			&Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u32}}}},
   390  			coreAccessor{0, 0, 9000},
   391  			coreField{u16, bytes(u16) * 9000, 0, 0},
   392  			coreField{u32, bytes(u32) * 9000, 0, 0},
   393  		},
   394  		{
   395  			"struct.0",
   396  			aStruct, bStruct,
   397  			coreAccessor{0, 0},
   398  			coreField{ptr, 1, 0, 0},
   399  			coreField{ptr, 2, 0, 0},
   400  		},
   401  		{
   402  			"struct.0 anon",
   403  			aStruct, &Struct{Members: anon(bStruct, 24)},
   404  			coreAccessor{0, 0},
   405  			coreField{ptr, 1, 0, 0},
   406  			coreField{ptr, 3 + 2, 0, 0},
   407  		},
   408  		{
   409  			"struct.0 with base offset",
   410  			aStruct, bStruct,
   411  			coreAccessor{3, 0},
   412  			coreField{ptr, 3*bytes(aStruct) + 1, 0, 0},
   413  			coreField{ptr, 3*bytes(bStruct) + 2, 0, 0},
   414  		},
   415  		{
   416  			"struct.1",
   417  			aStruct, bStruct,
   418  			coreAccessor{0, 1},
   419  			coreField{u16, 2, 0, 0},
   420  			coreField{u32, 1, 0, 0},
   421  		},
   422  		{
   423  			"struct.1 anon",
   424  			aStruct, &Struct{Members: anon(bStruct, 24)},
   425  			coreAccessor{0, 1},
   426  			coreField{u16, 2, 0, 0},
   427  			coreField{u32, 3 + 1, 0, 0},
   428  		},
   429  		{
   430  			"union.1",
   431  			&Union{Members: aFields, Size: 32},
   432  			&Union{Members: bFields, Size: 32},
   433  			coreAccessor{0, 1},
   434  			coreField{u16, 2, 0, 0},
   435  			coreField{u32, 1, 0, 0},
   436  		},
   437  		{
   438  			"interchangeable composites",
   439  			&Struct{
   440  				Members: []Member{
   441  					anonStruct(anonUnion(Member{Name: "_1", Type: u16})),
   442  				},
   443  			},
   444  			&Struct{
   445  				Members: []Member{
   446  					anonUnion(anonStruct(Member{Name: "_1", Type: u16})),
   447  				},
   448  			},
   449  			coreAccessor{0, 0, 0, 0},
   450  			coreField{u16, 0, 0, 0},
   451  			coreField{u16, 0, 0, 0},
   452  		},
   453  		{
   454  			"struct.2 (bitfield baz)",
   455  			aStruct, bStruct,
   456  			coreAccessor{0, 2},
   457  			coreField{u32, 4, 0, 3},
   458  			coreField{u32, 8, 0, 3},
   459  		},
   460  		{
   461  			"struct.3 (bitfield quux)",
   462  			aStruct, bStruct,
   463  			coreAccessor{0, 3},
   464  			coreField{u32, 4, 3, 10},
   465  			coreField{u16, 12, 0, 10},
   466  		},
   467  		{
   468  			"struct.4 (bitfield quuz)",
   469  			aStruct, bStruct,
   470  			coreAccessor{0, 4},
   471  			coreField{u32, 4, 13, 8},
   472  			coreField{u16, 14, 0, 0},
   473  		},
   474  	}
   475  
   476  	allowCoreField := cmp.AllowUnexported(coreField{})
   477  
   478  	checkCOREField := func(t *testing.T, which string, got, want coreField) {
   479  		t.Helper()
   480  		if diff := cmp.Diff(want, got, allowCoreField); diff != "" {
   481  			t.Errorf("%s mismatch (-want +got):\n%s", which, diff)
   482  		}
   483  	}
   484  
   485  	for _, test := range valid {
   486  		t.Run(test.name, func(t *testing.T) {
   487  			localField, targetField, err := coreFindField(test.local, test.acc, test.target)
   488  			qt.Assert(t, err, qt.IsNil)
   489  			checkCOREField(t, "local", localField, test.localField)
   490  			checkCOREField(t, "target", targetField, test.targetField)
   491  		})
   492  	}
   493  }
   494  
   495  func TestCOREFindFieldCyclical(t *testing.T) {
   496  	members := []Member{{Name: "foo", Type: &Pointer{}}}
   497  
   498  	cyclicStruct := &Struct{}
   499  	cyclicStruct.Members = []Member{{Type: cyclicStruct}}
   500  
   501  	cyclicUnion := &Union{}
   502  	cyclicUnion.Members = []Member{{Type: cyclicUnion}}
   503  
   504  	cyclicArray := &Array{Nelems: 1}
   505  	cyclicArray.Type = &Pointer{Target: cyclicArray}
   506  
   507  	tests := []struct {
   508  		name          string
   509  		local, cyclic Type
   510  	}{
   511  		{"struct", &Struct{Members: members}, cyclicStruct},
   512  		{"union", &Union{Members: members}, cyclicUnion},
   513  		{"array", &Array{Nelems: 2, Type: &Int{}}, cyclicArray},
   514  	}
   515  
   516  	for _, test := range tests {
   517  		t.Run(test.name, func(t *testing.T) {
   518  			_, _, err := coreFindField(test.local, coreAccessor{0, 0}, test.cyclic)
   519  			if !errors.Is(err, errImpossibleRelocation) {
   520  				t.Fatal("Should return errImpossibleRelocation, got", err)
   521  			}
   522  		})
   523  	}
   524  }
   525  
   526  func TestCORERelocation(t *testing.T) {
   527  	testutils.Files(t, testutils.Glob(t, "testdata/*.elf"), func(t *testing.T, file string) {
   528  		rd, err := os.Open(file)
   529  		if err != nil {
   530  			t.Fatal(err)
   531  		}
   532  		defer rd.Close()
   533  
   534  		spec, extInfos, err := LoadSpecAndExtInfosFromReader(rd)
   535  		if err != nil {
   536  			t.Fatal(err)
   537  		}
   538  
   539  		if extInfos == nil {
   540  			t.Skip("No ext_infos")
   541  		}
   542  
   543  		errs := map[string]error{
   544  			"err_ambiguous":         errAmbiguousRelocation,
   545  			"err_ambiguous_flavour": errAmbiguousRelocation,
   546  		}
   547  
   548  		for section := range extInfos.funcInfos {
   549  			name := strings.TrimPrefix(section, "socket_filter/")
   550  			t.Run(name, func(t *testing.T) {
   551  				var relos []*CORERelocation
   552  				for _, reloInfo := range extInfos.relocationInfos[section] {
   553  					relos = append(relos, reloInfo.relo)
   554  				}
   555  
   556  				fixups, err := CORERelocate(relos, spec, spec.byteOrder)
   557  				if want := errs[name]; want != nil {
   558  					if !errors.Is(err, want) {
   559  						t.Fatal("Expected", want, "got", err)
   560  					}
   561  					return
   562  				}
   563  
   564  				if err != nil {
   565  					t.Fatal("Can't relocate against itself:", err)
   566  				}
   567  
   568  				for offset, fixup := range fixups {
   569  					if want := fixup.local; !fixup.skipLocalValidation && want != fixup.target {
   570  						// Since we're relocating against ourselves both values
   571  						// should match.
   572  						t.Errorf("offset %d: local %v doesn't match target %d (kind %s)", offset, fixup.local, fixup.target, fixup.kind)
   573  					}
   574  				}
   575  			})
   576  		}
   577  	})
   578  }
   579  
   580  func TestCORECopyWithoutQualifiers(t *testing.T) {
   581  	qualifiers := []struct {
   582  		name string
   583  		fn   func(Type) Type
   584  	}{
   585  		{"const", func(t Type) Type { return &Const{Type: t} }},
   586  		{"volatile", func(t Type) Type { return &Volatile{Type: t} }},
   587  		{"restrict", func(t Type) Type { return &Restrict{Type: t} }},
   588  		{"typedef", func(t Type) Type { return &Typedef{Type: t} }},
   589  	}
   590  
   591  	for _, test := range qualifiers {
   592  		t.Run(test.name+" cycle", func(t *testing.T) {
   593  			root := &Volatile{}
   594  			root.Type = test.fn(root)
   595  
   596  			cycle, ok := Copy(root, UnderlyingType).(*cycle)
   597  			qt.Assert(t, ok, qt.IsTrue)
   598  			qt.Assert(t, cycle.root, qt.Equals, root)
   599  		})
   600  	}
   601  
   602  	for _, a := range qualifiers {
   603  		for _, b := range qualifiers {
   604  			t.Run(a.name+" "+b.name, func(t *testing.T) {
   605  				v := a.fn(&Pointer{Target: b.fn(&Int{Name: "z"})})
   606  				want := &Pointer{Target: &Int{Name: "z"}}
   607  
   608  				got := Copy(v, UnderlyingType)
   609  				qt.Assert(t, got, qt.DeepEquals, want)
   610  			})
   611  		}
   612  	}
   613  
   614  	t.Run("long chain", func(t *testing.T) {
   615  		root := &Int{Name: "abc"}
   616  		v := Type(root)
   617  		for i := 0; i < maxTypeDepth; i++ {
   618  			q := qualifiers[rand.Intn(len(qualifiers))]
   619  			v = q.fn(v)
   620  			t.Log(q.name)
   621  		}
   622  
   623  		got := Copy(v, UnderlyingType)
   624  		qt.Assert(t, got, qt.DeepEquals, root)
   625  	})
   626  }