Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: mocks for supervised tasks #750

Merged

Conversation

tomekowal
Copy link
Contributor

@tomekowal tomekowal commented Feb 3, 2025

This PR is a follow up of this comment:
#746 (comment)
Thanks again @carrascoacd for the original fix. Using Process.info inspired me to improve it even further :)


pid_holder |> Process.info() |> Keyword.get(:dictionary) |> Keyword.get(__MODULE__)
Enum.find_value(potential_mock_holder_pids, nil, fn pid ->
{:dictionary, process_dictionary} = Process.info(pid, :dictionary)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Process.info(pid, :dictionary) instead of just Process.info(pid) reduces the amount data copied between processes.


pid_holder |> Process.info() |> Keyword.get(:dictionary) |> Keyword.get(__MODULE__)
Enum.find_value(potential_mock_holder_pids, nil, fn pid ->
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using find_value instead of find returns the mock directly, so we don't have to call Process.info for a second time after we got the pid.

pid when is_pid(pid) -> pid
name when is_atom(name) -> Process.whereis(name)
end
potential_mock_holder_pids = [self() | Enum.reverse(Process.get(:"$callers", []))]
Copy link
Contributor Author

@tomekowal tomekowal Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We search for mocks hierarchically. Even though, it is unlikely that we've set mocks both in the parent of the test process and in the test process, I would be super confused if I defined the mock right before calling a function and it found a mock from somewhere else.

Hence, I start searching from mocks from the current process and then up the callers hierarchy.

In most situations $callers and $ancestors are the same. E.g. spawning a task, starting a GenServer puts the same pid in both.

The notable difference is starting a supervised task because the ancestors of a supervised task is its supervisor, while the caller is the process that asked to spawn it.

I believe that for mocking $callers is the correct one to use.

@@ -113,6 +113,41 @@ defmodule Tesla.MockTest do
end
end

describe "supervised task" do
test "allows mocking in the caller process" do
# in real apps, task supervisor will be part of the supervision tree
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with testing callers vs ancestors was that if I start supervisor from test process, the test process would still be a grand-child and it would be both caller and ancestor.

This test makes sure that mocking task is caller and not ancestor of the supervised task.
E.g. if you swap $callers back to $ancestors, it would fail.


!is_nil(is_holder?)
end)
|> case do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That case statement is a leftover from the previous solution that assumed using non-existent Process.get(pid, value). Now, it is not necessary.

@carrascoacd
Copy link
Contributor

It makes sense to me, thanks for the improvements!

@yordis yordis changed the title Fix mocks for supervised tasks fix: mocks for supervised tasks Feb 3, 2025
@yordis
Copy link
Member

yordis commented Feb 3, 2025

btw, are you familiar with https://hexdocs.pm/tesla/Tesla.Test.html and https://hexdocs.pm/tesla/1-testing.html ideally you would migrate to use the module

@yordis yordis merged commit 2f6b2a6 into elixir-tesla:master Feb 3, 2025
5 of 7 checks passed
@yordis
Copy link
Member

yordis commented Feb 3, 2025

Thank you @tomekowal 🚀

@tomekowal
Copy link
Contributor Author

Thanks @yordis I know Tesla.Mock is getting deprecated. We are in the process of upgrading from Tesla 1.4 to 1.13, so we have four years of work to migrate :) Tesla.Mock worked flawlessly so far :)
Again, kudos for keeping the library up and running all this years!

@yordis
Copy link
Member

yordis commented Feb 3, 2025

I dont mind keeping it around for a long time, so don't sweat over it.

Let me know if you need any help with the migration! I wrote a guide for it https://hexdocs.pm/tesla/v1-macro-migration.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants