Skip to content

Commit de1a8ef

Browse files
committed
fix(ci): correct print callback arity in AdoCli.CI.Watcher
Bug: the default :print_callback was \&IO.write/2 (a 2-arity function expecting device + data), but every render function called it as `print.(line)` (1-arity). On first status update, this raised: (BadArityError) &IO.write/2 with arity 2 called with 1 argument ("? Build 9655 · status=inProgress result=nil · <1s\n") (ado_cli 0.2.2) lib/ado_cli/ci/watcher.ex:124: render_status/3 So every invocation of `ado ci watch` crashed on the first poll, before it could ever print a single status line. Fix: change the default to a 1-arity wrapper: &IO.write(:stdio, &1) Also made render_status/3 and render_final/2 public (`def` instead of `defp`) so they can be unit-tested directly. Added a regression test that exercises render_status/3 with a 1-arity print collector and asserts the printed output. This test would have failed (BadArityError) on the broken version. 315 tests pass, mix ci 8/8 green, reach still 1 finding in our code (logout behaviour candidate, false positive).
1 parent 8ac8dde commit de1a8ef

2 files changed

Lines changed: 43 additions & 5 deletions

File tree

lib/ado_cli/ci/watcher.ex

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ defmodule AdoCli.CI.Watcher do
3737
## Options
3838
3939
* `:poll_ms` — how often to poll (default 2000ms, min 250ms)
40-
* `:print_callback` — function for output (default: &IO.write/2).
41-
Tests inject a collector here.
40+
* `:print_callback` — 1-arity function for output (default:
41+
`&IO.write(:stdio, &1)`). Tests inject a collector here.
4242
"""
4343
@spec watch(integer(), String.t(), String.t() | nil, keyword()) ::
4444
:ok | {:error, term()}
4545
def watch(build_id, project, org, opts \\ []) do
4646
poll_ms = max(Keyword.get(opts, :poll_ms, 2000), 250)
47-
print = Keyword.get(opts, :print_callback, &IO.write/2)
47+
print = Keyword.get(opts, :print_callback, &IO.write(:stdio, &1))
4848

4949
state = %{
5050
build_id: build_id,
@@ -99,7 +99,9 @@ defmodule AdoCli.CI.Watcher do
9999

100100
# ── status rendering ────────────────────────────────────────────────
101101

102-
defp render_status(build, started_at, print) do
102+
# Public for unit testing. Not part of the user-facing API; the
103+
# only caller is the watcher loop in this module.
104+
def render_status(build, started_at, print) do
103105
elapsed_ms = System.monotonic_time(:millisecond) - started_at
104106
elapsed = format_duration(elapsed_ms)
105107

@@ -124,7 +126,8 @@ defmodule AdoCli.CI.Watcher do
124126
print.(line <> "\n")
125127
end
126128

127-
defp render_final(build, print) do
129+
# Public for unit testing. Not part of the user-facing API.
130+
def render_final(build, print) do
128131
case build do
129132
%{"result" => "succeeded"} ->
130133
print.("\n Build succeeded.\n")

test/ado_cli/ci/watcher_test.exs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,41 @@ defmodule AdoCli.CI.WatcherTest do
3838
end
3939
end
4040

41+
describe "render_status/3 (regression: BadArityError on print callback)" do
42+
# In v0.2.1/v0.2.2 the default :print_callback was &IO.write/2
43+
# (a 2-arity function), but render_status/3 calls it as
44+
# `print.(line)` — 1-arity. That raised BadArityError on every
45+
# status update. The fix changed the default to a 1-arity
46+
# wrapper: `&IO.write(:stdio, &1)`.
47+
#
48+
# This test exercises render_status/3 directly with a simple
49+
# 1-arity print collector and asserts the printed output. It
50+
# would also have caught the original bug (it would have raised
51+
# BadArityError at `print.(line)`).
52+
test "prints status line via a 1-arity print callback" do
53+
build = %{
54+
"id" => 9655,
55+
"status" => "inProgress",
56+
"result" => nil,
57+
"definition" => %{"name" => "my-pipeline"},
58+
"sourceBranch" => "refs/heads/main"
59+
}
60+
61+
parent = self()
62+
print = fn line -> send(parent, {:printed, line}) end
63+
64+
started_at = System.monotonic_time(:millisecond)
65+
Watcher.render_status(build, started_at, print)
66+
67+
assert_receive {:printed, line}, 500
68+
assert line =~ "Build 9655"
69+
assert line =~ "my-pipeline"
70+
assert line =~ "refs/heads/main"
71+
assert line =~ "running for"
72+
assert line =~ "\n"
73+
end
74+
end
75+
4176
describe "stream_log (with mock client)" do
4277
test "prints new log lines" do
4378
output = run_with_mocked_log(build_id: 123, log_id: 7, lines: ["line one", "line two"])

0 commit comments

Comments
 (0)