scottming

scottming

How to speed up the start of `mix test`?

I work on a very large umbrella project. I did this config in my web app:

config :logger,
  level: String.to_atom(System.get_env("LOGGER_LEVEL") || "info"),
  handle_otp_reports: true,
  handle_sasl_reports: true
config :my_web, MyWeb.Gettext, one_module_per_locale: true, allowed_locales: ["en"]

But finally, I found that when I was testing a file, it took almost 5 seconds before my gettext app started:

These are the top logs when I run mix test filename.exs at 11:10:30:

11:10:35.496 [info]  Child Gettext.ExtractorAgent of Supervisor #PID<0.4876.0> (Supervisor.Default) started
Pid: #PID<0.4877.0>
Start Call: Gettext.ExtractorAgent.start_link([])
Restart: :permanent
Shutdown: 5000
Type: :worker

then, I removed the gettext deps, but It doesn’t solve the problem.

These are the top logs when I run mix test filename.exs at 12:37:00 after I remove the gettext deps:

2021-10-31 12:37:04.800 [info] module=application_controller function=info_started/2 line=2089  Application logger started at :nonode@nohost
2021-10-31 12:37:04.844 [info] module=supervisor function=report_progress/2 line=1546  Child :ttb_autostart of Supervisor :runtime_tools_sup started
Pid: #PID<0.4893.0>

References

Marked As Solved

scottming

scottming

FYI, I have found a solution to this problem by working at the editor tool plugin level. Now my tests for complex projects that take more than 10 seconds take less than 1 second to run on iex, and those for simple projects usually take only 0.2 seconds. The experience from this speed is excellent.

I have written my own plugin: Elixir Test in IEx - Visual Studio Marketplace, which is very handy for Vscode users

Jhon Pedroza is working on the nvim neotest side of the integration: IEx strategy by jfpedroza · Pull Request #14 · jfpedroza/neotest-elixir · GitHub; and if you want to quickly experience vscode-like effects, you can also use these simple command define:

vim.api.nvim_create_user_command("TestIexStart", function()
	local code = 'Code.eval_file("~/.test_iex/lib/test_iex.ex");TestIex.start()'
	toggleterm.exec(string.format("MIX_ENV=test iex --no-pry -S mix run -e %q", code), 1)
	ttt.get_or_create_term(1):close()
end, {})

vim.api.nvim_create_user_command("TestFileAtCursorInIex", function()
	local line_col = vim.api.nvim_win_get_cursor(0)[1]
	local path = vim.fn.expand("%")
	local test_command = string.format("TestIex.test(%q, %q)", path, line_col)
	if vim.endswith(path, ".exs") then
		toggleterm.exec(test_command, 1)
		vim.g.last_test_in_iex_command = test_command
	else
		toggleterm.exec(vim.g.last_test_in_iex_command, 1)
	end
end, {})

vim.api.nvim_create_user_command("TestFileInIex", function()
	local path = vim.fn.expand("%")
	local test_command = string.format("TestIex.test(%q)", path)
	if vim.endswith(path, ".exs") then
		toggleterm.exec(test_command, 1)
		vim.g.last_test_in_iex_command = test_command
	else
		toggleterm.exec(vim.g.last_test_in_iex_command, 1)
	end
end, {})

Also Liked

josevalim

josevalim

Creator of Elixir

You can run MIX_DEBUG=1 mix test and it will show you the timing to run tasks in recent Elixir versions. If you are on Elixir v1.11 or later and you have a Phoenix application running on latest v1.5 or v1.6, you can remove the :phoenix compiler in the :compilers option of your mix.exs. That should speed it up by 2-3s in large apps.

10
Post #2
al2o3cr

al2o3cr

I’ve seen this kind of “silent hang” in situations when multiple sandboxed tests tried to insert the same value for a column with a unique constraint.

None of the INSERTs can report success or failure until the other one’s transaction commits or aborts, so everything comes to a halt.

That usually produced one error per connection in the pool, though. In your case, matching the number of repos suggests something different might be happening. :thinking:

shamshirz

shamshirz

Updates!

Thanks so much for the ideas y’all! Interesting turn of events, I went digging into the DB side of this because we have addressed issues like transaction locks in the past and implemented unique values for constrained columns, but I think I misled us! :crazy_face:

Red Herring - DB timeout

My current hypothesis is that the DBConnection.ConnectionError) owner #PID<0.726.0> timed out because it owned the connection for longer than 120000ms timeout is a redherring! It is timing out and worth looking into, but the DB isn’t the cause of the timeout, it’s just reporting that it’s been blocked for quite a while with an open connection(? I think).

I re-ran tests locally to discover that the cover portion of the test command is using most of the unaccounted-for time!

### Just Test (21s)
> time MIX_DEBUG=1 mix test test/crowbar/scan/vciso_card_test.exs > tmp.txt && elixir ./analyze.exs

### Partitioned (21s)
> time MIX_TEST_PARTITION=1 MIX_DEBUG=1 mix test test/crowbar/scan/vciso_card_test.exs --partitions 2 > tmp.txt && elixir ./analyze.exs

### Cover (61s)
> time MIX_DEBUG=1 mix test test/crowbar/scan/vciso_card_test.exs --cover > tmp.txt && elixir ./analyze.exs

### Partition & Cover (73s)
> time MIX_TEST_PARTITION=1 MIX_DEBUG=1 mix test test/crowbar/scan/vciso_card_test.exs --partitions 2 --cover > tmp.txt && elixir ./analyze.exs

I put the analyze.exs into a Gist here if you want to recreate. Should be code base agnostic.

New Hypothesis: ExCoveralls is using the time!

I annotated ExCoveralls locally to reproduce and I found the time here

-> Running mix test test/crowbar/scan/vciso_card_test.exs --cover (inside Crowbar.Mixfile)
-> Running ExCoveralls Cover.compile
<- Ran ExCoveralls Cover.compile in 25757ms
…
Finished in 4.9 seconds (4.9s async, 0.00s sync)
…
-> Running ExCoveralls Cover.execute
<- Ran ExCoveralls Cover.execute in 13249ms
<- Ran mix test in 43973ms

This example just ran 1 test file, but the time breakdown is clear
44s total = 26s Cover.compile + 13s Cover.execute + 5s tests

note: These time are all much faster because it’s local on a multicore laptop vs. CI with the default github action runner

Mystery solved and if y’all happen to have any thoughts or experience on how to address things like this in CI, I’d love to hear your thoughts!

My likely next step is to make a --cover Action for the end of CI, so we can run the tests and get feedback as fast as possible without coverage.

scottming

scottming

Sorry, Jose, my expression may be a bit ambiguous, this startup speed is acceptable if you add this bunch of dependency frameworks. Thanks to your work and the community, I haven’t used a web framework in another language for many years.

But I was actually comparing the speed of unit testing in other languages because our business area has a lot of unit tests, and I myself like them, and unit test dependencies should be very few, not depending on the DB.

I wonder if there is any way to make my Unit Tests(use ExUnit.Case, async: true) startup speed faster as well?

josevalim

josevalim

Creator of Elixir

The fixes in that thread are already in place. Can you please post the full output of your mix command with the debug flag here?

Where Next?

Popular in Questions Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
dokuzbir
Hello, I am trying to convert my lists to string without losing brackets.For start i have 3 map. They look like these buyer = %{ id: ...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

Other popular topics Top

belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
977 41022 311
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

We're in Beta

About us Mission Statement