gopls/telemetry: test that telemetry counters are written

Change-Id: Iceb8406cf3290180690f29bcba9f2fe6019285ad
Reviewed-on: https://go-review.googlesource.com/c/tools/+/517135
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/go.mod b/gopls/go.mod
index f28b5bd..1d6fdd3 100644
--- a/gopls/go.mod
+++ b/gopls/go.mod
@@ -10,7 +10,7 @@
 	golang.org/x/mod v0.12.0
 	golang.org/x/sync v0.3.0
 	golang.org/x/sys v0.11.0
-	golang.org/x/telemetry v0.0.0-20230728182230-e84a26264b60
+	golang.org/x/telemetry v0.0.0-20230808152233-a65b40c0fdb0
 	golang.org/x/text v0.12.0
 	golang.org/x/tools v0.6.0
 	golang.org/x/vuln v0.0.0-20230110180137-6ad3e3d07815
diff --git a/gopls/go.sum b/gopls/go.sum
index 3745b42..39157d6 100644
--- a/gopls/go.sum
+++ b/gopls/go.sum
@@ -77,6 +77,10 @@
 golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/telemetry v0.0.0-20230728182230-e84a26264b60 h1:OCiXqf7/gdoaS7dKppAtPxi783Ke/JIb+r20ZYGiEFg=
 golang.org/x/telemetry v0.0.0-20230728182230-e84a26264b60/go.mod h1:kO7uNSGGmqCHII6C0TYfaLwSBIfcyhj53//nu0+Fy4A=
+golang.org/x/telemetry v0.0.0-20230803164656-36ff770d3d6b h1:FZUooIb6Dx+mzx9n5mi6wmY/xpUZ4U1ffUVX1DCsuSs=
+golang.org/x/telemetry v0.0.0-20230803164656-36ff770d3d6b/go.mod h1:kO7uNSGGmqCHII6C0TYfaLwSBIfcyhj53//nu0+Fy4A=
+golang.org/x/telemetry v0.0.0-20230808152233-a65b40c0fdb0 h1:ZB9hzIbPBkRCCOVWOmfZEI5f6YiTbRAq6LK2x/StgiU=
+golang.org/x/telemetry v0.0.0-20230808152233-a65b40c0fdb0/go.mod h1:kO7uNSGGmqCHII6C0TYfaLwSBIfcyhj53//nu0+Fy4A=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
diff --git a/gopls/internal/bug/bug.go b/gopls/internal/bug/bug.go
index 7331ba8..7c290b0 100644
--- a/gopls/internal/bug/bug.go
+++ b/gopls/internal/bug/bug.go
@@ -65,7 +65,8 @@
 	report(description)
 }
 
-var bugReport = counter.NewStack("gopls/bug", 16)
+// BugReportCount is a telemetry counter that tracks # of bug reports.
+var BugReportCount = counter.NewStack("gopls/bug", 16)
 
 func report(description string) {
 	_, file, line, ok := runtime.Caller(2) // all exported reporting functions call report directly
@@ -102,7 +103,7 @@
 	mu.Unlock()
 
 	if newBug {
-		bugReport.Inc()
+		BugReportCount.Inc()
 	}
 	// Call the handlers outside the critical section since a
 	// handler may itself fail and call bug.Report. Since handlers
diff --git a/gopls/internal/telemetry/telemetry_test.go b/gopls/internal/telemetry/telemetry_test.go
new file mode 100644
index 0000000..7951a41
--- /dev/null
+++ b/gopls/internal/telemetry/telemetry_test.go
@@ -0,0 +1,82 @@
+// Copyright 2023 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.
+
+//go:build go1.21 && !openbsd && !js && !wasip1 && !solaris && !android && !386
+// +build go1.21,!openbsd,!js,!wasip1,!solaris,!android,!386
+
+package telemetry_test
+
+import (
+	"os"
+	"strconv"
+	"strings"
+	"testing"
+
+	"golang.org/x/telemetry/counter"
+	"golang.org/x/telemetry/counter/countertest" // requires go1.21+
+	"golang.org/x/tools/gopls/internal/bug"
+	"golang.org/x/tools/gopls/internal/hooks"
+	. "golang.org/x/tools/gopls/internal/lsp/regtest"
+)
+
+func TestMain(m *testing.M) {
+	tmp, err := os.MkdirTemp("", "gopls-telemetry-test")
+	if err != nil {
+		panic(err)
+	}
+	countertest.Open(tmp)
+	defer os.RemoveAll(tmp)
+	Main(m, hooks.Options)
+}
+
+func TestTelemetry(t *testing.T) {
+	var (
+		goversion = ""
+		editor    = "vscode" // We set ClientName("Visual Studio Code") below.
+	)
+
+	// Verify that a properly configured session gets notified of a bug on the
+	// server.
+	WithOptions(
+		Modes(Default), // must be in-process to receive the bug report below
+		Settings{"showBugReports": true},
+		ClientName("Visual Studio Code"),
+	).Run(t, "", func(t *testing.T, env *Env) {
+		goversion = strconv.Itoa(env.GoVersion())
+		const desc = "got a bug"
+		bug.Report(desc) // want a stack counter with the trace starting from here.
+		env.Await(ShownMessage(desc))
+	})
+
+	// gopls/editor:client
+	// gopls/goversion:1.x
+	for _, c := range []*counter.Counter{
+		counter.New("gopls/client:" + editor),
+		counter.New("gopls/goversion:1." + goversion),
+	} {
+		count, err := countertest.ReadCounter(c)
+		if err != nil || count != 1 {
+			t.Errorf("ReadCounter(%q) = (%v, %v), want (1, nil)", c.Name(), count, err)
+		}
+	}
+
+	// gopls/bug
+	bugcount := bug.BugReportCount
+	counts, err := countertest.ReadStackCounter(bugcount)
+	if err != nil {
+		t.Fatalf("ReadStackCounter(bugreportcount) failed - %v", err)
+	}
+	if len(counts) != 1 || !hasEntry(counts, t.Name(), 1) {
+		t.Errorf("read stackcounter(%q) = (%#v, %v), want one entry", "gopls/bug", counts, err)
+	}
+}
+
+func hasEntry(counts map[string]uint64, pattern string, want uint64) bool {
+	for k, v := range counts {
+		if strings.Contains(k, pattern) && v == want {
+			return true
+		}
+	}
+	return false
+}