go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/sink/artifact_uploader_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 "fmt" 20 "net/http" 21 "os" 22 "testing" 23 24 . "github.com/smartystreets/goconvey/convey" 25 "go.chromium.org/luci/common/sync/dispatcher/buffer" 26 . "go.chromium.org/luci/common/testing/assertions" 27 28 pb "go.chromium.org/luci/resultdb/proto/v1" 29 ) 30 31 type mockTransport func(*http.Request) (*http.Response, error) 32 33 func (c mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { 34 return c(req) 35 } 36 37 func TestArtifactUploader(t *testing.T) { 38 t.Parallel() 39 40 name := "invocations/inv1/tests/t1/results/r1/artifacts/stderr" 41 token := "this is an update token" 42 content := "the test passed" 43 contentType := "test/output" 44 // the hash of "the test passed" 45 hash := "sha256:e5d2956e29776b1bca33ff1572bf5ca457cabfb8c370852dbbfcea29953178d2" 46 gcsURI := "gs://bucket/foo" 47 48 Convey("ArtifactUploader", t, func() { 49 ctx := context.Background() 50 reqCh := make(chan *http.Request, 1) 51 keepReq := func(req *http.Request) (*http.Response, error) { 52 reqCh <- req 53 return &http.Response{StatusCode: http.StatusNoContent}, nil 54 } 55 batchReqCh := make(chan *pb.BatchCreateArtifactsRequest, 1) 56 keepBatchReq := func(ctx context.Context, in *pb.BatchCreateArtifactsRequest) (*pb.BatchCreateArtifactsResponse, error) { 57 batchReqCh <- in 58 return nil, nil 59 } 60 uploader := &artifactUploader{ 61 Recorder: &mockRecorder{batchCreateArtifacts: keepBatchReq}, 62 StreamClient: &http.Client{Transport: mockTransport(keepReq)}, 63 StreamHost: "example.org", 64 MaxBatchable: 10 * 1024 * 1024, 65 } 66 67 Convey("Upload w/ file", func() { 68 art := testArtifactWithFile(func(f *os.File) { 69 _, err := f.Write([]byte(content)) 70 So(err, ShouldBeNil) 71 }) 72 art.ContentType = contentType 73 defer os.Remove(art.GetFilePath()) 74 75 Convey("works", func() { 76 ut, err := newUploadTask(name, art) 77 So(err, ShouldBeNil) 78 So(uploader.StreamUpload(ctx, ut, token), ShouldBeNil) 79 80 // validate the request 81 sent := <-reqCh 82 So(sent.URL.String(), ShouldEqual, fmt.Sprintf("https://example.org/%s", name)) 83 So(sent.ContentLength, ShouldEqual, len(content)) 84 So(sent.Header.Get("Content-Hash"), ShouldEqual, hash) 85 So(sent.Header.Get("Content-Type"), ShouldEqual, contentType) 86 So(sent.Header.Get("Update-Token"), ShouldEqual, token) 87 }) 88 89 Convey("fails if file doesn't exist", func() { 90 ut, err := newUploadTask(name, art) 91 So(err, ShouldBeNil) 92 So(os.Remove(art.GetFilePath()), ShouldBeNil) 93 94 // "no such file or directory" 95 So(uploader.StreamUpload(ctx, ut, token), ShouldErrLike, "open "+art.GetFilePath()) 96 }) 97 }) 98 99 Convey("Upload w/ contents", func() { 100 art := testArtifactWithContents([]byte(content)) 101 art.ContentType = contentType 102 ut, err := newUploadTask(name, art) 103 So(err, ShouldBeNil) 104 So(uploader.StreamUpload(ctx, ut, token), ShouldBeNil) 105 106 // validate the request 107 sent := <-reqCh 108 So(sent.URL.String(), ShouldEqual, fmt.Sprintf("https://example.org/%s", name)) 109 So(sent.ContentLength, ShouldEqual, len(content)) 110 So(sent.Header.Get("Content-Hash"), ShouldEqual, hash) 111 So(sent.Header.Get("Content-Type"), ShouldEqual, contentType) 112 So(sent.Header.Get("Update-Token"), ShouldEqual, token) 113 }) 114 115 Convey("Upload w/ gcs not supported by stream upload", func() { 116 art := testArtifactWithGcs(gcsURI) 117 art.ContentType = contentType 118 ut, err := newUploadTask(name, art) 119 So(err, ShouldBeNil) 120 121 // StreamUpload does not support gcsUri upload 122 So(uploader.StreamUpload(ctx, ut, token), ShouldErrLike, "StreamUpload does not support gcsUri upload") 123 }) 124 125 Convey("Batch Upload w/ gcs", func() { 126 art := testArtifactWithGcs(gcsURI) 127 art.ContentType = contentType 128 ut, err := newUploadTask(name, art) 129 ut.size = 5 * 1024 * 1024 130 So(err, ShouldBeNil) 131 132 b := &buffer.Batch{ 133 Data: []buffer.BatchItem{{Item: ut}}, 134 } 135 136 So(uploader.BatchUpload(ctx, b), ShouldBeNil) 137 sent := <-batchReqCh 138 139 So(sent.Requests[0].Artifact.ContentType, ShouldEqual, contentType) 140 So(sent.Requests[0].Artifact.Contents, ShouldBeNil) 141 So(sent.Requests[0].Artifact.SizeBytes, ShouldEqual, 5*1024*1024) 142 So(sent.Requests[0].Artifact.GcsUri, ShouldEqual, gcsURI) 143 }) 144 145 Convey("Batch Upload w/ gcs with artifact size greater than max batch", 146 func() { 147 art := testArtifactWithGcs(gcsURI) 148 art.ContentType = contentType 149 ut, err := newUploadTask(name, art) 150 ut.size = 15 * 1024 * 1024 151 So(err, ShouldBeNil) 152 153 b := &buffer.Batch{ 154 Data: []buffer.BatchItem{{Item: ut}}, 155 } 156 157 So(uploader.BatchUpload(ctx, b), ShouldErrLike, "an artifact is greater than") 158 159 }) 160 161 Convey("Batch Upload w/ mixed artifacts", 162 func() { 163 art1 := testArtifactWithFile(func(f *os.File) { 164 _, err := f.Write([]byte(content)) 165 So(err, ShouldBeNil) 166 }) 167 art1.ContentType = contentType 168 defer os.Remove(art1.GetFilePath()) 169 ut1, err := newUploadTask(name, art1) 170 So(err, ShouldBeNil) 171 172 art2 := testArtifactWithContents([]byte(content)) 173 art2.ContentType = contentType 174 ut2, err := newUploadTask(name, art2) 175 So(err, ShouldBeNil) 176 177 art3 := testArtifactWithGcs(gcsURI) 178 art3.ContentType = contentType 179 ut3, err := newUploadTask(name, art3) 180 ut3.size = 5 * 1024 * 1024 181 So(err, ShouldBeNil) 182 183 b := &buffer.Batch{ 184 Data: []buffer.BatchItem{{Item: ut1}, {Item: ut2}, {Item: ut3}}, 185 } 186 187 So(uploader.BatchUpload(ctx, b), ShouldBeNil) 188 sent := <-batchReqCh 189 190 So(sent.Requests[0].Artifact.ContentType, ShouldEqual, contentType) 191 So(sent.Requests[0].Artifact.Contents, ShouldResemble, []byte(content)) 192 So(sent.Requests[0].Artifact.SizeBytes, ShouldEqual, len(content)) 193 So(sent.Requests[0].Artifact.GcsUri, ShouldEqual, "") 194 195 So(sent.Requests[1].Artifact.ContentType, ShouldEqual, contentType) 196 So(sent.Requests[1].Artifact.Contents, ShouldResemble, []byte(content)) 197 So(sent.Requests[1].Artifact.SizeBytes, ShouldEqual, len(content)) 198 So(sent.Requests[1].Artifact.GcsUri, ShouldEqual, "") 199 200 So(sent.Requests[2].Artifact.ContentType, ShouldEqual, contentType) 201 So(sent.Requests[2].Artifact.Contents, ShouldBeNil) 202 So(sent.Requests[2].Artifact.SizeBytes, ShouldEqual, 5*1024*1024) 203 So(sent.Requests[2].Artifact.GcsUri, ShouldEqual, gcsURI) 204 }) 205 }) 206 }