| // 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 trace_test | |
| import ( | |
| "bytes" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "os" | |
| "path/filepath" | |
| "strings" | |
| "testing" | |
| "internal/trace" | |
| "internal/trace/raw" | |
| "internal/trace/testtrace" | |
| "internal/trace/version" | |
| ) | |
| var ( | |
| logEvents = flag.Bool("log-events", false, "whether to log high-level events; significantly slows down tests") | |
| dumpTraces = flag.Bool("dump-traces", false, "dump traces even on success") | |
| ) | |
| func TestReaderGolden(t *testing.T) { | |
| matches, err := filepath.Glob("./testdata/tests/*.test") | |
| if err != nil { | |
| t.Fatalf("failed to glob for tests: %v", err) | |
| } | |
| for _, testPath := range matches { | |
| testPath := testPath | |
| testName, err := filepath.Rel("./testdata", testPath) | |
| if err != nil { | |
| t.Fatalf("failed to relativize testdata path: %v", err) | |
| } | |
| t.Run(testName, func(t *testing.T) { | |
| tr, exp, err := testtrace.ParseFile(testPath) | |
| if err != nil { | |
| t.Fatalf("failed to parse test file at %s: %v", testPath, err) | |
| } | |
| testReader(t, tr, exp) | |
| }) | |
| } | |
| } | |
| func FuzzReader(f *testing.F) { | |
| // Currently disabled because the parser doesn't do much validation and most | |
| // getters can be made to panic. Turn this on once the parser is meant to | |
| // reject invalid traces. | |
| const testGetters = false | |
| f.Fuzz(func(t *testing.T, b []byte) { | |
| r, err := trace.NewReader(bytes.NewReader(b)) | |
| if err != nil { | |
| return | |
| } | |
| for { | |
| ev, err := r.ReadEvent() | |
| if err != nil { | |
| break | |
| } | |
| if !testGetters { | |
| continue | |
| } | |
| // Make sure getters don't do anything that panics | |
| switch ev.Kind() { | |
| case trace.EventLabel: | |
| ev.Label() | |
| case trace.EventLog: | |
| ev.Log() | |
| case trace.EventMetric: | |
| ev.Metric() | |
| case trace.EventRangeActive, trace.EventRangeBegin: | |
| ev.Range() | |
| case trace.EventRangeEnd: | |
| ev.Range() | |
| ev.RangeAttributes() | |
| case trace.EventStateTransition: | |
| ev.StateTransition() | |
| case trace.EventRegionBegin, trace.EventRegionEnd: | |
| ev.Region() | |
| case trace.EventTaskBegin, trace.EventTaskEnd: | |
| ev.Task() | |
| case trace.EventSync: | |
| case trace.EventStackSample: | |
| case trace.EventBad: | |
| } | |
| } | |
| }) | |
| } | |
| func testReader(t *testing.T, tr io.Reader, exp *testtrace.Expectation) { | |
| r, err := trace.NewReader(tr) | |
| if err != nil { | |
| if err := exp.Check(err); err != nil { | |
| t.Error(err) | |
| } | |
| return | |
| } | |
| v := testtrace.NewValidator() | |
| for { | |
| ev, err := r.ReadEvent() | |
| if err == io.EOF { | |
| break | |
| } | |
| if err != nil { | |
| if err := exp.Check(err); err != nil { | |
| t.Error(err) | |
| } | |
| return | |
| } | |
| if *logEvents { | |
| t.Log(ev.String()) | |
| } | |
| if err := v.Event(ev); err != nil { | |
| t.Error(err) | |
| } | |
| } | |
| if err := exp.Check(nil); err != nil { | |
| t.Error(err) | |
| } | |
| } | |
| func dumpTraceToText(t *testing.T, b []byte) string { | |
| t.Helper() | |
| br, err := raw.NewReader(bytes.NewReader(b)) | |
| if err != nil { | |
| t.Fatalf("dumping trace: %v", err) | |
| } | |
| var sb strings.Builder | |
| tw, err := raw.NewTextWriter(&sb, version.Current) | |
| if err != nil { | |
| t.Fatalf("dumping trace: %v", err) | |
| } | |
| for { | |
| ev, err := br.ReadEvent() | |
| if err == io.EOF { | |
| break | |
| } | |
| if err != nil { | |
| t.Fatalf("dumping trace: %v", err) | |
| } | |
| if err := tw.WriteEvent(ev); err != nil { | |
| t.Fatalf("dumping trace: %v", err) | |
| } | |
| } | |
| return sb.String() | |
| } | |
| func dumpTraceToFile(t *testing.T, testName string, stress bool, b []byte) string { | |
| t.Helper() | |
| desc := "default" | |
| if stress { | |
| desc = "stress" | |
| } | |
| name := fmt.Sprintf("%s.%s.trace.", testName, desc) | |
| f, err := os.CreateTemp("", name) | |
| if err != nil { | |
| t.Fatalf("creating temp file: %v", err) | |
| } | |
| defer f.Close() | |
| if _, err := io.Copy(f, bytes.NewReader(b)); err != nil { | |
| t.Fatalf("writing trace dump to %q: %v", f.Name(), err) | |
| } | |
| return f.Name() | |
| } |