• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mlange-42 / modo / 13142075205

04 Feb 2025 06:14PM CUT coverage: 72.507%. Remained the same
13142075205

Pull #197

github

web-flow
Merge b84b51c5c into e47cc6076
Pull Request #197: Add source links to file rendering test

1527 of 2106 relevant lines covered (72.51%)

25.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.61
/document/doctest.go
1
package document
2

3
import (
4
        "bufio"
5
        "fmt"
6
        "os"
7
        "path"
8
        "path/filepath"
9
        "sort"
10
        "strings"
11
)
12

13
const docTestAttr = "doctest"
14
const hideAttr = "hide"
15
const globalAttr = "global"
16

17
func (proc *Processor) extractDocTests() error {
6✔
18
        proc.docTests = []*docTest{}
6✔
19
        return proc.walkAllDocStrings(proc.Docs, proc.extractTests, func(elem Named) string {
53✔
20
                return elem.GetFileName()
47✔
21
        })
47✔
22
}
23

24
func (proc *Processor) extractDocTestsMarkdown(baseDir string, build bool) error {
2✔
25
        proc.docTests = []*docTest{}
2✔
26
        outDir := filepath.Clean(proc.Config.OutputDir)
2✔
27
        baseDir = filepath.Clean(baseDir)
2✔
28
        err := filepath.WalkDir(baseDir,
2✔
29
                func(p string, info os.DirEntry, err error) error {
8✔
30
                        if err != nil {
6✔
31
                                return err
×
32
                        }
×
33
                        if info.IsDir() {
8✔
34
                                return nil
2✔
35
                        }
2✔
36
                        return proc.extractMarkdown(p, baseDir, outDir, build)
4✔
37
                })
38
        if err != nil {
2✔
39
                return err
×
40
        }
×
41
        if proc.Config.TestOutput != "" {
3✔
42
                fmt.Printf("Extracted %d test(s) from Markdown files.\n", len(proc.docTests))
1✔
43
                err = proc.writeDocTests(proc.Config.TestOutput)
1✔
44
                if err != nil {
1✔
45
                        return err
×
46
                }
×
47
        }
48
        return nil
2✔
49
}
50

51
func (proc *Processor) extractMarkdown(file, baseDir, outDir string, build bool) error {
4✔
52
        if strings.HasSuffix(strings.ToLower(file), ".json") {
5✔
53
                return nil
1✔
54
        }
1✔
55

56
        cleanPath := path.Clean(file)
3✔
57
        relPath := filepath.Clean(strings.TrimPrefix(cleanPath, baseDir))
3✔
58
        targetPath := filepath.Join(outDir, relPath)
3✔
59
        targetDir, _ := filepath.Split(targetPath)
3✔
60

3✔
61
        content, err := os.ReadFile(cleanPath)
3✔
62
        if err != nil {
3✔
63
                return err
×
64
        }
×
65
        contentStr := string(content)
3✔
66
        if strings.HasSuffix(strings.ToLower(file), ".md") {
5✔
67
                var err error
2✔
68
                contentStr, err = proc.extractTests(contentStr, []string{strings.TrimSuffix(relPath, ".md")}, 1)
2✔
69
                if err != nil {
2✔
70
                        return err
×
71
                }
×
72
        }
73

74
        if build {
6✔
75
                err = proc.mkDirs(targetDir)
3✔
76
                if err != nil {
3✔
77
                        return err
×
78
                }
×
79
                return proc.WriteFile(targetPath, contentStr)
3✔
80
        }
81
        return nil
×
82
}
83

84
func (proc *Processor) writeDocTests(dir string) error {
1✔
85
        if dir == "" {
1✔
86
                return nil
×
87
        }
×
88
        for _, test := range proc.docTests {
2✔
89
                b := strings.Builder{}
1✔
90
                err := proc.Template.ExecuteTemplate(&b, "doctest.mojo", test)
1✔
91
                if err != nil {
1✔
92
                        return err
×
93
                }
×
94
                filePath := strings.Join(test.Path, "_")
1✔
95
                filePath += "_" + test.Name + "_test.mojo"
1✔
96
                fullPath := path.Join(dir, filePath)
1✔
97

1✔
98
                parentDir, _ := filepath.Split(filepath.Clean(fullPath))
1✔
99
                err = proc.mkDirs(parentDir)
1✔
100
                if err != nil {
1✔
101
                        return err
×
102
                }
×
103

104
                err = proc.WriteFile(fullPath, b.String())
1✔
105
                if err != nil {
1✔
106
                        return err
×
107
                }
×
108
        }
109
        return nil
1✔
110
}
111

112
func (proc *Processor) extractTests(text string, elems []string, modElems int) (string, error) {
310✔
113
        t, tests, err := extractTestsText(text, elems, proc.Config.Strict)
310✔
114
        if err != nil {
310✔
115
                return "", err
×
116
        }
×
117
        proc.docTests = append(proc.docTests, tests...)
310✔
118
        return t, nil
310✔
119
}
120

121
func extractTestsText(text string, elems []string, strict bool) (string, []*docTest, error) {
310✔
122
        scanner := bufio.NewScanner(strings.NewReader(text))
310✔
123
        outText := strings.Builder{}
310✔
124

310✔
125
        fenced := fenceNone
310✔
126
        blocks := map[string]*docTest{}
310✔
127
        var blockLines []string
310✔
128
        var globalLines []string
310✔
129
        var blockName string
310✔
130
        var excluded bool
310✔
131
        var global bool
310✔
132
        var count int
310✔
133
        for scanner.Scan() {
566✔
134
                origLine := scanner.Text()
256✔
135

256✔
136
                isStart := false
256✔
137
                currFence := getFenceType(origLine)
256✔
138
                if currFence != fenceNone && fenced == fenceNone {
277✔
139
                        var ok bool
21✔
140
                        var err error
21✔
141
                        blockName, excluded, global, ok, err = parseBlockAttr(origLine)
21✔
142
                        if err != nil {
21✔
143
                                if err := warnOrError(strict, "%s in %s", err.Error(), strings.Join(elems, ".")); err != nil {
×
144
                                        return "", nil, err
×
145
                                }
×
146
                        }
147
                        if !ok {
21✔
148
                                blockName = ""
×
149
                        }
×
150
                        fenced = currFence
21✔
151
                        isStart = true
21✔
152
                }
153

154
                if !excluded {
453✔
155
                        outText.WriteString(origLine)
197✔
156
                        outText.WriteRune('\n')
197✔
157
                }
197✔
158

159
                if fenced != fenceNone && currFence != fenced && blockName != "" {
294✔
160
                        if global {
58✔
161
                                globalLines = append(globalLines, origLine)
20✔
162
                        } else {
38✔
163
                                blockLines = append(blockLines, origLine)
18✔
164
                        }
18✔
165
                }
166
                count++
256✔
167

256✔
168
                if fenced != fenceNone && currFence == fenced && !isStart {
277✔
169
                        if blockName == "" {
21✔
170
                                excluded = false
×
171
                                global = false
×
172
                                fenced = fenceNone
×
173
                                continue
×
174
                        }
175
                        if dt, ok := blocks[blockName]; ok {
33✔
176
                                dt.Code = append(dt.Code, blockLines...)
12✔
177
                                dt.Global = append(dt.Global, globalLines...)
12✔
178
                        } else {
21✔
179
                                blocks[blockName] = &docTest{
9✔
180
                                        Name:   blockName,
9✔
181
                                        Path:   elems,
9✔
182
                                        Code:   append([]string{}, blockLines...),
9✔
183
                                        Global: append([]string{}, globalLines...),
9✔
184
                                }
9✔
185
                        }
9✔
186
                        blockLines = blockLines[:0]
21✔
187
                        globalLines = globalLines[:0]
21✔
188
                        excluded = false
21✔
189
                        global = false
21✔
190
                        fenced = fenceNone
21✔
191
                }
192
        }
193
        if err := scanner.Err(); err != nil {
310✔
194
                panic(err)
×
195
        }
196
        if fenced != fenceNone {
310✔
197
                if err := warnOrError(strict, "unbalanced code block in %s", strings.Join(elems, ".")); err != nil {
×
198
                        return "", nil, err
×
199
                }
×
200
        }
201

202
        tests := make([]*docTest, 0, len(blocks))
310✔
203
        for _, block := range blocks {
319✔
204
                tests = append(tests, block)
9✔
205
        }
9✔
206
        sort.Slice(tests, func(i, j int) bool { return tests[i].Name < tests[j].Name })
311✔
207

208
        return strings.TrimSuffix(outText.String(), "\n"), tests, nil
310✔
209
}
210

211
func parseBlockAttr(line string) (name string, hide bool, global bool, ok bool, err error) {
28✔
212
        parts := strings.SplitN(line, "{", 2)
28✔
213
        if len(parts) < 2 {
29✔
214
                return
1✔
215
        }
1✔
216
        attrString := strings.TrimSpace(parts[1])
27✔
217
        if !strings.HasSuffix(attrString, "}") {
27✔
218
                err = fmt.Errorf("missing closing parentheses in code block attributes")
×
219
                return
×
220
        }
×
221
        attrString = strings.TrimSuffix(attrString, "}")
27✔
222

27✔
223
        quoted := false
27✔
224
        attrPairs := strings.FieldsFunc(attrString, func(r rune) bool {
703✔
225
                if r == '"' {
730✔
226
                        quoted = !quoted
54✔
227
                }
54✔
228
                return !quoted && r == ' '
676✔
229
        })
230

231
        for _, pair := range attrPairs {
81✔
232
                elems := strings.Split(pair, "=")
54✔
233
                if len(elems) > 2 {
54✔
234
                        err = fmt.Errorf("malformed code block attributes '%s'", pair)
×
235
                        return
×
236
                }
×
237
                if len(elems) < 2 {
56✔
238
                        continue
2✔
239
                }
240

241
                key := strings.TrimSpace(elems[0])
52✔
242
                if key == docTestAttr {
78✔
243
                        name = strings.Trim(elems[1], "\"")
26✔
244
                        continue
26✔
245
                }
246
                if key == hideAttr {
43✔
247
                        h := strings.Trim(elems[1], "\" ")
17✔
248
                        if h == "true" {
34✔
249
                                hide = true
17✔
250
                        }
17✔
251
                        continue
17✔
252
                }
253
                if key == globalAttr {
17✔
254
                        g := strings.Trim(elems[1], "\"")
8✔
255
                        if g == "true" {
16✔
256
                                global = true
8✔
257
                        }
8✔
258
                        continue
8✔
259
                }
260
        }
261
        ok = true
27✔
262
        return
27✔
263
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc