epgsql_cth.erl 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. -module(epgsql_cth).
  2. -export([
  3. init/2,
  4. terminate/1,
  5. pre_init_per_suite/3
  6. ]).
  7. -include_lib("common_test/include/ct.hrl").
  8. -include("epgsql_tests.hrl").
  9. init(_Id, State) ->
  10. Start = os:timestamp(),
  11. PgConfig = start_postgres(),
  12. ok = create_testdbs(PgConfig),
  13. error_logger:info_msg("postgres started in ~p ms\n",
  14. [timer:now_diff(os:timestamp(), Start) / 1000]),
  15. [{pg_config, PgConfig}|State].
  16. pre_init_per_suite(_SuiteName, Config, State) ->
  17. {Config ++ State, State}.
  18. terminate(State) ->
  19. ok = stop_postgres(?config(pg_config, State)).
  20. create_testdbs(Config) ->
  21. PgHost = ?config(host, Config),
  22. PgPort = ?config(port, Config),
  23. PgUser = ?config(user, Config),
  24. Utils = ?config(utils, Config),
  25. Psql = ?config(psql, Utils),
  26. CreateDB = ?config(createdb, Utils),
  27. Opts = lists:concat([" -h ", PgHost, " -p ", PgPort, " "]),
  28. Cmds = [
  29. [CreateDB, Opts, PgUser],
  30. [Psql, Opts, "template1 < ", filename:join(?TEST_DATA_DIR, "test_schema.sql")]
  31. ],
  32. lists:foreach(fun(Cmd) ->
  33. {ok, []} = exec:run(lists:flatten(Cmd), [sync])
  34. end, Cmds).
  35. %% =============================================================================
  36. %% start postgresql
  37. %% =============================================================================
  38. -define(PG_TIMEOUT, 30000).
  39. start_postgres() ->
  40. ok = application:start(erlexec),
  41. pipe([
  42. fun find_utils/1,
  43. fun init_database/1,
  44. fun write_postgresql_config/1,
  45. fun copy_certs/1,
  46. fun write_pg_hba_config/1,
  47. fun start_postgresql/1
  48. ], []).
  49. stop_postgres(Config) ->
  50. PgProc = ?config(proc, Config),
  51. PgProc ! stop,
  52. ok.
  53. find_utils(State) ->
  54. Utils = [initdb, createdb, postgres, psql],
  55. UtilsConfig = lists:foldl(fun(U, Acc) ->
  56. UList = atom_to_list(U),
  57. Path = case os:find_executable(UList) of
  58. false ->
  59. case filelib:wildcard("/usr/lib/postgresql/**/bin/" ++ UList) of
  60. [] ->
  61. error_logger:error_msg("~s not found", [U]),
  62. throw({util_no_found, U});
  63. List -> lists:last(lists:sort(List))
  64. end;
  65. P -> P
  66. end,
  67. [{U, Path}|Acc]
  68. end, [], Utils),
  69. [{utils, UtilsConfig}|State].
  70. start_postgresql(Config) ->
  71. PgDataDir = ?config(datadir, Config),
  72. Utils = ?config(utils, Config),
  73. Postgres = ?config(postgres, Utils),
  74. PgHost = "localhost",
  75. PgPort = get_free_port(),
  76. SocketDir = "/tmp",
  77. Pid = proc_lib:spawn(fun() ->
  78. {ok, _, I} = exec:run_link(lists:concat([Postgres,
  79. " -k ", SocketDir,
  80. " -D ", PgDataDir,
  81. " -h ", PgHost,
  82. " -p ", PgPort]),
  83. [{stderr,
  84. fun(_, _, Msg) ->
  85. error_logger:info_msg("postgres: ~s", [Msg])
  86. end}]),
  87. loop(I)
  88. end),
  89. ConfigR = [
  90. {host, PgHost},
  91. {port, PgPort},
  92. {proc, Pid}
  93. | Config
  94. ],
  95. wait_postgresql_ready(SocketDir, ConfigR).
  96. loop(I) ->
  97. receive
  98. stop -> exec:kill(I, 0);
  99. _ -> loop(I)
  100. end.
  101. wait_postgresql_ready(SocketDir, Config) ->
  102. PgPort = ?config(port, Config),
  103. PgFile = lists:concat([".s.PGSQL.", PgPort]),
  104. Path = filename:join(SocketDir, PgFile),
  105. WaitUntil = ts_add(os:timestamp(), ?PG_TIMEOUT),
  106. case wait_(Path, WaitUntil) of
  107. true -> ok;
  108. false -> throw(<<"Postgresql init timeout">>)
  109. end,
  110. Config.
  111. wait_(Path, Until) ->
  112. case file:read_file_info(Path) of
  113. {error, enoent} ->
  114. case os:timestamp() > Until of
  115. true -> false;
  116. _ ->
  117. timer:sleep(300),
  118. wait_(Path, Until)
  119. end;
  120. _ -> true
  121. end.
  122. init_database(Config) ->
  123. Utils = ?config(utils, Config),
  124. Initdb = ?config(initdb, Utils),
  125. {ok, Cwd} = file:get_cwd(),
  126. PgDataDir = filename:append(Cwd, "datadir"),
  127. {ok, _} = exec:run(Initdb ++ " --locale en_US.UTF8 " ++ PgDataDir, [sync,stdout,stderr]),
  128. [{datadir, PgDataDir}|Config].
  129. write_postgresql_config(Config) ->
  130. PgDataDir = ?config(datadir, Config),
  131. PGConfig = [
  132. "ssl = on\n",
  133. "ssl_ca_file = 'root.crt'\n",
  134. "lc_messages = 'en_US.UTF-8'\n",
  135. "wal_level = 'logical'\n",
  136. "max_replication_slots = 15\n",
  137. "max_wal_senders = 15"
  138. ],
  139. FilePath = filename:join(PgDataDir, "postgresql.conf"),
  140. ok = file:write_file(FilePath, PGConfig),
  141. Config.
  142. copy_certs(Config) ->
  143. PgDataDir = ?config(datadir, Config),
  144. Files = [
  145. {"epgsql.crt", "server.crt", 8#00660},
  146. {"epgsql.key", "server.key", 8#00600},
  147. {"root.crt", "root.crt", 8#00660},
  148. {"root.key", "root.key", 8#00660}
  149. ],
  150. lists:foreach(fun({From, To, Mode}) ->
  151. FromPath = filename:join(?TEST_DATA_DIR, From),
  152. ToPath = filename:join(PgDataDir, To),
  153. {ok, _} = file:copy(FromPath, ToPath),
  154. ok = file:change_mode(ToPath, Mode)
  155. end, Files),
  156. Config.
  157. write_pg_hba_config(Config) ->
  158. PgDataDir = ?config(datadir, Config),
  159. User = os:getenv("USER"),
  160. PGConfig = [
  161. "local all ", User, " trust\n",
  162. "host template1 ", User, " 127.0.0.1/32 trust\n",
  163. "host ", User, " ", User, " 127.0.0.1/32 trust\n",
  164. "hostssl postgres ", User, " 127.0.0.1/32 trust\n",
  165. "host epgsql_test_db1 ", User, " 127.0.0.1/32 trust\n",
  166. "host epgsql_test_db1 epgsql_test 127.0.0.1/32 trust\n",
  167. "host epgsql_test_db1 epgsql_test_md5 127.0.0.1/32 md5\n",
  168. "host epgsql_test_db1 epgsql_test_cleartext 127.0.0.1/32 password\n",
  169. "hostssl epgsql_test_db1 epgsql_test_cert 127.0.0.1/32 cert clientcert=1\n",
  170. "host replication epgsql_test_replication 127.0.0.1/32 trust"
  171. ],
  172. FilePath = filename:join(PgDataDir, "pg_hba.conf"),
  173. ok = file:write_file(FilePath, PGConfig),
  174. [{user, User}|Config].
  175. %% =============================================================================
  176. %% Internal functions
  177. %% =============================================================================
  178. get_free_port() ->
  179. {ok, Listen} = gen_tcp:listen(0, []),
  180. {ok, Port} = inet:port(Listen),
  181. ok = gen_tcp:close(Listen),
  182. Port.
  183. pipe(Funs, Config) ->
  184. lists:foldl(fun(F, S) -> F(S) end, Config, Funs).
  185. ts_add({Mega, Sec, Micro}, Timeout) ->
  186. V = (Mega * 1000000 + Sec)*1000000 + Micro + Timeout * 1000,
  187. {V div 1000000000000,
  188. V div 1000000 rem 1000000,
  189. V rem 1000000}.