aegatlin

aegatlin

How to find and kill an application-supervised, static (unnamed) Task in ExUnit (but only for some tests)?

I have an Agent and an infinite-running static Task that are application-supervised on startup. The Task is generating an array of numbers that are being stored in the Agent.

When testing, ExUnit starts these processes automatically, which I want for most of my tests (i.e., I don’t want to run mix test --no-start).

For both the Agent and the Task, I want to test their interactions. But, since they start automatically, by the time I can actually test them, they have been populated already with an arbitrary amount of data. It seems like a reasonable approach would be to kill both process and then recreate them with ExUnit.start_supervised. But, the static Task doesn’t have a name. So, the main question is: How do I find and kill an application-supervised static Task? (I don’t have this problem with my Agent because it is ‘named’, i.e., name: __MODULE__.

But, this doesn’t really solve my testing problem. Why? Because if I kill my application processes, the rest of my tests, which test functionality that depends on the Agent data being there, will all fail. What I really need is the processes to exist for the majority of my test suite, and then, somehow, I need to be able to isolate the tests of my Agent and Task from the greater application environment. (This presumably includes the complication of have the Agent have the same __MODULE__ name, so, maybe would involve a separate Registry? And, I’d prefer to not refactor my Agent module to receive different atom names just so I could test it, but if that’s necessary to get it work, I’d be happy to do it.)

I’ve been reading and experimenting all day, trying to find a way to get a passing test suite, but have failed so far. I can’t manage to find a way. Any feedback would be appreciated :smiley:

Most Liked

LostKobrakai

LostKobrakai

The biggest problem I feel people have with testing “global” processes, is that the fact that they’re hard coded to be global. Things get quite a bit simpler if naming a process (or registering elsewhere) becomes something controlled by start parameters, where an application started instance can still make sure its registered, but e.g. tests can easily start their own instances unnamed and therefore be more flexible in how to test the complete livecycle of the process.

LostKobrakai

LostKobrakai

Instead of testing the processes started by your application start another set of those processes for testing the interactions. Basically splitting up the parts of “the processes are started as part of the application” from the tests about “assert that the processes correctly interact with each other”.

aegatlin

aegatlin

Okay, well, this post seems to imply that long-running Tasks are almost an anti-pattern, and you should instead use a genserver: Best way to name permanent tasks? - #8 by ityonemo

The TL;DR as it applies to my issue involves using Process.register inside the Task module, so it’s named. And, also, to probably not use a Task, and instead use a GenServer.

I will attempt to rewrite my Task as a GenServer. It seems a bit like overkill, but maybe the added functionality potential will serve me well in the long run. What do you think @LostKobrakai ?

ityonemo

ityonemo

So, it seems like you are building some sort of “random number service” as the Agent. I guess, my question to you would be then why not just use a single GenServer and instead of using a task to generate your numbers, generate numbers in your GenServer between requests (you could probably also do this with Agent, but i kind of have a bias against Agent, in almost all cases you should not be using Agent). Do you really need the number generation to be asynchronous? Because once it becomes asynchronous, your complexity increases, you need to think about strange failure modes (what happens when the Task disappears but not the Agent? what about vice versa?), and if you have a “synchronous” genserver service doing the generation, then you only have to think about one line of execution and stuff stays (relatively) sane.

You could also build out a single instance of the server and test it in isolation.

Finally, if you are really adamant about having this Agent/Task architecture, I would put the Agent and the Task under a common supervisor, and start a “fresh supervisor” in every test you run. Don’t forget to hoist the “naming” option to an option in the supervisor setup, so that in your Application.start call it gets properly named, but when you spawn off new ones for testing, they’re not bound to a locally unique name

ityonemo

ityonemo

you might want to do something like :timer.send_interval(interval, self(), :tick) on init, Erlang -- timer if you need the generation to be triggered indepedently of other events happening to your GenServer and even if no other events happen.

My personal preference is that I don’t like Process.send_after(self(), :tick, interval), because it feels like you if you are not careful coding, you could accidentally retrigger it n times and wind up with n times more events than you wanted.

If you only need it in a responsive sense (only need to update the list after some event has happened), I recommend using the handle_continue, I think if you can get away with doing that that is the best way, because your event schedule is more “deterministic” for testing purposes, and probably it’s easier to reason about.

Where Next?

Popular in Questions Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

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
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
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
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
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

We're in Beta

About us Mission Statement