Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions bin/fm-tool-update-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
# "<tool> update not in effect" a newer copy is installed on this host, but
# PATH still resolves an older one.
#
# A tool that announces its own update is only reported as "update available"
# when the version it announces is newer than the newest installed copy found;
# a version already installed is reported only as "update not in effect".
#
# The second condition is the reason this script exists. A tool that
# self-installs into ~/.local/bin while a version manager keeps its own older
# copy earlier on PATH looks fully up to date to anything that asks only "is a
Expand Down Expand Up @@ -243,6 +247,12 @@ parse_version() {
printf '%s' "$1" | grep -oE '[0-9]+(\.[0-9]+)+' | head -n 1
}

# Last dotted number in the text: an announcement phrase like "v1.46.0 ->
# v1.47.0" names the current version first and the announced version last.
parse_announced_version() {
printf '%s' "$1" | grep -oE '[0-9]+(\.[0-9]+)+' | tail -n 1
}

# version_newer <a> <b>: true when version a is numerically newer than b.
version_newer() {
local a=$1 b=$2 i left right
Expand Down Expand Up @@ -403,7 +413,7 @@ probe_output() {

command_findings() {
local name=$1 command_name=$2 args_joined=$3 announce=$4 announce_args=$5
local hit out version matched announce_out status
local hit out version matched announce_out status matched_line announced_version
local resolved_path='' resolved_version='' resolved_out=''
local best_path='' best_version='' unreadable='' hits=''

Expand Down Expand Up @@ -478,7 +488,14 @@ EOF
if [ "$status" -gt 1 ]; then
emit "$name check failed: announce_pattern is not a usable extended regular expression"
elif [ -n "$matched" ]; then
emit "$name update available: $(printf '%s\n' "$matched" | head -n 1)"
matched_line=$(printf '%s\n' "$matched" | head -n 1)
announced_version=$(parse_announced_version "$matched_line")
# An announcement naming no readable version is reported as today; one
# naming a version already installed is not an available update.
if [ -z "$announced_version" ] || [ -z "$best_version" ] \
|| version_newer "$announced_version" "$best_version"; then
emit "$name update available: $matched_line"
fi
fi
fi
fi
Expand Down
3 changes: 2 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,8 @@ This section is the single owner of the canonical schema.
**Entry fields and probe behavior**

- Each entry needs a `name` and at least one of `command` or `git`; an entry may carry both.
- A `command` entry gives the `PATH` comparison above, and adding `announce_pattern` also reports the tool's own update announcement, which is how a tool that already reports its own updates is read rather than reimplemented.
- A `command` entry gives the `PATH` comparison above, and adding `announce_pattern` also reads the tool's own update announcement, which is how a tool that already reports its own updates is read rather than reimplemented.
- The announcement counts as `update available` only when the version it names is newer than the newest installed copy found; a version already installed is reported only as `update not in effect`, so one completed install does not report both in the same sweep. An announcement naming no readable version is reported as an available update as before.
- A tool does not always announce a new release on the command that prints its version: `no-mistakes --version` prints only the version, while its other commands carry the announcement.
- `announce_args` names the command to search for the announcement in that case, and it is asked only of the copy `PATH` resolves; without it the version probe's own output is searched.
- An `announce_pattern` that is not a usable extended regular expression stops `arm`, and during a sweep it is reported as that one tool's own check failure so one broken pattern never stops the other watched tools from being checked.
Expand Down
52 changes: 52 additions & 0 deletions tests/fm-tool-update-check.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,56 @@ SH
pass "a tool's own update announcement is read from its output"
}

test_announced_update_already_installed_is_not_double_reported() {
local home first second out report
# One completed install: the newer copy sits on PATH behind the older
# self-installing copy, so PATH skew is already reported. The older copy
# keeps announcing the very release it has already been superseded by, and
# that announcement must not also be read as a still-available update.
home=$(make_home announce-installed)
first="$TMP_ROOT/announce-installed/old/bin"
second="$TMP_ROOT/announce-installed/new/bin"
mkdir -p "$first" "$second"
cat > "$first/no-mistakes-fixture" <<'SH'
#!/usr/bin/env bash
printf '1.46.0\n'
printf 'A new version of no-mistakes is available: v1.46.0 -> v1.47.0\n' >&2
SH
chmod 0755 "$first/no-mistakes-fixture"
make_copy "$second" "no-mistakes-fixture" '1.47.0'
write_config "$home" '{"tools":[{"name":"no-mistakes","command":"no-mistakes-fixture","announce_pattern":"A new version of no-mistakes is available: [^ ]+ -> [^ ]+"}]}'
out="$home/out.txt"
run_check "$home" "$(fixture_path "$first:$second")" "$out"
report=$(cat "$out")
assert_contains "$report" "no-mistakes update not in effect" "the already-installed newer copy was not reported as PATH skew"
assert_not_contains "$report" "update available" "an announcement naming an already-installed version was also reported as a still-available update"
pass "an announcement naming an already-installed version is not also reported as an available update"
}

test_announced_update_newer_than_installed_is_still_reported() {
local home first second out report
# Control: the announced version is genuinely newer than every installed
# copy, so it must still be reported as available alongside the skew.
home=$(make_home announce-not-installed)
first="$TMP_ROOT/announce-not-installed/old/bin"
second="$TMP_ROOT/announce-not-installed/new/bin"
mkdir -p "$first" "$second"
cat > "$first/no-mistakes-fixture" <<'SH'
#!/usr/bin/env bash
printf '1.46.0\n'
printf 'A new version of no-mistakes is available: v1.46.0 -> v1.47.0\n' >&2
SH
chmod 0755 "$first/no-mistakes-fixture"
make_copy "$second" "no-mistakes-fixture" '1.46.5'
write_config "$home" '{"tools":[{"name":"no-mistakes","command":"no-mistakes-fixture","announce_pattern":"A new version of no-mistakes is available: [^ ]+ -> [^ ]+"}]}'
out="$home/out.txt"
run_check "$home" "$(fixture_path "$first:$second")" "$out"
report=$(cat "$out")
assert_contains "$report" "no-mistakes update available: A new version of no-mistakes is available: v1.46.0 -> v1.47.0" "an announcement naming a version newer than every installed copy was not reported"
assert_contains "$report" "no-mistakes update not in effect" "the installed newer-than-resolved copy was not reported as PATH skew"
pass "an announcement naming a version newer than every installed copy is still reported as available"
}

test_announcement_is_read_from_a_second_command() {
local home dir out report quiet_home
# The real no-mistakes prints its version for --version but announces a new
Expand Down Expand Up @@ -1012,6 +1062,8 @@ test_one_copy_reached_twice_is_probed_once
test_unreadable_version_is_a_failure_not_a_pass
test_missing_command_is_reported
test_announced_update_is_reported_from_the_tool_itself
test_announced_update_already_installed_is_not_double_reported
test_announced_update_newer_than_installed_is_still_reported
test_announcement_is_read_from_a_second_command
test_unusable_announce_pattern_is_reported_not_read_as_silence
test_one_broken_pattern_does_not_blind_the_rest_of_the_sweep
Expand Down
Loading