Skip to content

Commit baa4f4e

Browse files
authored
Merge pull request #323 from smvv/parse-event-path-file
Parse event path file to enable reduced CI mode
2 parents 9207261 + f4dc327 commit baa4f4e

File tree

11 files changed

+460
-19
lines changed

11 files changed

+460
-19
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
)
8+
9+
const (
10+
githubEventUnknown = "unknown"
11+
githubEventPullRequest = "pull_request"
12+
githubEventPush = "push"
13+
)
14+
15+
func detectGitHubEventTypeFromEnv() (string, error) {
16+
eventPath := os.Getenv("GITHUB_EVENT_PATH")
17+
if eventPath == "" {
18+
return githubEventUnknown, nil
19+
}
20+
21+
eventType, err := detectGitHubEventTypeFromFile(eventPath)
22+
if err != nil {
23+
return "", fmt.Errorf("read GitHub event %q: %w", eventPath, err)
24+
}
25+
26+
return eventType, nil
27+
}
28+
29+
func detectGitHubEventTypeFromFile(path string) (string, error) {
30+
data, err := os.ReadFile(path)
31+
if err != nil {
32+
return "", err
33+
}
34+
35+
var payload map[string]json.RawMessage
36+
if err := json.Unmarshal(data, &payload); err != nil {
37+
return "", err
38+
}
39+
40+
if _, ok := payload[githubEventPullRequest]; ok {
41+
return githubEventPullRequest, nil
42+
}
43+
if _, ok := payload["ref"]; ok {
44+
return githubEventPush, nil
45+
}
46+
47+
return githubEventUnknown, nil
48+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestDetectGitHubEventTypeFromEnv(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
eventPath string
16+
want string
17+
wantErr bool
18+
}{
19+
{
20+
name: "missing env var returns unknown",
21+
want: githubEventUnknown,
22+
},
23+
{
24+
name: "pull request fixture",
25+
eventPath: fixturePath(t, "extension_template_pull_request.json"),
26+
want: githubEventPullRequest,
27+
},
28+
{
29+
name: "push fixture",
30+
eventPath: fixturePath(t, "extension_template_push.json"),
31+
want: githubEventPush,
32+
},
33+
{
34+
name: "unknown fixture",
35+
eventPath: fixturePath(t, "extension_template_unknown.json"),
36+
want: githubEventUnknown,
37+
},
38+
{
39+
name: "missing file returns error",
40+
eventPath: filepath.Join(t.TempDir(), "missing.json"),
41+
wantErr: true,
42+
},
43+
}
44+
45+
for _, tc := range tests {
46+
t.Run(tc.name, func(t *testing.T) {
47+
if tc.eventPath == "" {
48+
t.Setenv("GITHUB_EVENT_PATH", "")
49+
} else {
50+
t.Setenv("GITHUB_EVENT_PATH", tc.eventPath)
51+
}
52+
53+
got, err := detectGitHubEventTypeFromEnv()
54+
if tc.wantErr {
55+
require.Error(t, err)
56+
return
57+
}
58+
59+
require.NoError(t, err)
60+
assert.Equal(t, tc.want, got)
61+
})
62+
}
63+
}
64+
65+
func TestDetectGitHubEventTypeFromEnvInvalidJSON(t *testing.T) {
66+
tmpDir := t.TempDir()
67+
invalidPath := filepath.Join(tmpDir, "invalid.json")
68+
require.NoError(t, os.WriteFile(invalidPath, []byte("{"), 0o600))
69+
70+
t.Setenv("GITHUB_EVENT_PATH", invalidPath)
71+
_, err := detectGitHubEventTypeFromEnv()
72+
require.Error(t, err)
73+
}
74+
75+
func TestMatrixSubcommandLogsDetectedEventType(t *testing.T) {
76+
eventPath := fixturePath(t, "extension_template_pull_request.json")
77+
t.Setenv("GITHUB_EVENT_PATH", eventPath)
78+
_, stderr, err := executeRootCommandWithResult(t, []string{
79+
"matrix",
80+
"--input", matrixConfigPath(t),
81+
"--platform", "linux",
82+
})
83+
require.NoError(t, err)
84+
assert.Contains(t, stderr, "\x1b[90m")
85+
assert.Contains(t, stderr, "\x1b[34mINF\x1b[0m")
86+
assert.Contains(t, stderr, "Using GitHub event payload file")
87+
assert.Contains(t, stderr, "event_path="+eventPath)
88+
assert.Contains(t, stderr, "Detected GitHub event type")
89+
assert.Contains(t, stderr, "event_type=pull_request")
90+
}
91+
92+
func TestMatrixSubcommandFailsWhenEventPathInvalid(t *testing.T) {
93+
t.Setenv("GITHUB_EVENT_PATH", filepath.Join(t.TempDir(), "missing.json"))
94+
_, _, err := executeRootCommandWithResult(t, []string{
95+
"matrix",
96+
"--input", matrixConfigPath(t),
97+
"--platform", "linux",
98+
})
99+
require.Error(t, err)
100+
assert.ErrorContains(t, err, "detect GitHub event type")
101+
}
102+
103+
func TestMatrixSubcommandPullRequestEnablesReducedCIWhenAuto(t *testing.T) {
104+
t.Setenv("GITHUB_EVENT_PATH", fixturePath(t, "extension_template_pull_request.json"))
105+
106+
inputJSON := `{
107+
"linux": {
108+
"include": [
109+
{"duckdb_arch":"linux_amd64","run_in_reduced_ci_mode":true,"opt_in":false},
110+
{"duckdb_arch":"linux_arm64","run_in_reduced_ci_mode":false,"opt_in":false}
111+
]
112+
}
113+
}`
114+
115+
outputPath, _ := runMatrixCommand(t, inputJSON, []string{
116+
"--platform", "linux",
117+
"--reduced-ci-mode", "auto",
118+
})
119+
120+
out, err := os.ReadFile(outputPath)
121+
require.NoError(t, err)
122+
assert.Contains(t, string(out), "linux_amd64")
123+
assert.NotContains(t, string(out), "linux_arm64")
124+
}
125+
126+
func fixturePath(t *testing.T, name string) string {
127+
t.Helper()
128+
return filepath.Join(moduleRootPath(t), "testdata", "github", "events", name)
129+
}
130+
131+
func matrixConfigPath(t *testing.T) string {
132+
t.Helper()
133+
return filepath.Join(moduleRootPath(t), "..", "..", "config", "distribution_matrix.json")
134+
}
135+
136+
func moduleRootPath(t *testing.T) string {
137+
t.Helper()
138+
wd, err := os.Getwd()
139+
require.NoError(t, err)
140+
return filepath.Clean(filepath.Join(wd, "..", ".."))
141+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"log/slog"
8+
"strconv"
9+
"strings"
10+
"sync"
11+
)
12+
13+
const (
14+
ansiReset = "\x1b[0m"
15+
ansiBlue = "\x1b[34m"
16+
ansiYellow = "\x1b[33m"
17+
ansiRed = "\x1b[31m"
18+
ansiGray = "\x1b[90m"
19+
)
20+
21+
func newLogger(w io.Writer) *slog.Logger {
22+
return slog.New(&prettyHandler{
23+
writer: w,
24+
level: slog.LevelInfo,
25+
})
26+
}
27+
28+
func colorizeLevel(level slog.Level) string {
29+
token := shortLevel(level)
30+
switch {
31+
case level >= slog.LevelError:
32+
return ansiRed + token + ansiReset
33+
case level >= slog.LevelWarn:
34+
return ansiYellow + token + ansiReset
35+
case level <= slog.LevelDebug:
36+
return ansiGray + token + ansiReset
37+
default:
38+
return ansiBlue + token + ansiReset
39+
}
40+
}
41+
42+
func shortLevel(level slog.Level) string {
43+
switch {
44+
case level >= slog.LevelError:
45+
return "ERR"
46+
case level >= slog.LevelWarn:
47+
return "WRN"
48+
case level <= slog.LevelDebug:
49+
return "DBG"
50+
default:
51+
return "INF"
52+
}
53+
}
54+
55+
type prettyHandler struct {
56+
writer io.Writer
57+
level slog.Level
58+
attrs []slog.Attr
59+
group string
60+
mu sync.Mutex
61+
}
62+
63+
func (h prettyHandler) Enabled(ctx context.Context, level slog.Level) bool {
64+
return level >= h.level
65+
}
66+
67+
func (h prettyHandler) Handle(ctx context.Context, record slog.Record) error {
68+
var b strings.Builder
69+
b.WriteString(ansiGray)
70+
b.WriteString(record.Time.Format("15:04"))
71+
b.WriteString(ansiReset)
72+
b.WriteByte(' ')
73+
b.WriteString(colorizeLevel(record.Level))
74+
if record.Message != "" {
75+
b.WriteByte(' ')
76+
b.WriteString(record.Message)
77+
}
78+
79+
for _, attr := range h.attrs {
80+
appendAttr(&b, h.group, attr)
81+
}
82+
record.Attrs(func(attr slog.Attr) bool {
83+
appendAttr(&b, h.group, attr)
84+
return true
85+
})
86+
87+
b.WriteByte('\n')
88+
89+
h.mu.Lock()
90+
defer h.mu.Unlock()
91+
_, err := io.WriteString(h.writer, b.String())
92+
return err
93+
}
94+
95+
func (h prettyHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
96+
cloned := h
97+
cloned.attrs = append(append([]slog.Attr{}, h.attrs...), attrs...)
98+
return &cloned
99+
}
100+
101+
func (h prettyHandler) WithGroup(name string) slog.Handler {
102+
cloned := h
103+
if cloned.group == "" {
104+
cloned.group = name
105+
} else {
106+
cloned.group = cloned.group + "." + name
107+
}
108+
return &cloned
109+
}
110+
111+
func appendAttr(b *strings.Builder, group string, attr slog.Attr) {
112+
attr.Value = attr.Value.Resolve()
113+
if attr.Equal(slog.Attr{}) {
114+
return
115+
}
116+
b.WriteByte(' ')
117+
if group != "" {
118+
b.WriteString(group)
119+
b.WriteByte('.')
120+
}
121+
b.WriteString(attr.Key)
122+
b.WriteByte('=')
123+
b.WriteString(formatAttrValue(attr.Value.Any()))
124+
}
125+
126+
func formatAttrValue(v any) string {
127+
s := fmt.Sprint(v)
128+
if strings.ContainsAny(s, " \t\n\r\"") {
129+
return strconv.Quote(s)
130+
}
131+
return s
132+
}

scripts/extbuild/cmd/extbuild/matrix.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"fmt"
5+
"log/slog"
56
"os"
67

78
"github.com/duckdb/extension-ci-tools/internal/distmatrix"
@@ -10,19 +11,40 @@ import (
1011

1112
func newMatrixCommand() *cobra.Command {
1213
var (
13-
inputPath string
14-
platformsRaw string
15-
archsRaw string
16-
excludeRaw string
17-
optInRaw string
18-
reducedCIMode string
19-
outPath string
14+
inputPath string
15+
platformsRaw string
16+
archsRaw string
17+
excludeRaw string
18+
optInRaw string
19+
reducedCIModeRaw string
20+
outPath string
2021
)
2122

2223
cmd := &cobra.Command{
2324
Use: "matrix",
2425
Short: "Compute distribution matrices and emit GitHub output lines",
2526
RunE: func(cmd *cobra.Command, _ []string) error {
27+
if eventPath := os.Getenv("GITHUB_EVENT_PATH"); eventPath != "" {
28+
slog.Info("Using GitHub event payload file", "event_path", eventPath)
29+
} else {
30+
slog.Info("GITHUB_EVENT_PATH is not set so event type is unknown")
31+
}
32+
33+
eventType, err := detectGitHubEventTypeFromEnv()
34+
if err != nil {
35+
return fmt.Errorf("detect GitHub event type: %w", err)
36+
}
37+
slog.Info("Detected GitHub event type", "event_type", eventType)
38+
39+
reducedCIMode, err := distmatrix.ParseReducedCIMode(reducedCIModeRaw)
40+
if err != nil {
41+
return err
42+
}
43+
if eventType == githubEventPullRequest && reducedCIMode == distmatrix.ReducedCIAuto {
44+
reducedCIMode = distmatrix.ReducedCIEnabled
45+
slog.Info("Enabled reduced CI mode for pull_request event when mode is auto")
46+
}
47+
2648
data, err := os.ReadFile(inputPath)
2749
if err != nil {
2850
return fmt.Errorf("read input matrix %q: %w", inputPath, err)
@@ -37,7 +59,7 @@ func newMatrixCommand() *cobra.Command {
3759
Arch: archsRaw,
3860
Exclude: excludeRaw,
3961
OptIn: optInRaw,
40-
ReducedCIMode: distmatrix.ReducedCIMode(reducedCIMode),
62+
ReducedCIMode: reducedCIMode,
4163
})
4264
if err != nil {
4365
return fmt.Errorf("compute platform matrices: %w", err)
@@ -69,7 +91,7 @@ func newMatrixCommand() *cobra.Command {
6991
cmd.Flags().StringVar(&archsRaw, "arch", "", "Comma-separated list of arch tokens (amd64;arm64)")
7092
cmd.Flags().StringVar(&excludeRaw, "exclude", "", "Comma-separated list of duckdb_arch values to exclude")
7193
cmd.Flags().StringVar(&optInRaw, "opt-in", "", "Comma-separated list of opt-in duckdb_arch values")
72-
cmd.Flags().StringVar(&reducedCIMode, "reduced-ci-mode", "", "Reduced CI mode: auto|enabled|disabled")
94+
cmd.Flags().StringVar(&reducedCIModeRaw, "reduced-ci-mode", "", "Reduced CI mode: auto|enabled|disabled")
7395
cmd.Flags().StringVar(&outPath, "out", "", "Path to write GitHub output lines")
7496

7597
return cmd

0 commit comments

Comments
 (0)