Skip to content

Commit

Permalink
Upgrade steps to fix "autogenerated" refs in output and simplify inte…
Browse files Browse the repository at this point in the history
…rnal API (#7)

* Upgrade steps to fix "autogenerated" refs in output and simplify internal API

* Fix concurrency test
  • Loading branch information
vearutop authored Nov 8, 2024
1 parent 2ca10d5 commit 04126cb
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 85 deletions.
6 changes: 3 additions & 3 deletions _testdata/DatabaseConcurrentBlocked.feature
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
Feature: No locking for different tables

Scenario: Table 1
Given I sleep
Given I should not be blocked for "db1::t1"
Given there are no rows in table "t1" of database "db1"
Then I sleep
And I sleep
But I sleep

Scenario: Table 1 again
Given I sleep
Given I should not be blocked for "db1::t1"
Given I should be blocked for "db1::t1"
Given there are no rows in table "t1" of database "db1"
And I sleep

Scenario: Table 3
Given I sleep
Expand Down
117 changes: 51 additions & 66 deletions manager.go → dbsteps.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,80 +146,86 @@ func (m *Manager) RegisterSteps(s *godog.ScenarioContext) {
}

func (m *Manager) registerPrerequisites(s *godog.ScenarioContext) {
s.Step(`^there are no rows in table "([^"]*)" of database "([^"]*)"$`,
m.noRowsInTableOfDatabase)
s.Given(`^there are no rows in table "([^"]*)" of database "([^"]*)"$`,
func(ctx context.Context, tableName, dbName string) (context.Context, error) {
return m.givenNoRowsInTableOfDatabase(ctx, tableName, dbName)
})

s.Step(`^there are no rows in table "([^"]*)"$`,
s.Given(`^there are no rows in table "([^"]*)"$`,
func(ctx context.Context, tableName string) (context.Context, error) {
return m.noRowsInTableOfDatabase(ctx, tableName, Default)
return m.givenNoRowsInTableOfDatabase(ctx, tableName, Default)
})

s.Step(`^these rows are stored in table "([^"]*)" of database "([^"]*)"[:]?$`,
s.Given(`^these rows are stored in table "([^"]*)" of database "([^"]*)"[:]?$`,
func(ctx context.Context, tableName, database string, data *godog.Table) (context.Context, error) {
return m.theseRowsAreStoredInTableOfDatabase(ctx, tableName, database, Rows(data))
return m.givenTheseRowsAreStoredInTableOfDatabase(ctx, tableName, database, Rows(data))
})

s.Step(`^rows from this file are stored in table "([^"]*)" of database "([^"]*)"[:]?$`,
s.Given(`^rows from this file are stored in table "([^"]*)" of database "([^"]*)"[:]?$`,
func(ctx context.Context, tableName, database string, filePath string) (context.Context, error) {
return m.rowsFromThisFileAreStoredInTableOfDatabase(ctx, tableName, database, filePath)
return m.givenRowsFromThisFileAreStoredInTableOfDatabase(ctx, tableName, database, filePath)
})

s.Step(`^these rows are stored in table "([^"]*)"[:]?$`,
s.Given(`^these rows are stored in table "([^"]*)"[:]?$`,
func(ctx context.Context, tableName string, data *godog.Table) (context.Context, error) {
return m.theseRowsAreStoredInTableOfDatabase(ctx, tableName, Default, Rows(data))
return m.givenTheseRowsAreStoredInTableOfDatabase(ctx, tableName, Default, Rows(data))
})

s.Step(`^rows from this file are stored in table "([^"]*)"[:]?$`,
s.Given(`^rows from this file are stored in table "([^"]*)"[:]?$`,
func(ctx context.Context, tableName string, filePath string) (context.Context, error) {
return m.rowsFromThisFileAreStoredInTableOfDatabase(ctx, tableName, Default, filePath)
return m.givenRowsFromThisFileAreStoredInTableOfDatabase(ctx, tableName, Default, filePath)
})
}

func (m *Manager) registerAssertions(s *godog.ScenarioContext) {
s.Step(`^only rows from this file are available in table "([^"]*)" of database "([^"]*)"[:]?$`,
s.Then(`^only rows from this file are available in table "([^"]*)" of database "([^"]*)"[:]?$`,
func(ctx context.Context, tableName, database string, filePath string) (context.Context, error) {
return m.onlyRowsFromThisFileAreAvailableInTableOfDatabase(ctx, tableName, database, filePath)
return m.assertRowsFromFile(ctx, tableName, database, filePath, true)
})

s.Step(`^only these rows are available in table "([^"]*)" of database "([^"]*)"[:]?$`,
s.Then(`^only these rows are available in table "([^"]*)" of database "([^"]*)"[:]?$`,
func(ctx context.Context, tableName, database string, data *godog.Table) (context.Context, error) {
return m.onlyTheseRowsAreAvailableInTableOfDatabase(ctx, tableName, database, Rows(data))
return m.assertRows(ctx, tableName, database, Rows(data), true)
})

s.Step(`^only rows from this file are available in table "([^"]*)"[:]?$`,
s.Then(`^only rows from this file are available in table "([^"]*)"[:]?$`,
func(ctx context.Context, tableName string, filePath string) (context.Context, error) {
return m.onlyRowsFromThisFileAreAvailableInTableOfDatabase(ctx, tableName, Default, filePath)
return m.assertRowsFromFile(ctx, tableName, Default, filePath, true)
})

s.Step(`^only these rows are available in table "([^"]*)"[:]?$`,
s.Then(`^only these rows are available in table "([^"]*)"[:]?$`,
func(ctx context.Context, tableName string, data *godog.Table) (context.Context, error) {
return m.onlyTheseRowsAreAvailableInTableOfDatabase(ctx, tableName, Default, Rows(data))
return m.assertRows(ctx, tableName, Default, Rows(data), true)
})

s.Step(`^no rows are available in table "([^"]*)" of database "([^"]*)"$`,
m.noRowsAreAvailableInTableOfDatabase)
s.Then(`^no rows are available in table "([^"]*)" of database "([^"]*)"$`,
func(ctx context.Context, tableName, dbName string) (context.Context, error) {
return m.assertRows(ctx, tableName, dbName, nil, true)
})

s.Step(`^no rows are available in table "([^"]*)"$`,
s.Then(`^no rows are available in table "([^"]*)"$`,
func(ctx context.Context, tableName string) (context.Context, error) {
return m.noRowsAreAvailableInTableOfDatabase(ctx, tableName, Default)
return m.assertRows(ctx, tableName, Default, nil, true)
})

s.Step(`^rows from this file are available in table "([^"]*)" of database "([^"]*)"[:]?$`,
m.rowsFromThisFileAreAvailableInTableOfDatabase)
s.Then(`^rows from this file are available in table "([^"]*)" of database "([^"]*)"[:]?$`,
func(ctx context.Context, tableName, dbName string, filePath string) (context.Context, error) {
return m.assertRowsFromFile(ctx, tableName, dbName, filePath, false)
})

s.Step(`^these rows are available in table "([^"]*)" of database "([^"]*)"[:]?$`,
func(ctx context.Context, tableName, database string, data *godog.Table) (context.Context, error) {
return m.theseRowsAreAvailableInTableOfDatabase(ctx, tableName, database, Rows(data))
return m.assertRows(ctx, tableName, database, Rows(data), false)
})

s.Step(`^rows from this file are available in table "([^"]*)"[:]?$`,
s.Then(`^rows from this file are available in table "([^"]*)"[:]?$`,
func(ctx context.Context, tableName string, filePath string) (context.Context, error) {
return m.rowsFromThisFileAreAvailableInTableOfDatabase(ctx, tableName, Default, filePath)
return m.assertRowsFromFile(ctx, tableName, Default, filePath, false)
})

s.Step(`^these rows are available in table "([^"]*)"[:]?$`,
s.Then(`^these rows are available in table "([^"]*)"[:]?$`,
func(ctx context.Context, tableName string, data *godog.Table) (context.Context, error) {
return m.theseRowsAreAvailableInTableOfDatabase(ctx, tableName, Default, Rows(data))
return m.assertRows(ctx, tableName, Default, Rows(data), false)
})
}

Expand Down Expand Up @@ -305,7 +311,7 @@ func (m *Manager) instance(ctx context.Context, tableName, dbName string) (Insta
return instance, row, ctx, nil
}

func (m *Manager) noRowsInTableOfDatabase(ctx context.Context, tableName, dbName string) (context.Context, error) {
func (m *Manager) givenNoRowsInTableOfDatabase(ctx context.Context, tableName, dbName string) (context.Context, error) {
instance, _, ctx, err := m.instance(ctx, tableName, dbName)
if err != nil {
return ctx, err
Expand Down Expand Up @@ -382,16 +388,16 @@ func Rows(data *godog.Table) [][]string {
return d
}

func (m *Manager) rowsFromThisFileAreStoredInTableOfDatabase(ctx context.Context, tableName, dbName string, filePath string) (context.Context, error) {
func (m *Manager) givenRowsFromThisFileAreStoredInTableOfDatabase(ctx context.Context, tableName, dbName string, filePath string) (context.Context, error) {
data, err := loadTableFromFile(filePath)
if err != nil {
return ctx, fmt.Errorf("failed to load rows from file: %w", err)
}

return m.theseRowsAreStoredInTableOfDatabase(ctx, tableName, dbName, data)
return m.givenTheseRowsAreStoredInTableOfDatabase(ctx, tableName, dbName, data)
}

func (m *Manager) theseRowsAreStoredInTableOfDatabase(ctx context.Context, tableName, dbName string, data [][]string) (context.Context, error) {
func (m *Manager) givenTheseRowsAreStoredInTableOfDatabase(ctx context.Context, tableName, dbName string, data [][]string) (context.Context, error) {
instance, row, ctx, err := m.instance(ctx, tableName, dbName)
if err != nil {
return ctx, err
Expand Down Expand Up @@ -422,36 +428,6 @@ func (m *Manager) theseRowsAreStoredInTableOfDatabase(ctx context.Context, table
return ctx, err
}

func (m *Manager) onlyRowsFromThisFileAreAvailableInTableOfDatabase(ctx context.Context, tableName, dbName string, filePath string) (context.Context, error) {
data, err := loadTableFromFile(filePath)
if err != nil {
return ctx, fmt.Errorf("failed to load rows from file: %w", err)
}

return m.assertRows(ctx, tableName, dbName, data, true)
}

func (m *Manager) onlyTheseRowsAreAvailableInTableOfDatabase(ctx context.Context, tableName, dbName string, data [][]string) (context.Context, error) {
return m.assertRows(ctx, tableName, dbName, data, true)
}

func (m *Manager) noRowsAreAvailableInTableOfDatabase(ctx context.Context, tableName, dbName string) (context.Context, error) {
return m.assertRows(ctx, tableName, dbName, nil, true)
}

func (m *Manager) rowsFromThisFileAreAvailableInTableOfDatabase(ctx context.Context, tableName, dbName string, filePath string) (context.Context, error) {
data, err := loadTableFromFile(filePath)
if err != nil {
return ctx, fmt.Errorf("failed to load rows from file: %w", err)
}

return m.assertRows(ctx, tableName, dbName, data, false)
}

func (m *Manager) theseRowsAreAvailableInTableOfDatabase(ctx context.Context, tableName, dbName string, data [][]string) (context.Context, error) {
return m.assertRows(ctx, tableName, dbName, data, false)
}

type testingT struct {
Err error
}
Expand Down Expand Up @@ -599,7 +575,7 @@ func combine(keys []string, vals []interface{}) map[string]interface{} {
func (t *tableQuery) skipDecode(column, value string) bool {
// Databases do not provide JSON equality conditions in general,
// so if value looks like a non-scalar JSON it is removed from WHERE condition and checked for equality
// using Go values during post processing.
// using Go values during post-processing.
if len(value) > 0 && (value[0] == '{' || value[0] == '[') && json.Valid([]byte(value)) {
t.postCheck = append(t.postCheck, column)
t.skipWhereCols = append(t.skipWhereCols, column)
Expand Down Expand Up @@ -701,6 +677,15 @@ func (m *Manager) assertRows(ctx context.Context, tableName, dbName string, data
return ctx, err
}

func (m *Manager) assertRowsFromFile(ctx context.Context, tableName, dbName string, filePath string, exhaustiveList bool) (context.Context, error) {
data, err := loadTableFromFile(filePath)
if err != nil {
return ctx, fmt.Errorf("failed to load rows from file: %w", err)
}

return m.assertRows(ctx, tableName, dbName, data, exhaustiveList)
}

func (t *tableQuery) doPostCheck(colNames []string, postCheck []string, argsExp, argsRcv map[string]interface{}, rawValues []string) error {
for i, name := range colNames {
if t.vars.IsVar(rawValues[i]) {
Expand Down
File renamed without changes.
27 changes: 11 additions & 16 deletions manager_concurrency_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package dbsteps //nolint:testpackage

import (
"bytes"
"context"
"database/sql/driver"
"fmt"
Expand Down Expand Up @@ -59,7 +58,7 @@ func TestNewManager_concurrent(t *testing.T) {
return nil
})
s.Step("^I sleep$", func() {
time.Sleep(time.Millisecond * time.Duration(rand.Int63n(100)))
time.Sleep(time.Millisecond * time.Duration(100+rand.Int63n(100)))
})
},
Options: &godog.Options{
Expand All @@ -80,14 +79,12 @@ func TestNewManager_concurrent_blocked(t *testing.T) {

db1, mock1, err := sqlmock.New()
assert.NoError(t, err)
db2, mock2, err := sqlmock.New()
assert.NoError(t, err)
db3, mock3, err := sqlmock.New()
assert.NoError(t, err)

mock1.ExpectExec(`DELETE FROM t1`).
WillReturnResult(driver.ResultNoRows)
mock2.ExpectExec(`DELETE FROM t2`).
mock1.ExpectExec(`DELETE FROM t1`).
WillReturnResult(driver.ResultNoRows)
mock3.ExpectExec(`DELETE FROM t3`).
WillReturnResult(driver.ResultNoRows)
Expand All @@ -97,18 +94,12 @@ func TestNewManager_concurrent_blocked(t *testing.T) {
Storage: sqluct.NewStorage(sqlx.NewDb(db1, "sqlmock")),
Tables: map[string]interface{}{"t1": nil},
},
"db2": {
Storage: sqluct.NewStorage(sqlx.NewDb(db2, "sqlmock")),
Tables: map[string]interface{}{"t2": nil},
},
"db3": {
Storage: sqluct.NewStorage(sqlx.NewDb(db3, "sqlmock")),
Tables: map[string]interface{}{"t3": nil},
},
}

out := bytes.Buffer{}

suite := godog.TestSuite{
ScenarioInitializer: func(s *godog.ScenarioContext) {
dbm.RegisterSteps(s)
Expand All @@ -119,22 +110,26 @@ func TestNewManager_concurrent_blocked(t *testing.T) {

return nil
})
s.Step(`^I should be blocked for "([^"]*)"$`, func(ctx context.Context, key string) error {
if !dbm.lock.IsLocked(ctx, key) {
return fmt.Errorf("%s is not locked", key)
}

return nil
})
s.Step("^I sleep$", func() {
time.Sleep(time.Millisecond * time.Duration(rand.Int63n(100)))
time.Sleep(time.Millisecond * time.Duration(100+rand.Int63n(100)))
})
},
Options: &godog.Options{
Output: &out,
Format: "pretty",
Strict: true,
Paths: []string{"_testdata/DatabaseConcurrentBlocked.feature"},
Concurrency: 10,
},
}

if suite.Run() != 1 {
if suite.Run() != 0 {
t.Fatal("test failed")
}

assert.Contains(t, out.String(), "db1::t1 is locked")
}

0 comments on commit 04126cb

Please sign in to comment.