Skip to content

Commit 4577165

Browse files
committed
feat(test): wire --output into runTestAndWait for server and dry-run paths
Signed-off-by: caesarsage <destinyerhabor6@gmail.com>
1 parent 6bd82fc commit 4577165

3 files changed

Lines changed: 98 additions & 38 deletions

File tree

cmd/test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/microcks/microcks-cli/pkg/config"
2626
"github.com/microcks/microcks-cli/pkg/connectors"
2727
"github.com/microcks/microcks-cli/pkg/errors"
28+
"github.com/microcks/microcks-cli/pkg/output"
2829
"github.com/spf13/cobra"
2930
)
3031

@@ -45,6 +46,7 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
4546
readyTimeout time.Duration
4647
watch bool
4748
driver string
49+
outputFormat string
4850
)
4951
var testCmd = &cobra.Command{
5052

@@ -82,6 +84,11 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
8284
os.Exit(1)
8385
}
8486

87+
if !output.IsValid(outputFormat) {
88+
fmt.Fprintln(os.Stderr, "--output must be one of: text, json, yaml, github-actions")
89+
os.Exit(1)
90+
}
91+
8592
// Collect optional HTTPS transport flags.
8693
config.InsecureTLS = globalClientOpts.InsecureTLS
8794
config.CaCertPaths = globalClientOpts.CaCertPaths
@@ -121,6 +128,7 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
121128
filteredOperations: filteredOperations,
122129
operationsHeaders: operationsHeaders,
123130
oAuth2Context: oAuth2Context,
131+
outputFormat: outputFormat,
124132
}
125133

126134
if !dryRun {
@@ -214,11 +222,11 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
214222

215223
success, testResultID, err := runTestAndWait(mc, params)
216224
if err != nil {
217-
fmt.Print(err)
225+
fmt.Fprintln(os.Stderr, err)
218226
os.Exit(1)
219227
}
220228

221-
fmt.Printf("Full TestResult details are available here: %s/#/tests/%s \n", serverAddr, testResultID)
229+
fmt.Fprintf(progressWriter(outputFormat), "Full TestResult details are available here: %s/#/tests/%s \n", serverAddr, testResultID)
222230

223231
if !success {
224232
os.Exit(1)
@@ -237,6 +245,7 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
237245
testCmd.Flags().DurationVar(&readyTimeout, "ready-timeout", 90*time.Second, "How long to wait for the ephemeral container to be ready (--dry-run only)")
238246
testCmd.Flags().BoolVar(&watch, "watch", false, "Watch the artifact file and re-run the test on change (--dry-run only)")
239247
testCmd.Flags().StringVar(&driver, "driver", "", "Container runtime for --dry-run: 'docker' or 'podman' (default: auto-detect)")
248+
testCmd.Flags().StringVar(&outputFormat, "output", "text", "Output format: text, json, yaml, or github-actions")
240249

241250
return testCmd
242251
}

cmd/testDryRun.go

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package cmd
1818
import (
1919
"context"
2020
"fmt"
21+
"io"
2122
"net/url"
2223
"os"
2324
"os/exec"
@@ -68,8 +69,6 @@ func configureDriver(driver string) error {
6869
}
6970
}
7071

71-
// shouldUsePodman auto-detects podman only when it's clearly the intended
72-
// runtime: no explicit DOCKER_HOST, podman on PATH, and docker absent.
7372
func shouldUsePodman() bool {
7473
if os.Getenv("DOCKER_HOST") != "" {
7574
return false // respect an explicitly configured endpoint
@@ -142,14 +141,18 @@ func rewriteLocalEndpoint(testEndpoint string) (string, int, bool) {
142141
}
143142

144143
func runDryRunTest(opts dryRunOptions) bool {
144+
// Progress/diagnostics go to stderr for machine output formats so stdout
145+
// carries only the formatted result.
146+
progress := progressWriter(opts.params.outputFormat)
147+
145148
if err := validateDryRunOptions(opts); err != nil {
146-
fmt.Println(err)
149+
fmt.Fprintln(os.Stderr, err)
147150
return false
148151
}
149152

150153
// Select the container runtime (docker default, podman wired via DOCKER_HOST).
151154
if err := configureDriver(opts.driver); err != nil {
152-
fmt.Println(err)
155+
fmt.Fprintln(os.Stderr, err)
153156
return false
154157
}
155158

@@ -164,32 +167,32 @@ func runDryRunTest(opts dryRunOptions) bool {
164167
// A localhost test endpoint refers to the user's machine, not the
165168
// container: expose the port and point Microcks at the host gateway.
166169
if rewritten, hostPort, ok := rewriteLocalEndpoint(opts.params.testEndpoint); ok {
167-
fmt.Printf("Test endpoint %s is local: reaching it from the container as %s\n", opts.params.testEndpoint, rewritten)
170+
fmt.Fprintf(progress, "Test endpoint %s is local: reaching it from the container as %s\n", opts.params.testEndpoint, rewritten)
168171
opts.params.testEndpoint = rewritten
169172
containerOpts = append(containerOpts, testcontainers.WithHostPortAccess(hostPort))
170173
}
171174

172-
fmt.Printf("Starting ephemeral Microcks container (%s)...\n", opts.image)
175+
fmt.Fprintf(progress, "Starting ephemeral Microcks container (%s)...\n", opts.image)
173176
startCtx, startCancel := context.WithTimeout(ctx, opts.readyTimeout)
174177
defer startCancel()
175178

176179
container, err := microcks.Run(startCtx, opts.image, containerOpts...)
177180
if err != nil {
178-
fmt.Printf("Failed to start ephemeral Microcks container: %s\n", err)
179-
fmt.Println("Check that the container runtime is running, the port is free and the image is reachable (or raise --ready-timeout).")
181+
fmt.Fprintf(os.Stderr, "Failed to start ephemeral Microcks container: %s\n", err)
182+
fmt.Fprintln(os.Stderr, "Check that the container runtime is running, the port is free and the image is reachable (or raise --ready-timeout).")
180183
if container != nil {
181-
terminateContainer(container)
184+
terminateContainer(container, progress)
182185
}
183186
return false
184187
}
185-
defer terminateContainer(container)
188+
defer terminateContainer(container, progress)
186189

187190
endpoint, err := container.HttpEndpoint(ctx)
188191
if err != nil {
189-
fmt.Printf("Failed to resolve ephemeral Microcks endpoint: %s\n", err)
192+
fmt.Fprintf(os.Stderr, "Failed to resolve ephemeral Microcks endpoint: %s\n", err)
190193
return false
191194
}
192-
fmt.Printf("Ephemeral Microcks is ready at %s\n", endpoint)
195+
fmt.Fprintf(progress, "Ephemeral Microcks is ready at %s\n", endpoint)
193196

194197
// The uber-native image runs without Keycloak: a headless client with
195198
// the unauthenticated token is enough.
@@ -198,30 +201,32 @@ func runDryRunTest(opts dryRunOptions) bool {
198201

199202
success, testResultID, err := runTestAndWait(mc, opts.params)
200203
if err != nil {
201-
fmt.Println(err)
204+
fmt.Fprintln(os.Stderr, err)
202205
return false
203206
}
204207

205208
if !opts.watch {
206209
return success
207210
}
208-
printDetailsLink(endpoint, testResultID)
211+
printDetailsLink(progress, endpoint, testResultID)
209212
return watchAndRerun(ctx, mc, endpoint, opts)
210213
}
211214

212-
func terminateContainer(container *microcks.MicrocksContainer) {
215+
func terminateContainer(container *microcks.MicrocksContainer, progress io.Writer) {
213216
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
214217
defer cancel()
215-
fmt.Println("Tearing down ephemeral Microcks container...")
218+
fmt.Fprintln(progress, "Tearing down ephemeral Microcks container...")
216219
if err := container.Terminate(ctx); err != nil {
217-
fmt.Printf("Failed to terminate container %s: %s\n", container.GetContainerID(), err)
220+
fmt.Fprintf(os.Stderr, "Failed to terminate container %s: %s\n", container.GetContainerID(), err)
218221
}
219222
}
220223

221224
func watchAndRerun(ctx context.Context, mc connectors.MicrocksClient, serverAddr string, opts dryRunOptions) bool {
225+
progress := progressWriter(opts.params.outputFormat)
226+
222227
watcher, err := fsnotify.NewWatcher()
223228
if err != nil {
224-
fmt.Printf("Failed to create file watcher: %s\n", err)
229+
fmt.Fprintf(os.Stderr, "Failed to create file watcher: %s\n", err)
225230
return false
226231
}
227232
defer watcher.Close()
@@ -230,23 +235,23 @@ func watchAndRerun(ctx context.Context, mc connectors.MicrocksClient, serverAddr
230235
// (rename + create), which silently drops a watch set on the file itself.
231236
artifactPath, err := filepath.Abs(opts.artifact)
232237
if err != nil {
233-
fmt.Printf("Failed to resolve artifact path: %s\n", err)
238+
fmt.Fprintf(os.Stderr, "Failed to resolve artifact path: %s\n", err)
234239
return false
235240
}
236241
if err := watcher.Add(filepath.Dir(artifactPath)); err != nil {
237-
fmt.Printf("Failed to watch %s: %s\n", filepath.Dir(artifactPath), err)
242+
fmt.Fprintf(os.Stderr, "Failed to watch %s: %s\n", filepath.Dir(artifactPath), err)
238243
return false
239244
}
240245

241-
fmt.Printf("\nWatching %s for changes — press Ctrl+C to stop.\n", opts.artifact)
246+
fmt.Fprintf(progress, "\nWatching %s for changes — press Ctrl+C to stop.\n", opts.artifact)
242247

243248
rerun := make(chan struct{}, 1)
244249
var debounce *time.Timer
245250

246251
for {
247252
select {
248253
case <-ctx.Done():
249-
fmt.Println("\nStopping watch mode.")
254+
fmt.Fprintln(progress, "\nStopping watch mode.")
250255
return true
251256

252257
case event, ok := <-watcher.Events:
@@ -275,32 +280,32 @@ func watchAndRerun(ctx context.Context, mc connectors.MicrocksClient, serverAddr
275280
if !ok {
276281
return true
277282
}
278-
fmt.Printf("Watch error: %s\n", err)
283+
fmt.Fprintf(os.Stderr, "Watch error: %s\n", err)
279284

280285
case <-rerun:
281-
fmt.Println(strings.Repeat("-", 60))
282-
fmt.Printf("Artifact changed, re-importing %s ...\n", opts.artifact)
286+
fmt.Fprintln(progress, strings.Repeat("-", 60))
287+
fmt.Fprintf(progress, "Artifact changed, re-importing %s ...\n", opts.artifact)
283288
if _, err := mc.UploadArtifact(opts.artifact, true); err != nil {
284289
// Invalid spec mid-edit is normal in a TDD loop: report and
285290
// keep watching, the next valid save recovers.
286-
fmt.Printf("Re-import failed, waiting for next change: %s\n", err)
291+
fmt.Fprintf(os.Stderr, "Re-import failed, waiting for next change: %s\n", err)
287292
continue
288293
}
289294
success, testResultID, err := runTestAndWait(mc, opts.params)
290295
if err != nil {
291-
fmt.Printf("Test run failed, waiting for next change: %s\n", err)
296+
fmt.Fprintf(os.Stderr, "Test run failed, waiting for next change: %s\n", err)
292297
continue
293298
}
294-
printDetailsLink(serverAddr, testResultID)
299+
printDetailsLink(progress, serverAddr, testResultID)
295300
if success {
296-
fmt.Println("Contract test PASSED — waiting for next change.")
301+
fmt.Fprintln(progress, "Contract test PASSED — waiting for next change.")
297302
} else {
298-
fmt.Println("Contract test FAILED — waiting for next change.")
303+
fmt.Fprintln(progress, "Contract test FAILED — waiting for next change.")
299304
}
300305
}
301306
}
302307
}
303308

304-
func printDetailsLink(serverAddr, testResultID string) {
305-
fmt.Printf("Test details (live while watching): %s/#/tests/%s\n", serverAddr, testResultID)
309+
func printDetailsLink(progress io.Writer, serverAddr, testResultID string) {
310+
fmt.Fprintf(progress, "Test details (live while watching): %s/#/tests/%s\n", serverAddr, testResultID)
306311
}

cmd/testExecutor.go

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ package cmd
1717

1818
import (
1919
"fmt"
20+
"io"
21+
"os"
2022
"time"
2123

2224
"github.com/microcks/microcks-cli/pkg/connectors"
25+
"github.com/microcks/microcks-cli/pkg/output"
2326
)
2427

2528
// testParams bundles the inputs needed to launch and poll a Microcks test.
@@ -33,11 +36,26 @@ type testParams struct {
3336
filteredOperations string
3437
operationsHeaders string
3538
oAuth2Context string
39+
outputFormat string
3640
}
3741

38-
// runTestAndWait creates a test on the Microcks server and polls its result
39-
// until completion or timeout. Shared by the regular and --dry-run paths.
42+
// progressWriter returns where human progress/diagnostics should go. For
43+
// machine-readable output formats they go to stderr, leaving stdout for the
44+
// formatted result only.
45+
func progressWriter(format string) io.Writer {
46+
if format != "" && output.OutputFormat(format) != output.FormatText {
47+
return os.Stderr
48+
}
49+
return os.Stdout
50+
}
51+
52+
// runTestAndWait creates a test on the Microcks server, polls until completion
53+
// or timeout, then renders the result in the requested output format (result to
54+
// stdout, progress to stderr for machine formats). Shared by the regular and
55+
// --dry-run paths.
4056
func runTestAndWait(mc connectors.MicrocksClient, params testParams) (bool, string, error) {
57+
progress := progressWriter(params.outputFormat)
58+
4159
testResultID, err := mc.CreateTestResult(params.serviceRef, params.testEndpoint, params.runnerType, params.secretName,
4260
params.waitForMillis, params.filteredOperations, params.operationsHeaders, params.oAuth2Context)
4361
if err != nil {
@@ -59,19 +77,47 @@ func runTestAndWait(mc connectors.MicrocksClient, params testParams) (bool, stri
5977
}
6078
success = testResultSummary.Success
6179
inProgress := testResultSummary.InProgress
62-
fmt.Printf("MicrocksClient got status for test \"%s\" - success: %s, inProgress: %s \n", testResultID, fmt.Sprint(success), fmt.Sprint(inProgress))
80+
fmt.Fprintf(progress, "MicrocksClient got status for test \"%s\" - success: %s, inProgress: %s \n", testResultID, fmt.Sprint(success), fmt.Sprint(inProgress))
6381

6482
if !inProgress {
6583
break
6684
}
6785

68-
fmt.Println("MicrocksTester waiting for 2 seconds before checking again or exiting.")
86+
fmt.Fprintln(progress, "MicrocksTester waiting for 2 seconds before checking again or exiting.")
6987
time.Sleep(2 * time.Second)
7088
}
7189

90+
if err := renderTestResult(mc, testResultID, params.outputFormat); err != nil {
91+
return false, testResultID, err
92+
}
93+
7294
return success, testResultID, nil
7395
}
7496

97+
// renderTestResult fetches the full result and writes it to stdout in the
98+
// requested format.
99+
func renderTestResult(mc connectors.MicrocksClient, testResultID, format string) error {
100+
if format == "" {
101+
format = string(output.FormatText)
102+
}
103+
full, err := mc.GetFullTestResult(testResultID)
104+
if err != nil {
105+
return fmt.Errorf("Got error when retrieving full test result: %s", err)
106+
}
107+
formatter, err := output.NewFormatter(output.OutputFormat(format))
108+
if err != nil {
109+
return err
110+
}
111+
rendered, err := formatter.Format(full)
112+
if err != nil {
113+
return fmt.Errorf("Got error when formatting test result: %s", err)
114+
}
115+
if rendered != "" {
116+
fmt.Println(rendered)
117+
}
118+
return nil
119+
}
120+
75121
func nowInMilliseconds() int64 {
76122
return time.Now().UnixNano() / int64(time.Millisecond)
77123
}

0 commit comments

Comments
 (0)