Source file src/cmd/link/internal/ld/ld_test.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ld
     6  
     7  import (
     8  	"bytes"
     9  	"debug/pe"
    10  	"fmt"
    11  	"internal/testenv"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strings"
    16  	"testing"
    17  )
    18  
    19  func TestUndefinedRelocErrors(t *testing.T) {
    20  	testenv.MustHaveGoBuild(t)
    21  
    22  	// When external linking, symbols may be defined externally, so we allow
    23  	// undefined symbols and let external linker resolve. Skip the test.
    24  	//
    25  	// N.B. go build below explicitly doesn't pass through
    26  	// -asan/-msan/-race, so we don't care about those.
    27  	testenv.MustInternalLink(t, testenv.NoSpecialBuildTypes)
    28  
    29  	t.Parallel()
    30  
    31  	out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "./testdata/issue10978").CombinedOutput()
    32  	if err == nil {
    33  		t.Fatal("expected build to fail")
    34  	}
    35  
    36  	wantErrors := map[string]int{
    37  		// Main function has dedicated error message.
    38  		"function main is undeclared in the main package": 1,
    39  
    40  		// Single error reporting per each symbol.
    41  		// This way, duplicated messages are not reported for
    42  		// multiple relocations with a same name.
    43  		"main.defined1: relocation target main.undefined not defined": 1,
    44  		"main.defined2: relocation target main.undefined not defined": 1,
    45  	}
    46  	unexpectedErrors := map[string]int{}
    47  
    48  	for _, l := range strings.Split(string(out), "\n") {
    49  		if strings.HasPrefix(l, "#") || l == "" {
    50  			continue
    51  		}
    52  		matched := ""
    53  		for want := range wantErrors {
    54  			if strings.Contains(l, want) {
    55  				matched = want
    56  				break
    57  			}
    58  		}
    59  		if matched != "" {
    60  			wantErrors[matched]--
    61  		} else {
    62  			unexpectedErrors[l]++
    63  		}
    64  	}
    65  
    66  	for want, n := range wantErrors {
    67  		switch {
    68  		case n > 0:
    69  			t.Errorf("unmatched error: %s (x%d)", want, n)
    70  		case n < 0:
    71  			if runtime.GOOS == "android" && runtime.GOARCH == "arm64" {
    72  				testenv.SkipFlaky(t, 58807)
    73  			}
    74  			t.Errorf("extra errors: %s (x%d)", want, -n)
    75  		}
    76  	}
    77  	for unexpected, n := range unexpectedErrors {
    78  		t.Errorf("unexpected error: %s (x%d)", unexpected, n)
    79  	}
    80  }
    81  
    82  const carchiveSrcText = `
    83  package main
    84  
    85  //export GoFunc
    86  func GoFunc() {
    87  	println(42)
    88  }
    89  
    90  func main() {
    91  }
    92  `
    93  
    94  func TestArchiveBuildInvokeWithExec(t *testing.T) {
    95  	t.Parallel()
    96  	testenv.MustHaveGoBuild(t)
    97  	testenv.MustHaveCGO(t)
    98  
    99  	// run this test on just a small set of platforms (no need to test it
   100  	// across the board given the nature of the test).
   101  	pair := runtime.GOOS + "-" + runtime.GOARCH
   102  	switch pair {
   103  	case "darwin-amd64", "darwin-arm64", "linux-amd64", "freebsd-amd64":
   104  	default:
   105  		t.Skip("no need for test on " + pair)
   106  	}
   107  	switch runtime.GOOS {
   108  	case "openbsd", "windows":
   109  		t.Skip("c-archive unsupported")
   110  	}
   111  	dir := t.TempDir()
   112  
   113  	srcfile := filepath.Join(dir, "test.go")
   114  	arfile := filepath.Join(dir, "test.a")
   115  	if err := os.WriteFile(srcfile, []byte(carchiveSrcText), 0666); err != nil {
   116  		t.Fatal(err)
   117  	}
   118  
   119  	ldf := fmt.Sprintf("-ldflags=-v -tmpdir=%s", dir)
   120  	argv := []string{"build", "-buildmode=c-archive", "-o", arfile, ldf, srcfile}
   121  	out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
   122  	if err != nil {
   123  		t.Fatalf("build failure: %s\n%s\n", err, string(out))
   124  	}
   125  
   126  	found := false
   127  	const want = "invoking archiver with syscall.Exec"
   128  	for _, l := range strings.Split(string(out), "\n") {
   129  		if strings.HasPrefix(l, want) {
   130  			found = true
   131  			break
   132  		}
   133  	}
   134  
   135  	if !found {
   136  		t.Errorf("expected '%s' in -v output, got:\n%s\n", want, string(out))
   137  	}
   138  }
   139  
   140  func TestLargeTextSectionSplitting(t *testing.T) {
   141  	switch runtime.GOARCH {
   142  	case "ppc64", "ppc64le", "arm":
   143  	case "arm64":
   144  		if runtime.GOOS == "darwin" {
   145  			break
   146  		}
   147  		fallthrough
   148  	default:
   149  		t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH)
   150  	}
   151  
   152  	testenv.MustHaveGoBuild(t)
   153  	testenv.MustHaveCGO(t)
   154  	t.Parallel()
   155  	dir := t.TempDir()
   156  
   157  	// NB: the use of -ldflags=-debugtextsize=1048576 tells the linker to
   158  	// split text sections at a size threshold of 1M instead of the
   159  	// architected limit of 67M or larger. The choice of building cmd/go
   160  	// is arbitrary; we just need something sufficiently large that uses
   161  	// external linking.
   162  	exe := filepath.Join(dir, "go.exe")
   163  	out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugtextsize=1048576", "cmd/go").CombinedOutput()
   164  	if err != nil {
   165  		t.Fatalf("build failure: %s\n%s\n", err, string(out))
   166  	}
   167  
   168  	// Check that we did split text sections.
   169  	out, err = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", exe).CombinedOutput()
   170  	if err != nil {
   171  		t.Fatalf("nm failure: %s\n%s\n", err, string(out))
   172  	}
   173  	if !bytes.Contains(out, []byte("runtime.text.1")) {
   174  		t.Errorf("runtime.text.1 not found, text section not split?")
   175  	}
   176  
   177  	// Result should be runnable.
   178  	_, err = testenv.Command(t, exe, "version").CombinedOutput()
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  }
   183  
   184  // TestTextSectionSplitOverAligned checks that text section splitting copes
   185  // with functions aligned more strictly than Funcalign (issue 82517).
   186  func TestTextSectionSplitOverAligned(t *testing.T) {
   187  	// funcAsm is one assembly function: an optional PCALIGN, a load and
   188  	// store of sink (for a relocation), filler, and a return of the
   189  	// function's index.
   190  	var funcAsm, filler string
   191  	switch runtime.GOARCH {
   192  	case "arm64":
   193  		if runtime.GOOS != "darwin" {
   194  			t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH)
   195  		}
   196  		funcAsm = `
   197  TEXT ·f%[1]d(SB), NOSPLIT, $0-8
   198  %[2]s
   199  	MOVD	$·sink(SB), R1
   200  	MOVD	(R1), R2
   201  	ADD	$1, R2
   202  	MOVD	R2, (R1)
   203  %[3]s
   204  	MOVD	$%[1]d, R0
   205  	MOVD	R0, ret+0(FP)
   206  	RET
   207  `
   208  		filler = "\tNOOP\n"
   209  	case "ppc64", "ppc64le":
   210  		funcAsm = `
   211  TEXT ·f%[1]d(SB), NOSPLIT, $0-8
   212  %[2]s
   213  	MOVD	$·sink(SB), R3
   214  	MOVD	0(R3), R4
   215  	ADD	$1, R4
   216  	MOVD	R4, 0(R3)
   217  %[3]s
   218  	MOVD	$%[1]d, R3
   219  	MOVD	R3, ret+0(FP)
   220  	RET
   221  `
   222  		filler = "\tADD\t$1, R5\n"
   223  	default:
   224  		// arm also splits text sections, but its assembler has no
   225  		// PCALIGN, so its functions can't be over-aligned.
   226  		t.Skipf("no over-aligned text section splitting on %s/%s", runtime.GOOS, runtime.GOARCH)
   227  	}
   228  	testenv.MustHaveGoBuild(t)
   229  	testenv.MustHaveCGO(t)
   230  	t.Parallel()
   231  	dir := t.TempDir()
   232  
   233  	// Interleave 64-byte-aligned and plain functions, each about 8KiB
   234  	// long (1MiB in all), so that some split text sections start at a
   235  	// plain function and contain an over-aligned one.
   236  	const n = 128
   237  	var goSrc, asmSrc bytes.Buffer
   238  	goSrc.WriteString("package main\n\nvar sink int\n\n")
   239  	asmSrc.WriteString("#include \"textflag.h\"\n")
   240  	for i := range n {
   241  		fmt.Fprintf(&goSrc, "func f%d() int\n", i)
   242  		align := ""
   243  		if i%2 == 0 {
   244  			align = "\tPCALIGN\t$64"
   245  		}
   246  		fmt.Fprintf(&asmSrc, funcAsm, i, align, strings.Repeat(filler, 2000))
   247  	}
   248  	goSrc.WriteString("\nvar fs = []func() int{")
   249  	for i := range n {
   250  		fmt.Fprintf(&goSrc, "f%d, ", i)
   251  	}
   252  	fmt.Fprintf(&goSrc, `}
   253  
   254  func main() {
   255  	s := 0
   256  	for _, f := range fs {
   257  		s += f()
   258  	}
   259  	if s != %d || sink != %d {
   260  		panic(s)
   261  	}
   262  }
   263  `, n*(n-1)/2, n)
   264  	for name, src := range map[string][]byte{
   265  		"go.mod":                         []byte("module overaligned\n"),
   266  		"main.go":                        goSrc.Bytes(),
   267  		"funcs_" + runtime.GOARCH + ".s": asmSrc.Bytes(),
   268  	} {
   269  		if err := os.WriteFile(filepath.Join(dir, name), src, 0o666); err != nil {
   270  			t.Fatal(err)
   271  		}
   272  	}
   273  
   274  	// Several split sizes, so the test doesn't depend on one boundary
   275  	// happening to land in the right place. Much smaller sizes exceed
   276  	// the Mach-O limit on sections in __TEXT.
   277  	for _, size := range []int{256 << 10, 320 << 10, 384 << 10} {
   278  		exe := filepath.Join(dir, fmt.Sprintf("split%d.exe", size))
   279  		cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe,
   280  			fmt.Sprintf("-ldflags=-linkmode=external -debugtextsize=%d", size))
   281  		cmd.Dir = dir
   282  		if out, err := cmd.CombinedOutput(); err != nil {
   283  			t.Fatalf("build with -debugtextsize=%d: %v\n%s", size, err, out)
   284  		}
   285  		if out, err := testenv.Command(t, exe).CombinedOutput(); err != nil {
   286  			t.Fatalf("run with -debugtextsize=%d: %v\n%s", size, err, out)
   287  		}
   288  	}
   289  }
   290  
   291  func TestWindowsBuildmodeCSharedASLR(t *testing.T) {
   292  	platform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
   293  	switch platform {
   294  	case "windows/amd64", "windows/386":
   295  	default:
   296  		t.Skip("skipping windows amd64/386 only test")
   297  	}
   298  
   299  	testenv.MustHaveCGO(t)
   300  
   301  	t.Run("aslr", func(t *testing.T) {
   302  		testWindowsBuildmodeCSharedASLR(t, true)
   303  	})
   304  	t.Run("no-aslr", func(t *testing.T) {
   305  		testWindowsBuildmodeCSharedASLR(t, false)
   306  	})
   307  }
   308  
   309  func TestWindowsBuildmodeCSharedTrailingDotOutput(t *testing.T) {
   310  	if runtime.GOOS != "windows" {
   311  		t.Skip("skipping windows only test")
   312  	}
   313  
   314  	t.Parallel()
   315  	testenv.MustHaveGoBuild(t)
   316  	testenv.MustHaveCGO(t)
   317  	testenv.MustHaveBuildMode(t, "c-shared")
   318  
   319  	dir := t.TempDir()
   320  	srcfile := filepath.Join(dir, "test.go")
   321  	objfile := filepath.Join(dir, "mypackage.")
   322  	linktmp := filepath.Join(dir, "linktmp")
   323  	if err := os.Mkdir(linktmp, 0777); err != nil {
   324  		t.Fatal(err)
   325  	}
   326  	if err := os.WriteFile(srcfile, []byte(`package main
   327  import "C"
   328  
   329  //export Hello
   330  func Hello() {}
   331  
   332  func main() {}
   333  `), 0666); err != nil {
   334  		t.Fatal(err)
   335  	}
   336  
   337  	argv := []string{"build", "-buildmode=c-shared", "-o", objfile, "-ldflags", "-tmpdir=" + linktmp, srcfile}
   338  	out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
   339  	if err != nil {
   340  		t.Fatalf("build failure: %s\n%s\n", err, string(out))
   341  	}
   342  
   343  	def, err := os.ReadFile(filepath.Join(linktmp, "export_file.def"))
   344  	if err != nil {
   345  		t.Fatal(err)
   346  	}
   347  	if want := []byte("LIBRARY \"mypackage.\"\n"); !bytes.HasPrefix(def, want) {
   348  		t.Fatalf("export_file.def begins with %q, want %q", def, want)
   349  	}
   350  }
   351  
   352  func testWindowsBuildmodeCSharedASLR(t *testing.T, useASLR bool) {
   353  	t.Parallel()
   354  	testenv.MustHaveGoBuild(t)
   355  
   356  	dir := t.TempDir()
   357  
   358  	srcfile := filepath.Join(dir, "test.go")
   359  	objfile := filepath.Join(dir, "test.dll")
   360  	if err := os.WriteFile(srcfile, []byte(`package main; func main() { print("hello") }`), 0666); err != nil {
   361  		t.Fatal(err)
   362  	}
   363  	argv := []string{"build", "-buildmode=c-shared"}
   364  	if !useASLR {
   365  		argv = append(argv, "-ldflags", "-aslr=false")
   366  	}
   367  	argv = append(argv, "-o", objfile, srcfile)
   368  	out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
   369  	if err != nil {
   370  		t.Fatalf("build failure: %s\n%s\n", err, string(out))
   371  	}
   372  
   373  	f, err := pe.Open(objfile)
   374  	if err != nil {
   375  		t.Fatal(err)
   376  	}
   377  	defer f.Close()
   378  	var dc uint16
   379  	switch oh := f.OptionalHeader.(type) {
   380  	case *pe.OptionalHeader32:
   381  		dc = oh.DllCharacteristics
   382  	case *pe.OptionalHeader64:
   383  		dc = oh.DllCharacteristics
   384  		hasHEVA := (dc & pe.IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) != 0
   385  		if useASLR && !hasHEVA {
   386  			t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag is not set")
   387  		} else if !useASLR && hasHEVA {
   388  			t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag should not be set")
   389  		}
   390  	default:
   391  		t.Fatalf("unexpected optional header type of %T", f.OptionalHeader)
   392  	}
   393  	hasASLR := (dc & pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) != 0
   394  	if useASLR && !hasASLR {
   395  		t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag is not set")
   396  	} else if !useASLR && hasASLR {
   397  		t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag should not be set")
   398  	}
   399  }
   400  
   401  // TestMemProfileCheck tests that cmd/link sets
   402  // runtime.disableMemoryProfiling if the runtime.MemProfile
   403  // symbol is unreachable after deadcode (and not dynlinking).
   404  // The runtime then uses that to set the default value of
   405  // runtime.MemProfileRate, which this test checks.
   406  func TestMemProfileCheck(t *testing.T) {
   407  	testenv.MustHaveGoBuild(t)
   408  	t.Parallel()
   409  
   410  	tests := []struct {
   411  		name    string
   412  		prog    string
   413  		wantOut string
   414  	}{
   415  		{
   416  			"no_memprofile",
   417  			`
   418  package main
   419  import "runtime"
   420  func main() {
   421  	println(runtime.MemProfileRate)
   422  }
   423  `,
   424  			"0",
   425  		},
   426  		{
   427  			"with_memprofile",
   428  			`
   429  package main
   430  import "runtime"
   431  func main() {
   432  	runtime.MemProfile(nil, false)
   433  	println(runtime.MemProfileRate)
   434  }
   435  `,
   436  			"524288",
   437  		},
   438  		{
   439  			"with_memprofile_indirect",
   440  			`
   441  package main
   442  import "runtime"
   443  var f = runtime.MemProfile
   444  func main() {
   445  	if f == nil {
   446  		panic("no f")
   447  	}
   448  	println(runtime.MemProfileRate)
   449  }
   450  `,
   451  			"524288",
   452  		},
   453  		{
   454  			"with_memprofile_runtime_pprof",
   455  			`
   456  package main
   457  import "runtime"
   458  import "runtime/pprof"
   459  func main() {
   460  	_ = pprof.Profiles()
   461  	println(runtime.MemProfileRate)
   462  }
   463  `,
   464  			"524288",
   465  		},
   466  		{
   467  			"with_memprofile_runtime_pprof_writeheap",
   468  			`
   469  package main
   470  import "io"
   471  import "runtime"
   472  import "runtime/pprof"
   473  func main() {
   474  	_ = pprof.WriteHeapProfile(io.Discard)
   475  	println(runtime.MemProfileRate)
   476  }
   477  `,
   478  			"524288",
   479  		},
   480  		{
   481  			"with_memprofile_runtime_pprof_lookupheap",
   482  			`
   483  package main
   484  import "runtime"
   485  import "runtime/pprof"
   486  func main() {
   487  	_ = pprof.Lookup("heap")
   488  	println(runtime.MemProfileRate)
   489  }
   490  `,
   491  			"524288",
   492  		},
   493  		{
   494  			"with_memprofile_http_pprof",
   495  			`
   496  package main
   497  import "runtime"
   498  import _ "net/http/pprof"
   499  func main() {
   500  	println(runtime.MemProfileRate)
   501  }
   502  `,
   503  			"524288",
   504  		},
   505  	}
   506  	for _, tt := range tests {
   507  		t.Run(tt.name, func(t *testing.T) {
   508  			t.Parallel()
   509  			tempDir := t.TempDir()
   510  			src := filepath.Join(tempDir, "x.go")
   511  			if err := os.WriteFile(src, []byte(tt.prog), 0644); err != nil {
   512  				t.Fatal(err)
   513  			}
   514  			cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src)
   515  			out, err := cmd.CombinedOutput()
   516  			if err != nil {
   517  				t.Fatal(err)
   518  			}
   519  			got := strings.TrimSpace(string(out))
   520  			if got != tt.wantOut {
   521  				t.Errorf("got %q; want %q", got, tt.wantOut)
   522  			}
   523  		})
   524  	}
   525  }
   526  
   527  func TestRISCVTrampolines(t *testing.T) {
   528  	// Don't build library for non-standard target in short mode.
   529  	if testing.Short() && (runtime.GOOS != "linux" || runtime.GOARCH != "riscv64") {
   530  		t.Skipf("skipping on %s/%s in short mode", runtime.GOOS, runtime.GOARCH)
   531  	}
   532  
   533  	testenv.MustHaveGoBuild(t)
   534  	t.Parallel()
   535  
   536  	tmpDir := t.TempDir()
   537  	tmpFile := filepath.Join(tmpDir, "x.s")
   538  
   539  	// Calling b from a or c should not use trampolines, however
   540  	// calling from d to a will require one.
   541  	buf := new(bytes.Buffer)
   542  	fmt.Fprintf(buf, "TEXT a(SB),$0-0\n")
   543  	for i := 0; i < 1<<17; i++ {
   544  		fmt.Fprintf(buf, "\tADD $0, X5, X0\n")
   545  	}
   546  	fmt.Fprintf(buf, "\tCALL b(SB)\n")
   547  	fmt.Fprintf(buf, "\tRET\n")
   548  	fmt.Fprintf(buf, "TEXT b(SB),$0-0\n")
   549  	fmt.Fprintf(buf, "\tRET\n")
   550  	fmt.Fprintf(buf, "TEXT c(SB),$0-0\n")
   551  	fmt.Fprintf(buf, "\tCALL b(SB)\n")
   552  	fmt.Fprintf(buf, "\tRET\n")
   553  	fmt.Fprintf(buf, "TEXT ·d(SB),0,$0-0\n")
   554  	for i := 0; i < 1<<17; i++ {
   555  		fmt.Fprintf(buf, "\tADD $0, X5, X0\n")
   556  	}
   557  	fmt.Fprintf(buf, "\tCALL a(SB)\n")
   558  	fmt.Fprintf(buf, "\tCALL c(SB)\n")
   559  	fmt.Fprintf(buf, "\tRET\n")
   560  	if err := os.WriteFile(tmpFile, buf.Bytes(), 0644); err != nil {
   561  		t.Fatalf("Failed to write assembly file: %v", err)
   562  	}
   563  
   564  	if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte("module riscvtramp"), 0644); err != nil {
   565  		t.Fatalf("Failed to write file: %v\n", err)
   566  	}
   567  	main := `package main
   568  func main() {
   569  	d()
   570  }
   571  
   572  func d()
   573  `
   574  	if err := os.WriteFile(filepath.Join(tmpDir, "x.go"), []byte(main), 0644); err != nil {
   575  		t.Fatalf("failed to write main: %v\n", err)
   576  	}
   577  	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal")
   578  	cmd.Dir = tmpDir
   579  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   580  	out, err := cmd.CombinedOutput()
   581  	if err != nil {
   582  		t.Fatalf("Build failed: %v, output: %s", err, out)
   583  	}
   584  
   585  	// Check what trampolines exist.
   586  	cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", filepath.Join(tmpDir, "riscvtramp"))
   587  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   588  	out, err = cmd.CombinedOutput()
   589  	if err != nil {
   590  		t.Fatalf("nm failure: %s\n%s\n", err, string(out))
   591  	}
   592  	if !bytes.Contains(out, []byte(" T a-tramp0")) {
   593  		t.Errorf("Trampoline a-tramp0 is missing")
   594  	}
   595  	if bytes.Contains(out, []byte(" T b-tramp0")) {
   596  		t.Errorf("Trampoline b-tramp0 exists unnecessarily")
   597  	}
   598  }
   599  
   600  func TestRounding(t *testing.T) {
   601  	testCases := []struct {
   602  		input    int64
   603  		quantum  int64
   604  		expected int64
   605  	}{
   606  		{0x30000000, 0x2000, 0x30000000}, // Already aligned
   607  		{0x30002000, 0x2000, 0x30002000}, // Exactly on boundary
   608  		{0x30001234, 0x2000, 0x30002000},
   609  		{0x30001000, 0x2000, 0x30002000},
   610  		{0x30001fff, 0x2000, 0x30002000},
   611  	}
   612  
   613  	for _, tc := range testCases {
   614  		result := Rnd(tc.input, tc.quantum)
   615  		if result != tc.expected {
   616  			t.Errorf("Rnd(0x%x, 0x%x) = 0x%x, expected 0x%x",
   617  				tc.input, tc.quantum, result, tc.expected)
   618  		}
   619  	}
   620  }
   621  

View as plain text