go.temporal.io/server/tools/parallelize/parallelize.go

198 LOC · 102 covered · 96 uncovered · 43 ranges · 13 concepts · 13 introducers · 6 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the fileparallelize.go ×2 · 7 introduced LOCparallelize.go ×2parallelize.go ×1 · 2 introduced LOCparallelize.go ×1parallelize.go ×8 · 21 introduced LOCparallelize.go ×8parallelize.go ×2 · 5 introduced LOCparallelize.go ×2parallelize.go ×3 · 5 introduced LOCparallelize.go ×3parallelize.go ×2 · 3 introduced LOCparallelize.go ×2parallelize.go ×15 · 33 introduced LOCparallelize.go ×15parallelize.go ×1 · 2 introduced LOCparallelize.go ×1parallelize.go ×1 · 2 introduced LOCparallelize.go ×1parallelize.go ×1 · 1 introduced LOCparallelize.go ×1parallelize.go ×1 · 1 introduced LOCparallelize.go ×1parallelize.go ×1 · 2 introduced LOCparallelize.go ×1parallelize.go ×5 · 18 introduced LOCparallelize.go ×5TestProcessDir · introduced test · go.temporal.io/server/tools/parallelize/TestProcessDirTestProcessDiradds_t.Parallel_to_multiple_tests · introduced test · go.temporal.io/server/tools/parallelize/TestProcessFile/adds_t.Parallel_to_multiple_testsadds_t.Parallel_to_multi…skips_non-test_functions · introduced test · go.temporal.io/server/tools/parallelize/TestProcessFile/skips_non-test_functionsskips_non-test_functionsskips_test_suite_methods · introduced test · go.temporal.io/server/tools/parallelize/TestProcessFile/skips_test_suite_methodsskips_test_suite_methodsskips_test_that_already_has_t.Parallel · introduced test · go.temporal.io/server/tools/parallelize/TestProcessFile/skips_test_that_already_has_t.Parallelskips_test_that_already_…skips_test_with_parallelize:ignore · introduced test · go.temporal.io/server/tools/parallelize/TestProcessFile/skips_test_with_parallelize:ignoreskips_test_with_parallel…Focused file · go.temporal.io/server/tools/parallelize/parallelize.go · 198 LOCparallelize/parallelize.…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 package parallelize
2
3 import (
4 "errors"
5 "fmt"
6 "go/ast"
7 "go/parser"
8 "go/token"
9 "os"
10 "path/filepath"
11 "sort"
12 "strings"
13 )
14
15 func Main() error {
16 if len(os.Args) < 2 {
17 return errors.New("usage: parallelize <dir> [<dir>...]")
18 }
19
20 var failed bool
21 for _, dir := range os.Args[1:] {
22 if err := processDir(dir); err != nil {
23 fmt.Fprintf(os.Stderr, "error processing %s: %v\n", dir, err)
24 failed = true
25 }
26 }
27 if failed {
28 return errors.New("some files failed to process")
29 }
30 return nil
31 }
32
33 > func processDir(dir string) error { parallelize.go ×2
34 > return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
35 > if err != nil {
36 return err
37 }
38 > if d.IsDir() || !strings.HasSuffix(path, "_test.go") { parallelize.go ×2
39 > return nil
40 > }
41 > return processFile(path)
42 })
43 }
44
45 > func processFile(path string) error { parallelize.go ×5
46 > fset := token.NewFileSet()
47 > f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
48 > if err != nil {
49 return fmt.Errorf("parse %s: %w", path, err)
50 }
51
52 // Collect line numbers where we need to insert t.Parallel().
53 // Each entry is the line of the opening '{' of the test function body.
54 > type insertion struct { parallelize.go ×5
55 > line int // line number of the '{' opening the function body
56 > paramName string // name of the *testing.T parameter
57 > }
58 > var insertions []insertion
59 >
60 > for _, decl := range f.Decls {
61 > fn, ok := decl.(*ast.FuncDecl)
62 > if !ok {
63 > continue
64 }
65 > if !isTestFunc(fn) { parallelize.go ×5
66 > continue parallelize.go ×1
67 }
68 > paramName := testingTParamName(fn) parallelize.go ×15
69 > if paramName == "" {
70 continue
71 }
72 > if hasParallelCall(fn.Body, paramName) { parallelize.go ×15
73 > continue parallelize.go ×3
74 }
75 > if hasNoLintComment(fn) { parallelize.go ×2
76 > continue parallelize.go ×2
77 }
78 > bodyLine := fset.Position(fn.Body.Lbrace).Line parallelize.go ×8
79 > insertions = append(insertions, insertion{line: bodyLine, paramName: paramName})
80 }
81
82 > if len(insertions) == 0 { parallelize.go ×5
83 > return nil parallelize.go ×1
84 > }
85
86 // Sort by line descending so insertions don't shift line numbers of subsequent insertions.
87 > sort.Slice(insertions, func(i, j int) bool { parallelize.go ×8
88 > return insertions[i].line > insertions[j].line parallelize.go ×1
89 > })
90
91 > fi, err := os.Stat(path) parallelize.go ×8
92 > if err != nil {
93 return fmt.Errorf("stat %s: %w", path, err)
94 }
95
96 > src, err := os.ReadFile(path) parallelize.go ×8
97 > if err != nil {
98 return fmt.Errorf("read %s: %w", path, err)
99 }
100
101 > lines := strings.Split(string(src), "\n") parallelize.go ×8
102 > for _, ins := range insertions {
103 > // ins.line is 1-indexed, so it conveniently equals the 0-based index
104 > // of the line right after '{', which is where we want to insert.
105 > idx := ins.line
106 > newLine := "\t" + ins.paramName + ".Parallel()"
107 > lines = append(lines[:idx+1], lines[idx:]...)
108 > lines[idx] = newLine
109 > }
110
111 > if err := os.WriteFile(path, []byte(strings.Join(lines, "\n")), fi.Mode()); err != nil { parallelize.go ×8
112 return fmt.Errorf("write %s: %w", path, err)
113 }
114
115 > fmt.Printf("parallelize: %s\n", path) parallelize.go ×8
116 > return nil
117 }
118
119 // isTestFunc returns true for func TestXxx(t *testing.T).
120 > func isTestFunc(fn *ast.FuncDecl) bool { parallelize.go ×5
121 > if fn.Recv != nil {
122 > return false // method, not a function parallelize.go ×1
123 > }
124 > if !strings.HasPrefix(fn.Name.Name, "Test") { parallelize.go ×1
125 > return false parallelize.go ×1
126 > }
127 > if fn.Body == nil { parallelize.go ×15
128 return false
129 }
130 > return testingTParamName(fn) != "" parallelize.go ×15
131 }
132
133 // testingTParamName returns the name of the *testing.T parameter, or "" if not found.
134 > func testingTParamName(fn *ast.FuncDecl) string { parallelize.go ×15
135 > if fn.Type.Params == nil || len(fn.Type.Params.List) == 0 {
136 return ""
137 }
138 > for _, field := range fn.Type.Params.List { parallelize.go ×15
139 > starExpr, ok := field.Type.(*ast.StarExpr)
140 > if !ok {
141 continue
142 }
143 > selExpr, ok := starExpr.X.(*ast.SelectorExpr) parallelize.go ×15
144 > if !ok {
145 continue
146 }
147 > pkg, ok := selExpr.X.(*ast.Ident) parallelize.go ×15
148 > if !ok {
149 continue
150 }
151 > if pkg.Name == "testing" && selExpr.Sel.Name == "T" { parallelize.go ×15
152 > if len(field.Names) > 0 {
153 > return field.Names[0].Name
154 > }
155 }
156 }
157 return ""
158 }
159
160 // hasNoLintComment checks for //parallelize:ignore in the function's doc comment.
161 > func hasNoLintComment(fn *ast.FuncDecl) bool { parallelize.go ×2
162 > if fn.Doc == nil {
163 > return false parallelize.go ×8
164 > }
165 > for _, c := range fn.Doc.List { parallelize.go ×2
166 > if strings.Contains(c.Text, "parallelize:ignore") {
167 > return true
168 > }
169 }
170 return false
171 }
172
173 // hasParallelCall checks if the function body already contains <param>.Parallel().
174 > func hasParallelCall(body *ast.BlockStmt, paramName string) bool { parallelize.go ×15
175 > found := false
176 > ast.Inspect(body, func(n ast.Node) bool {
177 > if found {
178 > return false parallelize.go ×3
179 > }
180 > call, ok := n.(*ast.CallExpr) parallelize.go ×15
181 > if !ok {
182 > return true
183 > }
184 > sel, ok := call.Fun.(*ast.SelectorExpr)
185 > if !ok {
186 return true
187 }
188 > ident, ok := sel.X.(*ast.Ident) parallelize.go ×15
189 > if !ok {
190 return true
191 }
192 > if ident.Name == paramName && sel.Sel.Name == "Parallel" { parallelize.go ×15
193 > found = true parallelize.go ×3
194 > }
195 > return true parallelize.go ×15
196 })
197 > return found parallelize.go ×15
198 }