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

Unit tests for db package #409

Merged
merged 4 commits into from
Jan 4, 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
297 changes: 264 additions & 33 deletions db/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"sync"
"testing"
Expand All @@ -14,7 +13,6 @@ import (
"github.com/google/uuid"
"github.com/tinkerbell/tink/client/informers"
"github.com/tinkerbell/tink/db"
"github.com/tinkerbell/tink/pkg"
"github.com/tinkerbell/tink/protos/events"
"github.com/tinkerbell/tink/protos/hardware"
)
Expand All @@ -30,10 +28,6 @@ func TestCreateEventsForHardware(t *testing.T) {
// Expectation is the function used to apply the assertions.
// You can use it to validate if the Input are created as you expect
Expectation func(*testing.T, []*hardware.Hardware, *db.TinkDB)
// ExpectedErr is used to check for error during
// createHardware execution. If you expect a particular error
// and you want to assert it, you can use this function
ExpectedErr func(*testing.T, error)
}{
{
Name: "single-hardware-create-event",
Expand Down Expand Up @@ -77,10 +71,220 @@ func TestCreateEventsForHardware(t *testing.T) {
}
return input
}(),
ExpectedErr: func(t *testing.T, err error) {
Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) {
count := 0
err := tinkDB.Events(&events.WatchRequest{}, func(n informers.Notification) error {
event, err := n.ToEvent()
if err != nil {
return err
}

if event.EventType != events.EventType_EVENT_TYPE_CREATED {
return fmt.Errorf("unexpected event type: %s", event.EventType)
}
count = count + 1
return nil
})
if err != nil {
t.Error(err)
}
if len(input) != count {
t.Errorf("expected %d events stored in the database but we got %d", len(input), count)
}
},
},
}

ctx := context.Background()
for _, s := range tests {
t.Run(s.Name, func(t *testing.T) {
t.Parallel()
_, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{
ApplyMigration: true,
})
defer func() {
err := cl()
if err != nil {
t.Error(err)
}
}()
var wg sync.WaitGroup
wg.Add(len(s.Input))
for _, hw := range s.Input {
if s.InputAsync {
go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) {
defer wg.Done()
err := createHardware(ctx, tinkDB, hw)
if err != nil {
t.Error(err)
}
}(ctx, tinkDB, hw)
} else {
wg.Done()
err := createHardware(ctx, tinkDB, hw)
if err != nil {
t.Error(err)
}
}
}
wg.Wait()
s.Expectation(t, s.Input, tinkDB)
})
}
}

func TestUpdateEventsForHardware(t *testing.T) {
tests := []struct {
// Name identifies the single test in a table test scenario
Name string
// Input is a list of hardwares that will be used to pre-populate the database
Input []*hardware.Hardware
// InputAsync if set to true inserts all the input concurrently
InputAsync bool
// Expectation is the function used to apply the assertions.
// You can use it to validate if the Input are created as you expect
Expectation func(*testing.T, []*hardware.Hardware, *db.TinkDB)
}{
{
Name: "update-single-hardware",
Input: []*hardware.Hardware{
readHardwareData("./testdata/hardware.json"),
},
Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) {
err := tinkDB.Events(
&events.WatchRequest{
EventTypes: []events.EventType{events.EventType_EVENT_TYPE_UPDATED},
},
func(n informers.Notification) error {
event, err := n.ToEvent()
if err != nil {
return err
}

if event.EventType != events.EventType_EVENT_TYPE_UPDATED {
return fmt.Errorf("unexpected event type: %s", event.EventType)
}

hw, err := getHardwareFromEventData(event)
if err != nil {
return err
}
if dif := cmp.Diff(input[0], hw, cmp.Comparer(hardwareComparer)); dif != "" {
t.Errorf(dif)
}
return nil
})
if err != nil {
t.Error(err)
}
},
},
{
Name: "update-stress-test",
InputAsync: true,
Input: func() []*hardware.Hardware {
input := []*hardware.Hardware{}
for ii := 0; ii < 10; ii++ {
hw := readHardwareData("./testdata/hardware.json")
hw.Id = uuid.New().String()
hw.Network.Interfaces[0].Dhcp.Mac = strings.Replace(hw.Network.Interfaces[0].Dhcp.Mac, "00", fmt.Sprintf("0%d", ii), 1)
}
return input
}(),
Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) {
count := 0
err := tinkDB.Events(
&events.WatchRequest{
EventTypes: []events.EventType{events.EventType_EVENT_TYPE_UPDATED},
},
func(n informers.Notification) error {
event, err := n.ToEvent()
if err != nil {
return err
}

if event.EventType != events.EventType_EVENT_TYPE_UPDATED {
return fmt.Errorf("unexpected event type: %s", event.EventType)
}
count = count + 1
return nil
})
if err != nil {
t.Error(err)
}
if len(input) != count {
t.Errorf("expected %d events stored in the database but we got %d", len(input), count)
}
},
},
}

ctx := context.Background()
for _, s := range tests {
t.Run(s.Name, func(t *testing.T) {
t.Parallel()
_, tinkDB, cl := NewPostgresDatabaseClient(t, ctx, NewPostgresDatabaseRequest{
ApplyMigration: true,
})
defer func() {
err := cl()
if err != nil {
t.Error(err)
}
}()

for _, hw := range s.Input {
go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) {
err := createHardware(ctx, tinkDB, hw)
if err != nil {
t.Error(err)
}
}(ctx, tinkDB, hw)
}

var wg sync.WaitGroup
wg.Add(len(s.Input))
for _, hw := range s.Input {
if s.InputAsync {
go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) {
defer wg.Done()
hw.Id = uuid.New().String()
err := createHardware(ctx, tinkDB, hw)
if err != nil {
t.Error(err)
}
}(ctx, tinkDB, hw)
} else {
wg.Done()
hw.Id = uuid.New().String()
err := createHardware(ctx, tinkDB, hw)
if err != nil {
t.Error(err)
}
}
}
wg.Wait()
s.Expectation(t, s.Input, tinkDB)
})
}
}

func TestDeleteEventsForHardware(t *testing.T) {
tests := []struct {
// Name identifies the single test in a table test scenario
Name string
// Input is a list of hardwares that will be used to pre-populate the database
Input []*hardware.Hardware
// InputAsync if set to true inserts all the input concurrently
InputAsync bool
// Expectation is the function used to apply the assertions.
// You can use it to validate if the Input are created as you expect
Expectation func(*testing.T, []*hardware.Hardware, *db.TinkDB)
}{
{
Name: "delete-single-hardware",
Input: []*hardware.Hardware{
readHardwareData("./testdata/hardware.json"),
},
Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) {
err := tinkDB.Events(&events.WatchRequest{}, func(n informers.Notification) error {
Expand All @@ -89,7 +293,7 @@ func TestCreateEventsForHardware(t *testing.T) {
return err
}

if event.EventType != events.EventType_EVENT_TYPE_CREATED {
if event.EventType != events.EventType_EVENT_TYPE_DELETED {
return fmt.Errorf("unexpected event type: %s", event.EventType)
}

Expand All @@ -107,6 +311,44 @@ func TestCreateEventsForHardware(t *testing.T) {
}
},
},
{
Name: "delete-stress-test",
InputAsync: true,
Input: func() []*hardware.Hardware {
input := []*hardware.Hardware{}
for ii := 0; ii < 10; ii++ {
hw := readHardwareData("./testdata/hardware.json")
hw.Id = uuid.New().String()
hw.Network.Interfaces[0].Dhcp.Mac = strings.Replace(hw.Network.Interfaces[0].Dhcp.Mac, "00", fmt.Sprintf("0%d", ii), 1)
}
return input
}(),
Expectation: func(t *testing.T, input []*hardware.Hardware, tinkDB *db.TinkDB) {
count := 0
err := tinkDB.Events(
&events.WatchRequest{
EventTypes: []events.EventType{events.EventType_EVENT_TYPE_DELETED},
},
func(n informers.Notification) error {
event, err := n.ToEvent()
if err != nil {
return err
}

if event.EventType != events.EventType_EVENT_TYPE_DELETED {
return fmt.Errorf("unexpected event type: %s", event.EventType)
}
count = count + 1
return nil
})
if err != nil {
t.Error(err)
}
if len(input) != count {
t.Errorf("expected %d events stored in the database but we got %d", len(input), count)
}
},
},
}

ctx := context.Background()
Expand All @@ -122,22 +364,32 @@ func TestCreateEventsForHardware(t *testing.T) {
t.Error(err)
}
}()

for _, hw := range s.Input {
go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) {
err := createHardware(ctx, tinkDB, hw)
if err != nil {
t.Error(err)
}
}(ctx, tinkDB, hw)
}

var wg sync.WaitGroup
wg.Add(len(s.Input))
for _, hw := range s.Input {
if s.InputAsync {
go func(ctx context.Context, tinkDB *db.TinkDB, hw *hardware.Hardware) {
defer wg.Done()
err := createHardware(ctx, tinkDB, hw)
err := tinkDB.DeleteFromDB(ctx, hw.Id)
if err != nil {
s.ExpectedErr(t, err)
t.Error(err)
}
}(ctx, tinkDB, hw)
} else {
wg.Done()
err := createHardware(ctx, tinkDB, hw)
err := tinkDB.DeleteFromDB(ctx, hw.Id)
if err != nil {
s.ExpectedErr(t, err)
t.Error(err)
}
}
}
Expand All @@ -147,27 +399,6 @@ func TestCreateEventsForHardware(t *testing.T) {
}
}

func readHardwareData(file string) *hardware.Hardware {
data, err := ioutil.ReadFile(file)
if err != nil {
panic(err)
}
var hw pkg.HardwareWrapper
err = json.Unmarshal([]byte(data), &hw)
if err != nil {
panic(err)
}
return hw.Hardware
}

func createHardware(ctx context.Context, db *db.TinkDB, hw *hardware.Hardware) error {
data, err := json.Marshal(hw)
if err != nil {
return err
}
return db.InsertIntoDB(ctx, string(data))
}

func getHardwareFromEventData(event *events.Event) (*hardware.Hardware, error) {
d, err := base64.StdEncoding.DecodeString(strings.Trim(string(event.Data), "\""))
if err != nil {
Expand Down
Loading