DEV Community

Cover image for How a dog could clean your orphan infrastructures after a test integration process crash
NotoriousWRLD
NotoriousWRLD

Posted on

How a dog could clean your orphan infrastructures after a test integration process crash

ℹ English is not my native language and i'm not using any AI to write this article. Links to code will be added so you can follow the whole process through the articles, do not hesitate to click links

Never have you ever debugged an integration test, found that it has failed, and unfortunately pressed the red squared button to kill the debugging, skipping the whole teardown logic?

Tell me you've never seen an integration test CI starting your test and for some reason, be stopped unexpectedly ? (Timeout, OOM, Job canceled).

That's why TestContainers has made Ryuk, a docker container alongside others that clean them after your tests.
It works absolutely wonderfully because it does not rely on the test process to clean your environment.

But i had one problem.
Docker was not available for the infrastructure i wanted to set up.
And my dev server was not always cleaned by the test.

A lot of orphan test message bus subscriptions were left alone.
Making the dev server a complete mess of useless queues with large random guid names. Databases suffered from the same problem.

That's why i have been working on a watchdog that handles not only docker containers, but any infrastructure that is set up in your tests.
And it is integrated in NotoriousTest, an integration test framework made to simplify the making of clean, reusable integration tests.

DoggyDog has been made to work alongside NotoriousTest, all behaviour described here will be supported natively by NT without you doing anything.

But since we are here to talk about the watchdog, lets see how it works :

Doggydog, the nice and gentle dog that keeps tracks of your infrastructures.

DoggyDog is a self-contained executable, made with .NET 10.
It has the responsibility to clean all your orphan infrastructures after the test process quits without success.

A well trained dog that waits for your test to crash

🐶 DoggyDog is well trained, he will sit gently before doing anything to your infrastructures. Don't worry, he will move only after the right signal is emitted.

You will need to provide a PID to the Watchdog.
He will wait until the process ends.

After the process ended, he will look for a signal, more specifically, a temporary signal file.
This file name is formatted with an "EnvironmentId" (nt-{EnvironmentId}.signal).
If he finds this file, it means that the process has exited correctly and had the time to clean the environment.

Why does DoggyDog not use exit code to ensure the test process exits correctly or not ?
On Windows, the exit code is available for everyone as a public information.
On Linux, however, this information is available only for the parent process. Which is not the case here since DoggyDog is finding the process with the PID.
For compatibility reasons, i needed to find an alternative.

🐶 Then, the DoggyDog will leave and return to his Doghouse until you need him for another guard duty.

DoggyDog does nothing if test exit cleanly

But what if he doesn't find his signal file ?
If the file is not present, DoggyDog will start the clean process.

How DoggyDog takes care of the mess after dinner

Loading the test assembly as a plugin

While tests were running, DoggyDog loaded the test assembly inside his default load context.

To do so, he takes a path to the test assembly and its runtimes to load them inside his default load context.

DoggyDog loading assemblies

You need to think of the test assembly as a plugin of DoggyDog.
You define classes with predefined interfaces and attributes, and DoggyDog will find them later to do his job.
DoggyDog takes runtimes as well to resolve ASP.NET Core assemblies and/or .NET assemblies that could be referenced inside the test projects. (e.g. Microsoft.* or System.* packages).

ℹ Why not using an Assembly Load Context ?
DoggyDog must be seen as a process "inside" the test process.
Later, we will see that DoggyDog uses a sqlite registry to retrieve infrastructure types, and loads them from the test assembly.
He will need to make comparisons between referenced types in DoggyDog and types in the test project.
Comparison between 2 instances of the same version but with different calling assemblies will not result in equality. I didn't want to have to design DoggyDog around full reflection.

Runtimes paths are needed for two reasons :

  • Because DoggyDog is self contained. You don't need to have .NET 10 installed on your machine to run DoggyDog (NotoriousTest is trying to be as compatible as possible with .netstandard2.1, and when it's not possible, .NET 8). Therefore, he will not resolve framework types from the .net assembly.
  • Because DoggyDog is a .NET executable, and does not reference ASP.NET Core. And i didn't want to make a direct reference to ASP.NET to handle assembly resolution.

Retrieving orphan infrastructures and their cleaners

The test process will populate a sqlite registry when it initializes an infrastructure with InfrastructureRegistryEntry:

  • EnvironmentId : An ID that identifies a subset of infrastructures associated with a test campaign.
  • InfrastructureId
  • InfrastructureType
  • Metadata and MetadataType : Necessary information to clean the infrastructure (such as connection strings, container name/id, subscription name, etc...)

For every infrastructure that has not been deleted from the test process, DoggyDog will :

By adding on your infrastructure classes the cleaner attribute, and implementing an IInfrastructureCleaner, you can clean any infrastructure that you want, even if it's a docker container, a real database, a file.

Doggydog cleans every infrastructure with their cleaner

Infrastructure events

DoggyDog is always watching the registry.
When the test project creates an infrastructure in the registry, adds a reset date, or deletes an infrastructure, DoggyDog will see it and log into the console what happened.

DoggyDog show init, reset and destroy events

The End

In conclusion, DoggyDog is a watchdog that waits for your test process to stop, looks in a sqlite registry for all infrastructures that have been created, and calls their associated cleaner, while notifying what happened to them.

It was really fun to get through all this reflection stuff.
I had never had the chance to dive into it and making everything work makes me genuinely proud.

Do not hesitate to share your thoughts about it, go through the repository , the docs and samples.
And to star the repo if you like it !

Have a great day !

🐶 Woof !

Top comments (0)