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

feat: add support for custom filesystems #1

Merged
merged 3 commits into from
May 27, 2021
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
54 changes: 52 additions & 2 deletions file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"go/token"
"io/fs"
"io/ioutil"
"path"
"regexp"
Expand All @@ -16,6 +17,17 @@ import (
"github.com/housinganywhere/migrate/migrate/direction"
)

type Filesystem interface {
ReadDir(name string) ([]fs.DirEntry, error)
ReadFile(name string) ([]byte, error)
}

var fileSystemCustom Filesystem

func SetFilesystem(newfileSystem Filesystem) {
fileSystemCustom = newfileSystem
}

var filenameRegex = `^([0-9]+)_(.*)\.(up|down)\.%s$`

// FilenameRegex builds regular expression stmt with given
Expand Down Expand Up @@ -67,7 +79,15 @@ type MigrationFiles []MigrationFile
// ReadContent reads the file's content if the content is empty
func (f *File) ReadContent() error {
if len(f.Content) == 0 {
content, err := ioutil.ReadFile(path.Join(f.Path, f.FileName))
var (
content []byte
err error
)
if fileSystemCustom != nil {
content, err = fileSystemCustom.ReadFile(path.Join(f.Path, f.FileName))
} else {
content, err = ioutil.ReadFile(path.Join(f.Path, f.FileName))
}
if err != nil {
return err
}
Expand Down Expand Up @@ -150,10 +170,40 @@ func (mf *MigrationFiles) From(version uint64, relativeN int) (Files, error) {
return files, nil
}

func readFromFilesystem(path string) (ioFiles []fs.FileInfo, err error) {
if fileSystemCustom == nil {
ioFiles, err = ioutil.ReadDir(path)
if err != nil {
return nil, err
}

} else {
dirEntries, err := fileSystemCustom.ReadDir(path)
if err != nil {
return nil, err
}

for _, e := range dirEntries {
if e.IsDir() {
continue
}

info, err := e.Info()
if err != nil {
return nil, err
}

ioFiles = append(ioFiles, info)
}
}

return ioFiles, err
}

// ReadMigrationFiles reads all migration files from a given path
func ReadMigrationFiles(path string, filenameRegex *regexp.Regexp) (files MigrationFiles, err error) {
// find all migration files in path
ioFiles, err := ioutil.ReadDir(path)
ioFiles, err := readFromFilesystem(path)
if err != nil {
return nil, err
}
Expand Down
253 changes: 136 additions & 117 deletions file/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path"
"testing"

"github.com/housinganywhere/migrate/file/testdata"
"github.com/housinganywhere/migrate/migrate/direction"
)

Expand Down Expand Up @@ -55,15 +56,9 @@ func TestParseFilenameSchema(t *testing.T) {
}
}

func TestFiles(t *testing.T) {
tmpdir, err := ioutil.TempDir("/tmp", "TestLookForMigrationFilesInSearchPath")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)

if err = ioutil.WriteFile(path.Join(tmpdir, "nonsense.txt"), nil, 0755); err != nil {
t.Fatal("Unable to write files in tmpdir", err)
func prepareDisk(tmpdir string) error {
if err := ioutil.WriteFile(path.Join(tmpdir, "nonsense.txt"), nil, 0755); err != nil {
return err
}
ioutil.WriteFile(path.Join(tmpdir, "002_migrationfile.up.sql"), nil, 0755)
ioutil.WriteFile(path.Join(tmpdir, "002_migrationfile.down.sql"), nil, 0755)
Expand All @@ -78,138 +73,162 @@ func TestFiles(t *testing.T) {

ioutil.WriteFile(path.Join(tmpdir, "401_migrationfile.down.sql"), []byte("test"), 0755)

files, err := ReadMigrationFiles(tmpdir, FilenameRegex("sql"))
return nil
}
func TestFiles(t *testing.T) {
tmpdir, err := ioutil.TempDir("/tmp", "TestLookForMigrationFilesInSearchPath")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)

if len(files) == 0 {
t.Fatal("No files returned.")
err = prepareDisk(tmpdir)
if err != nil {
t.Fatal(err)
}

if len(files) != 5 {
t.Fatal("Wrong number of files returned.")
}
t.Run("disk files", checkFiles(tmpdir))

// test sort order
if files[0].Version != 1 || files[1].Version != 2 || files[2].Version != 101 || files[3].Version != 301 || files[4].Version != 401 {
t.Error("Sort order is incorrect")
t.Error(files)
}
SetFilesystem(testdata.MigrationsFiles)

// test UpFile and DownFile
if files[0].UpFile == nil {
t.Fatalf("Missing up file for version %v", files[0].Version)
}
if files[0].DownFile == nil {
t.Fatalf("Missing down file for version %v", files[0].Version)
}
t.Run("embedded files", checkFiles("."))
}

if files[1].UpFile == nil {
t.Fatalf("Missing up file for version %v", files[1].Version)
}
if files[1].DownFile == nil {
t.Fatalf("Missing down file for version %v", files[1].Version)
}
func checkFiles(path string) func(t *testing.T) {
return func(t *testing.T) {
files, err := ReadMigrationFiles(path, FilenameRegex("sql"))
// files, err := ReadMigrationFiles(tmpdir, FilenameRegex("sql"))
if err != nil {
t.Fatal(err)
}

if files[2].UpFile == nil {
t.Fatalf("Missing up file for version %v", files[2].Version)
}
if files[2].DownFile == nil {
t.Fatalf("Missing down file for version %v", files[2].Version)
}
if len(files) == 0 {
t.Fatal("No files returned.")
}

if files[3].UpFile == nil {
t.Fatalf("Missing up file for version %v", files[3].Version)
}
if files[3].DownFile != nil {
t.Fatalf("There should not be a down file for version %v", files[3].Version)
}
if len(files) != 5 {
t.Fatal("Wrong number of files returned.")
}

if files[4].UpFile != nil {
t.Fatalf("There should not be a up file for version %v", files[4].Version)
}
if files[4].DownFile == nil {
t.Fatalf("Missing down file for version %v", files[4].Version)
}
// test sort order
if files[0].Version != 1 || files[1].Version != 2 || files[2].Version != 101 || files[3].Version != 301 || files[4].Version != 401 {
t.Error("Sort order is incorrect")
t.Error(files)
}

// test read
if err = files[4].DownFile.ReadContent(); err != nil {
t.Error("Unable to read file", err)
}
if files[4].DownFile.Content == nil {
t.Fatal("Read content is nil")
}
if string(files[4].DownFile.Content) != "test" {
t.Fatal("Read content is wrong")
}
// test UpFile and DownFile
if files[0].UpFile == nil {
t.Fatalf("Missing up file for version %v", files[0].Version)
}
if files[0].DownFile == nil {
t.Fatalf("Missing down file for version %v", files[0].Version)
}

// test names
if files[0].UpFile.Name != "migrationfile" {
t.Error("file name is not correct", files[0].UpFile.Name)
}
if files[0].UpFile.FileName != "001_migrationfile.up.sql" {
t.Error("file name is not correct", files[0].UpFile.FileName)
}
if files[1].UpFile == nil {
t.Fatalf("Missing up file for version %v", files[1].Version)
}
if files[1].DownFile == nil {
t.Fatalf("Missing down file for version %v", files[1].Version)
}

// test file.From()
// there should be the following versions:
// 1(up&down), 2(up&down), 101(up&down), 301(up), 401(down)
var tests = []struct {
from uint64
relative int
expectRange []uint64
}{
{0, 2, []uint64{1, 2}},
{1, 4, []uint64{2, 101, 301}},
{1, 0, nil},
{0, 1, []uint64{1}},
{0, 0, nil},
{101, -2, []uint64{101, 2}},
{401, -1, []uint64{401}},
}
if files[2].UpFile == nil {
t.Fatalf("Missing up file for version %v", files[2].Version)
}
if files[2].DownFile == nil {
t.Fatalf("Missing down file for version %v", files[2].Version)
}

for _, test := range tests {
var rangeFiles Files
rangeFiles, err = files.From(test.from, test.relative)
if err != nil {
t.Error("Unable to fetch range:", err)
if files[3].UpFile == nil {
t.Fatalf("Missing up file for version %v", files[3].Version)
}
if files[3].DownFile != nil {
t.Fatalf("There should not be a down file for version %v", files[3].Version)
}

if files[4].UpFile != nil {
t.Fatalf("There should not be a up file for version %v", files[4].Version)
}
if files[4].DownFile == nil {
t.Fatalf("Missing down file for version %v", files[4].Version)
}
if len(rangeFiles) != len(test.expectRange) {
t.Fatalf("file.From(): expected %v files, got %v. For test %v.", len(test.expectRange), len(rangeFiles), test.expectRange)

// test read
if err = files[4].DownFile.ReadContent(); err != nil {
t.Error("Unable to read file", err)
}
if files[4].DownFile.Content == nil {
t.Fatal("Read content is nil")
}
if string(files[4].DownFile.Content) != "test" {
t.Fatal("Read content is wrong")
}

// test names
if files[0].UpFile.Name != "migrationfile" {
t.Error("file name is not correct", files[0].UpFile.Name)
}
if files[0].UpFile.FileName != "001_migrationfile.up.sql" {
t.Error("file name is not correct", files[0].UpFile.FileName)
}

// test file.From()
// there should be the following versions:
// 1(up&down), 2(up&down), 101(up&down), 301(up), 401(down)
var tests = []struct {
from uint64
relative int
expectRange []uint64
}{
{0, 2, []uint64{1, 2}},
{1, 4, []uint64{2, 101, 301}},
{1, 0, nil},
{0, 1, []uint64{1}},
{0, 0, nil},
{101, -2, []uint64{101, 2}},
{401, -1, []uint64{401}},
}

for _, test := range tests {
var rangeFiles Files
rangeFiles, err = files.From(test.from, test.relative)
if err != nil {
t.Error("Unable to fetch range:", err)
}
if len(rangeFiles) != len(test.expectRange) {
t.Fatalf("file.From(): expected %v files, got %v. For test %v.", len(test.expectRange), len(rangeFiles), test.expectRange)
}

for i, version := range test.expectRange {
if rangeFiles[i].Version != version {
t.Fatal("file.From(): returned files dont match expectations", test.expectRange)
for i, version := range test.expectRange {
if rangeFiles[i].Version != version {
t.Fatal("file.From(): returned files dont match expectations", test.expectRange)
}
}
}
}

// test ToFirstFrom
tffFiles, err := files.ToFirstFrom(401)
if err != nil {
t.Fatal(err)
}
if len(tffFiles) != 4 {
t.Fatalf("Wrong number of files returned by ToFirstFrom(), expected %v, got %v.", 5, len(tffFiles))
}
if tffFiles[0].Direction != direction.Down {
t.Error("ToFirstFrom() did not return DownFiles")
}
// test ToFirstFrom
tffFiles, err := files.ToFirstFrom(401)
if err != nil {
t.Fatal(err)
}
if len(tffFiles) != 4 {
t.Fatalf("Wrong number of files returned by ToFirstFrom(), expected %v, got %v.", 5, len(tffFiles))
}
if tffFiles[0].Direction != direction.Down {
t.Error("ToFirstFrom() did not return DownFiles")
}

// test ToLastFrom
tofFiles, err := files.ToLastFrom(0)
if err != nil {
t.Fatal(err)
}
if len(tofFiles) != 4 {
t.Fatalf("Wrong number of files returned by ToLastFrom(), expected %v, got %v.", 5, len(tofFiles))
}
if tofFiles[0].Direction != direction.Up {
t.Error("ToFirstFrom() did not return UpFiles")
// test ToLastFrom
tofFiles, err := files.ToLastFrom(0)
if err != nil {
t.Fatal(err)
}
if len(tofFiles) != 4 {
t.Fatalf("Wrong number of files returned by ToLastFrom(), expected %v, got %v.", 5, len(tofFiles))
}
if tofFiles[0].Direction != direction.Up {
t.Error("ToFirstFrom() did not return UpFiles")
}
}

}

func TestDuplicateFiles(t *testing.T) {
Expand Down
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
1 change: 1 addition & 0 deletions file/testdata/401_migrationfile.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
6 changes: 6 additions & 0 deletions file/testdata/migrations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package testdata

import "embed"

//go:embed *.sql
var MigrationsFiles embed.FS
Empty file added file/testdata/nonsense.txt
Empty file.
Loading