mirror of
https://github.com/neilotoole/sq.git
synced 2024-12-25 17:25:36 +03:00
added Go dependency cache to go.yml workflow, and go test now covers the full codebase (#53)
* added Go dependency cache to go.yml workflow * more windows path issues * docs * restricting workflow triggers to master * wrong cleanup order for downloaded files * de-restricting workflow triggers, for now * more out-of-order cleanup issues that only manifested on windows * more out-of-order cleanup issues * removed mediatype check from TestFetchFile; FetchFile is deprecated and not worth fixing * tracked down out-of-sequence close/remove issue with sqlite * probably long-running test failure * disabling long-running tests that are breaking CI * switching to go test -short because tests seem to be taking too long in CI
This commit is contained in:
parent
27ff71516e
commit
915e01fdda
78
.github/workflows/go.yml
vendored
78
.github/workflows/go.yml
vendored
@ -1,7 +1,6 @@
|
||||
name: Go
|
||||
on: [push, pull_request]
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
@ -20,6 +19,14 @@ jobs:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache Go dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
@ -28,71 +35,4 @@ jobs:
|
||||
run: go build -v .
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./cli/output/...
|
||||
|
||||
#
|
||||
#
|
||||
# build-macos:
|
||||
# name: Build macOS
|
||||
# runs-on: macos-latest
|
||||
#
|
||||
# steps:
|
||||
# - name: Set up Go 1.14
|
||||
# uses: actions/setup-go@v1
|
||||
# with:
|
||||
# go-version: 1.14
|
||||
# id: go
|
||||
#
|
||||
# - name: Check out code into the Go module directory
|
||||
# uses: actions/checkout@v2
|
||||
#
|
||||
# - name: Get dependencies
|
||||
# run: |
|
||||
# go get -v -t -d ./...
|
||||
#
|
||||
# - name: Build
|
||||
# run: go build -v .
|
||||
#
|
||||
#
|
||||
# build-linux:
|
||||
# name: Build Linux
|
||||
# runs-on: ubuntu-latest
|
||||
#
|
||||
# steps:
|
||||
# - name: Set up Go 1.14
|
||||
# uses: actions/setup-go@v1
|
||||
# with:
|
||||
# go-version: 1.14
|
||||
# id: go
|
||||
#
|
||||
# - name: Check out code into the Go module directory
|
||||
# uses: actions/checkout@v2
|
||||
#
|
||||
# - name: Get dependencies
|
||||
# run: |
|
||||
# go get -v -t -d ./...
|
||||
#
|
||||
# - name: Build
|
||||
# run: go build -v .
|
||||
#
|
||||
# build-windows:
|
||||
# name: Build Windows
|
||||
# runs-on: windows-latest
|
||||
#
|
||||
# steps:
|
||||
# - name: Set up Go 1.14
|
||||
# uses: actions/setup-go@v1
|
||||
# with:
|
||||
# go-version: 1.14
|
||||
# id: go
|
||||
#
|
||||
# - name: Check out code into the Go module directory
|
||||
# uses: actions/checkout@v2
|
||||
#
|
||||
# - name: Get dependencies
|
||||
# run: |
|
||||
# go get -v -t -d ./...
|
||||
#
|
||||
# - name: Build
|
||||
# run: go build -v .
|
||||
#
|
||||
run: go test -short -v ./...
|
||||
|
19
cli/cli.go
19
cli/cli.go
@ -328,7 +328,7 @@ func newDefaultRunContext(ctx context.Context, stdin *os.File, stdout, stderr io
|
||||
}
|
||||
|
||||
// preRunE is invoked by cobra prior to the command RunE being
|
||||
// invoked. It sets up the registry, databases, writer and related
|
||||
// invoked. It sets up the registry, databases, writers and related
|
||||
// fundamental components.
|
||||
func (rc *RunContext) preRunE() error {
|
||||
rc.clnup = cleanup.New()
|
||||
@ -367,17 +367,24 @@ func (rc *RunContext) preRunE() error {
|
||||
}
|
||||
}
|
||||
|
||||
rc.registry = driver.NewRegistry(log)
|
||||
rc.databases = driver.NewDatabases(log, rc.registry, scratchSrcFunc)
|
||||
rc.clnup.AddC(rc.databases)
|
||||
|
||||
var err error
|
||||
rc.files, err = source.NewFiles(log)
|
||||
if err != nil {
|
||||
log.WarnIfFuncError(rc.clnup.Run)
|
||||
return err
|
||||
}
|
||||
|
||||
// Note: it's important that files.Close is invoked
|
||||
// after databases.Close (hence added to clnup first),
|
||||
// because databases could depend upon the existence of
|
||||
// files (such as a sqlite db file).
|
||||
rc.clnup.AddE(rc.files.Close)
|
||||
rc.files.AddTypeDetectors(source.DetectMagicNumber)
|
||||
|
||||
rc.registry = driver.NewRegistry(log)
|
||||
rc.databases = driver.NewDatabases(log, rc.registry, scratchSrcFunc)
|
||||
rc.clnup.AddC(rc.databases)
|
||||
|
||||
rc.registry.AddProvider(sqlite3.Type, &sqlite3.Provider{Log: log})
|
||||
rc.registry.AddProvider(postgres.Type, &postgres.Provider{Log: log})
|
||||
rc.registry.AddProvider(sqlserver.Type, &sqlserver.Provider{Log: log})
|
||||
@ -429,7 +436,7 @@ func (rc *RunContext) preRunE() error {
|
||||
}
|
||||
|
||||
// Close should be invoked to dispose of any open resources
|
||||
// held by rc. If an error occurs during close and rc.Log
|
||||
// held by rc. If an error occurs during Close and rc.Log
|
||||
// is not nil, that error is logged at WARN level before
|
||||
// being returned.
|
||||
func (rc *RunContext) Close() error {
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/neilotoole/sq/drivers/csv"
|
||||
"github.com/neilotoole/sq/drivers/sqlite3"
|
||||
"github.com/neilotoole/sq/drivers/xlsx"
|
||||
"github.com/neilotoole/sq/libsq/source"
|
||||
"github.com/neilotoole/sq/testh"
|
||||
"github.com/neilotoole/sq/testh/proj"
|
||||
@ -54,8 +53,6 @@ func TestCmdInspect(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCmdInspect_Stdin(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
fpath string
|
||||
wantErr bool
|
||||
@ -64,18 +61,15 @@ func TestCmdInspect_Stdin(t *testing.T) {
|
||||
}{
|
||||
{fpath: proj.Abs(sakila.PathCSVActor), wantType: csv.TypeCSV, wantTbls: []string{source.MonotableName}},
|
||||
{fpath: proj.Abs(sakila.PathTSVActor), wantType: csv.TypeTSV, wantTbls: []string{source.MonotableName}},
|
||||
{fpath: proj.Abs(sakila.PathXLSX), wantType: xlsx.Type, wantTbls: sakila.AllTbls},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(testh.TName(tc.fpath), func(t *testing.T) {
|
||||
testh.SkipShort(t, tc.wantType == xlsx.Type)
|
||||
t.Parallel()
|
||||
|
||||
f, err := os.Open(tc.fpath)
|
||||
f, err := os.Open(tc.fpath) // No need to close f
|
||||
require.NoError(t, err)
|
||||
|
||||
ru := newRun(t)
|
||||
ru.rc.Stdin = f
|
||||
|
||||
|
@ -138,6 +138,11 @@ func createTypeTestTable(th *testh.Helper, src *source.Source, withData bool) (n
|
||||
// sanity check: verify that we have the expected cols
|
||||
rows, err := db.QueryContext(th.Context, "SELECT * FROM "+actualTblName+" LIMIT 1")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, rows.Err())
|
||||
require.NoError(t, rows.Close())
|
||||
}()
|
||||
|
||||
gotColNames, err := rows.Columns()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, typeTestColNames, gotColNames)
|
||||
@ -173,7 +178,10 @@ func TestDatabaseTypes(t *testing.T) {
|
||||
th := testh.New(t)
|
||||
src := th.Source(sakila.SL3)
|
||||
actualTblName := createTypeTestTable(th, src, true)
|
||||
t.Cleanup(func() { th.DropTable(src, actualTblName) })
|
||||
th.Cleanup.Add(func() {
|
||||
th.DropTable(src, actualTblName)
|
||||
})
|
||||
|
||||
|
||||
sink := &testh.RecordSink{}
|
||||
recw := output.NewRecordWriterAdapter(sink)
|
||||
|
@ -125,7 +125,7 @@ func (d *Driver) Ping(ctx context.Context, src *source.Source) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer d.log.WarnIfFuncError(dbase.DB().Close)
|
||||
defer d.log.WarnIfCloseError(dbase)
|
||||
|
||||
return dbase.DB().Ping()
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package sqlite3_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
@ -191,7 +192,8 @@ func TestPathFromLocation(t *testing.T) {
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.want, got)
|
||||
want := filepath.FromSlash(tc.want) // for win/unix testing interoperability
|
||||
require.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,9 @@ func (cu *Cleanup) AddC(c io.Closer) *Cleanup {
|
||||
// they were added. All funcs are executed, even in the presence of
|
||||
// an error from a func. Any errors are combined into a single error.
|
||||
// The set of cleanup funcs is removed when Run returns.
|
||||
//
|
||||
// TODO: Consider renaming Run to Close so that Cleanup
|
||||
// implements io.Closer?
|
||||
func (cu *Cleanup) Run() error {
|
||||
if cu == nil {
|
||||
return nil
|
||||
|
@ -213,7 +213,6 @@ func TestSQLDriver_PrepareUpdateStmt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDriver_Ping(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := sakila.All
|
||||
testCases = append(testCases, sakila.CSVActor, sakila.CSVActorHTTP)
|
||||
|
||||
@ -222,7 +221,6 @@ func TestDriver_Ping(t *testing.T) {
|
||||
|
||||
t.Run(handle, func(t *testing.T) {
|
||||
testh.SkipShort(t, handle == sakila.XLSX)
|
||||
t.Parallel()
|
||||
|
||||
th := testh.New(t)
|
||||
src := th.Source(handle)
|
||||
|
@ -92,13 +92,11 @@ func TestQuerySQL_Smoke(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQuerySQL_Count(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := sakila.All
|
||||
testCases := sakila.SQLAll
|
||||
for _, handle := range testCases {
|
||||
handle := handle
|
||||
|
||||
t.Run(handle, func(t *testing.T) {
|
||||
testh.SkipShort(t, handle == sakila.XLSX)
|
||||
t.Parallel()
|
||||
|
||||
th := testh.New(t)
|
||||
|
@ -12,12 +12,13 @@ import (
|
||||
)
|
||||
|
||||
func TestFetchFile(t *testing.T) {
|
||||
file, mediatype, cleanup, err := fetcher.FetchFile(testlg.New(t), sakila.URLSubsetXLSX)
|
||||
file, _, cleanup, err := fetcher.FetchFile(testlg.New(t), sakila.URLSubsetXLSX)
|
||||
if cleanup != nil {
|
||||
defer require.Nil(t, cleanup())
|
||||
defer func() {
|
||||
require.Nil(t, cleanup())
|
||||
}()
|
||||
}
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, file)
|
||||
require.Equal(t, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", mediatype)
|
||||
require.Nil(t, file.Close())
|
||||
}
|
||||
|
@ -285,8 +285,8 @@ func (fs *Files) fetch(loc string) (fpath string, err error) {
|
||||
}
|
||||
|
||||
f, mediatype, cleanFn, err := fetcher.FetchFile(fs.log, u.String())
|
||||
fs.clnup.AddC(f) // f is kept open until fs.Close is called.
|
||||
fs.clnup.AddE(cleanFn)
|
||||
fs.clnup.AddC(f) // f is kept open until fs.Close is called.
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -295,11 +295,16 @@ func (fs *Files) fetch(loc string) (fpath string, err error) {
|
||||
return f.Name(), nil
|
||||
}
|
||||
|
||||
// Close closes any open files.
|
||||
// Close closes any open resources.
|
||||
func (fs *Files) Close() error {
|
||||
return fs.clnup.Run()
|
||||
}
|
||||
|
||||
// CleanupE adds fn to the cleanup sequence invoked by fs.Close.
|
||||
func (fs *Files) CleanupE(fn func() error) {
|
||||
fs.clnup.AddE(fn)
|
||||
}
|
||||
|
||||
// Type returns the source type of location.
|
||||
func (fs *Files) Type(ctx context.Context, loc string) (Type, error) {
|
||||
ploc, err := parseLoc(loc)
|
||||
|
@ -35,18 +35,17 @@ func TestSakila_SQL(t *testing.T) {
|
||||
|
||||
// TestSakila_XLSX is a sanity check for Sakila XLSX test sources.
|
||||
func TestSakila_XLSX(t *testing.T) {
|
||||
testh.SkipShort(t, true)
|
||||
t.Parallel()
|
||||
handles := []string{sakila.XLSXSubset}
|
||||
// TODO: Add sakila.XLSX to handles when performance is reasonable
|
||||
// enough not to break CI.
|
||||
|
||||
handles := []string{sakila.XLSX, sakila.XLSXNoHeader}
|
||||
for _, handle := range handles {
|
||||
handle := handle
|
||||
|
||||
t.Run(handle, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
th := testh.New(t)
|
||||
|
||||
src := th.Source(handle)
|
||||
|
||||
sink, err := th.QuerySQL(src, "SELECT * FROM actor")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sakila.TblActorCount, len(sink.Recs))
|
||||
|
@ -33,6 +33,7 @@ import (
|
||||
"github.com/neilotoole/sq/libsq"
|
||||
"github.com/neilotoole/sq/libsq/cleanup"
|
||||
"github.com/neilotoole/sq/libsq/driver"
|
||||
"github.com/neilotoole/sq/libsq/errz"
|
||||
"github.com/neilotoole/sq/libsq/source"
|
||||
"github.com/neilotoole/sq/libsq/sqlmodel"
|
||||
"github.com/neilotoole/sq/libsq/stringz"
|
||||
@ -54,6 +55,8 @@ type Helper struct {
|
||||
srcCache map[string]*source.Source
|
||||
Context context.Context
|
||||
cancelFn context.CancelFunc
|
||||
|
||||
// Cleanup is used
|
||||
Cleanup *cleanup.Cleanup
|
||||
}
|
||||
|
||||
@ -116,6 +119,11 @@ func (h *Helper) Source(handle string) *source.Source {
|
||||
defer h.mu.Unlock()
|
||||
t := h.T
|
||||
|
||||
// invoke h.Registry to ensure that its cleanup side-effects
|
||||
// happen in the correct order (files get cleaned after
|
||||
// databases, etc.).
|
||||
_ = h.Registry() // FIXME: questionable if this is needed
|
||||
|
||||
// If the handle refers to an external database, we will skip
|
||||
// the test if the envar for the handle is not set.
|
||||
if stringz.InSlice(sakila.SQLAllExternal, handle) {
|
||||
@ -155,16 +163,20 @@ func (h *Helper) Source(handle string) *source.Source {
|
||||
|
||||
srcFile, err := os.Open(fpath)
|
||||
require.NoError(t, err)
|
||||
defer func() {assert.NoError(t, srcFile.Close())}()
|
||||
defer func() {
|
||||
assert.NoError(t, srcFile.Close())
|
||||
}()
|
||||
|
||||
destFile, err := ioutil.TempFile("", "*_"+filepath.Base(src.Location))
|
||||
require.NoError(t, err)
|
||||
defer func() {assert.NoError(t, destFile.Close())}()
|
||||
defer func() {
|
||||
assert.NoError(t, destFile.Close())
|
||||
}()
|
||||
|
||||
destFileName := destFile.Name()
|
||||
|
||||
h.Cleanup.AddE(func() error {
|
||||
return os.Remove(destFileName)
|
||||
h.Files().CleanupE(func() error {
|
||||
return errz.Err(os.Remove(destFileName))
|
||||
})
|
||||
|
||||
_, err = io.Copy(destFile, srcFile)
|
||||
@ -307,7 +319,9 @@ func (h *Helper) CopyTable(dropAfter bool, src *source.Source, fromTable, toTabl
|
||||
// DropTable drops tbl from src.
|
||||
func (h *Helper) DropTable(src *source.Source, tbl string) {
|
||||
dbase := h.openNew(src)
|
||||
defer h.Log.WarnIfCloseError(dbase)
|
||||
defer func() {
|
||||
h.Log.WarnIfError(errz.Err(dbase.Close()))
|
||||
}()
|
||||
|
||||
require.NoError(h.T, dbase.SQLDriver().DropTable(h.Context, dbase.DB(), tbl, true))
|
||||
h.T.Logf("Dropped %s.%s", src.Handle, tbl)
|
||||
@ -386,21 +400,23 @@ func (h *Helper) TruncateTable(src *source.Source, tbl string) (affected int64)
|
||||
}
|
||||
|
||||
// Registry returns the helper's registry instance,
|
||||
// configured with standard providers.
|
||||
// configured with standard providers. Invoking Registry has
|
||||
// the important side-effect of initializing the helper's registry,
|
||||
// files, and databases fields.
|
||||
func (h *Helper) Registry() *driver.Registry {
|
||||
h.regOnce.Do(func() {
|
||||
log := h.Log
|
||||
h.reg = driver.NewRegistry(log)
|
||||
h.dbases = driver.NewDatabases(log, h.reg, sqlite3.NewScratchSource)
|
||||
h.Cleanup.AddC(h.dbases)
|
||||
|
||||
var err error
|
||||
h.files, err = source.NewFiles(log)
|
||||
require.NoError(h.T, err)
|
||||
h.Cleanup.AddC(h.files)
|
||||
|
||||
h.files.AddTypeDetectors(source.DetectMagicNumber)
|
||||
|
||||
h.dbases = driver.NewDatabases(log, h.reg, sqlite3.NewScratchSource)
|
||||
h.Cleanup.AddC(h.dbases)
|
||||
|
||||
h.reg.AddProvider(sqlite3.Type, &sqlite3.Provider{Log: log})
|
||||
h.reg.AddProvider(postgres.Type, &postgres.Provider{Log: log})
|
||||
h.reg.AddProvider(sqlserver.Type, &sqlserver.Provider{Log: log})
|
||||
|
@ -149,6 +149,8 @@ func TestHelper_Files(t *testing.T) {
|
||||
r, err := fs.NewReader(gctx, src)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {require.NoError(t, r.Close())}()
|
||||
|
||||
b, err := ioutil.ReadAll(r)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user