Last active
February 5, 2025 20:03
-
-
Save Nezteb/d0e1c0d7ed1a7d2bf1a58782a8aea6a0 to your computer and use it in GitHub Desktop.
Playing around with DST and ExUnit seed generation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Simulator do | |
@moduledoc """ | |
Handles parsing of different seed formats for simulation purposes. | |
Supports Git hashes, ISO8601 dates, and normal integer seeds. | |
Based on: | |
- https://docs.tigerbeetle.com/about/vopr/ | |
- https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/HACKING.md#simulation-tests | |
- https://github.com/tigerbeetle/tigerbeetle/blob/main/src/testing/fuzz.zig#L72-L90 | |
- https://github.com/whatyouhide/stream_data/blob/main/lib/ex_unit_properties.ex#L674-L689 | |
- https://notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html | |
""" | |
@git_hash_length 40 | |
@max_seed_value Bitwise.bsl(1, 64) | |
@type seed_error :: :invalid_seed | |
@type parse_result :: {:ok, non_neg_integer()} | {:error, seed_error} | |
@doc """ | |
Parses a seed value from either: | |
- A Git hash (40 characters) | |
- An ISO8601 datetime string | |
- A decimal string | |
Returns a 64-bit integer seed value wrapped in a result tuple. | |
## Examples | |
iex> Simulator.parse_seed("1234567890abcdef1234567890abcdef12345678") | |
{:ok, 10424652189165442680} | |
iex> Simulator.parse_seed("2025-02-05T12:00:00Z") | |
{:ok, 1738756800000000} | |
iex> Simulator.parse_seed("12345") | |
{:ok, 12345} | |
iex> Simulator.parse_seed("invalid") | |
{:error, :invalid_seed} | |
""" | |
@spec parse_seed(binary()) :: parse_result() | |
def parse_seed(input) when is_binary(input) do | |
input | |
|> determine_input_type() | |
|> parse_input() | |
rescue | |
_ -> {:error, :invalid_seed} | |
end | |
defp determine_input_type(input) do | |
cond do | |
is_length_of_git_hash?(input) -> {:git_hash, input} | |
is_iso_datetime_string?(input) -> {:datetime, input} | |
true -> {:integer, input} | |
end | |
end | |
defp is_length_of_git_hash?(string), do: byte_size(string) == @git_hash_length | |
defp is_iso_datetime_string?(string) do | |
case DateTime.from_iso8601(string) do | |
{:ok, _datetime, _offset} -> true | |
_ -> false | |
end | |
end | |
defp parse_input({:git_hash, hash}) do | |
seed = | |
hash | |
|> String.downcase() | |
|> Base.decode16!(case: :lower) | |
|> :binary.decode_unsigned() | |
|> normalize_seed() | |
{:ok, seed} | |
end | |
defp parse_input({:datetime, dt_string}) do | |
{:ok, datetime, 0} = DateTime.from_iso8601(dt_string) | |
seed = | |
datetime | |
|> DateTime.to_unix(:microsecond) | |
|> normalize_seed() | |
{:ok, seed} | |
end | |
defp parse_input({:integer, int_string}) do | |
{num, ""} = Integer.parse(int_string) | |
{:ok, normalize_seed(num)} | |
end | |
defp normalize_seed(value) do | |
rem(value, @max_seed_value) | |
end | |
end | |
{:ok, git_sha_seed} = Simulator.parse_seed("1234567890abcdef1234567890abcdef12345678") | |
{:ok, date_time_seed} = Simulator.parse_seed("2025-02-05T12:00:00Z") | |
{:ok, regular_int_seed} = Simulator.parse_seed("12345") | |
{git_sha_seed, date_time_seed, regular_int_seed} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment