diff --git a/.gitignore b/.gitignore index 07d3264..35dfb13 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,3 @@ pglabAll.pro.user.4.8-pre1 *.user /pglabAll.pro.user* /public/ -/Testing/Temporary \ No newline at end of file diff --git a/.idea/pgLab.iml b/.idea/pgLab.iml new file mode 100644 index 0000000..4c94235 --- /dev/null +++ b/.idea/pgLab.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.junie/skills/pg-integration-tests/SKILL.md b/.junie/skills/pg-integration-tests/SKILL.md deleted file mode 100644 index 9cc3328..0000000 --- a/.junie/skills/pg-integration-tests/SKILL.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: pg-integration-tests -description: Write and run pgLab tests against a real PostgreSQL server (14–17) using the pgintegration harness (DbTest fixture, template database, REQUIRE_PG_AT_LEAST). Use whenever a test needs a live server, e.g. catalog introspection, generated SQL that must be executed, version-specific behaviour, or when the user mentions integration tests, IntegrationTests, docker-compose Postgres or TEST_DATABASE_URL. ---- - -# Integration tests against real PostgreSQL - -Build/run commands live in `AGENTS.md` (Build and test); don't duplicate them here. - -## When to use -Use for behaviour that depends on a real server: catalog queries, `pg_get_*` output, executing generated SQL, version differences. Pure string/logic tests belong in `PgsqlTests` / `pglabtests` instead. - -## Layout -- `tests/pgintegration/` – static lib: `PgEnv` (global environment, builds template DB `tmpl_` from `fixtures/schema.sql`), `DbTest` fixture, helpers. -- `tests/integration/` – `IntegrationTests` executable, `tst_*.cpp`, `fixtures/schema.sql`, `docker-compose.yml`. -- The binary only reads `TEST_DATABASE_URL`; unset means every `DbTest` skips. - -## Writing a test -```cpp -#include "DbTest.h" -#include "PgTestEnvironment.h" -class MyFeature : public pgtest::DbTest {}; - -TEST_F(MyFeature, Works) -{ - REQUIRE_PG_AT_LEAST(15); // skip on older servers - auto r = conn().query("SELECT 1"); // Pgsql::Connection, throws on SQL error - EXPECT_EQ(r.rows(), 1); -} -``` -- Derive from `pgtest::DbTest`. Each test gets a fresh database (`t__`) cloned from the template and dropped afterwards, so never rely on state from another test and freely modify data/schema. -- `conn()` is the test connection, `dbUrl()` gives a URL for code under test that opens its own connection (close those before the test ends; teardown uses `DROP DATABASE ... WITH (FORCE)`). -- Gate version-specific features with `REQUIRE_PG_AT_LEAST(v)`; use `pgtest::serverVersionNum()` for finer checks. Also test graceful behaviour on PG 14. -- `query()` throws on error; use `EXPECT_THROW` for expected failures. - -## Adding things -- New test file: add it to the `add_executable(IntegrationTests ...)` list in `tests/integration/CMakeLists.txt` (no globbing). -- Shared schema: extend `tests/integration/fixtures/schema.sql` (plain multi-statement SQL, loaded once per process into the template). Keep it small; it must work on all of 14–17. Version-specific schema should be created inside the test. -- Cluster-wide objects (roles, tablespaces) are not isolated by cloning: use unique names (include `pgtest::processId()`) and drop them in `TearDown`. - -## Running and checking -- One version manually: set `TEST_DATABASE_URL` and use `--gtest_filter` (see AGENTS.md); start servers with `docker compose -f tests/integration/docker-compose.yml up -d --wait pg16`, stop with `down -v`. -- After a run no `t_*` or `tmpl_*` databases should remain: `psql -Atc "select datname from pg_database where datname ~ '^(t|tmpl)_'"`. diff --git a/AGENTS.md b/AGENTS.md index 3244f6a..b9c08b0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,6 @@ cmake --build build ctest --test-dir build --output-on-failure ``` - Run one suite: `./build/tests/pglabtests/pglabtests --gtest_filter='SqlLexer*'` (same for `PgsqlTests`). -- Integration tests against real PostgreSQL 14–17 (`IntegrationTests`, label `integration`): configure with `-DPGLAB_INTEGRATION_TESTS=ON` (needs Docker), then `ctest --test-dir build -L integration -j4`. This starts `tests/integration/docker-compose.yml` (ports 54314–54317), runs the binary once per version and tears it down. Manual run against any server: `TEST_DATABASE_URL=postgresql://postgres:test@localhost:54316/postgres ./build/tests/integration/IntegrationTests --gtest_filter='Smoke*'`. Without the variable the tests skip. Developer-facing only, no reno note. See the `pg-integration-tests` skill. - `-DBUILD_TESTS=OFF` skips tests. `build/` is git-ignored; the first configure builds dependencies and is slow. - Run the app headless with `QT_QPA_PLATFORM=offscreen ./build/pglab/pglab`. It needs a PostgreSQL server for anything useful. diff --git a/pglab/catalog/models/DatabasesTableModel.cpp b/pglab/catalog/models/DatabasesTableModel.cpp index 789c981..289eea2 100644 --- a/pglab/catalog/models/DatabasesTableModel.cpp +++ b/pglab/catalog/models/DatabasesTableModel.cpp @@ -205,9 +205,26 @@ void DatabasesTableModel::StartLoadDatabaseSizes(std::map oidIndex) DatabasesTableModel::DatabaseSizes DatabasesTableModel::QueryDatabaseSizes() { + std::string q = + "SELECT pg_database.oid, " + " case when has_database_privilege(current_role, pg_database.oid, \n" + " 'connect') then pg_database_size(pg_database.oid) else -1 end \n" + "FROM pg_database"; + Pgsql::Connection connection; connection.connect(openDatabase->config().connectionString()); - return loadDatabaseSizes(connection); + Pgsql::Result sizesResult = connection.query(q.c_str()); + + DatabaseSizes result; + result.reserve(sizesResult.rows()); + for (auto& row : sizesResult) + { + DatabaseSize ds; + ds.oid = row.get(0); + ds.totalBytes = row.get(1); + result.push_back(ds); + } + return result; } void DatabasesTableModel::PopulateSizes(std::map oidIndex, DatabaseSizes sizes) diff --git a/pglab/catalog/models/DatabasesTableModel.h b/pglab/catalog/models/DatabasesTableModel.h index c25550c..8e46aaa 100644 --- a/pglab/catalog/models/DatabasesTableModel.h +++ b/pglab/catalog/models/DatabasesTableModel.h @@ -3,7 +3,6 @@ #include "BaseTableModel.h" #include "catalog/PgDatabase.h" -#include "catalog/PgDatabaseSizes.h" #include class OpenDatabase; @@ -46,8 +45,17 @@ public: protected: virtual QVariant getDataMeaning(const QModelIndex &index) const override; private: - using DatabaseSize = PgDatabaseSize; - using DatabaseSizes = PgDatabaseSizes; + class DatabaseSize { + public: + Oid oid; + int64_t totalBytes; + + DatabaseSize() + : oid(InvalidOid) + , totalBytes(-1) + {} + }; + using DatabaseSizes = std::vector; class Database { public: diff --git a/pglablib/CMakeLists.txt b/pglablib/CMakeLists.txt index 249e59c..1734d09 100644 --- a/pglablib/CMakeLists.txt +++ b/pglablib/CMakeLists.txt @@ -21,7 +21,6 @@ add_library(pglablib STATIC catalog/PgIndexContainer.cpp catalog/PgConstraint.cpp catalog/PgConstraintContainer.cpp - catalog/PgDatabaseSizes.cpp ParamListJson.cpp ParamListModel.cpp sqlite/SQLiteConnection.cpp @@ -96,7 +95,6 @@ add_library(pglablib STATIC catalog/PgIndexContainer.h catalog/PgConstraint.h catalog/PgConstraintContainer.h - catalog/PgDatabaseSizes.h ParamListJson.h ParamListModel.h sqlite/SQLiteConnection.h diff --git a/pglablib/catalog/PgDatabaseSizes.cpp b/pglablib/catalog/PgDatabaseSizes.cpp deleted file mode 100644 index 244509a..0000000 --- a/pglablib/catalog/PgDatabaseSizes.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "PgDatabaseSizes.h" - -#include "Pgsql_Result.h" - -PgDatabaseSizes loadDatabaseSizes(Pgsql::Connection &connection) -{ - std::string q = - "SELECT pg_database.oid, " - " case when has_database_privilege(current_role, pg_database.oid, \n" - " 'connect') then pg_database_size(pg_database.oid) else -1 end \n" - "FROM pg_database"; - - Pgsql::Result sizesResult = connection.query(q.c_str()); - - PgDatabaseSizes result; - result.reserve(sizesResult.rows()); - for (auto& row : sizesResult) - { - PgDatabaseSize ds; - ds.oid = row.get(0); - ds.totalBytes = row.get(1); - result.push_back(ds); - } - return result; -} diff --git a/pglablib/catalog/PgDatabaseSizes.h b/pglablib/catalog/PgDatabaseSizes.h deleted file mode 100644 index 3c39671..0000000 --- a/pglablib/catalog/PgDatabaseSizes.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef PGDATABASESIZES_H -#define PGDATABASESIZES_H - -#include "Pgsql_Connection.h" -#include -#include -#include - -class PgDatabaseSize { -public: - Oid oid; - int64_t totalBytes; - - PgDatabaseSize() - : oid(InvalidOid) - , totalBytes(-1) - {} -}; - -using PgDatabaseSizes = std::vector; - -/// Returns the size of every database. The size is -1 when the current role has no CONNECT privilege. -PgDatabaseSizes loadDatabaseSizes(Pgsql::Connection &connection); - -#endif // PGDATABASESIZES_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 645cac5..a02ee3c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,5 +3,3 @@ find_package(GTest CONFIG REQUIRED) add_subdirectory(mygtestutils) add_subdirectory(PgsqlTests) add_subdirectory(pglabtests) -add_subdirectory(pgintegration) -add_subdirectory(integration) diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt deleted file mode 100644 index 1284cdb..0000000 --- a/tests/integration/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -add_executable(IntegrationTests - main.cpp - tst_Smoke.cpp - tst_CatalogLoad.cpp - tst_DatabaseSizes.cpp - ) - -target_link_libraries(IntegrationTests PRIVATE - pgintegration - pglablib - GTest::gtest - Qt6::Core - ) - -target_compile_definitions(IntegrationTests PRIVATE - PGTEST_FIXTURE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/fixtures" - ) - -set_target_properties(IntegrationTests PROPERTIES - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED ON - ) - -option(PGLAB_INTEGRATION_TESTS "Register Docker based PostgreSQL integration tests" OFF) - -if (PGLAB_INTEGRATION_TESTS) - set(PGTEST_COMPOSE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/docker-compose.yml) - - add_test(NAME db_up - COMMAND docker compose -f ${PGTEST_COMPOSE_FILE} up -d --wait) - add_test(NAME db_down - COMMAND docker compose -f ${PGTEST_COMPOSE_FILE} down -v) - set_tests_properties(db_up PROPERTIES FIXTURES_SETUP pgcluster LABELS integration) - set_tests_properties(db_down PROPERTIES FIXTURES_CLEANUP pgcluster LABELS integration) - - foreach (v 14 15 16 17 18) - add_test(NAME integration_pg${v} COMMAND IntegrationTests) - set_tests_properties(integration_pg${v} PROPERTIES - FIXTURES_REQUIRED pgcluster - LABELS integration - ENVIRONMENT "TEST_DATABASE_URL=postgresql://postgres:test@localhost:543${v}/postgres") - endforeach () -else () - add_test(NAME IntegrationTests COMMAND IntegrationTests) - set_tests_properties(IntegrationTests PROPERTIES LABELS integration) -endif () diff --git a/tests/integration/docker-compose.yml b/tests/integration/docker-compose.yml deleted file mode 100644 index b2749e2..0000000 --- a/tests/integration/docker-compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -x-pg: &pg - environment: { POSTGRES_PASSWORD: test } - tmpfs: [/var/lib/postgresql] - command: > - postgres -c fsync=off -c synchronous_commit=off - -c full_page_writes=off -c max_connections=200 - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 1s - retries: 30 - -services: - pg14: { <<: *pg, image: "postgres:14", ports: ["54314:5432"] } - pg15: { <<: *pg, image: "postgres:15", ports: ["54315:5432"] } - pg16: { <<: *pg, image: "postgres:16", ports: ["54316:5432"] } - pg17: { <<: *pg, image: "postgres:17", ports: ["54317:5432"] } - pg18: { <<: *pg, image: "postgres:18", ports: ["54318:5432"] } diff --git a/tests/integration/fixtures/schema.sql b/tests/integration/fixtures/schema.sql deleted file mode 100644 index ee8554f..0000000 --- a/tests/integration/fixtures/schema.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE fixture_item ( - id integer PRIMARY KEY, - name text NOT NULL -); - -INSERT INTO fixture_item (id, name) VALUES (1, 'one'), (2, 'two'); diff --git a/tests/integration/main.cpp b/tests/integration/main.cpp deleted file mode 100644 index 60bed8d..0000000 --- a/tests/integration/main.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -#include "PgTestEnvironment.h" - -int main(int argc, char *argv[]) -{ - ::testing::InitGoogleTest(&argc, argv); - ::testing::AddGlobalTestEnvironment(new pgtest::PgEnv(PGTEST_FIXTURE_DIR)); - return RUN_ALL_TESTS(); -} diff --git a/tests/integration/tst_CatalogLoad.cpp b/tests/integration/tst_CatalogLoad.cpp deleted file mode 100644 index 5993337..0000000 --- a/tests/integration/tst_CatalogLoad.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "DbTest.h" -#include "PgTestEnvironment.h" -#include "catalog/PgClassContainer.h" -#include "catalog/PgDatabaseCatalog.h" -#include "Pgsql_Result.h" - -using namespace pgtest; - -class CatalogLoad : public DbTest {}; - -TEST_F(CatalogLoad, SuperuserLoadsCatalog) -{ - PgDatabaseCatalog catalog; - catalog.loadAll(conn(), {}); - - EXPECT_EQ(catalog.serverVersion(), serverVersionNum()); - EXPECT_EQ(catalog.getDBName(), dbName()); - ASSERT_NE(catalog.classes(), nullptr); - EXPECT_NE(catalog.classes()->getByObjectNsAndName("public", "fixture_item"), nullptr); -} - -TEST_F(CatalogLoad, PlainRoleLoadsCatalog) -{ - auto plain = plainConn(); - Pgsql::Result r = plain->query("SELECT rolsuper FROM pg_roles WHERE rolname = current_user"); - ASSERT_EQ(r.rows(), 1); - EXPECT_EQ(QString::fromUtf8(r.val(0, 0)), QString("f")); - - PgDatabaseCatalog catalog; - catalog.loadAll(*plain, {}); - - EXPECT_EQ(catalog.serverVersion(), serverVersionNum()); - EXPECT_EQ(catalog.getDBName(), dbName()); - ASSERT_NE(catalog.classes(), nullptr); - EXPECT_NE(catalog.classes()->getByObjectNsAndName("public", "fixture_item"), nullptr); -} diff --git a/tests/integration/tst_DatabaseSizes.cpp b/tests/integration/tst_DatabaseSizes.cpp deleted file mode 100644 index 7ed8345..0000000 --- a/tests/integration/tst_DatabaseSizes.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "DbTest.h" -#include "PgTestEnvironment.h" -#include "catalog/PgDatabaseSizes.h" -#include "Pgsql_Result.h" - -using namespace pgtest; - -namespace { - -Oid oidOfDatabase(Pgsql::Connection &c, const QString &db) -{ - Pgsql::Result r = c.query(QString("SELECT oid FROM pg_database WHERE datname = '%1'").arg(db)); - if (!r || r.rows() != 1) - return InvalidOid; - return QString::fromUtf8(r.val(0, 0)).toUInt(); -} - -const PgDatabaseSize *findSize(const PgDatabaseSizes &sizes, Oid oid) -{ - for (const auto &s : sizes) { - if (s.oid == oid) - return &s; - } - return nullptr; -} - -} // namespace - -class DatabaseSizes : public DbTest {}; - -TEST_F(DatabaseSizes, ConnectableDatabaseReturnsSize) -{ - auto plain = plainConn(); - Oid oid = oidOfDatabase(*plain, dbName()); - ASSERT_NE(oid, InvalidOid); - - PgDatabaseSizes sizes = loadDatabaseSizes(*plain); - const PgDatabaseSize *s = findSize(sizes, oid); - ASSERT_NE(s, nullptr); - EXPECT_GE(s->totalBytes, 0); -} - -TEST_F(DatabaseSizes, RevokedConnectReturnsMinusOne) -{ - Oid oid = oidOfDatabase(conn(), dbName()); - ASSERT_NE(oid, InvalidOid); - conn().query(QString("REVOKE CONNECT ON DATABASE %1 FROM PUBLIC").arg(quoteIdent(dbName()))); - - auto plain = std::make_unique(); - plain->connect(urlForDbAsPlainRole("postgres")); - - PgDatabaseSizes sizes = loadDatabaseSizes(*plain); - const PgDatabaseSize *s = findSize(sizes, oid); - ASSERT_NE(s, nullptr); - EXPECT_EQ(s->totalBytes, -1); -} - -TEST_F(DatabaseSizes, SuperuserSeesAllDatabases) -{ - PgDatabaseSizes sizes = loadDatabaseSizes(conn()); - - Pgsql::Result r = conn().query("SELECT count(*) FROM pg_database"); - ASSERT_EQ(r.rows(), 1); - EXPECT_EQ(static_cast(QString::fromUtf8(r.val(0, 0)).toULongLong()), sizes.size()); - - for (const auto &s : sizes) - EXPECT_GE(s.totalBytes, 0) << "database oid " << s.oid; -} diff --git a/tests/integration/tst_Smoke.cpp b/tests/integration/tst_Smoke.cpp deleted file mode 100644 index 900a278..0000000 --- a/tests/integration/tst_Smoke.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "DbTest.h" -#include "PgTestEnvironment.h" -#include "Pgsql_Result.h" - -using namespace pgtest; - -namespace { - -int scalarInt(Pgsql::Connection &c, const char *sql) -{ - Pgsql::Result r = c.query(sql); - EXPECT_EQ(r.rows(), 1); - return QString::fromUtf8(r.val(0, 0)).toInt(); -} - -} // namespace - -class Smoke : public DbTest {}; - -TEST_F(Smoke, SelectOne) -{ - EXPECT_EQ(scalarInt(conn(), "SELECT 1"), 1); -} - -TEST_F(Smoke, RunsOnClonedDatabase) -{ - Pgsql::Result r = conn().query("SELECT current_database()"); - ASSERT_EQ(r.rows(), 1); - EXPECT_EQ(QString::fromUtf8(r.val(0, 0)), dbName()); - EXPECT_TRUE(dbName().startsWith("t_")); -} - -TEST_F(Smoke, ServerVersionAtLeast14) -{ - EXPECT_GE(serverVersionNum(), 140000); -} - -TEST_F(Smoke, FixtureSchemaPresent) -{ - EXPECT_EQ(scalarInt(conn(), "SELECT count(*) FROM fixture_item"), 2); -} - -// The two tests below each modify the database and expect the pristine -// fixture state, in whichever order they run. -TEST_F(Smoke, IsolationA) -{ - EXPECT_EQ(scalarInt(conn(), "SELECT count(*) FROM fixture_item"), 2); - conn().query("INSERT INTO fixture_item VALUES (100, 'a')"); - EXPECT_EQ(scalarInt(conn(), "SELECT count(*) FROM fixture_item"), 3); -} - -TEST_F(Smoke, IsolationB) -{ - EXPECT_EQ(scalarInt(conn(), "SELECT count(*) FROM fixture_item"), 2); - conn().query("DELETE FROM fixture_item"); - EXPECT_EQ(scalarInt(conn(), "SELECT count(*) FROM fixture_item"), 0); -} - -TEST_F(Smoke, VersionGate) -{ - REQUIRE_PG_AT_LEAST(15); - EXPECT_GE(serverVersionNum(), 150000); -} diff --git a/tests/pgintegration/CMakeLists.txt b/tests/pgintegration/CMakeLists.txt deleted file mode 100644 index 96f623a..0000000 --- a/tests/pgintegration/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -add_library(pgintegration STATIC - PgTestEnvironment.h - PgTestEnvironment.cpp - DbTest.h - DbTest.cpp - ) - -target_link_libraries(pgintegration PUBLIC - pgsql - GTest::gtest - Qt6::Core - ) - -target_include_directories(pgintegration PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ) - -set_target_properties(pgintegration PROPERTIES - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED ON - ) diff --git a/tests/pgintegration/DbTest.cpp b/tests/pgintegration/DbTest.cpp deleted file mode 100644 index c6caaf2..0000000 --- a/tests/pgintegration/DbTest.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "DbTest.h" -#include "PgTestEnvironment.h" - -#include -#include - -namespace pgtest { - -namespace { -std::atomic g_counter{0}; -} - -QString DbTest::dbUrl() const -{ - return urlForDb(dbName_); -} - -std::unique_ptr DbTest::plainConn() const -{ - auto c = std::make_unique(); - c->connect(urlForDbAsPlainRole(dbName_)); - return c; -} - -void DbTest::SetUp() -{ - if (!available()) - GTEST_SKIP() << "TEST_DATABASE_URL not set or template database unavailable"; - - dbName_ = QString("t_%1_%2").arg(g_counter++).arg(processId()); - try { - { - Pgsql::Connection admin; - admin.connect(*baseUrl()); - admin.query(QString("CREATE DATABASE %1 TEMPLATE %2") - .arg(quoteIdent(dbName_), quoteIdent(templateDbName()))); - } - conn_ = std::make_unique(); - conn_->connect(dbUrl()); - } - catch (const std::exception &e) { - FAIL() << "Creating test database failed: " << e.what(); - } -} - -void DbTest::TearDown() -{ - conn_.reset(); - if (dbName_.isEmpty()) - return; - try { - Pgsql::Connection admin; - admin.connect(*baseUrl()); - admin.query(QString("DROP DATABASE IF EXISTS %1 WITH (FORCE)").arg(quoteIdent(dbName_))); - } - catch (const std::exception &e) { - ADD_FAILURE() << "Dropping test database failed: " << e.what(); - } - dbName_.clear(); -} - -} // namespace pgtest diff --git a/tests/pgintegration/DbTest.h b/tests/pgintegration/DbTest.h deleted file mode 100644 index 37477a5..0000000 --- a/tests/pgintegration/DbTest.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "Pgsql_Connection.h" - -namespace pgtest { - -/// Fixture giving every test its own database cloned from the template. -class DbTest : public ::testing::Test { -protected: - void SetUp() override; - void TearDown() override; - - Pgsql::Connection& conn() { return *conn_; } - QString dbName() const { return dbName_; } - QString dbUrl() const; - /// Opens a new connection to this test's database as the plain (non-superuser) role. - std::unique_ptr plainConn() const; - -private: - QString dbName_; - std::unique_ptr conn_; -}; - -} // namespace pgtest diff --git a/tests/pgintegration/PgTestEnvironment.cpp b/tests/pgintegration/PgTestEnvironment.cpp deleted file mode 100644 index e2557b2..0000000 --- a/tests/pgintegration/PgTestEnvironment.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "PgTestEnvironment.h" - -#include -#include -#include -#include -#include - -#include "Pgsql_Connection.h" -#include "Pgsql_Result.h" - -namespace pgtest { - -namespace { - -bool g_available = false; -int g_versionNum = 0; - -const char *kPlainRolePassword = "pglab_plain_test"; - -} // namespace - -std::optional baseUrl() -{ - QByteArray v = qgetenv("TEST_DATABASE_URL"); - if (v.isEmpty()) - return std::nullopt; - return QString::fromUtf8(v); -} - -QString urlForDb(const QString &db) -{ - auto base = baseUrl(); - if (!base) - throw std::runtime_error("TEST_DATABASE_URL is not set"); - QUrl url(*base); - url.setPath("/" + db); - return url.toString(); -} - -bool available() -{ - return g_available; -} - -int serverVersionNum() -{ - return g_versionNum; -} - -qint64 processId() -{ - return QCoreApplication::applicationPid(); -} - -QString templateDbName() -{ - return QString("tmpl_%1").arg(processId()); -} - -QString plainRoleName() -{ - return QString("tst_%1").arg(processId()); -} - -QString urlForDbAsPlainRole(const QString &db) -{ - auto base = baseUrl(); - if (!base) - throw std::runtime_error("TEST_DATABASE_URL is not set"); - QUrl url(*base); - url.setPath("/" + db); - url.setUserName(plainRoleName()); - url.setPassword(QString::fromUtf8(kPlainRolePassword)); - return url.toString(); -} - -QString quoteIdent(const QString &name) -{ - QString r = name; - r.replace('"', "\"\""); - return "\"" + r + "\""; -} - -void PgEnv::SetUp() -{ - auto base = baseUrl(); - if (!base) { - std::cerr << "TEST_DATABASE_URL not set, integration tests will be skipped\n"; - return; - } - - const QString tmpl = templateDbName(); - try { - { - Pgsql::Connection admin; - admin.connect(*base); - Pgsql::Result r = admin.query("SHOW server_version_num"); - if (!r || r.rows() != 1) - throw std::runtime_error("could not query server_version_num"); - g_versionNum = QString::fromUtf8(r.val(0, 0)).toInt(); - admin.query(QString("DROP DATABASE IF EXISTS %1 WITH (FORCE)").arg(quoteIdent(tmpl))); - admin.query(QString("CREATE DATABASE %1").arg(quoteIdent(tmpl))); - admin.query(QString("DROP ROLE IF EXISTS %1").arg(quoteIdent(plainRoleName()))); - admin.query(QString("CREATE ROLE %1 LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD '%2'") - .arg(quoteIdent(plainRoleName()), QString::fromUtf8(kPlainRolePassword))); - } - { - QFile f(fixtureDir_ + "/schema.sql"); - if (!f.open(QIODevice::ReadOnly)) - throw std::runtime_error("cannot open " + f.fileName().toStdString()); - QByteArray sql = f.readAll(); - - Pgsql::Connection c; - c.connect(urlForDb(tmpl)); - c.query(sql.constData()); - } - g_available = true; - } - catch (const std::exception &e) { - ADD_FAILURE() << "Setting up template database failed: " << e.what(); - try { - Pgsql::Connection admin; - admin.connect(*base); - admin.query(QString("DROP DATABASE IF EXISTS %1 WITH (FORCE)").arg(quoteIdent(tmpl))); - admin.query(QString("DROP ROLE IF EXISTS %1").arg(quoteIdent(plainRoleName()))); - } - catch (const std::exception &) { - } - } -} - -void PgEnv::TearDown() -{ - if (!g_available) - return; - try { - Pgsql::Connection admin; - admin.connect(*baseUrl()); - admin.query(QString("DROP DATABASE IF EXISTS %1 WITH (FORCE)").arg(quoteIdent(templateDbName()))); - admin.query(QString("DROP ROLE IF EXISTS %1").arg(quoteIdent(plainRoleName()))); - } - catch (const std::exception &e) { - ADD_FAILURE() << "Dropping template database or plain role failed: " << e.what(); - } - g_available = false; -} - -} // namespace pgtest diff --git a/tests/pgintegration/PgTestEnvironment.h b/tests/pgintegration/PgTestEnvironment.h deleted file mode 100644 index 0bf3f7a..0000000 --- a/tests/pgintegration/PgTestEnvironment.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace pgtest { - -/// Value of the TEST_DATABASE_URL environment variable, if set and non-empty. -std::optional baseUrl(); - -/// Same as baseUrl() but with the database name replaced. -QString urlForDb(const QString &db); - -/// True when a server is configured and the template database was created. -bool available(); - -/// server_version_num of the server under test (0 when unavailable). -int serverVersionNum(); - -/// Portable process id. -qint64 processId(); - -/// Name of the template database for this process. -QString templateDbName(); - -/// Name of the login role without superuser rights, unique per process. -QString plainRoleName(); - -/// Same as urlForDb() but with the plain role as user. -QString urlForDbAsPlainRole(const QString &db); - -/// Quotes an identifier for use in SQL. -QString quoteIdent(const QString &name); - -/// Global environment, register it in main(): -/// ::testing::AddGlobalTestEnvironment(new pgtest::PgEnv(PGTEST_FIXTURE_DIR)); -/// Creates a template database from /schema.sql once per process. -class PgEnv : public ::testing::Environment { -public: - explicit PgEnv(QString fixtureDir) : fixtureDir_(std::move(fixtureDir)) {} - void SetUp() override; - void TearDown() override; - -private: - QString fixtureDir_; -}; - -} // namespace pgtest - -#define REQUIRE_PG_AT_LEAST(v) \ - if (pgtest::serverVersionNum() < (v) * 10000) GTEST_SKIP() << "needs PG " #v