defmodule BuildkiteTestCollector.Payload do
@moduledoc """
A structure that represents all data about a test suite run needed for analytics.
"""
defstruct run_env: nil, data: [], started_at: nil, data_size: 0
alias BuildkiteTestCollector.{CiEnv, Instant, Payload, TestResult}
@type t :: %Payload{
run_env: serialised_environment,
data: [TestResult.t()],
started_at: nil | Instant.t(),
data_size: non_neg_integer()
}
@type serialised_environment :: %{
required(:CI) => String.t(),
required(:key) => String.t(),
optional(:number) => String.t(),
optional(:job_id) => String.t(),
optional(:branch) => String.t(),
optional(:commit_sha) => String.t(),
optional(:message) => String.t(),
optional(:url) => String.t()
}
@doc """
Initialise an empty payload with the given CI environment.
"""
@spec init(CiEnv.t()) :: t
def init(ci_env_mod) do
%Payload{
run_env: serialise_env(ci_env_mod)
}
end
@doc """
Push a test pesult into the payload.
"""
@spec push_test_result(Payload.t(), TestResult.t()) :: Payload.t()
def push_test_result(
%Payload{data: data, data_size: size} = payload,
%TestResult{} = test_result
),
do: %Payload{payload | data: [test_result | data], data_size: size + 1}
@doc """
Set the start time of the suite.
"""
@spec set_start_time(Payload.t(), Instant.t()) :: Payload.t()
def set_start_time(%Payload{} = payload, started_at), do: %{payload | started_at: started_at}
defp serialise_env(ci_env_mod) do
~w[CI key number job_id branch commit_sha message url]a
|> Enum.reduce(%{}, fn
:CI, env -> Map.put(env, :CI, ci_env_mod.ci())
key, env -> Map.put(env, key, apply(ci_env_mod, key, []))
end)
end
@doc """
Convert the payload into a map ready for serialisation to JSON.
This is done as a separate step because all timings are relative to the
payload start time, so must be calculated.
"""
@spec as_json(t) :: map
def as_json(%Payload{} = payload) do
%{
format: "json",
run_env: payload.run_env,
data:
payload.data
|> Enum.map(&TestResult.as_json(&1, payload.started_at))
|> Enum.reverse()
}
end
defimpl Jason.Encoder do
@doc false
@spec encode(Payload.t(), Jason.Encode.opts()) :: iodata
def encode(payload, opts) do
payload
|> Payload.as_json()
|> Jason.Encode.map(opts)
end
end
end