Skip to content

Commit 9e1c8a4

Browse files
authored
Run subtasks with Mix.Task.run (#190)
Remove watch option
1 parent d8c9385 commit 9e1c8a4

File tree

5 files changed

+53
-127
lines changed

5 files changed

+53
-127
lines changed

.github/workflows/elixir.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ jobs:
7272
run: |
7373
cd examples/phoenix_project
7474
mix deps.get
75-
mix testcontainers.test
75+
MIX_ENV=test mix testcontainers.test
7676
MIX_ENV=prod mix release
7777
- name: Run example mix project tests
7878
run: |

README.md

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ container(:redis, Testcontainers.RedisContainer.new())
100100

101101
To run/wrap testcontainers around a project use the testcontainers.run task.
102102

103-
`mix testcontainers.run [sub_task] [--database postgres|mysql] [--watch dir ...] [--db-volume VOLUME]`
103+
`mix testcontainers.run [sub_task] [--database postgres|mysql] [--db-volume VOLUME]`
104104

105105
to use postgres you can just run
106106

@@ -110,19 +110,19 @@ to use postgres you can just run
110110

111111
```bash
112112
# Run tests with PostgreSQL (default)
113-
mix testcontainers.run test
113+
MIX_ENV=test mix testcontainers.run test
114114

115115
# Run tests with MySQL
116-
mix testcontainers.run test --database mysql
116+
MIX_ENV=test mix testcontainers.run test --database mysql
117117

118118
# Run Phoenix server with PostgreSQL and persistent volume
119119
mix testcontainers.run phx.server --database postgres --db-volume my_postgres_data
120120

121121
# Run tests with MySQL and persistent volume
122-
mix testcontainers.run test --database mysql --db-volume my_mysql_data
122+
MIX_ENV=test mix testcontainers.run test --database mysql --db-volume my_mysql_data
123123

124-
# Run tests with file watching
125-
mix testcontainers.run test --watch lib --watch test
124+
# Start Phoenix server with containerized DB (will keep running until stopped)
125+
mix testcontainers.run phx.server --database postgres --db-volume my_dev_data
126126
```
127127

128128
#### Persistent Volumes
@@ -134,38 +134,25 @@ The `--db-volume` parameter allows you to specify a persistent volume for databa
134134

135135
This is particularly useful when you want to maintain database state across test runs or development sessions.
136136

137-
#### Configuration
138-
139-
**For testing (config/test.exs):**
140-
141-
```elixir
142-
config :my_app, MyApp.Repo,
143-
username: System.get_env("DB_USER") || "postgres",
144-
password: System.get_env("DB_PASSWORD") || "postgres",
145-
hostname: System.get_env("DB_HOST") || "localhost",
146-
port: System.get_env("DB_PORT") || "5432",
147-
database: "my_app_test#{System.get_env("MIX_TEST_PARTITION")}",
148-
pool: Ecto.Adapters.SQL.Sandbox,
149-
pool_size: System.schedulers_online() * 2
150-
```
151-
152-
**For development (config/dev.exs):**
137+
#### Configuration (runtime.exs)
153138

154-
If you want to use `mix testcontainers.run phx.server` or other development tasks with a containerized database, you can configure your `config/dev.exs` similarly:
139+
Instead of editing dev.exs or test.exs, you can let testcontainers set `DATABASE_URL` and use it from `config/runtime.exs` for dev and test:
155140

156141
```elixir
157-
config :my_app, MyApp.Repo,
158-
username: System.get_env("DB_USER") || "postgres",
159-
password: System.get_env("DB_PASSWORD") || "postgres",
160-
hostname: System.get_env("DB_HOST") || "localhost",
161-
port: System.get_env("DB_PORT") || "5432",
162-
database: "my_app_dev",
163-
stacktrace: true,
164-
show_sensitive_data_on_connection_error: true,
165-
pool_size: 10
142+
# config/runtime.exs
143+
144+
if config_env() in [:dev, :test] do
145+
if url = System.get_env("DATABASE_URL") do
146+
config :my_app, MyApp.Repo,
147+
url: url,
148+
pool: Ecto.Adapters.SQL.Sandbox,
149+
show_sensitive_data_on_connection_error: true,
150+
pool_size: 10
151+
end
152+
end
166153
```
167154

168-
This allows you to run your Phoenix server with a containerized database:
155+
This allows you to run your Phoenix server or tests with a containerized database without changing dev.exs or test.exs (remember to set MIX_ENV when running tests):
169156

170157
```bash
171158
# Start Phoenix server with PostgreSQL container
@@ -180,12 +167,16 @@ mix testcontainers.run phx.server --database postgres --db-volume my_dev_data
180167

181168
Activate reuse of database containers started by mix task with adding `testcontainers.reuse.enable=true` in `~/.testcontainers.properties`. This is experimental.
182169

183-
You can pass arguments to the sub-task by appending them after the sub-task name. For example, to pass arguments to mix test:
170+
You can pass arguments to the sub-task by appending them after `--`. For example, to pass arguments to mix test:
184171

185-
`mix testcontainers.run test --exclude flaky --stale`
172+
`MIX_ENV=test mix testcontainers.run test -- --exclude flaky --stale`
186173

187174
In the example above we are running tests while excluding flaky tests and using the --stale option.
188175

176+
Note: MIX_ENV is not overridden by the run task. For tests, set it explicitly in the shell:
177+
178+
`MIX_ENV=test mix testcontainers.run test`
179+
189180
#### Backward Compatibility
190181

191182
For backward compatibility, the old `mix testcontainers.test` task is still available and works exactly as before. It automatically delegates to `mix testcontainers.run test`, so existing scripts and workflows will continue to work without modification:

lib/mix/tasks/testcontainers/run.ex

Lines changed: 24 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ defmodule Mix.Tasks.Testcontainers.Run do
66
@shortdoc "Runs a Mix sub-task (test, phx.server, etc) with a database container"
77
@moduledoc """
88
Usage:
9-
mix testcontainers.run [sub_task] [--database DB] [--watch folder] [--db-volume VOLUME] [sub_task_args...]
9+
mix testcontainers.run [sub_task] [--database DB] [--db-volume VOLUME] [sub_task_args...]
1010
1111
Examples:
1212
mix testcontainers.run test --database postgres
1313
mix testcontainers.run phx.server --database mysql
14-
mix testcontainers.run test --watch lib --watch test
1514
mix testcontainers.run test --database postgres --db-volume my_postgres_data
16-
mix testcontainers.run test --database mysql --db-volume my_mysql_data
15+
mix testcontainers.run phx.server --db-volume my_postgres_data
16+
mix testcontainers.run some.custom.server
1717
"""
1818

1919
def run(args) do
@@ -27,13 +27,11 @@ defmodule Mix.Tasks.Testcontainers.Run do
2727
OptionParser.parse(args,
2828
switches: [
2929
database: :string,
30-
watch: [:string, :keep],
3130
db_volume: :string
3231
]
3332
)
3433

3534
database = opts[:database] || "postgres"
36-
folder_to_watch = Keyword.get_values(opts, :watch)
3735
db_volume = opts[:db_volume]
3836

3937
# Determine sub_task and its args
@@ -43,41 +41,25 @@ defmodule Mix.Tasks.Testcontainers.Run do
4341
[] -> {"test", []}
4442
end
4543

46-
if Enum.empty?(folder_to_watch) do
47-
IO.puts("No folders specified. Only running subtask '#{sub_task}'.")
48-
run_sub_task_and_exit(database, sub_task, sub_task_args, db_volume)
49-
else
50-
check_folders_exist(folder_to_watch)
51-
run_sub_task_and_watch(database, sub_task, sub_task_args, folder_to_watch, db_volume)
52-
end
53-
end
54-
55-
defp check_folders_exist(folders) do
56-
Enum.each(folders, fn folder ->
57-
unless File.dir?(folder) do
58-
raise("Folder does not exist: #{folder}")
59-
end
60-
end)
44+
run_sub_task_and_exit(database, sub_task, sub_task_args, db_volume)
6145
end
6246

6347
@spec run_sub_task_and_exit(String.t(), String.t(), list(String.t()), String.t() | nil) :: no_return()
6448
defp run_sub_task_and_exit(database, sub_task, sub_task_args, db_volume) do
6549
{container, env} = setup_container(database, db_volume)
66-
exit_code = run_mix_task(env, sub_task, sub_task_args)
67-
Testcontainers.stop_container(container.container_id)
68-
System.halt(exit_code)
69-
end
7050

71-
defp run_sub_task_and_watch(database, sub_task, sub_task_args, folders, db_volume) do
72-
{container, env} = setup_container(database, db_volume)
51+
IO.puts("Starting in-process mix task: #{sub_task} #{Enum.join(sub_task_args, " ")}")
7352

74-
Enum.each(folders, fn folder ->
75-
:fs.start_link(String.to_atom("watcher_" <> folder), Path.absname(folder))
76-
:fs.subscribe(String.to_atom("watcher_" <> folder))
53+
System.at_exit(fn _ ->
54+
try do
55+
Testcontainers.stop_container(container.container_id)
56+
catch
57+
_, _ -> :ok
58+
end
7759
end)
7860

79-
run_mix_task(env, sub_task, sub_task_args)
80-
loop(env, sub_task, sub_task_args, container)
61+
Enum.each(env, fn {k, v} -> System.put_env(k, v) end)
62+
Mix.Task.run(sub_task, sub_task_args)
8163
end
8264

8365
defp setup_container(database, db_volume) do
@@ -88,13 +70,7 @@ defmodule Mix.Tasks.Testcontainers.Run do
8870
|> PostgresContainer.with_user("test")
8971
|> PostgresContainer.with_password("test")
9072
|> PostgresContainer.with_reuse(true)
91-
|> (fn config ->
92-
if db_volume do
93-
PostgresContainer.with_persistent_volume(config, db_volume)
94-
else
95-
config
96-
end
97-
end).()
73+
|> maybe_with_persistent_volume(db_volume, PostgresContainer)
9874

9975
{:ok, container} = Testcontainers.start_container(container_def)
10076
port = PostgresContainer.port(container)
@@ -106,13 +82,7 @@ defmodule Mix.Tasks.Testcontainers.Run do
10682
|> MySqlContainer.with_user("test")
10783
|> MySqlContainer.with_password("test")
10884
|> MySqlContainer.with_reuse(true)
109-
|> (fn config ->
110-
if db_volume do
111-
MySqlContainer.with_persistent_volume(config, db_volume)
112-
else
113-
config
114-
end
115-
end).()
85+
|> maybe_with_persistent_volume(db_volume, MySqlContainer)
11686

11787
{:ok, container} = Testcontainers.start_container(container_def)
11888
port = MySqlContainer.port(container)
@@ -123,52 +93,17 @@ defmodule Mix.Tasks.Testcontainers.Run do
12393
end
12494
end
12595

126-
defp create_env(port) do
127-
[
128-
{"DB_USER", "test"},
129-
{"DB_PASSWORD", "test"},
130-
{"DB_HOST", Testcontainers.get_host()},
131-
{"DB_PORT", Integer.to_string(port)}
132-
]
133-
end
134-
135-
defp run_mix_task(env, sub_task, sub_task_args) do
136-
case System.cmd("mix", [sub_task] ++ sub_task_args,
137-
env: env,
138-
into: IO.stream(),
139-
stderr_to_stdout: false
140-
) do
141-
{_, exit_code} ->
142-
if exit_code == 0 do
143-
IO.puts("Task '#{sub_task}' completed successfully")
144-
else
145-
IO.puts(:stderr, "Mix task '#{sub_task}' failed with exit code: #{exit_code}")
146-
end
147-
exit_code
148-
end
149-
end
150-
151-
defp loop(env, sub_task, sub_task_args, container) do
152-
receive do
153-
{_watcher_process, {:fs, :file_event}, {changed_file, _type}} ->
154-
IO.puts("#{changed_file} was updated, waiting for more changes...")
155-
wait_for_changes(env, sub_task, sub_task_args, container)
156-
after
157-
5000 ->
158-
loop(env, sub_task, sub_task_args, container)
96+
defp maybe_with_persistent_volume(config, db_volume, module) do
97+
if db_volume do
98+
module.with_persistent_volume(config, db_volume)
99+
else
100+
config
159101
end
160102
end
161103

162-
defp wait_for_changes(env, sub_task, sub_task_args, container) do
163-
receive do
164-
{_watcher_process, {:fs, :file_event}, {changed_file, _type}} ->
165-
IO.puts("#{changed_file} was updated, waiting for more changes...")
166-
wait_for_changes(env, sub_task, sub_task_args, container)
167-
after
168-
1000 ->
169-
IO.ANSI.clear()
170-
run_mix_task(env, sub_task, sub_task_args)
171-
loop(env, sub_task, sub_task_args, container)
172-
end
104+
defp create_env(port) do
105+
[
106+
{"DATABASE_URL", "ecto://test:test@#{Testcontainers.get_host()}:#{port}/test"}
107+
]
173108
end
174109
end

lib/util/constants.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Testcontainers.Constants do
22
@moduledoc false
33

44
def library_name, do: :testcontainers
5-
def library_version, do: "1.13.0"
5+
def library_version, do: "1.13.1"
66
def ryuk_version, do: "0.11.0"
77
def container_label, do: "org.testcontainers"
88
def container_lang_label, do: "org.testcontainers.lang"

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule TestcontainersElixir.MixProject do
22
use Mix.Project
33

44
@app :testcontainers
5-
@version "1.13.0"
5+
@version "1.13.1"
66
@source_url "https://github.com/testcontainers/testcontainers-elixir"
77

88
def project do

0 commit comments

Comments
 (0)