github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/spyglass/gcsartifact_test.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package spyglass
    18  
    19  import (
    20  	"bytes"
    21  	"compress/gzip"
    22  	"context"
    23  	"fmt"
    24  	"io"
    25  	"testing"
    26  
    27  	"cloud.google.com/go/storage"
    28  )
    29  
    30  type ByteReadCloser struct {
    31  	io.Reader
    32  }
    33  
    34  func (rc *ByteReadCloser) Close() error {
    35  	return nil
    36  }
    37  
    38  func (rc *ByteReadCloser) Read(p []byte) (int, error) {
    39  	read, err := rc.Reader.Read(p)
    40  	if err != nil {
    41  		return 0, err
    42  	}
    43  	if bytes.Equal(p[:read], []byte("deeper unreadable contents")) {
    44  		return 0, fmt.Errorf("it's just turtes all the way down")
    45  	}
    46  	return read, nil
    47  }
    48  
    49  type fakeArtifactHandle struct {
    50  	oAttrs   *storage.ObjectAttrs
    51  	contents []byte
    52  }
    53  
    54  func (h *fakeArtifactHandle) Attrs(ctx context.Context) (*storage.ObjectAttrs, error) {
    55  	if bytes.Equal(h.contents, []byte("no attrs")) {
    56  		return nil, fmt.Errorf("error getting attrs")
    57  	}
    58  	return h.oAttrs, nil
    59  }
    60  
    61  func (h *fakeArtifactHandle) NewRangeReader(ctx context.Context, offset, length int64) (io.ReadCloser, error) {
    62  	if bytes.Equal(h.contents, []byte("unreadable contents")) {
    63  		return nil, fmt.Errorf("cannot read unreadable contents")
    64  	}
    65  	lenContents := int64(len(h.contents))
    66  	var err error
    67  	var toRead int64
    68  	if length < 0 {
    69  		toRead = lenContents - offset
    70  		err = io.EOF
    71  	} else {
    72  		toRead = length
    73  		if offset+length > lenContents {
    74  			toRead = lenContents - offset
    75  			err = io.EOF
    76  		}
    77  	}
    78  	return &ByteReadCloser{bytes.NewReader(h.contents[offset : offset+toRead])}, err
    79  }
    80  
    81  func (h *fakeArtifactHandle) NewReader(ctx context.Context) (io.ReadCloser, error) {
    82  	var buf bytes.Buffer
    83  	zw := gzip.NewWriter(&buf)
    84  	_, err := zw.Write([]byte("unreadable contents"))
    85  	if err != nil {
    86  		return nil, fmt.Errorf("Failed to gzip log text, err: %v", err)
    87  	}
    88  	if err := zw.Close(); err != nil {
    89  		return nil, fmt.Errorf("Failed to close gzip writer, err: %v", err)
    90  	}
    91  	if bytes.Equal(h.contents, buf.Bytes()) {
    92  		return nil, fmt.Errorf("cannot read unreadable contents, even if they're gzipped")
    93  	}
    94  	if bytes.Equal(h.contents, []byte("unreadable contents")) {
    95  		return nil, fmt.Errorf("cannot read unreadable contents")
    96  	}
    97  	return &ByteReadCloser{bytes.NewReader(h.contents)}, nil
    98  }
    99  
   100  // Tests reading the tail n bytes of data from an artifact
   101  func TestReadTail(t *testing.T) {
   102  	var buf bytes.Buffer
   103  	zw := gzip.NewWriter(&buf)
   104  	_, err := zw.Write([]byte("Oh wow\nlogs\nthis is\ncrazy"))
   105  	if err != nil {
   106  		t.Fatalf("Failed to gzip log text, err: %v", err)
   107  	}
   108  	if err := zw.Close(); err != nil {
   109  		t.Fatalf("Failed to close gzip writer, err: %v", err)
   110  	}
   111  	gzippedLog := buf.Bytes()
   112  	testCases := []struct {
   113  		name      string
   114  		n         int64
   115  		contents  []byte
   116  		encoding  string
   117  		expected  []byte
   118  		expectErr bool
   119  	}{
   120  		{
   121  			name:      "ReadTail example build log",
   122  			n:         4,
   123  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   124  			expected:  []byte("razy"),
   125  			expectErr: false,
   126  		},
   127  		{
   128  			name:      "ReadTail build log, gzipped",
   129  			n:         23,
   130  			contents:  gzippedLog,
   131  			encoding:  "gzip",
   132  			expectErr: true,
   133  		},
   134  		{
   135  			name:      "ReadTail build log, claimed gzipped but not actually gzipped",
   136  			n:         2333,
   137  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   138  			encoding:  "gzip",
   139  			expectErr: true,
   140  		},
   141  		{
   142  			name:      "ReadTail N>size of build log",
   143  			n:         2222,
   144  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   145  			expected:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   146  			expectErr: false,
   147  		},
   148  	}
   149  	for _, tc := range testCases {
   150  		artifact := NewGCSArtifact(context.Background(), &fakeArtifactHandle{
   151  			contents: tc.contents,
   152  			oAttrs: &storage.ObjectAttrs{
   153  				Bucket:          "foo-bucket",
   154  				Name:            "build-log.txt",
   155  				Size:            int64(len(tc.contents)),
   156  				ContentEncoding: tc.encoding,
   157  			},
   158  		}, "", "build-log.txt", 500e6)
   159  		actualBytes, err := artifact.ReadTail(tc.n)
   160  		if err != nil && !tc.expectErr {
   161  			t.Fatalf("Test %s failed with err: %v", tc.name, err)
   162  		}
   163  		if err == nil && tc.expectErr {
   164  			t.Errorf("Test %s did not produce error when expected", tc.name)
   165  		}
   166  		if !bytes.Equal(actualBytes, tc.expected) {
   167  			t.Errorf("Test %s failed.\nExpected: %s\nActual: %s", tc.name, tc.expected, actualBytes)
   168  		}
   169  	}
   170  }
   171  
   172  // Tests reading at most n bytes of data from files in GCS
   173  func TestReadAtMost(t *testing.T) {
   174  	var buf bytes.Buffer
   175  	zw := gzip.NewWriter(&buf)
   176  	_, err := zw.Write([]byte("Oh wow\nlogs\nthis is\ncrazy"))
   177  	if err != nil {
   178  		t.Fatalf("Failed to gzip log text, err: %v", err)
   179  	}
   180  	if err := zw.Close(); err != nil {
   181  		t.Fatalf("Failed to close gzip writer, err: %v", err)
   182  	}
   183  	testCases := []struct {
   184  		name      string
   185  		n         int64
   186  		contents  []byte
   187  		encoding  string
   188  		expected  []byte
   189  		expectErr bool
   190  		expectEOF bool
   191  	}{
   192  		{
   193  			name:      "ReadAtMost example build log",
   194  			n:         4,
   195  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   196  			expected:  []byte("Oh w"),
   197  			expectErr: false,
   198  		},
   199  		{
   200  			name:      "ReadAtMost build log, transparently gzipped",
   201  			n:         8,
   202  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   203  			expected:  []byte("Oh wow\nl"),
   204  			encoding:  "gzip",
   205  			expectErr: false,
   206  		},
   207  		{
   208  			name:      "ReadAtMost unreadable contents",
   209  			n:         2,
   210  			contents:  []byte("unreadable contents"),
   211  			expectErr: true,
   212  		},
   213  		{
   214  			name:      "ReadAtMost unreadable contents",
   215  			n:         45,
   216  			contents:  []byte("deeper unreadable contents"),
   217  			expectErr: true,
   218  		},
   219  		{
   220  			name:      "ReadAtMost N>size of build log",
   221  			n:         2222,
   222  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   223  			expected:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   224  			expectErr: true,
   225  			expectEOF: true,
   226  		},
   227  	}
   228  	for _, tc := range testCases {
   229  		artifact := NewGCSArtifact(context.Background(), &fakeArtifactHandle{
   230  			contents: tc.contents,
   231  			oAttrs: &storage.ObjectAttrs{
   232  				Bucket:          "foo-bucket",
   233  				Name:            "build-log.txt",
   234  				Size:            int64(len(tc.contents)),
   235  				ContentEncoding: tc.encoding,
   236  			},
   237  		}, "", "build-log.txt", 500e6)
   238  		actualBytes, err := artifact.ReadAtMost(tc.n)
   239  		if err != nil && !tc.expectErr {
   240  			if tc.expectEOF && err != io.EOF {
   241  				t.Fatalf("Test %s failed with err: %v, expected EOF", tc.name, err)
   242  			}
   243  			t.Fatalf("Test %s failed with err: %v", tc.name, err)
   244  		}
   245  		if err != nil && tc.expectEOF && err != io.EOF {
   246  			t.Fatalf("Test %s failed with err: %v, expected EOF", tc.name, err)
   247  		}
   248  		if err == nil && tc.expectErr {
   249  			t.Errorf("Test %s did not produce error when expected", tc.name)
   250  		}
   251  		if !bytes.Equal(actualBytes, tc.expected) {
   252  			t.Errorf("Test %s failed.\nExpected: %s\nActual: %s", tc.name, tc.expected, actualBytes)
   253  		}
   254  	}
   255  }
   256  
   257  // Tests reading at offset from files in GCS
   258  func TestReadAt(t *testing.T) {
   259  	var buf bytes.Buffer
   260  	zw := gzip.NewWriter(&buf)
   261  	_, err := zw.Write([]byte("Oh wow\nlogs\nthis is\ncrazy"))
   262  	if err != nil {
   263  		t.Fatalf("Failed to gzip log text, err: %v", err)
   264  	}
   265  	if err := zw.Close(); err != nil {
   266  		t.Fatalf("Failed to close gzip writer, err: %v", err)
   267  	}
   268  	gzippedLog := buf.Bytes()
   269  	testCases := []struct {
   270  		name      string
   271  		n         int64
   272  		offset    int64
   273  		contents  []byte
   274  		encoding  string
   275  		expected  []byte
   276  		expectErr bool
   277  	}{
   278  		{
   279  			name:      "ReadAt example build log",
   280  			n:         4,
   281  			offset:    6,
   282  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   283  			expected:  []byte("\nlog"),
   284  			expectErr: false,
   285  		},
   286  		{
   287  			name:      "ReadAt offset past file size",
   288  			n:         4,
   289  			offset:    400,
   290  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   291  			expectErr: true,
   292  		},
   293  		{
   294  			name:      "ReadAt build log, gzipped",
   295  			n:         23,
   296  			contents:  gzippedLog,
   297  			encoding:  "gzip",
   298  			expectErr: true,
   299  		},
   300  		{
   301  			name:      "ReadAt, claimed gzipped but not actually gzipped",
   302  			n:         2333,
   303  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   304  			encoding:  "gzip",
   305  			expectErr: true,
   306  		},
   307  		{
   308  			name:      "ReadAt offset negative",
   309  			offset:    -3,
   310  			n:         32,
   311  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   312  			expectErr: true,
   313  		},
   314  	}
   315  	for _, tc := range testCases {
   316  		artifact := NewGCSArtifact(context.Background(), &fakeArtifactHandle{
   317  			contents: tc.contents,
   318  			oAttrs: &storage.ObjectAttrs{
   319  				Bucket:          "foo-bucket",
   320  				Name:            "build-log.txt",
   321  				Size:            int64(len(tc.contents)),
   322  				ContentEncoding: tc.encoding,
   323  			},
   324  		}, "", "build-log.txt", 500e6)
   325  		p := make([]byte, tc.n)
   326  		bytesRead, err := artifact.ReadAt(p, tc.offset)
   327  		if err != nil && !tc.expectErr {
   328  			t.Fatalf("Test %s failed with err: %v", tc.name, err)
   329  		}
   330  		if err == nil && tc.expectErr {
   331  			t.Errorf("Test %s did not produce error when expected", tc.name)
   332  		}
   333  		readBytes := p[:bytesRead]
   334  		if !bytes.Equal(readBytes, tc.expected) {
   335  			t.Errorf("Test %s failed.\nExpected: %s\nActual: %s", tc.name, tc.expected, readBytes)
   336  		}
   337  	}
   338  
   339  }
   340  
   341  // Tests reading all data from files in GCS
   342  func TestReadAll(t *testing.T) {
   343  	testCases := []struct {
   344  		name      string
   345  		sizeLimit int64
   346  		contents  []byte
   347  		expectErr bool
   348  		expected  []byte
   349  	}{
   350  		{
   351  			name:      "ReadAll example build log",
   352  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   353  			sizeLimit: 500e6,
   354  			expected:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   355  		},
   356  		{
   357  			name:      "ReadAll example too large build log",
   358  			sizeLimit: 20,
   359  			contents:  []byte("Oh wow\nlogs\nthis is\ncrazy"),
   360  			expectErr: true,
   361  			expected:  nil,
   362  		},
   363  		{
   364  			name:      "ReadAll unable to get reader",
   365  			sizeLimit: 500e6,
   366  			contents:  []byte("unreadable contents"),
   367  			expectErr: true,
   368  			expected:  nil,
   369  		},
   370  		{
   371  			name:      "ReadAll unable to read contents",
   372  			sizeLimit: 500e6,
   373  			contents:  []byte("deeper unreadable contents"),
   374  			expectErr: true,
   375  			expected:  nil,
   376  		},
   377  	}
   378  	for _, tc := range testCases {
   379  		artifact := NewGCSArtifact(context.Background(), &fakeArtifactHandle{
   380  			contents: tc.contents,
   381  			oAttrs: &storage.ObjectAttrs{
   382  				Bucket: "foo-bucket",
   383  				Name:   "build-log.txt",
   384  				Size:   int64(len(tc.contents)),
   385  			},
   386  		}, "", "build-log.txt", tc.sizeLimit)
   387  
   388  		actualBytes, err := artifact.ReadAll()
   389  		if err != nil && !tc.expectErr {
   390  			t.Fatalf("Test %s failed with err: %v", tc.name, err)
   391  		}
   392  		if err == nil && tc.expectErr {
   393  			t.Errorf("Test %s did not produce error when expected", tc.name)
   394  		}
   395  		if !bytes.Equal(actualBytes, tc.expected) {
   396  			t.Errorf("Test %s failed.\nExpected: %s\nActual: %s", tc.name, tc.expected, actualBytes)
   397  		}
   398  	}
   399  }
   400  
   401  func TestSize_GCS(t *testing.T) {
   402  	fakeGCSClient := fakeGCSServer.Client()
   403  	fakeGCSBucket := fakeGCSClient.Bucket("test-bucket")
   404  	startedContent := []byte("hi jason, im started")
   405  	testCases := []struct {
   406  		name      string
   407  		handle    artifactHandle
   408  		expected  int64
   409  		expectErr bool
   410  	}{
   411  		{
   412  			name: "Test size simple",
   413  			handle: &fakeArtifactHandle{
   414  				contents: startedContent,
   415  				oAttrs: &storage.ObjectAttrs{
   416  					Bucket: "foo-bucket",
   417  					Name:   "started.json",
   418  					Size:   int64(len(startedContent)),
   419  				},
   420  			},
   421  			expected:  int64(len(startedContent)),
   422  			expectErr: false,
   423  		},
   424  		{
   425  			name: "Test size from attrs error",
   426  			handle: &fakeArtifactHandle{
   427  				contents: []byte("no attrs"),
   428  				oAttrs: &storage.ObjectAttrs{
   429  					Bucket: "foo-bucket",
   430  					Name:   "started.json",
   431  					Size:   8,
   432  				},
   433  			},
   434  			expectErr: true,
   435  		},
   436  		{
   437  			name:      "Size of nonexistentArtifact",
   438  			handle:    &gcsArtifactHandle{fakeGCSBucket.Object("logs/example-ci-run/404/started.json")},
   439  			expectErr: true,
   440  		},
   441  	}
   442  	for _, tc := range testCases {
   443  		artifact := NewGCSArtifact(context.Background(), tc.handle, "", "started.json", 500e6)
   444  		actual, err := artifact.Size()
   445  		if err != nil && !tc.expectErr {
   446  			t.Fatalf("%s failed getting size for artifact %s, err: %v", tc.name, artifact.JobPath(), err)
   447  		}
   448  		if err == nil && tc.expectErr {
   449  			t.Errorf("%s did not produce error when error was expected.", tc.name)
   450  		}
   451  		if tc.expected != actual {
   452  			t.Errorf("Test %s failed.\nExpected:\n%d\nActual:\n%d", tc.name, tc.expected, actual)
   453  		}
   454  	}
   455  }