go.starlark.net@v0.0.0-20231101134539-556fd59b42f6/starlark/int_test.go (about)

     1  // Copyright 2017 The Bazel 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 starlark
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"log"
    11  	"math"
    12  	"math/big"
    13  	"os"
    14  	"os/exec"
    15  	"runtime"
    16  	"strings"
    17  	"testing"
    18  )
    19  
    20  // TestIntOpts exercises integer arithmetic, especially at the boundaries.
    21  func TestIntOpts(t *testing.T) {
    22  	f := MakeInt64
    23  	left, right := big.NewInt(math.MinInt32), big.NewInt(math.MaxInt32)
    24  
    25  	for i, test := range []struct {
    26  		val  Int
    27  		want string
    28  	}{
    29  		// Add
    30  		{f(math.MaxInt32).Add(f(1)), "80000000"},
    31  		{f(math.MinInt32).Add(f(-1)), "-80000001"},
    32  		// Mul
    33  		{f(math.MaxInt32).Mul(f(math.MaxInt32)), "3fffffff00000001"},
    34  		{f(math.MinInt32).Mul(f(math.MinInt32)), "4000000000000000"},
    35  		{f(math.MaxUint32).Mul(f(math.MaxUint32)), "fffffffe00000001"},
    36  		{f(math.MinInt32).Mul(f(-1)), "80000000"},
    37  		// Div
    38  		{f(math.MinInt32).Div(f(-1)), "80000000"},
    39  		{f(1 << 31).Div(f(2)), "40000000"},
    40  		// And
    41  		{f(math.MaxInt32).And(f(math.MaxInt32)), "7fffffff"},
    42  		{f(math.MinInt32).And(f(math.MinInt32)), "-80000000"},
    43  		{f(1 << 33).And(f(1 << 32)), "0"},
    44  		// Mod
    45  		{f(1 << 32).Mod(f(2)), "0"},
    46  		// Or
    47  		{f(1 << 32).Or(f(0)), "100000000"},
    48  		{f(math.MaxInt32).Or(f(0)), "7fffffff"},
    49  		{f(math.MaxUint32).Or(f(0)), "ffffffff"},
    50  		{f(math.MinInt32).Or(f(math.MinInt32)), "-80000000"},
    51  		// Xor
    52  		{f(math.MinInt32).Xor(f(-1)), "7fffffff"},
    53  		// Not
    54  		{f(math.MinInt32).Not(), "7fffffff"},
    55  		{f(math.MaxInt32).Not(), "-80000000"},
    56  		// Shift
    57  		{f(1).Lsh(31), "80000000"},
    58  		{f(1).Lsh(32), "100000000"},
    59  		{f(math.MaxInt32 + 1).Rsh(1), "40000000"},
    60  		{f(math.MinInt32 * 2).Rsh(1), "-80000000"},
    61  	} {
    62  		if got := fmt.Sprintf("%x", test.val); got != test.want {
    63  			t.Errorf("%d equals %s, want %s", i, got, test.want)
    64  		}
    65  		small, big := test.val.get()
    66  		if small < math.MinInt32 || math.MaxInt32 < small {
    67  			t.Errorf("expected big, %d %s", i, test.val)
    68  		}
    69  		if big == nil {
    70  			continue
    71  		}
    72  		if small != 0 {
    73  			t.Errorf("expected 0 small, %d %s with %d", i, test.val, small)
    74  		}
    75  		if big.Cmp(left) >= 0 && big.Cmp(right) <= 0 {
    76  			t.Errorf("expected small, %d %s", i, test.val)
    77  		}
    78  	}
    79  }
    80  
    81  func TestImmutabilityMakeBigInt(t *testing.T) {
    82  	// use max int64 for the test
    83  	expect := int64(^uint64(0) >> 1)
    84  
    85  	mutint := big.NewInt(expect)
    86  	value := MakeBigInt(mutint)
    87  	mutint.Set(big.NewInt(1))
    88  
    89  	got, _ := value.Int64()
    90  	if got != expect {
    91  		t.Errorf("expected %d, got %d", expect, got)
    92  	}
    93  }
    94  
    95  func TestImmutabilityBigInt(t *testing.T) {
    96  	// use 1 and max int64 for the test
    97  	for _, expect := range []int64{1, int64(^uint64(0) >> 1)} {
    98  		value := MakeBigInt(big.NewInt(expect))
    99  
   100  		bigint := value.BigInt()
   101  		bigint.Set(big.NewInt(2))
   102  
   103  		got, _ := value.Int64()
   104  		if got != expect {
   105  			t.Errorf("expected %d, got %d", expect, got)
   106  		}
   107  	}
   108  }
   109  
   110  // TestIntFallback creates a small Int value in a child process with
   111  // limited address space to ensure that it still works, but prints a warning.
   112  func TestIntFallback(t *testing.T) {
   113  	if runtime.GOOS != "linux" {
   114  		t.Skipf("test disabled on this platform (requires ulimit -v)")
   115  	}
   116  	exe, err := os.Executable()
   117  	if err != nil {
   118  		t.Fatalf("can't find file name of executable: %v", err)
   119  	}
   120  	// ulimit -v limits the address space in KB. Not portable.
   121  	// 4GB is enough for the Go runtime but not for the optimization.
   122  	cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("ulimit -v 4000000 && %q --entry=intfallback", exe))
   123  	out, err := cmd.CombinedOutput()
   124  	if err != nil {
   125  		t.Fatalf("intfallback subcommand failed: %v\n%s", err, out)
   126  	}
   127  
   128  	// Check the warning was printed.
   129  	if !strings.Contains(string(out), "Integer performance may suffer") {
   130  		t.Errorf("expected warning was not printed. Output=<<%s>>", out)
   131  	}
   132  }
   133  
   134  // intfallback is called in a child process with limited address space.
   135  func intfallback() {
   136  	const want = 123
   137  	if got, _ := MakeBigInt(big.NewInt(want)).Int64(); got != want {
   138  		log.Fatalf("intfallback: got %d, want %d", got, want)
   139  	}
   140  }
   141  
   142  // The --entry flag invokes an alternate entry point, for use in subprocess tests.
   143  var testEntry = flag.String("entry", "", "child process entry-point")
   144  
   145  func TestMain(m *testing.M) {
   146  	// In some build systems, notably Blaze, flag.Parse is called before TestMain,
   147  	// in violation of the TestMain contract, making this second call a no-op.
   148  	flag.Parse()
   149  	switch *testEntry {
   150  	case "":
   151  		os.Exit(m.Run()) // normal case
   152  	case "intfallback":
   153  		intfallback()
   154  	default:
   155  		log.Fatalf("unknown entry point: %s", *testEntry)
   156  	}
   157  }