From 63ade1e68df9c0889688f47c6ea87e4923394019 Mon Sep 17 00:00:00 2001 From: xinnjie Date: Fri, 13 Dec 2024 01:37:51 +0800 Subject: [PATCH] feat: tkn-result add pipelinerun list and result describe command --- pkg/api/server/v1alpha2/plugin/plugin_logs.go | 1 + pkg/cli/cmd/pipelinerun/list.go | 97 ++++++++++++++ pkg/cli/cmd/pipelinerun/list_test.go | 90 +++++++++++++ pkg/cli/cmd/pipelinerun/pipelinerun.go | 22 ++++ pkg/cli/cmd/result/describe.go | 121 ++++++++++++++++++ pkg/cli/cmd/result/describe_test.go | 64 +++++++++ pkg/cli/cmd/{ => result}/list.go | 6 +- pkg/cli/cmd/result/result.go | 25 ++++ pkg/cli/cmd/root.go | 12 +- pkg/cli/flags/flags.go | 3 + pkg/cli/format/format.go | 46 +++++++ pkg/test/cobra.go | 20 +++ pkg/test/expect.go | 25 ++++ pkg/test/fake/results.go | 90 +++++++++++++ 14 files changed, 617 insertions(+), 5 deletions(-) create mode 100644 pkg/cli/cmd/pipelinerun/list.go create mode 100644 pkg/cli/cmd/pipelinerun/list_test.go create mode 100644 pkg/cli/cmd/pipelinerun/pipelinerun.go create mode 100644 pkg/cli/cmd/result/describe.go create mode 100644 pkg/cli/cmd/result/describe_test.go rename pkg/cli/cmd/{ => result}/list.go (95%) create mode 100644 pkg/cli/cmd/result/result.go create mode 100644 pkg/test/cobra.go create mode 100644 pkg/test/expect.go create mode 100644 pkg/test/fake/results.go diff --git a/pkg/api/server/v1alpha2/plugin/plugin_logs.go b/pkg/api/server/v1alpha2/plugin/plugin_logs.go index 88f8342c4..0ca788de9 100644 --- a/pkg/api/server/v1alpha2/plugin/plugin_logs.go +++ b/pkg/api/server/v1alpha2/plugin/plugin_logs.go @@ -396,6 +396,7 @@ func (s *LogServer) setLogPlugin() bool { s.IsLogPluginEnabled = true s.getLog = getBlobLogs default: + // TODO(xinnjie) when s.config.LOGS_TYPE is File also show this error log s.IsLogPluginEnabled = false s.logger.Errorf("unsupported type of logs given for plugin") } diff --git a/pkg/cli/cmd/pipelinerun/list.go b/pkg/cli/cmd/pipelinerun/list.go new file mode 100644 index 000000000..c7709fe61 --- /dev/null +++ b/pkg/cli/cmd/pipelinerun/list.go @@ -0,0 +1,97 @@ +package pipelinerun + +import ( + "fmt" + "text/tabwriter" + "text/template" + + "github.com/tektoncd/cli/pkg/cli" + + "github.com/jonboulle/clockwork" + "github.com/spf13/cobra" + "github.com/tektoncd/results/pkg/cli/flags" + "github.com/tektoncd/results/pkg/cli/format" + pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto" +) + +const listTemplate = `{{- $size := len .Results -}}{{- if eq $size 0 -}} +No PipelineRuns found +{{ else -}} +NAMESPACE UID STARTED DURATION STATUS +{{- range $_, $result := .Results }} +{{ formatNamespace $result.Name }} {{ $result.Uid }} {{ formatAge $result.Summary.StartTime $.Time }} {{ formatDuration $result.Summary.StartTime $result.Summary.EndTime }} {{ formatStatus $result.Summary.Status }} +{{- end -}} +{{- end -}}` + +type ListOptions struct { + Namespace string + Limit int +} + +// listCommand initializes a cobra command to list PipelineRuns +func listCommand(params *flags.Params) *cobra.Command { + opts := &ListOptions{Limit: 0, Namespace: "default"} + + eg := `List all PipelineRuns in a namespace 'foo': + tkn-results pipelinerun list -n foo + +List all PipelineRuns in 'default' namespace: + tkn-results pipelinerun list -n default +` + cmd := &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List PipelineRuns in a namespace", + Annotations: map[string]string{ + "commandType": "main", + }, + Example: eg, + RunE: func(cmd *cobra.Command, args []string) error { + if opts.Limit < 0 { + return fmt.Errorf("limit was %d, but must be greater than 0", opts.Limit) + } + + resp, err := params.ResultsClient.ListResults(cmd.Context(), &pb.ListResultsRequest{ + Parent: opts.Namespace, + PageSize: int32(opts.Limit), + Filter: `summary.type==PIPELINE_RUN`, + }) + if err != nil { + return fmt.Errorf("failed to list PipelineRuns from namespace %s: %v", opts.Namespace, err) + } + stream := &cli.Stream{ + Out: cmd.OutOrStdout(), + Err: cmd.OutOrStderr(), + } + return printFormatted(stream, resp.Results, params.Clock) + }, + } + cmd.Flags().StringVarP(&opts.Namespace, "namespace", "n", "default", "Namespace to list PipelineRuns in") + cmd.Flags().IntVarP(&opts.Limit, "limit", "l", 0, "Limit the number of PipelineRuns to return") + return cmd +} + +func printFormatted(s *cli.Stream, results []*pb.Result, c clockwork.Clock) error { + var data = struct { + Results []*pb.Result + Time clockwork.Clock + }{ + Results: results, + Time: c, + } + funcMap := template.FuncMap{ + "formatAge": format.Age, + "formatDuration": format.Duration, + "formatStatus": format.Status, + "formatNamespace": format.Namespace, + } + + w := tabwriter.NewWriter(s.Out, 0, 5, 3, ' ', tabwriter.TabIndent) + t := template.Must(template.New("List PipelineRuns").Funcs(funcMap).Parse(listTemplate)) + + err := t.Execute(w, data) + if err != nil { + return err + } + return w.Flush() +} diff --git a/pkg/cli/cmd/pipelinerun/list_test.go b/pkg/cli/cmd/pipelinerun/list_test.go new file mode 100644 index 000000000..2f0bff041 --- /dev/null +++ b/pkg/cli/cmd/pipelinerun/list_test.go @@ -0,0 +1,90 @@ +package pipelinerun + +import ( + "testing" + "time" + + "github.com/tektoncd/results/pkg/test" + + "github.com/tektoncd/results/pkg/cli/flags" + "github.com/tektoncd/results/pkg/test/fake" + + "github.com/jonboulle/clockwork" + + "github.com/spf13/cobra" + pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestListPipelineRuns_empty(t *testing.T) { + results := []*pb.Result{} + now := time.Now() + cmd := command(results, now) + + output, err := test.ExecuteCommand(cmd, "list") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + test.AssertOutput(t, "No PipelineRuns found\n", output) +} + +func TestListPipelineRuns(t *testing.T) { + clock := clockwork.NewFakeClock() + createTime := clock.Now().Add(time.Duration(-3) * time.Minute) + updateTime := clock.Now().Add(time.Duration(-2) * time.Minute) + startTime := clock.Now().Add(time.Duration(-3) * time.Minute) + endTime := clock.Now().Add(time.Duration(-1) * time.Minute) + results := []*pb.Result{ + { + Name: "default/results/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7", + Uid: "949eebd9-1cf7-478f-a547-9ee313035f10", + CreateTime: timestamppb.New(createTime), + UpdateTime: timestamppb.New(updateTime), + Annotations: map[string]string{ + "object.metadata.name": "hello-goodbye-run-vfsxn", + "tekton.dev/pipeline": "hello-goodbye", + }, + Summary: &pb.RecordSummary{ + Record: "default/results/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7/records/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7", + Type: "tekton.dev/v1.PipelineRun", + StartTime: timestamppb.New(startTime), + EndTime: timestamppb.New(endTime), + Status: pb.RecordSummary_SUCCESS, + }, + }, + { + Name: "default/results/3dacd30b-ce42-476c-be7e-84b0f664df55", + Uid: "c8d4cd50-06e8-4325-9ba2-044e6cc45235", + CreateTime: timestamppb.New(createTime), + UpdateTime: timestamppb.New(updateTime), + Annotations: map[string]string{ + "object.metadata.name": "hello-goodbye-run-xtw2j", + }, + Summary: &pb.RecordSummary{ + Record: "default/results/3dacd30b-ce42-476c-be7e-84b0f664df55/records/3dacd30b-ce42-476c-be7e-84b0f664df55", + Type: "tekton.dev/v1.PipelineRun", + }, + }, + } + cmd := command(results, clock.Now()) + output, err := test.ExecuteCommand(cmd, "list") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + test.AssertOutput(t, `NAMESPACE UID STARTED DURATION STATUS +default 949eebd9-1cf7-478f-a547-9ee313035f10 3 minutes ago 2m0s Succeeded +default c8d4cd50-06e8-4325-9ba2-044e6cc45235 --- --- Unknown`, output) +} + +func command(results []*pb.Result, now time.Time) *cobra.Command { + clock := clockwork.NewFakeClockAt(now) + + param := &flags.Params{ + ResultsClient: fake.NewResultsClient(results), + LogsClient: nil, + PluginLogsClient: nil, + Clock: clock, + } + cmd := Command(param) + return cmd +} diff --git a/pkg/cli/cmd/pipelinerun/pipelinerun.go b/pkg/cli/cmd/pipelinerun/pipelinerun.go new file mode 100644 index 000000000..a84176840 --- /dev/null +++ b/pkg/cli/cmd/pipelinerun/pipelinerun.go @@ -0,0 +1,22 @@ +package pipelinerun + +import ( + "github.com/spf13/cobra" + "github.com/tektoncd/results/pkg/cli/flags" +) + +// Command initializes a cobra command for `pipelinerun` sub commands +func Command(params *flags.Params) *cobra.Command { + cmd := &cobra.Command{ + Use: "pipelinerun", + Aliases: []string{"pr", "pipelineruns"}, + Short: "Query PipelineRuns", + Annotations: map[string]string{ + "commandType": "main", + }, + } + + cmd.AddCommand(listCommand(params)) + + return cmd +} diff --git a/pkg/cli/cmd/result/describe.go b/pkg/cli/cmd/result/describe.go new file mode 100644 index 000000000..c69e524da --- /dev/null +++ b/pkg/cli/cmd/result/describe.go @@ -0,0 +1,121 @@ +package result + +import ( + "fmt" + "text/tabwriter" + "text/template" + + "github.com/tektoncd/cli/pkg/cli" + + "github.com/jonboulle/clockwork" + "github.com/spf13/cobra" + "github.com/tektoncd/results/pkg/cli/flags" + "github.com/tektoncd/results/pkg/cli/format" + pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto" +) + +const resultDescTmpl = `Name: {{.Result.Name}} +UID: {{.Result.Uid}} +{{- if .Result.Annotations}} +Annotations: +{{- range $key, $value := .Result.Annotations}} + {{$key}}={{$value}} +{{- end}} +{{- end}} +Status: + Created: {{formatAge .Result.CreateTime .Time}} DURATION: {{formatDuration .Result.CreateTime .Result.UpdateTime}} +{{- if .Result.Summary}} +Summary: + Type: {{.Result.Summary.Type}} + Status: + STARTED DURATION STATUS + {{formatAge .Result.Summary.StartTime .Time}} {{formatDuration .Result.Summary.StartTime .Result.Summary.EndTime}} {{.Result.Summary.Status}} + {{- if .Result.Summary.Annotations}} + Annotations: + {{- range $key, $value := .Result.Summary.Annotations}} + {{$key}}={{$value}} + {{- end}} + {{- end}} +{{- end}} +` + +type DescribeOptions struct { + Parent string + Uid string +} + +func describeCommand(params *flags.Params) *cobra.Command { + opts := &DescribeOptions{} + eg := `Query by name: +tkn-result result describe default/results/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7 + +Query by parent and uid: +tkn-result result desc --parent default --uid 949eebd9-1cf7-478f-a547-9ee313035f10 +` + cmd := &cobra.Command{ + Use: "describe [-p parent -u uid] [name]", + Aliases: []string{"desc"}, + Short: "Describes a Result", + Annotations: map[string]string{ + "commandType": "main", + }, + Example: eg, + RunE: func(cmd *cobra.Command, args []string) error { + stream := &cli.Stream{ + Out: cmd.OutOrStdout(), + Err: cmd.OutOrStderr(), + } + if len(args) > 0 { + name := args[0] + result, err := params.ResultsClient.GetResult(cmd.Context(), &pb.GetResultRequest{ + Name: name, + }) + if err != nil { + return fmt.Errorf("failed to get result of name %s: %v", name, err) + } + return printResultDescription(stream, result, params.Clock) + } + + if opts.Parent != "" && opts.Uid != "" { + resp, err := params.ResultsClient.ListResults(cmd.Context(), &pb.ListResultsRequest{ + Parent: opts.Parent, + Filter: fmt.Sprintf(`uid=="%s"`, opts.Uid), + }) + if err != nil { + return fmt.Errorf("failed to get result of parent %s and uid %s: %v", opts.Parent, opts.Uid, err) + } + if len(resp.Results) == 0 { + return fmt.Errorf("no result found with parent %s and uid %s", opts.Parent, opts.Uid) + } + return printResultDescription(stream, resp.Results[0], params.Clock) + } + return nil + }, + } + + cmd.Flags().StringVarP(&opts.Parent, "parent", "p", "", "parent of the result") + cmd.Flags().StringVarP(&opts.Uid, "uid", "u", "", "uid of the result") + return cmd +} + +func printResultDescription(s *cli.Stream, result *pb.Result, c clockwork.Clock) error { + data := struct { + Result *pb.Result + Time clockwork.Clock + }{ + Result: result, + Time: c, + } + funcMap := template.FuncMap{ + "formatAge": format.Age, + "formatDuration": format.Duration, + "formatStatus": format.Status, + } + w := tabwriter.NewWriter(s.Out, 0, 5, 3, ' ', tabwriter.TabIndent) + t := template.Must(template.New("Describe A Result").Funcs(funcMap).Parse(resultDescTmpl)) + err := t.Execute(w, data) + if err != nil { + return err + } + return w.Flush() +} diff --git a/pkg/cli/cmd/result/describe_test.go b/pkg/cli/cmd/result/describe_test.go new file mode 100644 index 000000000..14ac4885e --- /dev/null +++ b/pkg/cli/cmd/result/describe_test.go @@ -0,0 +1,64 @@ +package result + +import ( + "github.com/jonboulle/clockwork" + "github.com/tektoncd/results/pkg/cli/flags" + "github.com/tektoncd/results/pkg/test" + "github.com/tektoncd/results/pkg/test/fake" + pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto" + "google.golang.org/protobuf/types/known/timestamppb" + "testing" + "time" +) + +func TestDescribeResult(t *testing.T) { + clock := clockwork.NewFakeClock() + createTime := clock.Now().Add(time.Duration(-3) * time.Minute) + updateTime := clock.Now().Add(time.Duration(-2) * time.Minute) + startTime := clock.Now().Add(time.Duration(-3) * time.Minute) + endTime := clock.Now().Add(time.Duration(-1) * time.Minute) + results := []*pb.Result{ + { + Name: "default/results/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7", + Uid: "949eebd9-1cf7-478f-a547-9ee313035f10", + CreateTime: timestamppb.New(createTime), + UpdateTime: timestamppb.New(updateTime), + Annotations: map[string]string{ + "object.metadata.name": "hello-goodbye-run-vfsxn", + "tekton.dev/pipeline": "hello-goodbye", + }, + Summary: &pb.RecordSummary{ + Record: "default/results/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7/records/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7", + Type: "tekton.dev/v1.PipelineRun", + StartTime: timestamppb.New(startTime), + EndTime: timestamppb.New(endTime), + Status: pb.RecordSummary_SUCCESS, + }, + }} + + param := &flags.Params{ + ResultsClient: fake.NewResultsClient(results), + LogsClient: nil, + PluginLogsClient: nil, + Clock: clock, + } + cmd := Command(param) + + output, err := test.ExecuteCommand(cmd, "describe", "default/results/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + test.AssertOutput(t, `Name: default/results/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7 +UID: 949eebd9-1cf7-478f-a547-9ee313035f10 +Annotations: + object.metadata.name=hello-goodbye-run-vfsxn + tekton.dev/pipeline=hello-goodbye +Status: + Created: 3 minutes ago DURATION: 1m0s +Summary: + Type: tekton.dev/v1.PipelineRun + Status: + STARTED DURATION STATUS + 3 minutes ago 2m0s SUCCESS +`, output) +} diff --git a/pkg/cli/cmd/list.go b/pkg/cli/cmd/result/list.go similarity index 95% rename from pkg/cli/cmd/list.go rename to pkg/cli/cmd/result/list.go index ea9083d76..faf15bb51 100644 --- a/pkg/cli/cmd/list.go +++ b/pkg/cli/cmd/result/list.go @@ -1,4 +1,4 @@ -package cmd +package result import ( "fmt" @@ -20,9 +20,9 @@ func ListCommand(params *flags.Params) *cobra.Command { : Parent name to query. This is typically corresponds to a namespace, but may vary depending on the API Server. "-" may be used to query all parents.`, Short: "List Results", RunE: func(cmd *cobra.Command, args []string) error { - + parent := args[0] resp, err := params.ResultsClient.ListResults(cmd.Context(), &pb.ListResultsRequest{ - Parent: args[0], + Parent: parent, Filter: opts.Filter, PageSize: opts.Limit, PageToken: opts.PageToken, diff --git a/pkg/cli/cmd/result/result.go b/pkg/cli/cmd/result/result.go new file mode 100644 index 000000000..9f9c0d5c9 --- /dev/null +++ b/pkg/cli/cmd/result/result.go @@ -0,0 +1,25 @@ +package result + +import ( + "github.com/spf13/cobra" + "github.com/tektoncd/results/pkg/cli/flags" +) + +// Command initializes a cobra command for `pipelinerun` sub commands +func Command(params *flags.Params) *cobra.Command { + cmd := &cobra.Command{ + Use: "result", + Aliases: []string{"r", "results"}, + Short: "Query Results", + Annotations: map[string]string{ + "commandType": "main", + }, + } + + cmd.AddCommand( + ListCommand(params), + describeCommand(params), + ) + + return cmd +} diff --git a/pkg/cli/cmd/root.go b/pkg/cli/cmd/root.go index 0c57bd0ec..71728bcd5 100644 --- a/pkg/cli/cmd/root.go +++ b/pkg/cli/cmd/root.go @@ -4,13 +4,15 @@ import ( _ "embed" "flag" "fmt" - + "github.com/jonboulle/clockwork" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" "github.com/tektoncd/results/pkg/cli/client" "github.com/tektoncd/results/pkg/cli/cmd/logs" + "github.com/tektoncd/results/pkg/cli/cmd/pipelinerun" "github.com/tektoncd/results/pkg/cli/cmd/records" + "github.com/tektoncd/results/pkg/cli/cmd/result" "github.com/tektoncd/results/pkg/cli/config" "github.com/tektoncd/results/pkg/cli/flags" "github.com/tektoncd/results/pkg/cli/portforward" @@ -77,6 +79,8 @@ func Root() *cobra.Command { params.PluginLogsClient = pluginLogsClient + params.Clock = clockwork.NewRealClock() + return nil }, PersistentPostRun: func(_ *cobra.Command, _ []string) { @@ -97,7 +101,11 @@ func Root() *cobra.Command { cmd.PersistentFlags().Bool("insecure", false, "determines whether to run insecure GRPC tls request") cmd.PersistentFlags().Bool("v1alpha2", false, "use v1alpha2 API for get log command") - cmd.AddCommand(ListCommand(params), records.Command(params), logs.Command(params)) + cmd.AddCommand(result.Command(params), + records.Command(params), + logs.Command(params), + pipelinerun.Command(params), + ) pflag.CommandLine.AddGoFlagSet(flag.CommandLine) diff --git a/pkg/cli/flags/flags.go b/pkg/cli/flags/flags.go index fa9faebd1..35a7a811d 100644 --- a/pkg/cli/flags/flags.go +++ b/pkg/cli/flags/flags.go @@ -1,6 +1,7 @@ package flags import ( + "github.com/jonboulle/clockwork" "github.com/spf13/cobra" pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto" pb3 "github.com/tektoncd/results/proto/v1alpha3/results_go_proto" @@ -11,6 +12,8 @@ type Params struct { ResultsClient pb.ResultsClient LogsClient pb.LogsClient PluginLogsClient pb3.LogsClient + + Clock clockwork.Clock } // ListOptions is used on commands that list Results, Records or Logs diff --git a/pkg/cli/format/format.go b/pkg/cli/format/format.go index 6221bc977..7cc053567 100644 --- a/pkg/cli/format/format.go +++ b/pkg/cli/format/format.go @@ -7,10 +7,13 @@ import ( "text/tabwriter" "time" + "github.com/hako/durafmt" + "github.com/jonboulle/clockwork" pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" ) // PrintProto prints the given proto message to the given writer in the given format. @@ -68,3 +71,46 @@ func PrintProto(w io.Writer, m proto.Message, format string) error { } return nil } + +func Age(timestamp *timestamppb.Timestamp, c clockwork.Clock) string { + if timestamp == nil { + return "---" + } + t := timestamp.AsTime() + if t.IsZero() { + return "---" + } + duration := c.Since(t) + return durafmt.ParseShort(duration).String() + " ago" +} + +func Duration(timestamp1, timestamp2 *timestamppb.Timestamp) string { + if timestamp1 == nil || timestamp2 == nil { + return "---" + } + t1 := timestamp1.AsTime() + t2 := timestamp2.AsTime() + if t1.IsZero() || t2.IsZero() { + return "---" + } + duration := t2.Sub(t1) + return duration.String() +} + +func Status(status pb.RecordSummary_Status) string { + switch status { + case pb.RecordSummary_SUCCESS: + return "Succeeded" + case pb.RecordSummary_FAILURE: + return "Failed" + case pb.RecordSummary_TIMEOUT: + return "Timed Out" + case pb.RecordSummary_CANCELLED: + return "Cancelled" + } + return "Unknown" +} + +func Namespace(resultName string) string { + return strings.Split(resultName, "/")[0] +} diff --git a/pkg/test/cobra.go b/pkg/test/cobra.go new file mode 100644 index 000000000..fb0c95cb4 --- /dev/null +++ b/pkg/test/cobra.go @@ -0,0 +1,20 @@ +package test + +import ( + "bytes" + "github.com/spf13/cobra" +) + +// ExecuteCommand executes the root command passing the args and returns +// the output as a string and error +func ExecuteCommand(c *cobra.Command, args ...string) (string, error) { + buf := new(bytes.Buffer) + c.SetOut(buf) + c.SetErr(buf) + c.SetArgs(args) + c.SilenceUsage = true + + _, err := c.ExecuteC() + + return buf.String(), err +} diff --git a/pkg/test/expect.go b/pkg/test/expect.go new file mode 100644 index 000000000..69be0281b --- /dev/null +++ b/pkg/test/expect.go @@ -0,0 +1,25 @@ +package test + +import ( + "github.com/google/go-cmp/cmp" + "testing" +) + +func AssertOutput(t *testing.T, expected, actual interface{}) { + t.Helper() + diff := cmp.Diff(actual, expected) + if diff == "" { + return + } + + t.Errorf(` +Unexpected output: +%s + +Expected +%s + +Actual +%s +`, diff, expected, actual) +} diff --git a/pkg/test/fake/results.go b/pkg/test/fake/results.go new file mode 100644 index 000000000..73bbc6711 --- /dev/null +++ b/pkg/test/fake/results.go @@ -0,0 +1,90 @@ +package fake + +import ( + "context" + "fmt" + + pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" +) + +// ResultsClient is a fake implementation of the ResultsClient interface +type ResultsClient struct { + // Map of result name to Result for GetResult and ListResults + results map[string]*pb.Result +} + +// NewResultsClient creates a new fake ResultsClient +func NewResultsClient(testData []*pb.Result) *ResultsClient { + r := &ResultsClient{ + results: make(map[string]*pb.Result), + } + for _, result := range testData { + r.results[result.Name] = result + } + return r +} + +// AddResult adds a Result to the fake client's data store +func (c *ResultsClient) AddResult(name string, result *pb.Result) { + c.results[name] = result +} + +// GetResult implements ResultsClient.GetResult +func (c *ResultsClient) GetResult(ctx context.Context, in *pb.GetResultRequest, opts ...grpc.CallOption) (*pb.Result, error) { + result, exists := c.results[in.Name] + if !exists { + return nil, fmt.Errorf("result not found: %s", in.Name) + } + return result, nil +} + +// ListResults implements ResultsClient.ListResults +func (c *ResultsClient) ListResults(ctx context.Context, in *pb.ListResultsRequest, opts ...grpc.CallOption) (*pb.ListResultsResponse, error) { + results := make([]*pb.Result, 0, len(c.results)) + for _, result := range c.results { + results = append(results, result) + } + + return &pb.ListResultsResponse{ + Results: results, + }, nil +} + +// Unimplemented methods below +func (c *ResultsClient) CreateResult(ctx context.Context, in *pb.CreateResultRequest, opts ...grpc.CallOption) (*pb.Result, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (c *ResultsClient) UpdateResult(ctx context.Context, in *pb.UpdateResultRequest, opts ...grpc.CallOption) (*pb.Result, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (c *ResultsClient) DeleteResult(ctx context.Context, in *pb.DeleteResultRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (c *ResultsClient) CreateRecord(ctx context.Context, in *pb.CreateRecordRequest, opts ...grpc.CallOption) (*pb.Record, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (c *ResultsClient) UpdateRecord(ctx context.Context, in *pb.UpdateRecordRequest, opts ...grpc.CallOption) (*pb.Record, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (c *ResultsClient) GetRecord(ctx context.Context, in *pb.GetRecordRequest, opts ...grpc.CallOption) (*pb.Record, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (c *ResultsClient) ListRecords(ctx context.Context, in *pb.ListRecordsRequest, opts ...grpc.CallOption) (*pb.ListRecordsResponse, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (c *ResultsClient) DeleteRecord(ctx context.Context, in *pb.DeleteRecordRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (c *ResultsClient) GetRecordListSummary(ctx context.Context, in *pb.RecordListSummaryRequest, opts ...grpc.CallOption) (*pb.RecordListSummary, error) { + return nil, fmt.Errorf("unimplemented") +}