On one project we are starting docker container with our services running on the IBM WebSphere Liberty server and run tests which make sure our services are behaving according to our expectations.
Problem is that some of the scenarios we need to test include specific dates and times. For example, you can have a system which behaves differently if it is called during the working day vs. during a weekend. Service can behave differently if it is called during working hours or during the night.
Current date and time in the docker container are connected to time on the host machine it is running on. We wanted to find a way to force our service to behave as it was called on any specific date and time – not only the current time of test execution.
Of course, we could change our service for the purpose of testing but I thought it would be nicer to find another way to accomplish the same task, without any changes to our services and our docker images.
Modifying the Linux system time without modifying the Linux system time
Fortunately, we did find some solutions to our problems:
Both of those libraries intercept various system calls that programs use to retrieve the current date and time and change results according to library configuration.
Here are examples of typical libfaketime & datefudge usage applied to a date
command:
# LD_PRELOAD=/usr/lib64/faketime/libfaketime.so.1 FAKETIME="2020-10-22 12:13:14" date -R
Thu, 22 Oct 2020 12:13:14 +0200
# datefudge "2020-10-22 12:13:14" date -R
Thu, 22 Oct 2020 12:13:14 +0200
After an initial investigation, libfaketime seemed like a great solution for our task. Its configuration is quite flexible and it supports configuring it statically through the environment variable FAKETIME
or dynamically through a local file.
For example, if we set the libfaketime library to be loaded by LD_PRELOAD
environment variable it will check FAKETIME
environment variable for date & time configuration to use and if the environment variable is not found it will check the local file ~/.faketimerc
for its configuration. This way we can change contents of this file to change date & time in a running program.
Unfortunately, libfaketime slowed down our service considerably (almost by order of magnitude) so I decided to investigate the datefudge library.
Datefudge configuration was unfortunately much less flexible – it supported only the configuration during the initial start of our service. However, initial tests showed that our service didn’t show visible slow down so I checked how hard would it be to make it a bit more configurable.
Customizing datefudge
Source code of datefudge version 1.24 is ingeniously simple, it combines a single shell script and less than 100 lines of C code.
Datefudge uses shell date
command to parse date string and to get the current time which makes C code quite straight-forward – C code just uses prepared values passed to it through environment variables.
Unfortunately this approach is not possible if date is configured dynamically – if configuration is changed after datefudge is initialized. In that case our date/time configuration string must be parsed by C code. GNU time.h
library fortunately implements strptime function which makes parsing quite straight-forward. With just a few tweaks partial date-time string parsing & correct daylight saving handling is possible:
// Initialize tm struct to 1900-01-01 00:00:00 & tells mktime() to
// determine whether daylight saving time is in effect
struct tm tm = (struct tm){.tm_mday = 1, .tm_isdst = -1};
strptime(datetime_str, "%Y-%m-%d %H:%M:%S", &tm);
time_t config_time = mktime(&tm);
Customized datefudge library adds the following feature to the datefudge v1.24 version:
- date & time can be configured using a local file
- when this file is changed datefudge will load new configuration, however, the datefudge configuration file is checked for changes at most each 100 ms (this time can be configured using
-s
flag) to lower the performance costs of a file access operation
- when this file is changed datefudge will load new configuration, however, the datefudge configuration file is checked for changes at most each 100 ms (this time can be configured using
Datefudging a docker container with our service
We have multiple test scenarios containing multiple test steps. We can execute a single test scenario or all test scenarios using a single Docker container started automatically before our tests start to run.
For example we can have:
- test scenario 1
- test step 1
- test step 2
- test scenario 2
- test step 1
- test step 2
- test step 3
For our tests, we wanted to be able to set our service date & time for each of our test steps and every call to service during that step (and steps following it) will use new date & time.
(We prepared automated tests using Testcontainers project but that is a different story which doesn’t belong to this post)
The final solution consists of the following:
- copy customized datefudge command (shell script) & datefudge shared library to the container (we are using
Testcontainers GenericContainer.withCopyFileToContainer to achieve that but this could be done using different strategies otherwise) - change the CMD (default docker command from the image)
- for our WebSphere Liberty image CMD is by default set to
CMD ["/opt/ibm/wlp/bin/server" "run" "defaultServer"]
- on container run we set it’s CMD to
CMD ["datefudge" "-f" "/home/default/date.txt" "/opt/ibm/wlp/bin/server" "run" "defaultServer"]
- for our WebSphere Liberty image CMD is by default set to
- for each test scenario step where we need to set custom date & time we copy a new file with specific date & time to the running container as a datefudge configuration file
/home/default/date.txt
before running tests (and wait (configurable) 100 ms to make sure datefudge will apply new date & time configuration before tests are run)
Running tests with customized system times
For above given example if we add custom datefudge configuration for scenario 1 / step 2 (date-time A) and additional configuratno for scenario 2 / step 1 (date-time B) tests are executed like this:
- scenario 1
- test step 1 (container uses “normal” date & time)
- configure date-time A
- test step 2 (container uses date-time A date & time)
- test scenario 2
- configure date-time B
- test step 1 (container uses date-time B date & time)
- test step 2 (container uses date-time B date & time)
- test step 3 (container uses date-time B date & time)
Conclusion
If you need to test the behaviour of your application at specific date & time (on Linux, Docker or otherwise) it is quite straight-forward to use datefudge and/or libfaketime to achieve this without ever touching your application code.
The great news is that both of those libraries are opensource under GPL-2.0 so if you need some additional features you could add them without any problems. If you do that, please let the original authors know you did those changes so they include them in the next releases of libraries. The open-source community will be thankful to you
The cover image is created by Kaboompics .com from Pexels.
Related News