blob: 769fb7ab35a758e8362e49f316e0ba12a691ce07 [file] [log] [blame]
// 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.
package main
import (
"bytes"
"context"
_ "embed"
"flag"
"io"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"golang.org/x/telemetry/godev/internal/config"
tconfig "golang.org/x/telemetry/internal/config"
"golang.org/x/telemetry/internal/telemetry"
"golang.org/x/telemetry/internal/testenv"
)
func TestMain(m *testing.M) {
if !canRunGoDevModuleTests() {
return
}
os.Exit(m.Run())
}
// First class ports, https://go.dev/wiki/PortingPolicy, excluding 386 and arm.
var onSupportedPlatform = map[string]bool{
"darwin/amd64": true,
"darwin/arm64": true,
"linux/amd64": true,
"linux/arm64": true,
"windows/amd64": true,
}
// canRunGoDevModuleTests returns whether the current test environment
// is suitable for golang.org/x/telemetry/godev module tests.
func canRunGoDevModuleTests() bool {
// Even though telemetry.go.dev runs on linux,
// we should be still able to debug and test telemetry.go.dev on
// contributors' machines locally.
if goosarch := runtime.GOOS + "/" + runtime.GOARCH; !onSupportedPlatform[goosarch] {
return false
}
// Must be able to run 'go'.
if err := testenv.HasGo(); err != nil {
return false
}
// Our tests must run from the repository source, not from module cache.
// Check golang.org/x/telemetry directory is accessible and has go.mod and config/config.json.
output, err := exec.Command("go", "list", "-f", "{{.Dir}}", "golang.org/x/telemetry").Output()
if err != nil {
return false
}
xTelemetryDir := string(bytes.TrimSpace(output))
if xTelemetryDir == "" {
return false
}
if _, err := os.Stat(filepath.Join(xTelemetryDir, "go.mod")); err != nil {
return false
}
// config/config.json is in the golang.org/x/telemetry/config module, so
// this doesn't hold from e.g. GOMODCACHE.
if _, err := os.Stat(filepath.Join(xTelemetryDir, "config", "config.json")); err != nil {
return false
}
return true
}
// If telemetry_url is configured, TestPaths may be used as a basic push test.
var telemetryURL = flag.String("telemetry_url", "", "url of the telemetry instance to test")
func TestPaths(t *testing.T) {
rootURL := *telemetryURL
if rootURL == "" {
ctx := context.Background()
cfg := config.NewConfig()
cfg.LocalStorage = t.TempDir()
cfg.ProjectID = "" // defensive: don't use a real project ID for tests.
// NewConfig assumes that the command is run from the repo root, but tests
// run from their test directory. We should fix this, but for now just
// fix up the config path.
// TODO(rfindley): fix this.
cfg.UploadConfig = filepath.Join("..", "..", "..", "config", "config.json")
handler := newHandler(ctx, cfg)
ts := httptest.NewServer(handler)
defer ts.Close()
rootURL = ts.URL
}
tests := []struct {
method string
path string
body string
code int
fragments []string
}{
{"GET", "/", "", 200, []string{"Go Telemetry"}},
{"GET", "/privacy", "", 200, []string{"Privacy Policy"}},
{"GET", "/config", "", 200, []string{"Chart Config"}},
{
"POST",
"/upload/2023-01-01/123.json",
`{"Week":"2023-01-01","LastWeek":"2022-12-25","X":0.123,"Programs":null,"Config":"v0.0.0-20230822160736-17171dbf1d76"}`,
200,
nil, // the body returned by /upload doesn't matter
},
}
for _, test := range tests {
t.Run(test.method+" "+test.path, func(t *testing.T) {
url := strings.TrimRight(rootURL, "/") + test.path
r := strings.NewReader(test.body)
req, err := http.NewRequest(test.method, url, r)
if err != nil {
t.Fatalf("NewRequest failed: %v", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != test.code {
t.Errorf("status code = %d, want %d", resp.StatusCode, test.code)
}
content, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("reading body: %v", err)
}
for _, fragment := range test.fragments {
if !bytes.Contains(content, []byte(fragment)) {
t.Errorf("missing fragment %q", fragment)
}
}
})
}
}
func TestValidate(t *testing.T) {
cfg, err := tconfig.ReadConfig("testdata/config.json")
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
report *telemetry.Report
wantErr bool
}{
{
name: "empty report",
report: &telemetry.Report{},
wantErr: true,
},
{
name: "valid report with no counters",
report: &telemetry.Report{
Week: "2023-06-15",
LastWeek: "",
X: 0.1,
Programs: []*telemetry.ProgramReport{},
Config: "v0.0.1-test",
},
wantErr: false,
},
{
name: "valid report with counters",
report: &telemetry.Report{
Week: "2023-06-15",
LastWeek: "",
X: 0.1,
Programs: []*telemetry.ProgramReport{
{
Program: "golang.org/x/tools/gopls",
Version: "v0.10.1",
GoVersion: "go1.20.1",
GOOS: "linux",
GOARCH: "arm64",
Counters: map[string]int64{
"editor:vim": 100,
},
},
},
Config: "v0.0.1-test",
},
},
{
name: "valid report with a stack counter",
report: &telemetry.Report{
Week: "2023-06-15",
LastWeek: "",
X: 1.0,
Programs: []*telemetry.ProgramReport{
{
Program: "golang.org/x/tools/gopls",
Version: "v0.10.1",
GoVersion: "go1.20.1",
GOOS: "linux",
GOARCH: "arm64",
Stacks: map[string]int64{
"gopls/bug\ngolang.org/x/tools/gopls/internal/bug.report:35\ngolang.org/x/tools/gopls/internal/bug.Errorf:2\ngolang.org/x/tools/gopls/internal/lsp.(*Server).SignatureHelp:1\nruntime.goexit:0": 1,
},
},
},
Config: "v0.0.1-test",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validate(tt.report, cfg); (err != nil) != tt.wantErr {
t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}