How to test Supervisor and GenServer?

Hi,

I’m a newbie to Elixir and OTP and trying to learn them by building a small testing tool. It is a tool that regularly make some REST call to my service to check their SLA ( in this case, response time). Here is the supervision tree that I come up with.

Quick summary on the roles of each process.

  • Control center is the brain and it will spawn a synthesizer for each system that I want to test

  • Synthesizer is responsible for scheduling new Test (by messaging Tester Supervisor to spawn new Tester process) based on some schedule (for example one test per minutes)

  • Tester Supervisor is responsible for spawning new tester and manage their lifecycle

  • Tester is the process that will make some rest calls and measure the response time.

I ran into couple things that I’m not sure and would be great if someone can give me some advice.

  • Am I structuring the process right? I am not sure when to use Supervisor and when to use GenServer and this is what I come up with the best of my knowledge.

  • From my understanding, I need to explicitly make a call to Supervisor.start_child/2 when I want to spawn a new child supervised using simple_one_to_one strategy. But where should I call this? From different online tutorials or docs, I can see that there’re different approaches. For example, some will suggest adding a new client API in the supervisor, something like create_child and in that call, invoke Supervisor.start_child/2. Others will add a new client API in the child, like create, and invoke Supervisor.start_child/2 and refer to the Supervisor using Module name. Is there a convention in Elixir for this? or it’s really up to the implementer? If it’s the later one, anyone can share some pros and cons of the two approaches?

  • How can I unit test those process (Control Center, Synthesizer, etc)? They are all wired together and how can I test each of them individually? ( for example, if I write a test to verify synthesizer is messaging Tester Supervisor correctly, it will involve every process, from Control Center coz it spawn Synthesizer, to Tester process coz I want to make sure new Tester processes are spawned correctly).

Thanks for reading this long posts and would be great if someone can give me some advice :slight_smile:

2 Likes

ferdinand, these are all really great questions. I have some of these questions myself. I ran into your post when I was looking at answers for the questions you posted on testing. I can’t help with those, but I will chime in on the others.

There’s a wall of text below - here’s the summary:

  • Supervisors should only supervise. They know how to restart the processes that they supervise - that is it. They are not the brains of an application.
  • If it does any kind of “work”, then it should be a worker / GenServer. If it simply restarts processes when those processes die, then it is a Supervisor.
  • Your supervision tree doesn’t look correct. The leaves on a supervision tree are always workers / GenServers. A GenServer should not be supervising other processes.
  • Simply because two (or more processes) coordinate to achieve a task does not mean that there is a relationship in terms of a supervision tree. As long as they can send messages to each other, they can be supervised by any other process anywhere in the supervision tree.

First, on the Control Center - it shouldn’t do anything but supervise. A supervisor is just another process, if it crashes, it will bring down anything that it is supervising because supervisors and the processes they supervise are linked. If it does something besides supervise, then there is a higher likelihood that it will crash. I bring this up only because you mentioned that Control Center is the brains, when really it should not do anything but supervise. Something else needs to be the brains.

This doesn’t mean that you can’t have functions in the same module that “do things”, as long as those functions are not being executed by the supervisor process. For example, in one of your questions, you ask how to start a child. There’s no problem with defining a function in the TestSupervisor module that delegates to Supervisor.start_child, but that function should not be called by the process that is actually doing the supervision. I would clarify that TestSupervisor is not responsible for spawning new testers. Technically yes that’s what happens under the hood, since the supervisor has to spawn the process and link it to itself. But I think what we are really talking about is the process that will execute Supervisor.start_child, and that should definitely not be the TestSupervisor.

In fact, this is a good segue into what should be a Supervisor and what should be a GenServer. Supervisors are an Erlang / Elixir construct for creating resilient applications. So, if the process will be executing business logic, it should be a GenServer.

Next point - your supervision tree looks really funky. Your Synthesizer is a worker, but then it seems like it also somehow supervises a TestSupervisor. Workers / GenServers should not be above any supervisor. Supervisors can supervise other supervisors, but a worker shouldn’t supervise anything - it should do work. I think what you want is a Top-Level Supervisor - e.g., Control Center - and that supervises a SynthesizerSupervisor which knows how to supervise Synthesizers, and a TesterSupervisor, which knows how to supervise Testers. In other words, all the leaves on a supervision tree should be workers. (Unless you have a supervisor that doesn’t supervise anything, which I don’t think anyone would do on purpose).

Here’s an important point - even though a Synthesizer will coordinate with Tester workers, that doesn’t mean that that hierarchy is reflected in the supervision tree. I think what you are showing is how workers - Synthesizer and Tester - work together to achieve a goal, but the supervision tree is not that.

Note: Sometimes supervisors do something else than supervise, but this is almost always exclusively on startup, in the start_link or init function(s).

Hopefully someone else chimes in with testing.

5 Likes