Browse Source

Move to rebar3 and common_tests

Kozlov Yakov 8 years ago
parent
commit
e6c09abd53

+ 2 - 6
.gitignore

@@ -1,7 +1,3 @@
-*.beam
-*.app
-*.plt
-/ebin/
-/.eunit/
-rebar
+_build
+rebar3
 datadir/

+ 5 - 21
Makefile

@@ -1,5 +1,4 @@
-REBAR = rebar
-LASTVERSION = $(shell git rev-parse HEAD )
+REBAR = rebar3
 
 all: compile
 
@@ -12,25 +11,10 @@ clean:
 src/epgsql_errcodes.erl:
 	./generate_errcodes_src.sh > src/epgsql_errcodes.erl
 
-# The INSERT is used to make sure the schema_version matches the tests
-# being run.
-create_testdbs:
-	# Uses the test environment set up with setup_test_db.sh
-	echo "CREATE DATABASE ${USER};" | psql -h 127.0.0.1 -p 10432 template1
-	psql -h 127.0.0.1 -p 10432 template1 < ./test/data/test_schema.sql
-	psql -h 127.0.0.1 -p 10432 epgsql_test_db1 -c "INSERT INTO schema_version (version) VALUES ('${LASTVERSION}');"
-
 test: compile
-	@$(REBAR) eunit
-
-performance_test: compile
-	erlc ./test/epgsql_perf_tests.erl
-	erl -noshell -pa ./ebin -eval "eunit:test(epgsql_perf_tests, [verbose])" -run init stop
-
-dialyzer: build.plt compile
-	dialyzer --plt $< ebin
+	@$(REBAR) do ct
 
-build.plt:
-	dialyzer -q --build_plt --apps erts kernel stdlib ssl --output_plt $@
+dialyzer: compile
+	@$(REBAR) dialyzer
 
-.PHONY: all compile release clean create_testdbs performance_test test dialyzer
+.PHONY: all compile clean test dialyzer

+ 4 - 11
README.md

@@ -528,22 +528,15 @@ Here's how to create a patch that's easy to integrate:
 
 ## Test Setup
 
-In order to run the epgsql tests, you will need to set up a local
-Postgres database that runs within its own, self-contained directory,
-in order to avoid modifying the system installation of Postgres.
+In order to run the epgsql tests, you will need to install local
+Postgres database.
 
 NOTE: you will need the postgis and hstore extensions to run these
 tests!  On Ubuntu, you can install them with a command like this:
 
-    apt-get install postgresql-9.3-postgis-2.1 postgresql-contrib
+1.  apt-get install postgresql-9.3-postgis-2.1 postgresql-contrib
 
-1. `./setup_test_db.sh` # This sets up an installation of Postgres in datadir/
-
-2. `./start_test_db.sh` # Starts a Postgres instance on its own port (10432).
-
-3. `make create_testdbs` # Creates the test database environment.
-
-4. `make test` # Runs the tests
+2. `make test` # Runs the tests
 
 [![Build Status Master](https://travis-ci.org/epgsql/epgsql.svg?branch=master)](https://travis-ci.org/epgsql/epgsql)
 [![Build Status Devel](https://travis-ci.org/epgsql/epgsql.svg?branch=devel)](https://travis-ci.org/epgsql/epgsql)

+ 0 - 0
ebin/.empty


+ 12 - 0
rebar.config

@@ -4,3 +4,15 @@
 
 {cover_enabled, true}.
 {cover_print_enabled, true}.
+
+{profiles, [
+    {test, [
+        {deps, [
+            {erlexec, {git, "https://github.com/saleyn/erlexec.git", {ref, "576fb5d"}}}
+        ]}
+    ]}
+]}.
+
+{ct_opts, [
+    {ct_hooks, [epgsql_cth]}
+]}.

+ 1 - 0
rebar.lock

@@ -0,0 +1 @@
+[].

+ 0 - 38
setup_test_db.sh

@@ -1,38 +0,0 @@
-#!/bin/sh
-
-if [ -z $(which initdb) ] ; then
-    echo "Postgres not found, you may need to launch like so: PATH=\$PATH:/usr/lib/postgresql/9.3/bin/ $0"
-    exit 1
-fi
-
-## Thanks to Matwey V. Kornilov ( https://github.com/matwey ) for
-## this:
-
-initdb --locale en_US.UTF-8 datadir
-cat > datadir/postgresql.conf <<EOF
-ssl = on
-ssl_ca_file = 'root.crt'
-lc_messages = 'en_US.UTF-8'
-wal_level = 'logical'
-max_replication_slots = 15
-max_wal_senders = 15
-EOF
-
-cp test/data/epgsql.crt datadir/server.crt
-cp test/data/epgsql.key datadir/server.key
-cp test/data/root.crt datadir/root.crt
-cp test/data/root.key datadir/root.key
-chmod 0600 datadir/server.key
-
-cat > datadir/pg_hba.conf <<EOF
-local   all             $USER                                   trust
-host    template1	$USER                   127.0.0.1/32    trust
-host    $USER           $USER                   127.0.0.1/32    trust
-host    epgsql_test_db1 $USER                   127.0.0.1/32    trust
-host    epgsql_test_db1 epgsql_test             127.0.0.1/32    trust
-host    epgsql_test_db1 epgsql_test_md5         127.0.0.1/32    md5
-host    epgsql_test_db1 epgsql_test_cleartext   127.0.0.1/32    password
-hostssl epgsql_test_db1 epgsql_test_cert        127.0.0.1/32    cert clientcert=1
-host    replication     epgsql_test_replication 127.0.0.1/32    trust
-EOF
-

+ 0 - 8
start_test_db.sh

@@ -1,8 +0,0 @@
-#!/bin/sh
-
-if [ -z $(which postgres) ] ; then
-    echo "Postgres not found, you may need to launch like so: PATH=\$PATH:/usr/lib/postgresql/9.3/bin/ $0"
-    exit 1
-fi
-
-postgres -D datadir/ -p 10432 -k `pwd`/datadir/

+ 1 - 6
test/data/test_schema.sql

@@ -13,9 +13,6 @@ CREATE USER epgsql_test_cleartext WITH PASSWORD 'epgsql_test_cleartext';
 CREATE USER epgsql_test_cert;
 CREATE USER epgsql_test_replication WITH REPLICATION PASSWORD 'epgsql_test_replication';
 
-DROP DATABASE epgsql_test_db1;
-DROP DATABASE epgsql_test_db2;
-
 CREATE DATABASE epgsql_test_db1 WITH ENCODING 'UTF8';
 CREATE DATABASE epgsql_test_db2 WITH ENCODING 'UTF8';
 
@@ -26,7 +23,6 @@ GRANT ALL ON DATABASE epgsql_test_db2 to epgsql_test;
 
 \c epgsql_test_db1;
 
-DROP TABLE schema_version;
 CREATE TABLE schema_version (version varchar);
 
 -- This requires Postgres to be compiled with SSL:
@@ -69,7 +65,7 @@ CREATE TABLE test_table2 (
   c_json json,
   c_jsonb jsonb);
 
-CREATE LANGUAGE plpgsql;
+-- CREATE LANGUAGE plpgsql;
 
 CREATE OR REPLACE FUNCTION insert_test1(_id integer, _value text)
 returns integer
@@ -87,7 +83,6 @@ begin
 end
 $$ language plpgsql;
 
-GRANT ALL ON TABLE schema_version TO epgsql_test;
 GRANT ALL ON TABLE test_table1 TO epgsql_test;
 GRANT ALL ON TABLE test_table2 TO epgsql_test;
 GRANT ALL ON FUNCTION insert_test1(integer, text) TO epgsql_test;

+ 1086 - 0
test/epgsql_SUITE.erl

@@ -0,0 +1,1086 @@
+-module(epgsql_SUITE).
+
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
+-include("epgsql_tests.hrl").
+-include("epgsql.hrl").
+
+
+-export([
+    init_per_suite/1,
+    init_per_group/2,
+    all/0,
+    groups/0,
+    end_per_group/2,
+    end_per_suite/1
+]).
+
+-compile(export_all).
+
+modules() ->
+    [
+     epgsql,
+     epgsql_cast,
+     epgsql_incremental
+    ].
+
+init_per_suite(Config) ->
+    Config.
+
+all() ->
+    [{group, M} || M <- modules()].
+
+groups() ->
+    Groups = [
+        {connect, [parrallel], [
+            connect,
+            connect_to_db,
+            connect_as,
+            connect_with_cleartext,
+            connect_with_md5,
+            connect_with_invalid_user,
+            connect_with_invalid_password,
+            connect_with_ssl,
+            connect_with_client_cert
+        ]},
+        {types, [parallel], [
+            numeric_type,
+            character_type,
+            uuid_type,
+            point_type,
+            geometry_type,
+            uuid_select,
+            date_time_type,
+            json_type,
+            misc_type,
+            hstore_type,
+            net_type,
+            array_type,
+            range_type,
+            range8_type,
+            custom_types
+        ]}
+    ],
+
+    Tests = [
+        {group, connect},
+        {group, types},
+
+        prepared_query,
+        select,
+        insert,
+        update,
+        delete,
+        create_and_drop_table,
+        cursor,
+        multiple_result,
+        execute_batch,
+        batch_error,
+        single_batch,
+        extended_select,
+        extended_sync_ok,
+        extended_sync_error,
+        returning_from_insert,
+        returning_from_update,
+        returning_from_delete,
+        parse,
+        parse_column_format,
+        parse_error,
+        parse_and_close,
+        bind,
+        bind_parameter_format,
+        bind_error,
+        bind_and_close,
+        execute_error,
+        describe,
+        describe_with_param,
+        describe_named,
+        describe_error,
+        portal,
+        returning,
+        multiple_statement,
+        multiple_portal,
+        execute_function,
+        parameter_get,
+        parameter_set,
+
+        text_format,
+        query_timeout,
+        execute_timeout,
+        connection_closed,
+        connection_closed_by_server,
+        active_connection_closed,
+        warning_notice,
+        listen_notify,
+        listen_notify_payload,
+        set_notice_receiver,
+        get_cmd_status
+    ],
+    Groups ++ [{Module, [], Tests} || Module <- modules()].
+
+end_per_suite(_Config) ->
+    ok.
+
+init_per_group(GroupName, Config) ->
+    case lists:member(GroupName, modules()) of
+        true -> [{module, GroupName}|Config];
+        false -> Config
+    end.
+end_per_group(_GroupName, _Config) ->
+    ok.
+
+-define(UUID1,
+        <<163,189,240,40,149,151,17,227,141,6,112,24,139,130,16,73>>).
+
+-define(UUID2,
+        <<183,55,22,52,149,151,17,227,187,167,112,24,139,130,16,73>>).
+
+-define(UUID3,
+        <<198,188,155,66,149,151,17,227,138,98,112,24,139,130,16,73>>).
+
+-define(TIMEOUT_ERROR, {error, #error{
+        severity = error,
+        code = <<"57014">>,
+        codename = query_canceled,
+        message = <<"canceling statement due to statement timeout">>,
+        extra = [{file, <<"postgres.c">>},
+                 {line, _},
+                 {routine, _}]
+        }}).
+
+%% From uuid.erl in http://gitorious.org/avtobiff/erlang-uuid
+uuid_to_string(<<U0:32, U1:16, U2:16, U3:16, U4:48>>) ->
+    lists:flatten(io_lib:format(
+                    "~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b",
+                    [U0, U1, U2, U3, U4])).
+
+connect(Config) ->
+    epgsql_ct:connect_only(Config, []).
+
+connect_to_db(Connect) ->
+    epgsql_ct:connect_only(Connect, [{database, "epgsql_test_db1"}]).
+
+connect_as(Config) ->
+    epgsql_ct:connect_only(Config, ["epgsql_test", [{database, "epgsql_test_db1"}]]).
+
+connect_with_cleartext(Config) ->
+    epgsql_ct:connect_only(Config, [
+        "epgsql_test_cleartext",
+        "epgsql_test_cleartext",
+        [{database, "epgsql_test_db1"}]
+    ]).
+
+connect_with_md5(Config) ->
+    epgsql_ct:connect_only(Config, [
+        "epgsql_test_md5",
+        "epgsql_test_md5",
+        [{database, "epgsql_test_db1"}]
+    ]).
+
+connect_with_invalid_user(Config) ->
+    #{pg_port := Port, pg_host := Host} = ?config(pg_config, Config),
+    Module = ?config(module, Config),
+    {error, Why} = Module:connect(
+        Host,
+        "epgsql_test_invalid",
+        "epgsql_test_invalid",
+        [{port, Port}, {database, "epgsql_test_db1"}]),
+    case Why of
+        invalid_authorization_specification -> ok; % =< 8.4
+        invalid_password                    -> ok  % >= 9.0
+    end.
+
+connect_with_invalid_password(Config) ->
+    #{pg_port := Port, pg_host := Host} = ?config(pg_config, Config),
+    Module = ?config(module, Config),
+    {error, Why} = Module:connect(
+        Host,
+        "epgsql_test_md5",
+        "epgsql_test_invalid",
+        [{port, Port}, {database, "epgsql_test_db1"}]),
+    case Why of
+        invalid_authorization_specification -> ok; % =< 8.4
+        invalid_password                    -> ok  % >= 9.0
+    end.
+
+connect_with_ssl(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config,
+        fun(C) ->
+             {ok, _Cols, [{true}]} = Module:equery(C, "select ssl_is_used()")
+        end,
+        "epgsql_test",
+        [{ssl, true}]).
+
+connect_with_client_cert(Config) ->
+    Module = ?config(module, Config),
+    Dir = filename:join(code:lib_dir(epgsql), ?TEST_DATA_DIR),
+    File = fun(Name) -> filename:join(Dir, Name) end,
+    {ok, Pem} = file:read_file(File("epgsql.crt")),
+    [{'Certificate', Der, not_encrypted}] = public_key:pem_decode(Pem),
+    Cert = public_key:pkix_decode_cert(Der, plain),
+    #'TBSCertificate'{serialNumber = Serial} = Cert#'Certificate'.tbsCertificate,
+    Serial2 = list_to_binary(integer_to_list(Serial)),
+
+    epgsql_ct:with_connection(Config,
+         fun(C) ->
+             {ok, _, [{true}]} = Module:equery(C, "select ssl_is_used()"),
+             {ok, _, [{Serial2}]} = Module:equery(C, "select ssl_client_serial()")
+         end,
+         "epgsql_test_cert",
+        [{ssl, true}, {keyfile, File("epgsql.key")}, {certfile, File("epgsql.crt")}]).
+
+%% -ifdef(have_maps).
+%% connect_map_test(Module) ->
+%%     Opts = #{host => ?host,
+%%              port => ?port,
+%%              database => "epgsql_test_db1",
+%%              username => "epgsql_test_md5",
+%%              password => "epgsql_test_md5"
+%%             },
+%%     {ok, C} = Module:connect(Opts),
+%%     Module:close(C),
+%%     epgsql_ct:flush(),
+%%     ok.
+%% -endif.
+
+prepared_query(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, _} = Module:parse(C, "inc", "select $1+1", []),
+        {ok, Cols, [{5}]} = Module:prepared_query(C, "inc", [4]),
+        {ok, Cols, [{2}]} = Module:prepared_query(C, "inc", [1]),
+        {ok, Cols, [{23}]} = Module:prepared_query(C, "inc", [22]),
+        {error, _} = Module:prepared_query(C, "non_existent_query", [4])
+    end).
+
+select(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, Cols, Rows} = Module:squery(C, "select * from test_table1"),
+        [
+            #column{name = <<"id">>, type = int4, size = 4},
+            #column{name = <<"value">>, type = text, size = -1}
+        ] = Cols,
+        [{<<"1">>, <<"one">>}, {<<"2">>, <<"two">>}] = Rows
+      end).
+
+insert(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        {ok, 1} = Module:squery(C, "insert into test_table1 (id, value) values (3, 'three')")
+    end).
+
+update(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        {ok, 1} = Module:squery(C, "insert into test_table1 (id, value) values (3, 'three')"),
+        {ok, 1} = Module:squery(C, "insert into test_table1 (id, value) values (4, 'four')"),
+        {ok, 2} = Module:squery(C, "update test_table1 set value = 'foo' where id > 2"),
+        {ok, _, [{<<"2">>}]} = Module:squery(C, "select count(*) from test_table1 where value = 'foo'")
+    end).
+
+delete(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        {ok, 1} = Module:squery(C, "insert into test_table1 (id, value) values (3, 'three')"),
+        {ok, 1} = Module:squery(C, "insert into test_table1 (id, value) values (4, 'four')"),
+        {ok, 2} = Module:squery(C, "delete from test_table1 where id > 2"),
+        {ok, _, [{<<"2">>}]} = Module:squery(C, "select count(*) from test_table1")
+    end).
+
+create_and_drop_table(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        {ok, [], []} = Module:squery(C, "create table test_table3 (id int4)"),
+        {ok, [#column{type = int4}], []} = Module:squery(C, "select * from test_table3"),
+        {ok, [], []} = Module:squery(C, "drop table test_table3")
+    end).
+
+cursor(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, [], []} = Module:squery(C, "begin"),
+        {ok, [], []} = Module:squery(C, "declare c cursor for select id from test_table1"),
+        {ok, 2} = Module:squery(C, "move forward 2 from c"),
+        {ok, 1} = Module:squery(C, "move backward 1 from c"),
+        {ok, 1, _Cols, [{<<"2">>}]} = Module:squery(C, "fetch next from c"),
+        {ok, [], []} = Module:squery(C, "close c")
+    end).
+
+multiple_result(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        [{ok, _, [{<<"1">>}]}, {ok, _, [{<<"2">>}]}] = Module:squery(C, "select 1; select 2"),
+        [{ok, _, [{<<"1">>}]}, {error, #error{}}] = Module:squery(C, "select 1; select foo;")
+    end).
+
+execute_batch(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S1} = Module:parse(C, "one", "select $1", [int4]),
+        {ok, S2} = Module:parse(C, "two", "select $1 + $2", [int4, int4]),
+        [{ok, [{1}]}, {ok, [{3}]}] =
+            Module:execute_batch(C, [{S1, [1]}, {S2, [1, 2]}])
+    end).
+
+batch_error(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "insert into test_table1(id, value) values($1, $2)"),
+        [{ok, 1}, {error, _}] =
+            Module:execute_batch(
+              C,
+              [{S, [3, "batch_error 3"]},
+               {S, [2, "batch_error 2"]}, % duplicate key error
+               {S, [5, "batch_error 5"]}  % won't be executed
+              ])
+    end).
+
+single_batch(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S1} = Module:parse(C, "one", "select $1", [int4]),
+        [{ok, [{1}]}] = Module:execute_batch(C, [{S1, [1]}])
+    end).
+
+extended_select(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, Cols, Rows} = Module:equery(C, "select * from test_table1", []),
+        [#column{name = <<"id">>, type = int4, size = 4},
+         #column{name = <<"value">>, type = text, size = -1}] = Cols,
+        [{1, <<"one">>}, {2, <<"two">>}] = Rows
+    end).
+
+extended_sync_ok(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, _Cols, [{<<"one">>}]} = Module:equery(C, "select value from test_table1 where id = $1", [1]),
+        {ok, _Cols, [{<<"two">>}]} = Module:equery(C, "select value from test_table1 where id = $1", [2])
+    end).
+
+extended_sync_error(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {error, #error{}} = Module:equery(C, "select _alue from test_table1 where id = $1", [1]),
+        {ok, _Cols, [{<<"one">>}]} = Module:equery(C, "select value from test_table1 where id = $1", [1])
+    end).
+
+returning_from_insert(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        {ok, 1, _Cols, [{3}]} = Module:equery(C, "insert into test_table1 (id) values (3) returning id")
+    end).
+
+returning_from_update(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        {ok, 2, _Cols, [{1}, {2}]} = Module:equery(C, "update test_table1 set value = 'hi' returning id"),
+        ?assertMatch({ok, 0, [#column{}], []},
+                     Module:equery(C, "update test_table1 set value = 'hi' where false returning id")),
+        ?assertMatch([{ok, 2, [#column{}], [{<<"1">>}, {<<"2">>}]},
+               {ok, 0, [#column{}], []}],
+                     Module:squery(C,
+                            "update test_table1 set value = 'hi2' returning id; "
+                            "update test_table1 set value = 'hi' where false returning id"))
+    end).
+
+returning_from_delete(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        {ok, 2, _Cols, [{1}, {2}]} = Module:equery(C, "delete from test_table1 returning id"),
+        ?assertMatch({ok, 0, [#column{}], []},
+                     Module:equery(C, "delete from test_table1 returning id"))
+    end).
+
+parse(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "select * from test_table1"),
+        [#column{name = <<"id">>}, #column{name = <<"value">>}] = S#statement.columns,
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+parse_column_format(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "select 1::int4, false::bool, 2.0::float4"),
+        [#column{type = int4},
+         #column{type = bool},
+         #column{type = float4}] = S#statement.columns,
+        ok = Module:bind(C, S, []),
+        {ok, [{1, false, 2.0}]} = Module:execute(C, S, 0),
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+parse_error(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {error, #error{
+            extra = [{file, _}, {line, _}, {position, <<"8">>}, {routine, _}]
+        }} = Module:parse(C, "select _ from test_table1"),
+        {ok, S} = Module:parse(C, "select * from test_table1"),
+        [#column{name = <<"id">>}, #column{name = <<"value">>}] = S#statement.columns,
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+parse_and_close(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        Parse = fun() -> Module:parse(C, "test", "select * from test_table1", []) end,
+        {ok, S} = Parse(),
+        {error, #error{code = <<"42P05">>, codename = duplicate_prepared_statement}} = Parse(),
+        Module:close(C, S),
+        {ok, S} = Parse(),
+        ok = Module:sync(C)
+    end).
+
+bind(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "select value from test_table1 where id = $1"),
+        ok = Module:bind(C, S, [1]),
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+bind_parameter_format(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "select $1, $2, $3", [int2, text, bool]),
+        [int2, text, bool] = S#statement.types,
+        ok = Module:bind(C, S, [1, "hi", true]),
+        {ok, [{1, <<"hi">>, true}]} = Module:execute(C, S, 0),
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+bind_error(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "select $1::char"),
+        {error, #error{}} = Module:bind(C, S, [0]),
+        ok = Module:bind(C, S, [$A]),
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+bind_and_close(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "select * from test_table1"),
+        ok = Module:bind(C, S, "one", []),
+        {error, #error{code = <<"42P03">>, codename = duplicate_cursor}} = Module:bind(C, S, "one", []),
+        ok = Module:close(C, portal, "one"),
+        ok = Module:bind(C, S, "one", []),
+        ok = Module:sync(C)
+    end).
+
+execute_error(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+          {ok, S} = Module:parse(C, "insert into test_table1 (id, value) values ($1, $2)"),
+          ok = Module:bind(C, S, [1, <<"foo">>]),
+          {error, #error{
+              code = <<"23505">>, codename = unique_violation,
+              extra = [
+                  {constraint_name, <<"test_table1_pkey">>},
+                  {detail, _},
+                  {file, _},
+                  {line, _},
+                  {routine, _},
+                  {schema_name, <<"public">>},
+                  {table_name, <<"test_table1">>}
+              ]
+          }} = Module:execute(C, S, 0),
+          {error, sync_required} = Module:bind(C, S, [3, <<"quux">>]),
+          ok = Module:sync(C),
+          ok = Module:bind(C, S, [3, <<"quux">>]),
+          {ok, _} = Module:execute(C, S, 0),
+          {ok, 1} = Module:squery(C, "delete from test_table1 where id = 3")
+    end).
+
+describe(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "select * from test_table1"),
+        [#column{name = <<"id">>}, #column{name = <<"value">>}] = S#statement.columns,
+        {ok, S} = Module:describe(C, S),
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+describe_with_param(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "select id from test_table1 where id = $1"),
+        [int4] = S#statement.types,
+        [#column{name = <<"id">>}] = S#statement.columns,
+        {ok, S} = Module:describe(C, S),
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+describe_named(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "name", "select * from test_table1", []),
+        [#column{name = <<"id">>}, #column{name = <<"value">>}] = S#statement.columns,
+        {ok, S} = Module:describe(C, S),
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+describe_error(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {error, #error{}} = Module:describe(C, statement, ""),
+        {ok, S} = Module:parse(C, "select * from test_table1"),
+        {ok, S} = Module:describe(C, statement, ""),
+        ok = Module:sync(C)
+
+    end).
+
+portal(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "select value from test_table1"),
+        ok = Module:bind(C, S, []),
+        {partial, [{<<"one">>}]} = Module:execute(C, S, 1),
+        {partial, [{<<"two">>}]} = Module:execute(C, S, 1),
+        {ok, []} = Module:execute(C, S,1),
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+returning(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "update test_table1 set value = $1 returning id"),
+        ok = Module:bind(C, S, ["foo"]),
+        {ok, 2, [{1}, {2}]} = Module:execute(C, S),
+        ok = Module:sync(C)
+    end).
+
+multiple_statement(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S1} = Module:parse(C, "one", "select value from test_table1 where id = 1", []),
+        ok = Module:bind(C, S1, []),
+        {partial, [{<<"one">>}]} = Module:execute(C, S1, 1),
+        {ok, S2} = Module:parse(C, "two", "select value from test_table1 where id = 2", []),
+        ok = Module:bind(C, S2, []),
+        {partial, [{<<"two">>}]} = Module:execute(C, S2, 1),
+        {ok, []} = Module:execute(C, S1, 1),
+        {ok, []} = Module:execute(C, S2, 1),
+        ok = Module:close(C, S1),
+        ok = Module:close(C, S2),
+        ok = Module:sync(C)
+    end).
+
+multiple_portal(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, S} = Module:parse(C, "select value from test_table1 where id = $1"),
+        ok = Module:bind(C, S, "one", [1]),
+        ok = Module:bind(C, S, "two", [2]),
+        {ok, [{<<"one">>}]} = Module:execute(C, S, "one", 0),
+        {ok, [{<<"two">>}]} = Module:execute(C, S, "two", 0),
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end).
+
+execute_function(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        {ok, _Cols1, [{3}]} = Module:equery(C, "select insert_test1(3, 'three')"),
+        {ok, _Cols2, [{<<>>}]} = Module:equery(C, "select do_nothing()")
+    end).
+
+parameter_get(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, <<"off">>} = Module:get_parameter(C, "is_superuser")
+    end).
+
+parameter_set(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, [], []} = Module:squery(C, "set DateStyle = 'ISO, MDY'"),
+        {ok, <<"ISO, MDY">>} = Module:get_parameter(C, "DateStyle"),
+        {ok, _Cols, [{<<"2000-01-02">>}]} = Module:squery(C, "select '2000-01-02'::date"),
+        {ok, [], []} = Module:squery(C, "set DateStyle = 'German'"),
+        {ok, <<"German, DMY">>} = Module:get_parameter(C, "DateStyle"),
+        {ok, _Cols, [{<<"02.01.2000">>}]} = Module:squery(C, "select '2000-01-02'::date")
+    end).
+
+numeric_type(Config) ->
+    check_type(Config, int2, "1", 1, [0, 256, -32768, +32767]),
+    check_type(Config, int4, "1", 1, [0, 512, -2147483648, +2147483647]),
+    check_type(Config, int8, "1", 1, [0, 1024, -9223372036854775808, +9223372036854775807]),
+    check_type(Config, float4, "1.0", 1.0, [0.0, 1.23456, -1.23456]),
+    check_type(Config, float8, "1.0", 1.0, [0.0, 1.23456789012345, -1.23456789012345]).
+
+character_type(Config) ->
+    Alpha = unicode:characters_to_binary([16#03B1]),
+    Ka    = unicode:characters_to_binary([16#304B]),
+    One   = unicode:characters_to_binary([16#10D360]),
+    check_type(Config, bpchar, "'A'", $A, [1, $1, 16#7F, Alpha, Ka, One], "c_char"),
+    check_type(Config, text, "'hi'", <<"hi">>, [<<"">>, <<"hi">>]),
+    check_type(Config, varchar, "'hi'", <<"hi">>, [<<"">>, <<"hi">>]).
+
+uuid_type(Config) ->
+    check_type(Config, uuid,
+        io_lib:format("'~s'", [uuid_to_string(?UUID1)]),
+        list_to_binary(uuid_to_string(?UUID1)), []).
+
+point_type(Config) ->
+    check_type(Config, point, "'(23.15, 100)'", {23.15, 100.0}, []).
+
+geometry_type(Config) ->
+    check_type(Config, geometry, "'COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1))'",
+        {compound_curve,'2d', [
+            {circular_string,'2d', [
+                {point,'2d',0.0,0.0,undefined,undefined},
+                {point,'2d',1.0,1.0,undefined,undefined},
+                {point,'2d',1.0,0.0,undefined,undefined}
+            ]},
+            {line_string,'2d', [
+                {point,'2d',1.0,0.0,undefined,undefined},
+                {point,'2d',0.0,1.0,undefined,undefined}
+            ]}
+        ]}, []).
+
+uuid_select(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_rollback(Config, fun(C) ->
+        U1 = uuid_to_string(?UUID1),
+        U2 = uuid_to_string(?UUID2),
+        U3 = uuid_to_string(?UUID3),
+        {ok, 1} =
+            Module:equery(C, "insert into test_table2 (c_varchar, c_uuid) values ('UUID1', $1)",
+                   [U1]),
+        {ok, 1} =
+            Module:equery(C, "insert into test_table2 (c_varchar, c_uuid) values ('UUID2', $1)",
+                   [U2]),
+        {ok, 1} =
+            Module:equery(C, "insert into test_table2 (c_varchar, c_uuid) values ('UUID3', $1)",
+                   [U3]),
+        Res = Module:equery(C, "select c_varchar, c_uuid from test_table2 where c_uuid = any($1)",
+                    [[U1, U2]]),
+        U1Bin = list_to_binary(U1),
+        U2Bin = list_to_binary(U2),
+        {ok,[{column,<<"c_varchar">>,varchar,_,_,_},
+             {column,<<"c_uuid">>,uuid,_,_,_}],
+         [{<<"UUID1">>, U1Bin},
+          {<<"UUID2">>, U2Bin}]} = Res
+    end).
+
+date_time_type(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        case Module:get_parameter(C, "integer_datetimes") of
+            {ok, <<"on">>}  -> MaxTsDate = 294276;
+            {ok, <<"off">>} -> MaxTsDate = 5874897
+        end,
+
+        check_type(Config, date, "'2008-01-02'", {2008,1,2}, [{-4712,1,1}, {5874897,1,1}]),
+        check_type(Config, time, "'00:01:02'", {0,1,2.0}, [{0,0,0.0}, {24,0,0.0}]),
+        check_type(Config, timetz, "'00:01:02-01'", {{0,1,2.0},1*60*60},
+                   [{{0,0,0.0},0}, {{24,0,0.0},-13*60*60}]),
+        check_type(Config, timestamp, "'2008-01-02 03:04:05'", {{2008,1,2},{3,4,5.0}},
+                   [{{-4712,1,1},{0,0,0.0}}, {{MaxTsDate,12,31}, {23,59,59.0}}, {1322,334285,440966}]),
+        check_type(Config, timestamptz, "'2011-01-02 03:04:05+3'", {{2011, 1, 2}, {0, 4, 5.0}}, [{1324,875970,286983}]),
+        check_type(Config, interval, "'1 hour 2 minutes 3.1 seconds'", {{1,2,3.1},0,0},
+                   [{{0,0,0.0},0,-178000000 * 12}, {{0,0,0.0},0,178000000 * 12}])
+    end).
+
+json_type(Config) ->
+    check_type(Config, json, "'{}'", <<"{}">>,
+               [<<"{}">>, <<"[]">>, <<"1">>, <<"1.0">>, <<"true">>, <<"\"string\"">>, <<"{\"key\": []}">>]),
+    check_type(Config, jsonb, "'{}'", <<"{}">>,
+               [<<"{}">>, <<"[]">>, <<"1">>, <<"1.0">>, <<"true">>, <<"\"string\"">>, <<"{\"key\": []}">>]).
+
+misc_type(Config) ->
+    check_type(Config, bool, "true", true, [true, false]),
+    check_type(Config, bytea, "E'\001\002'", <<1,2>>, [<<>>, <<0,128,255>>]).
+
+hstore_type(Config) ->
+    Values = [
+        {[]},
+        {[{null, null}]},
+        {[{null, undefined}]},
+        {[{1, null}]},
+        {[{1.0, null}]},
+        {[{1, undefined}]},
+        {[{1.0, undefined}]},
+        {[{<<"a">>, <<"c">>}, {<<"c">>, <<"d">>}]},
+        {[{<<"a">>, <<"c">>}, {<<"c">>, null}]},
+        {[{<<"a">>, <<"c">>}, {<<"c">>, undefined}]}
+    ],
+    check_type(Config, hstore, "''", {[]}, []),
+    check_type(Config, hstore,
+               "'a => 1, b => 2.0, c => null'",
+               {[{<<"c">>, null}, {<<"b">>, <<"2.0">>}, {<<"a">>, <<"1">>}]}, Values).
+
+net_type(Config) ->
+    check_type(Config, cidr, "'127.0.0.1/32'", {{127,0,0,1}, 32}, [{{127,0,0,1}, 32}, {{0,0,0,0,0,0,0,1}, 128}]),
+    check_type(Config, inet, "'127.0.0.1'", {127,0,0,1}, [{127,0,0,1}, {0,0,0,0,0,0,0,1}]).
+
+array_type(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, _, [{[1, 2]}]} = Module:equery(C, "select ($1::int[])[1:2]", [[1, 2, 3]]),
+        {ok, _, [{[{1, <<"one">>}, {2, <<"two">>}]}]} =
+            Module:equery(C, "select Array(select (id, value) from test_table1)", []),
+        Select = fun(Type, A) ->
+            Query = "select $1::" ++ atom_to_list(Type) ++ "[]",
+            {ok, _Cols, [{A2}]} = Module:equery(C, Query, [A]),
+            case lists:all(fun({V, V2}) -> compare(Type, V, V2) end, lists:zip(A, A2)) of
+                true  -> ok;
+                false -> ?assertMatch(A, A2)
+            end
+        end,
+        Select(int2,   []),
+        Select(int2,   [1, 2, 3, 4]),
+        Select(int2,   [[1], [2], [3], [4]]),
+        Select(int2,   [[[[[[1, 2]]]]]]),
+        Select(bool,   [true]),
+        Select(char,   [$a, $b, $c]),
+        Select(int4,   [[1, 2]]),
+        Select(int8,   [[[[1, 2]], [[3, 4]]]]),
+        Select(text,   [<<"one">>, <<"two>">>]),
+        Select(varchar,   [<<"one">>, <<"two>">>]),
+        Select(float4, [0.0, 1.0, 0.123]),
+        Select(float8, [0.0, 1.0, 0.123]),
+        Select(date, [{2008,1,2}, {2008,1,3}]),
+        Select(time, [{0,1,2.0}, {0,1,3.0}]),
+        Select(timetz, [{{0,1,2.0},1*60*60}, {{0,1,3.0},1*60*60}]),
+        Select(timestamp, [{{2008,1,2},{3,4,5.0}}, {{2008,1,2},{3,4,6.0}}]),
+        Select(timestamptz, [{{2008,1,2},{3,4,5.0}}, {{2008,1,2},{3,4,6.0}}]),
+        Select(interval, [{{1,2,3.1},0,0}, {{1,2,3.2},0,0}]),
+        Select(hstore, [{[{null, null}, {a, 1}, {1, 2}, {b, undefined}]}]),
+        Select(hstore, [[{[{null, null}, {a, 1}, {1, 2}, {b, undefined}]}, {[]}], [{[{a, 1}]}, {[{null, 2}]}]]),
+        Select(cidr, [{{127,0,0,1}, 32}, {{0,0,0,0,0,0,0,1}, 128}]),
+        Select(inet, [{127,0,0,1}, {0,0,0,0,0,0,0,1}]),
+        Select(json, [<<"{}">>, <<"[]">>, <<"1">>, <<"1.0">>, <<"true">>, <<"\"string\"">>, <<"{\"key\": []}">>]),
+        Select(jsonb, [<<"{}">>, <<"[]">>, <<"1">>, <<"1.0">>, <<"true">>, <<"\"string\"">>, <<"{\"key\": []}">>])
+    end).
+
+custom_types(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        Module:squery(C, "drop table if exists t_foo;"),
+        Module:squery(C, "drop type foo;"),
+        {ok, [], []} = Module:squery(C, "create type foo as enum('foo', 'bar');"),
+        ok = epgsql:update_type_cache(C, [<<"foo">>]),
+        {ok, [], []} = Module:squery(C, "create table t_foo (col foo);"),
+        {ok, S} = Module:parse(C, "insert_foo", "insert into t_foo values ($1)", [foo]),
+        ok = Module:bind(C, S, ["bar"]),
+        {ok, 1} = Module:execute(C, S)
+    end).
+
+text_format(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        Select = fun(Type, V) ->
+            V2 = list_to_binary(V),
+            Query = "select $1::" ++ Type,
+            {ok, _Cols, [{V2}]} = Module:equery(C, Query, [V]),
+            {ok, _Cols, [{V2}]} = Module:equery(C, Query, [V2])
+        end,
+        Select("numeric", "123456")
+    end).
+
+query_timeout(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, _, _} = Module:squery(C, "SET statement_timeout = 500"),
+        ?TIMEOUT_ERROR = Module:squery(C, "SELECT pg_sleep(1)"),
+        ?TIMEOUT_ERROR = Module:equery(C, "SELECT pg_sleep(2)"),
+        {ok, _Cols, [{1}]} = Module:equery(C, "SELECT 1")
+    end, []).
+
+execute_timeout(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, _, _} = Module:squery(C, "SET statement_timeout = 500"),
+        {ok, S} = Module:parse(C, "select pg_sleep($1)"),
+        ok = Module:bind(C, S, [2]),
+        ?TIMEOUT_ERROR = Module:execute(C, S, 0),
+        ok = Module:sync(C),
+        ok = Module:bind(C, S, [0]),
+        {ok, [{<<>>}]} = Module:execute(C, S, 0),
+        ok = Module:close(C, S),
+        ok = Module:sync(C)
+    end, []).
+
+connection_closed(Config) ->
+    Module = ?config(module, Config),
+    #{pg_host := Host, pg_port := Port} = ?config(pg_config, Config),
+    P = self(),
+    spawn_link(fun() ->
+        process_flag(trap_exit, true),
+        {ok, C} = Module:connect(Host, "epgsql_test",[
+            {port, Port},
+            {database, "epgsql_test_db1"}
+        ]),
+        P ! {connected, C},
+        receive
+            Any -> P ! Any
+        end
+    end),
+    receive
+        {connected, C} ->
+            timer:sleep(100),
+            Module:close(C),
+            {'EXIT', C, _} = receive R -> R end
+    end,
+    epgsql_ct:flush().
+
+connection_closed_by_server(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C1) ->
+        P = self(),
+        spawn_link(fun() ->
+            process_flag(trap_exit, true),
+            epgsql_ct:with_connection(Config, fun(C2) ->
+                {ok, _, [{Pid}]} = Module:equery(C2, "select pg_backend_pid()"),
+                % emulate of disconnection
+                {ok, _, [{true}]} = Module:equery(C1,
+                "select pg_terminate_backend($1)", [Pid]),
+                receive
+                    {'EXIT', C2, {shutdown, #error{code = <<"57P01">>}}} ->
+                        P ! ok;
+                    Other ->
+                        ?debugFmt("Unexpected msg: ~p~n", [Other]),
+                        P ! error
+                end
+            end)
+        end),
+        receive ok -> ok end
+    end).
+
+active_connection_closed(Config) ->
+    Module = ?config(module, Config),
+    #{pg_host := Host, pg_port := Port} = ?config(pg_config, Config),
+    P = self(),
+    F = fun() ->
+          process_flag(trap_exit, true),
+          {ok, C} = Module:connect(Host, [
+              {database, "epgsql_test_db1"},
+              {port, Port}
+          ]),
+          P ! {connected, C},
+          R = Module:squery(C, "select pg_sleep(10)"),
+          P ! R
+        end,
+    spawn_link(F),
+    receive
+        {connected, C} ->
+            timer:sleep(100),
+            Module:close(C),
+            {error, closed} = receive R -> R end
+    end,
+    epgsql_ct:flush().
+
+warning_notice(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        Q = "create function pg_temp.raise() returns void as $$
+             begin
+               raise warning 'oops';
+             end;
+             $$ language plpgsql;
+             select pg_temp.raise()",
+        [{ok, _, _}, _] = Module:squery(C, Q),
+        receive
+            {epgsql, C, {notice, #error{message = <<"oops">>, extra = Extra}}} ->
+                ?assertMatch([{file, _},{line, _},{routine, _}], Extra),
+                ok
+        after
+            100 -> erlang:error(didnt_receive_notice)
+        end
+    end, [{async, self()}]).
+
+listen_notify(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, [], []}     = Module:squery(C, "listen epgsql_test"),
+        {ok, _, [{Pid}]} = Module:equery(C, "select pg_backend_pid()"),
+        {ok, [], []}     = Module:squery(C, "notify epgsql_test"),
+        receive
+            {epgsql, C, {notification, <<"epgsql_test">>, Pid, <<>>}} -> ok
+        after
+            100 -> erlang:error(didnt_receive_notification)
+        end
+    end, [{async, self()}]).
+
+listen_notify_payload(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_min_version(Config, 9.0, fun(C) ->
+        {ok, [], []}     = Module:squery(C, "listen epgsql_test"),
+        {ok, _, [{Pid}]} = Module:equery(C, "select pg_backend_pid()"),
+        {ok, [], []}     = Module:squery(C, "notify epgsql_test, 'test!'"),
+        receive
+            {epgsql, C, {notification, <<"epgsql_test">>, Pid, <<"test!">>}} -> ok
+        after
+            100 -> erlang:error(didnt_receive_notification)
+        end
+    end, [{async, self()}]).
+
+set_notice_receiver(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_min_version(Config, 9.0, fun(C) ->
+        {ok, [], []}     = Module:squery(C, "listen epgsql_test"),
+        {ok, _, [{Pid}]} = Module:equery(C, "select pg_backend_pid()"),
+
+        EnsureNoNotification = fun(Payload) ->
+            {ok, [], []}     = Module:squery(C, ["notify epgsql_test, '", Payload, "'"]),
+            receive
+                {epgsql, _, _} -> erlang:error(got_unexpected_notification)
+            after
+                10 -> ok
+            end
+        end,
+        EnsureNotification = fun(Payload) ->
+            {ok, [], []}     = Module:squery(C, ["notify epgsql_test, '", Payload, "'"]),
+            receive
+                {epgsql, C, {notification, <<"epgsql_test">>, Pid, Payload}} -> ok
+            after
+                100 -> erlang:error(didnt_receive_notification)
+            end
+        end,
+        Self = self(),
+
+        EnsureNoNotification(<<"test1">>),
+
+        % Set pid()
+        {ok, undefined} = Module:set_notice_receiver(C, Self),
+        EnsureNotification(<<"test2">>),
+
+        %% test PL/PgSQL NOTICE
+        {ok, [], []} = Module:squery(C, ["DO $$ BEGIN RAISE WARNING 'test notice'; END $$;"]),
+        receive
+            {epgsql, C, {notice, #error{severity = warning,
+                         code = <<"01000">>,
+                         message = <<"test notice">>,
+                         extra = _}}} -> ok
+        after
+           100 -> erlang:error(didnt_receive_notice)
+        end,
+
+        % set registered pid
+        Receiver = pg_notification_receiver,
+        register(Receiver, Self),
+        {ok, Self} = Module:set_notice_receiver(C, Receiver),
+        EnsureNotification(<<"test3">>),
+
+        % make registered name invalid
+        unregister(Receiver),
+        EnsureNoNotification(<<"test4">>),
+
+        % disable
+        {ok, Receiver} = Module:set_notice_receiver(C, undefined),
+        EnsureNoNotification(<<"test5">>)
+    end, []).
+
+get_cmd_status(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, [], []} = Module:squery(C, "BEGIN"),
+        ?assertEqual({ok, 'begin'}, Module:get_cmd_status(C)),
+        {ok, [], []} = epgsql:equery(C, "CREATE TEMPORARY TABLE cmd_status_t(col INTEGER)"),
+        ?assertEqual({ok, 'create'}, Module:get_cmd_status(C)),
+        %% Some commands have number of affected rows in status
+        {ok, N} = Module:squery(C, "INSERT INTO cmd_status_t (col) VALUES (1), (2)"),
+        ?assertEqual({ok, {'insert', N}}, Module:get_cmd_status(C)),
+        {ok, 1} = Module:squery(C, "UPDATE cmd_status_t SET col=3 WHERE col=1"),
+        ?assertEqual({ok, {'update', 1}}, Module:get_cmd_status(C)),
+        %% Failed queries have no status
+        {error, _} = Module:squery(C, "UPDATE cmd_status_t SET col='text' WHERE col=2"),
+        ?assertEqual({ok, undefined}, Module:get_cmd_status(C)),
+        %% if COMMIT failed, status will be 'rollback'
+        {ok, [], []} = Module:squery(C, "COMMIT"),
+        ?assertEqual({ok, 'rollback'}, Module:get_cmd_status(C)),
+        %% Only last command's status returned
+        [_, _, _] = Module:squery(C, "BEGIN; SELECT 1; COMMIT"),
+        ?assertEqual({ok, 'commit'}, Module:get_cmd_status(C))
+    end).
+
+range_type(Config) ->
+    epgsql_ct:with_min_version(Config, 9.2, fun(_C) ->
+        check_type(Config, int4range, "int4range(10, 20)", {10, 20}, [
+            {1, 58}, {-1, 12}, {-985521, 5412687}, {minus_infinity, 0},
+            {984655, plus_infinity}, {minus_infinity, plus_infinity}
+        ])
+   end, []).
+
+range8_type(Config) ->
+    epgsql_ct:with_min_version(Config, 9.2, fun(_C) ->
+        check_type(Config, int8range, "int8range(10, 20)", {10, 20}, [
+            {1, 58}, {-1, 12}, {-9223372036854775808, 5412687},
+            {minus_infinity, 9223372036854775807},
+            {984655, plus_infinity}, {minus_infinity, plus_infinity}
+        ])
+    end, []).
+
+%% =============================================================================
+%% Internal functions
+%% ============================================================================
+
+check_type(Config, Type, In, Out, Values) ->
+    Column = "c_" ++ atom_to_list(Type),
+    check_type(Config, Type, In, Out, Values, Column).
+
+check_type(Config, Type, In, Out, Values, Column) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        Select = io_lib:format("select ~s::~w", [In, Type]),
+        Res = Module:equery(C, Select),
+        {ok, [#column{type = Type}], [{Out}]} = Res,
+        Sql = io_lib:format("insert into test_table2 (~s) values ($1) returning ~s", [Column, Column]),
+        {ok, #statement{columns = [#column{type = Type}]} = S} = Module:parse(C, Sql),
+        Insert = fun(V) ->
+            ok = Module:bind(C, S, [V]),
+            {ok, 1, [{V2}]} = Module:execute(C, S),
+            case compare(Type, V, V2) of
+                true  -> ok;
+                false -> ?debugFmt("~p =/= ~p~n", [V, V2]), ?assert(false)
+            end,
+            ok = Module:sync(C)
+        end,
+        lists:foreach(Insert, [null, undefined | Values])
+    end).
+
+compare(_Type, null, null)      -> true;
+compare(_Type, undefined, null) -> true;
+compare(float4, V1, V2)         -> abs(V2 - V1) < 0.000001;
+compare(float8, V1, V2)         -> abs(V2 - V1) < 0.000000000000001;
+compare(hstore, {V1}, V2)       -> compare(hstore, V1, V2);
+compare(hstore, V1, {V2})       -> compare(hstore, V1, V2);
+compare(hstore, V1, V2)         ->
+    orddict:from_list(format_hstore(V1)) =:= orddict:from_list(format_hstore(V2));
+compare(Type, V1 = {_, _, MS}, {D2, {H2, M2, S2}}) when Type == timestamp;
+                                                        Type == timestamptz ->
+    {D1, {H1, M1, S1}} = calendar:now_to_universal_time(V1),
+    ({D1, H1, M1} =:= {D2, H2, M2}) and (abs(S1 + MS/1000000 - S2) < 0.000000000000001);
+compare(_Type, V1, V2)     -> V1 =:= V2.
+
+format_hstore({Hstore}) -> Hstore;
+format_hstore(Hstore) ->
+    [{format_hstore_key(Key), format_hstore_value(Value)} || {Key, Value} <- Hstore].
+
+format_hstore_key(Key) -> format_hstore_string(Key).
+
+format_hstore_value(null) -> null;
+format_hstore_value(undefined) -> null;
+format_hstore_value(Value) -> format_hstore_string(Value).
+
+format_hstore_string(Num) when is_number(Num) -> iolist_to_binary(io_lib:format("~w", [Num]));
+format_hstore_string(Str) -> iolist_to_binary(io_lib:format("~s", [Str])).

+ 80 - 0
test/epgsql_ct.erl

@@ -0,0 +1,80 @@
+-module(epgsql_ct).
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-export([
+    connect_only/2,
+    with_connection/2,
+    with_connection/3,
+    with_connection/4,
+    with_rollback/2,
+    with_min_version/4,
+    flush/0
+]).
+
+connect_only(Config, Args) ->
+    #{pg_host := Host, pg_port := Port} = ?config(pg_config, Config),
+    Module = ?config(module, Config),
+    TestOpts = [{port, Port}],
+    case Args of
+        [User, Opts]       -> Args2 = [User, TestOpts ++ Opts];
+        [User, Pass, Opts] -> Args2 = [User, Pass, TestOpts ++ Opts];
+        Opts               -> Args2 = [TestOpts ++ Opts]
+    end,
+    {ok, C} = apply(Module, connect, [Host | Args2]),
+    Module:close(C),
+    flush().
+
+with_connection(Config, F) ->
+    with_connection(Config, F, "epgsql_test", []).
+
+with_connection(Config, F, Args) ->
+    with_connection(Config, F, "epgsql_test", Args).
+
+with_connection(Config, F, Username, Args) ->
+    #{pg_host := Host, pg_port := Port} = ?config(pg_config, Config),
+    Module = ?config(module, Config),
+    Args2 = [{port, Port}, {database, "epgsql_test_db1"} | Args],
+    {ok, C} = Module:connect(Host, Username, Args2),
+    try
+        F(C)
+    after
+        Module:close(C)
+    end,
+    flush().
+
+with_rollback(Config, F) ->
+    Module = ?config(module, Config),
+    with_connection(
+      Config,
+      fun(C) ->
+              try
+                  Module:squery(C, "begin"),
+                  F(C)
+                  after
+                      Module:squery(C, "rollback")
+                  end
+      end).
+
+with_min_version(Config, Min, F, Args) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(Config, fun(C) ->
+        {ok, Bin} = Module:get_parameter(C, <<"server_version">>),
+        {ok, [{float, 1, Ver} | _], _} = erl_scan:string(binary_to_list(Bin)),
+        case Ver >= Min of
+            true  -> F(C);
+            false -> ?debugFmt("skipping test requiring PostgreSQL >= ~.2f~n", [Min])
+        end
+    end, Args).
+
+%% flush mailbox
+flush() ->
+    ?assertEqual([], flush([])).
+
+flush(Acc) ->
+    receive
+        {'EXIT', _Pid, normal} -> flush(Acc);
+        M                      -> flush([M | Acc])
+    after
+        0 -> lists:reverse(Acc)
+    end.

+ 191 - 0
test/epgsql_cth.erl

@@ -0,0 +1,191 @@
+-module(epgsql_cth).
+
+-export([
+         init/2,
+         terminate/1,
+         pre_init_per_suite/3
+        ]).
+
+-include_lib("common_test/include/ct.hrl").
+-include("epgsql_tests.hrl").
+
+init(_Id, State) ->
+    Start = os:timestamp(),
+    PgConfig = start_postgres(),
+    ok = create_testdbs(PgConfig),
+    error_logger:info_msg("postgres started in ~p ms\n",
+        [timer:now_diff(os:timestamp(), Start) / 1000]),
+    [{pg_config, PgConfig}|State].
+
+pre_init_per_suite(_SuiteName, Config, State) ->
+    {Config ++ State, State}.
+
+terminate(State) ->
+    ok = stop_postgres(?config(pg_config, State)).
+
+create_testdbs(#{pg_host := PgHost, pg_port := PgPort, pg_user := PgUser,
+                 utils := #{psql := Psql, createdb := CreateDB}}) ->
+    Opts = lists:concat([" -h ", PgHost, " -p ", PgPort, " "]),
+    Cmds = [
+        [CreateDB, Opts, PgUser],
+        [Psql, Opts, "template1 < ", filename:join(?TEST_DATA_DIR, "test_schema.sql")]
+    ],
+    lists:foreach(fun(Cmd) ->
+        {ok, []} = exec:run(lists:flatten(Cmd), [sync])
+    end, Cmds).
+
+%% =============================================================================
+%% start postgresql
+%% =============================================================================
+
+-define(PG_TIMEOUT, 30000).
+
+start_postgres() ->
+    {ok, _} = application:ensure_all_started(erlexec),
+    pipe([
+        fun find_utils/1,
+        fun init_database/1,
+        fun write_postgresql_config/1,
+        fun copy_certs/1,
+        fun write_pg_hba_config/1,
+        fun start_postgresql/1
+    ], #{}).
+
+stop_postgres(#{pg_proc := PgProc}) ->
+    PgProc ! stop,
+    ok.
+
+find_utils(State) ->
+    Utils = [initdb, createdb, postgres, psql],
+    UtilsMap = lists:foldl(fun(U, Map) ->
+        UList = atom_to_list(U),
+        Path = case os:find_executable(UList) of
+            false ->
+                case filelib:wildcard("/usr/lib/postgresql/**/" ++ UList) of
+                    [] ->
+                        error_logger:error_msg("~s not found", [U]),
+                        throw({util_no_found, U});
+                    List -> lists:last(lists:sort(List))
+                end;
+            P -> P
+        end,
+        maps:put(U, Path, Map)
+    end, #{}, Utils),
+    State#{utils => UtilsMap}.
+
+start_postgresql(#{pg_datadir := PgDataDir, utils := #{postgres := Postgres}} = Config) ->
+    PgHost = "localhost",
+    PgPort = get_free_port(),
+    SocketDir = "/tmp",
+    Pid = proc_lib:spawn(fun() ->
+        {ok, _, I} = exec:run_link(lists:concat([Postgres,
+            " -k ", SocketDir,
+            " -D ", PgDataDir,
+            " -h ", PgHost,
+            " -p ", PgPort]),
+            [{stderr,
+              fun(_, _, Msg) ->
+                  error_logger:info_msg("postgres: ~s", [Msg])
+              end}]),
+        (fun Loop() ->
+             receive
+                 stop -> exec:kill(I, 0);
+                 _ -> Loop()
+             end
+        end)()
+    end),
+    ConfigR = Config#{pg_host => PgHost, pg_port => PgPort, pg_proc => Pid},
+    wait_postgresql_ready(SocketDir, ConfigR).
+
+wait_postgresql_ready(SocketDir, #{pg_port := PgPort} = Config) ->
+    PgFile = lists:concat([".s.PGSQL.", PgPort]),
+    Path = filename:join(SocketDir, PgFile),
+    WaitUntil = ts_add(os:timestamp(), ?PG_TIMEOUT),
+    case wait_(Path, WaitUntil) of
+        true -> ok;
+        false -> throw(<<"Postgresql init timeout">>)
+    end,
+    Config.
+
+wait_(Path, Until) ->
+    case file:read_file_info(Path) of
+        {error, enoent} ->
+            case os:timestamp() > Until of
+                true -> false;
+                _ ->
+                    timer:sleep(300),
+                    wait_(Path, Until)
+            end;
+        _ -> true
+    end.
+
+init_database(#{utils := #{initdb := Initdb}}=Config) ->
+    {ok, Cwd} = file:get_cwd(),
+    PgDataDir = filename:append(Cwd, "datadir"),
+    {ok, _} = exec:run(Initdb ++ " --locale en_US.UTF8 " ++ PgDataDir, [sync,stdout,stderr]),
+    Config#{pg_datadir => PgDataDir}.
+
+write_postgresql_config(#{pg_datadir := PgDataDir}=Config) ->
+    PGConfig = [
+        "ssl = on\n",
+        "ssl_ca_file = 'root.crt'\n",
+        "lc_messages = 'en_US.UTF-8'\n",
+        "wal_level = 'logical'\n",
+        "max_replication_slots = 15\n",
+        "max_wal_senders = 15"
+    ],
+    FilePath = filename:join(PgDataDir, "postgresql.conf"),
+    ok = file:write_file(FilePath, PGConfig),
+    Config.
+
+copy_certs(#{pg_datadir := PgDataDir}=Config) ->
+    Files = [
+        {"epgsql.crt", "server.crt", 8#00660},
+        {"epgsql.key", "server.key", 8#00600},
+        {"root.crt", "root.crt", 8#00660},
+        {"root.key", "root.key", 8#00660}
+    ],
+    lists:foreach(fun({From, To, Mode}) ->
+        FromPath = filename:join(?TEST_DATA_DIR, From),
+        ToPath = filename:join(PgDataDir, To),
+        {ok, _} = file:copy(FromPath, ToPath),
+        ok = file:change_mode(ToPath, Mode)
+    end, Files),
+    Config.
+
+write_pg_hba_config(#{pg_datadir := PgDataDir}=Config) ->
+    User = os:getenv("USER"),
+    PGConfig = [
+        "local   all             ", User, "                              trust\n",
+        "host    template1       ", User, "              127.0.0.1/32    trust\n",
+        "host    ", User, "      ", User, "              127.0.0.1/32    trust\n",
+        "hostssl postgres        ", User, "              127.0.0.1/32    trust\n",
+        "host    epgsql_test_db1 ", User, "              127.0.0.1/32    trust\n",
+        "host    epgsql_test_db1 epgsql_test             127.0.0.1/32    trust\n",
+        "host    epgsql_test_db1 epgsql_test_md5         127.0.0.1/32    md5\n",
+        "host    epgsql_test_db1 epgsql_test_cleartext   127.0.0.1/32    password\n",
+        "hostssl epgsql_test_db1 epgsql_test_cert        127.0.0.1/32    cert clientcert=1\n",
+        "host    replication     epgsql_test_replication 127.0.0.1/32    trust"
+    ],
+    FilePath = filename:join(PgDataDir, "pg_hba.conf"),
+    ok = file:write_file(FilePath, PGConfig),
+    Config#{pg_user => User}.
+
+%% =============================================================================
+%% Internal functions
+%% =============================================================================
+
+get_free_port() ->
+    {ok, Listen} = gen_tcp:listen(0, []),
+    {ok, Port} = inet:port(Listen),
+    ok = gen_tcp:close(Listen),
+    Port.
+
+pipe(Funs, Config) ->
+    lists:foldl(fun(F, S) -> F(S) end, Config, Funs).
+
+ts_add({Mega, Sec, Micro}, Timeout) ->
+    V = (Mega * 1000000 + Sec)*1000000 + Micro + Timeout * 1000,
+    {V div 1000000000000,
+     V div 1000000 rem 1000000,
+     V rem 1000000}.

+ 76 - 0
test/epgsql_perf_SUITE.erl

@@ -0,0 +1,76 @@
+-module(epgsql_perf_SUITE).
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-export([
+    init_per_suite/1,
+    all/0,
+    end_per_suite/1,
+
+    prepare_data/0,
+    prepare_data/1,
+    get_data/0,
+    get_data/1,
+    drop_data/1
+]).
+
+init_per_suite(Config) ->
+    Config.
+
+end_per_suite(_Config) ->
+    ok.
+
+all() ->
+    [
+     prepare_data,
+     get_data,
+     drop_data
+    ].
+
+%% =============================================================================
+%% Test cases
+%% =============================================================================
+
+-define(noise_size, 10000000).
+
+prepare_data() -> [{timetrap, {seconds, 60}}].
+prepare_data(Config) ->
+    with_connection(Config, fun (C) ->
+        Noise = noise(?noise_size),
+        {ok, [], []} = epgsql:squery(C, "create table test_big_blobs (id int4 primary key, noise bytea)"),
+        {ok, 1} = epgsql:equery(C, "insert into test_big_blobs (id, noise) values (1, $1)", [Noise])
+    end).
+
+get_data() -> [{timetrap, {seconds, 60}}].
+get_data(Config) ->
+    with_connection(Config, fun (C) ->
+        {ok, _, [{Noise}]} = epgsql:equery(C, "select noise from test_big_blobs"),
+        ?assertEqual(?noise_size, byte_size(Noise))
+    end).
+
+drop_data(Config) ->
+    with_connection(Config, fun (C) ->
+        {ok, [], []} = epgsql:squery(C, "drop table test_big_blobs")
+    end).
+
+%% =============================================================================
+%% Internal functions
+%% =============================================================================
+
+noise(N) ->
+    crypto:strong_rand_bytes(N).
+
+with_connection(Config, F) ->
+    with_connection(Config, F, "epgsql_test", []).
+
+with_connection(Config, F, Username, Args) ->
+    #{pg_host := Host, pg_port := Port} = ?config(pg_config, Config),
+    Args2 = [{port, Port}, {database, "epgsql_test_db1"} | Args],
+    fun () ->
+        {ok, C} = epgsql:connect(Host, Username, Args2),
+        try
+            F(C)
+        after
+            epgsql:close(C)
+        end
+    end.

+ 0 - 60
test/epgsql_perf_tests.erl

@@ -1,60 +0,0 @@
-%%%
-
--module(epgsql_perf_tests).
-
--include_lib("eunit/include/eunit.hrl").
-
-%%
-
-perf_test_() ->
-    [
-        {timeout, 60, prepare_data()},
-        {timeout, 60, get_data()}
-    ].
-
-drop_data_test_() ->
-    drop_data().
-
-%%
-
--define(noise_size, 10000000).
-
-prepare_data() ->
-    {"insert blob", with_connection(fun (C) ->
-        Noise = noise(?noise_size),
-        {ok, [], []} = epgsql:squery(C, "create table test_big_blobs (id int4 primary key, noise bytea)"),
-        {ok, 1} = epgsql:equery(C, "insert into test_big_blobs (id, noise) values (1, $1)", [Noise])
-    end)}.
-
-get_data() ->
-    {"get blob back", with_connection(fun (C) ->
-        {ok, _, [{Noise}]} = epgsql:equery(C, "select noise from test_big_blobs"),
-        ?assertEqual(?noise_size, byte_size(Noise))
-    end)}.
-
-drop_data() ->
-    {"cleanup", with_connection(fun (C) ->
-        {ok, [], []} = epgsql:squery(C, "drop table test_big_blobs")
-    end)}.
-
-noise(N) ->
-    crypto:rand_bytes(N).
-
-%%
-
--define(host, "localhost").
--define(port, 10432).
-
-with_connection(F) ->
-    with_connection(F, "epgsql_test", []).
-
-with_connection(F, Username, Args) ->
-    Args2 = [{port, ?port}, {database, "epgsql_test_db1"} | Args],
-    fun () ->
-        {ok, C} = epgsql:connect(?host, Username, Args2),
-        try
-            F(C)
-        after
-            epgsql:close(C)
-        end
-    end.

+ 49 - 59
test/epgsql_replication_tests.erl → test/epgsql_replication_SUITE.erl

@@ -1,19 +1,45 @@
--module(epgsql_replication_tests).
-
--export([run_tests/0]).
--compile([export_all]).
-
+-module(epgsql_replication_SUITE).
 -include_lib("eunit/include/eunit.hrl").
+-include_lib("common_test/include/ct.hrl").
 -include("epgsql.hrl").
 
-connect_in_repl_mode_test(Module) ->
-    epgsql_tests:connect_only(Module, ["epgsql_test_replication",
+-export([
+    init_per_suite/1,
+    all/0,
+    end_per_suite/1,
+
+    connect_in_repl_mode/1,
+    create_drop_replication_slot/1,
+    replication_sync/1,
+    replication_async/1,
+
+    %% Callbacks
+    handle_x_log_data/4
+]).
+
+init_per_suite(Config) ->
+    [{module, epgsql}|Config].
+
+end_per_suite(_Config) ->
+    ok.
+
+all() ->
+    [
+     connect_in_repl_mode,
+     create_drop_replication_slot,
+     replication_async,
+     replication_sync
+    ].
+
+connect_in_repl_mode(Config) ->
+    epgsql_ct:connect_only(Config, ["epgsql_test_replication",
         "epgsql_test_replication",
         [{database, "epgsql_test_db1"}, {replication, "database"}]]).
 
-create_drop_replication_slot_test(Module) ->
-    epgsql_tests:with_connection(
-        Module,
+create_drop_replication_slot(Config) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(
+        Config,
         fun(C) ->
             {ok, Cols, Rows} = Module:squery(C, "CREATE_REPLICATION_SLOT ""epgsql_test"" LOGICAL ""test_decoding"""),
             [#column{name = <<"slot_name">>}, #column{name = <<"consistent_point">>},
@@ -24,58 +50,22 @@ create_drop_replication_slot_test(Module) ->
         "epgsql_test_replication",
         [{replication, "database"}]).
 
-replication_async_test(Module) ->
-    replication_test_run(Module, self()).
-
-replication_sync_test(Module) ->
-    replication_test_run(Module, ?MODULE).
-
-%% -- run all tests --
-
-run_tests() ->
-    Files = filelib:wildcard(filename:dirname(code:which(epgsql_replication_tests))
-                             ++ "/*tests.beam"),
-    Mods = [list_to_atom(filename:basename(F, ".beam")) || F <- Files],
-    eunit:test(Mods, []).
-
-all_test_() ->
-    Tests =
-        lists:map(
-          fun({Name, _}) ->
-                  {Name, fun(X) -> ?MODULE:Name(X) end}
-          end,
-          lists:filter(
-            fun({Name, Arity}) ->
-                    case {lists:suffix("_test", atom_to_list(Name)), Arity} of
-                        {true, 1} -> true;
-                        _ -> false
-                    end
-            end,
-            ?MODULE:module_info(functions))),
-    WithModule =
-        fun(Module) ->
-                lists:map(
-                  fun({Name, Test}) ->
-                          {lists:flatten(
-                             io_lib:format("~s(~s)", [Name, Module])),
-                           fun() -> Test(Module) end}
-                  end,
-                  Tests)
-        end,
-    [WithModule(epgsql)
-    ].
+replication_async(Config) ->
+    replication_test_run(Config, self()).
 
-%% -- internal functions --
+replication_sync(Config) ->
+    replication_test_run(Config, ?MODULE).
 
-replication_test_run(Module, Callback) ->
-    epgsql_tests:with_connection(
-        Module,
+replication_test_run(Config, Callback) ->
+    Module = ?config(module, Config),
+    epgsql_ct:with_connection(
+        Config,
         fun(C) ->
             {ok, _, _} = Module:squery(C, "CREATE_REPLICATION_SLOT ""epgsql_test"" LOGICAL ""test_decoding"""),
 
             %% new connection because main id in a replication mode
-            epgsql_tests:with_connection(
-                Module,
+            epgsql_ct:with_connection(
+                Config,
                 fun(C2) ->
                     [{ok, 1},{ok, 1}] = Module:squery(C2,
                         "insert into test_table1 (id, value) values (5, 'five');delete from test_table1 where id = 5;")
@@ -90,8 +80,8 @@ replication_test_run(Module, Callback) ->
         "epgsql_test_replication",
         [{replication, "database"}]),
     %% cleanup
-    epgsql_tests:with_connection(
-        Module,
+    epgsql_ct:with_connection(
+        Config,
         fun(C) ->
             [{ok, _, _}, {ok, _, _}] = Module:squery(C, "DROP_REPLICATION_SLOT ""epgsql_test""")
         end,
@@ -118,4 +108,4 @@ receive_replication_msgs(Pattern, Pid, ReceivedMsgs) ->
 handle_x_log_data(StartLSN, EndLSN, Data, CbState) ->
     {C, Pid} = CbState,
     Pid ! {epgsql, C, {x_log_data, StartLSN, EndLSN, Data}},
-    {ok, EndLSN, EndLSN, CbState}.
+    {ok, EndLSN, EndLSN, CbState}.

+ 0 - 1149
test/epgsql_tests.erl

@@ -1,1149 +0,0 @@
--module(epgsql_tests).
-
--export([run_tests/0]).
--compile([export_all]).
-
--include_lib("eunit/include/eunit.hrl").
--include_lib("public_key/include/public_key.hrl").
--include("epgsql.hrl").
-
--define(host, "localhost").
--define(port, 10432).
-
--define(ssl_apps, [crypto, asn1, public_key, ssl]).
-
--define(UUID1,
-        <<163,189,240,40,149,151,17,227,141,6,112,24,139,130,16,73>>).
-
--define(UUID2,
-        <<183,55,22,52,149,151,17,227,187,167,112,24,139,130,16,73>>).
-
--define(UUID3,
-        <<198,188,155,66,149,151,17,227,138,98,112,24,139,130,16,73>>).
-
--define(TIMEOUT_ERROR, {error, #error{
-        severity = error,
-        code = <<"57014">>,
-        codename = query_canceled,
-        message = <<"canceling statement due to statement timeout">>,
-        extra = [{file, <<"postgres.c">>},
-                 {line, _},
-                 {routine, _}]
-        }}).
-
-%% From uuid.erl in http://gitorious.org/avtobiff/erlang-uuid
-uuid_to_string(<<U0:32, U1:16, U2:16, U3:16, U4:48>>) ->
-    lists:flatten(io_lib:format(
-                    "~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b",
-                    [U0, U1, U2, U3, U4])).
-
-connect_test(Module) ->
-    connect_only(Module, []).
-
-connect_to_db_test(Module) ->
-    connect_only(Module, [{database, "epgsql_test_db1"}]).
-
-connect_as_test(Module) ->
-    connect_only(Module, ["epgsql_test", [{database, "epgsql_test_db1"}]]).
-
-connect_with_cleartext_test(Module) ->
-    connect_only(Module, ["epgsql_test_cleartext",
-                  "epgsql_test_cleartext",
-                  [{database, "epgsql_test_db1"}]]).
-
-connect_with_md5_test(Module) ->
-    connect_only(Module, ["epgsql_test_md5",
-                  "epgsql_test_md5",
-                  [{database, "epgsql_test_db1"}]]).
-
-connect_with_invalid_user_test(Module) ->
-    {error, Why} =
-        Module:connect(?host,
-                      "epgsql_test_invalid",
-                      "epgsql_test_invalid",
-                      [{port, ?port}, {database, "epgsql_test_db1"}]),
-    case Why of
-        invalid_authorization_specification -> ok; % =< 8.4
-        invalid_password                    -> ok  % >= 9.0
-    end.
-
-connect_with_invalid_password_test(Module) ->
-    {error, Why} =
-        Module:connect(?host,
-                      "epgsql_test_md5",
-                      "epgsql_test_invalid",
-                      [{port, ?port}, {database, "epgsql_test_db1"}]),
-    case Why of
-        invalid_authorization_specification -> ok; % =< 8.4
-        invalid_password                    -> ok  % >= 9.0
-    end.
-
-
-connect_with_ssl_test(Module) ->
-    lists:foreach(fun application:start/1, ?ssl_apps),
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, _Cols, [{true}]} = Module:equery(C, "select ssl_is_used()")
-      end,
-      "epgsql_test",
-      [{ssl, true}]).
-
-connect_with_client_cert_test(Module) ->
-    lists:foreach(fun application:start/1, ?ssl_apps),
-    Dir = filename:join(code:lib_dir(epgsql), "test/data"),
-    File = fun(Name) -> filename:join(Dir, Name) end,
-    {ok, Pem} = file:read_file(File("epgsql.crt")),
-    [{'Certificate', Der, not_encrypted}] = public_key:pem_decode(Pem),
-    Cert = public_key:pkix_decode_cert(Der, plain),
-    #'TBSCertificate'{serialNumber = Serial} = Cert#'Certificate'.tbsCertificate,
-    Serial2 = list_to_binary(integer_to_list(Serial)),
-
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, _, [{true}]} = Module:equery(C, "select ssl_is_used()"),
-              {ok, _, [{Serial2}]} = Module:equery(C, "select ssl_client_serial()")
-      end,
-      "epgsql_test_cert",
-      [{ssl, true}, {keyfile, File("epgsql.key")}, {certfile, File("epgsql.crt")}]).
-
--ifdef(have_maps).
-connect_map_test(Module) ->
-    Opts = #{host => ?host,
-             port => ?port,
-             database => "epgsql_test_db1",
-             username => "epgsql_test_md5",
-             password => "epgsql_test_md5"
-            },
-    {ok, C} = Module:connect(Opts),
-    Module:close(C),
-    flush(),
-    ok.
--endif.
-
-prepared_query_test(Module) ->
-  with_connection(
-    Module,
-    fun(C) ->
-      {ok, _} = Module:parse(C, "inc", "select $1+1", []),
-      {ok, Cols, [{5}]} = Module:prepared_query(C, "inc", [4]),
-      {ok, Cols, [{2}]} = Module:prepared_query(C, "inc", [1]),
-      {ok, Cols, [{23}]} = Module:prepared_query(C, "inc", [22]),
-      {error, _} = Module:prepared_query(C, "non_existent_query", [4])
-    end).
-
-
-select_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, Cols, Rows} = Module:squery(C, "select * from test_table1"),
-              [#column{name = <<"id">>, type = int4, size = 4},
-               #column{name = <<"value">>, type = text, size = -1}] = Cols,
-              [{<<"1">>, <<"one">>}, {<<"2">>, <<"two">>}] = Rows
-      end).
-
-insert_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              {ok, 1} = Module:squery(C, "insert into test_table1 (id, value) values (3, 'three')")
-      end).
-
-update_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              {ok, 1} = Module:squery(C, "insert into test_table1 (id, value) values (3, 'three')"),
-              {ok, 1} = Module:squery(C, "insert into test_table1 (id, value) values (4, 'four')"),
-              {ok, 2} = Module:squery(C, "update test_table1 set value = 'foo' where id > 2"),
-              {ok, _, [{<<"2">>}]} = Module:squery(C, "select count(*) from test_table1 where value = 'foo'")
-      end).
-
-delete_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              {ok, 1} = Module:squery(C, "insert into test_table1 (id, value) values (3, 'three')"),
-              {ok, 1} = Module:squery(C, "insert into test_table1 (id, value) values (4, 'four')"),
-              {ok, 2} = Module:squery(C, "delete from test_table1 where id > 2"),
-              {ok, _, [{<<"2">>}]} = Module:squery(C, "select count(*) from test_table1")
-      end).
-
-create_and_drop_table_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              {ok, [], []} = Module:squery(C, "create table test_table3 (id int4)"),
-              {ok, [#column{type = int4}], []} = Module:squery(C, "select * from test_table3"),
-              {ok, [], []} = Module:squery(C, "drop table test_table3")
-      end).
-
-cursor_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, [], []} = Module:squery(C, "begin"),
-              {ok, [], []} = Module:squery(C, "declare c cursor for select id from test_table1"),
-              {ok, 2} = Module:squery(C, "move forward 2 from c"),
-              {ok, 1} = Module:squery(C, "move backward 1 from c"),
-              {ok, 1, _Cols, [{<<"2">>}]} = Module:squery(C, "fetch next from c"),
-              {ok, [], []} = Module:squery(C, "close c")
-      end).
-
-multiple_result_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              [{ok, _, [{<<"1">>}]}, {ok, _, [{<<"2">>}]}] = Module:squery(C, "select 1; select 2"),
-              [{ok, _, [{<<"1">>}]}, {error, #error{}}] = Module:squery(C, "select 1; select foo;")
-      end).
-
-execute_batch_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S1} = Module:parse(C, "one", "select $1", [int4]),
-              {ok, S2} = Module:parse(C, "two", "select $1 + $2", [int4, int4]),
-              [{ok, [{1}]}, {ok, [{3}]}] =
-                  Module:execute_batch(C, [{S1, [1]}, {S2, [1, 2]}])
-      end).
-
-batch_error_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "insert into test_table1(id, value) values($1, $2)"),
-              [{ok, 1}, {error, _}] =
-                  Module:execute_batch(
-                    C,
-                    [{S, [3, "batch_error 3"]},
-                     {S, [2, "batch_error 2"]}, % duplicate key error
-                     {S, [5, "batch_error 5"]}  % won't be executed
-                    ])
-      end).
-
-single_batch_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S1} = Module:parse(C, "one", "select $1", [int4]),
-              [{ok, [{1}]}] = Module:execute_batch(C, [{S1, [1]}])
-      end).
-
-extended_select_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, Cols, Rows} = Module:equery(C, "select * from test_table1", []),
-              [#column{name = <<"id">>, type = int4, size = 4},
-               #column{name = <<"value">>, type = text, size = -1}] = Cols,
-              [{1, <<"one">>}, {2, <<"two">>}] = Rows
-      end).
-
-extended_sync_ok_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, _Cols, [{<<"one">>}]} = Module:equery(C, "select value from test_table1 where id = $1", [1]),
-              {ok, _Cols, [{<<"two">>}]} = Module:equery(C, "select value from test_table1 where id = $1", [2])
-      end).
-
-extended_sync_error_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {error, #error{}} = Module:equery(C, "select _alue from test_table1 where id = $1", [1]),
-              {ok, _Cols, [{<<"one">>}]} = Module:equery(C, "select value from test_table1 where id = $1", [1])
-      end).
-
-returning_from_insert_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              {ok, 1, _Cols, [{3}]} = Module:equery(C, "insert into test_table1 (id) values (3) returning id")
-      end).
-
-returning_from_update_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              {ok, 2, _Cols, [{1}, {2}]} = Module:equery(C, "update test_table1 set value = 'hi' returning id"),
-              ?assertMatch({ok, 0, [#column{}], []},
-                           Module:equery(C, "update test_table1 set value = 'hi' where false returning id")),
-              ?assertMatch([{ok, 2, [#column{}], [{<<"1">>}, {<<"2">>}]},
-                            {ok, 0, [#column{}], []}],
-                           Module:squery(C,
-                                         "update test_table1 set value = 'hi2' returning id; "
-                                         "update test_table1 set value = 'hi' where false returning id"))
-      end).
-
-returning_from_delete_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              {ok, 2, _Cols, [{1}, {2}]} = Module:equery(C, "delete from test_table1 returning id"),
-              ?assertMatch({ok, 0, [#column{}], []},
-                           Module:equery(C, "delete from test_table1 returning id"))
-      end).
-
-parse_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "select * from test_table1"),
-              [#column{name = <<"id">>}, #column{name = <<"value">>}] = S#statement.columns,
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-parse_column_format_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "select 1::int4, false::bool, 2.0::float4"),
-              [#column{type = int4},
-               #column{type = bool},
-               #column{type = float4}] = S#statement.columns,
-              ok = Module:bind(C, S, []),
-              {ok, [{1, false, 2.0}]} = Module:execute(C, S, 0),
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-parse_error_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {error, #error{extra = [{file, _},
-                                      {line, _},
-                                      {position, <<"8">>},
-                                      {routine, _}]}} = Module:parse(C, "select _ from test_table1"),
-              {ok, S} = Module:parse(C, "select * from test_table1"),
-              [#column{name = <<"id">>}, #column{name = <<"value">>}] = S#statement.columns,
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-parse_and_close_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              Parse = fun() -> Module:parse(C, "test", "select * from test_table1", []) end,
-              {ok, S} = Parse(),
-              {error, #error{code = <<"42P05">>, codename = duplicate_prepared_statement}} = Parse(),
-              Module:close(C, S),
-              {ok, S} = Parse(),
-              ok = Module:sync(C)
-      end).
-
-bind_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "select value from test_table1 where id = $1"),
-              ok = Module:bind(C, S, [1]),
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-bind_parameter_format_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "select $1, $2, $3", [int2, text, bool]),
-              [int2, text, bool] = S#statement.types,
-              ok = Module:bind(C, S, [1, "hi", true]),
-              {ok, [{1, <<"hi">>, true}]} = Module:execute(C, S, 0),
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-bind_error_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "select $1::char"),
-              {error, #error{}} = Module:bind(C, S, [0]),
-              ok = Module:bind(C, S, [$A]),
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-bind_and_close_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "select * from test_table1"),
-              ok = Module:bind(C, S, "one", []),
-              {error, #error{code = <<"42P03">>, codename = duplicate_cursor}} = Module:bind(C, S, "one", []),
-              ok = Module:close(C, portal, "one"),
-              ok = Module:bind(C, S, "one", []),
-              ok = Module:sync(C)
-      end).
-
-execute_error_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-          {ok, S} = Module:parse(C, "insert into test_table1 (id, value) values ($1, $2)"),
-          ok = Module:bind(C, S, [1, <<"foo">>]),
-          {error, #error{code = <<"23505">>, codename = unique_violation,
-                         extra = [{constraint_name, <<"test_table1_pkey">>},
-                                  {detail, _},
-                                  {file, _},
-                                  {line, _},
-                                  {routine, _},
-                                  {schema_name, <<"public">>},
-                                  {table_name, <<"test_table1">>}]}} = Module:execute(C, S, 0),
-          {error, sync_required} = Module:bind(C, S, [3, <<"quux">>]),
-          ok = Module:sync(C),
-          ok = Module:bind(C, S, [3, <<"quux">>]),
-          {ok, _} = Module:execute(C, S, 0),
-          {ok, 1} = Module:squery(C, "delete from test_table1 where id = 3")
-      end).
-
-describe_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "select * from test_table1"),
-              [#column{name = <<"id">>}, #column{name = <<"value">>}] = S#statement.columns,
-              {ok, S} = Module:describe(C, S),
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-describe_with_param_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "select id from test_table1 where id = $1"),
-              [int4] = S#statement.types,
-              [#column{name = <<"id">>}] = S#statement.columns,
-              {ok, S} = Module:describe(C, S),
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-describe_named_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "name", "select * from test_table1", []),
-              [#column{name = <<"id">>}, #column{name = <<"value">>}] = S#statement.columns,
-              {ok, S} = Module:describe(C, S),
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-describe_error_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {error, #error{}} = Module:describe(C, statement, ""),
-              {ok, S} = Module:parse(C, "select * from test_table1"),
-              {ok, S} = Module:describe(C, statement, ""),
-              ok = Module:sync(C)
-
-      end).
-
-portal_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "select value from test_table1"),
-              ok = Module:bind(C, S, []),
-              {partial, [{<<"one">>}]} = Module:execute(C, S, 1),
-              {partial, [{<<"two">>}]} = Module:execute(C, S, 1),
-              {ok, []} = Module:execute(C, S,1),
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-returning_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "update test_table1 set value = $1 returning id"),
-              ok = Module:bind(C, S, ["foo"]),
-              {ok, 2, [{1}, {2}]} = Module:execute(C, S),
-              ok = Module:sync(C)
-      end).
-
-multiple_statement_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S1} = Module:parse(C, "one", "select value from test_table1 where id = 1", []),
-              ok = Module:bind(C, S1, []),
-              {partial, [{<<"one">>}]} = Module:execute(C, S1, 1),
-              {ok, S2} = Module:parse(C, "two", "select value from test_table1 where id = 2", []),
-              ok = Module:bind(C, S2, []),
-              {partial, [{<<"two">>}]} = Module:execute(C, S2, 1),
-              {ok, []} = Module:execute(C, S1, 1),
-              {ok, []} = Module:execute(C, S2, 1),
-              ok = Module:close(C, S1),
-              ok = Module:close(C, S2),
-              ok = Module:sync(C)
-      end).
-
-multiple_portal_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, S} = Module:parse(C, "select value from test_table1 where id = $1"),
-              ok = Module:bind(C, S, "one", [1]),
-              ok = Module:bind(C, S, "two", [2]),
-              {ok, [{<<"one">>}]} = Module:execute(C, S, "one", 0),
-              {ok, [{<<"two">>}]} = Module:execute(C, S, "two", 0),
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end).
-
-execute_function_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              {ok, _Cols1, [{3}]} = Module:equery(C, "select insert_test1(3, 'three')"),
-              {ok, _Cols2, [{<<>>}]} = Module:equery(C, "select do_nothing()")
-      end).
-
-parameter_get_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, <<"off">>} = Module:get_parameter(C, "is_superuser")
-      end).
-
-parameter_set_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, [], []} = Module:squery(C, "set DateStyle = 'ISO, MDY'"),
-              {ok, <<"ISO, MDY">>} = Module:get_parameter(C, "DateStyle"),
-              {ok, _Cols, [{<<"2000-01-02">>}]} = Module:squery(C, "select '2000-01-02'::date"),
-              {ok, [], []} = Module:squery(C, "set DateStyle = 'German'"),
-              {ok, <<"German, DMY">>} = Module:get_parameter(C, "DateStyle"),
-              {ok, _Cols, [{<<"02.01.2000">>}]} = Module:squery(C, "select '2000-01-02'::date")
-      end).
-
-numeric_type_test(Module) ->
-    check_type(Module, int2, "1", 1, [0, 256, -32768, +32767]),
-    check_type(Module, int4, "1", 1, [0, 512, -2147483648, +2147483647]),
-    check_type(Module, int8, "1", 1, [0, 1024, -9223372036854775808, +9223372036854775807]),
-    check_type(Module, float4, "1.0", 1.0, [0.0, 1.23456, -1.23456]),
-    check_type(Module, float8, "1.0", 1.0, [0.0, 1.23456789012345, -1.23456789012345]).
-
-character_type_test(Module) ->
-    Alpha = unicode:characters_to_binary([16#03B1]),
-    Ka    = unicode:characters_to_binary([16#304B]),
-    One   = unicode:characters_to_binary([16#10D360]),
-    check_type(Module, bpchar, "'A'", $A, [1, $1, 16#7F, Alpha, Ka, One], "c_char"),
-    check_type(Module, text, "'hi'", <<"hi">>, [<<"">>, <<"hi">>]),
-    check_type(Module, varchar, "'hi'", <<"hi">>, [<<"">>, <<"hi">>]).
-
-uuid_type_test(Module) ->
-    check_type(Module, uuid,
-               io_lib:format("'~s'", [uuid_to_string(?UUID1)]),
-               list_to_binary(uuid_to_string(?UUID1)), []).
-
-point_type_test(Module) ->
-    check_type(Module, point, "'(23.15, 100)'", {23.15, 100.0}, []).
-
-geometry_type_test(Module) ->
-    check_type(Module, geometry, "'COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1))'",
-      {compound_curve,'2d',
-          [{circular_string,'2d',
-              [{point,'2d',0.0,0.0,undefined,undefined},
-               {point,'2d',1.0,1.0,undefined,undefined},
-               {point,'2d',1.0,0.0,undefined,undefined}]},
-         {line_string,'2d',
-              [{point,'2d',1.0,0.0,undefined,undefined},
-                 {point,'2d',0.0,1.0,undefined,undefined}]}]},
-      []).
-
-uuid_select_test(Module) ->
-    with_rollback(
-      Module,
-      fun(C) ->
-              U1 = uuid_to_string(?UUID1),
-              U2 = uuid_to_string(?UUID2),
-              U3 = uuid_to_string(?UUID3),
-              {ok, 1} =
-                  Module:equery(C, "insert into test_table2 (c_varchar, c_uuid) values ('UUID1', $1)",
-                                [U1]),
-              {ok, 1} =
-                  Module:equery(C, "insert into test_table2 (c_varchar, c_uuid) values ('UUID2', $1)",
-                                [U2]),
-              {ok, 1} =
-                  Module:equery(C, "insert into test_table2 (c_varchar, c_uuid) values ('UUID3', $1)",
-                                [U3]),
-              Res = Module:equery(C, "select c_varchar, c_uuid from test_table2 where c_uuid = any($1)",
-                                 [[U1, U2]]),
-              U1Bin = list_to_binary(U1),
-              U2Bin = list_to_binary(U2),
-              {ok,[{column,<<"c_varchar">>,varchar,_,_,_},
-                   {column,<<"c_uuid">>,uuid,_,_,_}],
-               [{<<"UUID1">>, U1Bin},
-                {<<"UUID2">>, U2Bin}]} = Res
-      end).
-
-
-date_time_type_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              case Module:get_parameter(C, "integer_datetimes") of
-                  {ok, <<"on">>}  -> MaxTsDate = 294276;
-                  {ok, <<"off">>} -> MaxTsDate = 5874897
-              end,
-
-              check_type(Module, date, "'2008-01-02'", {2008,1,2}, [{-4712,1,1}, {5874897,1,1}]),
-              check_type(Module, time, "'00:01:02'", {0,1,2.0}, [{0,0,0.0}, {24,0,0.0}]),
-              check_type(Module, timetz, "'00:01:02-01'", {{0,1,2.0},1*60*60},
-                         [{{0,0,0.0},0}, {{24,0,0.0},-13*60*60}]),
-              check_type(Module, timestamp, "'2008-01-02 03:04:05'", {{2008,1,2},{3,4,5.0}},
-                         [{{-4712,1,1},{0,0,0.0}}, {{MaxTsDate,12,31}, {23,59,59.0}}, {1322,334285,440966}]),
-              check_type(Module, timestamptz, "'2011-01-02 03:04:05+3'", {{2011, 1, 2}, {0, 4, 5.0}}, [{1324,875970,286983}]),
-              check_type(Module, interval, "'1 hour 2 minutes 3.1 seconds'", {{1,2,3.1},0,0},
-                         [{{0,0,0.0},0,-178000000 * 12}, {{0,0,0.0},0,178000000 * 12}])
-      end).
-
-json_type_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              check_type(Module, json, "'{}'", <<"{}">>,
-                         [<<"{}">>, <<"[]">>, <<"1">>, <<"1.0">>, <<"true">>, <<"\"string\"">>, <<"{\"key\": []}">>]),
-              check_type(Module, jsonb, "'{}'", <<"{}">>,
-                         [<<"{}">>, <<"[]">>, <<"1">>, <<"1.0">>, <<"true">>, <<"\"string\"">>, <<"{\"key\": []}">>])
-      end
-    ).
-
-misc_type_test(Module) ->
-    check_type(Module, bool, "true", true, [true, false]),
-    check_type(Module, bytea, "E'\001\002'", <<1,2>>, [<<>>, <<0,128,255>>]).
-
-hstore_type_test(Module) ->
-    Values = [
-        {[]},
-        {[{null, null}]},
-        {[{null, undefined}]},
-        {[{1, null}]},
-        {[{1.0, null}]},
-        {[{1, undefined}]},
-        {[{1.0, undefined}]},
-        {[{<<"a">>, <<"c">>}, {<<"c">>, <<"d">>}]},
-        {[{<<"a">>, <<"c">>}, {<<"c">>, null}]},
-        {[{<<"a">>, <<"c">>}, {<<"c">>, undefined}]}
-    ],
-    with_connection(
-      Module,
-      fun(_C) ->
-              check_type(Module, hstore, "''", {[]}, []),
-              check_type(Module, hstore,
-                         "'a => 1, b => 2.0, c => null'",
-                         {[{<<"c">>, null}, {<<"b">>, <<"2.0">>}, {<<"a">>, <<"1">>}]}, Values)
-      end).
-
-net_type_test(Module) ->
-    check_type(Module, cidr, "'127.0.0.1/32'", {{127,0,0,1}, 32}, [{{127,0,0,1}, 32}, {{0,0,0,0,0,0,0,1}, 128}]),
-    check_type(Module, inet, "'127.0.0.1'", {127,0,0,1}, [{127,0,0,1}, {0,0,0,0,0,0,0,1}]).
-
-array_type_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-          {ok, _, [{[1, 2]}]} = Module:equery(C, "select ($1::int[])[1:2]", [[1, 2, 3]]),
-          {ok, _, [{[{1, <<"one">>}, {2, <<"two">>}]}]} =
-              Module:equery(C, "select Array(select (id, value) from test_table1)", []),
-          Select = fun(Type, A) ->
-                       Query = "select $1::" ++ atom_to_list(Type) ++ "[]",
-                       {ok, _Cols, [{A2}]} = Module:equery(C, Query, [A]),
-                       case lists:all(fun({V, V2}) -> compare(Type, V, V2) end, lists:zip(A, A2)) of
-                           true  -> ok;
-                           false -> ?assertMatch(A, A2)
-                       end
-                   end,
-          Select(int2,   []),
-          Select(int2,   [1, 2, 3, 4]),
-          Select(int2,   [[1], [2], [3], [4]]),
-          Select(int2,   [[[[[[1, 2]]]]]]),
-          Select(bool,   [true]),
-          Select(char,   [$a, $b, $c]),
-          Select(int4,   [[1, 2]]),
-          Select(int8,   [[[[1, 2]], [[3, 4]]]]),
-          Select(text,   [<<"one">>, <<"two>">>]),
-          Select(varchar,   [<<"one">>, <<"two>">>]),
-          Select(float4, [0.0, 1.0, 0.123]),
-          Select(float8, [0.0, 1.0, 0.123]),
-          Select(date, [{2008,1,2}, {2008,1,3}]),
-          Select(time, [{0,1,2.0}, {0,1,3.0}]),
-          Select(timetz, [{{0,1,2.0},1*60*60}, {{0,1,3.0},1*60*60}]),
-          Select(timestamp, [{{2008,1,2},{3,4,5.0}}, {{2008,1,2},{3,4,6.0}}]),
-          Select(timestamptz, [{{2008,1,2},{3,4,5.0}}, {{2008,1,2},{3,4,6.0}}]),
-          Select(interval, [{{1,2,3.1},0,0}, {{1,2,3.2},0,0}]),
-          Select(hstore, [{[{null, null}, {a, 1}, {1, 2}, {b, undefined}]}]),
-          Select(hstore, [[{[{null, null}, {a, 1}, {1, 2}, {b, undefined}]}, {[]}], [{[{a, 1}]}, {[{null, 2}]}]]),
-          Select(cidr, [{{127,0,0,1}, 32}, {{0,0,0,0,0,0,0,1}, 128}]),
-          Select(inet, [{127,0,0,1}, {0,0,0,0,0,0,0,1}]),
-          Select(json, [<<"{}">>, <<"[]">>, <<"1">>, <<"1.0">>, <<"true">>, <<"\"string\"">>, <<"{\"key\": []}">>]),
-          Select(jsonb, [<<"{}">>, <<"[]">>, <<"1">>, <<"1.0">>, <<"true">>, <<"\"string\"">>, <<"{\"key\": []}">>])
-      end).
-
-custom_types_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              Module:squery(C, "drop table if exists t_foo;"),
-              Module:squery(C, "drop type foo;"),
-              {ok, [], []} = Module:squery(C, "create type foo as enum('foo', 'bar');"),
-              ok = epgsql:update_type_cache(C, [<<"foo">>]),
-              {ok, [], []} = Module:squery(C, "create table t_foo (col foo);"),
-              {ok, S} = Module:parse(C, "insert_foo", "insert into t_foo values ($1)", [foo]),
-              ok = Module:bind(C, S, ["bar"]),
-              {ok, 1} = Module:execute(C, S)
-
-      end).
-
-text_format_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              Select = fun(Type, V) ->
-                               V2 = list_to_binary(V),
-                               Query = "select $1::" ++ Type,
-                               {ok, _Cols, [{V2}]} = Module:equery(C, Query, [V]),
-                               {ok, _Cols, [{V2}]} = Module:equery(C, Query, [V2])
-                       end,
-              Select("numeric", "123456")
-      end).
-
-query_timeout_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, _, _} = Module:squery(C, "SET statement_timeout = 500"),
-              ?TIMEOUT_ERROR = Module:squery(C, "SELECT pg_sleep(1)"),
-              ?TIMEOUT_ERROR = Module:equery(C, "SELECT pg_sleep(2)"),
-              {ok, _Cols, [{1}]} = Module:equery(C, "SELECT 1")
-      end,
-      []).
-
-execute_timeout_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, _, _} = Module:squery(C, "SET statement_timeout = 500"),
-              {ok, S} = Module:parse(C, "select pg_sleep($1)"),
-              ok = Module:bind(C, S, [2]),
-              ?TIMEOUT_ERROR = Module:execute(C, S, 0),
-              ok = Module:sync(C),
-              ok = Module:bind(C, S, [0]),
-              {ok, [{<<>>}]} = Module:execute(C, S, 0),
-              ok = Module:close(C, S),
-              ok = Module:sync(C)
-      end,
-      []).
-
-connection_closed_test(Module) ->
-    P = self(),
-    F = fun() ->
-                process_flag(trap_exit, true),
-                {ok, C} = Module:connect(?host, "epgsql_test",
-                                         [{port, ?port}, {database, "epgsql_test_db1"}]),
-                P ! {connected, C},
-                receive
-                    Any -> P ! Any
-                end
-        end,
-    spawn_link(F),
-    receive
-        {connected, C} ->
-            timer:sleep(100),
-            Module:close(C),
-            {'EXIT', C, _} = receive R -> R end
-    end,
-    flush().
-
-connection_closed_by_server_test(Module) ->
-    with_connection(Module,
-        fun(C1) ->
-            P = self(),
-            spawn_link(fun() ->
-                process_flag(trap_exit, true),
-                with_connection(Module,
-                    fun(C2) ->
-                        {ok, _, [{Pid}]} = Module:equery(C2, "select pg_backend_pid()"),
-                        % emulate of disconnection
-                        {ok, _, [{true}]} = Module:equery(C1,
-                            "select pg_terminate_backend($1)", [Pid]),
-                        receive
-                            {'EXIT', C2, {shutdown, #error{code = <<"57P01">>}}} ->
-                                P ! ok;
-                            Other ->
-                                ?debugFmt("Unexpected msg: ~p~n", [Other]),
-                                P ! error
-                        end
-                    end)
-            end),
-            receive ok -> ok end
-        end).
-
-active_connection_closed_test(Module) ->
-    P = self(),
-    F = fun() ->
-                process_flag(trap_exit, true),
-                {ok, C} = Module:connect(?host, [{database,
-                                                  "epgsql_test_db1"}, {port, ?port}]),
-                P ! {connected, C},
-                R = Module:squery(C, "select pg_sleep(10)"),
-                P ! R
-        end,
-    spawn_link(F),
-    receive
-        {connected, C} ->
-            timer:sleep(100),
-            Module:close(C),
-            {error, closed} = receive R -> R end
-    end,
-    flush().
-
-warning_notice_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-          Q = "create function pg_temp.raise() returns void as $$
-               begin
-                 raise warning 'oops';
-               end;
-               $$ language plpgsql;
-               select pg_temp.raise()",
-          [{ok, _, _}, _] = Module:squery(C, Q),
-          receive
-              {epgsql, C, {notice, #error{message = <<"oops">>, extra = Extra}}} ->
-                  ?assertMatch([{file, _},
-                                {line, _},
-                                {routine, _}], Extra),
-                  ok
-          after
-              100 -> erlang:error(didnt_receive_notice)
-          end
-      end,
-      [{async, self()}]).
-
-listen_notify_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-          {ok, [], []}     = Module:squery(C, "listen epgsql_test"),
-          {ok, _, [{Pid}]} = Module:equery(C, "select pg_backend_pid()"),
-          {ok, [], []}     = Module:squery(C, "notify epgsql_test"),
-          receive
-              {epgsql, C, {notification, <<"epgsql_test">>, Pid, <<>>}} -> ok
-          after
-              100 -> erlang:error(didnt_receive_notification)
-          end
-      end,
-      [{async, self()}]).
-
-listen_notify_payload_test(Module) ->
-    with_min_version(
-      Module,
-      9.0,
-      fun(C) ->
-          {ok, [], []}     = Module:squery(C, "listen epgsql_test"),
-          {ok, _, [{Pid}]} = Module:equery(C, "select pg_backend_pid()"),
-          {ok, [], []}     = Module:squery(C, "notify epgsql_test, 'test!'"),
-          receive
-              {epgsql, C, {notification, <<"epgsql_test">>, Pid, <<"test!">>}} -> ok
-          after
-              100 -> erlang:error(didnt_receive_notification)
-          end
-      end,
-      [{async, self()}]).
-
-set_notice_receiver_test(Module) ->
-    with_min_version(
-      Module,
-      9.0,
-      fun(C) ->
-          {ok, [], []}     = Module:squery(C, "listen epgsql_test"),
-          {ok, _, [{Pid}]} = Module:equery(C, "select pg_backend_pid()"),
-
-          EnsureNoNotification = fun(Payload) ->
-              {ok, [], []}     = Module:squery(C, ["notify epgsql_test, '", Payload, "'"]),
-              receive
-                  {epgsql, _, _} -> erlang:error(got_unexpected_notification)
-              after
-                  10 -> ok
-              end
-          end,
-          EnsureNotification = fun(Payload) ->
-              {ok, [], []}     = Module:squery(C, ["notify epgsql_test, '", Payload, "'"]),
-              receive
-                  {epgsql, C, {notification, <<"epgsql_test">>, Pid, Payload}} -> ok
-              after
-                  100 -> erlang:error(didnt_receive_notification)
-              end
-          end,
-          Self = self(),
-
-          EnsureNoNotification(<<"test1">>),
-
-          % Set pid()
-          {ok, undefined} = Module:set_notice_receiver(C, Self),
-          EnsureNotification(<<"test2">>),
-
-          %% test PL/PgSQL NOTICE
-          {ok, [], []} = Module:squery(
-                           C, ["DO $$ BEGIN RAISE WARNING 'test notice'; END $$;"]),
-          receive
-              {epgsql, C, {notice, #error{severity = warning,
-                                          code = <<"01000">>,
-                                          message = <<"test notice">>,
-                                          extra = _}}} -> ok
-          after
-              100 -> erlang:error(didnt_receive_notice)
-          end,
-
-          % set registered pid
-          Receiver = pg_notification_receiver,
-          register(Receiver, Self),
-          {ok, Self} = Module:set_notice_receiver(C, Receiver),
-          EnsureNotification(<<"test3">>),
-
-          % make registered name invalid
-          unregister(Receiver),
-          EnsureNoNotification(<<"test4">>),
-
-          % disable
-          {ok, Receiver} = Module:set_notice_receiver(C, undefined),
-          EnsureNoNotification(<<"test5">>)
-      end,
-      []).
-
-get_cmd_status_test(Module) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              {ok, [], []} = Module:squery(C, "BEGIN"),
-              ?assertEqual({ok, 'begin'}, Module:get_cmd_status(C)),
-              {ok, [], []} = epgsql:equery(C, "CREATE TEMPORARY TABLE cmd_status_t(col INTEGER)"),
-              ?assertEqual({ok, 'create'}, Module:get_cmd_status(C)),
-              %% Some commands have number of affected rows in status
-              {ok, N} = Module:squery(C, "INSERT INTO cmd_status_t (col) VALUES (1), (2)"),
-              ?assertEqual({ok, {'insert', N}}, Module:get_cmd_status(C)),
-              {ok, 1} = Module:squery(C, "UPDATE cmd_status_t SET col=3 WHERE col=1"),
-              ?assertEqual({ok, {'update', 1}}, Module:get_cmd_status(C)),
-              %% Failed queries have no status
-              {error, _} = Module:squery(C, "UPDATE cmd_status_t SET col='text' WHERE col=2"),
-              ?assertEqual({ok, undefined}, Module:get_cmd_status(C)),
-              %% if COMMIT failed, status will be 'rollback'
-              {ok, [], []} = Module:squery(C, "COMMIT"),
-              ?assertEqual({ok, 'rollback'}, Module:get_cmd_status(C)),
-              %% Only last command's status returned
-              [_, _, _] = Module:squery(C, "BEGIN; SELECT 1; COMMIT"),
-              ?assertEqual({ok, 'commit'}, Module:get_cmd_status(C))
-      end).
-
-application_test(_Module) ->
-    lists:foreach(fun application:start/1, ?ssl_apps),
-    ok = application:start(epgsql),
-    ok = application:stop(epgsql).
-
-range_type_test(Module) ->
-    with_min_version(
-      Module,
-      9.2,
-      fun(_C) ->
-          check_type(Module, int4range, "int4range(10, 20)", {10, 20},
-                     [{1, 58}, {-1, 12}, {-985521, 5412687}, {minus_infinity, 0},
-                      {984655, plus_infinity}, {minus_infinity, plus_infinity}])
-      end,
-      []).
-
-range8_type_test(Module) ->
-    with_min_version(
-      Module,
-      9.2,
-      fun(_C) ->
-          check_type(Module, int8range, "int8range(10, 20)", {10, 20},
-                     [{1, 58}, {-1, 12}, {-9223372036854775808, 5412687},
-                      {minus_infinity, 9223372036854775807},
-                      {984655, plus_infinity}, {minus_infinity, plus_infinity}])
-      end,
-      []).
-
-%% -- run all tests --
-
-run_tests() ->
-    Files = filelib:wildcard(filename:dirname(code:which(epgsql_tests))
-                             ++ "/*tests.beam"),
-    Mods = [list_to_atom(filename:basename(F, ".beam")) || F <- Files],
-    eunit:test(Mods, []).
-
-all_test_() ->
-    Version =
-        erlang:list_to_binary(
-          re:replace(os:cmd("git rev-parse HEAD"), "\\s+", "")),
-
-    with_connection(
-      epgsql,
-      fun(C) ->
-              {ok, _Cols, [{DBVersion}]} = epgsql:squery(C, "SELECT version FROM schema_version"),
-              case DBVersion == Version of
-                  false ->
-                      error_logger:info_msg("Git version of test schema does not match: ~p ~p~nPlease run make create_testdbs to update your test databases", [Version, DBVersion]),
-                      erlang:exit(1);
-                  _ ->
-                      undefined
-              end
-      end),
-
-    Tests =
-        lists:map(
-          fun({Name, _}) ->
-                  {Name, fun(X) -> ?MODULE:Name(X) end}
-          end,
-          lists:filter(
-            fun({Name, Arity}) ->
-                    case {lists:suffix("_test", atom_to_list(Name)), Arity} of
-                        {true, 1} -> true;
-                        _ -> false
-                    end
-            end,
-            ?MODULE:module_info(functions))),
-    WithModule =
-        fun(Module) ->
-                lists:map(
-                  fun({Name, Test}) ->
-                          {lists:flatten(
-                             io_lib:format("~s(~s)", [Name, Module])),
-                           fun() -> Test(Module) end}
-                  end,
-                  Tests)
-        end,
-    [WithModule(epgsql),
-     WithModule(epgsql_cast),
-     WithModule(epgsql_incremental)].
-
-%% -- internal functions --
-
-connect_only(Module, Args) ->
-    TestOpts = [{port, ?port}],
-    case Args of
-        [User, Opts]       -> Args2 = [User, TestOpts ++ Opts];
-        [User, Pass, Opts] -> Args2 = [User, Pass, TestOpts ++ Opts];
-        Opts               -> Args2 = [TestOpts ++ Opts]
-    end,
-    {ok, C} = apply(Module, connect, [?host | Args2]),
-    Module:close(C),
-    flush().
-
-with_connection(Module, F) ->
-    with_connection(Module, F, "epgsql_test", []).
-
-with_connection(Module, F, Args) ->
-    with_connection(Module, F, "epgsql_test", Args).
-
-with_connection(Module, F, Username, Args) ->
-    Args2 = [{port, ?port}, {database, "epgsql_test_db1"} | Args],
-    {ok, C} = Module:connect(?host, Username, Args2),
-    try
-        F(C)
-    after
-        Module:close(C)
-    end,
-    flush().
-
-with_rollback(Module, F) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              try
-                  Module:squery(C, "begin"),
-                  F(C)
-                  after
-                      Module:squery(C, "rollback")
-                  end
-      end).
-
-with_min_version(Module, Min, F, Args) ->
-    with_connection(
-      Module,
-      fun(C) ->
-          {ok, Bin} = Module:get_parameter(C, <<"server_version">>),
-          {ok, [{float, 1, Ver} | _], _} = erl_scan:string(binary_to_list(Bin)),
-          case Ver >= Min of
-              true  -> F(C);
-              false -> ?debugFmt("skipping test requiring PostgreSQL >= ~.2f~n", [Min])
-          end
-      end,
-      Args).
-
-check_type(Module, Type, In, Out, Values) ->
-    Column = "c_" ++ atom_to_list(Type),
-    check_type(Module, Type, In, Out, Values, Column).
-
-check_type(Module, Type, In, Out, Values, Column) ->
-    with_connection(
-      Module,
-      fun(C) ->
-              Select = io_lib:format("select ~s::~w", [In, Type]),
-              Res = Module:equery(C, Select),
-              {ok, [#column{type = Type}], [{Out}]} = Res,
-              Sql = io_lib:format("insert into test_table2 (~s) values ($1) returning ~s", [Column, Column]),
-              {ok, #statement{columns = [#column{type = Type}]} = S} = Module:parse(C, Sql),
-              Insert = fun(V) ->
-                               ok = Module:bind(C, S, [V]),
-                               {ok, 1, [{V2}]} = Module:execute(C, S),
-                               case compare(Type, V, V2) of
-                                   true  -> ok;
-                                   false -> ?debugFmt("~p =/= ~p~n", [V, V2]), ?assert(false)
-                               end,
-                               ok = Module:sync(C)
-                       end,
-              lists:foreach(Insert, [null, undefined | Values])
-      end).
-
-compare(_Type, null, null)      -> true;
-compare(_Type, undefined, null) -> true;
-compare(float4, V1, V2)         -> abs(V2 - V1) < 0.000001;
-compare(float8, V1, V2)         -> abs(V2 - V1) < 0.000000000000001;
-compare(hstore, {V1}, V2)       -> compare(hstore, V1, V2);
-compare(hstore, V1, {V2})       -> compare(hstore, V1, V2);
-compare(hstore, V1, V2)         ->
-    orddict:from_list(format_hstore(V1)) =:= orddict:from_list(format_hstore(V2));
-compare(Type, V1 = {_, _, MS}, {D2, {H2, M2, S2}}) when Type == timestamp;
-                                                        Type == timestamptz ->
-    {D1, {H1, M1, S1}} = calendar:now_to_universal_time(V1),
-    ({D1, H1, M1} =:= {D2, H2, M2}) and (abs(S1 + MS/1000000 - S2) < 0.000000000000001);
-compare(_Type, V1, V2)     -> V1 =:= V2.
-
-format_hstore({Hstore}) -> Hstore;
-format_hstore(Hstore) ->
-    [{format_hstore_key(Key), format_hstore_value(Value)} || {Key, Value} <- Hstore].
-
-format_hstore_key(Key) -> format_hstore_string(Key).
-
-format_hstore_value(null) -> null;
-format_hstore_value(undefined) -> null;
-format_hstore_value(Value) -> format_hstore_string(Value).
-
-format_hstore_string(Num) when is_number(Num) -> iolist_to_binary(io_lib:format("~w", [Num]));
-format_hstore_string(Str) -> iolist_to_binary(io_lib:format("~s", [Str])).
-
-%% flush mailbox
-flush() ->
-    ?assertEqual([], flush([])).
-
-flush(Acc) ->
-    receive
-        {'EXIT', _Pid, normal} -> flush(Acc);
-        M                      -> flush([M | Acc])
-    after
-        0 -> lists:reverse(Acc)
-    end.

+ 1 - 0
test/epgsql_tests.hrl

@@ -0,0 +1 @@
+-define(TEST_DATA_DIR, "../../../../test/data").