defmodule GitGud.Workflows.CommitStatusTest do
@moduledoc """
One commit status per job, named `<workflow> / <job> (<event>)` as
Forgejo names them.
Runs are made by `rerun/1` of a stored document, which creates jobs by
the same path a dispatch does.
"""
use GitGud.DataCase, async: false
import GitGud.ForgeFixtures
alias GitGud.Workflows
alias GitGud.Workflows.CommitStatus
alias GitGud.Workflows.WorkflowJob
alias GitGud.Workflows.WorkflowRun
alias GitGud.Workflows.Yaml
@ci """
name: ci
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: mix test
lint:
runs-on: ubuntu-latest
steps:
- run: mix format --check-formatted
"""
setup do
{_user, repo} = repository_fixture()
{:ok, repo: repo, sha: :crypto.strong_rand_bytes(20)}
end
defp run_workflow(repo, sha, yaml, trigger \\ "push") do
{:ok, doc} = Yaml.parse(yaml)
seed =
%WorkflowRun{repository_id: repo.id}
|> WorkflowRun.create_changeset(%{
"number" => System.unique_integer([:positive]),
"workflow_path" => ".forgejo/workflows/ci.yml",
"workflow_name" => doc.name,
"trigger" => trigger,
"head_ref" => "refs/heads/main",
"head_sha" => sha,
"workflow_doc" => doc.raw
})
|> Repo.insert!()
{:ok, run} = Workflows.rerun(seed)
run
end
defp statuses(repo, sha),
do: repo |> Workflows.list_commit_statuses(sha) |> Map.new(&{&1.context, &1})
defp job(run, job_id) do
Repo.one!(from j in WorkflowJob, where: j.workflow_run_id == ^run.id and j.job_id == ^job_id)
end
test "each job gets its own pending status, and fails on its own", %{repo: repo, sha: sha} do
run = run_workflow(repo, sha, @ci)
assert %{
"ci / lint (push)" => %CommitStatus{state: "pending", description: "Waiting to run"},
"ci / test (push)" => %CommitStatus{state: "pending"} = test_status
} = statuses(repo, sha)
assert test_status.target_url =~ ~r"/actions/#{run.number}#job-\d+$"
{:ok, _} = Workflows.update_job_status(job(run, "lint"), "failure")
assert %{
"ci / lint (push)" => %CommitStatus{state: "failure", description: "Failing" <> _},
"ci / test (push)" => %CommitStatus{state: "pending"}
} = statuses(repo, sha)
{:ok, _} = Workflows.update_job_status(job(run, "test"), "success")
assert %{"ci / test (push)" => %CommitStatus{state: "success"}} = statuses(repo, sha)
end
test "a push run and a pull_request run on one head don't overwrite each other",
%{repo: repo, sha: sha} do
push = run_workflow(repo, sha, @ci, "push")
pr = run_workflow(repo, sha, @ci, "pull_request")
assert statuses(repo, sha) |> Map.keys() |> Enum.sort() == [
"ci / lint (pull_request)",
"ci / lint (push)",
"ci / test (pull_request)",
"ci / test (push)"
]
{:ok, _} = Workflows.update_job_status(job(push, "test"), "success")
{:ok, _} = Workflows.update_job_status(job(pr, "test"), "failure")
assert %{
"ci / test (push)" => %CommitStatus{state: "success"},
"ci / test (pull_request)" => %CommitStatus{state: "failure"}
} = statuses(repo, sha)
end
test "cancelling a run fails each cancelled job's status", %{repo: repo, sha: sha} do
run = run_workflow(repo, sha, @ci)
{:ok, _} = Workflows.update_job_status(job(run, "lint"), "success")
{:ok, _} = Workflows.cancel(Repo.get!(WorkflowRun, run.id))
assert %{
"ci / test (push)" => %CommitStatus{state: "failure", description: "Cancelled"},
"ci / lint (push)" => %CommitStatus{state: "success"}
} = statuses(repo, sha)
end
test "a run superseded in its concurrency group shows as cancelled, not failed",
%{repo: repo} do
yaml = """
name: deploy
on: push
concurrency: {group: deploy, cancel-in-progress: true}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: ./deploy
"""
old_sha = :crypto.strong_rand_bytes(20)
_old = run_workflow(repo, old_sha, yaml)
_new = run_workflow(repo, :crypto.strong_rand_bytes(20), yaml)
assert %{
"deploy / deploy (push)" => %CommitStatus{
state: "cancelled",
description: "Cancelled by run #" <> _
}
} = statuses(repo, old_sha)
end
test "a skipped job passes, saying so", %{repo: repo, sha: sha} do
run =
run_workflow(repo, sha, """
name: ci
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: make
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: ./deploy
""")
assert %{
"ci / deploy (push)" => %CommitStatus{
state: "pending",
description: "Waiting for build"
}
} =
statuses(repo, sha)
{:ok, _} = Workflows.update_job_status(job(run, "build"), "failure")
assert %{"ci / deploy (push)" => %CommitStatus{state: "success", description: "Skipped"}} =
statuses(repo, sha)
end
test "a warning job reports warning, for either reason", %{repo: repo, sha: sha} do
run =
run_workflow(repo, sha, """
name: ci
on: push
jobs:
experimental:
continue-on-error: true
runs-on: ubuntu-latest
steps:
- run: false
audit:
runs-on: ubuntu-latest
outputs:
gitgud-conclusion: ${{ steps.scan.outputs.conclusion }}
steps:
- id: scan
run: ./scan
""")
{:ok, _} = Workflows.update_job_status(job(run, "experimental"), "failure")
{:ok, audit} = Workflows.set_outputs(job(run, "audit"), %{"gitgud-conclusion" => "warning"})
{:ok, _} = Workflows.update_job_status(audit, "success")
assert %{
"ci / experimental (push)" => %CommitStatus{
state: "warning",
description: "Failing" <> experimental
},
"ci / audit (push)" => %CommitStatus{state: "warning", description: "Warning" <> _}
} = statuses(repo, sha)
assert experimental =~ "continue-on-error"
assert Repo.get!(WorkflowRun, run.id).status == "success"
end
test "a re-run drops the old per-workflow status", %{repo: repo, sha: sha} do
Repo.insert!(%CommitStatus{
repository_id: repo.id,
sha: sha,
context: "gitgud-actions/ci",
state: "failure"
})
_run = run_workflow(repo, sha, @ci)
refute Map.has_key?(statuses(repo, sha), "gitgud-actions/ci")
end
test "every status write wakes the CI topic", %{repo: repo, sha: sha} do
run = run_workflow(repo, sha, @ci)
Phoenix.PubSub.subscribe(GitGud.PubSub, Workflows.ci_topic())
run_id = run.id
{:ok, _} = Workflows.update_job_status(job(run, "lint"), "failure")
assert_receive {:ci_run_changed, ^run_id}
assert Workflows.run_repository_id(run_id) == repo.id
end
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
7.0 KiB · text
History
b868907