-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgen_tests_json.go
More file actions
144 lines (127 loc) · 4.24 KB
/
gen_tests_json.go
File metadata and controls
144 lines (127 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package main
import (
"encoding/json"
"flag"
"fmt"
"maps"
"os"
"path/filepath"
"slices"
"strings"
)
const (
testsJSONFile = "tests.json"
errMsg = "No tests.json file generated"
)
// testCommands holds the test commands for supported languages.
// The language key should be lowercase.
var testCommands = map[string]string{
"go": "go test -v -tags solution ./...",
"c#": "dotnet -v",
}
type score struct {
TestName string
MaxScore int
Weight int
}
// deduplicateScores returns a slice of unique score objects based on TestName.
func deduplicateScores(scores []score) []score {
seenMap := make(map[string]score)
for _, s := range scores {
if _, exists := seenMap[s.TestName]; !exists {
seenMap[s.TestName] = s
}
}
uniqueScores := slices.Collect(maps.Values(seenMap))
slices.SortFunc(uniqueScores, func(a, b score) int {
return strings.Compare(a.TestName, b.TestName)
})
return uniqueScores
}
// formatCompactJSON formats the score slice as JSON with each test entry on a single line
func formatCompactJSON(scores []score) ([]byte, error) {
var lines []string
lines = append(lines, "[")
for i, s := range scores {
// Marshal each score object to a single line
data, err := json.Marshal(s)
if err != nil {
return nil, err
}
line := " " + string(data)
if i < len(scores)-1 {
line += ","
}
lines = append(lines, line)
}
lines = append(lines, "]")
return []byte(strings.Join(lines, "\n") + "\n"), nil
}
func genTestsJSON(args []string) {
fs := flag.NewFlagSet(genTestsJSONCmd, flag.ExitOnError)
labs := fs.String("labs", "", "Lab folders to generate tests.json for (space separated)")
view := fs.Bool("view", false, "Show the JSON output of the generated tests.json file")
runnerLang := fs.String("runner", "go", "Test runner's programming language (default: go)")
if err := fs.Parse(args); err != nil {
exitErr(err, "Error parsing flags")
}
lang := strings.ToLower(*runnerLang)
testRunnerCmd, ok := testCommands[lang]
if !ok {
languages := slices.Collect(maps.Keys(testCommands))
exitErr(fmt.Errorf("unsupported language %q, supported languages are: %v", lang, languages), errMsg)
}
for dir := range strings.SplitSeq(*labs, " ") {
if !exists(courseRepoPath(dir)) {
exitErr(fmt.Errorf("directory %q does not exist", dir), errMsg)
}
// We can should run the test command with the SCORE_INIT environment variable set to 1,
// which will avoid running the tests and only initialize the score objects.
if err := os.Setenv("SCORE_INIT", "1"); err != nil {
exitErr(err, errMsg)
}
// Annoyingly, we need to run this with both -v and ./... to get the JSON output.
// The ./... because some labs contain several subdirectories with tests.
// Running without the -v flag, it will suppress the output and we won't get the JSON.
scoreList, err := runCommandWithOutput[[]score](courseRepoPath(dir), strings.Split(testRunnerCmd, " ")...)
if err != nil {
fmt.Printf("Error running %q for %s: %v\n", testRunnerCmd, dir, err)
fmt.Printf("You may want to run %q manually to debug the issue.\n", testRunnerCmd)
// Try to process the scores anyway; this will allow us to generate a tests.json
// file even if the command fails. This is usually fine since the empty score
// objects that we need are usually printed at the start of the test output.
// The exception could be if there was a compile error or panic situation.
}
// Skip generating tests.json if there are no tests
if len(scoreList) == 0 {
fmt.Printf("No tests found for %s, skipping %s generation\n", dir, testsJSONFile)
continue
}
scoreList = deduplicateScores(scoreList)
// Write the scores to tests.json
file := filepath.Join(courseRepoPath(dir), testsJSONFile)
f, err := os.Create(file)
if err != nil {
exitErr(err, errMsg)
}
defer f.Close()
// Use custom formatter to keep each test entry on a single line
jsonData, err := formatCompactJSON(scoreList)
if err != nil {
exitErr(err, errMsg)
}
if _, err := f.Write(jsonData); err != nil {
exitErr(err, errMsg)
}
fmt.Printf("Generated %s for %s\n", testsJSONFile, courseRepoPath(dir))
if *view {
for _, s := range scoreList {
jsonData, err := json.Marshal(s)
if err != nil {
continue
}
fmt.Println(string(jsonData))
}
}
}
}