Skip to content

Commit

Permalink
fix reuse not working and improve mix task with watch feature (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
jarlah authored Sep 30, 2024
1 parent ebc135f commit 5e8ac62
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 29 deletions.
7 changes: 3 additions & 4 deletions lib/container/protocols/container_builder_helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,22 @@ defmodule Testcontainers.ContainerBuilderHelper do
def build(builder, state) when is_map(state) and is_struct(builder) do
config =
ContainerBuilder.build(builder)
|> Container.with_label(container_version_label(), library_version())
|> Container.with_label(container_lang_label(), container_lang_value())
|> Container.with_label(container_label(), "#{true}")

reuse = config.reuse && true == Map.get(state.properties, "testcontainers.reuse.enable", false)

if reuse do
if config.reuse && "true" == Map.get(state.properties, "testcontainers.reuse.enable", "false") do
hash = Hash.struct_to_hash(config)
config
|> Container.with_label(container_reuse(), "true")
|> Container.with_label(container_reuse_hash_label(), hash)
|> Container.with_label(container_sessionId_label(), state.session_id)
|> Container.with_label(container_version_label(), library_version())
|> Kernel.then(&{:reuse, &1, hash})
else
config
|> Container.with_label(container_reuse(), "false")
|> Container.with_label(container_sessionId_label(), state.session_id)
|> Container.with_label(container_version_label(), library_version())
|> Kernel.then(&{:noreuse, &1, nil})
end
end
Expand Down
110 changes: 91 additions & 19 deletions lib/mix/tasks/testcontainers/test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,61 @@ defmodule Mix.Tasks.Testcontainers.Test do

@shortdoc "Runs tests with a Postgres container"
def run(args) do
Application.ensure_all_started(:tesla)
Application.ensure_all_started(:hackney)
Enum.each([:tesla, :hackney, :fs, :logger], fn app ->
{:ok, _} = Application.ensure_all_started(app)
end)

{:ok, _} = Testcontainers.start_link()

{opts, _, _} = OptionParser.parse(args, switches: [
database: :string
])
{opts, _, _} = OptionParser.parse(args,
switches: [
database: :string,
watch: [:string, :keep]
]
)

database = opts[:database] || "postgres"
folder_to_watch = Keyword.get_values(opts, :watch)

if Enum.empty?(folder_to_watch) do
IO.puts("No folders specified. Only running tests.")
run_tests_and_exit(database)
else
check_folders_exist(folder_to_watch)
run_tests_and_watch(database, folder_to_watch)
end
end

defp check_folders_exist(folders) do
Enum.each(folders, fn folder ->
unless File.dir?(folder) do
Mix.raise("Folder does not exist: #{folder}")
end
end)
end

defp run_tests_and_exit(database) do
{container, env} = setup_container(database)
run_tests(env)
Testcontainers.stop_container(container.container_id)
end

defp run_tests_and_watch(database, folders) do
{container, env} = setup_container(database)

Enum.each(folders, fn folder ->
:fs.start_link(String.to_atom("watcher_" <> folder), Path.absname(folder))
:fs.subscribe(String.to_atom("watcher_" <> folder))
end)

Process.flag(:trap_exit, true)

run_tests(env)
loop(env, container)
end

{container, port} = case database do
defp setup_container(database) do
case database do
"postgres" ->
{:ok, container} = Testcontainers.start_container(
PostgresContainer.new()
Expand All @@ -24,7 +68,7 @@ defmodule Mix.Tasks.Testcontainers.Test do
|> PostgresContainer.with_reuse(true)
)
port = PostgresContainer.port(container)
{container, port}
{container, create_env(port)}
"mysql" ->
{:ok, container} = Testcontainers.start_container(
MySqlContainer.new()
Expand All @@ -33,26 +77,54 @@ defmodule Mix.Tasks.Testcontainers.Test do
|> MySqlContainer.with_reuse(true)
)
port = MySqlContainer.port(container)
{container, port}
{container, create_env(port)}
_ -> Mix.raise("Unsupported database: #{database}")
end
end

env = [
defp create_env(port) do
[
{"DB_USER", "test"},
{"DB_PASSWORD", "test"},
{"DB_HOST", Testcontainers.get_host()},
{"DB_PORT", port |> Integer.to_string()}
{"DB_PORT", Integer.to_string(port)}
]
end

try do
{output, exit_code} = System.cmd("mix", ["test"], env: env)
if exit_code != 0 do
IO.puts(output)
raise "\u274c Tests failed"
end
IO.puts("\u2705 Tests passed")
after
Testcontainers.stop_container(container.container_id)
defp run_tests(env) do
test_pid = spawn(fn ->
System.cmd("mix", ["test"], env: env, into: IO.stream(:stdio, :line))
end)

Process.monitor(test_pid)

receive do
{:DOWN, _ref, :process, ^test_pid, _reason} ->
IO.puts("Test process completed.")
end
end

defp loop(env, container) do
receive do
{_watcher_process, {:fs, :file_event}, {changed_file, _type}} ->
IO.puts("#{changed_file} was updated, waiting for more changes...")
wait_for_changes(env, container)

after 5000 ->
loop(env, container)
end
end

defp wait_for_changes(env, container) do
receive do
{_watcher_process, {:fs, :file_event}, {changed_file, _type}} ->
IO.puts("#{changed_file} was updated, waiting for more changes...")
wait_for_changes(env, container)

after 1000 ->
IO.ANSI.clear()
run_tests(env)
loop(env, container)
end
end
end
1 change: 0 additions & 1 deletion lib/testcontainers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ defmodule Testcontainers do
alias Testcontainers.Connection
alias Testcontainers.Container
alias Testcontainers.ContainerBuilder
alias Testcontainers.Util.Hash
alias Testcontainers.Util.PropertiesParser

import Testcontainers.Constants
Expand Down
2 changes: 1 addition & 1 deletion lib/util/constants.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Testcontainers.Constants do
@moduledoc false

def library_name, do: :testcontainers
def library_version, do: "1.10.3"
def library_version, do: "1.10.4"
def container_label, do: "org.testcontainers"
def container_lang_label, do: "org.testcontainers.lang"
def container_reuse_hash_label, do: "org.testcontainers.reuse-hash"
Expand Down
8 changes: 5 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule TestcontainersElixir.MixProject do
use Mix.Project

@app :testcontainers
@version "1.10.3"
@version "1.10.4"
@source_url "https://github.com/testcontainers/testcontainers-elixir"

def project do
Expand All @@ -22,7 +22,7 @@ defmodule TestcontainersElixir.MixProject do
licenses: ["MIT"]
],
test_coverage: [
summary: [threshold: 68],
summary: [threshold: 60],
ignore_modules: [
TestHelper,
Inspect.Testcontainers.TestUser,
Expand Down Expand Up @@ -86,7 +86,9 @@ defmodule TestcontainersElixir.MixProject do
# RabbitMQ
{:amqp, "~> 3.3", only: [:dev, :test]},
# EMQX
{:tortoise311, "~> 0.12.0", only: [:dev, :test]}
{:tortoise311, "~> 0.12.0", only: [:dev, :test]},
# For watching directories for file changes in mix task
{:fs, "~> 8.6"}
]
end

Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
"ex_docker_engine_api": {:hex, :ex_docker_engine_api, "1.43.1", "1161e34b6bea5cef84d8fdc1d5d510fcb0c463941ce84c36f4a0f44a9096eb96", [:mix], [{:hackney, "~> 1.20", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.7", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "ec8fc499389aeef56ddca67e89e9e98098cff50587b56e8b4613279f382793b1"},
"exmqtt": {:git, "https://github.com/ryanwinchester/exmqtt.git", "b6da7d412b1e7fe8c78f5e8de1895c990e34ff67", [branch: "master"]},
"fs": {:hex, :fs, "8.6.1", "7c9c0d0211e8c520e4e9eda63b960605c2711839f47285e6166c332d973be8ea", [:rebar3], [], "hexpm", "61ea2bdaedae4e2024d0d25c63e44dccf65622d4402db4a2df12868d1546503f"},
"gen_state_machine": {:hex, :gen_state_machine, "3.0.0", "1e57f86a494e5c6b14137ebef26a7eb342b3b0070c7135f2d6768ed3f6b6cdff", [:mix], [], "hexpm", "0a59652574bebceb7309f6b749d2a41b45fdeda8dbb4da0791e355dd19f0ed15"},
"getopt": {:hex, :getopt, "1.0.2", "33d9b44289fe7ad08627ddfe1d798e30b2da0033b51da1b3a2d64e72cd581d02", [:rebar3], [], "hexpm", "a0029aea4322fb82a61f6876a6d9c66dc9878b6cb61faa13df3187384fd4ea26"},
"gun": {:hex, :gun, "1.3.3", "cf8b51beb36c22b9c8df1921e3f2bc4d2b1f68b49ad4fbc64e91875aa14e16b4", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "3106ce167f9c9723f849e4fb54ea4a4d814e3996ae243a1c828b256e749041e0"},
Expand All @@ -34,6 +35,7 @@
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mix_rebar3": {:hex, :mix_rebar3, "0.2.0", "b33656ef3047f21a19fac3254cb30a1d2c75ea419a3ad28c4b88f42c62a4202d", [:mix], [], "hexpm", "11eabb70c0a7ead9aa3631f048c3d7d5e868172b87b6493d0dc6f6d591c1afae"},
"myxql": {:hex, :myxql, "0.6.4", "1502ea37ee23c31b79725b95d4cc3553693c2bda7421b1febc50722fd988c918", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a3307f4671f3009d3708283649adf205bfe280f7e036fc8ef7f16dbf821ab8e9"},
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
Expand Down
2 changes: 1 addition & 1 deletion test/container/container_builder_helper_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ defmodule Testcontainers.ContainerBuilderHelperTest do

test "build/2 returns a tuple with true, built config with correct labels and a non nil hash" do
builder = Testcontainers.PostgresContainer.new() |> Testcontainers.PostgresContainer.with_reuse(true)
state = %{ properties: %{ "testcontainers.reuse.enable" => true }, session_id: "123" }
state = %{ properties: %{ "testcontainers.reuse.enable" => "true" }, session_id: "123" }
{:reuse, built, hash} = ContainerBuilderHelper.build(builder, state)
assert hash != nil
assert Map.get(built.labels, container_reuse()) == "true"
Expand Down

0 comments on commit 5e8ac62

Please sign in to comment.