defmodule GitGud.WorkflowsDurationsTest do
@moduledoc """
The run-duration history behind the CI charts.
What the charts need and the run list doesn't: only finished runs,
oldest first, with the duration computed once in the database rather
than re-derived per view.
"""
use GitGud.DataCase, async: false
import GitGud.ForgeFixtures
alias GitGud.Workflows
alias GitGud.Workflows.WorkflowRun
setup do
{user, repo} = repository_fixture()
%{user: user, repo: repo}
end
defp run(repo, number, opts) do
# `:utc_datetime` columns reject microseconds, and `utc_now/0` has
# them.
default_start = DateTime.add(DateTime.utc_now(:second), -number * 60, :second)
started = Keyword.get(opts, :started, default_start)
seconds = Keyword.get(opts, :seconds)
%WorkflowRun{repository_id: repo.id}
|> WorkflowRun.create_changeset(%{
number: number,
workflow_path: ".forgejo/workflows/build.yml",
trigger: "push",
head_sha: :crypto.strong_rand_bytes(20)
})
|> Ecto.Changeset.change(
status: Keyword.get(opts, :status, "success"),
conclusion: Keyword.get(opts, :conclusion, "success"),
started_at: started,
completed_at: seconds && DateTime.add(started, seconds, :second)
)
|> GitGud.Repo.insert!()
end
describe "run_durations/2" do
test "returns finished runs oldest first, with their duration", %{repo: repo} do
run(repo, 1, seconds: 30, started: ~U[2026-09-01 10:00:00Z])
run(repo, 2, seconds: 90, started: ~U[2026-09-02 10:00:00Z])
assert [first, second] = Workflows.run_durations(repo)
assert first.number == 1
assert first.seconds == 30
assert second.number == 2
assert second.seconds == 90
end
test "skips runs that haven't finished", %{repo: repo} do
run(repo, 1, seconds: 30)
run(repo, 2, seconds: nil, status: "running", conclusion: nil)
assert [only] = Workflows.run_durations(repo)
assert only.number == 1
end
test "keeps failures — a slow failing run is the interesting one", %{repo: repo} do
run(repo, 1, seconds: 10, status: "failure", conclusion: "failure")
assert [point] = Workflows.run_durations(repo)
assert point.conclusion == "failure"
end
test "honours the limit, keeping the most recent", %{repo: repo} do
for n <- 1..5,
do:
run(repo, n,
seconds: n,
started: DateTime.add(~U[2026-09-01 00:00:00Z], n * 3600, :second)
)
points = Workflows.run_durations(repo, limit: 2)
assert Enum.map(points, & &1.number) == [4, 5]
end
test "a clock that went backwards doesn't make a negative bar", %{repo: repo} do
run(repo, 1, seconds: -30)
assert [point] = Workflows.run_durations(repo)
assert point.seconds == 0
end
end
describe "run_durations_for_repos/2" do
test "groups by repository in one query", %{repo: repo, user: user} do
{_user, other} = repository_fixture(%{owner: user})
run(repo, 1, seconds: 10)
run(other, 1, seconds: 20)
histories = Workflows.run_durations_for_repos([repo.id, other.id])
assert [%{seconds: 10}] = histories[repo.id]
assert [%{seconds: 20}] = histories[other.id]
end
test "caps each repository's series independently", %{repo: repo} do
for n <- 1..5,
do:
run(repo, n,
seconds: n,
started: DateTime.add(~U[2026-09-01 00:00:00Z], n * 3600, :second)
)
assert [%{number: 4}, %{number: 5}] =
Workflows.run_durations_for_repos([repo.id], per_repo: 2)[repo.id]
end
test "is empty for no repositories, rather than querying for none" do
assert Workflows.run_durations_for_repos([]) == %{}
end
end
describe "median_duration/1" do
test "is the middle of an odd series" do
assert Workflows.median_duration([%{seconds: 10}, %{seconds: 90}, %{seconds: 20}]) == 20
end
test "averages the two middles of an even one" do
assert Workflows.median_duration([%{seconds: 10}, %{seconds: 20}]) == 15
end
test "resists one pathological run, which a mean would not" do
points = Enum.map([10, 10, 10, 10, 6000], &%{seconds: &1})
assert Workflows.median_duration(points) == 10
end
test "is nil for nothing" do
assert Workflows.median_duration([]) == nil
end
end
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
4.4 KiB · text
History
b868907