github.com/vishvananda/netlink@v1.3.0/class_test.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package netlink
     5  
     6  import (
     7  	"reflect"
     8  	"testing"
     9  )
    10  
    11  func SafeQdiscList(link Link) ([]Qdisc, error) {
    12  	qdiscs, err := QdiscList(link)
    13  	if err != nil {
    14  		return nil, err
    15  	}
    16  	result := []Qdisc{}
    17  	for _, qdisc := range qdiscs {
    18  		// fmt.Printf("%+v\n", qdisc)
    19  		// filter default qdisc created by kernel when custom one deleted
    20  		attrs := qdisc.Attrs()
    21  		if attrs.Handle == HANDLE_NONE && attrs.Parent == HANDLE_ROOT {
    22  			continue
    23  		}
    24  		result = append(result, qdisc)
    25  	}
    26  	return result, nil
    27  }
    28  
    29  func SafeClassList(link Link, handle uint32) ([]Class, error) {
    30  	classes, err := ClassList(link, handle)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  	result := []Class{}
    35  	for ind := range classes {
    36  		double := false
    37  		for _, class2 := range classes[ind+1:] {
    38  			if classes[ind].Attrs().Handle == class2.Attrs().Handle {
    39  				double = true
    40  			}
    41  		}
    42  		if !double {
    43  			result = append(result, classes[ind])
    44  		}
    45  	}
    46  	return result, nil
    47  }
    48  
    49  func testClassStats(this, that *ClassStatistics, t *testing.T) {
    50  	ok := reflect.DeepEqual(this, that)
    51  	if !ok {
    52  		t.Fatalf("%#v is expected but it actually was %#v", that, this)
    53  	}
    54  }
    55  
    56  func TestClassAddDel(t *testing.T) {
    57  	tearDown := setUpNetlinkTest(t)
    58  	defer tearDown()
    59  	if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
    60  		t.Fatal(err)
    61  	}
    62  	if err := LinkAdd(&Ifb{LinkAttrs{Name: "bar"}}); err != nil {
    63  		t.Fatal(err)
    64  	}
    65  	link, err := LinkByName("foo")
    66  	if err != nil {
    67  		t.Fatal(err)
    68  	}
    69  	if err := LinkSetUp(link); err != nil {
    70  		t.Fatal(err)
    71  	}
    72  	attrs := QdiscAttrs{
    73  		LinkIndex: link.Attrs().Index,
    74  		Handle:    MakeHandle(0xffff, 0),
    75  		Parent:    HANDLE_ROOT,
    76  	}
    77  	qdisc := NewHtb(attrs)
    78  	if err := QdiscAdd(qdisc); err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	qdiscs, err := SafeQdiscList(link)
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  	if len(qdiscs) != 1 {
    86  		t.Fatal("Failed to add qdisc")
    87  	}
    88  	_, ok := qdiscs[0].(*Htb)
    89  	if !ok {
    90  		t.Fatal("Qdisc is the wrong type")
    91  	}
    92  
    93  	classattrs := ClassAttrs{
    94  		LinkIndex: link.Attrs().Index,
    95  		Parent:    MakeHandle(0xffff, 0),
    96  		Handle:    MakeHandle(0xffff, 2),
    97  	}
    98  
    99  	htbclassattrs := HtbClassAttrs{
   100  		Rate:    1234000,
   101  		Cbuffer: 1690,
   102  		Prio:    2,
   103  		Quantum: 1000,
   104  	}
   105  	class := NewHtbClass(classattrs, htbclassattrs)
   106  	if err := ClassAdd(class); err != nil {
   107  		t.Fatal(err)
   108  	}
   109  	classes, err := SafeClassList(link, MakeHandle(0xffff, 0))
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  	if len(classes) != 1 {
   114  		t.Fatal("Failed to add class")
   115  	}
   116  
   117  	htb, ok := classes[0].(*HtbClass)
   118  	if !ok {
   119  		t.Fatal("Class is the wrong type")
   120  	}
   121  	if htb.Rate != class.Rate {
   122  		t.Fatal("Rate doesn't match")
   123  	}
   124  	if htb.Ceil != class.Ceil {
   125  		t.Fatal("Ceil doesn't match")
   126  	}
   127  	if htb.Buffer != class.Buffer {
   128  		t.Fatal("Buffer doesn't match")
   129  	}
   130  	if htb.Cbuffer != class.Cbuffer {
   131  		t.Fatal("Cbuffer doesn't match")
   132  	}
   133  	if htb.Prio != class.Prio {
   134  		t.Fatal("Prio doesn't match")
   135  	}
   136  	if htb.Quantum != class.Quantum {
   137  		t.Fatal("Quantum doesn't match")
   138  	}
   139  
   140  	testClassStats(htb.ClassAttrs.Statistics, NewClassStatistics(), t)
   141  
   142  	qattrs := QdiscAttrs{
   143  		LinkIndex: link.Attrs().Index,
   144  		Handle:    MakeHandle(0x2, 0),
   145  		Parent:    MakeHandle(0xffff, 2),
   146  	}
   147  	nattrs := NetemQdiscAttrs{
   148  		Latency:     20000,
   149  		Loss:        23.4,
   150  		Duplicate:   14.3,
   151  		LossCorr:    8.34,
   152  		Jitter:      1000,
   153  		DelayCorr:   12.3,
   154  		ReorderProb: 23.4,
   155  		CorruptProb: 10.0,
   156  		CorruptCorr: 10,
   157  		Rate64:      10 * 1024 * 1024,
   158  	}
   159  	qdiscnetem := NewNetem(qattrs, nattrs)
   160  	if err := QdiscAdd(qdiscnetem); err != nil {
   161  		t.Fatal(err)
   162  	}
   163  
   164  	qdiscs, err = SafeQdiscList(link)
   165  	if err != nil {
   166  		t.Fatal(err)
   167  	}
   168  	if len(qdiscs) != 2 {
   169  		t.Fatal("Failed to add qdisc")
   170  	}
   171  	_, ok = qdiscs[0].(*Htb)
   172  	if !ok {
   173  		t.Fatal("Qdisc is the wrong type")
   174  	}
   175  
   176  	netem, ok := qdiscs[1].(*Netem)
   177  	if !ok {
   178  		t.Fatal("Qdisc is the wrong type")
   179  	}
   180  	// Compare the record we got from the list with the one we created
   181  	if netem.Loss != qdiscnetem.Loss {
   182  		t.Fatal("Loss does not match")
   183  	}
   184  	if netem.Latency != qdiscnetem.Latency {
   185  		t.Fatal("Latency does not match")
   186  	}
   187  	if netem.CorruptProb != qdiscnetem.CorruptProb {
   188  		t.Fatal("CorruptProb does not match")
   189  	}
   190  	if netem.Jitter != qdiscnetem.Jitter {
   191  		t.Fatal("Jitter does not match")
   192  	}
   193  	if netem.LossCorr != qdiscnetem.LossCorr {
   194  		t.Fatal("Loss does not match")
   195  	}
   196  	if netem.DuplicateCorr != qdiscnetem.DuplicateCorr {
   197  		t.Fatal("DuplicateCorr does not match")
   198  	}
   199  	if netem.Rate64 != qdiscnetem.Rate64 {
   200  		t.Fatalf("Rate64 does not match. Expected %d, got %d", netem.Rate64, qdiscnetem.Rate64)
   201  	}
   202  
   203  	// Deletion
   204  	// automatically removes netem qdisc
   205  	if err := ClassDel(class); err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	classes, err = SafeClassList(link, MakeHandle(0xffff, 0))
   209  	if err != nil {
   210  		t.Fatal(err)
   211  	}
   212  	if len(classes) != 0 {
   213  		t.Fatal("Failed to remove class")
   214  	}
   215  	if err := QdiscDel(qdisc); err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	qdiscs, err = SafeQdiscList(link)
   219  	if err != nil {
   220  		t.Fatal(err)
   221  	}
   222  	if len(qdiscs) != 0 {
   223  		t.Fatal("Failed to remove qdisc")
   224  	}
   225  }
   226  
   227  func TestHtbClassAddHtbClassChangeDel(t *testing.T) {
   228  	/**
   229  	This test first set up a interface ans set up a Htb qdisc
   230  	A HTB class is attach to it and a Netem qdisc is attached to that class
   231  	Next, we test changing the HTB class in place and confirming the Netem is
   232  	still attached. We also check that invoting ClassChange with a non-existing
   233  	class will fail.
   234  	Finally, we test ClassReplace. We confirm it correctly behave like
   235  	ClassChange when the parent/handle pair exists and that it will create a
   236  	new class if the handle is modified.
   237  	*/
   238  	tearDown := setUpNetlinkTest(t)
   239  	defer tearDown()
   240  	if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
   241  		t.Fatal(err)
   242  	}
   243  	link, err := LinkByName("foo")
   244  	if err != nil {
   245  		t.Fatal(err)
   246  	}
   247  	if err := LinkSetUp(link); err != nil {
   248  		t.Fatal(err)
   249  	}
   250  	attrs := QdiscAttrs{
   251  		LinkIndex: link.Attrs().Index,
   252  		Handle:    MakeHandle(0xffff, 0),
   253  		Parent:    HANDLE_ROOT,
   254  	}
   255  	qdisc := NewHtb(attrs)
   256  	if err := QdiscAdd(qdisc); err != nil {
   257  		t.Fatal(err)
   258  	}
   259  	qdiscs, err := SafeQdiscList(link)
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  	if len(qdiscs) != 1 {
   264  		t.Fatal("Failed to add qdisc")
   265  	}
   266  	_, ok := qdiscs[0].(*Htb)
   267  	if !ok {
   268  		t.Fatal("Qdisc is the wrong type")
   269  	}
   270  
   271  	classattrs := ClassAttrs{
   272  		LinkIndex: link.Attrs().Index,
   273  		Parent:    MakeHandle(0xffff, 0),
   274  		Handle:    MakeHandle(0xffff, 2),
   275  	}
   276  
   277  	htbclassattrs := HtbClassAttrs{
   278  		Rate:    uint64(1<<32) + 10,
   279  		Ceil:    uint64(1<<32) + 20,
   280  		Cbuffer: 1690,
   281  	}
   282  	class := NewHtbClass(classattrs, htbclassattrs)
   283  	if err := ClassAdd(class); err != nil {
   284  		t.Fatal(err)
   285  	}
   286  	classes, err := SafeClassList(link, 0)
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  	if len(classes) != 1 {
   291  		t.Fatal("Failed to add class")
   292  	}
   293  
   294  	htb, ok := classes[0].(*HtbClass)
   295  	if !ok {
   296  		t.Fatal("Class is the wrong type")
   297  	}
   298  
   299  	testClassStats(htb.ClassAttrs.Statistics, NewClassStatistics(), t)
   300  
   301  	qattrs := QdiscAttrs{
   302  		LinkIndex: link.Attrs().Index,
   303  		Handle:    MakeHandle(0x2, 0),
   304  		Parent:    MakeHandle(0xffff, 2),
   305  	}
   306  	nattrs := NetemQdiscAttrs{
   307  		Latency:     20000,
   308  		Loss:        23.4,
   309  		Duplicate:   14.3,
   310  		LossCorr:    8.34,
   311  		Jitter:      1000,
   312  		DelayCorr:   12.3,
   313  		ReorderProb: 23.4,
   314  		CorruptProb: 10.0,
   315  		CorruptCorr: 10,
   316  	}
   317  	qdiscnetem := NewNetem(qattrs, nattrs)
   318  	if err := QdiscAdd(qdiscnetem); err != nil {
   319  		t.Fatal(err)
   320  	}
   321  
   322  	qdiscs, err = SafeQdiscList(link)
   323  	if err != nil {
   324  		t.Fatal(err)
   325  	}
   326  	if len(qdiscs) != 2 {
   327  		t.Fatal("Failed to add qdisc")
   328  	}
   329  
   330  	_, ok = qdiscs[1].(*Netem)
   331  	if !ok {
   332  		t.Fatal("Qdisc is the wrong type")
   333  	}
   334  
   335  	// Change
   336  	// For change to work, the handle and parent cannot be changed.
   337  
   338  	// First, test it fails if we change the Handle.
   339  	oldHandle := classattrs.Handle
   340  	classattrs.Handle = MakeHandle(0xffff, 3)
   341  	class = NewHtbClass(classattrs, htbclassattrs)
   342  	if err := ClassChange(class); err == nil {
   343  		t.Fatal("ClassChange should not work when using a different handle.")
   344  	}
   345  	// It should work with the same handle
   346  	classattrs.Handle = oldHandle
   347  	htbclassattrs.Rate = 4321000
   348  	class = NewHtbClass(classattrs, htbclassattrs)
   349  	if err := ClassChange(class); err != nil {
   350  		t.Fatal(err)
   351  	}
   352  
   353  	classes, err = SafeClassList(link, MakeHandle(0xffff, 0))
   354  	if err != nil {
   355  		t.Fatal(err)
   356  	}
   357  	if len(classes) != 1 {
   358  		t.Fatalf(
   359  			"1 class expected, %d found",
   360  			len(classes),
   361  		)
   362  	}
   363  
   364  	htb, ok = classes[0].(*HtbClass)
   365  	if !ok {
   366  		t.Fatal("Class is the wrong type")
   367  	}
   368  	// Verify that the rate value has changed.
   369  	if htb.Rate != class.Rate {
   370  		t.Fatal("Rate did not get changed while changing the class.")
   371  	}
   372  
   373  	// Check that we still have the netem child qdisc
   374  	qdiscs, err = SafeQdiscList(link)
   375  	if err != nil {
   376  		t.Fatal(err)
   377  	}
   378  
   379  	if len(qdiscs) != 2 {
   380  		t.Fatalf("2 qdisc expected, %d found", len(qdiscs))
   381  	}
   382  	_, ok = qdiscs[0].(*Htb)
   383  	if !ok {
   384  		t.Fatal("Qdisc is the wrong type")
   385  	}
   386  
   387  	_, ok = qdiscs[1].(*Netem)
   388  	if !ok {
   389  		t.Fatal("Qdisc is the wrong type")
   390  	}
   391  
   392  	// Replace
   393  	// First replace by keeping the same handle, class will be changed.
   394  	// Then, replace by providing a new handle, n new class will be created.
   395  
   396  	// Replace acting as Change
   397  	class = NewHtbClass(classattrs, htbclassattrs)
   398  	if err := ClassReplace(class); err != nil {
   399  		t.Fatal("Failed to replace class that is existing.")
   400  	}
   401  
   402  	classes, err = SafeClassList(link, MakeHandle(0xffff, 0))
   403  	if err != nil {
   404  		t.Fatal(err)
   405  	}
   406  	if len(classes) != 1 {
   407  		t.Fatalf(
   408  			"1 class expected, %d found",
   409  			len(classes),
   410  		)
   411  	}
   412  
   413  	htb, ok = classes[0].(*HtbClass)
   414  	if !ok {
   415  		t.Fatal("Class is the wrong type")
   416  	}
   417  	// Verify that the rate value has changed.
   418  	if htb.Rate != class.Rate {
   419  		t.Fatal("Rate did not get changed while changing the class.")
   420  	}
   421  
   422  	// It should work with the same handle
   423  	classattrs.Handle = MakeHandle(0xffff, 3)
   424  	class = NewHtbClass(classattrs, htbclassattrs)
   425  	if err := ClassReplace(class); err != nil {
   426  		t.Fatal(err)
   427  	}
   428  
   429  	classes, err = SafeClassList(link, MakeHandle(0xffff, 0))
   430  	if err != nil {
   431  		t.Fatal(err)
   432  	}
   433  	if len(classes) != 2 {
   434  		t.Fatalf(
   435  			"2 classes expected, %d found",
   436  			len(classes),
   437  		)
   438  	}
   439  
   440  	htb, ok = classes[1].(*HtbClass)
   441  	if !ok {
   442  		t.Fatal("Class is the wrong type")
   443  	}
   444  	// Verify that the rate value has changed.
   445  	if htb.Rate != class.Rate {
   446  		t.Fatal("Rate did not get changed while changing the class.")
   447  	}
   448  
   449  	// Deletion
   450  	for _, class := range classes {
   451  		if err := ClassDel(class); err != nil {
   452  			t.Fatal(err)
   453  		}
   454  	}
   455  
   456  	classes, err = SafeClassList(link, MakeHandle(0xffff, 0))
   457  	if err != nil {
   458  		t.Fatal(err)
   459  	}
   460  	if len(classes) != 0 {
   461  		t.Fatal("Failed to remove class")
   462  	}
   463  	if err := QdiscDel(qdisc); err != nil {
   464  		t.Fatal(err)
   465  	}
   466  	qdiscs, err = SafeQdiscList(link)
   467  	if err != nil {
   468  		t.Fatal(err)
   469  	}
   470  	if len(qdiscs) != 0 {
   471  		t.Fatal("Failed to remove qdisc")
   472  	}
   473  }
   474  
   475  func TestClassHfsc(t *testing.T) {
   476  	// New network namespace for tests
   477  	tearDown := setUpNetlinkTestWithKModule(t, "sch_hfsc")
   478  	defer tearDown()
   479  
   480  	// Set up testing link and check if succeeded
   481  	if err := LinkAdd(&Ifb{LinkAttrs{Name: "foo"}}); err != nil {
   482  		t.Fatal(err)
   483  	}
   484  	link, err := LinkByName("foo")
   485  	if err != nil {
   486  		t.Fatal(err)
   487  	}
   488  	if err := LinkSetUp(link); err != nil {
   489  		t.Fatal(err)
   490  	}
   491  
   492  	// Adding HFSC qdisc
   493  	qdiscAttrs := QdiscAttrs{
   494  		LinkIndex: link.Attrs().Index,
   495  		Handle:    MakeHandle(1, 0),
   496  		Parent:    HANDLE_ROOT,
   497  	}
   498  	hfscQdisc := NewHfsc(qdiscAttrs)
   499  	hfscQdisc.Defcls = 2
   500  
   501  	err = QdiscAdd(hfscQdisc)
   502  	if err != nil {
   503  		t.Fatal(err)
   504  	}
   505  	qdiscs, err := SafeQdiscList(link)
   506  	if err != nil {
   507  		t.Fatal(err)
   508  	}
   509  	if len(qdiscs) != 1 {
   510  		t.Fatal("Failed to add qdisc")
   511  	}
   512  	_, ok := qdiscs[0].(*Hfsc)
   513  	if !ok {
   514  		t.Fatal("Qdisc is the wrong type")
   515  	}
   516  
   517  	// Adding some HFSC classes
   518  	classAttrs := ClassAttrs{
   519  		LinkIndex: link.Attrs().Index,
   520  		Parent:    MakeHandle(1, 0),
   521  		Handle:    MakeHandle(1, 1),
   522  	}
   523  	hfscClass := NewHfscClass(classAttrs)
   524  	hfscClass.SetLS(5e6, 10, 5e6)
   525  
   526  	err = ClassAdd(hfscClass)
   527  	if err != nil {
   528  		t.Fatal(err)
   529  	}
   530  
   531  	hfscClass2 := hfscClass
   532  	hfscClass2.SetLS(0, 0, 0)
   533  	hfscClass2.Attrs().Parent = MakeHandle(1, 1)
   534  	hfscClass2.Attrs().Handle = MakeHandle(1, 2)
   535  	hfscClass2.SetRsc(0, 0, 2e6)
   536  
   537  	err = ClassAdd(hfscClass2)
   538  	if err != nil {
   539  		t.Fatal(err)
   540  	}
   541  
   542  	hfscClass3 := hfscClass
   543  	hfscClass3.SetLS(0, 0, 0)
   544  	hfscClass3.Attrs().Parent = MakeHandle(1, 1)
   545  	hfscClass3.Attrs().Handle = MakeHandle(1, 3)
   546  
   547  	err = ClassAdd(hfscClass3)
   548  	if err != nil {
   549  		t.Fatal(err)
   550  	}
   551  
   552  	// Check the classes
   553  	classes, err := SafeClassList(link, MakeHandle(1, 0))
   554  	if err != nil {
   555  		t.Fatal(err)
   556  	}
   557  	if len(classes) != 4 {
   558  		t.Fatal("Failed to add classes")
   559  	}
   560  	for _, c := range classes {
   561  		class, ok := c.(*HfscClass)
   562  		if !ok {
   563  			t.Fatal("Wrong type of class")
   564  		}
   565  		if class.ClassAttrs.Handle == hfscClass.ClassAttrs.Handle {
   566  			if class.Fsc != hfscClass.Fsc {
   567  				t.Fatal("HfscClass FSC don't match")
   568  			}
   569  			if class.Usc != hfscClass.Usc {
   570  				t.Fatal("HfscClass USC don't match")
   571  			}
   572  			if class.Rsc != hfscClass.Rsc {
   573  				t.Fatal("HfscClass RSC don't match")
   574  			}
   575  		}
   576  		if class.ClassAttrs.Handle == hfscClass2.ClassAttrs.Handle {
   577  			if class.Fsc != hfscClass2.Fsc {
   578  				t.Fatal("HfscClass2 FSC don't match")
   579  			}
   580  			if class.Usc != hfscClass2.Usc {
   581  				t.Fatal("HfscClass2 USC don't match")
   582  			}
   583  			if class.Rsc != hfscClass2.Rsc {
   584  				t.Fatal("HfscClass2 RSC don't match")
   585  			}
   586  		}
   587  		if class.ClassAttrs.Handle == hfscClass3.ClassAttrs.Handle {
   588  			if class.Fsc != hfscClass3.Fsc {
   589  				t.Fatal("HfscClass3 FSC don't match")
   590  			}
   591  			if class.Usc != hfscClass3.Usc {
   592  				t.Fatal("HfscClass3 USC don't match")
   593  			}
   594  			if class.Rsc != hfscClass3.Rsc {
   595  				t.Fatal("HfscClass3 RSC don't match")
   596  			}
   597  		}
   598  	}
   599  
   600  	// Terminating the leafs with fq_codel qdiscs
   601  	fqcodelAttrs := QdiscAttrs{
   602  		LinkIndex: link.Attrs().Index,
   603  		Parent:    MakeHandle(1, 2),
   604  		Handle:    MakeHandle(2, 0),
   605  	}
   606  	fqcodel1 := NewFqCodel(fqcodelAttrs)
   607  	fqcodel1.ECN = 0
   608  	fqcodel1.Limit = 1200
   609  	fqcodel1.Flows = 65535
   610  	fqcodel1.Target = 5
   611  
   612  	err = QdiscAdd(fqcodel1)
   613  	if err != nil {
   614  		t.Fatal(err)
   615  	}
   616  
   617  	fqcodel2 := fqcodel1
   618  	fqcodel2.Attrs().Handle = MakeHandle(3, 0)
   619  	fqcodel2.Attrs().Parent = MakeHandle(1, 3)
   620  
   621  	err = QdiscAdd(fqcodel2)
   622  	if err != nil {
   623  		t.Fatal(err)
   624  	}
   625  
   626  	// Check the amount of qdiscs
   627  	qdiscs, _ = SafeQdiscList(link)
   628  	if len(qdiscs) != 3 {
   629  		t.Fatal("Failed to add qdisc")
   630  	}
   631  	for _, q := range qdiscs[1:] {
   632  		_, ok = q.(*FqCodel)
   633  		if !ok {
   634  			t.Fatal("Qdisc is the wrong type")
   635  		}
   636  	}
   637  
   638  	// removing a class
   639  	if err := ClassDel(hfscClass3); err != nil {
   640  		t.Fatal(err)
   641  	}
   642  	// Check the classes
   643  	classes, err = SafeClassList(link, MakeHandle(1, 0))
   644  	if err != nil {
   645  		t.Fatal(err)
   646  	}
   647  	if len(classes) != 3 {
   648  		t.Fatal("Failed to delete classes")
   649  	}
   650  	// Check qdisc
   651  	qdiscs, _ = SafeQdiscList(link)
   652  	if len(qdiscs) != 2 {
   653  		t.Fatal("Failed to delete qdisc")
   654  	}
   655  
   656  	// Changing a class
   657  	hfscClass2.SetRsc(0, 0, 0)
   658  	hfscClass2.SetSC(5e6, 100, 1e6)
   659  	hfscClass2.SetUL(6e6, 50, 2e6)
   660  	hfscClass2.Attrs().Handle = MakeHandle(1, 8)
   661  	if err := ClassChange(hfscClass2); err == nil {
   662  		t.Fatal("Class change shouldn't work with a different handle")
   663  	}
   664  	hfscClass2.Attrs().Handle = MakeHandle(1, 2)
   665  	if err := ClassChange(hfscClass2); err != nil {
   666  		t.Fatal(err)
   667  	}
   668  
   669  	// Replacing a class
   670  	// If the handle doesn't exist, create it
   671  	hfscClass2.SetSC(6e6, 100, 2e6)
   672  	hfscClass2.SetUL(8e6, 500, 4e6)
   673  	hfscClass2.Attrs().Handle = MakeHandle(1, 8)
   674  	if err := ClassReplace(hfscClass2); err != nil {
   675  		t.Fatal(err)
   676  	}
   677  	// If the handle exists, replace it
   678  	hfscClass.SetLS(5e6, 200, 1e6)
   679  	if err := ClassChange(hfscClass); err != nil {
   680  		t.Fatal(err)
   681  	}
   682  
   683  }