blob: eeee431efdb5c8127974fa53381f9d319838a9f3 [file]
// 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 (
"bytes"
"regexp"
"runtime"
"testing"
"golang.org/x/net/html"
"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`)
}
}
})
}
// TestAssemblyMisquote exercises that compiler output is properly quoted.
func TestAssemblyMisquote(t *testing.T) {
const files = `
-- go.mod --
module example.com
-- a/a.go --
package a
func f() {
// The compiler rejects this addition. The error it prints may include
// the string literal but this should not cause the resulting HTML page
// to contain a <u> element, which would result in misformatting.
const _ = "</script><u>underline</u>" + 1
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("a/a.go")
loc := env.RegexpSearch("a/a.go", "const")
report := asmFor(t, env, loc)
doc, err := html.Parse(bytes.NewReader(report))
if err != nil {
t.Fatalf("html.Parse: %v", err)
}
for n := range doc.Descendants() {
if n.Type == html.ElementNode && n.Data == "u" {
t.Fatalf("unwanted <u> element in HTML document:\n%s", report)
}
}
})
}
// 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
}