Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/ring #50

Merged
merged 9 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions .yamllint.yml

This file was deleted.

110 changes: 41 additions & 69 deletions algorithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,86 +2,58 @@ package medley

import (
"hash"
"hash/fnv"
"strings"
"unsafe"

"github.com/spaolacci/murmur3"
)

const (
// AlgorithmFNV is the configuration value for an fnv.New64a algorithm
AlgorithmFNV = "fnv"

// AlgorithmMurmur3 is the configuration value for a murmur3.New64 algorithm
AlgorithmMurmur3 = "murmur3"
)

// UnknownAlgorithmError indicates that no algorithm could be created using
// the given Name
type UnknownAlgorithmError struct {
// Name is the name which is unrecognized. This will never be blank,
// as a blank name is interpreted as AlgorithmDefault.
Name string
}

// Error fulfills the error interface
func (e *UnknownAlgorithmError) Error() string {
var o strings.Builder
o.WriteString(" algorithm with name: ")
o.WriteString(e.Name)
return o.String()
// Algorithm represents a hash algorithm which medley can use to implement
// service location.
type Algorithm struct {
// New64 is the constructor for a Hash64 appropriate for this algorithm.
// This field is required. If this field is unset, methods on this
// Algorithm may panic.
New64 func() hash.Hash64

// Sum64 is this algorithm's simple function to compute a hash over a
// byte slice. For many algorithms, this function will be more efficient
// in simple cases.
//
// This field is not required. If not supplied, New64 will be used to
// create a hash of the given bytes.
Sum64 func([]byte) uint64
}

// Algorithm is a constructor for a 64-bit hash object. The various NewXXX function
// in the stdlib hash subpackages are of this type.
type Algorithm func() hash.Hash64

// DefaultAlgorithm returns the Algorithm to be used when none is supplied or configured.
// Currently, this package defaults to github.com/spaolacci/murmur3.
func DefaultAlgorithm() Algorithm {
return murmur3.New64
}

var builtinAlgorithms = map[string]Algorithm{
"fnv": fnv.New64a,
"murmur3": murmur3.New64,
}

// findAlgorithm first consults builtinAlgorithms, then the extensions, for the named algorithm.
// If name is empty, DefaultAlgorithm() is returned.
func findAlgorithm(name string, extensions map[string]Algorithm) (alg Algorithm, err error) {
if len(name) > 0 {
var found bool
alg, found = builtinAlgorithms[name]
if !found {
alg, found = extensions[name]
}

if !found {
err = &UnknownAlgorithmError{Name: name}
}
} else {
alg = DefaultAlgorithm()
// Sumb64Bytes uses Sum64 to compute the hash of the given byte slice. If
// the Sum64 field isn't set, New64 is used to create a Hash64 and write
// the given bytes.
func (alg Algorithm) Sum64Bytes(v []byte) uint64 {
if alg.Sum64 != nil {
return alg.Sum64(v)
}

return
h := alg.New64()
h.Write(v)
return h.Sum64()
}

// GetAlgorithm accepts a configured name and attempts to locate the built-in algorithm
// associated with that name. If name is empty, DefaultAlgorithm() is returned.
// Sum64String creates the hash of a string in a way that doesn't create
// unnecessary allocations.
//
// This function will return an error of type *UnknownAlgorithmError if no such algorithm
// was found.
func GetAlgorithm(name string) (Algorithm, error) {
return findAlgorithm(name, nil)
// If Sum64 is set, that function is used to compute the hash. Otherwise,
// New64 is used to create a Hash64 and write the string's bytes.
func (alg Algorithm) Sum64String(v string) uint64 {
return alg.Sum64Bytes(
unsafe.Slice(unsafe.StringData(v), len(v)),
)
}

// FindAlgorithm accepts a configured name and attempts to locate the appropriate Algorithm.
// The set of built-in algorithms is consulted first, followed by the extensions (if supplied).
// The set of extensions can be nil. If name is empty, DefaultAlgorithm() is returned.
//
// This function will return an error of type *UnknownAlgorithmError if no such algorithm
// was found.
func FindAlgorithm(name string, extensions map[string]Algorithm) (Algorithm, error) {
return findAlgorithm(name, extensions)
// DefaultAlgorithm returns the default hash algorithm for medley.
// The returned object uses the murmur3 algorithm. The specific
// implementation is github.com/spaolacci/murmur3.
func DefaultAlgorithm() Algorithm {
return Algorithm{
New64: murmur3.New64,
Sum64: murmur3.Sum64,
}
}
156 changes: 75 additions & 81 deletions algorithm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,105 +2,99 @@ package medley

import (
"hash/fnv"
"strconv"
"testing"

"github.com/spaolacci/murmur3"
"github.com/stretchr/testify/suite"
)

type AlgorithmTestSuite struct {
type AlgorithmSuite struct {
suite.Suite

hashInput string
expected uint64
}

// sum64 is just a Sum64 function that runs the fnv hash
func (suite *AlgorithmSuite) sum64(v []byte) uint64 {
hash := fnv.New64()
hash.Write(v)
return hash.Sum64()
}

func (suite *AlgorithmSuite) SetupTest() {
suite.hashInput = "test hash value"
suite.expected = suite.sum64([]byte(suite.hashInput))
}

func (suite *AlgorithmTestSuite) TestGetAlgorithm() {
testData := []struct {
name string
expectErr bool
}{
{
name: "",
expectErr: false,
},
{
name: AlgorithmFNV,
expectErr: false,
},
{
name: AlgorithmMurmur3,
expectErr: false,
},
{
name: "unknown",
expectErr: true,
},
func (suite *AlgorithmSuite) assertExpected(v uint64) {
suite.Equal(suite.expected, v)
}

func (suite *AlgorithmSuite) testSum64BytesUsingSum64() {
alg := Algorithm{
New64: fnv.New64,
Sum64: suite.sum64,
}

for i, record := range testData {
suite.Run(strconv.Itoa(i), func() {
alg, err := GetAlgorithm(record.name)
suite.Equal(record.expectErr, alg == nil)
if err != nil {
suite.NotEmpty(err.Error())
}
})
suite.assertExpected(
alg.Sum64Bytes([]byte(suite.hashInput)),
)
}

func (suite *AlgorithmSuite) testSum64BytesUsingNew64() {
alg := Algorithm{
New64: fnv.New64,
Sum64: nil,
}

suite.assertExpected(
alg.Sum64Bytes([]byte(suite.hashInput)),
)
}

func (suite *AlgorithmTestSuite) TestFindAlgorithm() {
testData := []struct {
name string
extensions map[string]Algorithm
expectErr bool
}{
{
name: "",
extensions: nil,
expectErr: false,
},
{
name: AlgorithmMurmur3,
extensions: nil,
expectErr: false,
},
{
name: AlgorithmMurmur3,
extensions: map[string]Algorithm{AlgorithmMurmur3: murmur3.New64},
expectErr: false,
},
{
name: AlgorithmMurmur3,
extensions: map[string]Algorithm{"new": fnv.New64a},
expectErr: false,
},
{
name: "unknown",
extensions: nil,
expectErr: true,
},
{
name: "unknown",
extensions: map[string]Algorithm{"new": fnv.New64a},
expectErr: true,
},
{
name: "new",
extensions: map[string]Algorithm{"new": fnv.New64a},
expectErr: false,
},
func (suite *AlgorithmSuite) TestSum64Bytes() {
suite.Run("UsingSum64", suite.testSum64BytesUsingSum64)
suite.Run("UsingNew64", suite.testSum64BytesUsingNew64)
}

func (suite *AlgorithmSuite) testSum64StringUsingSum64() {
alg := Algorithm{
New64: fnv.New64,
Sum64: suite.sum64,
}

for i, record := range testData {
suite.Run(strconv.Itoa(i), func() {
alg, err := FindAlgorithm(record.name, record.extensions)
suite.Equal(record.expectErr, alg == nil)
if err != nil {
suite.NotEmpty(err.Error())
}
})
suite.assertExpected(
alg.Sum64String(suite.hashInput),
)
}

func (suite *AlgorithmSuite) testSum64StringUsingNew64() {
alg := Algorithm{
New64: fnv.New64,
Sum64: nil,
}

suite.assertExpected(
alg.Sum64String(suite.hashInput),
)
}

func (suite *AlgorithmSuite) TestSum64String() {
suite.Run("UsingSum64", suite.testSum64StringUsingSum64)
suite.Run("UsingNew64", suite.testSum64StringUsingNew64)
}

func (suite *AlgorithmSuite) TestDefaultAlgorithm() {
alg := DefaultAlgorithm()
suite.Require().NotNil(alg.New64)
suite.Require().NotNil(alg.Sum64)

expected := murmur3.Sum64([]byte(suite.hashInput))
suite.Equal(expected, alg.Sum64Bytes([]byte(suite.hashInput)))
suite.Equal(expected, alg.Sum64String(suite.hashInput))
}

func TestAlgorithm(t *testing.T) {
suite.Run(t, new(AlgorithmTestSuite))
suite.Run(t, new(AlgorithmSuite))
}
Loading
Loading