Repository: puppetlabs/wash
Pull request #182 (2019-04-02 23:00:02)

Remove attribute-metadata syncing logic


Patch:
diff --git a/plugin/aws/ec2Instance.go b/plugin/aws/ec2Instance.go
index 1a80657..830fa74 100644
--- a/plugin/aws/ec2Instance.go
+++ b/plugin/aws/ec2Instance.go
@@ -58,11 +58,7 @@ func newEC2Instance(ctx context.Context, inst *ec2Client.Instance, session *sess
 		SetCtime(metaObj.ctime).
 		SetMtime(metaObj.mtime).
 		SetMeta(metaObj.toMeta())
-	ec2Instance.SetInitialAttributes(attr)
-
-	ec2Instance.
-		Sync(plugin.CtimeAttr(), "CreationTime").
-		Sync(plugin.MtimeAttr(), "LastModifiedTime")
+	ec2Instance.SetAttributes(attr)
 
 	return ec2Instance
 }
diff --git a/plugin/aws/ec2InstanceConsoleOutput.go b/plugin/aws/ec2InstanceConsoleOutput.go
index b798e31..fd63d52 100644
--- a/plugin/aws/ec2InstanceConsoleOutput.go
+++ b/plugin/aws/ec2InstanceConsoleOutput.go
@@ -44,12 +44,7 @@ func newEC2InstanceConsoleOutput(ctx context.Context, inst *ec2Instance, latest
 		SetAtime(output.mtime).
 		SetSize(uint64(len(output.content))).
 		SetMeta(output.toMeta())
-	cl.SetInitialAttributes(attr)
-
-	cl.Sync(plugin.CtimeAttr(), "LastModified")
-	cl.Sync(plugin.MtimeAttr(), "LastModified")
-	cl.Sync(plugin.AtimeAttr(), "LastModified")
-	cl.Sync(plugin.SizeAttr(), "Size")
+	cl.SetAttributes(attr)
 
 	return cl, nil
 }
diff --git a/plugin/aws/ec2InstanceMetadataJSON.go b/plugin/aws/ec2InstanceMetadataJSON.go
index 355feb0..5f69d0c 100644
--- a/plugin/aws/ec2InstanceMetadataJSON.go
+++ b/plugin/aws/ec2InstanceMetadataJSON.go
@@ -30,8 +30,7 @@ func newEC2InstanceMetadataJSON(ctx context.Context, inst *ec2Instance) (*ec2Ins
 	attr := plugin.EntryAttributes{}
 	attr.SetSize(uint64(meta["Size"].(int64)))
 	attr.SetMeta(meta)
-	im.SetInitialAttributes(attr)
-	im.Sync(plugin.SizeAttr(), "Size")
+	im.SetAttributes(attr)
 
 	return im, nil
 }
diff --git a/plugin/aws/s3Bucket.go b/plugin/aws/s3Bucket.go
index fea8340..a4da323 100644
--- a/plugin/aws/s3Bucket.go
+++ b/plugin/aws/s3Bucket.go
@@ -164,7 +164,7 @@ func newS3Bucket(name string, ctime time.Time, session *session.Session) *s3Buck
 		SetCtime(bucket.ctime).
 		SetMtime(bucket.ctime).
 		SetAtime(bucket.ctime)
-	bucket.SetInitialAttributes(attr)
+	bucket.SetAttributes(attr)
 
 	return bucket
 }
diff --git a/plugin/aws/s3Object.go b/plugin/aws/s3Object.go
index 5d43f51..0563544 100644
--- a/plugin/aws/s3Object.go
+++ b/plugin/aws/s3Object.go
@@ -45,13 +45,7 @@ func newS3Object(o *s3Client.Object, name string, bucket string, key string, cli
 		SetMtime(mtime).
 		SetAtime(mtime).
 		SetSize(uint64(awsSDK.Int64Value(o.Size)))
-	s3Obj.SetInitialAttributes(attr)
-
-	s3Obj.
-		Sync(plugin.CtimeAttr(), "LastModified").
-		Sync(plugin.MtimeAttr(), "LastModified").
-		Sync(plugin.AtimeAttr(), "LastModified").
-		Sync(plugin.SizeAttr(), "ContentLength")
+	s3Obj.SetAttributes(attr)
 
 	return s3Obj
 }
diff --git a/plugin/docker/container.go b/plugin/docker/container.go
index 9b2ba80..62562b5 100644
--- a/plugin/docker/container.go
+++ b/plugin/docker/container.go
@@ -38,8 +38,7 @@ func (c *container) List(ctx context.Context) ([]plugin.Entry, error) {
 	}
 	cmAttr := plugin.EntryAttributes{}
 	cmAttr.SetSize(uint64(meta["Size"].(int64)))
-	cm.SetInitialAttributes(cmAttr)
-	cm.Sync(plugin.SizeAttr(), "Size")
+	cm.SetAttributes(cmAttr)
 
 	clf := &containerLogFile{plugin.NewEntry("log"), c.Name(), c.client}
 	clf.DisableCachingFor(plugin.MetadataOp)
@@ -49,8 +48,7 @@ func (c *container) List(ctx context.Context) ([]plugin.Entry, error) {
 	}
 	clfAttr := plugin.EntryAttributes{}
 	clfAttr.SetSize(uint64(meta["Size"].(int64)))
-	clf.SetInitialAttributes(clfAttr)
-	clf.Sync(plugin.SizeAttr(), "Size")
+	clf.SetAttributes(clfAttr)
 
 	return []plugin.Entry{cm, clf}, nil
 }
diff --git a/plugin/docker/containers.go b/plugin/docker/containers.go
index 8d0a81c..9a69697 100644
--- a/plugin/docker/containers.go
+++ b/plugin/docker/containers.go
@@ -36,7 +36,7 @@ func (cs *containers) List(ctx context.Context) ([]plugin.Entry, error) {
 			SetCtime(startTime).
 			SetMtime(startTime).
 			SetAtime(startTime)
-		cont.SetInitialAttributes(attr)
+		cont.SetAttributes(attr)
 
 		keys[i] = cont
 	}
diff --git a/plugin/docker/volume.go b/plugin/docker/volume.go
index 754d87b..a1a925d 100644
--- a/plugin/docker/volume.go
+++ b/plugin/docker/volume.go
@@ -43,7 +43,7 @@ func newVolume(c *client.Client, v *types.Volume) (*volume, error) {
 		SetMtime(startTime).
 		SetAtime(startTime).
 		SetMeta(plugin.ToMeta(v))
-	vol.SetInitialAttributes(attr)
+	vol.SetAttributes(attr)
 
 	return vol, nil
 }
diff --git a/plugin/entryAttributes.go b/plugin/entryAttributes.go
index 6a98474..5ccee09 100644
--- a/plugin/entryAttributes.go
+++ b/plugin/entryAttributes.go
@@ -45,7 +45,7 @@ to do something like
 		SetCtime(ctime).
 		SetMtime(mtime).
 		SetMeta(meta)
-	entry.SetInitialAttributes(attr)
+	entry.SetAttributes(attr)
 */
 type EntryAttributes struct {
 	atime   time.Time
@@ -173,27 +173,3 @@ func (a *EntryAttributes) SetMeta(meta EntryMetadata) *EntryAttributes {
 	a.meta = meta
 	return a
 }
-
-// This helper's useful for testing.
-func (a *EntryAttributes) toMap() map[string]interface{} {
-	mp := make(map[string]interface{})
-
-	if a.HasAtime() {
-		mp[AtimeAttr().name] = a.Atime()
-	}
-	if a.HasMtime() {
-		mp[MtimeAttr().name] = a.Mtime()
-	}
-	if a.HasCtime() {
-		mp[CtimeAttr().name] = a.Ctime()
-	}
-	if a.HasMode() {
-		mp[ModeAttr().name] = a.Mode()
-	}
-	if a.HasSize() {
-		mp[SizeAttr().name] = a.Size()
-	}
-	mp["meta"] = a.Meta()
-
-	return mp
-}
diff --git a/plugin/entryBase.go b/plugin/entryBase.go
index 1afaea6..2e3861c 100644
--- a/plugin/entryBase.go
+++ b/plugin/entryBase.go
@@ -3,8 +3,6 @@ package plugin
 import (
 	"context"
 	"flag"
-	"fmt"
-	"sync"
 	"time"
 )
 
@@ -21,18 +19,11 @@ const (
 
 var defaultOpCodeToNameMap = [3]string{"List", "Open", "Metadata"}
 
-type syncedAttribute struct {
-	*SyncableAttribute
-	key string
-}
-
 // EntryBase implements Entry, making it easy to create new entries.
 // You should use plugin.NewEntry to create new EntryBase objects.
 type EntryBase struct {
 	entryName          string
-	attrMux            *sync.RWMutex
 	attr               EntryAttributes
-	syncedAttrs        []syncedAttribute
 	slashReplacementCh rune
 	// washID represents the entry's wash ID. It is set in CachedList.
 	washID string
@@ -47,7 +38,6 @@ func NewEntry(name string) EntryBase {
 
 	e := EntryBase{
 		entryName:          name,
-		attrMux:            &sync.RWMutex{},
 		slashReplacementCh: '#',
 	}
 	for op := range e.ttl {
@@ -73,9 +63,6 @@ func (e *EntryBase) name() string {
 }
 
 func (e *EntryBase) attributes() EntryAttributes {
-	e.attrMux.RLock()
-	defer e.attrMux.RUnlock()
-
 	return e.attr
 }
 
@@ -105,40 +92,12 @@ func (e *EntryBase) Name() string {
 	return e.name()
 }
 
-// SetInitialAttributes sets the entry's initial attributes. Use it
+// SetAttributes sets the entry's attributes. Use it
 // after creating the entry via a call to NewEntry.
-func (e *EntryBase) SetInitialAttributes(attr EntryAttributes) {
+func (e *EntryBase) SetAttributes(attr EntryAttributes) {
 	e.attr = attr
 }
 
-/*
-Sync syncs the attribute with the given key's value in the entry's
-metadata whenever the latter's refreshed via a call to plugin.CachedMetadata.
-Use Sync if you expect the attribute to change. It returns the entry, e,
-to facilitate the builder pattern, which lets you do something like
-
-	entry.
-		Sync(plugin.CtimeAttr(), "LastModified").
-		Sync(plugin.MtimeAttr(), "LastModified").
-		Sync(plugin.AtimeAttr(), "LastModified").
-		Sync(plugin.SizeAttr(), "ContentLength")
-
-TODO: Find a way to let plugin authors specify a munging function. We'll
-need this for the State attribute, and in case the metadata returns some of
-the other attributes in a different format (e.g. like an arbitrarily formatted
-string for one of the time fields)
-*/
-func (e *EntryBase) Sync(attr *SyncableAttribute, key string) *EntryBase {
-	// We use an array instead of a map because the latter takes up a lot of
-	// memory (~40 bytes for an empty map). That adds up quick when there's
-	// hundreds of thousands of entries.
-	e.syncedAttrs = append(
-		e.syncedAttrs,
-		syncedAttribute{attr, key},
-	)
-	return e
-}
-
 /*
 SetSlashReplacementChar overrides the default '/' replacement
 character of '#' to char. The '/' replacement character is used
@@ -181,34 +140,6 @@ func (e *EntryBase) SetTestID(id string) {
 	e.setID(id)
 }
 
-func (e *EntryBase) hasSyncedAttributes() bool {
-	return len(e.syncedAttrs) > 0
-}
-
-func (e *EntryBase) syncAttributesWith(meta EntryMetadata) error {
-	e.attrMux.Lock()
-	defer e.attrMux.Unlock()
-
-	var allErrs error
-	for _, attr := range e.syncedAttrs {
-		err := attr.sync(e, meta, attr.key)
-		if err != nil {
-			err := fmt.Errorf("failed to sync the %v attribute: %v", attr.name, err)
-			if allErrs != nil {
-				allErrs = fmt.Errorf("%v; %v", allErrs, err)
-			} else {
-				allErrs = err
-			}
-		}
-	}
-	if allErrs != nil {
-		return allErrs
-	}
-	e.attr.SetMeta(meta)
-
-	return nil
-}
-
 func notRunningTests() bool {
 	return flag.Lookup("test.v") == nil
 }
diff --git a/plugin/helpers.go b/plugin/helpers.go
index f42e455..e0faac5 100644
--- a/plugin/helpers.go
+++ b/plugin/helpers.go
@@ -1,7 +1,6 @@
 package plugin
 
 import (
-	"context"
 	"fmt"
 	"io"
 	"os"
@@ -11,7 +10,6 @@ import (
 	"syscall"
 	"time"
 
-	"github.com/puppetlabs/wash/journal"
 	log "github.com/sirupsen/logrus"
 )
 
@@ -162,31 +160,6 @@ func Attributes(e Entry) EntryAttributes {
 	return e.attributes()
 }
 
-// RefreshAttributes refreshes the entry's attributes. It is primarily
-// needed by FUSE because FUSE nodes have a very long lifetime.
-func RefreshAttributes(ctx context.Context, e Entry) error {
-	if !e.hasSyncedAttributes() {
-		// No need to refresh if the entry doesn't have any synced
-		// attributes. This prevents an unnecessary fetch of the
-		// entry's metadata.
-		return nil
-	}
-
-	journal.Record(ctx, "Refreshing the attributes of %v", Path(e))
-
-	// We can't put the refresh-attributes logic in EntryBase because doing
-	// so would call EntryBase#Metadata.
-	meta, err := CachedMetadata(ctx, e)
-	if err != nil {
-		return fmt.Errorf("failed to refresh the attributes: %v", err)
-	}
-	err = e.syncAttributesWith(meta)
-	if err != nil {
-		return fmt.Errorf("failed to refresh the attributes: %v", err)
-	}
-	return nil
-}
-
 // CreateCommand creates a cmd object encapsulating the given cmd and its args.
 // It returns the cmd object + its stdout and stderr pipes.
 //
diff --git a/plugin/kubernetes/pod.go b/plugin/kubernetes/pod.go
index 59d3963..cf9505d 100644
--- a/plugin/kubernetes/pod.go
+++ b/plugin/kubernetes/pod.go
@@ -49,8 +49,7 @@ func newPod(ctx context.Context, client *k8s.Clientset, config *rest.Config, ns
 		SetAtime(attr.Ctime()).
 		SetSize(uint64(meta["LogSize"].(int))).
 		SetMeta(meta)
-	pd.SetInitialAttributes(attr)
-	pd.Sync(plugin.SizeAttr(), "LogSize")
+	pd.SetAttributes(attr)
 
 	return pd, nil
 }
diff --git a/plugin/kubernetes/pvc.go b/plugin/kubernetes/pvc.go
index 7b39474..e543a75 100644
--- a/plugin/kubernetes/pvc.go
+++ b/plugin/kubernetes/pvc.go
@@ -40,7 +40,7 @@ func newPVC(pi typedv1.PersistentVolumeClaimInterface, pd typedv1.PodInterface,
 		SetCtime(p.CreationTimestamp.Time).
 		SetMtime(attr.Ctime()).
 		SetAtime(attr.Ctime())
-	vol.SetInitialAttributes(attr)
+	vol.SetAttributes(attr)
 
 	return vol
 }
diff --git a/plugin/syncableAttribute.go b/plugin/syncableAttribute.go
deleted file mode 100644
index e8ce21c..0000000
--- a/plugin/syncableAttribute.go
+++ /dev/null
@@ -1,170 +0,0 @@
-package plugin
-
-import (
-	"fmt"
-	"time"
-)
-
-// AtimeAttr represents the Atime attribute in plugin.EntryAttributes
-func AtimeAttr() *SyncableAttribute {
-	return &atimeAttr
-}
-
-// MtimeAttr represents the Mtime attribute in plugin.EntryAttributes
-func MtimeAttr() *SyncableAttribute {
-	return &mtimeAttr
-}
-
-// CtimeAttr represents the Ctime attribute in plugin.EntryAttributes
-func CtimeAttr() *SyncableAttribute {
-	return &ctimeAttr
-}
-
-// ModeAttr represents the Mode attribute in plugin.EntryAttributes
-func ModeAttr() *SyncableAttribute {
-	return &modeAttr
-}
-
-// SizeAttr represents the Size attribute in plugin.EntryAttributes
-func SizeAttr() *SyncableAttribute {
-	return &sizeAttr
-}
-
-// SyncableAttribute represents a syncable attribute in
-// plugin.EntryAttributes. It abstracts away the common logic of
-// syncing the attribute with its corresponding key in the entry's
-// metadata whenever the latter's refreshed.
-type SyncableAttribute struct {
-	name   string
-	setter func(*EntryBase, interface{}) error
-}
-
-func (field SyncableAttribute) sync(entry *EntryBase, meta EntryMetadata, key string) error {
-	value, ok := meta[key]
-	if !ok {
-		return fmt.Errorf(
-			"the metadata does not contain a value in its %v key",
-			key,
-		)
-	}
-	if value == nil {
-		return fmt.Errorf(
-			"the metadata's %v key is set to null",
-			key,
-		)
-	}
-	if err := field.setter(entry, value); err != nil {
-		return fmt.Errorf(
-			"failed to munge the metadata's %v key: %v",
-			key,
-			err,
-		)
-	}
-
-	return nil
-}
-
-var atimeAttr = SyncableAttribute{
-	name: "atime",
-	setter: func(entry *EntryBase, v interface{}) error {
-		t, err := mungeToTimeVal(v)
-		if err != nil {
-			return err
-		}
-
-		entry.attr.SetAtime(t)
-		return nil
-	},
-}
-
-var mtimeAttr = SyncableAttribute{
-	name: "mtime",
-	setter: func(entry *EntryBase, v interface{}) error {
-		t, err := mungeToTimeVal(v)
-		if err != nil {
-			return err
-		}
-
-		entry.attr.SetMtime(t)
-		return nil
-	},
-}
-
-var ctimeAttr = SyncableAttribute{
-	name: "ctime",
-	setter: func(entry *EntryBase, v interface{}) error {
-		t, err := mungeToTimeVal(v)
-		if err != nil {
-			return err
-		}
-
-		entry.attr.SetCtime(t)
-		return nil
-	},
-}
-
-var modeAttr = SyncableAttribute{
-	name: "mode",
-	setter: func(entry *EntryBase, v interface{}) error {
-		m, err := ToFileMode(v)
-		if err != nil {
-			return err
-		}
-
-		entry.attr.SetMode(m)
-		return nil
-	},
-}
-
-var sizeAttr = SyncableAttribute{
-	name: "size",
-	setter: func(entry *EntryBase, v interface{}) error {
-		size, err := mungeToSizeVal(v)
-		if err != nil {
-			return err
-		}
-
-		entry.attr.SetSize(size)
-		return nil
-	},
-}
-
-func mungeToTimeVal(v interface{}) (time.Time, error) {
-	switch t := v.(type) {
-	case time.Time:
-		return t, nil
-	case string:
-		// TODO: The layout should be specified by the plugin author.
-		// We could likely have helpers that generate a munging function
-		// given some parameters. For now, this is enough.
-		layout := "2006-01-02T15:04:05Z"
-		return time.Parse(layout, t)
-	default:
-		return time.Time{}, fmt.Errorf("%v is not a time.Time value", v)
-	}
-}
-
-func mungeIntSize(size int64) (uint64, error) {
-	if size < 0 {
-		return 0, fmt.Errorf("%v is a negative size", size)
-	}
-
-	return uint64(size), nil
-}
-
-func mungeToSizeVal(v interface{}) (uint64, error) {
-	switch sz := v.(type) {
-	case uint64:
-		return sz, nil
-	case int:
-		return mungeIntSize(int64(sz))
-	case int32:
-		return mungeIntSize(int64(sz))
-	case int64:
-		return mungeIntSize(sz)
-	case float64:
-		return mungeIntSize(int64(sz))
-	default:
-		return 0, fmt.Errorf("%v is not a valid size type. Valid size types are uint64, int, int64", v)
-	}
-}
diff --git a/plugin/types.go b/plugin/types.go
index 1ca6d3f..b82cd42 100644
--- a/plugin/types.go
+++ b/plugin/types.go
@@ -38,8 +38,6 @@ type Entry interface {
 	Metadata(ctx context.Context) (EntryMetadata, error)
 	name() string
 	attributes() EntryAttributes
-	hasSyncedAttributes() bool
-	syncAttributesWith(meta EntryMetadata) error
 	slashReplacementChar() rune
 	id() string
 	setID(id string)
diff --git a/volume/dir.go b/volume/dir.go
index fc7dd36..c8739c0 100644
--- a/volume/dir.go
+++ b/volume/dir.go
@@ -29,7 +29,7 @@ func NewDir(name string, attr plugin.EntryAttributes, cb ContentCB, path string,
 		path:      path,
 		dirs:      dirs,
 	}
-	vd.SetInitialAttributes(attr)
+	vd.SetAttributes(attr)
 	vd.SetTTLOf(plugin.OpenOp, 60*time.Second)
 
 	return vd
diff --git a/volume/file.go b/volume/file.go
index 7b6c9ed..f063701 100644
--- a/volume/file.go
+++ b/volume/file.go
@@ -24,7 +24,7 @@ func NewFile(name string, attr plugin.EntryAttributes, cb ContentCB, path string
 		contentcb: cb,
 		path:      path,
 	}
-	vf.SetInitialAttributes(attr)
+	vf.SetAttributes(attr)
 	vf.SetTTLOf(plugin.OpenOp, 60*time.Second)
 
 	return vf


Test patch:
diff --git a/plugin/entryBase_test.go b/plugin/entryBase_test.go
index 6dab3e0..ced270e 100644
--- a/plugin/entryBase_test.go
+++ b/plugin/entryBase_test.go
@@ -2,7 +2,6 @@ package plugin
 
 import (
 	"context"
-	"os"
 	"testing"
 	"time"
 
@@ -35,7 +34,7 @@ func (suite *EntryBaseTestSuite) TestNewEntry() {
 	initialAttr.SetCtime(time.Now())
 	e := NewEntry("foo")
 
-	e.SetInitialAttributes(initialAttr)
+	e.SetAttributes(initialAttr)
 	suite.Equal(initialAttr, e.attr)
 
 	suite.Equal("foo", e.Name())
@@ -67,17 +66,6 @@ func (suite *EntryBaseTestSuite) TestMetadata() {
 	suite.assertOpTTL(e, MetadataOp, "Metadata", -1)
 }
 
-func (suite *EntryBaseTestSuite) TestSync() {
-	e := NewEntry("foo")
-
-	e.Sync(CtimeAttr(), "CreationDate")
-	if suite.Equal(1, len(e.syncedAttrs)) {
-		syncedAttr := e.syncedAttrs[0]
-		suite.Equal(CtimeAttr().name, syncedAttr.name)
-		suite.Equal("CreationDate", syncedAttr.key)
-	}
-}
-
 func (suite *EntryBaseTestSuite) TestSetSlashReplacementChar() {
 	e := NewEntry("foo/bar")
 
@@ -90,57 +78,6 @@ func (suite *EntryBaseTestSuite) TestSetSlashReplacementChar() {
 	suite.Equal(e.slashReplacementChar(), ':')
 }
 
-func (suite *EntryBaseTestSuite) TestHasSyncedAttributes() {
-	e := NewEntry("foo")
-	suite.Equal(false, e.hasSyncedAttributes())
-	e.Sync(CtimeAttr(), "CtimeKey")
-	suite.Equal(true, e.hasSyncedAttributes())
-}
-
-func (suite *EntryBaseTestSuite) TestSyncAttributesWithNoErrors() {
-	initialAttr := EntryAttributes{}
-	initialAttr.
-		SetCtime(time.Now()).
-		SetMtime(time.Now())
-
-	e := NewEntry("foo")
-	e.SetInitialAttributes(initialAttr)
-	e.Sync(MtimeAttr(), "LastModified")
-	e.Sync(SizeAttr(), "Size")
-
-	meta := EntryMetadata{
-		"LastModified": time.Now(),
-		"Size":         uint64(15),
-	}
-	err := e.syncAttributesWith(meta)
-	if suite.NoError(err) {
-		expectedAttr := initialAttr.toMap()
-		expectedAttr[MtimeAttr().name] = meta["LastModified"]
-		expectedAttr[SizeAttr().name] = meta["Size"]
-		expectedAttr["meta"] = meta
-
-		suite.Equal(expectedAttr, e.attr.toMap())
-	}
-}
-
-func (suite *EntryBaseTestSuite) TestSyncAttributesWithErrors() {
-	initialAttr := EntryAttributes{}
-	initialAttr.SetMode(os.FileMode(0))
-	initialAttr.SetSize(10)
-
-	e := NewEntry("foo")
-	e.SetInitialAttributes(initialAttr)
-	e.Sync(ModeAttr(), "Mode")
-	e.Sync(SizeAttr(), "Size")
-
-	meta := EntryMetadata{
-		"Mode": "badmode",
-		"Size": int64(-1),
-	}
-	err := e.syncAttributesWith(meta)
-	suite.Regexp("sync.*mode.*attr.*sync.*size.*attr", err)
-}
-
 func TestEntryBase(t *testing.T) {
 	suite.Run(t, new(EntryBaseTestSuite))
 }
diff --git a/plugin/syncableAttribute_test.go b/plugin/syncableAttribute_test.go
deleted file mode 100644
index 9c02a0f..0000000
--- a/plugin/syncableAttribute_test.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package plugin
-
-import (
-	"os"
-	"regexp"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-)
-
-type SyncableAttributeTestSuite struct {
-	suite.Suite
-}
-
-func (suite *SyncableAttributeTestSuite) testAttr(
-	attr *SyncableAttribute,
-	badValue interface{},
-	goodValue interface{},
-	mungedGoodValue interface{},
-) {
-	entry := NewEntry("foo")
-	meta := EntryMetadata{}
-	key := "bar"
-
-	err := attr.sync(&entry, meta, key)
-	suite.Regexp("does not.*bar.*", err)
-
-	meta[key] = nil
-	err = attr.sync(&entry, meta, key)
-	suite.Regexp("bar.*null.*", err)
-
-	meta[key] = badValue
-	err = attr.sync(&entry, meta, key)
-	suite.Regexp("munge.*bar.*key", err)
-
-	meta[key] = goodValue
-	err = attr.sync(&entry, meta, key)
-	if suite.NoError(err) {
-		expectedAttr := make(map[string]interface{})
-		expectedAttr[attr.name] = mungedGoodValue
-		expectedAttr["meta"] = EntryMetadata{}
-
-		attr := entry.attributes()
-		suite.Equal(expectedAttr, attr.toMap())
-	}
-}
-
-func (suite *SyncableAttributeTestSuite) TestAtimeAttr() {
-	t := time.Now()
-	suite.testAttr(AtimeAttr(), "foo", t, t)
-}
-
-func (suite *SyncableAttributeTestSuite) TestMtimeAttr() {
-	t := time.Now()
-	suite.testAttr(MtimeAttr(), "foo", t, t)
-}
-
-func (suite *SyncableAttributeTestSuite) TestCtimeAttr() {
-	t := time.Now()
-	suite.testAttr(CtimeAttr(), "foo", t, t)
-}
-
-func (suite *SyncableAttributeTestSuite) TestModeAttr() {
-	suite.testAttr(ModeAttr(), "badMode", "0777", os.FileMode(0777))
-}
-
-func (suite *SyncableAttributeTestSuite) TestSizeAttr() {
-	suite.testAttr(SizeAttr(), "badSize", int64(12), uint64(12))
-}
-
-func (suite *SyncableAttributeTestSuite) TestMungeToTimeVal() {
-	_, err := mungeToTimeVal("foo")
-	suite.Regexp("parse.*foo", err)
-
-	expectedTime := time.Now()
-	t, err := mungeToTimeVal(expectedTime)
-	if suite.NoError(err) {
-		suite.Equal(expectedTime, t)
-	}
-}
-
-func (suite *SyncableAttributeTestSuite) TestMungeToSizeVal() {
-	type testCase struct {
-		input    interface{}
-		expected uint64
-		errRegex string
-	}
-
-	cases := []testCase{
-		{input: uint64(10), expected: 10},
-		{input: int64(10), expected: 10},
-		{input: int(10), expected: 10},
-		{input: "foo", errRegex: "foo.*size.*uint64.*int.*int64"},
-	}
-
-	for _, c := range cases {
-		actual, err := mungeToSizeVal(c.input)
-		if c.errRegex != "" {
-			suite.Regexp(regexp.MustCompile(c.errRegex), err)
-		} else {
-			if suite.NoError(err) {
-				suite.Equal(c.expected, actual)
-			}
-		}
-	}
-}
-
-func TestSyncableAttribute(t *testing.T) {
-	suite.Run(t, new(SyncableAttributeTestSuite))
-}