From 87eb0b89df82239adde2ce60a4a2ee4dec94fabb Mon Sep 17 00:00:00 2001
From: Garet Halliday <me@garet.holiday>
Date: Thu, 14 Sep 2023 14:45:42 -0500
Subject: [PATCH] stress testing

---
 test/config.go         |  3 +++
 test/runner.go         | 38 +++++++++++++++++++++++++++++++++++++-
 test/test.go           |  4 ++++
 test/tester_test.go    |  2 ++
 test/tests/copy_out.go |  6 ++++--
 5 files changed, 50 insertions(+), 3 deletions(-)

diff --git a/test/config.go b/test/config.go
index 589910f8..42668081 100644
--- a/test/config.go
+++ b/test/config.go
@@ -5,5 +5,8 @@ import (
 )
 
 type Config struct {
+	// Stress is how many connections to run simultaneously for stress testing. <= 1 disables stress testing.
+	Stress int
+
 	Modes map[string]dialer.Dialer
 }
diff --git a/test/runner.go b/test/runner.go
index bbedd974..5f985f1f 100644
--- a/test/runner.go
+++ b/test/runner.go
@@ -10,6 +10,7 @@ import (
 	packets "pggat/lib/fed/packets/v3.0"
 	"pggat/lib/gat/pool/dialer"
 	"pggat/lib/gsql"
+	"pggat/lib/util/flip"
 	"pggat/test/inst"
 )
 
@@ -82,7 +83,7 @@ func (T *Runner) prepare(client *gsql.Client) []Capturer {
 	return results
 }
 
-func (T *Runner) runMode(dialer dialer.Dialer) ([]Capturer, error) {
+func (T *Runner) runModeOnce(dialer dialer.Dialer) ([]Capturer, error) {
 	server, _, err := dialer.Dial()
 	if err != nil {
 		return nil, err
@@ -119,6 +120,41 @@ func (T *Runner) runMode(dialer dialer.Dialer) ([]Capturer, error) {
 	return results, nil
 }
 
+func (T *Runner) runMode(dialer dialer.Dialer) ([]Capturer, error) {
+	instances := T.config.Stress
+	if instances < 1 || T.test.SideEffects {
+		instances = 1
+	}
+
+	expected, err := T.runModeOnce(dialer)
+	if err != nil {
+		return nil, err
+	}
+
+	var b flip.Bank
+
+	for i := 0; i < instances-1; i++ {
+		b.Queue(func() error {
+			actual, err := T.runModeOnce(dialer)
+			if err != nil {
+				return err
+			}
+			if len(expected) != len(actual) {
+				return fmt.Errorf("wrong number of results! expected %d but got %d", len(expected), len(actual))
+			}
+			for i, exp := range expected {
+				act := actual[i]
+				if err = exp.Check(&act); err != nil {
+					return err
+				}
+			}
+			return nil
+		})
+	}
+
+	return expected, b.Wait()
+}
+
 func (T *Runner) Run() error {
 	var errs []error
 
diff --git a/test/test.go b/test/test.go
index 000fa8ff..2a6d1f9a 100644
--- a/test/test.go
+++ b/test/test.go
@@ -3,6 +3,10 @@ package test
 import "pggat/test/inst"
 
 type Test struct {
+	// SideEffects determines whether this test has side effects such as creating or dropping tables.
+	// This will prevent fail and stress testing, as those would immediately fail.
+	SideEffects bool
+
 	Name         string
 	Instructions []inst.Instruction
 }
diff --git a/test/tester_test.go b/test/tester_test.go
index 9ba07394..4b1fa6f2 100644
--- a/test/tester_test.go
+++ b/test/tester_test.go
@@ -141,6 +141,8 @@ func TestTester(t *testing.T) {
 	}
 
 	tester := test.NewTester(test.Config{
+		Stress: 16,
+
 		Modes: map[string]dialer.Dialer{
 			"control":     control,
 			"transaction": transactionDialer,
diff --git a/test/tests/copy_out.go b/test/tests/copy_out.go
index 681e0033..d1d314e8 100644
--- a/test/tests/copy_out.go
+++ b/test/tests/copy_out.go
@@ -6,7 +6,8 @@ import (
 )
 
 var CopyOut0 = test.Test{
-	Name: "Copy Out 0",
+	SideEffects: true,
+	Name:        "Copy Out 0",
 	Instructions: []inst.Instruction{
 		inst.SimpleQuery("CREATE TABLE test ( x integer NOT NULL, y varchar(40) NOT NULL PRIMARY KEY )"),
 		inst.SimpleQuery("INSERT INTO test VALUES (123, 'hello world')"),
@@ -17,7 +18,8 @@ var CopyOut0 = test.Test{
 }
 
 var CopyOut1 = test.Test{
-	Name: "Copy Out 1",
+	SideEffects: true,
+	Name:        "Copy Out 1",
 	Instructions: []inst.Instruction{
 		inst.SimpleQuery("CREATE TABLE test ( x integer NOT NULL, y varchar(40) NOT NULL PRIMARY KEY )"),
 		inst.SimpleQuery("INSERT INTO test VALUES (123, 'hello world')"),
-- 
GitLab