Skip to content

Commit

Permalink
Create methodWrappers file and move helpers over to it
Browse files Browse the repository at this point in the history
This includes helpers like plugin.CName, plugin.Name, etc.

Signed-off-by: Enis Inan <[email protected]>
  • Loading branch information
ekinanp committed Oct 18, 2019
1 parent bec6659 commit 014fbc2
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 174 deletions.
89 changes: 0 additions & 89 deletions plugin/helpers.go
Original file line number Diff line number Diff line change
@@ -1,103 +1,14 @@
package plugin

import (
"fmt"
"strings"
"time"

log "github.com/sirupsen/logrus"
)

// DefaultTimeout is the default timeout for prefetching
var DefaultTimeout = 10 * time.Second

/*
Name returns the entry's name as it was passed into
plugin.NewEntry. It is meant to be called by other
Wash packages. Plugin authors should use EntryBase#Name
when writing their plugins.
*/
func Name(e Entry) string {
// The reason we don't expose EntryBase#Name in the Entry
// interface is so plugin authors don't override it. It ensures
// that whatever name they pass into plugin.NewEntry is the
// name received by Wash.
return e.name()
}

/*
CName returns the entry's canonical name, which is what Wash uses to
construct the entry's path. The entry's cname is plugin.Name(e), but with
all '/' characters replaced by a '#' character. CNames are necessary
because it is possible for entry names to have '/'es in them, which is
illegal in bourne shells and UNIX-y filesystems.
CNames are unique. CName uniqueness is checked in plugin.CachedList.
NOTE: The '#' character was chosen because it is unlikely to appear in
a meaningful entry's name. If, however, there's a good chance that an
entry's name can contain the '#' character, and that two entries can
have the same cname (e.g. 'foo/bar', 'foo#bar'), then you can use
e.SetSlashReplacer(<char>) to change the default slash replacer from
a '#' to <char>.
*/
func CName(e Entry) string {
if len(e.name()) == 0 {
panic("plugin.CName: e.name() is empty")
}
// We make the CName a separate function instead of embedding it
// in the Entry interface because doing so prevents plugin authors
// from overriding it.
return strings.Replace(
e.name(),
"/",
string(e.slashReplacer()),
-1,
)
}

// ID returns the entry's ID, which is just its path rooted at Wash's mountpoint.
// An entry's ID is described as
// /<plugin_name>/<parent1_cname>/<parent2_cname>/.../<entry_cname>
//
// NOTE: <plugin_name> is really <plugin_cname>. However since <plugin_name>
// can never contain a '/', <plugin_cname> reduces to <plugin_name>.
func ID(e Entry) string {
if e.id() == "" {
msg := fmt.Sprintf("plugin.ID: entry %v (cname %v) has no ID", e.name(), CName(e))
panic(msg)
}
return e.id()
}

// TrackTime helper is useful for timing functions.
// Use with `defer plugin.TrackTime(time.Now(), "funcname")`.
func TrackTime(start time.Time, name string) {
elapsed := time.Since(start)
log.Infof("%s took %s", name, elapsed)
}

// Attributes returns the entry's attributes. If size is unknown, it will check whether the entry
// has locally cached content and if so set that for the size.
func Attributes(e Entry) EntryAttributes {
// Sometimes an entry doesn't know its size unless it's already downloaded some content. Having
// to download content makes list slow, and is a burden for external plugin developers. Check if
// we already know the size. If not, FUSE will use a reasonable default so tools don't ignore it.
attr := e.attributes()
if !attr.HasSize() && cache != nil {
// We have no way to preserve this on the entry, and it likely wouldn't help because we often
// recreate the entry to ensure we have an accurate representation. So when the cache expires
// we revert to stating the size is unknown until the next read operation.
if val, _ := cache.Get(defaultOpCodeToNameMap[OpenOp], e.id()); val != nil {
rdr := val.(SizedReader)
attr.SetSize(uint64(rdr.Size()))
}
}
return attr
}

// IsPrefetched returns whether an entry has data that was added during creation that it would
// like to have updated.
func IsPrefetched(e Entry) bool {
return e.isPrefetched()
}
85 changes: 0 additions & 85 deletions plugin/helpers_test.go

This file was deleted.

99 changes: 99 additions & 0 deletions plugin/methodWrappers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package plugin

import (
"fmt"
"strings"
"time"
)

// This file contains all of the plugin.<Method> wrappers. You should
// invoke plugin.<Method> instead of e.<Method> because plugin.<Method>
// could contain additional, plugin-agnostic code required to get e.<Method>
// working correctly (like e.g. caching and validation).

// DefaultTimeout is the default timeout for prefetching
var DefaultTimeout = 10 * time.Second

/*
Name returns the entry's name as it was passed into
plugin.NewEntry. It is meant to be called by other
Wash packages. Plugin authors should use EntryBase#Name
when writing their plugins.
*/
func Name(e Entry) string {
// The reason we don't expose EntryBase#Name in the Entry
// interface is so plugin authors don't override it. It ensures
// that whatever name they pass into plugin.NewEntry is the
// name received by Wash.
return e.name()
}

/*
CName returns the entry's canonical name, which is what Wash uses to
construct the entry's path. The entry's cname is plugin.Name(e), but with
all '/' characters replaced by a '#' character. CNames are necessary
because it is possible for entry names to have '/'es in them, which is
illegal in bourne shells and UNIX-y filesystems.
CNames are unique. CName uniqueness is checked in plugin.CachedList.
NOTE: The '#' character was chosen because it is unlikely to appear in
a meaningful entry's name. If, however, there's a good chance that an
entry's name can contain the '#' character, and that two entries can
have the same cname (e.g. 'foo/bar', 'foo#bar'), then you can use
e.SetSlashReplacer(<char>) to change the default slash replacer from
a '#' to <char>.
*/
func CName(e Entry) string {
if len(e.name()) == 0 {
panic("plugin.CName: e.name() is empty")
}
// We make the CName a separate function instead of embedding it
// in the Entry interface because doing so prevents plugin authors
// from overriding it.
return strings.Replace(
e.name(),
"/",
string(e.slashReplacer()),
-1,
)
}

// ID returns the entry's ID, which is just its path rooted at Wash's mountpoint.
// An entry's ID is described as
// /<plugin_name>/<parent1_cname>/<parent2_cname>/.../<entry_cname>
//
// NOTE: <plugin_name> is really <plugin_cname>. However since <plugin_name>
// can never contain a '/', <plugin_cname> reduces to <plugin_name>.
func ID(e Entry) string {
if e.id() == "" {
msg := fmt.Sprintf("plugin.ID: entry %v (cname %v) has no ID", e.name(), CName(e))
panic(msg)
}
return e.id()
}

// Attributes returns the entry's attributes. If size is unknown, it will check whether the entry
// has locally cached content and if so set that for the size.
func Attributes(e Entry) EntryAttributes {
// Sometimes an entry doesn't know its size unless it's already downloaded some content. Having
// to download content makes list slow, and is a burden for external plugin developers. Check if
// we already know the size. If not, FUSE will use a reasonable default so tools don't ignore it.
attr := e.attributes()
if !attr.HasSize() && cache != nil {
// We have no way to preserve this on the entry, and it likely wouldn't help because we often
// recreate the entry to ensure we have an accurate representation. So when the cache expires
// we revert to stating the size is unknown until the next read operation.
if val, _ := cache.Get(defaultOpCodeToNameMap[OpenOp], e.id()); val != nil {
rdr := val.(SizedReader)
attr.SetSize(uint64(rdr.Size()))
}
}
return attr
}

// IsPrefetched returns whether an entry has data that was added during creation that it would
// like to have updated.
func IsPrefetched(e Entry) bool {
return e.isPrefetched()
}
85 changes: 85 additions & 0 deletions plugin/methodWrappers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package plugin

import (
"testing"
"time"

"github.com/puppetlabs/wash/datastore"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)

type MethodWrappersTestSuite struct {
suite.Suite
}

func (suite *MethodWrappersTestSuite) SetupSuite() {
SetTestCache(datastore.NewMemCache())
}

func (suite *MethodWrappersTestSuite) TearDownSuite() {
UnsetTestCache()
}

func (suite *MethodWrappersTestSuite) TestName() {
e := newmethodWrappersTestsMockEntry("foo")
suite.Equal(Name(e), "foo")
}

func (suite *MethodWrappersTestSuite) TestCName() {
e := newmethodWrappersTestsMockEntry("foo/bar/baz")
suite.Equal("foo#bar#baz", CName(e))

e.SetSlashReplacer(':')
suite.Equal("foo:bar:baz", CName(e))
}

func (suite *MethodWrappersTestSuite) TestID() {
e := newmethodWrappersTestsMockEntry("foo/bar")

e.SetTestID("")
suite.Panics(
func() { ID(e) },
"plugin.ID: entry foo (cname foo#bar) has no ID",
)

e.SetTestID("/foo/bar")
suite.Equal(ID(e), "/foo/bar")
}

type methodWrappersTestsMockEntry struct {
EntryBase
mock.Mock
}

func (e *methodWrappersTestsMockEntry) Schema() *EntrySchema {
return nil
}

func newmethodWrappersTestsMockEntry(name string) *methodWrappersTestsMockEntry {
e := &methodWrappersTestsMockEntry{
EntryBase: NewEntry(name),
}
e.SetTestID("id")
e.DisableDefaultCaching()

return e
}

func (suite *MethodWrappersTestSuite) TestAttributes() {
e := newmethodWrappersTestsMockEntry("mockEntry")
e.attr = EntryAttributes{}
e.attr.SetCtime(time.Now())
suite.Equal(e.attr, Attributes(e))
}

func (suite *MethodWrappersTestSuite) TestPrefetched() {
e := newmethodWrappersTestsMockEntry("mockEntry")
suite.False(IsPrefetched(e))
e.Prefetched()
suite.True(IsPrefetched(e))
}

func TestMethodWrappers(t *testing.T) {
suite.Run(t, new(MethodWrappersTestSuite))
}

0 comments on commit 014fbc2

Please sign in to comment.