shared

package
v0.0.0-...-3715b1e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2022 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package shared implements utility functions for acceptance testing Hydra as well as shared test cases.

Index

Constants

This section is empty.

Variables

View Source
var (
	BeforeUpgradeCases = []Case{
		{
			Name: "create columnar table",
			SQL: `
CREATE TABLE columnar_table
(
    id UUID,
    i1 INT,
    i2 INT8,
    n NUMERIC,
    t TEXT
) USING columnar;
		`,
		},
		{
			Name: "insert into columnar table",
			SQL: `
INSERT INTO columnar_table (id, i1, i2, n, t)
VALUES ('75372aac-d74a-4e5a-8bf3-43cdaf9011de', 2, 3, 100.1, 'hydra');
		`,
		},
	}
	AfterUpgradeCases = []Case{
		{
			Name: "create another columnar table",
			SQL: `
CREATE TABLE columnar_table2
(
    id UUID,
    i1 INT,
    i2 INT8,
    n NUMERIC,
    t TEXT
) USING columnar;
		`,
		},
		{
			Name: "validate columnar data",
			SQL:  "SELECT id, i1, i2, n, t FROM columnar_table LIMIT 1;",
			Validate: func(t *testing.T, row pgx.Row) {
				var result struct {
					ID uuid.UUID
					I1 int
					I2 int
					N  float32
					T  string
				}

				if err := row.Scan(&result.ID, &result.I1, &result.I2, &result.N, &result.T); err != nil {
					t.Fatal(err)
				}

				if result.ID != uuid.MustParse("75372aac-d74a-4e5a-8bf3-43cdaf9011de") {
					t.Errorf("id returned %s after upgrade, expected 75372aac-d74a-4e5a-8bf3-43cdaf9011de", result.ID)
				}

				if result.I1 != 2 {
					t.Errorf("i1 returned %d after upgrade, expected 2", result.I1)
				}

				if result.I2 != 3 {
					t.Errorf("i2 returned %d after upgrade, expected 3", result.I2)
				}

				if result.N != 100.1 {
					t.Errorf("n returned %f after upgrade, expected 100.1", result.N)
				}

				if result.T != "hydra" {
					t.Errorf("t returned %s after upgrade, expected hydra", result.T)
				}
			},
		},
		{
			Name: "http ext available",
			SQL: `
SELECT count(1) FROM pg_available_extensions WHERE name = 'http';
			`,
			Validate: func(t *testing.T, row pgx.Row) {
				var count int
				if err := row.Scan(&count); err != nil {
					t.Fatal(err)
				}

				if want, got := 1, count; want != got {
					t.Errorf("columnar ext should exist")
				}
			},
		},
		{
			Name: "http ext enabled",
			SQL: `
SELECT count(1) FROM pg_extension WHERE extname = 'http';
			`,
			Validate: func(t *testing.T, row pgx.Row) {
				var count int
				if err := row.Scan(&count); err != nil {
					t.Fatal(err)
				}

				if want, got := 1, count; want != got {
					t.Errorf("columnar ext should exist")
				}
			},
		},
		{
			Name: "http put",
			SQL: `
SELECT status, content_type, content::json->>'data' AS data
  FROM http_put('http://httpbin.org/put', 'some text', 'text/plain');
			`,
			Validate: func(t *testing.T, row pgx.Row) {
				var result struct {
					Status      int
					ContentType string
					Data        string
				}

				if err := row.Scan(&result.Status, &result.ContentType, &result.Data); err != nil {
					t.Fatal(err)
				}

				if want, got := 200, result.Status; want != got {
					t.Errorf("http put response status should match: want=%d got=%d", want, got)
				}
				if want, got := "application/json", result.ContentType; want != got {
					t.Errorf("http put response content type should match: want=%s got=%s", want, got)
				}
				if want, got := "some text", result.Data; want != got {
					t.Errorf("http put response data should match: want=%s got=%s", want, got)
				}
			},
		},
	}
)

These describe the shared setup and validation cases that occur to validate the upgrade between two version of a Hydra-derived image.

View Source
var AcceptanceCases = []Case{
	{
		Name: "columnar ext available",
		SQL: `
SELECT count(1) FROM pg_available_extensions WHERE name = 'columnar';
			`,
		Validate: func(t *testing.T, row pgx.Row) {
			var count int
			if err := row.Scan(&count); err != nil {
				t.Fatal(err)
			}

			if want, got := 1, count; want != got {
				t.Error("columnar ext should exist")
			}
		},
	},
	{
		Name: "columnar ext enabled",
		SQL: `
SELECT count(1) FROM pg_extension WHERE extname = 'columnar';
			`,
		Validate: func(t *testing.T, row pgx.Row) {
			var count int
			if err := row.Scan(&count); err != nil {
				t.Fatal(err)
			}

			if want, got := 1, count; want != got {
				t.Error("columnar ext should exist")
			}
		},
	},
	{
		Name: "using a columnar table",
		SQL: `
CREATE TABLE my_columnar_table
(
    id INT,
    i1 INT,
    i2 INT8,
    n NUMERIC,
    t TEXT
) USING columnar;
			`,
	},
	{
		Name: "convert between row and columnar",
		SQL: `
		CREATE TABLE my_table(i INT8 DEFAULT '7');
		INSERT INTO my_table VALUES(1);
		-- convert to columnar
		SELECT columnar.alter_table_set_access_method('my_table', 'columnar');
		-- back to row
		-- TODO: reenable this after it's supported
		-- SELECT alter_table_set_access_method('my_table', 'heap');
		`,
	},
	{
		Name: "convert by copying",
		SQL: `
CREATE TABLE table_heap (i INT8);
CREATE TABLE table_columnar (LIKE table_heap) USING columnar;
INSERT INTO table_columnar SELECT * FROM table_heap;
			`,
	},
	{
		Name: "partition",
		SQL: `
CREATE TABLE parent(ts timestamptz, i int, n numeric, s text)
  PARTITION BY RANGE (ts);

-- columnar partition
CREATE TABLE p0 PARTITION OF parent
  FOR VALUES FROM ('2020-01-01') TO ('2020-02-01')
  USING COLUMNAR;
-- columnar partition
CREATE TABLE p1 PARTITION OF parent
  FOR VALUES FROM ('2020-02-01') TO ('2020-03-01')
  USING COLUMNAR;
-- row partition
CREATE TABLE p2 PARTITION OF parent
  FOR VALUES FROM ('2020-03-01') TO ('2020-04-01');

INSERT INTO parent VALUES ('2020-01-15', 10, 100, 'one thousand'); -- columnar
INSERT INTO parent VALUES ('2020-02-15', 20, 200, 'two thousand'); -- columnar
INSERT INTO parent VALUES ('2020-03-15', 30, 300, 'three thousand'); -- row

CREATE INDEX p2_ts_idx ON p2 (ts);
CREATE UNIQUE INDEX p2_i_unique ON p2 (i);
ALTER TABLE p2 ADD UNIQUE (n);
			`,
	},
	{
		Name: "options",
		SQL: `
SELECT columnar.alter_columnar_table_set(
    'my_columnar_table',
    compression => 'none',
    stripe_row_limit => 10000);
			`,
	},
}

AcceptanceCases describe the shared acceptance criteria for any Hydra-based images.

View Source
var ErrPgPoolConnect = errors.New("pgxpool did not connect")

ErrPgPoolConnect is used when pgxpool cannot connect to a database.

Functions

func CreatePGPool

func CreatePGPool(t *testing.T, ctx context.Context, username, password string, port int) (*pgxpool.Pool, error)

CreatePGPool calls pgxpool.New and then sends a Ping to the database to ensure it is running. If the ping fails it returns a wrapped ErrPgPoolConnect.

func MustHaveValidContainerLogDir

func MustHaveValidContainerLogDir(logDir string)

MustHaveValidContainerLogDir ensures that if a container log directory is present it is has an absolute path as go tests cannot determine the directory that they are running from.

func RunAcceptanceTests

func RunAcceptanceTests(t *testing.T, ctx context.Context, cm ContainerManager, additionalCases ...Case)

RunAcceptanceTests runs the shared acceptance tests for a given ContainerManager as well as any additional cases provided.

func RunUpgradeTests

func RunUpgradeTests(t *testing.T, ctx context.Context, cm ContainerManager)

RunUpgradeTests runs the shared upgrade tests for a given ContainerManager.

func TerminateContainer

func TerminateContainer(t *testing.T, ctx context.Context, containerName, logDir string, kill bool)

TerminateContainer terminates a running docker container. If logDir is included then the container logs are saved to that directory before it is terminated. If kill is false docker stop is used, otherwise docker kill is.

Types

type Case

type Case struct {
	Name     string                          // name of the test
	SQL      string                          // SQL to run during the test
	Validate func(t *testing.T, row pgx.Row) // optional validation function
}

A Case describes an acceptance test case. If a Validate function is provided then the test will call that function and expect it to handle test failues. Otherwise the case will fail if pool.Exec fails on the SQL.

type ContainerManager

type ContainerManager interface {
	// StartContainer is responsible for starting a Hydra-based container and
	// then blocking until the container is able to accept Postgres connections.
	StartContainer(t *testing.T, ctx context.Context, img string)
	// TerminateContainer handles terminating the container, typically by using
	// [TerminateContainer].
	TerminateContainer(t *testing.T, ctx context.Context, kill bool)
	// Returns the image for the [ContainerManager]s.
	Image() string
	// Returns the UpgradeFromImage when the [ContainerManager] is used for
	// upgrade tests.
	UpgradeFromImage() string
	// Returns the already established pool for the container manager, typically
	// by calling [CreatePGPool]
	PGPool() *pgxpool.Pool
}

A ContainerManager provides a shared interface for managing the lifecycle of Hydra-based containers during testing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL