mirror of
https://github.com/burrowers/garble.git
synced 2026-04-22 15:47:04 +08:00
29356f30f7
In particular, internal/abi now has some actual code, so obfuscating those literals was breaking as expected. Document how to update the list in the future as well. The change above gets "go test" to just one test failure on: go version devel go1.18-578ada410d Tue Nov 9 22:58:24 2021 +0000 linux/amd64 We also move the doc about why we disable GarbleLiterals, so that it's next to where the disabling happens. While here, we also rename GarbleLiterals to ObfuscateLiterals, as we have been trying to move away from "to garble" as a verb. Finally, limit the verbosity of diffoscope. One test was failing for me, and diffoscope printed thousands of lines. Not particularly useful when I'm trying to skim test results. Usually, seeing a few dozen lines of output is enough. Updates #385.
332 lines
8.1 KiB
Go
332 lines
8.1 KiB
Go
// Copyright (c) 2019, The Garble Authors.
|
|
// See LICENSE for licensing information.
|
|
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/printer"
|
|
"go/token"
|
|
mathrand "math/rand"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/rogpeppe/go-internal/goproxytest"
|
|
"github.com/rogpeppe/go-internal/gotooltest"
|
|
"github.com/rogpeppe/go-internal/testscript"
|
|
|
|
ah "mvdan.cc/garble/internal/asthelper"
|
|
)
|
|
|
|
var proxyURL string
|
|
|
|
func TestMain(m *testing.M) {
|
|
os.Exit(testscript.RunMain(garbleMain{m}, map[string]func() int{
|
|
"garble": main1,
|
|
}))
|
|
}
|
|
|
|
type garbleMain struct {
|
|
m *testing.M
|
|
}
|
|
|
|
func (m garbleMain) Run() int {
|
|
// Start the Go proxy server running for all tests.
|
|
srv, err := goproxytest.NewServer("testdata/mod", "")
|
|
if err != nil {
|
|
panic(fmt.Sprintf("cannot start proxy: %v", err))
|
|
}
|
|
proxyURL = srv.URL
|
|
|
|
return m.m.Run()
|
|
}
|
|
|
|
var update = flag.Bool("u", false, "update testscript output files")
|
|
|
|
func TestScripts(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := testscript.Params{
|
|
Dir: filepath.Join("testdata", "scripts"),
|
|
Setup: func(env *testscript.Env) error {
|
|
env.Vars = append(env.Vars,
|
|
// Use testdata/mod as our module proxy.
|
|
"GOPROXY="+proxyURL,
|
|
|
|
// We use our own proxy, so avoid sum.golang.org.
|
|
"GONOSUMDB=*",
|
|
|
|
// "go build" starts many short-lived Go processes,
|
|
// such as asm, buildid, compile, and link.
|
|
// They don't allocate huge amounts of memory,
|
|
// and they'll exit within seconds,
|
|
// so using the GC is basically a waste of CPU.
|
|
// Turn it off entirely, releasing memory on exit.
|
|
//
|
|
// We don't want this setting always on,
|
|
// as it could result in memory problems for users.
|
|
// But it helps for our test suite,
|
|
// as the packages are relatively small.
|
|
"GOGC=off",
|
|
|
|
"gofullversion="+runtime.Version(),
|
|
)
|
|
|
|
if os.Getenv("TESTSCRIPT_COVER_DIR") != "" {
|
|
// Don't reuse the build cache if we want to collect
|
|
// code coverage. Otherwise, many toolexec calls would
|
|
// be avoided and the coverage would be incomplete.
|
|
env.Vars = append(env.Vars, "GOCACHE="+filepath.Join(env.WorkDir, "go-cache-tmp"))
|
|
}
|
|
return nil
|
|
},
|
|
Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
|
|
"binsubstr": binsubstr,
|
|
"bincmp": bincmp,
|
|
"generate-literals": generateLiterals,
|
|
},
|
|
UpdateScripts: *update,
|
|
}
|
|
if err := gotooltest.Setup(&p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testscript.Run(t, p)
|
|
}
|
|
|
|
func createFile(ts *testscript.TestScript, path string) *os.File {
|
|
file, err := os.Create(ts.MkAbs(path))
|
|
if err != nil {
|
|
ts.Fatalf("%v", err)
|
|
}
|
|
return file
|
|
}
|
|
|
|
func binsubstr(ts *testscript.TestScript, neg bool, args []string) {
|
|
if len(args) < 2 {
|
|
ts.Fatalf("usage: binsubstr file substr...")
|
|
}
|
|
data := ts.ReadFile(args[0])
|
|
var failed []string
|
|
for _, substr := range args[1:] {
|
|
match := strings.Contains(data, substr)
|
|
if match && neg {
|
|
failed = append(failed, substr)
|
|
} else if !match && !neg {
|
|
failed = append(failed, substr)
|
|
}
|
|
}
|
|
if len(failed) > 0 && neg {
|
|
ts.Fatalf("unexpected match for %q in %s", failed, args[0])
|
|
} else if len(failed) > 0 {
|
|
ts.Fatalf("expected match for %q in %s", failed, args[0])
|
|
}
|
|
}
|
|
|
|
func bincmp(ts *testscript.TestScript, neg bool, args []string) {
|
|
if len(args) != 2 {
|
|
ts.Fatalf("usage: bincmp file1 file2")
|
|
}
|
|
data1 := ts.ReadFile(args[0])
|
|
data2 := ts.ReadFile(args[1])
|
|
if neg {
|
|
if data1 == data2 {
|
|
ts.Fatalf("%s and %s don't differ", args[0], args[1])
|
|
}
|
|
return
|
|
}
|
|
if data1 != data2 {
|
|
if _, err := exec.LookPath("diffoscope"); err != nil {
|
|
ts.Logf("diffoscope is not installing; skipping binary diff")
|
|
} else {
|
|
// We'll error below; ignore the exec error here.
|
|
ts.Exec("diffoscope",
|
|
"--diff-context", "2", // down from 7 by default
|
|
"--max-text-report-size", "4096", // no limit (in bytes) by default; avoid huge output
|
|
ts.MkAbs(args[0]), ts.MkAbs(args[1]))
|
|
}
|
|
sizeDiff := len(data2) - len(data1)
|
|
ts.Fatalf("%s and %s differ; diffoscope above, size diff: %+d",
|
|
args[0], args[1], sizeDiff)
|
|
}
|
|
}
|
|
|
|
func generateStringLit(size int) *ast.BasicLit {
|
|
buffer := make([]byte, size)
|
|
_, err := mathrand.Read(buffer)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return ah.StringLit(string(buffer))
|
|
}
|
|
|
|
func generateLiterals(ts *testscript.TestScript, neg bool, args []string) {
|
|
if neg {
|
|
ts.Fatalf("unsupported: ! generate-literals")
|
|
}
|
|
if len(args) != 1 {
|
|
ts.Fatalf("usage: generate-literals file")
|
|
}
|
|
|
|
codePath := args[0]
|
|
|
|
// Add 100 randomly small literals.
|
|
var statements []ast.Stmt
|
|
for i := 0; i < 100; i++ {
|
|
literal := generateStringLit(1 + mathrand.Intn(255))
|
|
statements = append(statements, &ast.AssignStmt{
|
|
Lhs: []ast.Expr{ast.NewIdent("_")},
|
|
Tok: token.ASSIGN,
|
|
Rhs: []ast.Expr{literal},
|
|
})
|
|
}
|
|
// Add 5 huge literals, to make sure we don't try to obfuscate them.
|
|
// 5 * 128KiB is large enough that it would take a very, very long time
|
|
// to obfuscate those literals with our simple code.
|
|
for i := 0; i < 5; i++ {
|
|
literal := generateStringLit(128 << 10)
|
|
statements = append(statements, &ast.AssignStmt{
|
|
Lhs: []ast.Expr{ast.NewIdent("_")},
|
|
Tok: token.ASSIGN,
|
|
Rhs: []ast.Expr{literal},
|
|
})
|
|
}
|
|
|
|
file := &ast.File{
|
|
Name: ast.NewIdent("main"),
|
|
Decls: []ast.Decl{&ast.FuncDecl{
|
|
Name: ast.NewIdent("extraLiterals"),
|
|
Type: &ast.FuncType{Params: &ast.FieldList{}},
|
|
Body: ah.BlockStmt(statements...),
|
|
}},
|
|
}
|
|
|
|
codeFile := createFile(ts, codePath)
|
|
defer codeFile.Close()
|
|
|
|
if err := printer.Fprint(codeFile, token.NewFileSet(), file); err != nil {
|
|
ts.Fatalf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestSplitFlagsFromArgs(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
want [2][]string
|
|
}{
|
|
{"Empty", []string{}, [2][]string{{}, nil}},
|
|
{
|
|
"JustFlags",
|
|
[]string{"-foo", "bar", "-baz"},
|
|
[2][]string{{"-foo", "bar", "-baz"}, nil},
|
|
},
|
|
{
|
|
"JustArgs",
|
|
[]string{"some", "pkgs"},
|
|
[2][]string{{}, {"some", "pkgs"}},
|
|
},
|
|
{
|
|
"FlagsAndArgs",
|
|
[]string{"-foo=bar", "baz"},
|
|
[2][]string{{"-foo=bar"}, {"baz"}},
|
|
},
|
|
{
|
|
"BoolFlagsAndArgs",
|
|
[]string{"-race", "pkg"},
|
|
[2][]string{{"-race"}, {"pkg"}},
|
|
},
|
|
{
|
|
"ExplicitBoolFlag",
|
|
[]string{"-race=true", "pkg"},
|
|
[2][]string{{"-race=true"}, {"pkg"}},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
flags, args := splitFlagsFromArgs(test.args)
|
|
got := [2][]string{flags, args}
|
|
|
|
if diff := cmp.Diff(test.want, got); diff != "" {
|
|
t.Fatalf("splitFlagsFromArgs(%q) mismatch (-want +got):\n%s", test.args, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFilterBuildFlags(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
flags []string
|
|
want []string
|
|
}{
|
|
{"Empty", []string{}, nil},
|
|
{
|
|
"NoBuild",
|
|
[]string{"-short", "-json"},
|
|
nil,
|
|
},
|
|
{
|
|
"Mixed",
|
|
[]string{"-short", "-tags", "foo", "-mod=readonly", "-json"},
|
|
[]string{"-tags", "foo", "-mod=readonly"},
|
|
},
|
|
{
|
|
"NonBinarySkipped",
|
|
[]string{"-o", "binary", "-tags", "foo"},
|
|
[]string{"-tags", "foo"},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got, _ := filterBuildFlags(test.flags)
|
|
|
|
if diff := cmp.Diff(test.want, got); diff != "" {
|
|
t.Fatalf("filterBuildFlags(%q) mismatch (-want +got):\n%s", test.flags, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFlagValue(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
flags []string
|
|
flagName string
|
|
want string
|
|
}{
|
|
{"StrSpace", []string{"-buildid", "bar"}, "-buildid", "bar"},
|
|
{"StrSpaceDash", []string{"-buildid", "-bar"}, "-buildid", "-bar"},
|
|
{"StrEqual", []string{"-buildid=bar"}, "-buildid", "bar"},
|
|
{"StrEqualDash", []string{"-buildid=-bar"}, "-buildid", "-bar"},
|
|
{"StrMissing", []string{"-foo"}, "-buildid", ""},
|
|
{"StrNotFollowed", []string{"-buildid"}, "-buildid", ""},
|
|
{"StrEmpty", []string{"-buildid="}, "-buildid", ""},
|
|
}
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got := flagValue(test.flags, test.flagName)
|
|
if got != test.want {
|
|
t.Fatalf("flagValue(%q, %q) got %q, want %q",
|
|
test.flags, test.flagName, got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|