go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/sink/artifact_channel_test.go (about)

     1  // Copyright 2020 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 sink
    16  
    17  import (
    18  	"context"
    19  	"io"
    20  	"io/ioutil"
    21  	"net/http"
    22  	"os"
    23  	"testing"
    24  
    25  	. "github.com/smartystreets/goconvey/convey"
    26  	. "go.chromium.org/luci/common/testing/assertions"
    27  	pb "go.chromium.org/luci/resultdb/proto/v1"
    28  	sinkpb "go.chromium.org/luci/resultdb/sink/proto/v1"
    29  )
    30  
    31  func TestArtifactChannel(t *testing.T) {
    32  	t.Parallel()
    33  
    34  	Convey("schedule", t, func() {
    35  		cfg := testServerConfig("127.0.0.1:123", "secret")
    36  		ctx := context.Background()
    37  
    38  		streamCh := make(chan *http.Request, 10)
    39  		cfg.ArtifactStreamClient.Transport = mockTransport(
    40  			func(in *http.Request) (*http.Response, error) {
    41  				streamCh <- in
    42  				return &http.Response{StatusCode: http.StatusNoContent}, nil
    43  			},
    44  		)
    45  		batchCh := make(chan *pb.BatchCreateArtifactsRequest, 10)
    46  		cfg.Recorder.(*mockRecorder).batchCreateArtifacts = func(ctx context.Context, in *pb.BatchCreateArtifactsRequest) (*pb.BatchCreateArtifactsResponse, error) {
    47  			batchCh <- in
    48  			return nil, nil
    49  		}
    50  		createTask := func(name, content string) *uploadTask {
    51  			art := testArtifactWithContents([]byte(content))
    52  			task, err := newUploadTask(name, art)
    53  			So(err, ShouldBeNil)
    54  			return task
    55  		}
    56  
    57  		Convey("with a small artifact", func() {
    58  			task := createTask("invocations/inv/artifacts/art1", "content")
    59  			ac := newArtifactChannel(ctx, &cfg)
    60  			ac.schedule(task)
    61  			ac.closeAndDrain(ctx)
    62  
    63  			// The artifact should have been sent to the batch channel.
    64  			So(<-batchCh, ShouldResembleProto, &pb.BatchCreateArtifactsRequest{
    65  				Requests: []*pb.CreateArtifactRequest{{
    66  					Parent: "invocations/inv",
    67  					Artifact: &pb.Artifact{
    68  						ArtifactId:  "art1",
    69  						ContentType: task.art.ContentType,
    70  						SizeBytes:   int64(len("content")),
    71  						Contents:    []byte("content"),
    72  					},
    73  				}},
    74  			})
    75  		})
    76  
    77  		Convey("with multiple, small artifacts", func() {
    78  			cfg.MaxBatchableArtifactSize = 10
    79  			ac := newArtifactChannel(ctx, &cfg)
    80  
    81  			t1 := createTask("invocations/inv/artifacts/art1", "1234")
    82  			t2 := createTask("invocations/inv/artifacts/art2", "5678")
    83  			t3 := createTask("invocations/inv/artifacts/art3", "9012")
    84  			ac.schedule(t1)
    85  			ac.schedule(t2)
    86  			ac.schedule(t3)
    87  			ac.closeAndDrain(ctx)
    88  
    89  			// The 1st request should contain the first two artifacts.
    90  			So(<-batchCh, ShouldResembleProto, &pb.BatchCreateArtifactsRequest{
    91  				Requests: []*pb.CreateArtifactRequest{
    92  					// art1
    93  					{
    94  						Parent: "invocations/inv",
    95  						Artifact: &pb.Artifact{
    96  							ArtifactId:  "art1",
    97  							ContentType: t1.art.ContentType,
    98  							SizeBytes:   int64(len("1234")),
    99  							Contents:    []byte("1234"),
   100  						},
   101  					},
   102  					// art2
   103  					{
   104  						Parent: "invocations/inv",
   105  						Artifact: &pb.Artifact{
   106  							ArtifactId:  "art2",
   107  							ContentType: t2.art.ContentType,
   108  							SizeBytes:   int64(len("5678")),
   109  							Contents:    []byte("5678"),
   110  						},
   111  					},
   112  				},
   113  			})
   114  
   115  			// The 2nd request should only contain the last one.
   116  			So(<-batchCh, ShouldResembleProto, &pb.BatchCreateArtifactsRequest{
   117  				Requests: []*pb.CreateArtifactRequest{
   118  					// art3
   119  					{
   120  						Parent: "invocations/inv",
   121  						Artifact: &pb.Artifact{
   122  							ArtifactId:  "art3",
   123  							ContentType: t3.art.ContentType,
   124  							SizeBytes:   int64(len("9012")),
   125  							Contents:    []byte("9012"),
   126  						},
   127  					},
   128  				},
   129  			})
   130  		})
   131  
   132  		Convey("with a large artifact", func() {
   133  			cfg.MaxBatchableArtifactSize = 10
   134  			ac := newArtifactChannel(ctx, &cfg)
   135  
   136  			t1 := createTask("invocations/inv/artifacts/art1", "content-foo-bar")
   137  			ac.schedule(t1)
   138  			ac.closeAndDrain(ctx)
   139  
   140  			// The artifact should have been sent to the stream channel.
   141  			req := <-streamCh
   142  			So(req, ShouldNotBeNil)
   143  			So(req.URL.String(), ShouldEqual,
   144  				"https://"+cfg.ArtifactStreamHost+"/invocations/inv/artifacts/art1")
   145  			body, err := io.ReadAll(req.Body)
   146  			So(err, ShouldBeNil)
   147  			So(body, ShouldResemble, []byte("content-foo-bar"))
   148  		})
   149  	})
   150  
   151  }
   152  
   153  func TestUploadTask(t *testing.T) {
   154  	t.Parallel()
   155  
   156  	Convey("newUploadTask", t, func() {
   157  		name := "invocations/inv/artifacts/art1"
   158  		fArt := testArtifactWithFile(func(f *os.File) {
   159  			_, err := f.Write([]byte("content"))
   160  			So(err, ShouldBeNil)
   161  		})
   162  		fArt.ContentType = "plain/text"
   163  		defer os.Remove(fArt.GetFilePath())
   164  
   165  		Convey("works", func() {
   166  			t, err := newUploadTask(name, fArt)
   167  			So(err, ShouldBeNil)
   168  			So(t, ShouldResemble, &uploadTask{art: fArt, artName: name, size: int64(len("content"))})
   169  		})
   170  
   171  		Convey("fails", func() {
   172  			// stat error
   173  			So(os.Remove(fArt.GetFilePath()), ShouldBeNil)
   174  			_, err := newUploadTask(name, fArt)
   175  			So(err, ShouldErrLike, "querying file info")
   176  
   177  			// is a directory
   178  			path, err := ioutil.TempDir("", "foo")
   179  			So(err, ShouldBeNil)
   180  			defer os.RemoveAll(path)
   181  			fArt.Body.(*sinkpb.Artifact_FilePath).FilePath = path
   182  			_, err = newUploadTask(name, fArt)
   183  			So(err, ShouldErrLike, "is a directory")
   184  		})
   185  	})
   186  
   187  	Convey("CreateRequest", t, func() {
   188  		name := "invocations/inv/tests/t1/results/r1/artifacts/a1"
   189  		fArt := testArtifactWithFile(func(f *os.File) {
   190  			_, err := f.Write([]byte("content"))
   191  			So(err, ShouldBeNil)
   192  		})
   193  		fArt.ContentType = "plain/text"
   194  		defer os.Remove(fArt.GetFilePath())
   195  		ut, err := newUploadTask(name, fArt)
   196  		So(err, ShouldBeNil)
   197  
   198  		Convey("works", func() {
   199  			req, err := ut.CreateRequest()
   200  			So(err, ShouldBeNil)
   201  			So(req, ShouldResembleProto, &pb.CreateArtifactRequest{
   202  				Parent: "invocations/inv/tests/t1/results/r1",
   203  				Artifact: &pb.Artifact{
   204  					ArtifactId:  "a1",
   205  					ContentType: "plain/text",
   206  					SizeBytes:   int64(len("content")),
   207  					Contents:    []byte("content"),
   208  				},
   209  			})
   210  		})
   211  
   212  		Convey("fails", func() {
   213  			// the artifact content changed.
   214  			So(os.WriteFile(fArt.GetFilePath(), []byte("surprise!!"), 0), ShouldBeNil)
   215  			_, err := ut.CreateRequest()
   216  			So(err, ShouldErrLike, "the size of the artifact contents changed")
   217  
   218  			// the file no longer exists.
   219  			So(os.Remove(fArt.GetFilePath()), ShouldBeNil)
   220  			_, err = ut.CreateRequest()
   221  			So(err, ShouldErrLike, "open "+fArt.GetFilePath()) // no such file or directory
   222  		})
   223  	})
   224  }