Packages
testcontainers
1.10.4
2.3.1
2.3.0
2.2.0
2.1.0
2.1.0-rc1
2.0.0
2.0.0-rc3
2.0.0-rc2
2.0.0-rc
1.14.1
1.14.0
1.13.5
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.8
1.11.7
1.11.6
1.11.5
1.11.4
1.11.3
1.11.2
1.11.1
1.11.0
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.0
1.8.4
1.8.3
1.8.2
1.8.1
1.8.0
1.7.0
1.6.0
1.5.1
1.5.0
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.2.11
1.2.10
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-beta.1
1.0.0-beta
0.9.3
0.9.2
0.9.1
0.9.0
Testcontainers is an Elixir library that supports ExUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.
Current section
72 Versions
Jump to
Current section
72 Versions
Compare versions
6
files changed
+106
additions
-29
deletions
| @@ -63,5 +63,10 @@ | |
| 63 63 | {<<"name">>,<<"ecto">>}, |
| 64 64 | {<<"optional">>,true}, |
| 65 65 | {<<"repository">>,<<"hexpm">>}, |
| 66 | - {<<"requirement">>,<<"~> 3.3">>}]]}. |
| 67 | - {<<"version">>,<<"1.10.3">>}. |
| 66 | + {<<"requirement">>,<<"~> 3.3">>}], |
| 67 | + [{<<"app">>,<<"fs">>}, |
| 68 | + {<<"name">>,<<"fs">>}, |
| 69 | + {<<"optional">>,false}, |
| 70 | + {<<"repository">>,<<"hexpm">>}, |
| 71 | + {<<"requirement">>,<<"~> 8.6">>}]]}. |
| 72 | + {<<"version">>,<<"1.10.4">>}. |
| @@ -7,23 +7,22 @@ defmodule Testcontainers.ContainerBuilderHelper do | |
| 7 7 | def build(builder, state) when is_map(state) and is_struct(builder) do |
| 8 8 | config = |
| 9 9 | ContainerBuilder.build(builder) |
| 10 | - |> Container.with_label(container_version_label(), library_version()) |
| 11 10 | |> Container.with_label(container_lang_label(), container_lang_value()) |
| 12 11 | |> Container.with_label(container_label(), "#{true}") |
| 13 12 | |
| 14 | - reuse = config.reuse && true == Map.get(state.properties, "testcontainers.reuse.enable", false) |
| 15 | - |
| 16 | - if reuse do |
| 13 | + if config.reuse && "true" == Map.get(state.properties, "testcontainers.reuse.enable", "false") do |
| 17 14 | hash = Hash.struct_to_hash(config) |
| 18 15 | config |
| 19 16 | |> Container.with_label(container_reuse(), "true") |
| 20 17 | |> Container.with_label(container_reuse_hash_label(), hash) |
| 21 18 | |> Container.with_label(container_sessionId_label(), state.session_id) |
| 19 | + |> Container.with_label(container_version_label(), library_version()) |
| 22 20 | |> Kernel.then(&{:reuse, &1, hash}) |
| 23 21 | else |
| 24 22 | config |
| 25 23 | |> Container.with_label(container_reuse(), "false") |
| 26 24 | |> Container.with_label(container_sessionId_label(), state.session_id) |
| 25 | + |> Container.with_label(container_version_label(), library_version()) |
| 27 26 | |> Kernel.then(&{:noreuse, &1, nil}) |
| 28 27 | end |
| 29 28 | end |
| @@ -5,17 +5,61 @@ defmodule Mix.Tasks.Testcontainers.Test do | |
| 5 5 | |
| 6 6 | @shortdoc "Runs tests with a Postgres container" |
| 7 7 | def run(args) do |
| 8 | - Application.ensure_all_started(:tesla) |
| 9 | - Application.ensure_all_started(:hackney) |
| 8 | + Enum.each([:tesla, :hackney, :fs, :logger], fn app -> |
| 9 | + {:ok, _} = Application.ensure_all_started(app) |
| 10 | + end) |
| 11 | + |
| 10 12 | {:ok, _} = Testcontainers.start_link() |
| 11 13 | |
| 12 | - {opts, _, _} = OptionParser.parse(args, switches: [ |
| 13 | - database: :string |
| 14 | - ]) |
| 14 | + {opts, _, _} = OptionParser.parse(args, |
| 15 | + switches: [ |
| 16 | + database: :string, |
| 17 | + watch: [:string, :keep] |
| 18 | + ] |
| 19 | + ) |
| 15 20 | |
| 16 21 | database = opts[:database] || "postgres" |
| 22 | + folder_to_watch = Keyword.get_values(opts, :watch) |
| 17 23 | |
| 18 | - {container, port} = case database do |
| 24 | + if Enum.empty?(folder_to_watch) do |
| 25 | + IO.puts("No folders specified. Only running tests.") |
| 26 | + run_tests_and_exit(database) |
| 27 | + else |
| 28 | + check_folders_exist(folder_to_watch) |
| 29 | + run_tests_and_watch(database, folder_to_watch) |
| 30 | + end |
| 31 | + end |
| 32 | + |
| 33 | + defp check_folders_exist(folders) do |
| 34 | + Enum.each(folders, fn folder -> |
| 35 | + unless File.dir?(folder) do |
| 36 | + Mix.raise("Folder does not exist: #{folder}") |
| 37 | + end |
| 38 | + end) |
| 39 | + end |
| 40 | + |
| 41 | + defp run_tests_and_exit(database) do |
| 42 | + {container, env} = setup_container(database) |
| 43 | + run_tests(env) |
| 44 | + Testcontainers.stop_container(container.container_id) |
| 45 | + end |
| 46 | + |
| 47 | + defp run_tests_and_watch(database, folders) do |
| 48 | + {container, env} = setup_container(database) |
| 49 | + |
| 50 | + Enum.each(folders, fn folder -> |
| 51 | + :fs.start_link(String.to_atom("watcher_" <> folder), Path.absname(folder)) |
| 52 | + :fs.subscribe(String.to_atom("watcher_" <> folder)) |
| 53 | + end) |
| 54 | + |
| 55 | + Process.flag(:trap_exit, true) |
| 56 | + |
| 57 | + run_tests(env) |
| 58 | + loop(env, container) |
| 59 | + end |
| 60 | + |
| 61 | + defp setup_container(database) do |
| 62 | + case database do |
| 19 63 | "postgres" -> |
| 20 64 | {:ok, container} = Testcontainers.start_container( |
| 21 65 | PostgresContainer.new() |
| @@ -24,7 +68,7 @@ defmodule Mix.Tasks.Testcontainers.Test do | |
| 24 68 | |> PostgresContainer.with_reuse(true) |
| 25 69 | ) |
| 26 70 | port = PostgresContainer.port(container) |
| 27 | - {container, port} |
| 71 | + {container, create_env(port)} |
| 28 72 | "mysql" -> |
| 29 73 | {:ok, container} = Testcontainers.start_container( |
| 30 74 | MySqlContainer.new() |
| @@ -33,26 +77,54 @@ defmodule Mix.Tasks.Testcontainers.Test do | |
| 33 77 | |> MySqlContainer.with_reuse(true) |
| 34 78 | ) |
| 35 79 | port = MySqlContainer.port(container) |
| 36 | - {container, port} |
| 80 | + {container, create_env(port)} |
| 37 81 | _ -> Mix.raise("Unsupported database: #{database}") |
| 38 82 | end |
| 83 | + end |
| 39 84 | |
| 40 | - env = [ |
| 85 | + defp create_env(port) do |
| 86 | + [ |
| 41 87 | {"DB_USER", "test"}, |
| 42 88 | {"DB_PASSWORD", "test"}, |
| 43 89 | {"DB_HOST", Testcontainers.get_host()}, |
| 44 | - {"DB_PORT", port |> Integer.to_string()} |
| 90 | + {"DB_PORT", Integer.to_string(port)} |
| 45 91 | ] |
| 92 | + end |
| 46 93 | |
| 47 | - try do |
| 48 | - {output, exit_code} = System.cmd("mix", ["test"], env: env) |
| 49 | - if exit_code != 0 do |
| 50 | - IO.puts(output) |
| 51 | - raise "\u274c Tests failed" |
| 94 | + defp run_tests(env) do |
| 95 | + test_pid = spawn(fn -> |
| 96 | + System.cmd("mix", ["test"], env: env, into: IO.stream(:stdio, :line)) |
| 97 | + end) |
| 98 | + |
| 99 | + Process.monitor(test_pid) |
| 100 | + |
| 101 | + receive do |
| 102 | + {:DOWN, _ref, :process, ^test_pid, _reason} -> |
| 103 | + IO.puts("Test process completed.") |
| 52 104 | end |
| 53 | - IO.puts("\u2705 Tests passed") |
| 54 | - after |
| 55 | - Testcontainers.stop_container(container.container_id) |
| 105 | + end |
| 106 | + |
| 107 | + defp loop(env, container) do |
| 108 | + receive do |
| 109 | + {_watcher_process, {:fs, :file_event}, {changed_file, _type}} -> |
| 110 | + IO.puts("#{changed_file} was updated, waiting for more changes...") |
| 111 | + wait_for_changes(env, container) |
| 112 | + |
| 113 | + after 5000 -> |
| 114 | + loop(env, container) |
| 115 | + end |
| 116 | + end |
| 117 | + |
| 118 | + defp wait_for_changes(env, container) do |
| 119 | + receive do |
| 120 | + {_watcher_process, {:fs, :file_event}, {changed_file, _type}} -> |
| 121 | + IO.puts("#{changed_file} was updated, waiting for more changes...") |
| 122 | + wait_for_changes(env, container) |
| 123 | + |
| 124 | + after 1000 -> |
| 125 | + IO.ANSI.clear() |
| 126 | + run_tests(env) |
| 127 | + loop(env, container) |
| 56 128 | end |
| 57 129 | end |
| 58 130 | end |
| @@ -13,7 +13,6 @@ defmodule Testcontainers do | |
| 13 13 | alias Testcontainers.Connection |
| 14 14 | alias Testcontainers.Container |
| 15 15 | alias Testcontainers.ContainerBuilder |
| 16 | - alias Testcontainers.Util.Hash |
| 17 16 | alias Testcontainers.Util.PropertiesParser |
| 18 17 | |
| 19 18 | import Testcontainers.Constants |
| @@ -2,7 +2,7 @@ defmodule Testcontainers.Constants do | |
| 2 2 | @moduledoc false |
| 3 3 | |
| 4 4 | def library_name, do: :testcontainers |
| 5 | - def library_version, do: "1.10.3" |
| 5 | + def library_version, do: "1.10.4" |
| 6 6 | def container_label, do: "org.testcontainers" |
| 7 7 | def container_lang_label, do: "org.testcontainers.lang" |
| 8 8 | def container_reuse_hash_label, do: "org.testcontainers.reuse-hash" |
Loading more files…