Change current time of EVM in tests

Hi all,

I’m currently implementing a protocol that relies on a lot of crypto and certificates. I got some samples to use in my tests and one of them contains a certificate path that I need to validate. However, the valid_until field of the leaf certificate is 280520162044Z: the certificate has expired.

However, I cannot change it since I don’t have the parent certificate’s corresponding private key, and I guess changing a field would change the signature.

Therefore, is it possible to change the current time of the EVM to make my test pass, and using Erlang functions (not by changing the time of my machine)? And just in one (synced) test?

1 Like

You could mock out your source of time. Searching for timecop elixir also found a package by @kokolegorille so they are probably a good source of info.

2 Likes

Basically always accept a time argument to your function, defaulting to now. Then you just pass in a constant time within your test.

10 Likes

Ok so there’s no way to programmatically change the EVM time (which kind of seems a good thing after all) and I think you refer to this article: https://blog.sundaycoding.com/blog/2018/01/12/mock-current-time-in-elixir/

There’s an interesting comment saying it’s better to use the process dictionary since “time” change is limited to the process (the test case in my case) and is destroyed when the (test) process dies. So that you can run your tests in parallel! Thanks for the tip :slight_smile:

Here’s the code:

if
...
and parse_cert_datetime(valid_from) < Wax.Utils.Timestamp.get_timestamp()
and parse_cert_datetime(valid_to) > Wax.Utils.Timestamp.get_timestamp()
...

(instead of directly calling :os.system_time(:second))

Wax.Utils.Timestamp:

defmodule Wax.Utils.Timestamp do
  @adapter if Mix.env() == :test, do: Wax.Utils.Timestamp.TimeTravel, else: Wax.Utils.Timestamp.Real

  @spec get_timestamp() :: non_neg_integer()

  def get_timestamp do
    @adapter.get_timestamp()
  end
end

Wax.Utils.Timestamp.Real

defmodule Wax.Utils.Timestamp.Real do
  @spec get_timestamp() :: non_neg_integer()

  def get_timestamp() do
    :os.system_time(:second)
  end
end

Wax.Utils.Timestamp.TimeTravel

defmodule Wax.Utils.Timestamp.TimeTravel do
  @spec get_timestamp() :: non_neg_integer()

  def get_timestamp() do
    if Process.get(:mock_time) do
      Process.get(:mock_time)
    else
      :os.system_time(:second)
    end
  end

  @spec set_timestamp(non_neg_integer()) :: no_return()

  def set_timestamp(timestamp) do
    Process.put(:mock_time, timestamp)
  end
end

and finally its use in the test case:

~N[2016-05-26 00:00:00]
|> DateTime.from_naive!("Etc/UTC")
|> DateTime.to_unix()
|> Wax.Utils.Timestamp.TimeTravel.set_timestamp()

(I <3 Elixir juste for this syntactic sugar)

Any comment is welcome!

Basically always accept a time argument to your function, defaulting to now. Then you just pass in a constant time within your test.

I agree. I’ve done this with my own applications that deal heavily with time.

3 Likes