blob: b11c407b7d9a9993ab1e6e8706a3ef64231fa578 [file] [log] [blame] [edit]
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package web_test
import (
"regexp"
"runtime"
"testing"
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/gopls/internal/settings"
. "golang.org/x/tools/gopls/internal/test/integration"
"golang.org/x/tools/internal/testenv"
)
// TestAssembly is a basic test of the web-based assembly listing.
func TestAssembly(t *testing.T) {
testenv.NeedsGoCommand1Point(t, 22) // for up-to-date assembly listing
const files = `
-- go.mod --
module example.com
-- a/a.go --
package a
func f(x int) int {
println("hello")
defer println("world")
return x
}
func g() {
println("goodbye")
}
var v = [...]int{
f(123),
f(456),
}
func init() {
f(789)
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("a/a.go")
// Get the report and do some minimal checks for sensible results.
//
// Use only portable instructions below! Remember that
// this is a test of plumbing, not compilation, so
// it's better to skip the tests, rather than refine
// them, on any architecture that gives us trouble
// (e.g. uses JAL for CALL, or BL<cc> for RET).
// We conservatively test only on the two most popular
// architectures.
{
loc := env.RegexpSearch("a/a.go", "println")
report := asmFor(t, env, loc)
checkMatch(t, true, report, `TEXT.*example.com/a.f`)
switch runtime.GOARCH {
case "amd64", "arm64":
checkMatch(t, true, report, `CALL runtime.printlock`)
checkMatch(t, true, report, `CALL runtime.printstring`)
checkMatch(t, true, report, `CALL runtime.printunlock`)
checkMatch(t, true, report, `CALL example.com/a.f.deferwrap`)
checkMatch(t, true, report, `RET`)
checkMatch(t, true, report, `CALL runtime.morestack_noctxt`)
}
// Nested functions are also shown.
//
// The condition here was relaxed to unblock go.dev/cl/639515.
checkMatch(t, true, report, `example.com/a.f.deferwrap`)
// But other functions are not.
checkMatch(t, false, report, `TEXT.*example.com/a.g`)
}
// Check that code in a package-level var initializer is found too.
{
loc := env.RegexpSearch("a/a.go", `f\(123\)`)
report := asmFor(t, env, loc)
switch runtime.GOARCH {
case "amd64", "arm64":
checkMatch(t, true, report, `TEXT.*example.com/a.init`)
checkMatch(t, true, report, `MOV.? \$123`)
checkMatch(t, true, report, `MOV.? \$456`)
checkMatch(t, true, report, `CALL example.com/a.f`)
}
}
// And code in a source-level init function.
{
loc := env.RegexpSearch("a/a.go", `f\(789\)`)
report := asmFor(t, env, loc)
switch runtime.GOARCH {
case "amd64", "arm64":
checkMatch(t, true, report, `TEXT.*example.com/a.init`)
checkMatch(t, true, report, `MOV.? \$789`)
checkMatch(t, true, report, `CALL example.com/a.f`)
}
}
})
}
// TestTestAssembly exercises assembly listing of tests.
func TestTestAssembly(t *testing.T) {
testenv.NeedsGoCommand1Point(t, 22) // for up-to-date assembly listing
const files = `
-- go.mod --
module example.com
-- a/a_test.go --
package a
import "testing"
func Test1(*testing.T) { println(0) }
-- a/a_x_test.go --
package a_test
import "testing"
func Test2(*testing.T) { println(0) }
`
Run(t, files, func(t *testing.T, env *Env) {
for _, test := range []struct {
filename, symbol string
}{
{"a/a_test.go", "example.com/a.Test1"},
{"a/a_x_test.go", "example.com/a_test.Test2"},
} {
env.OpenFile(test.filename)
loc := env.RegexpSearch(test.filename, `println`)
report := asmFor(t, env, loc)
checkMatch(t, true, report, `TEXT.*`+regexp.QuoteMeta(test.symbol))
switch runtime.GOARCH {
case "amd64", "arm64":
checkMatch(t, true, report, `CALL runtime.printint`)
}
}
})
}
// asmFor returns the HTML document served by gopls for a "Browse assembly"
// command at the specified location in an open file.
func asmFor(t *testing.T, env *Env, loc protocol.Location) []byte {
_, content := codeActionWebPage(t, env, settings.GoAssembly, loc)
return content
}