Skip to content

Commit a1e170e

Browse files
authored
Merge pull request #31 from AlphaOne1/separate_test_package
Add internal test exports and improve golangci-lint configuration
2 parents 9b501a4 + 3d9c549 commit a1e170e

9 files changed

Lines changed: 472 additions & 558 deletions

File tree

.github/workflows/codeql.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ jobs:
8282
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
8383
# queries: security-extended,security-and-quality
8484

85-
# If the analyze step fails for one of the languages you are analyzing with
85+
# If the "analyze" step fails for one of the languages you are analyzing with
8686
# "We were unable to automatically build your code", modify the matrix above
8787
# to set the build mode to "manual" for that language. Then modify this step
8888
# to build your code.

.golangci.yaml

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Copyright the dmorph contributors.
2+
# SPDX-License-Identifier: MPL-2.0
3+
14
# Configuration file for golangci-lint
25
# See https://golangci-lint.run/usage/configuration/ for more information
36

@@ -9,25 +12,85 @@ run:
912

1013
linters:
1114
default: all
12-
#enable:
13-
# # Default linters
14-
# - errcheck
15-
# - govet
16-
# - ineffassign
17-
# - staticcheck
18-
# - unused
19-
# # Additional linters
20-
# - gosec
21-
# - misspell
22-
# - revive
23-
# - bodyclose
24-
# - noctx
15+
16+
disable:
17+
- exhaustruct
18+
- forbidigo
19+
- noinlineerr
20+
- nonamedreturns
21+
- wsl
22+
- wsl_v5
2523

2624
exclusions:
25+
warn-unused: true
26+
2727
rules:
28-
- path: main_test.go
28+
- path: _test\.go
2929
linters:
30-
- gosec
30+
- cyclop
31+
- dupl
32+
- dupword
33+
- err113
34+
- funlen
35+
- gocognit
36+
- maintidx
37+
- nestif
38+
39+
- path: examples/
40+
linters:
41+
- err113
42+
43+
settings:
44+
cyclop:
45+
max-complexity: 25
46+
47+
depguard:
48+
rules:
49+
main:
50+
files:
51+
- $all
52+
- "!$test"
53+
- "!**/examples/**/*"
54+
allow:
55+
- $gostd
56+
test:
57+
files:
58+
- $test
59+
- "**/examples/**/*"
60+
allow:
61+
- $gostd
62+
- github.com/stretchr/testify/assert
63+
- github.com/stretchr/testify/require
64+
- github.com/AlphaOne1/dmorph
65+
- modernc.org/sqlite
66+
67+
exhaustive:
68+
default-signifies-exhaustive: true
69+
70+
mnd:
71+
ignored-numbers:
72+
- "2"
73+
74+
paralleltest:
75+
ignore-missing: true
76+
77+
perfsprint:
78+
errorf: false
79+
80+
testpackage:
81+
skip-regexp: internal_test\.go
82+
83+
varnamelen:
84+
max-distance: 10
85+
ignore-names:
86+
- ctx
87+
- db
88+
- err
89+
- tx
90+
91+
whitespace:
92+
multi-if: true
93+
multi-func: true
3194

3295
issues:
3396
max-issues-per-linter: 0

dialects.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,58 @@
44
package dmorph
55

66
import (
7+
"context"
78
"database/sql"
89
"errors"
910
"fmt"
1011
)
1112

1213
// BaseDialect is a convenience type for databases that manage the necessary operations solely using
1314
// queries. Defining the CreateTemplate, AppliedTemplate and RegisterTemplate enables the BaseDialect to
14-
// perform all the necessary operation to fulfill the Dialect interface.
15+
// perform all the necessary operations to fulfill the Dialect interface.
1516
type BaseDialect struct {
1617
CreateTemplate string // statement ensuring the existence of the migration table
1718
AppliedTemplate string // statement getting applied migrations ordered by application date
1819
RegisterTemplate string // statement registering a migration
1920
}
2021

2122
// EnsureMigrationTableExists ensures that the migration table, saving the applied migrations ids, exists.
22-
func (b BaseDialect) EnsureMigrationTableExists(db *sql.DB, tableName string) error {
23-
tx, err := db.Begin()
23+
func (b BaseDialect) EnsureMigrationTableExists(ctx context.Context, db *sql.DB, tableName string) error {
24+
tx, err := db.BeginTx(ctx, nil)
2425

2526
if err != nil {
2627
return err
2728
}
2829

29-
defer func() { _ = tx.Rollback() }()
30-
31-
_, execErr := tx.Exec(fmt.Sprintf(b.CreateTemplate, tableName))
30+
// Safety net for unexpected panics
31+
defer func() {
32+
if tx != nil {
33+
_ = tx.Rollback()
34+
}
35+
}()
3236

33-
if execErr != nil {
37+
if _, execErr := tx.ExecContext(ctx, fmt.Sprintf(b.CreateTemplate, tableName)); execErr != nil {
3438
rollbackErr := tx.Rollback()
39+
tx = nil
40+
3541
return errors.Join(execErr, rollbackErr)
3642
}
3743

3844
if err := tx.Commit(); err != nil {
3945
rollbackErr := tx.Rollback()
46+
tx = nil
47+
4048
return errors.Join(err, rollbackErr)
4149
}
4250

51+
tx = nil
52+
4353
return nil
4454
}
4555

4656
// AppliedMigrations gets the already applied migrations from the database, ordered by application date.
47-
func (b BaseDialect) AppliedMigrations(db *sql.DB, tableName string) ([]string, error) {
48-
rows, rowsErr := db.Query(fmt.Sprintf(b.AppliedTemplate, tableName))
57+
func (b BaseDialect) AppliedMigrations(ctx context.Context, db *sql.DB, tableName string) ([]string, error) {
58+
rows, rowsErr := db.QueryContext(ctx, fmt.Sprintf(b.AppliedTemplate, tableName))
4959

5060
if rowsErr != nil {
5161
return nil, rowsErr
@@ -67,8 +77,8 @@ func (b BaseDialect) AppliedMigrations(db *sql.DB, tableName string) ([]string,
6777
}
6878

6979
// RegisterMigration registers a migration in the migration table.
70-
func (b BaseDialect) RegisterMigration(tx *sql.Tx, id string, tableName string) error {
71-
_, err := tx.Exec(fmt.Sprintf(b.RegisterTemplate, tableName),
80+
func (b BaseDialect) RegisterMigration(ctx context.Context, tx *sql.Tx, id string, tableName string) error {
81+
_, err := tx.ExecContext(ctx, fmt.Sprintf(b.RegisterTemplate, tableName),
7282
sql.Named("id", id))
7383

7484
return err

dialects_test.go

Lines changed: 26 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// Copyright the DMorph contributors.
22
// SPDX-License-Identifier: MPL-2.0
33

4-
package dmorph
4+
package dmorph_test
55

66
import (
7-
"database/sql"
8-
"os"
9-
"regexp"
107
"testing"
118

129
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/AlphaOne1/dmorph"
1313
)
1414

1515
// TestDialectStatements verifies that each database dialect has valid and
@@ -19,19 +19,17 @@ func TestDialectStatements(t *testing.T) {
1919
// that the statements for the databases are somehow filled
2020
tests := []struct {
2121
name string
22-
caller func() BaseDialect
22+
caller func() dmorph.BaseDialect
2323
}{
24-
{name: "CSVQ", caller: DialectCSVQ},
25-
{name: "DB2", caller: DialectDB2},
26-
{name: "MSSQL", caller: DialectMSSQL},
27-
{name: "MySQL", caller: DialectMySQL},
28-
{name: "Oracle", caller: DialectOracle},
29-
{name: "Postgres", caller: DialectPostgres},
30-
{name: "SQLite", caller: DialectSQLite},
24+
{name: "CSVQ", caller: dmorph.DialectCSVQ},
25+
{name: "DB2", caller: dmorph.DialectDB2},
26+
{name: "MSSQL", caller: dmorph.DialectMSSQL},
27+
{name: "MySQL", caller: dmorph.DialectMySQL},
28+
{name: "Oracle", caller: dmorph.DialectOracle},
29+
{name: "Postgres", caller: dmorph.DialectPostgres},
30+
{name: "SQLite", caller: dmorph.DialectSQLite},
3131
}
3232

33-
re := regexp.MustCompile("%s")
34-
3533
for k, v := range tests {
3634
d := v.caller()
3735

@@ -40,83 +38,54 @@ func TestDialectStatements(t *testing.T) {
4038
}
4139
assert.Contains(t, d.CreateTemplate, "%s",
4240
"no table name placeholder in create template for", v.name)
43-
assert.Regexp(t, re, d.CreateTemplate)
4441

4542
if len(d.AppliedTemplate) < 10 {
4643
t.Errorf("%v: applied template is too short for %v", k, v.name)
4744
}
4845
assert.Contains(t, d.AppliedTemplate, "%s",
4946
"no table name placeholder in applied template for", v.name)
50-
assert.Regexp(t, re, d.AppliedTemplate)
5147

5248
if len(d.RegisterTemplate) < 10 {
5349
t.Errorf("%v: register template is too short for %v", k, v.name)
5450
}
5551
assert.Contains(t, d.RegisterTemplate, "%s",
5652
"no table name placeholder in register template for", v.name)
57-
assert.Regexp(t, re, d.RegisterTemplate)
5853
}
5954
}
6055

6156
// TestCallsOnClosedDB verifies that methods fail as expected when called on a closed database connection.
6257
func TestCallsOnClosedDB(t *testing.T) {
63-
dbFile, dbFileErr := prepareDB()
64-
65-
if dbFileErr != nil {
66-
t.Errorf("DB file could not be created: %v", dbFileErr)
67-
} else {
68-
defer func() { _ = os.Remove(dbFile) }()
69-
}
70-
71-
db, dbErr := sql.Open("sqlite", dbFile)
72-
73-
if dbErr != nil {
74-
t.Errorf("DB file could not be created: %v", dbErr)
75-
} else {
76-
_ = db.Close()
77-
}
58+
db := openTempSQLite(t)
59+
require.NoError(t, db.Close())
7860

7961
assert.Error(t,
80-
DialectSQLite().EnsureMigrationTableExists(db, "irrelevant"),
62+
dmorph.DialectSQLite().EnsureMigrationTableExists(t.Context(), db, "irrelevant"),
8163
"expected error on closed database")
8264

83-
_, err := DialectSQLite().AppliedMigrations(db, "irrelevant")
65+
_, err := dmorph.DialectSQLite().AppliedMigrations(t.Context(), db, "irrelevant")
8466
assert.Error(t, err, "expected error on closed database")
8567
}
8668

8769
// TestEnsureMigrationTableExistsSQLError tests the EnsureMigrationTableExists function
8870
// for handling SQL errors during execution.
8971
func TestEnsureMigrationTableExistsSQLError(t *testing.T) {
90-
d := BaseDialect{
72+
dialect := dmorph.BaseDialect{
9173
CreateTemplate: `
9274
CRATE TABLE test (
9375
id VARCHAR(255) PRIMARY KEY,
9476
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP
9577
)`,
9678
}
9779

98-
dbFile, dbFileErr := prepareDB()
99-
100-
if dbFileErr != nil {
101-
t.Errorf("DB file could not be created: %v", dbFileErr)
102-
} else {
103-
defer func() { _ = os.Remove(dbFile) }()
104-
}
80+
db := openTempSQLite(t)
10581

106-
db, dbErr := sql.Open("sqlite", dbFile)
107-
108-
if dbErr != nil {
109-
t.Errorf("DB file could not be created: %v", dbErr)
110-
} else {
111-
defer func() { _ = db.Close() }()
112-
}
113-
114-
assert.Error(t, d.EnsureMigrationTableExists(db, "test"), "expected error")
82+
assert.Error(t, dialect.EnsureMigrationTableExists(t.Context(), db, "test"), "expected error")
11583
}
11684

117-
// TestEnsureMigrationTableExistsCommitError tests the behavior of EnsureMigrationTableExists when a commit error occurs.
85+
// TestEnsureMigrationTableExistsCommitError tests the behavior of EnsureMigrationTableExists
86+
// when a commit error occurs.
11887
func TestEnsureMigrationTableExistsCommitError(t *testing.T) {
119-
d := BaseDialect{
88+
dialect := dmorph.BaseDialect{
12089
CreateTemplate: `
12190
CREATE TABLE t0 (
12291
id INTEGER PRIMARY KEY
@@ -134,25 +103,11 @@ func TestEnsureMigrationTableExistsCommitError(t *testing.T) {
134103
DELETE FROM t0 WHERE id = 1;`,
135104
}
136105

137-
dbFile, dbFileErr := prepareDB()
138-
139-
if dbFileErr != nil {
140-
t.Errorf("DB file could not be created: %v", dbFileErr)
141-
} else {
142-
defer func() { _ = os.Remove(dbFile) }()
143-
}
144-
145-
db, dbErr := sql.Open("sqlite", dbFile)
146-
147-
if dbErr != nil {
148-
t.Errorf("DB file could not be created: %v", dbErr)
149-
} else {
150-
defer func() { _ = db.Close() }()
151-
}
106+
db := openTempSQLite(t)
152107

153-
_, execErr := db.Exec("PRAGMA foreign_keys = ON")
108+
_, execErr := db.ExecContext(t.Context(), "PRAGMA foreign_keys = ON")
154109

155-
assert.NoError(t, execErr, "foreign keys checking could not be enabled")
110+
require.NoError(t, execErr, "foreign keys checking could not be enabled")
156111

157-
assert.Error(t, d.EnsureMigrationTableExists(db, "test"), "expected error")
112+
assert.Error(t, dialect.EnsureMigrationTableExists(t.Context(), db, "test"), "expected error")
158113
}

exports_internal_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dmorph
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
)
7+
8+
// The exported names in this file are only used for internal testing and are not part of the public API.
9+
10+
var (
11+
TapplyStepsStream = applyStepsStream
12+
TmigrationFromFileFS = migrationFromFileFS
13+
TmigrationOrder = migrationOrder
14+
)
15+
16+
func (m *Morpher) TapplyMigrations(ctx context.Context, db *sql.DB, lastMigration string) error {
17+
return m.applyMigrations(ctx, db, lastMigration)
18+
}

0 commit comments

Comments
 (0)