gopls/internal/cmd: add tests of 'gopls help'

This CL turns tests that call tool.Run (which includes
only a part of the initialization process and is not
a reliable simulation of the gopls executable)
into true CLI integration tests.

These missing tests would have caught regressions in recent work
that was rolled back before the release. (See CL 793600.)

Also, use correct URL in help message.

For golang/go#80133

Change-Id: I985b48e7b44d74e4d481172a2cbe5e9b5804984b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/794300
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alex Putman <aputman@golang.org>
diff --git a/gopls/internal/cmd/cmd.go b/gopls/internal/cmd/cmd.go
index 3ccb9d5..314e20e 100644
--- a/gopls/internal/cmd/cmd.go
+++ b/gopls/internal/cmd/cmd.go
@@ -132,7 +132,7 @@
 
 For documentation of all its features, see:
 
-   https://github.com/golang/tools/blob/master/gopls/doc/features
+   https://go.dev/gopls/features
 
 Usage:
   gopls help [<subject>]
diff --git a/gopls/internal/cmd/help_test.go b/gopls/internal/cmd/help_test.go
index 6cea8bc..43f9daf 100644
--- a/gopls/internal/cmd/help_test.go
+++ b/gopls/internal/cmd/help_test.go
@@ -13,41 +13,40 @@
 //go:generate go test -run Help -update-help-files
 
 import (
-	"bytes"
-	"context"
 	"flag"
 	"os"
 	"path/filepath"
+	"strings"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/tools/gopls/internal/cmd"
-	"golang.org/x/tools/gopls/internal/tool"
 	"golang.org/x/tools/internal/testenv"
 )
 
 var updateHelpFiles = flag.Bool("update-help-files", false, "Write out the help files instead of checking them")
 
-const appName = "gopls"
-
 func TestHelpFiles(t *testing.T) {
 	testenv.NeedsGoBuild(t) // This is a lie. We actually need the source code.
+	t.Parallel()
 	app := cmd.New()
-	ctx := context.Background()
-	for _, page := range append(app.Commands(), app) {
-		t.Run(page.Name(), func(t *testing.T) {
-			var buf bytes.Buffer
-			s := flag.NewFlagSet(page.Name(), flag.ContinueOnError)
-			s.SetOutput(&buf)
-			tool.Run(ctx, s, page, []string{"-h"}) // ignore error
-			name := page.Name()
-			if name == appName {
+	tree := writeTree(t, "")
+	for _, cmd := range append(app.Commands(), app) {
+		name := cmd.Name()
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			args := []string{name, "-h"}
+			// The output of 'gopls -h' is in usage.hlp
+			if cmd == app {
+				args = args[1:]
 				name = "usage"
 			}
+			res := gopls(t, tree, args...)
+			res.checkExit(true) // -h should result in exit 0
+			got := res.stderr
 			helpFile := filepath.Join("usage", name+".hlp")
-			got := buf.Bytes()
 			if *updateHelpFiles {
-				if err := os.WriteFile(helpFile, got, 0666); err != nil {
+				if err := os.WriteFile(helpFile, []byte(got), 0666); err != nil {
 					t.Errorf("Failed writing %v: %v", helpFile, err)
 				}
 				return
@@ -56,26 +55,22 @@
 			if err != nil {
 				t.Fatalf("Missing help file %q", helpFile)
 			}
-			if diff := cmp.Diff(string(want), string(got)); diff != "" {
+			if diff := cmp.Diff(string(want), got); diff != "" {
 				t.Errorf("Help file %q did not match, run with -update-help-files to fix (-want +got)\n%s", helpFile, diff)
 			}
 		})
 	}
 }
-
 func TestVerboseHelp(t *testing.T) {
 	testenv.NeedsGoBuild(t) // This is a lie. We actually need the source code.
-	app := cmd.New()
-	ctx := context.Background()
-	var buf bytes.Buffer
-	s := flag.NewFlagSet(appName, flag.ContinueOnError)
-	s.SetOutput(&buf)
-	tool.Run(ctx, s, app, []string{"-v", "-h"}) // ignore error
-	got := buf.Bytes()
-
+	t.Parallel()
+	tree := writeTree(t, "")
+	res := gopls(t, tree, "-v", "-h")
+	res.checkExit(true) // -h should result in exit 0
+	got := res.stderr
 	helpFile := filepath.Join("usage", "usage-v.hlp")
 	if *updateHelpFiles {
-		if err := os.WriteFile(helpFile, got, 0666); err != nil {
+		if err := os.WriteFile(helpFile, []byte(got), 0666); err != nil {
 			t.Errorf("Failed writing %v: %v", helpFile, err)
 		}
 		return
@@ -84,7 +79,71 @@
 	if err != nil {
 		t.Fatalf("Missing help file %q", helpFile)
 	}
-	if diff := cmp.Diff(string(want), string(got)); diff != "" {
+	if diff := cmp.Diff(string(want), got); diff != "" {
 		t.Errorf("Help file %q did not match, run with -update-help-files to fix (-want +got)\n%s", helpFile, diff)
 	}
 }
+
+// TestHelpTree tests "gopls help" on a number
+// of levels of the commmand tree.
+func TestHelpTree(t *testing.T) {
+	t.Parallel()
+
+	tree := writeTree(t, ``)
+
+	for _, test := range []struct {
+		args         []string
+		wantSuccess  bool
+		wantPatterns []string
+	}{
+		// gopls help
+		{
+			args:        []string{"help"},
+			wantSuccess: true,
+			wantPatterns: []string{
+				"gopls is a Go language server",
+				"https://go.dev/gopls/features",
+				"Usage:",
+				"Command:",
+				"  links.*list links in a file", // command menu
+			},
+		},
+		// gopls help remote
+		{
+			args:        []string{"help", "remote"},
+			wantSuccess: true,
+			wantPatterns: []string{
+				"interact with the gopls daemon",
+				"Usage:",
+				"Subcommand:",
+				"  sessions.*print information about current gopls sessions", // subcommand menu
+			},
+		},
+		// gopls help remote sessions
+		{
+			args:        []string{"help", "remote", "sessions"},
+			wantSuccess: true,
+			wantPatterns: []string{
+				"print information about current gopls sessions",
+				"Usage:",
+				"list sessions for the default daemon",
+			},
+		},
+		// gopls help remote nonesuch
+		{
+			args: []string{"help", "remote", "nonesuch"},
+			wantPatterns: []string{
+				"gopls: no such subcommand: remote nonesuch",
+			},
+		},
+	} {
+		t.Run(strings.Join(test.args, " "), func(t *testing.T) {
+			res := gopls(t, tree, test.args...)
+			res.checkExit(test.wantSuccess)
+			res.checkStdout("^$") // no stdout
+			for _, pattern := range test.wantPatterns {
+				res.checkStderr(pattern)
+			}
+		})
+	}
+}
diff --git a/gopls/internal/cmd/usage/usage-v.hlp b/gopls/internal/cmd/usage/usage-v.hlp
index 435b397..2cf77ba 100644
--- a/gopls/internal/cmd/usage/usage-v.hlp
+++ b/gopls/internal/cmd/usage/usage-v.hlp
@@ -7,7 +7,7 @@
 
 For documentation of all its features, see:
 
-   https://github.com/golang/tools/blob/master/gopls/doc/features
+   https://go.dev/gopls/features
 
 Usage:
   gopls help [<subject>]
diff --git a/gopls/internal/cmd/usage/usage.hlp b/gopls/internal/cmd/usage/usage.hlp
index 73b86dc..c117ee8 100644
--- a/gopls/internal/cmd/usage/usage.hlp
+++ b/gopls/internal/cmd/usage/usage.hlp
@@ -7,7 +7,7 @@
 
 For documentation of all its features, see:
 
-   https://github.com/golang/tools/blob/master/gopls/doc/features
+   https://go.dev/gopls/features
 
 Usage:
   gopls help [<subject>]