Skip to content

Commit f7d6c77

Browse files
authored
cmd: Instrument with errtrace (#81)
Instruments the errtrace cmd with itself. Example output: ``` % ./errtrace -w - stdin:can't use -w with stdin main.(*mainCmd).readFile [..]/errtrace/cmd/errtrace/main.go:571 main.(*mainCmd).processFile [..]/errtrace/cmd/errtrace/main.go:351 ``` Includes a lint check to ensure that the command remains instrumented. Instrumentation is opt-in in the Makefile. We don't want to instrument the errtrace library or its dependencies because that will introduce a circular dependency.
1 parent f75fc32 commit f7d6c77

File tree

3 files changed

+65
-36
lines changed

3 files changed

+65
-36
lines changed

Makefile

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
SHELL := /bin/bash
12
PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
23

34
# 'go install' into the project's bin directory
45
# and add it to the PATH.
56
export GOBIN ?= $(PROJECT_ROOT)/bin
67
export PATH := $(GOBIN):$(PATH)
78

9+
ERRTRACE = $(GOBIN)/errtrace
10+
11+
# Packages to instrument with errtrace relative to the project root.
12+
ERRTRACE_PKGS = ./cmd/errtrace/...
13+
814
# only use -race if NO_RACE is unset.
915
RACE=$(if $(NO_RACE),,-race)
1016

@@ -31,7 +37,7 @@ bench-parallel:
3137
go test -run NONE -bench . -cpu 1,2,4,8
3238

3339
.PHONY: lint
34-
lint: golangci-lint
40+
lint: golangci-lint errtrace-lint
3541

3642
.PHONY: golangci-lint
3743
golangci-lint:
@@ -42,3 +48,20 @@ golangci-lint:
4248
fi; \
4349
echo "Running golangci-lint"; \
4450
golangci-lint run $(GOLANGCI_LINT_ARGS) ./...
51+
52+
.PHONY: errtrace
53+
errtrace: $(ERRTRACE)
54+
$(ERRTRACE) -w $(ERRTRACE_PKGS)
55+
56+
.PHONY: errtrace-lint
57+
errtrace-lint: $(ERRTRACE)
58+
@echo "Running errtrace"; \
59+
changed=$$($(ERRTRACE) -l $(ERRTRACE_PKGS)); \
60+
if [[ -n "$$changed" ]]; then \
61+
echo "Found uninstrumented files. Please run 'make errtrace'"; \
62+
echo "$$changed"; \
63+
exit 1; \
64+
fi
65+
66+
$(ERRTRACE):
67+
go install braces.dev/errtrace/cmd/errtrace

cmd/errtrace/main.go

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ import (
4949
"strconv"
5050
"strings"
5151
"time"
52+
53+
"braces.dev/errtrace"
5254
)
5355

5456
func main() {
@@ -100,7 +102,7 @@ func (p *mainParams) Parse(w io.Writer, args []string) error {
100102
// TODO: toolexec mode
101103

102104
if err := flag.Parse(args); err != nil {
103-
return err
105+
return errtrace.Wrap(err)
104106
}
105107

106108
p.Patterns = flag.Args()
@@ -153,7 +155,7 @@ func (f *format) Set(s string) error {
153155
case "never":
154156
*f = formatNever
155157
default:
156-
return fmt.Errorf("invalid format %q is not one of [auto, always, never]", s)
158+
return errtrace.Wrap(fmt.Errorf("invalid format %q is not one of [auto, always, never]", s))
157159
}
158160
return nil
159161
}
@@ -184,13 +186,13 @@ func (cmd *mainCmd) Run(args []string) (exitCode int) {
184186

185187
var p mainParams
186188
if err := p.Parse(cmd.Stderr, args); err != nil {
187-
cmd.log.Println("errtrace:", err)
189+
cmd.log.Printf("errtrace: %+v", err)
188190
return 1
189191
}
190192

191193
files, err := expandPatterns(p.Patterns)
192194
if err != nil {
193-
cmd.log.Println("errtrace:", err)
195+
cmd.log.Printf("errtrace: %+v", err)
194196
return 1
195197
}
196198

@@ -221,7 +223,7 @@ func (cmd *mainCmd) Run(args []string) (exitCode int) {
221223
ImplicitStdin: p.ImplicitStdin,
222224
}
223225
if err := cmd.processFile(req); err != nil {
224-
cmd.log.Printf("%s:%s", display, err)
226+
cmd.log.Printf("%s:%+v", display, err)
225227
exitCode = 1
226228
}
227229
}
@@ -254,7 +256,7 @@ func expandPatterns(args []string) ([]string, error) {
254256
if len(patterns) > 0 {
255257
pkgFiles, err := goListFiles(patterns)
256258
if err != nil {
257-
return nil, fmt.Errorf("go list: %w", err)
259+
return nil, errtrace.Wrap(fmt.Errorf("go list: %w", err))
258260
}
259261

260262
files = append(files, pkgFiles...)
@@ -279,11 +281,11 @@ func goListFiles(patterns []string) (files []string, err error) {
279281

280282
stdout, err := cmd.StdoutPipe()
281283
if err != nil {
282-
return nil, fmt.Errorf("create stdout pipe: %w", err)
284+
return nil, errtrace.Wrap(fmt.Errorf("create stdout pipe: %w", err))
283285
}
284286

285287
if err := cmd.Start(); err != nil {
286-
return nil, fmt.Errorf("start command: %w", err)
288+
return nil, errtrace.Wrap(fmt.Errorf("start command: %w", err))
287289
}
288290

289291
type packageInfo struct {
@@ -299,7 +301,7 @@ func goListFiles(patterns []string) (files []string, err error) {
299301
for decoder.More() {
300302
var pkg packageInfo
301303
if err := decoder.Decode(&pkg); err != nil {
302-
return nil, fmt.Errorf("output malformed: %v", err)
304+
return nil, errtrace.Wrap(fmt.Errorf("output malformed: %w", err))
303305
}
304306

305307
for _, pkgFiles := range [][]string{
@@ -316,7 +318,7 @@ func goListFiles(patterns []string) (files []string, err error) {
316318
}
317319

318320
if err := cmd.Wait(); err != nil {
319-
return nil, fmt.Errorf("%w\n%s", err, stderr.String())
321+
return nil, errtrace.Wrap(fmt.Errorf("%w\n%s", err, stderr.String()))
320322
}
321323

322324
return files, nil
@@ -346,12 +348,12 @@ func (cmd *mainCmd) processFile(r fileRequest) error {
346348

347349
src, err := cmd.readFile(r)
348350
if err != nil {
349-
return err
351+
return errtrace.Wrap(err)
350352
}
351353

352354
f, err := parser.ParseFile(fset, r.Filename, src, parser.ParseComments)
353355
if err != nil {
354-
return err
356+
return errtrace.Wrap(err)
355357
}
356358

357359
errtracePkg := "errtrace" // name to use for errtrace package
@@ -396,11 +398,11 @@ func (cmd *mainCmd) processFile(r fileRequest) error {
396398

397399
var inserts []insert
398400
w := walker{
399-
fset: fset,
400-
optouts: optoutLines(fset, f.Comments),
401-
errtrace: errtracePkg,
402-
logger: cmd.log,
403-
inserts: &inserts,
401+
fset: fset,
402+
optouts: optoutLines(fset, f.Comments),
403+
errtracePkg: errtracePkg,
404+
logger: cmd.log,
405+
inserts: &inserts,
404406
}
405407
ast.Walk(&w, f)
406408

@@ -423,7 +425,7 @@ func (cmd *mainCmd) processFile(r fileRequest) error {
423425
if len(inserts) > 0 {
424426
_, err = fmt.Fprintf(cmd.Stdout, "%s\n", r.Filename)
425427
}
426-
return err
428+
return errtrace.Wrap(err)
427429
}
428430

429431
// If errtrace isn't imported, but at least one insert was made,
@@ -546,7 +548,7 @@ func (cmd *mainCmd) processFile(r fileRequest) error {
546548
if r.Format {
547549
outSrc, err = gofmt.Source(outSrc)
548550
if err != nil {
549-
return fmt.Errorf("format: %w", err)
551+
return errtrace.Wrap(fmt.Errorf("format: %w", err))
550552
}
551553
}
552554

@@ -555,22 +557,22 @@ func (cmd *mainCmd) processFile(r fileRequest) error {
555557
} else {
556558
_, err = cmd.Stdout.Write(outSrc)
557559
}
558-
return err
560+
return errtrace.Wrap(err)
559561
}
560562

561563
var _stdinWait = 200 * time.Millisecond
562564

563565
func (cmd *mainCmd) readFile(r fileRequest) ([]byte, error) {
564566
if r.Filepath != "-" {
565-
return os.ReadFile(r.Filename)
567+
return errtrace.Wrap2(os.ReadFile(r.Filename))
566568
}
567569

568570
if r.Write {
569-
return nil, fmt.Errorf("can't use -w with stdin")
571+
return nil, errtrace.Wrap(fmt.Errorf("can't use -w with stdin"))
570572
}
571573

572574
if !r.ImplicitStdin {
573-
return io.ReadAll(cmd.Stdin)
575+
return errtrace.Wrap2(io.ReadAll(cmd.Stdin))
574576
}
575577

576578
// If we're reading from stdin because there were no other arguments,
@@ -594,7 +596,7 @@ func (cmd *mainCmd) readFile(r fileRequest) ([]byte, error) {
594596
if errors.Is(err, io.EOF) {
595597
err = nil
596598
}
597-
return buff.Bytes(), err
599+
return buff.Bytes(), errtrace.Wrap(err)
598600
}
599601

600602
if firstRead != nil {
@@ -607,9 +609,9 @@ func (cmd *mainCmd) readFile(r fileRequest) ([]byte, error) {
607609
type walker struct {
608610
// Inputs
609611

610-
fset *token.FileSet // file set for positional information
611-
errtrace string // name of the errtrace package
612-
logger *log.Logger
612+
fset *token.FileSet // file set for positional information
613+
errtracePkg string // name of the errtrace package
614+
logger *log.Logger
613615

614616
optouts map[int]int // map from line to number of uses
615617

@@ -911,7 +913,7 @@ func (t *walker) isErrtraceWrap(expr ast.Expr) bool {
911913
return false
912914
}
913915

914-
if !isIdent(sel.X, t.errtrace) {
916+
if !isIdent(sel.X, t.errtracePkg) {
915917
return false
916918
}
917919

cmd/errtrace/main_test.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"testing"
2020
"time"
2121

22+
"braces.dev/errtrace"
2223
"braces.dev/errtrace/internal/diff"
2324
)
2425

@@ -459,8 +460,11 @@ func TestFormatAuto(t *testing.T) {
459460
if want, got := "", out.String(); want != got {
460461
t.Errorf("stdout = %q, want %q", got, want)
461462
}
462-
if want, got := "stdin:can't use -w with stdin\n", err.String(); want != got {
463-
t.Errorf("stderr = %q, want %q", got, want)
463+
if want, got := "stdin:can't use -w with stdin\n", err.String(); !strings.Contains(got, want) {
464+
t.Errorf("stderr = %q, does not contain %q", got, want)
465+
}
466+
if want, got := "(*mainCmd).readFile", err.String(); !strings.Contains(got, want) {
467+
t.Errorf("stderr = %q, does not contain %q", got, want)
464468
}
465469
})
466470
}
@@ -799,7 +803,7 @@ func extractLogs(src []byte) ([]logLine, error) {
799803
fset := token.NewFileSet()
800804
f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
801805
if err != nil {
802-
return nil, fmt.Errorf("parse: %w", err)
806+
return nil, errtrace.Wrap(fmt.Errorf("parse: %w", err))
803807
}
804808

805809
var logs []logLine
@@ -813,7 +817,7 @@ func extractLogs(src []byte) ([]logLine, error) {
813817
pos := fset.Position(l.Pos())
814818
s, err := strconv.Unquote(lit)
815819
if err != nil {
816-
return nil, fmt.Errorf("%s:bad string literal: %s", pos, lit)
820+
return nil, errtrace.Wrap(fmt.Errorf("%s:bad string literal: %s", pos, lit))
817821
}
818822

819823
logs = append(logs, logLine{Line: pos.Line, Msg: s})
@@ -838,7 +842,7 @@ func parseLogOutput(file, s string) ([]logLine, error) {
838842
line = strings.TrimPrefix(line, file)
839843
parts := strings.SplitN(line, ":", 4)
840844
if len(parts) != 4 {
841-
return nil, fmt.Errorf("bad log line: %q", line)
845+
return nil, errtrace.Wrap(fmt.Errorf("bad log line: %q", line))
842846
}
843847

844848
var msg string
@@ -853,12 +857,12 @@ func parseLogOutput(file, s string) ([]logLine, error) {
853857
msg = strings.Join(parts[2:], ":")
854858
}
855859
if msg == "" {
856-
return nil, fmt.Errorf("bad log line: %q", line)
860+
return nil, errtrace.Wrap(fmt.Errorf("bad log line: %q", line))
857861
}
858862

859863
lineNum, err := strconv.Atoi(parts[1])
860864
if err != nil {
861-
return nil, fmt.Errorf("bad log line: %q", line)
865+
return nil, errtrace.Wrap(fmt.Errorf("bad log line: %q", line))
862866
}
863867

864868
logs = append(logs, logLine{

0 commit comments

Comments
 (0)