Recently, we had an interesting request from one of our clients where we needed to implement time travel for testing purposes - or fake time travel at least! The Quality Assurance (QA) team of one of our clients wanted to test functionality under particular time constraints within their application. To do that, they needed is to move the application’s internal clock to check and see whether some these time-based actions were allowed or forbidden (depending on the case). Mocking up operating system’s datetime settings on Heroku was the answer, with setting up environmental variables for datetime testing. Heroku datetime testing in a Ruby on Rails application required a little configuration - but we were able to get the job done.
Because the QA team tests full scenarios (using the web interface), we had to ensure that the time remained consistent between multiple requests, so when objects were saved to the database they stored the correct time with them (i.e. Rails’ timestamps).
Libfaketime to the rescue
Since we needed to modify the time for the Ruby process, we decided to use the libfaketime library. The library works by intercepting various system calls that programs might use to retrieve the current date and time. Thanks to libfaketime, we can easily modify the date & time just for our application’s processes (Puma and Rails, Sidekiq, workers, etc.) without affecting other applications running on the same machine.
Note: Since it relies on the library preload mechanism of the operating system, there are some cases when it might not work (check the *Compatibility issues* section of the `libfaketime` README), but we didn’t spot a case where it wouldn’t work in our Rails application.
Libfaketime is 3rd party library, you just need to install it (`configure, make, make install`) and you are ready to go. Or are you? Ah, we forgot to mention that our client is using Heroku…
Heroku custom buildpack
If you were using a VPS or self-hosted solution, then you would just install the libfaketime library globally and then modify the processes that need custom time. On Heroku, it’s not quite that easy - if you need to modify the container’s configuration / environment for your app, you need to use buildpack for that.
This means that in order to make `libfaketime` available, we had to create a buildpack that downloads it and compiles it during the build. We also had to modify the running processes to be wrapped with `libfaketime`.
However, building a custom Heroku buildpack is a topic for another blog post. Here we will just show how we wrap running processes with `libfaketime` without modifying the `Procfile`.
Setting up libfaketime on Heroku
Every Linux system supports the `LD_PRELOAD` env variable. It can contain one or more paths to shared libraries, which may be injected before any other shared library (including the C runtime). Since Heroku heavily relies on environment variables for dyno configurations, it sounded like the perfect solution for us - and indeed it was ;)
So! To get things ready to play with time, we need to set 2 environment variables and use a buildpack to modify the time in the Ruby on Rails application:
- `LD_PRELOAD` with path set to the `libfaketime1.so` file for injecting `libfaketime`
- `FAKE_TIME` with the relative date to modify the current time
Note: We relied on relative date to simulate time passing. If you specify `FAKE_TIME` to an absolute value (i.e. `2020-12-24 20:30:00`) all your further calls to `Time.now` will return the same date and time.
TimeTraveling interface
Since the project already had a dedicated user interface for the QA team to manage testing scenarios (i.e. create new users, etc.), we included a ‘Time travel’ component to it. But how can you modify the environment of a running application if it’s already running? By using Heroku’s API!
Heroku provides a platform-gem which allows you to interact with any Heroku application (i.e. updating environment variables, managing add-ons, builds and more), where any change to environment variables on Heroku results in immediate container restarts (to pickup the change). Very handy! With the platform-gem we connected a simple date picker with an environment variable (`FAKETIME`) value on Heroku. After approximately 30 seconds (time to fully restart Heroku dynos) from the change (using the UI) the QA team was able to move in time. Easy way to conduct time travel testing, right?
It’s always a pleasure making a QA team’s life easier. If you are interested in building more robust testing solutions that help out your QA team then come and speak to us! iRonin can help your project become stronger and better tested than ever before.