Skip to content

Commit 0e95a28

Browse files
committed
cmd: expose MakeMain() for testing
I'd like to add the kargo-demo repository to Unity to test evalv3, but can't get a handle on the main function to wire up to testscript. This patch fixes the problem by moving the MakeMain function to a public package so the kargo-demo go module can import and call it using the go mod tools technique.
1 parent 54efe3e commit 0e95a28

File tree

6 files changed

+120
-117
lines changed

6 files changed

+120
-117
lines changed

cmd/cmd.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
"os"
8+
"runtime/pprof"
9+
"runtime/trace"
10+
11+
"github.com/holos-run/holos/internal/cli"
12+
"github.com/holos-run/holos/internal/holos"
13+
)
14+
15+
// MakeMain makes a main function for the cli or tests.
16+
func MakeMain(options ...holos.Option) func() int {
17+
return func() (exitCode int) {
18+
cfg := holos.New(options...)
19+
slog.SetDefault(cfg.Logger())
20+
ctx := context.Background()
21+
22+
if format := os.Getenv("HOLOS_CPU_PROFILE"); format != "" {
23+
f, _ := os.Create(fmt.Sprintf(format, os.Getppid(), os.Getpid()))
24+
err := pprof.StartCPUProfile(f)
25+
defer func() {
26+
pprof.StopCPUProfile()
27+
f.Close()
28+
}()
29+
if err != nil {
30+
return cli.HandleError(ctx, err, cfg)
31+
}
32+
}
33+
defer memProfile(ctx, cfg)
34+
35+
if format := os.Getenv("HOLOS_TRACE"); format != "" {
36+
f, _ := os.Create(fmt.Sprintf(format, os.Getppid(), os.Getpid()))
37+
err := trace.Start(f)
38+
defer func() {
39+
trace.Stop()
40+
f.Close()
41+
}()
42+
if err != nil {
43+
return cli.HandleError(ctx, err, cfg)
44+
}
45+
}
46+
47+
feature := &holos.EnvFlagger{}
48+
if err := cli.New(cfg, feature).ExecuteContext(ctx); err != nil {
49+
return cli.HandleError(ctx, err, cfg)
50+
}
51+
return 0
52+
}
53+
}
54+
55+
func memProfile(ctx context.Context, cfg *holos.Config) {
56+
if format := os.Getenv("HOLOS_MEM_PROFILE"); format != "" {
57+
f, _ := os.Create(fmt.Sprintf(format, os.Getppid(), os.Getpid()))
58+
defer f.Close()
59+
if err := pprof.WriteHeapProfile(f); err != nil {
60+
_ = cli.HandleError(ctx, err, cfg)
61+
}
62+
}
63+
}

cmd/holos/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package main
33
import (
44
"os"
55

6-
"github.com/holos-run/holos/internal/cli"
6+
"github.com/holos-run/holos/cmd"
77
)
88

99
func main() {
10-
os.Exit(cli.MakeMain()())
10+
os.Exit(cmd.MakeMain()())
1111
}

cmd/holos/main_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import (
66
"testing"
77

88
cue "cuelang.org/go/cmd/cue/cmd"
9-
"github.com/holos-run/holos/internal/cli"
9+
"github.com/holos-run/holos/cmd"
1010
"github.com/rogpeppe/go-internal/testscript"
1111
)
1212

1313
func TestMain(m *testing.M) {
1414
os.Exit(testscript.RunMain(m, map[string]func() int{
15-
"holos": cli.MakeMain(),
15+
"holos": cmd.MakeMain(),
1616
"cue": cue.Main,
1717
}))
1818
}

internal/cli/main.go

Lines changed: 0 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1 @@
11
package cli
2-
3-
import (
4-
"context"
5-
"fmt"
6-
"log/slog"
7-
"os"
8-
"runtime/pprof"
9-
"runtime/trace"
10-
11-
"connectrpc.com/connect"
12-
cue "cuelang.org/go/cue/errors"
13-
"github.com/holos-run/holos/internal/errors"
14-
"github.com/holos-run/holos/internal/holos"
15-
"google.golang.org/genproto/googleapis/rpc/errdetails"
16-
)
17-
18-
func memProfile(ctx context.Context, cfg *holos.Config) {
19-
if format := os.Getenv("HOLOS_MEM_PROFILE"); format != "" {
20-
f, _ := os.Create(fmt.Sprintf(format, os.Getppid(), os.Getpid()))
21-
defer f.Close()
22-
if err := pprof.WriteHeapProfile(f); err != nil {
23-
_ = HandleError(ctx, err, cfg)
24-
}
25-
}
26-
}
27-
28-
// MakeMain makes a main function for the cli or tests.
29-
func MakeMain(options ...holos.Option) func() int {
30-
return func() (exitCode int) {
31-
cfg := holos.New(options...)
32-
slog.SetDefault(cfg.Logger())
33-
ctx := context.Background()
34-
35-
if format := os.Getenv("HOLOS_CPU_PROFILE"); format != "" {
36-
f, _ := os.Create(fmt.Sprintf(format, os.Getppid(), os.Getpid()))
37-
err := pprof.StartCPUProfile(f)
38-
defer func() {
39-
pprof.StopCPUProfile()
40-
f.Close()
41-
}()
42-
if err != nil {
43-
return HandleError(ctx, err, cfg)
44-
}
45-
}
46-
defer memProfile(ctx, cfg)
47-
48-
if format := os.Getenv("HOLOS_TRACE"); format != "" {
49-
f, _ := os.Create(fmt.Sprintf(format, os.Getppid(), os.Getpid()))
50-
err := trace.Start(f)
51-
defer func() {
52-
trace.Stop()
53-
f.Close()
54-
}()
55-
if err != nil {
56-
return HandleError(ctx, err, cfg)
57-
}
58-
}
59-
60-
feature := &holos.EnvFlagger{}
61-
if err := New(cfg, feature).ExecuteContext(ctx); err != nil {
62-
return HandleError(ctx, err, cfg)
63-
}
64-
return 0
65-
}
66-
}
67-
68-
// HandleError is the top level error handler that unwraps and logs errors.
69-
func HandleError(ctx context.Context, err error, hc *holos.Config) (exitCode int) {
70-
// Connect errors have codes, log them.
71-
log := hc.NewTopLevelLogger().With("code", connect.CodeOf(err))
72-
var cueErr cue.Error
73-
var errAt *errors.ErrorAt
74-
75-
if errors.As(err, &errAt) {
76-
loc := errAt.Source.Loc()
77-
err2 := errAt.Unwrap()
78-
log.ErrorContext(ctx, fmt.Sprintf("could not run: %s at %s", err2, loc), "err", err2, "loc", loc)
79-
} else {
80-
log.ErrorContext(ctx, fmt.Sprintf("could not run: %s", err), "err", err)
81-
}
82-
83-
// cue errors are bundled up as a list and refer to multiple files / lines.
84-
if errors.As(err, &cueErr) {
85-
msg := cue.Details(cueErr, nil)
86-
if _, err := fmt.Fprint(hc.Stderr(), msg); err != nil {
87-
log.ErrorContext(ctx, "could not write CUE error details: "+err.Error(), "err", err)
88-
}
89-
}
90-
// connect errors have details and codes.
91-
// Refer to https://connectrpc.com/docs/go/errors
92-
if connectErr := new(connect.Error); errors.As(err, &connectErr) {
93-
for _, detail := range connectErr.Details() {
94-
msg, valueErr := detail.Value()
95-
if valueErr != nil {
96-
log.WarnContext(ctx, "could not decode error detail", "err", err, "type", detail.Type(), "note", "this usually means we don't have the schema for the protobuf message type")
97-
continue
98-
}
99-
if info, ok := msg.(*errdetails.ErrorInfo); ok {
100-
logDetail := log.With("reason", info.GetReason(), "domain", info.GetDomain())
101-
for k, v := range info.GetMetadata() {
102-
logDetail = logDetail.With(k, v)
103-
}
104-
logDetail.ErrorContext(ctx, info.String())
105-
}
106-
}
107-
}
108-
109-
return 1
110-
}

internal/cli/root.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package cli
22

33
import (
4+
"context"
45
_ "embed"
56
"fmt"
67
"log/slog"
78

9+
"connectrpc.com/connect"
810
"github.com/spf13/cobra"
11+
"google.golang.org/genproto/googleapis/rpc/errdetails"
912

1013
"github.com/holos-run/holos/version"
1114

15+
"github.com/holos-run/holos/internal/errors"
1216
"github.com/holos-run/holos/internal/holos"
1317
"github.com/holos-run/holos/internal/logger"
1418
"github.com/holos-run/holos/internal/server"
@@ -28,7 +32,8 @@ import (
2832
"github.com/holos-run/holos/internal/cli/token"
2933
"github.com/holos-run/holos/internal/cli/txtar"
3034

31-
cue "cuelang.org/go/cmd/cue/cmd"
35+
cueCmd "cuelang.org/go/cmd/cue/cmd"
36+
cue_errors "cuelang.org/go/cue/errors"
3237
)
3338

3439
//go:embed help.txt
@@ -119,7 +124,7 @@ func newOrgCmd(feature holos.Flagger) (cmd *cobra.Command) {
119124

120125
func newCueCmd() (cmd *cobra.Command) {
121126
// Get a handle on the cue root command fields.
122-
root, _ := cue.New([]string{})
127+
root, _ := cueCmd.New([]string{})
123128
// Copy the fields to our embedded command.
124129
cmd = command.New("cue")
125130
cmd.Short = root.Short
@@ -130,8 +135,52 @@ func newCueCmd() (cmd *cobra.Command) {
130135

131136
// We do it this way so we handle errors correctly.
132137
cmd.RunE = func(cmd *cobra.Command, args []string) error {
133-
cueRootCommand, _ := cue.New(args)
138+
cueRootCommand, _ := cueCmd.New(args)
134139
return cueRootCommand.Run(cmd.Root().Context())
135140
}
136141
return cmd
137142
}
143+
144+
// HandleError is the top level error handler that unwraps and logs errors.
145+
func HandleError(ctx context.Context, err error, hc *holos.Config) (exitCode int) {
146+
// Connect errors have codes, log them.
147+
log := hc.NewTopLevelLogger().With("code", connect.CodeOf(err))
148+
var cueErr cue_errors.Error
149+
var errAt *errors.ErrorAt
150+
151+
if errors.As(err, &errAt) {
152+
loc := errAt.Source.Loc()
153+
err2 := errAt.Unwrap()
154+
log.ErrorContext(ctx, fmt.Sprintf("could not run: %s at %s", err2, loc), "err", err2, "loc", loc)
155+
} else {
156+
log.ErrorContext(ctx, fmt.Sprintf("could not run: %s", err), "err", err)
157+
}
158+
159+
// cue errors are bundled up as a list and refer to multiple files / lines.
160+
if errors.As(err, &cueErr) {
161+
msg := cue_errors.Details(cueErr, nil)
162+
if _, err := fmt.Fprint(hc.Stderr(), msg); err != nil {
163+
log.ErrorContext(ctx, "could not write CUE error details: "+err.Error(), "err", err)
164+
}
165+
}
166+
// connect errors have details and codes.
167+
// Refer to https://connectrpc.com/docs/go/errors
168+
if connectErr := new(connect.Error); errors.As(err, &connectErr) {
169+
for _, detail := range connectErr.Details() {
170+
msg, valueErr := detail.Value()
171+
if valueErr != nil {
172+
log.WarnContext(ctx, "could not decode error detail", "err", err, "type", detail.Type(), "note", "this usually means we don't have the schema for the protobuf message type")
173+
continue
174+
}
175+
if info, ok := msg.(*errdetails.ErrorInfo); ok {
176+
logDetail := log.With("reason", info.GetReason(), "domain", info.GetDomain())
177+
for k, v := range info.GetMetadata() {
178+
logDetail = logDetail.With(k, v)
179+
}
180+
logDetail.ErrorContext(ctx, info.String())
181+
}
182+
}
183+
}
184+
185+
return 1
186+
}

version/embedded/patch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7
1+
8

0 commit comments

Comments
 (0)