epgsql_cth.erl 6.4 KB

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