github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/builder/sizes_test.go (about)

     1  package builder
     2  
     3  import (
     4  	"runtime"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/tinygo-org/tinygo/compileopts"
     9  )
    10  
    11  var sema = make(chan struct{}, runtime.NumCPU())
    12  
    13  type sizeTest struct {
    14  	target     string
    15  	path       string
    16  	codeSize   uint64
    17  	rodataSize uint64
    18  	dataSize   uint64
    19  	bssSize    uint64
    20  }
    21  
    22  // Test whether code and data size is as expected for the given targets.
    23  // This tests both the logic of loadProgramSize and checks that code size
    24  // doesn't change unintentionally.
    25  //
    26  // If you find that code or data size is reduced, then great! You can reduce the
    27  // number in this test.
    28  // If you find that the code or data size is increased, take a look as to why
    29  // this is. It could be due to an update (LLVM version, Go version, etc) which
    30  // is fine, but it could also mean that a recent change introduced this size
    31  // increase. If so, please consider whether this new feature is indeed worth the
    32  // size increase for all users.
    33  func TestBinarySize(t *testing.T) {
    34  	if runtime.GOOS == "linux" && !hasBuiltinTools {
    35  		// Debian LLVM packages are modified a bit and tend to produce
    36  		// different machine code. Ideally we'd fix this (with some attributes
    37  		// or something?), but for now skip it.
    38  		t.Skip("Skip: using external LLVM version so binary size might differ")
    39  	}
    40  
    41  	// This is a small number of very diverse targets that we want to test.
    42  	tests := []sizeTest{
    43  		// microcontrollers
    44  		{"hifive1b", "examples/echo", 4476, 280, 0, 2252},
    45  		{"microbit", "examples/serial", 2724, 388, 8, 2256},
    46  		{"wioterminal", "examples/pininterrupt", 5996, 1484, 116, 6816},
    47  
    48  		// TODO: also check wasm. Right now this is difficult, because
    49  		// wasm binaries are run through wasm-opt and therefore the
    50  		// output varies by binaryen version.
    51  	}
    52  	for _, tc := range tests {
    53  		tc := tc
    54  		t.Run(tc.target+"/"+tc.path, func(t *testing.T) {
    55  			t.Parallel()
    56  
    57  			// Build the binary.
    58  			options := compileopts.Options{
    59  				Target:        tc.target,
    60  				Opt:           "z",
    61  				Semaphore:     sema,
    62  				InterpTimeout: 60 * time.Second,
    63  				Debug:         true,
    64  				VerifyIR:      true,
    65  			}
    66  			target, err := compileopts.LoadTarget(&options)
    67  			if err != nil {
    68  				t.Fatal("could not load target:", err)
    69  			}
    70  			config := &compileopts.Config{
    71  				Options: &options,
    72  				Target:  target,
    73  			}
    74  			result, err := Build(tc.path, "", t.TempDir(), config)
    75  			if err != nil {
    76  				t.Fatal("could not build:", err)
    77  			}
    78  
    79  			// Check whether the size of the binary matches the expected size.
    80  			sizes, err := loadProgramSize(result.Executable, nil)
    81  			if err != nil {
    82  				t.Fatal("could not read program size:", err)
    83  			}
    84  			if sizes.Code != tc.codeSize || sizes.ROData != tc.rodataSize || sizes.Data != tc.dataSize || sizes.BSS != tc.bssSize {
    85  				t.Errorf("Unexpected code size when compiling: -target=%s %s", tc.target, tc.path)
    86  				t.Errorf("            code rodata   data    bss")
    87  				t.Errorf("expected: %6d %6d %6d %6d", tc.codeSize, tc.rodataSize, tc.dataSize, tc.bssSize)
    88  				t.Errorf("actual:   %6d %6d %6d %6d", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS)
    89  			}
    90  		})
    91  	}
    92  }