inspektor-gadget

Форк
0
237 строк · 7.2 Кб
1
// Copyright 2023 The Inspektor Gadget authors
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package main
16

17
import (
18
	"bytes"
19
	"encoding/json"
20
	"flag"
21
	"fmt"
22
	"log"
23
	"os"
24
	"sort"
25
	"time"
26

27
	"github.com/medyagh/gopogh/pkg/models"
28
	"github.com/medyagh/gopogh/pkg/parser"
29
	"github.com/medyagh/gopogh/pkg/report"
30
)
31

32
const (
33
	// summaryLimitInBytes is the maximum size of the summary in bytes.
34
	// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#step-isolation-and-limits
35
	summaryLimitInBytes = 1000000
36
	// conclusion is the conclusion (success, failure, skipped and cancelled) indicating GitHub Action test step status.
37
	// https://docs.github.com/en/actions/learn-github-actions/contexts#steps-context
38
	conclusionSuccess   = "success"
39
	conclusionFailure   = "failure"
40
	conclusionSkipped   = "skipped"
41
	conclusionCancelled = "cancelled"
42
)
43

44
var ErrInvalidContent = fmt.Errorf("invalid content")
45

46
var (
47
	inPath     = flag.String("in", "", "path to JSON file produced by go tool test2json")
48
	outPath    = flag.String("out", "", "path to output file")
49
	outSummary = flag.String("out_summary", "", "path to summary file")
50
	conclusion = flag.String("conclusion", "", "conclusion (success, failure, skipped and cancelled) indicating GitHub Action test step status")
51
)
52

53
func main() {
54
	flag.Parse()
55

56
	if *inPath == "" {
57
		log.Fatal("must provide path to JSON input file")
58
	}
59

60
	if *outPath == "" {
61
		log.Fatal("must provide path to output file")
62
	}
63

64
	events, err := parser.ParseJSON(*inPath)
65
	if err != nil {
66
		log.Fatal(err)
67
	}
68
	groups := parser.ProcessEvents(events)
69
	content, err := report.Generate(models.ReportDetail{}, groups)
70
	if err != nil {
71
		log.Fatal(err)
72
	}
73

74
	if *outSummary != "" {
75
		r, err := summaryForContent(content)
76
		if err != nil {
77
			log.Fatal(err)
78
		}
79
		if err = os.WriteFile(*outSummary, r, 0644); err != nil {
80
			log.Fatal(err)
81
		}
82
	}
83

84
	markdown, err := markdownForContent(content)
85
	if err != nil {
86
		log.Fatal(err)
87
	}
88

89
	if err = os.WriteFile(*outPath, markdown, 0644); err != nil {
90
		log.Fatal(err)
91
	}
92
}
93

94
func markdownForContent(content report.DisplayContent) ([]byte, error) {
95
	// validation
96
	if _, ok := content.Results["pass"]; !ok {
97
		return nil, fmt.Errorf("checking passed tests: %w", ErrInvalidContent)
98
	}
99
	if _, ok := content.Results["skip"]; !ok {
100
		return nil, fmt.Errorf("checking skip tests: %w", ErrInvalidContent)
101
	}
102
	if _, ok := content.Results["fail"]; !ok {
103
		return nil, fmt.Errorf("checking failed tests: %w", ErrInvalidContent)
104
	}
105

106
	// set report status icon
107
	var statusIcon string
108
	switch *conclusion {
109
	case conclusionFailure:
110
		statusIcon = ":red_circle:"
111
	case conclusionSkipped:
112
		fallthrough
113
	case conclusionCancelled:
114
		statusIcon = ":white_circle:"
115
	case conclusionSuccess:
116
		fallthrough
117
	default:
118
		statusIcon = ":green_circle:"
119
	}
120

121
	// summary
122
	var buf bytes.Buffer
123
	fmt.Fprintf(&buf, "### Test Report %s\n", statusIcon)
124
	fmt.Fprintf(&buf, "#### Summary\n")
125
	fmt.Fprintf(&buf, "| Total Tests | Passed :heavy_check_mark: | Failed :x: | Skipped :arrow_right_hook: |\n")
126
	fmt.Fprintf(&buf, "| ----- | ---- | ---- | ---- |\n")
127
	fmt.Fprintf(&buf, "| %d | %d | %d | %d |\n", content.TotalTests,
128
		len(content.Results["pass"]), len(content.Results["fail"]), len(content.Results["skip"]))
129

130
	// test durations
131
	fmt.Fprintf(&buf, "#### Test Durations :stopwatch:\n")
132
	appendDuration(content, &buf, "Passed", "pass")
133
	appendDuration(content, &buf, "Failed", "fail")
134
	appendDuration(content, &buf, "Skipped", "skip")
135

136
	// failed tests
137
	if len(content.Results["fail"]) > 0 {
138
		fmt.Fprintf(&buf, "\n#### Failed Tests\n")
139
		for _, test := range content.Results["fail"] {
140
			s, d := testEventToDetailsBlock(test.TestName, test.Events)
141
			// check if we are over the limit
142
			if buf.Len()+s > summaryLimitInBytes {
143
				fmt.Fprintf(&buf, "<details><summary>%s</summary>\n\n", test.TestName)
144
				fmt.Fprintf(&buf, "Logs skipped due to size limitations. Please check workflow [logs](%s) for details.\n", ghaJobUrl())
145
				fmt.Fprintf(&buf, "</details>\n")
146
				continue
147
			}
148

149
			fmt.Fprintf(&buf, "%s", d)
150
		}
151
		fmt.Fprintf(&buf, "\n")
152
	}
153

154
	return buf.Bytes(), nil
155
}
156

157
func summaryForContent(content report.DisplayContent) ([]byte, error) {
158
	var s struct {
159
		Id          string `json:"id"`
160
		RunId       string `json:"run_id"`
161
		RefName     string `json:"ref_name"`
162
		PullRequest struct {
163
			Id     string `json:"id"`
164
			Author string `json:"author"`
165
			Title  string `json:"title"`
166
		} `json:"pull_request"`
167
		Summary struct {
168
			Conclusion string   `json:"conclusion"`
169
			Pass       []string `json:"pass"`
170
			Fail       []string `json:"fail"`
171
			Skip       []string `json:"skip"`
172
		} `json:"summary"`
173
	}
174

175
	if os.Getenv("GITHUB_ACTIONS") != "true" {
176
		return nil, fmt.Errorf("summary is only available in GitHub Actions")
177
	}
178

179
	s.Id = fmt.Sprintf("%s_%s", os.Getenv("GITHUB_RUN_NUMBER"), os.Getenv("GITHUB_RUN_ATTEMPT"))
180
	s.RunId = os.Getenv("GITHUB_RUN_ID")
181
	s.RefName = os.Getenv("GITHUB_REF_NAME")
182
	s.PullRequest.Id = os.Getenv("PULL_REQUEST_ID")
183
	s.PullRequest.Author = os.Getenv("PULL_REQUEST_AUTHOR")
184
	s.PullRequest.Title = os.Getenv("PULL_REQUEST_TITLE")
185

186
	for _, test := range content.Results["pass"] {
187
		s.Summary.Pass = append(s.Summary.Pass, test.TestName)
188
	}
189
	for _, test := range content.Results["fail"] {
190
		s.Summary.Fail = append(s.Summary.Fail, test.TestName)
191
	}
192
	for _, test := range content.Results["skip"] {
193
		s.Summary.Skip = append(s.Summary.Skip, test.TestName)
194
	}
195
	s.Summary.Conclusion = *conclusion
196

197
	return json.Marshal(s)
198
}
199

200
func appendDuration(content report.DisplayContent, buf *bytes.Buffer, title, status string) {
201
	if len(content.Results[status]) == 0 {
202
		return
203
	}
204
	fmt.Fprintf(buf, "<details><summary>%s</summary>\n\n", title)
205
	fmt.Fprintf(buf, "| Duration | Test | Run Order |\n")
206
	fmt.Fprintf(buf, "| -------- | ---- | --------- |\n")
207
	for _, test := range sortTestGroups(content.Results[status]) {
208
		fmt.Fprintf(buf, "| %s | %s | %d |\n", time.Duration(test.Duration*float64(time.Second)), test.TestName, test.TestOrder)
209
	}
210
	fmt.Fprintf(buf, "</details>\n")
211
}
212

213
func sortTestGroups(groups []models.TestGroup) []models.TestGroup {
214
	sort.Slice(groups, func(i, j int) bool {
215
		return groups[i].Duration > groups[j].Duration
216
	})
217
	return groups
218
}
219

220
func testEventToDetailsBlock(name string, events []models.TestEvent) (int, []byte) {
221
	var buf bytes.Buffer
222
	fmt.Fprintf(&buf, "<details><summary>%s</summary>\n\n", name)
223
	fmt.Fprintf(&buf, "```code\n")
224
	for _, event := range events {
225
		fmt.Fprintf(&buf, "%s", event.Output)
226
	}
227
	fmt.Fprintf(&buf, "```\n")
228
	fmt.Fprintf(&buf, "</details>\n")
229
	return len(buf.Bytes()), buf.Bytes()
230
}
231

232
func ghaJobUrl() string {
233
	if os.Getenv("GITHUB_ACTIONS") != "true" {
234
		return ""
235
	}
236
	return os.Getenv("GITHUB_SERVER_URL") + "/" + os.Getenv("GITHUB_REPOSITORY") + "/actions/runs/" + os.Getenv("GITHUB_RUN_ID") + "/attempts/" + os.Getenv("GITHUB_RUN_ATTEMPT")
237
}
238

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.