blob: c081d36f9c402d8d583c8fd2793c4eb8a8d294f8 [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 (
"net/url"
"reflect"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"golang.org/x/telemetry/internal/config"
"golang.org/x/telemetry/internal/telemetry"
)
var exampleReports = []telemetry.Report{
{
Week: "2999-01-01",
LastWeek: "2998-01-01",
X: 0.123456789,
Programs: []*telemetry.ProgramReport{
{
Program: "cmd/go",
Version: "go1.2.3",
GoVersion: "go1.2.3",
GOOS: "darwin",
GOARCH: "arm64",
Counters: map[string]int64{
"main": 1,
},
},
{
Program: "example.com/mod/pkg",
Version: "v2.3.4",
GoVersion: "go1.2.3",
GOOS: "darwin",
GOARCH: "arm64",
Counters: map[string]int64{
"main": 1,
"flag:a": 2,
"flag:b": 3,
},
// TODO: add support for stacks
Stacks: map[string]int64{
"panic": 4,
},
},
},
Config: "v0.0.1",
},
{
Week: "2999-01-01",
LastWeek: "2998-01-01",
X: 0.123456789,
Programs: []*telemetry.ProgramReport{
{
Program: "example.com/mod/pkg",
Version: "v1.2.3",
GoVersion: "go1.2.3",
GOOS: "darwin",
GOARCH: "arm64",
Counters: map[string]int64{
"main": 1,
"flag:a": 2,
"flag:b": 3,
},
// TODO: add support for stacks
Stacks: map[string]int64{
"panic": 4,
},
},
{
Program: "example.com/mod/pkg",
Version: "v2.3.4",
GoVersion: "go1.2.3",
GOOS: "darwin",
GOARCH: "arm64",
Counters: map[string]int64{
"main": 1,
"flag:a": 2,
"flag:b": 3,
},
// TODO: add support for stacks
Stacks: map[string]int64{
"panic": 4,
},
},
},
Config: "v0.0.1",
},
{
Week: "2999-01-01",
LastWeek: "2998-01-01",
X: 0.987654321,
Programs: []*telemetry.ProgramReport{
{
Program: "example.com/mod/pkg",
Version: "v1.2.3",
GoVersion: "go1.2.3",
GOOS: "linux",
GOARCH: "amd64",
Counters: map[string]int64{
"main": 4,
"flag:a": 5,
"flag:b": 6,
"flag:c": 1,
},
// TODO: add support for stacks
Stacks: map[string]int64{
"panic": 7,
},
},
},
Config: "v0.0.1",
},
}
func TestNest(t *testing.T) {
type args struct {
reports []telemetry.Report
}
tests := []struct {
name string
args args
want data
}{
{
name: "single report",
args: args{
[]telemetry.Report{
{
Week: "2999-01-01",
LastWeek: "2998-01-01",
X: 0.123456789,
Programs: []*telemetry.ProgramReport{
{
Program: "example.com/mod/pkg",
Version: "v1.2.3",
GoVersion: "go1.2.3",
GOOS: "darwin",
GOARCH: "arm64",
Counters: map[string]int64{
"main": 1,
"flag:a": 2,
"flag:b": 3,
},
// TODO: add support for stacks
Stacks: map[string]int64{
"panic": 4,
},
},
},
Config: "v0.0.1",
},
},
},
want: data{
weekName("2999-01-01"): {
programName("example.com/mod/pkg"): {
graphName("Version"): {
counterName("Version"): {
reportID(0.1234567890): 1,
},
counterName("Version:v1.2"): {
reportID(0.1234567890): 1,
},
},
graphName("GOOS"): {
counterName("GOOS"): {
reportID(0.1234567890): 1,
},
counterName("GOOS:darwin"): {
reportID(0.1234567890): 1,
},
},
graphName("GOARCH"): {
counterName("GOARCH"): {
reportID(0.1234567890): 1,
},
counterName("GOARCH:arm64"): {
reportID(0.1234567890): 1,
},
},
graphName("GoVersion"): {
counterName("GoVersion"): {
reportID(0.1234567890): 1,
},
counterName("GoVersion:go1.2"): {
reportID(0.1234567890): 1,
},
},
graphName("main"): {
counterName("main"): {
reportID(0.1234567890): 1,
},
},
graphName("flag"): {
counterName("flag"): {
reportID(0.1234567890): 5,
},
counterName("flag:a"): {
reportID(0.1234567890): 2,
},
counterName("flag:b"): {
reportID(0.1234567890): 3,
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := nest(tt.args.reports)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("nest() = %v, want %v", got, tt.want)
}
})
}
}
func TestPartition(t *testing.T) {
exampleData := nest(exampleReports)
type args struct {
program string
name string
buckets []string
}
tests := []struct {
name string
data data
args args
want *chart
}{
{
name: "major.minor.patch version counter",
data: exampleData,
args: args{
program: "example.com/mod/pkg",
name: "Version",
buckets: []string{"v1.2.3", "v2.3.4"},
},
want: &chart{
ID: "charts:example.com/mod/pkg:Version",
Name: "Version",
Type: "partition",
Data: []*datum{
{
Week: "2999-01-01",
Key: "v1.2",
Value: 2,
},
{
Week: "2999-01-01",
Key: "v2.3",
Value: 1,
},
},
},
},
{
name: "major.minor version counter should have same result as major.minor.patch",
data: exampleData,
args: args{
program: "example.com/mod/pkg",
name: "Version",
buckets: []string{"v1.2", "v2.3"},
},
want: &chart{
ID: "charts:example.com/mod/pkg:Version",
Name: "Version",
Type: "partition",
Data: []*datum{
{
Week: "2999-01-01",
Key: "v1.2",
Value: 2,
},
{
Week: "2999-01-01",
Key: "v2.3",
Value: 1,
},
},
},
},
{
name: "duplicated counter should be ignored",
data: exampleData,
args: args{
program: "example.com/mod/pkg",
name: "Version",
buckets: []string{"v1.2.3", "v2.3.4", "v1.2.3"},
},
want: &chart{
ID: "charts:example.com/mod/pkg:Version",
Name: "Version",
Type: "partition",
Data: []*datum{
{
Week: "2999-01-01",
Key: "v1.2",
Value: 2,
},
{
Week: "2999-01-01",
Key: "v2.3",
Value: 1,
},
},
},
},
{
name: "goos counter",
data: exampleData,
args: args{
program: "example.com/mod/pkg",
name: "GOOS",
buckets: []string{"darwin", "linux"},
},
want: &chart{
ID: "charts:example.com/mod/pkg:GOOS",
Name: "GOOS",
Type: "partition",
Data: []*datum{
{
Week: "2999-01-01",
Key: "darwin",
Value: 1,
},
{
Week: "2999-01-01",
Key: "linux",
Value: 1,
},
},
},
},
{
name: "three days, multiple versions",
data: data{
"2999-01-01": {"example.com/mod/pkg": {"Version": {
"Version": {0.1: 5},
"Version:v1.2": {0.1: 2},
"Version:v2.3": {0.1: 3},
},
}},
"2999-01-04": {"example.com/mod/pkg": {"Version": {
"Version": {0.3: 2, 0.4: 5},
"Version:v1.2": {0.3: 2},
"Version:v2.3": {0.4: 5},
},
}},
"2999-01-05": {"example.com/mod/pkg": {"Version": {
"Version": {0.5: 6},
"Version:v2.3": {0.5: 6},
}}},
},
args: args{
program: "example.com/mod/pkg",
name: "Version",
buckets: []string{"v1.2.3", "v2.3.4"},
},
want: &chart{
ID: "charts:example.com/mod/pkg:Version",
Name: "Version",
Type: "partition",
Data: []*datum{
{
Week: "2999-01-05",
Key: "v1.2",
Value: 2,
},
{
Week: "2999-01-05",
Key: "v2.3",
Value: 3,
},
},
},
},
{
name: "three days, multiple GOOS",
data: data{
"2999-01-01": {"example.com/mod/pkg": {"GOOS": {
"GOOS": {0.1: 4, 0.2: 4, 0.3: 2},
"GOOS:darwin": {0.1: 2, 0.2: 2, 0.3: 2},
"GOOS:linux": {0.1: 2, 0.2: 2},
},
}},
"2999-01-02": {"example.com/mod/pkg": {"GOOS": {
"GOOS": {0.4: 2, 0.5: 2, 0.6: 5},
"GOOS:darwin": {0.4: 2, 0.5: 2},
"GOOS:linux": {0.6: 5},
},
}},
"2999-01-03": {"example.com/mod/pkg": {"GOOS": {
"GOOS": {0.6: 3},
"GOOS:darwin": {0.6: 3},
},
}},
},
args: args{
program: "example.com/mod/pkg",
name: "GOOS",
buckets: []string{"darwin", "linux"},
},
want: &chart{
ID: "charts:example.com/mod/pkg:GOOS",
Name: "GOOS",
Type: "partition",
Data: []*datum{
{
Week: "2999-01-03",
Key: "darwin",
Value: 6,
},
{
Week: "2999-01-03",
Key: "linux",
Value: 3,
},
},
},
},
{
name: "two days data, missing GOOS in first day",
data: data{
"2999-01-01": {"example.com/mod/pkg": {"Version": {
"Version": {0.1: 2},
"Version:v1.2": {0.1: 2},
},
}},
"2999-01-02": {"example.com/mod/pkg": {"GOOS": {
"GOOS": {0.3: 4},
"GOOS:darwin": {0.3: 2},
"GOOS:linux": {0.3: 2},
},
}},
},
args: args{
program: "example.com/mod/pkg",
name: "GOOS",
buckets: []string{"darwin", "linux"},
},
want: &chart{
ID: "charts:example.com/mod/pkg:GOOS",
Name: "GOOS",
Type: "partition",
Data: []*datum{
{
Week: "2999-01-02",
Key: "darwin",
Value: 1,
},
{
Week: "2999-01-02",
Key: "linux",
Value: 1,
},
},
},
},
{
name: "three days, missing version data all days",
data: data{
"2999-01-01": {"example.com/mod/pkg": {"GOOS": {
"GOOS": {0.1: 2},
"GOOS:darwin": {0.1: 2},
},
}},
"2999-01-02": {"example.com/mod/pkg": {"GOOS": {
"GOOS": {0.6: 5},
"GOOS:linux": {0.6: 5},
},
}},
"2999-01-03": {"example.com/mod/pkg": {"GOOS": {
"GOOS": {0.6: 3},
"GOOS:darwin": {0.6: 3},
},
}},
},
args: args{
program: "example.com/mod/pkg",
name: "Version",
buckets: []string{"v1.2.3", "v2.3.4"},
},
want: nil,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := tc.data.partition(tc.args.program, tc.args.name, tc.args.buckets); !reflect.DeepEqual(got, tc.want) {
t.Errorf("partition() = %v, want %v", got, tc.want)
}
})
}
}
func TestCharts(t *testing.T) {
exampleData := nest(exampleReports)
cfg := &config.Config{
UploadConfig: &telemetry.UploadConfig{
GOOS: []string{"darwin"},
GOARCH: []string{"amd64"},
GoVersion: []string{"go1.2.3"},
SampleRate: 1,
Programs: []*telemetry.ProgramConfig{
{
Name: "cmd/go",
Versions: []string{"go1.2.3"},
Counters: []telemetry.CounterConfig{{
Name: "main",
}},
},
{
Name: "cmd/compiler",
Versions: []string{"go1.2.3"},
Counters: []telemetry.CounterConfig{{
Name: "count1",
}},
},
{
Name: "example.com/mod/pkg",
Versions: []string{"v0.15.0"},
Counters: []telemetry.CounterConfig{{
Name: "count2",
}},
},
},
},
}
want := &chartdata{
DateRange: [2]string{"2999-01-01", "2999-01-01"},
Programs: []*program{
{
ID: "charts:cmd/go",
Name: "cmd/go",
Charts: []*chart{
{
ID: "charts:cmd/go:GOOS",
Name: "GOOS",
Type: "partition",
Data: []*datum{{
Week: "2999-01-01",
Key: "darwin",
Value: 1,
}},
},
{
ID: "charts:cmd/go:GOARCH",
Name: "GOARCH",
Type: "partition",
Data: []*datum{{
Week: "2999-01-01",
Key: "amd64",
Value: 0,
}},
},
{
ID: "charts:cmd/go:GoVersion",
Name: "GoVersion",
Type: "partition",
Data: []*datum{{
Week: "2999-01-01",
Key: "go1.2",
Value: 1,
}},
},
{
ID: "charts:cmd/go:main",
Name: "main",
Type: "partition",
Data: []*datum{{
Week: "2999-01-01",
Key: "main",
Value: 1,
}},
},
},
},
{
ID: "charts:cmd/compiler",
Name: "cmd/compiler",
},
{
ID: "charts:example.com/mod/pkg",
Name: "example.com/mod/pkg",
Charts: []*chart{
{
ID: "charts:example.com/mod/pkg:Version",
Name: "Version",
Type: "partition",
Data: []*datum{{
Week: "2999-01-01",
Key: "v0.15",
Value: 0,
}},
},
{
ID: "charts:example.com/mod/pkg:GOOS",
Name: "GOOS",
Type: "partition",
Data: []*datum{{
Week: "2999-01-01",
Key: "darwin",
Value: 1,
}},
},
{
ID: "charts:example.com/mod/pkg:GOARCH",
Name: "GOARCH",
Type: "partition",
Data: []*datum{{
Week: "2999-01-01",
Key: "amd64",
Value: 1,
}},
},
{
ID: "charts:example.com/mod/pkg:GoVersion",
Name: "GoVersion",
Type: "partition",
Data: []*datum{{
Week: "2999-01-01",
Key: "go1.2",
Value: 2,
}},
},
},
},
},
NumReports: 1,
}
got := charts(cfg, "2999-01-01", "2999-01-01", exampleData, []float64{0.12345})
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("charts = %+v\n, (-want +got): %v", got, diff)
}
}
func TestNormalizeCounterName(t *testing.T) {
testcases := []struct {
name string
prefix string
counter string
want string
}{
{
name: "strip patch version for Version",
prefix: "Version",
counter: "v0.15.3",
want: "Version:v0.15",
},
{
name: "strip patch go version for Version",
prefix: "Version",
counter: "go1.12.3",
want: "Version:go1.12",
},
{
name: "concatenate devel for Version",
prefix: "Version",
counter: "devel",
want: "Version:devel",
},
{
name: "concatenate for GOOS",
prefix: "GOOS",
counter: "darwin",
want: "GOOS:darwin",
},
{
name: "concatenate for GOARCH",
prefix: "GOARCH",
counter: "amd64",
want: "GOARCH:amd64",
},
{
name: "strip patch version for GoVersion",
prefix: "GoVersion",
counter: "go1.12.3",
want: "GoVersion:go1.12",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got := normalizeCounterName(tc.prefix, tc.counter)
if tc.want != got {
t.Errorf("normalizeCounterName(%q, %q) = %q, want %q", tc.prefix, tc.counter, got, tc.want)
}
})
}
}
func TestWriteCount(t *testing.T) {
type keyValue struct {
week, program, prefix, counter string
x float64
value int64
}
testcases := []struct {
name string
inputs []keyValue
wants []keyValue
}{
{
name: "program version counter should have value",
inputs: []keyValue{
{"2987-07-01", "golang.org/x/tools/gopls", "Version", "v0.15.3", 0.00009, 1},
},
wants: []keyValue{
{"2987-07-01", "golang.org/x/tools/gopls", "Version", "Version:v0.15", 0.00009, 1},
{"2987-07-01", "golang.org/x/tools/gopls", "Version", "Version", 0.00009, 1},
},
},
{
name: "only one count with same prefix and counter",
inputs: []keyValue{
{"2987-06-30", "cmd/go", "go/invocations", "go/invocations", 0.86995, 84},
},
wants: []keyValue{
{"2987-06-30", "cmd/go", "go/invocations", "go/invocations", 0.86995, 84},
},
},
{
name: "sum together when calling multiple times",
inputs: []keyValue{
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "windows", 0.86018, 1},
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "windows", 0.86018, 2},
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "windows", 0.86018, 3},
},
wants: []keyValue{
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "GOOS:windows", 0.86018, 6},
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "GOOS", 0.86018, 6},
},
},
{
name: "sum together when the prefix is the same",
inputs: []keyValue{
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "windows", 0.86018, 1},
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "windows", 0.86018, 2},
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "linux", 0.86018, 4},
},
wants: []keyValue{
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "GOOS:windows", 0.86018, 3},
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "GOOS:linux", 0.86018, 4},
{"2987-06-30", "golang.org/x/tools/gopls", "GOOS", "GOOS", 0.86018, 7},
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
d := make(data)
for _, input := range tc.inputs {
d.writeCount(input.week, input.program, input.prefix, input.counter, input.x, input.value)
}
for _, want := range tc.wants {
got, _ := d.readCount(want.week, want.program, want.prefix, want.counter, want.x)
if want.value != got {
t.Errorf("d[%q][%q][%q][%q][%v] = %v, want %v", want.week, want.program, want.prefix, want.counter, want.x, got, want.value)
}
}
})
}
}
func TestParseDateRange(t *testing.T) {
testcases := []struct {
name string
url string
wantStart time.Time
wantEnd time.Time
wantErr bool
}{
{
name: "regular key start & end input",
url: "http://localhost:8082/chart/?start=2024-06-10&end=2024-06-17",
wantStart: time.Date(2024, 06, 10, 0, 0, 0, 0, time.UTC),
wantEnd: time.Date(2024, 06, 17, 0, 0, 0, 0, time.UTC),
},
{
name: "regular key date input",
url: "http://localhost:8082/chart/?date=2024-06-11",
wantStart: time.Date(2024, 06, 11, 0, 0, 0, 0, time.UTC),
wantEnd: time.Date(2024, 06, 11, 0, 0, 0, 0, time.UTC),
},
{
name: "malformatted value for start",
url: "http://localhost:8082/chart/?start=2024-066-01&end=2024-06-17",
wantErr: true,
},
{
name: "malformatted value for start",
url: "http://localhost:8082/chart/?start=2024-06-10&end=2024-06-179",
wantErr: true,
},
{
name: "end is earlier than start",
url: "http://localhost:8082/chart/?start=2024-06-17&end=2024-06-10",
wantErr: true,
},
{
name: "have only start but missing end",
url: "http://localhost:8082/chart/?start=2024-06-01",
wantErr: true,
},
{
name: "key date and start used together",
url: "http://localhost:8082/chart/?start=2024-06-17&date=2024-06-19",
wantErr: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
url, err := url.Parse(tc.url)
if err != nil {
t.Fatalf("failed to parse url %q: %v", url, err)
}
gotStart, gotEnd, err := parseDateRange(url)
if tc.wantErr && err == nil {
t.Errorf("parseDateRange %v should return error but return nil", tc.url)
}
if !tc.wantErr && err != nil {
t.Errorf("parseDateRange %v should return nil but return error: %v", tc.url, err)
}
if !tc.wantErr {
if !gotStart.Equal(tc.wantStart) || !gotEnd.Equal(tc.wantEnd) {
t.Errorf("parseDateRange(%s) = (%s, %s), want (%s, %s)", tc.url, gotStart.Format(time.DateOnly), gotEnd.Format(time.DateOnly), tc.wantStart.Format(time.DateOnly), tc.wantEnd.Format(time.DateOnly))
}
}
})
}
}