1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package trace_test
6
7import (
8	"bytes"
9	"flag"
10	"fmt"
11	"io"
12	"os"
13	"path/filepath"
14	"strings"
15	"testing"
16
17	"internal/trace"
18	"internal/trace/raw"
19	"internal/trace/testtrace"
20	"internal/trace/version"
21)
22
23var (
24	logEvents  = flag.Bool("log-events", false, "whether to log high-level events; significantly slows down tests")
25	dumpTraces = flag.Bool("dump-traces", false, "dump traces even on success")
26)
27
28func TestReaderGolden(t *testing.T) {
29	matches, err := filepath.Glob("./testdata/tests/*.test")
30	if err != nil {
31		t.Fatalf("failed to glob for tests: %v", err)
32	}
33	for _, testPath := range matches {
34		testPath := testPath
35		testName, err := filepath.Rel("./testdata", testPath)
36		if err != nil {
37			t.Fatalf("failed to relativize testdata path: %v", err)
38		}
39		t.Run(testName, func(t *testing.T) {
40			tr, exp, err := testtrace.ParseFile(testPath)
41			if err != nil {
42				t.Fatalf("failed to parse test file at %s: %v", testPath, err)
43			}
44			testReader(t, tr, exp)
45		})
46	}
47}
48
49func FuzzReader(f *testing.F) {
50	// Currently disabled because the parser doesn't do much validation and most
51	// getters can be made to panic. Turn this on once the parser is meant to
52	// reject invalid traces.
53	const testGetters = false
54
55	f.Fuzz(func(t *testing.T, b []byte) {
56		r, err := trace.NewReader(bytes.NewReader(b))
57		if err != nil {
58			return
59		}
60		for {
61			ev, err := r.ReadEvent()
62			if err != nil {
63				break
64			}
65
66			if !testGetters {
67				continue
68			}
69			// Make sure getters don't do anything that panics
70			switch ev.Kind() {
71			case trace.EventLabel:
72				ev.Label()
73			case trace.EventLog:
74				ev.Log()
75			case trace.EventMetric:
76				ev.Metric()
77			case trace.EventRangeActive, trace.EventRangeBegin:
78				ev.Range()
79			case trace.EventRangeEnd:
80				ev.Range()
81				ev.RangeAttributes()
82			case trace.EventStateTransition:
83				ev.StateTransition()
84			case trace.EventRegionBegin, trace.EventRegionEnd:
85				ev.Region()
86			case trace.EventTaskBegin, trace.EventTaskEnd:
87				ev.Task()
88			case trace.EventSync:
89			case trace.EventStackSample:
90			case trace.EventBad:
91			}
92		}
93	})
94}
95
96func testReader(t *testing.T, tr io.Reader, exp *testtrace.Expectation) {
97	r, err := trace.NewReader(tr)
98	if err != nil {
99		if err := exp.Check(err); err != nil {
100			t.Error(err)
101		}
102		return
103	}
104	v := testtrace.NewValidator()
105	for {
106		ev, err := r.ReadEvent()
107		if err == io.EOF {
108			break
109		}
110		if err != nil {
111			if err := exp.Check(err); err != nil {
112				t.Error(err)
113			}
114			return
115		}
116		if *logEvents {
117			t.Log(ev.String())
118		}
119		if err := v.Event(ev); err != nil {
120			t.Error(err)
121		}
122	}
123	if err := exp.Check(nil); err != nil {
124		t.Error(err)
125	}
126}
127
128func dumpTraceToText(t *testing.T, b []byte) string {
129	t.Helper()
130
131	br, err := raw.NewReader(bytes.NewReader(b))
132	if err != nil {
133		t.Fatalf("dumping trace: %v", err)
134	}
135	var sb strings.Builder
136	tw, err := raw.NewTextWriter(&sb, version.Current)
137	if err != nil {
138		t.Fatalf("dumping trace: %v", err)
139	}
140	for {
141		ev, err := br.ReadEvent()
142		if err == io.EOF {
143			break
144		}
145		if err != nil {
146			t.Fatalf("dumping trace: %v", err)
147		}
148		if err := tw.WriteEvent(ev); err != nil {
149			t.Fatalf("dumping trace: %v", err)
150		}
151	}
152	return sb.String()
153}
154
155func dumpTraceToFile(t *testing.T, testName string, stress bool, b []byte) string {
156	t.Helper()
157
158	desc := "default"
159	if stress {
160		desc = "stress"
161	}
162	name := fmt.Sprintf("%s.%s.trace.", testName, desc)
163	f, err := os.CreateTemp("", name)
164	if err != nil {
165		t.Fatalf("creating temp file: %v", err)
166	}
167	defer f.Close()
168	if _, err := io.Copy(f, bytes.NewReader(b)); err != nil {
169		t.Fatalf("writing trace dump to %q: %v", f.Name(), err)
170	}
171	return f.Name()
172}
173