Обманываем время: о тестировании с «подставным» временем на Linux и Docker
При разработке очередного бота для группы в Telegram у меня возникла необходимость испытать его при различных значениях системного времени. Этот бот в конце каждого дня отправляет (или, в зависимости от ряда условий, не отправляет) сообщение в чат и производит манипуляции с некоторыми предыдущими своими сообщениями (или, опять же, не производит).
Менять системное время глобально ой, как не хотелось. Муторно, плюс у меня в ней столько всего понаставлено, не дай Б-г что-то заглючит (вряд ли, но мало ли). Думал запустить VirtualBox, но уж больно лень было ставить «чистую» Убунту, расшаривать папки, и т. д., тем более что этот вариант жрёт, как троглодит серьёзно потребляет машинные ресурсы.
Но буквально недавно я начал ковырять Docker. «У него просто обязан быть механизм контроля системного времени внутри контейнера», — подумал я. Рассмотрим, что же в результате вышло.
Докер не выручил
Итак, создаём контейнер и залезаем в него:
docker run -it ubuntu bash
Сразу скажу, что в контейнере я работаю как root
, поэтому sudo
не требуется.
Пробую:
date --set='2017-04-20 23:59:50'
Выдаёт date: cannot set date: Operation not permitted
Пробую:
hwclock --set --date='2017-04-20 23:59:50'
Выдаёт hwclock: Cannot access the Hardware Clock via any known method.
Не выходит. Немного погуглив, натыкаюсь на этот ответ. Похоже, что Docker не настолько глубоко производит виртуализацию, как мне казалось. Он использует системное время, и тут уже никак не выкрутишься. Разве что можно поиграться с часовыми поясами, но в моём случае это не годится, мне нужен полный контроль над временем.
Решение оказалось не докеровским
Ещё один вариант — в самой моей программе перехватывать вызовы к системному времени, но, опять же, муторно. Но буквально этажом выше есть ответ, указывающий на некую библиотеку libfaketime. С помощью неё можно подставить «фальшивое время» для запускаемого процесса. Итак, устанавливаю её в контейнер:
git clone https://github.com/wolfcw/libfaketime.git
cd libfaketime
make install
Далее, следуя инструкции в ответе, запускаю бота с заданными переменными окружения:
LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1 FAKETIME_NO_CACHE=1 FAKETIME="2017-04-19 23:59:50" ./run.sh --debug#
Где в LD_PRELOAD
подставляем свежеустановленную библиотеку, а в FAKETIME
пишем время, которое хотим выставить для запускаемого процесса. FAKETIME_NO_CACHE
использовался в примере и предположительно отключает кеширование, используемое для повышения производительности. Не испытывал, но полагаю, что этот параметр необязателен.
Итак, программа запустилась, и действительно время выставилось так, как я хотел. Лишь с одной проблемой — время остановилось. Сообщения дебага показывают постоянно [2017-04-19 23:59:50]
. В этой библиотеке есть одна неинтуитивная особенность. Простое задание времени действительно задаёт и фиксирует его. Что бы время именно начиналось от данной точки, надо задать его, как FAKETIME='@2017-04-19 23:59:50'
. И врёмя пойдёт от этой точки.
Аналог из репозиториев
Оказывается, всё даже проще. Чуть позже я обнаружил, что эта библиотека есть в стандартных репозиториях Ubuntu, и спокойно ставится через apt-get install faketime
. А запускается так:
faketime -f '@2017-04-20 23:59:50' ./run.sh
Не забываем про @ перед временем, здесь такой же синтаксис, но в довольно кратком man
это не сказано. Только в подробном описании на Гитхабе.
Вместо заключения
Таким образом можно быстро и просто регулировать время, воспринимаемое запускаемой программой, будь она в контейнере Docker или в системе-хосте. В ответе указывалось, что если нужно изменить «фальшивое время» из самой программы, то достаточно изменить глобальную переменную. К примеру, на Питоне:
os.environ["FAKETIME"] = "2020-01-01"
Возможно есть другие, более удобные способы регулирования времени для процесса? Расскажите о них в комментариях.
Комментарии (1)
18 апреля 2017 в 14:58 (комментарий был изменён)
0↑
↓
Возможно есть другие, более удобные способы регулирования времени для процесса?
Немного оффтоп, т.к. не для процесса…
Но на практике, можно организовать классы/модули так, чтобы в unit-тестах просто подставлять нужное время без всяких костылей.
В теории можно в test env и для ручного тестирования похожий подход использовать.
Дело в том, что тут вся соль в тестировании алгоритмов самой вашей программы. Python от ОСи время может получать, это тестировать на мой взгляд — излишне.