mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-29 08:49:04 +00:00
builder: convert TestBinarySize to a golden file test
This commit is contained in:
+121
-33
@@ -1,8 +1,13 @@
|
|||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,21 +16,19 @@ import (
|
|||||||
|
|
||||||
var sema = make(chan struct{}, runtime.NumCPU())
|
var sema = make(chan struct{}, runtime.NumCPU())
|
||||||
|
|
||||||
|
var flagUpdate = flag.Bool("update", false, "update builder package tests")
|
||||||
|
|
||||||
type sizeTest struct {
|
type sizeTest struct {
|
||||||
target string
|
target string
|
||||||
path string
|
path string
|
||||||
codeSize uint64
|
|
||||||
rodataSize uint64
|
|
||||||
dataSize uint64
|
|
||||||
bssSize uint64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test whether code and data size is as expected for the given targets.
|
// Test whether code and data size is as expected for the given targets.
|
||||||
// This tests both the logic of loadProgramSize and checks that code size
|
// This tests both the logic of loadProgramSize and checks that code size
|
||||||
// doesn't change unintentionally.
|
// doesn't change unintentionally.
|
||||||
//
|
//
|
||||||
// If you find that code or data size is reduced, then great! You can reduce the
|
// If you find that code or data size is reduced, then great! You can update the
|
||||||
// number in this test.
|
// golden file by passing -update to the test.
|
||||||
// If you find that the code or data size is increased, take a look as to why
|
// If you find that the code or data size is increased, take a look as to why
|
||||||
// this is. It could be due to an update (LLVM version, Go version, etc) which
|
// this is. It could be due to an update (LLVM version, Go version, etc) which
|
||||||
// is fine, but it could also mean that a recent change introduced this size
|
// is fine, but it could also mean that a recent change introduced this size
|
||||||
@@ -42,34 +45,110 @@ func TestBinarySize(t *testing.T) {
|
|||||||
// This is a small number of very diverse targets that we want to test.
|
// This is a small number of very diverse targets that we want to test.
|
||||||
tests := []sizeTest{
|
tests := []sizeTest{
|
||||||
// microcontrollers
|
// microcontrollers
|
||||||
{"hifive1b", "examples/echo", 4321, 323, 0, 2268},
|
{"hifive1b", "examples/echo"},
|
||||||
{"microbit", "examples/serial", 2842, 382, 8, 2264},
|
{"microbit", "examples/serial"},
|
||||||
{"wioterminal", "examples/pininterrupt", 8039, 1665, 132, 7496},
|
{"wioterminal", "examples/pininterrupt"},
|
||||||
|
|
||||||
// TODO: also check wasm. Right now this is difficult, because
|
// TODO: also check wasm. Right now this is difficult, because
|
||||||
// wasm binaries are run through wasm-opt and therefore the
|
// wasm binaries are run through wasm-opt and therefore the
|
||||||
// output varies by binaryen version.
|
// output varies by binaryen version.
|
||||||
}
|
}
|
||||||
for _, tc := range tests {
|
sizes := measureBinarySizes(t, tests)
|
||||||
t.Run(tc.target+"/"+tc.path, func(t *testing.T) {
|
output := formatSizeTable(tests, sizes)
|
||||||
t.Parallel()
|
checkGolden(t, "testdata/binary-size.txt", output)
|
||||||
|
}
|
||||||
|
|
||||||
// Build the binary.
|
func checkGolden(t *testing.T, path, actual string) {
|
||||||
result := buildBinary(t, tc.target, tc.path)
|
t.Helper()
|
||||||
|
if *flagUpdate {
|
||||||
// Check whether the size of the binary matches the expected size.
|
if err := os.WriteFile(path, []byte(actual), 0o666); err != nil {
|
||||||
sizes, err := loadProgramSize(result.Executable, nil)
|
t.Fatal("failed to write updated golden file:", err)
|
||||||
if err != nil {
|
}
|
||||||
t.Fatal("could not read program size:", err)
|
return
|
||||||
}
|
|
||||||
if sizes.Code != tc.codeSize || sizes.ROData != tc.rodataSize || sizes.Data != tc.dataSize || sizes.BSS != tc.bssSize {
|
|
||||||
t.Errorf("Unexpected code size when compiling: -target=%s %s", tc.target, tc.path)
|
|
||||||
t.Errorf(" code rodata data bss")
|
|
||||||
t.Errorf("expected: %6d %6d %6d %6d", tc.codeSize, tc.rodataSize, tc.dataSize, tc.bssSize)
|
|
||||||
t.Errorf("actual: %6d %6d %6d %6d", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
expected, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("failed to read golden file:", err)
|
||||||
|
}
|
||||||
|
if actual != string(expected) {
|
||||||
|
t.Errorf("%s does not match expected output (re-run with -update to regenerate):\nexpected:\n%sactual:\n%s", path, expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func measureBinarySizes(t *testing.T, tests []sizeTest) []*programSize {
|
||||||
|
t.Helper()
|
||||||
|
type result struct {
|
||||||
|
index int
|
||||||
|
size *programSize
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make(chan result, len(tests))
|
||||||
|
for i, tc := range tests {
|
||||||
|
tmpdir := t.TempDir()
|
||||||
|
go func() {
|
||||||
|
size, err := measureBinarySize(tc, tmpdir)
|
||||||
|
results <- result{i, size, err}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes := make([]*programSize, len(tests))
|
||||||
|
failed := false
|
||||||
|
for range tests {
|
||||||
|
result := <-results
|
||||||
|
if result.err != nil {
|
||||||
|
tc := tests[result.index]
|
||||||
|
t.Errorf("%s/%s: %v", tc.target, tc.path, result.err)
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
sizes[result.index] = result.size
|
||||||
|
}
|
||||||
|
if failed {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
return sizes
|
||||||
|
}
|
||||||
|
|
||||||
|
func measureBinarySize(tc sizeTest, tmpdir string) (*programSize, error) {
|
||||||
|
result, err := buildBinaryInDir(tc.target, tc.path, tmpdir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
size, err := loadProgramSize(result.Executable, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read program size: %w", err)
|
||||||
|
}
|
||||||
|
return size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatSizeTable(tests []sizeTest, sizes []*programSize) string {
|
||||||
|
targetWidth := len("target")
|
||||||
|
packageWidth := len("package")
|
||||||
|
codeWidth := len("code")
|
||||||
|
rodataWidth := len("rodata")
|
||||||
|
dataWidth := len("data")
|
||||||
|
bssWidth := len("bss")
|
||||||
|
for i, tc := range tests {
|
||||||
|
targetWidth = max(targetWidth, len(tc.target))
|
||||||
|
packageWidth = max(packageWidth, len(tc.path))
|
||||||
|
codeWidth = max(codeWidth, len(strconv.FormatUint(sizes[i].Code, 10)))
|
||||||
|
rodataWidth = max(rodataWidth, len(strconv.FormatUint(sizes[i].ROData, 10)))
|
||||||
|
dataWidth = max(dataWidth, len(strconv.FormatUint(sizes[i].Data, 10)))
|
||||||
|
bssWidth = max(bssWidth, len(strconv.FormatUint(sizes[i].BSS, 10)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var output strings.Builder
|
||||||
|
fmt.Fprintf(&output, "%-*s %-*s %*s %*s %*s %*s\n",
|
||||||
|
targetWidth, "target", packageWidth, "package",
|
||||||
|
codeWidth, "code", rodataWidth, "rodata", dataWidth, "data", bssWidth, "bss")
|
||||||
|
for i, tc := range tests {
|
||||||
|
size := sizes[i]
|
||||||
|
fmt.Fprintf(&output, "%-*s %-*s %*d %*d %*d %*d\n",
|
||||||
|
targetWidth, tc.target, packageWidth, tc.path,
|
||||||
|
codeWidth, size.Code, rodataWidth, size.ROData,
|
||||||
|
dataWidth, size.Data, bssWidth, size.BSS)
|
||||||
|
}
|
||||||
|
return output.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the -size=full flag attributes binary size to the correct package
|
// Check that the -size=full flag attributes binary size to the correct package
|
||||||
@@ -114,6 +193,15 @@ func TestSizeFull(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
|
func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
|
||||||
|
t.Helper()
|
||||||
|
result, err := buildBinaryInDir(targetString, pkgName, t.TempDir())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildBinaryInDir(targetString, pkgName, tmpdir string) (BuildResult, error) {
|
||||||
options := compileopts.Options{
|
options := compileopts.Options{
|
||||||
Target: targetString,
|
Target: targetString,
|
||||||
Opt: "z",
|
Opt: "z",
|
||||||
@@ -124,15 +212,15 @@ func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
|
|||||||
}
|
}
|
||||||
target, err := compileopts.LoadTarget(&options)
|
target, err := compileopts.LoadTarget(&options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("could not load target:", err)
|
return BuildResult{}, fmt.Errorf("could not load target: %w", err)
|
||||||
}
|
}
|
||||||
config := &compileopts.Config{
|
config := &compileopts.Config{
|
||||||
Options: &options,
|
Options: &options,
|
||||||
Target: target,
|
Target: target,
|
||||||
}
|
}
|
||||||
result, err := Build(pkgName, "", t.TempDir(), config)
|
result, err := Build(pkgName, "", tmpdir, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("could not build:", err)
|
return BuildResult{}, fmt.Errorf("could not build: %w", err)
|
||||||
}
|
}
|
||||||
return result
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
target package code rodata data bss
|
||||||
|
hifive1b examples/echo 4321 323 0 2268
|
||||||
|
microbit examples/serial 2842 382 8 2264
|
||||||
|
wioterminal examples/pininterrupt 8039 1665 132 7496
|
||||||
Reference in New Issue
Block a user