1 #ifndef OSM2PGSQL_TESTS_COMMON_PG_HPP
2 #define OSM2PGSQL_TESTS_COMMON_PG_HPP
3 
4 /**
5  * SPDX-License-Identifier: GPL-2.0-or-later
6  *
7  * This file is part of osm2pgsql (https://osm2pgsql.org/).
8  *
9  * Copyright (C) 2006-2021 by the osm2pgsql developer community.
10  * For a full list of authors see the git log.
11  */
12 
13 #include <cstdio>
14 #include <cstdlib>
15 #include <stdexcept>
16 #include <string>
17 
18 #include "format.hpp"
19 #include "options.hpp"
20 #include "pgsql.hpp"
21 #include <catch.hpp>
22 
23 #ifdef _MSC_VER
24 #include <process.h>
25 #include <windows.h>
26 #define getpid _getpid
27 #else
28 #include <sys/types.h>
29 #include <unistd.h>
30 #endif
31 
32 namespace testing {
33 /// Helper classes for postgres connections
34 namespace pg {
35 
36 class conn_t : public pg_conn_t
37 {
38 public:
conn_t(std::string const & conninfo)39     conn_t(std::string const &conninfo) : pg_conn_t(conninfo) {}
40 
result_as_string(std::string const & cmd) const41     std::string result_as_string(std::string const &cmd) const
42     {
43         pg_result_t const res = query(PGRES_TUPLES_OK, cmd);
44         REQUIRE(res.num_tuples() == 1);
45         return res.get_value_as_string(0, 0);
46     }
47 
result_as_int(std::string const & cmd) const48     int result_as_int(std::string const &cmd) const
49     {
50         return std::stoi(result_as_string(cmd));
51     }
52 
result_as_ulong(std::string const & cmd) const53     unsigned long result_as_ulong(std::string const &cmd) const
54     {
55         return std::stoul(result_as_string(cmd));
56     }
57 
result_as_double(std::string const & cmd) const58     double result_as_double(std::string const &cmd) const
59     {
60         return std::stod(result_as_string(cmd));
61     }
62 
assert_double(double expected,std::string const & cmd) const63     void assert_double(double expected, std::string const &cmd) const
64     {
65         REQUIRE(Approx(expected).epsilon(0.01) == result_as_double(cmd));
66     }
67 
assert_null(std::string const & cmd) const68     void assert_null(std::string const &cmd) const
69     {
70         pg_result_t const res = query(PGRES_TUPLES_OK, cmd);
71         REQUIRE(res.num_tuples() == 1);
72         REQUIRE(res.is_null(0, 0));
73     }
74 
require_row(std::string const & cmd) const75     pg_result_t require_row(std::string const &cmd) const
76     {
77         pg_result_t res = query(PGRES_TUPLES_OK, cmd);
78         REQUIRE(res.num_tuples() == 1);
79 
80         return res;
81     }
82 
get_count(char const * table_name,std::string const & where="") const83     unsigned long get_count(char const *table_name,
84                             std::string const &where = "") const
85     {
86         auto const query = "SELECT count(*) FROM {} {} {}"_format(
87             table_name, (where.empty() ? "" : "WHERE"), where);
88 
89         return result_as_ulong(query);
90     }
91 
require_has_table(char const * table_name) const92     void require_has_table(char const *table_name) const
93     {
94         auto const where = "oid = '{}'::regclass"_format(table_name);
95 
96         REQUIRE(get_count("pg_catalog.pg_class", where) == 1);
97     }
98 };
99 
100 class tempdb_t
101 {
102 public:
tempdb_t()103     tempdb_t() noexcept
104     {
105         try {
106             conn_t conn{"dbname=postgres"};
107 
108             m_db_name = "osm2pgsql-test-{}-{}"_format(getpid(), time(nullptr));
109             conn.exec("DROP DATABASE IF EXISTS \"{}\""_format(m_db_name));
110             conn.exec("CREATE DATABASE \"{}\" WITH ENCODING 'UTF8'"_format(
111                 m_db_name));
112 
113             conn_t local = connect();
114             local.exec("CREATE EXTENSION postgis");
115             local.exec("CREATE EXTENSION hstore");
116         } catch (std::runtime_error const &e) {
117             fmt::print(stderr,
118                        "Test database cannot be created: {}\n"
119                        "Did you mean to run 'pg_virtualenv ctest'?\n",
120                        e.what());
121             std::exit(1);
122         }
123     }
124 
125     tempdb_t(tempdb_t const &) = delete;
126     tempdb_t &operator=(tempdb_t const &) = delete;
127 
128     tempdb_t(tempdb_t &&) = delete;
129     tempdb_t &operator=(tempdb_t const &&) = delete;
130 
~tempdb_t()131     ~tempdb_t() noexcept
132     {
133         if (!m_db_name.empty()) {
134             // Disable removal of the test database by setting the environment
135             // variable OSM2PGSQL_KEEP_TEST_DB to anything. This can be useful
136             // when debugging tests.
137             char const *const keep_db = std::getenv("OSM2PGSQL_KEEP_TEST_DB");
138             if (keep_db != nullptr) {
139                 return;
140             }
141             try {
142                 conn_t conn{"dbname=postgres"};
143                 conn.exec("DROP DATABASE IF EXISTS \"{}\""_format(m_db_name));
144             } catch (...) {
145                 fprintf(stderr, "DROP DATABASE failed. Ignored.\n");
146             }
147         }
148     }
149 
connect() const150     conn_t connect() const { return conn_t{conninfo()}; }
151 
conninfo() const152     std::string conninfo() const { return "dbname=" + m_db_name; }
153 
db_options() const154     database_options_t db_options() const
155     {
156         database_options_t opt;
157         opt.db = m_db_name;
158 
159         return opt;
160     }
161 
162 private:
163     std::string m_db_name;
164 };
165 
166 } // namespace pg
167 } // namespace testing
168 
169 #endif // OSM2PGSQL_TESTS_COMMON_PG_HPP
170