blob: 2a71fbf981e04df1778efe231be2ab409f7ca3ee [file] [log] [blame]
package child
import (
"context"
"encoding/hex"
"errors"
"path"
"regexp"
"testing"
"time"
"cloud.google.com/go/storage"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.skia.org/infra/autoroll/go/revision"
"go.skia.org/infra/go/gcs/mocks"
"go.skia.org/infra/go/testutils"
)
const (
revisionID_regex = "123-46-blahblah"
revisionID_basename = "123-46-blahblah.tar.gz"
shortRev = "123-46"
bucket = "my-bucket"
gcsPath_regex = "path/to/123-46-blahblah/object.tar.gz"
gcsPath_basename = "path/to/123-46-blahblah.tar.gz"
owner = "me@google.com"
url_regex = "https://my-bucket/path/to/123-46-blahblah/object.tar.gz"
url_basename = "https://my-bucket/path/to/123-46-blahblah.tar.gz"
)
var (
revisionIDRegex = regexp.MustCompile(`path/to/([0-9a-z_-]+)/.*`)
shortRevRegex = regexp.MustCompile(`(\d+-\d+)-.*`)
updatedTs = time.Unix(1650378340, 0)
fakeMD5 = "abc123"
expectRevision_regex = &revision.Revision{
Id: revisionID_regex,
Checksum: fakeMD5,
Author: owner,
Display: shortRev,
Timestamp: updatedTs,
URL: url_regex,
}
expectRevision_basename = &revision.Revision{
Id: revisionID_basename,
Checksum: fakeMD5,
Author: owner,
Display: shortRev,
Timestamp: updatedTs,
URL: url_basename,
}
objectAttrs_regex = &storage.ObjectAttrs{
Name: gcsPath_regex,
Owner: owner,
Updated: updatedTs,
MD5: mustDecodeHex(fakeMD5),
MediaLink: url_regex,
}
objectAttrs_basename = &storage.ObjectAttrs{
Name: gcsPath_basename,
Owner: owner,
Updated: updatedTs,
MD5: mustDecodeHex(fakeMD5),
MediaLink: url_basename,
}
)
func mustDecodeHex(str string) []byte {
rv, err := hex.DecodeString(str)
if err != nil {
panic(err)
}
return rv
}
func getShortRev(revID string) string {
matches := shortRevRegex.FindStringSubmatch(revID)
if len(matches) != 2 {
return "<short rev regex found no match>"
}
return matches[1]
}
func TestGCSChild_ObjectAttrsToRevision_Regex(t *testing.T) {
c := &gcsChild{
revisionIDRegex: revisionIDRegex,
shortRev: getShortRev,
}
rev, err := c.objectAttrsToRevision(objectAttrs_regex)
require.NoError(t, err)
require.Equal(t, expectRevision_regex, rev)
}
func TestGCSChild_ObjectAttrsToRevision_Basename(t *testing.T) {
c := &gcsChild{
revisionIDRegex: nil, // No revision ID regex; just use the basename.
shortRev: getShortRev,
}
rev, err := c.objectAttrsToRevision(objectAttrs_basename)
require.NoError(t, err)
require.Equal(t, expectRevision_basename, rev)
}
func TestGCSChild_GetRevision_Regex(t *testing.T) {
mockGCS := &mocks.GCSClient{}
c := &gcsChild{
gcs: mockGCS,
gcsPath: gcsPath_regex,
revisionIDRegex: revisionIDRegex,
shortRev: getShortRev,
}
// In this case, the ID was extracted out of the path as opposed to being a
// full path component. Therefore, we don't have a GCS path which exactly
// matches.
gcsObjectPath := path.Join(c.gcsPath, revisionID_regex)
mockGCS.On("GetFileObjectAttrs", testutils.AnyContext, gcsObjectPath).Return(nil, errors.New("not found"))
call := mockGCS.On("AllFilesInDirectory", testutils.AnyContext, c.gcsPath, mock.Anything).Return(nil)
call.RunFn = func(args mock.Arguments) {
_ = args.Get(2).(func(*storage.ObjectAttrs) error)(objectAttrs_regex)
}
rev, err := c.GetRevision(context.Background(), revisionID_regex)
require.NoError(t, err)
require.Equal(t, expectRevision_regex, rev)
}
func TestGCSChild_GetRevision_Basename(t *testing.T) {
mockGCS := &mocks.GCSClient{}
c := &gcsChild{
gcs: mockGCS,
gcsPath: gcsPath_basename,
shortRev: getShortRev,
}
// In this case, the ID was extracted out of the path as opposed to being a
// full path component. Therefore, we don't have a GCS path which exactly
// matches.
gcsObjectPath := path.Join(c.gcsPath, revisionID_basename)
mockGCS.On("GetFileObjectAttrs", testutils.AnyContext, gcsObjectPath).Return(objectAttrs_basename, nil)
rev, err := c.GetRevision(context.Background(), revisionID_basename)
require.NoError(t, err)
require.Equal(t, expectRevision_basename, rev)
}
func TestGCSChild_LogRevisions(t *testing.T) {
mockGCS := &mocks.GCSClient{}
c := &gcsChild{
gcs: mockGCS,
gcsPath: gcsPath_basename,
getGCSVersion: func(rev *revision.Revision) (gcsVersion, error) { return &testGcsVersion{rev.Id}, nil },
shortRev: getShortRev,
}
attrA := &storage.ObjectAttrs{
Name: "path/to/123-46-blahblah.tar.gz",
Updated: updatedTs,
}
revA := &revision.Revision{
Id: "123-46-blahblah.tar.gz",
Display: "123-46",
Timestamp: attrA.Updated,
}
attrB := &storage.ObjectAttrs{
Name: "path/to/123-47-blahblah.tar.gz",
Updated: updatedTs.Add(time.Duration(1)),
}
revB := &revision.Revision{
Id: "123-47-blahblah.tar.gz",
Display: "123-47",
Timestamp: attrB.Updated,
}
attrC := &storage.ObjectAttrs{
Name: "path/to/123-48-blahblah.tar.gz",
Updated: updatedTs.Add(time.Duration(2)),
}
revC := &revision.Revision{
Id: "123-48-blahblah.tar.gz",
Display: "123-48",
Timestamp: attrC.Updated,
}
call := mockGCS.On("AllFilesInDirectory", testutils.AnyContext, c.gcsPath, mock.Anything).Return(nil)
call.RunFn = func(args mock.Arguments) {
fn := args.Get(2).(func(*storage.ObjectAttrs) error)
_ = fn(attrA)
_ = fn(attrB)
_ = fn(attrC)
}
revs, err := c.LogRevisions(context.Background(), revA, revA)
require.NoError(t, err)
require.Nil(t, revs)
revs, err = c.LogRevisions(context.Background(), revA, revB)
require.NoError(t, err)
require.Equal(t, revs, []*revision.Revision{revB})
revs, err = c.LogRevisions(context.Background(), revA, revC)
require.NoError(t, err)
require.Equal(t, revs, []*revision.Revision{revC, revB})
}
func TestGCSChild_GetAllRevisions(t *testing.T) {
mockGCS := &mocks.GCSClient{}
c := &gcsChild{
gcs: mockGCS,
gcsPath: gcsPath_regex,
getGCSVersion: func(rev *revision.Revision) (gcsVersion, error) { return &testGcsVersion{rev.Id}, nil },
revisionIDRegex: revisionIDRegex,
shortRev: getShortRev,
}
objectAttrs_regexOther := &storage.ObjectAttrs{
Name: "path/to/123-47-blahblah/object.tar.gz",
Owner: owner,
Updated: updatedTs,
MD5: mustDecodeHex(fakeMD5),
MediaLink: "https://my-bucket/path/to/123-47-blahblah/object.tar.gz",
}
expectRevision_regexOther := &revision.Revision{
Id: "123-47-blahblah",
Checksum: fakeMD5,
Author: owner,
Display: "123-47",
Timestamp: updatedTs,
URL: "https://my-bucket/path/to/123-47-blahblah/object.tar.gz",
}
call := mockGCS.On("AllFilesInDirectory", testutils.AnyContext, c.gcsPath, mock.Anything).Return(nil)
call.RunFn = func(args mock.Arguments) {
fn := args.Get(2).(func(*storage.ObjectAttrs) error)
_ = fn(objectAttrs_regex)
_ = fn(objectAttrs_regexOther)
}
revs, err := c.getAllRevisions(context.Background())
require.NoError(t, err)
require.Equal(t, len(revs), 2)
require.Contains(t, revs, expectRevision_regex)
require.Contains(t, revs, expectRevision_regexOther)
}
type testGcsVersion struct {
value string
}
func (v *testGcsVersion) Compare(other gcsVersion) int {
if v.value == other.(*testGcsVersion).value {
return 0
} else if v.value > other.(*testGcsVersion).value {
return -1
}
return 1
}
func (v *testGcsVersion) Id() string {
return v.value
}
func TestGCSChild_Update_Regex(t *testing.T) {
mockGCS := &mocks.GCSClient{}
c := &gcsChild{
gcs: mockGCS,
gcsPath: gcsPath_regex,
getGCSVersion: func(rev *revision.Revision) (gcsVersion, error) { return &testGcsVersion{rev.Id}, nil },
revisionIDRegex: revisionIDRegex,
shortRev: getShortRev,
}
objectAttrsOldRevision := &storage.ObjectAttrs{
Name: "path/to/111-11-blahblah/object.tar.gz",
Owner: owner,
Updated: updatedTs,
MediaLink: "https://my-bucket/path/to/111-11-blahblah/object.tar.gz",
}
call := mockGCS.On("AllFilesInDirectory", testutils.AnyContext, c.gcsPath, mock.Anything).Return(nil)
call.RunFn = func(args mock.Arguments) {
fn := args.Get(2).(func(*storage.ObjectAttrs) error)
_ = fn(objectAttrs_regex)
_ = fn(objectAttrsOldRevision)
}
lastRollRev := &revision.Revision{
Id: "111-11-blahblah",
}
tipRev, notRolledRevs, err := c.Update(context.Background(), lastRollRev)
require.NoError(t, err)
require.Equal(t, expectRevision_regex, tipRev)
require.Equal(t, []*revision.Revision{expectRevision_regex}, notRolledRevs)
}
func TestGCSChild_Update_Basename(t *testing.T) {
mockGCS := &mocks.GCSClient{}
c := &gcsChild{
gcs: mockGCS,
gcsPath: gcsPath_regex,
getGCSVersion: func(rev *revision.Revision) (gcsVersion, error) { return &testGcsVersion{rev.Id}, nil },
shortRev: getShortRev,
}
objectAttrsOldRevision := &storage.ObjectAttrs{
Name: "path/to/111-11-blahblah.tar.gz",
Owner: owner,
Updated: updatedTs,
MediaLink: "https://my-bucket/path/to/111-11-blahblah.tar.gz",
}
call := mockGCS.On("AllFilesInDirectory", testutils.AnyContext, c.gcsPath, mock.Anything).Return(nil)
call.RunFn = func(args mock.Arguments) {
fn := args.Get(2).(func(*storage.ObjectAttrs) error)
_ = fn(objectAttrs_basename)
_ = fn(objectAttrsOldRevision)
}
lastRollRev := &revision.Revision{
Id: "111-11-blahblah.tar.gz",
}
tipRev, notRolledRevs, err := c.Update(context.Background(), lastRollRev)
require.NoError(t, err)
require.Equal(t, expectRevision_basename, tipRev)
require.Equal(t, []*revision.Revision{expectRevision_basename}, notRolledRevs)
}