go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/cli/print_test.go (about)

     1  // Copyright 2019 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cli
    16  
    17  import (
    18  	"bytes"
    19  	"regexp"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/golang/protobuf/proto"
    25  	"github.com/mgutz/ansi"
    26  	"google.golang.org/protobuf/encoding/protojson"
    27  
    28  	"go.chromium.org/luci/common/clock/testclock"
    29  
    30  	pb "go.chromium.org/luci/buildbucket/proto"
    31  
    32  	. "github.com/smartystreets/goconvey/convey"
    33  )
    34  
    35  var buildJSON = `
    36  {
    37    "id": "8917899588926498064",
    38    "builder": {
    39      "project": "chromium",
    40      "bucket": "try",
    41      "builder": "linux-rel"
    42    },
    43    "number": 1,
    44    "createdBy": "user:5071639625-1lppvbtck1morgivc6sq4dul7klu27sd@developer.gserviceaccount.com",
    45    "createTime": "2019-03-26T18:33:47.958975Z",
    46    "startTime": "2019-03-26T18:33:52.447816Z",
    47    "endTime": "2019-03-26T18:37:13.892433Z",
    48    "updateTime": "2019-03-26T18:37:14.629154Z",
    49    "status": "SUCCESS",
    50    "summaryMarkdown": "it was ok",
    51    "canary": true,
    52    "input": {
    53      "experimental": true,
    54  		"experiments": ["luci.buildbucket.canary_software", "luci.non_production"],
    55      "properties": {
    56        "$recipe_engine/cq": {
    57          "dry_run": true
    58        },
    59        "$recipe_engine/runtime": {
    60          "is_experimental": false
    61        },
    62        "blamelist": [
    63          "hinoka@chromium.org"
    64        ],
    65        "buildername": "Luci-go Mac Tester",
    66        "category": "cq"
    67      },
    68      "gitilesCommit": {
    69          "host": "chromium.googlesource.com",
    70          "project": "infra/luci/luci-go",
    71          "ref": "refs/heads/x",
    72          "id": "deadbeef"
    73      },
    74      "gerritChanges": [
    75        {
    76          "host": "chromium-review.googlesource.com",
    77          "project": "infra/luci/luci-go",
    78          "change": "1539021",
    79          "patchset": "1"
    80        }
    81      ]
    82    },
    83    "output": {
    84      "properties": {
    85        "$recipe_engine/cq": {
    86          "dry_run": true
    87        },
    88        "bot_id": "build234-m9"
    89      }
    90    },
    91    "steps": [
    92      {
    93        "name": "first",
    94        "startTime": "2019-03-26T18:34:01.896053Z",
    95        "endTime": "2019-03-26T18:34:01.954986Z",
    96        "status": "SUCCESS",
    97        "logs": [
    98          {
    99            "name": "stdout",
   100            "viewUrl": "https://logs.example.com/first/stdout"
   101          },
   102          {
   103            "name": "stderr",
   104            "viewUrl": "https://logs.example.com/first/stderr"
   105          }
   106        ],
   107        "summaryMarkdown": "first summary markdown\nsummary second line"
   108      },
   109      {
   110        "name": "second",
   111        "startTime": "2019-03-26T18:34:01.896053Z",
   112        "endTime": "2019-03-26T18:34:01.954986Z",
   113        "status": "SUCCESS",
   114        "logs": [
   115          {
   116            "name": "stdout",
   117            "viewUrl": "https://logs.example.com/second/stdout"
   118          },
   119          {
   120            "name": "stderr",
   121            "viewUrl": "https://logs.example.com/second/stderr"
   122          }
   123        ]
   124      }
   125    ],
   126    "infra": {
   127      "buildbucket": {
   128        "serviceConfigRevision": "80aefcf1d2f6c25894a968312eca86ba0f80737c"
   129      },
   130      "swarming": {
   131        "hostname": "chromium-swarm.appspot.com",
   132        "taskId": "43d4192e94d9ff10",
   133        "taskServiceAccount": "infra-try-builder@chops-service-accounts.iam.gserviceaccount.com"
   134      },
   135      "logdog": {
   136        "hostname": "logs.chromium.org",
   137        "project": "infra",
   138        "prefix": "buildbucket/cr-buildbucket.appspot.com/8917899588926498064"
   139      }
   140    },
   141    "tags": [
   142      {
   143        "key": "buildset",
   144        "value": "patch/gerrit/chromium-review.googlesource.com/1539021/1"
   145      },
   146      {
   147        "key": "cq_experimental",
   148        "value": "false"
   149      },
   150      {
   151        "key": "user_agent",
   152        "value": "cq"
   153      }
   154    ]
   155  }`
   156  
   157  const expectedBuildPrintedTemplate = `<white+b><white+u><green+h>http://ci.chromium.org/b/8917899588926498064<reset><white+b><green+h> SUCCESS   'chromium/try/linux-rel/1'<reset>
   158  <white+b>Summary<reset>: it was ok
   159  <white+b>Experimental<reset> <white+b>Canary<reset>
   160  <white+b>Created<reset> on 2019-03-26 at 18:33:47, <white+b>waited<reset> 4.5s, <white+b>started<reset> at 18:33:52, <white+b>ran<reset> for 3m21s, <white+b>ended<reset> at 18:37:13
   161  <white+b>By<reset>: user:5071639625-1lppvbtck1morgivc6sq4dul7klu27sd@developer.gserviceaccount.com
   162  <white+b>Commit<reset>: <white+u>https://crrev.com/deadbeef<reset> on refs/heads/x
   163  <white+b>CL<reset>: <white+u>https://crrev.com/c/1539021/1<reset>
   164  <white+b>Tag<reset>: buildset:patch/gerrit/chromium-review.googlesource.com/1539021/1
   165  <white+b>Tag<reset>: cq_experimental:false
   166  <white+b>Tag<reset>: user_agent:cq
   167  <white+b>Input properties<reset>: {
   168    "$recipe_engine/cq": {
   169      "dry_run": true
   170    },
   171    "$recipe_engine/runtime": {
   172      "is_experimental": false
   173    },
   174    "blamelist": [
   175      "hinoka@chromium.org"
   176    ],
   177    "buildername": "Luci-go Mac Tester",
   178    "category": "cq"
   179  }
   180  <white+b>Output properties<reset>: {
   181    "$recipe_engine/cq": {
   182      "dry_run": true
   183    },
   184    "bot_id": "build234-m9"
   185  }
   186  <white+b>Experiments<reset>: 
   187    luci.buildbucket.canary_software
   188    luci.non_production
   189  <green+h>Step "first"    SUCCESS   59ms      Logs: "stdout", "stderr"<reset>
   190    first summary markdown
   191    summary second line
   192  <green+h>Step "second"   SUCCESS   59ms      Logs: "stdout", "stderr"<reset>
   193  `
   194  
   195  func ansifyTemplate(template string) string {
   196  	return regexp.MustCompile("<[^>]+>").ReplaceAllStringFunc(
   197  		template,
   198  		func(style string) string {
   199  			style = strings.TrimPrefix(style, "<")
   200  			style = strings.TrimSuffix(style, ">")
   201  			return ansi.ColorCode(style)
   202  		})
   203  }
   204  
   205  func TestPrint(t *testing.T) {
   206  	Convey("Print", t, func() {
   207  		buf := &bytes.Buffer{}
   208  		p := newPrinter(buf, false, func() time.Time {
   209  			return testclock.TestRecentTimeUTC
   210  		})
   211  
   212  		Convey("Build", func() {
   213  			build := &pb.Build{}
   214  			So(protojson.Unmarshal([]byte(buildJSON), build), ShouldBeNil)
   215  
   216  			expectedBuildPrinted := ansifyTemplate(expectedBuildPrintedTemplate)
   217  			p.Build(build)
   218  
   219  			So(p.Err, ShouldBeNil)
   220  			So(buf.String(), ShouldEqual, expectedBuildPrinted)
   221  		})
   222  
   223  		Convey("Partial build", func() {
   224  			// Only Id, Status and Builder are available
   225  			build := &pb.Build{
   226  				Id:     8917899588926498064,
   227  				Status: pb.Status_STARTED,
   228  				Builder: &pb.BuilderID{
   229  					Project: "chromium",
   230  					Bucket:  "try",
   231  					Builder: "linux-rel",
   232  				},
   233  			}
   234  			expectedBuildPrinted := ansifyTemplate("<white+b><white+u><yellow+h>http://ci.chromium.org/b/8917899588926498064<reset><white+b><yellow+h> STARTED   'chromium/try/linux-rel'<reset>\n")
   235  			p.Build(build)
   236  
   237  			So(p.Err, ShouldBeNil)
   238  			So(buf.String(), ShouldEqual, expectedBuildPrinted)
   239  		})
   240  
   241  		Convey("Build error when any of required fields is missing", func() {
   242  			validBuild := &pb.Build{
   243  				Id:     8917899588926498064,
   244  				Status: pb.Status_STARTED,
   245  				Builder: &pb.BuilderID{
   246  					Project: "chromium",
   247  					Bucket:  "try",
   248  					Builder: "linux-rel",
   249  				},
   250  			}
   251  			// Make sure Build can be printed
   252  			p.Build(validBuild)
   253  			So(p.Err, ShouldBeNil)
   254  
   255  			buildWithoutID := proto.Clone(validBuild).(*pb.Build)
   256  			buildWithoutID.Id = 0
   257  			So(func() { p.Build(buildWithoutID) }, ShouldPanic)
   258  
   259  			buildWithoutStatus := proto.Clone(validBuild).(*pb.Build)
   260  			buildWithoutStatus.Status = pb.Status_STATUS_UNSPECIFIED
   261  			So(func() { p.Build(buildWithoutStatus) }, ShouldPanic)
   262  
   263  			buildWithoutBuilder := proto.Clone(validBuild).(*pb.Build)
   264  			buildWithoutBuilder.Builder = nil
   265  			So(func() { p.Build(buildWithoutBuilder) }, ShouldPanic)
   266  		})
   267  
   268  		Convey("Chromium commit", func() {
   269  			p.commit(&pb.GitilesCommit{
   270  				Host:    "chromium.googlesource.com",
   271  				Project: "chromium/src",
   272  				Ref:     "refs/heads/x",
   273  				Id:      "deadbeef",
   274  			})
   275  
   276  			So(p.Err, ShouldBeNil)
   277  			So(buf.String(), ShouldEqual, ansi.Color("https://crrev.com/deadbeef", "white+u")+" on refs/heads/x")
   278  		})
   279  
   280  		Convey("Chromium commit without id", func() {
   281  			p.commit(&pb.GitilesCommit{
   282  				Host:    "chromium.googlesource.com",
   283  				Project: "infra/luci/luci-go",
   284  				Ref:     "refs/heads/x",
   285  			})
   286  
   287  			So(p.Err, ShouldBeNil)
   288  			So(buf.String(), ShouldEqual, ansi.Color("https://chromium.googlesource.com/infra/luci/luci-go/+/refs/heads/x", "white+u"))
   289  		})
   290  
   291  		Convey("Arbitrary commit", func() {
   292  			p.commit(&pb.GitilesCommit{
   293  				Host:    "fuchsia.googlesource.com",
   294  				Project: "infra",
   295  				Ref:     "refs/heads/x",
   296  				Id:      "deadbeef",
   297  			})
   298  
   299  			So(p.Err, ShouldBeNil)
   300  			So(buf.String(), ShouldEqual, ansi.Color("https://fuchsia.googlesource.com/infra/+/deadbeef", "white+u")+" on refs/heads/x")
   301  		})
   302  
   303  		Convey("Arbitrary commit without id", func() {
   304  			p.commit(&pb.GitilesCommit{
   305  				Host:    "fuchsia.googlesource.com",
   306  				Project: "infra",
   307  				Ref:     "refs/heads/x",
   308  			})
   309  
   310  			So(p.Err, ShouldBeNil)
   311  			So(buf.String(), ShouldEqual, ansi.Color("https://fuchsia.googlesource.com/infra/+/refs/heads/x", "white+u"))
   312  		})
   313  
   314  		Convey("Chromium CL", func() {
   315  			p.change(&pb.GerritChange{
   316  				Host:     "chromium-review.googlesource.com",
   317  				Project:  "infra/luci/luci-go",
   318  				Change:   123,
   319  				Patchset: 4,
   320  			})
   321  
   322  			So(p.Err, ShouldBeNil)
   323  			So(buf.String(), ShouldEqual, ansi.Color("https://crrev.com/c/123/4", "white+u"))
   324  		})
   325  
   326  		Convey("Chrome CL", func() {
   327  			p.change(&pb.GerritChange{
   328  				Host:     "chrome-internal-review.googlesource.com",
   329  				Project:  "secret",
   330  				Change:   123,
   331  				Patchset: 4,
   332  			})
   333  
   334  			So(p.Err, ShouldBeNil)
   335  			So(buf.String(), ShouldEqual, ansi.Color("https://crrev.com/i/123/4", "white+u"))
   336  		})
   337  	})
   338  }