Skip to content

Commit

Permalink
Move rules for initialization to command (#52)
Browse files Browse the repository at this point in the history
# Changes

## Primary change

Move business logic for initializing a meta repo to the init command.

## Supporting changes

Build artifacts on CI in GitHub Actions

## Future work

the other commands
  • Loading branch information
kkrull authored Aug 7, 2024
1 parent 0da55da commit 72e7f67
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 44 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
cache-dependency-path: src/go/go.sum
go-version-file: src/go/go.mod

- run: go build -v ./...
- run: make all

go-test:
defaults:
Expand Down
3 changes: 3 additions & 0 deletions src/go/coremetarepo/meta_data_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ package coremetarepo
type MetaDataAdmin interface {
// Create a meta repository at the specified path on the local filesystem.
Create(metaRepoPath string) error

// Returns true if the specified path exists and is already a meta repository.
IsMetaRepo(metaRepoPath string) (bool, error)
}
22 changes: 17 additions & 5 deletions src/go/coremetarepomock/meta_data_admin_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,39 @@ import (

// Construct a test double for MetaDataAdmin.
func NewMetaDataAdmin() *MetaDataAdmin {
return &MetaDataAdmin{}
return &MetaDataAdmin{
isMetaRepoError: make(map[string]error),
isMetaRepoReturns: make(map[string]bool),
}
}

// Mock implementation for testing with MetaDataAdmin.
type MetaDataAdmin struct {
createCalls []string
createError error
createCalls []string
createError error
isMetaRepoError map[string]error
isMetaRepoReturns map[string]bool
}

func (admin *MetaDataAdmin) Create(metaRepoPath string) error {
admin.createCalls = append(admin.createCalls, metaRepoPath)
return admin.createError
}

// Assert that a meta repo was created at the specified path.
func (admin *MetaDataAdmin) CreateExpected(expectedPath string) {
ginkgo.GinkgoHelper()
Expect(admin.createCalls).To(ContainElement(expectedPath))
}

// Stub #Create to fail with the given error.
func (admin *MetaDataAdmin) CreateFails(err error) {
admin.createError = err
}

func (admin *MetaDataAdmin) IsMetaRepo(path string) (bool, error) {
return admin.isMetaRepoReturns[path], admin.isMetaRepoError[path]
}

func (admin *MetaDataAdmin) IsMetaRepoReturns(path string, value bool, err error) {
admin.isMetaRepoReturns[path] = value
admin.isMetaRepoError[path] = err
}
2 changes: 1 addition & 1 deletion src/go/cukesupport/meta_repo_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func addMetaRepoFixtureAfterLocalDir(ctx *godog.ScenarioContext) {
}

func afterMetaRepo(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
//Always clear state; it could have been initialized by a hook _or_ an explicit step
// Always clear state; it could have been initialized by a hook _or_ an explicit step
forgetThatMetaRepo()
return ctx, err
}
Expand Down
24 changes: 23 additions & 1 deletion src/go/svcfs/json_meta_repo_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ func (admin *JsonMetaRepoAdmin) Create(repositoryDir string) error {
} else if statErr != nil {
return fmt.Errorf("failed to check for existing meta repo %s; %w", repositoryDir, statErr)
} else {
return fmt.Errorf("path already exists: %s", marmotDataDir)
// Ignore an existing meta repo, for now
return nil
}
}

Expand All @@ -41,3 +42,24 @@ func initDirectory(metaDataFile string, rootObject *rootObjectData) error {
return nil
}
}

func (admin *JsonMetaRepoAdmin) IsMetaRepo(path string) (bool, error) {
pathStat, pathErr := os.Stat(path)
if errors.Is(pathErr, fs.ErrNotExist) {
return false, nil
} else if pathErr != nil {
return false, fmt.Errorf("%s: failed to stat meta repo path; %w", path, pathErr)
} else if pathStat.Mode().IsRegular() {
return false, nil
}

marmotDir := metaDataDir(path)
_, marmotDirErr := os.Stat(marmotDir)
if errors.Is(marmotDirErr, fs.ErrNotExist) {
return false, nil
} else if marmotDirErr != nil {
return false, fmt.Errorf("%s: failed to stat Marmot directory; %w", marmotDir, marmotDirErr)
} else {
return true, nil
}
}
100 changes: 73 additions & 27 deletions src/go/svcfs/json_meta_repo_admin_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package svcfs_test

import (
"fmt"
"io/fs"
"os"
"path/filepath"
Expand All @@ -13,59 +12,90 @@ import (
. "github.com/onsi/gomega"
)

var testFsRoot string

var _ = Describe("JsonMetaRepoAdmin", func() {
var (
subject *svcfs.JsonMetaRepoAdmin
metaRepoPath string
testFsRoot string
)
var subject *svcfs.JsonMetaRepoAdmin

BeforeEach(func() {
testFsRoot = expect.NoError(os.MkdirTemp("", "JsonMetaDataRepo-"))
metaRepoPath = filepath.Join(testFsRoot, "meta")
DeferCleanup(os.RemoveAll, testFsRoot)
})

Describe("#Create", func() {
It("is cool with an existing path in which marmot has not been initialized", func() {
Expect(os.MkdirAll(metaRepoPath, fs.ModePerm)).To(Succeed())
It("creates files in the directory, given a valid, writable path", func() {
subject = jsonMetaRepoAdmin(nil)
subject.Create(testFsRoot)

metaDataDir := filepath.Join(testFsRoot, ".marmot")
Expect(os.Stat(metaDataDir)).NotTo(BeNil())

metaDataFile := filepath.Join(metaDataDir, "meta-repo.json")
Expect(os.Stat(metaDataFile)).NotTo(BeNil())
})

It("returns no error, upon success", func() {
subject = jsonMetaRepoAdmin(nil)
Expect(subject.Create(metaRepoPath)).To(Succeed())
Expect(subject.Create(testFsRoot)).To(Succeed())
})

It("returns an error, given a path containing marmot data", func() {
marmotDataDir := filepath.Join(metaRepoPath, ".marmot")
It("accepts an existing directory that is not a Marmot repo", func() {
Expect(os.MkdirAll(testFsRoot, fs.ModePerm)).To(Succeed())
subject = jsonMetaRepoAdmin(nil)
Expect(subject.Create(testFsRoot)).To(Succeed())
})

It("ignores an existing directory already containing Marmot data", func() {
// What about the files inside of .marmot/? Should those be re-created or left alone?
marmotDataDir := filepath.Join(testFsRoot, ".marmot")
Expect(os.MkdirAll(marmotDataDir, fs.ModePerm)).To(Succeed())

subject = jsonMetaRepoAdmin(nil)
Expect(subject.Create(metaRepoPath)).To(
MatchError(fmt.Sprintf("path already exists: %s", marmotDataDir)))
Expect(subject.Create(testFsRoot)).To(Succeed())
})

It("returns an error when unable to check if the path exists", func() {
It("returns an error, given an invalid path", func() {
subject = jsonMetaRepoAdmin(nil)
invalidPathErr := subject.Create("\000x")
Expect(invalidPathErr).NotTo(BeNil())
Expect(subject.Create("\000x")).To(
MatchError(MatchRegexp("failed to check for existing meta repo")))
})

It("returns an error when creating files fails", func() {
It("returns an error, given a path in which files can not be created", func() {
Expect(os.Chmod(testFsRoot, 0o555)).To(Succeed())

subject = jsonMetaRepoAdmin(nil)
Expect(subject.Create(metaRepoPath)).To(
MatchError(ContainSubstring(fmt.Sprintf("failed to make directory %s", metaRepoPath))))
Expect(subject.Create(testFsRoot)).To(
MatchError(MatchRegexp("failed to make directory")))
})
})

It("creates files in the meta repository and returns nil, otherwise", func() {
Describe("#IsMetaRepo", func() {
BeforeEach(func() {
subject = jsonMetaRepoAdmin(nil)
Expect(subject.Create(metaRepoPath)).To(Succeed())
})

metaDataDir := filepath.Join(metaRepoPath, ".marmot")
Expect(os.Stat(metaDataDir)).NotTo(BeNil())
It("returns false, given a non-existent path", func() {
Expect(subject.IsMetaRepo(nonExistentPath())).To(Equal(false))
})

metaDataFile := filepath.Join(metaDataDir, "meta-repo.json")
Expect(os.Stat(metaDataFile)).NotTo(BeNil())
It("returns false, given an existing path that is not a directory", func() {
existingFile := expect.NoError(someFile())
Expect(subject.IsMetaRepo(existingFile)).To(Equal(false))
})

It("returns false, given a directory not containing a Marmot metadata", func() {
Expect(subject.IsMetaRepo(existingPath())).To(Equal(false))
})

It("returns true, given a directory containing Marmot metadata", func() {
marmotDataDir := filepath.Join(testFsRoot, ".marmot")
Expect(os.MkdirAll(marmotDataDir, fs.ModePerm)).To(Succeed())

Expect(subject.IsMetaRepo(testFsRoot)).To(Equal(true))
})

It("returns an error, when checking that path fails", func() {
_, err := subject.IsMetaRepo("\000x")
Expect(err).To(MatchError(ContainSubstring("failed to stat meta repo path")))
})
})
})
Expand All @@ -89,3 +119,19 @@ func (args jsonMetaRepoAdminArgs) Version() string {
return args.version
}
}

/* Filesystem */

func existingPath() string { return testFsRoot }

func someFile() (string, error) {
path := filepath.Join(testFsRoot, "existing-file")
if aFile, createErr := os.Create(path); createErr != nil {
return "", createErr
} else {
defer aFile.Close()
return path, nil
}
}

func nonExistentPath() string { return filepath.Join(testFsRoot, "not-created-yet") }
16 changes: 14 additions & 2 deletions src/go/usemetarepo/init_command.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
package usemetarepo

import core "github.com/kkrull/marmot/coremetarepo"
import (
"fmt"

core "github.com/kkrull/marmot/coremetarepo"
)

// Initializes a new meta repo where none existed before.
type InitCommand struct {
MetaDataAdmin core.MetaDataAdmin
}

func (cmd InitCommand) Run(metaRepoPath string) error {
return cmd.MetaDataAdmin.Create(metaRepoPath)
if isMetaRepo, isMetaRepoErr := cmd.MetaDataAdmin.IsMetaRepo(metaRepoPath); isMetaRepoErr != nil {
return fmt.Errorf("%s: unable to check path; %w", metaRepoPath, isMetaRepoErr)
} else if isMetaRepo {
return fmt.Errorf("%s: already a meta repo", metaRepoPath)
} else if createErr := cmd.MetaDataAdmin.Create(metaRepoPath); createErr != nil {
return fmt.Errorf("failed to initialize meta repo; %w", createErr)
} else {
return nil
}
}
46 changes: 39 additions & 7 deletions src/go/usemetarepo/init_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package usemetarepo_test

import (
"errors"
"os"
"path/filepath"

mock "github.com/kkrull/marmot/coremetarepomock"
expect "github.com/kkrull/marmot/testsupportexpect"
Expand All @@ -12,31 +14,61 @@ import (
. "github.com/onsi/gomega"
)

var testDir string

func existingPath() string { return testDir }
func nonExistentPath() string { return filepath.Join(testDir, "not-created-yet") }
func validPath() string { return filepath.Join(testDir, "meta-default") }

var _ = Describe("InitCommand", func() {
var (
subject *usemetarepo.InitCommand
metaDataAdmin *mock.MetaDataAdmin
)

BeforeEach(func() {
testDir = expect.NoError(os.MkdirTemp("", "InitCommand-"))
DeferCleanup(os.RemoveAll, testDir)

metaDataAdmin = mock.NewMetaDataAdmin()
factory := use.NewCommandFactory().WithMetaDataAdmin(metaDataAdmin)
subject = expect.NoError(factory.NewInitMetaRepo())
})

Describe("#Run", func() {
It("initializes the given meta data source", func() {
_ = subject.Run("/tmp")
metaDataAdmin.CreateExpected("/tmp")
It("creates a meta repo in that path", func() {
givenPath := validPath()
subject.Run(givenPath)
metaDataAdmin.CreateExpected(givenPath)
})

It("returns no error, upon success", func() {
Expect(subject.Run(validPath())).To(Succeed())
})

It("accepts paths that do and do not exist, provided a meta repo is not there", func() {
Expect(subject.Run(existingPath())).To(Succeed())
Expect(subject.Run(nonExistentPath())).To(Succeed())
})

It("returns an error when unable to check the path", func() {
path := filepath.Join(testDir, "stealth")
metaDataAdmin.IsMetaRepoReturns(path, false, errors.New("bang!"))
Expect(subject.Run(path)).To(
MatchError(ContainSubstring("stealth: unable to check path; bang!")))
})

It("returns nil, when everything succeeds", func() {
Expect(subject.Run("/tmp")).To(BeNil())
It("returns an error when the path is already a meta repo", func() {
existingMetaRepo := filepath.Join(testDir, "meta-already")
metaDataAdmin.IsMetaRepoReturns(existingMetaRepo, true, nil)
Expect(subject.Run(existingMetaRepo)).To(
MatchError(MatchRegexp("meta-already: already a meta repo$")))
})

It("returns an error when failing to initialize the meta data source", func() {
It("returns an error when creating a meta repo fails", func() {
metaDataAdmin.CreateFails(errors.New("bang!"))
Expect(subject.Run("/tmp")).To(MatchError("bang!"))
Expect(subject.Run(validPath())).To(
MatchError(MatchRegexp("^failed to initialize meta repo.*bang!$")))
})
})
})

0 comments on commit 72e7f67

Please # to comment.