Skip to content

Commit e471087

Browse files
authored
Make the function results separator configurable (#4778)
* Make the function results separator configurable Signed-off-by: Kushal Harish Naidu * Address IsZero() and add table driven tests for TestPrintFnResult Signed-off-by: Kushal Harish Naidu --------- Signed-off-by: Kushal Harish Naidu
1 parent 070479e commit e471087

4 files changed

Lines changed: 94 additions & 9 deletions

File tree

‎pkg/fn/runtime/runner.go‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ func (fr *FunctionRunner) Filter(input []*yaml.RNode) (output []*yaml.RNode, err
328328
output, err = fr.do(input)
329329
if err != nil {
330330
pr.Printf("[FAIL] %q in %v\n", fnName, time.Since(t0).Truncate(time.Millisecond)) // TODO: use OptPrintf
331-
printFnResult(fr.ctx, fr.fnResult)
331+
printFnResult(fr.ctx, fr.fnResult, fr.opts.LogOptions.ResultSeparator)
332332
if fnErr, ok := goerrors.AsType[*ExecError](err); ok {
333333
printFnExecErr(fr.ctx, fnErr)
334334
return nil, errors.ErrAlreadyHandled
@@ -337,7 +337,7 @@ func (fr *FunctionRunner) Filter(input []*yaml.RNode) (output []*yaml.RNode, err
337337
}
338338
if !fr.disableCLIOutput {
339339
pr.Printf("[PASS] %q in %v\n", fnName, time.Since(t0).Truncate(time.Millisecond)) // TODO: use OptPrintf
340-
printFnResult(fr.ctx, fr.fnResult)
340+
printFnResult(fr.ctx, fr.fnResult, fr.opts.LogOptions.ResultSeparator)
341341
printFnStderr(fr.ctx, fr.fnResult.Stderr)
342342
}
343343
return output, err
@@ -536,20 +536,24 @@ func populateResourceRef(item *yaml.RNode, resultItem *fnresultv1.ResultItem) er
536536
}
537537

538538
// printFnResult prints given function result in a user-friendly format on kpt CLI.
539-
func printFnResult(ctx context.Context, fnResult *fnresultv1.Result) {
539+
// separator joins the per-item result lines (defaults to "\n"); embedding
540+
// applications can set it to e.g. "; " to keep the [Results] output single-line.
541+
func printFnResult(ctx context.Context, fnResult *fnresultv1.Result, separator string) {
540542
pr := printer.FromContextOrDie(ctx)
541543
if len(fnResult.Results) > 0 {
542544
// function returned structured results
543545
var lines []string
544546
for _, item := range fnResult.Results {
545547
lines = append(lines, item.String())
546548
}
549+
if separator == "" {
550+
separator = "\n"
551+
}
547552
ri := &runneroptions.SingleLineFormatter{
548-
Title: "[Results]",
549-
Lines: lines,
550-
UseQuote: false,
551-
Separator: "\n",
552-
553+
Title: "[Results]",
554+
Lines: lines,
555+
UseQuote: false,
556+
Separator: separator,
553557
Indent: 2,
554558
LineIndent: 2,
555559
}

‎pkg/fn/runtime/runner_test.go‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,3 +1077,46 @@ data:
10771077
assert.Equal(t, "ns-b", meta.Namespace)
10781078
})
10791079
}
1080+
1081+
func TestPrintFnResult(t *testing.T) {
1082+
results := []fnresultv1.ResultItem{
1083+
{Severity: framework.Info, Message: "first"},
1084+
{Severity: framework.Error, Message: "second"},
1085+
}
1086+
1087+
tests := map[string]struct {
1088+
results []fnresultv1.ResultItem
1089+
separator string
1090+
expected string
1091+
}{
1092+
"no results produces no output": {
1093+
results: nil,
1094+
separator: "",
1095+
expected: "",
1096+
},
1097+
"empty separator preserves newline output": {
1098+
results: results,
1099+
separator: "",
1100+
expected: " [Results]:\n [info]: first\n [error]: second\n",
1101+
},
1102+
"custom separator joins result items": {
1103+
results: results,
1104+
separator: "; ",
1105+
expected: " [Results]:\n [info]: first; [error]: second\n",
1106+
},
1107+
}
1108+
1109+
for name, tc := range tests {
1110+
t.Run(name, func(t *testing.T) {
1111+
out := &bytes.Buffer{}
1112+
errOut := &bytes.Buffer{}
1113+
ctx := printer.WithContext(context.Background(), printer.New(out, errOut))
1114+
1115+
printFnResult(ctx, &fnresultv1.Result{Results: tc.results}, tc.separator)
1116+
1117+
// printFnResult writes to the printer's stderr stream.
1118+
assert.Equal(t, tc.expected, errOut.String())
1119+
assert.Equal(t, "", out.String())
1120+
})
1121+
}
1122+
}

‎pkg/lib/runneroptions/runneroptions.go‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,16 @@ type LogOptions struct {
104104
// TruncateImageName determines whether the full image name or just the base
105105
// name and the tag will be logged.
106106
TruncateImageName bool
107+
108+
// ResultSeparator is used to join the per-item lines of a function's
109+
// structured [Results] output. Defaults to "\n" (multi-line) when empty.
110+
// Embedding applications (e.g. Porch) can set this to "; " to keep the
111+
// result output on a single line for log-friendliness.
112+
ResultSeparator string
107113
}
108114

109115
func (o *LogOptions) IsZero() bool {
110-
return o.PkgNameFormat == "" && o.PkgNameSep == "" && o.PkgNameID == 0 && !o.TruncateImageName
116+
return o.PkgNameFormat == "" && o.PkgNameSep == "" && o.PkgNameID == 0 && !o.TruncateImageName && o.ResultSeparator == ""
111117
}
112118

113119
func (o *LogOptions) FillDefaults() {

‎pkg/lib/runneroptions/runneroptions_test.go‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,35 @@ func TestDefaultImagePrefix(t *testing.T) {
119119
}
120120
})
121121
}
122+
123+
func TestLogOptions_IsZero(t *testing.T) {
124+
tests := map[string]struct {
125+
opts LogOptions
126+
want bool
127+
}{
128+
"fully empty is zero": {
129+
opts: LogOptions{},
130+
want: true,
131+
},
132+
"separator-only is not zero": {
133+
opts: LogOptions{ResultSeparator: "; "},
134+
want: false,
135+
},
136+
"pkg name format set is not zero": {
137+
opts: LogOptions{PkgNameFormat: "%s"},
138+
want: false,
139+
},
140+
"truncate image name set is not zero": {
141+
opts: LogOptions{TruncateImageName: true},
142+
want: false,
143+
},
144+
}
145+
146+
for name, tc := range tests {
147+
t.Run(name, func(t *testing.T) {
148+
if got := tc.opts.IsZero(); got != tc.want {
149+
t.Errorf("IsZero() = %v, want %v", got, tc.want)
150+
}
151+
})
152+
}
153+
}

0 commit comments

Comments
 (0)