From 360ade93e9dc5569153c7a69ec68ee38963c0e6d Mon Sep 17 00:00:00 2001 From: Colin Gillespie Date: Thu, 15 Oct 2026 13:57:50 +0100 Subject: [PATCH 1/5] fix: Compare versions as numbers and don't call unknown newer releases EOL Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QVzHxqR2weizVuCKLw3fZm --- R/software-versions.R | 31 ++++++++++++++++++++----- tests/testthat/test-software-versions.R | 31 +++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/R/software-versions.R b/R/software-versions.R index ef62dda..7039ef4 100644 --- a/R/software-versions.R +++ b/R/software-versions.R @@ -49,7 +49,12 @@ print_colour_version = function(row) { return(invisible(NULL)) } - if (isFALSE(row$within_eol)) { + if (is.na(row$patch) && isTRUE(row$within_eol)) { + cli::cli_alert_info( + "{software_name}: v{row$installed_version} installed, which is newer than \\ + audit.base's version list. Try updating audit.base." + ) + } else if (isFALSE(row$within_eol)) { cli::cli_alert_danger( "{software_name}: v{row$installed_version} installed, \\ but EOL has past and should be removed." @@ -67,6 +72,16 @@ print_colour_version = function(row) { invisible(NULL) } +# Compare X.Y versions as numbers, so that 1.10 > 1.9. NA if either side is missing. +version_compare = function(x, y, op) { + out = rep(NA, length(x)) + ok = !is.na(x) & !is.na(y) + out[ok] = op(numeric_version(x[ok]), numeric_version(y[ok])) + out +} +max_version = function(x) as.character(max(numeric_version(x))) +min_version = function(x) as.character(min(numeric_version(x))) + # Returns X.Y from X.Y.Z get_major = function(v) unlist(stringr::str_match(v, "^([0-9]*\\.[0-9]*)")[, 2]) # Returns Z - use numeric for comparisons @@ -79,14 +94,14 @@ in_db = function(installed, remote = FALSE) { # latest/earliest versions stored in DB software_range = get_latest_versions(remote = remote) %>% dplyr::group_by(.data$software) %>% - dplyr::summarise(latest = max(.data$major), earliest = min(.data$major)) + dplyr::summarise(latest = max_version(.data$major), earliest = min_version(.data$major)) # Check installed packages are in software range installed %>% dplyr::left_join(software_range, by = c("software" = "software")) %>% dplyr::mutate( - to_old = .data$installed_major < .data$earliest, - to_new = .data$installed_major > .data$latest + to_old = version_compare(.data$installed_major, .data$earliest, `<`), + to_new = version_compare(.data$installed_major, .data$latest, `>`) ) %>% dplyr::select(-"latest", -"earliest") } @@ -147,10 +162,14 @@ versions_to_display = function(installed, remote = FALSE) { ) %>% dplyr::arrange(.data$software, .data$version) - # Old versions don't appear - so just tidy up some NAs + # Versions missing from the list are past EOL, unless they're newer than the list l = dplyr::mutate( all, - within_eol = ifelse(is.na(.data$within_eol), FALSE, .data$within_eol), + within_eol = dplyr::case_when( + !is.na(.data$within_eol) ~ .data$within_eol, + .data$to_new %in% TRUE ~ TRUE, + .default = FALSE + ), version = ifelse(is.na(.data$version), .data$installed_version, .data$version) ) diff --git a/tests/testthat/test-software-versions.R b/tests/testthat/test-software-versions.R index 5e06158..0c5a5a7 100644 --- a/tests/testthat/test-software-versions.R +++ b/tests/testthat/test-software-versions.R @@ -93,3 +93,34 @@ test_that("get_latest_versions() falls back to cache when remote lookup fails", versions = suppressMessages(get_latest_versions(remote = TRUE)) expect_equal(versions, suppressMessages(get_latest_versions(remote = FALSE))) }) + +test_that("Versions are compared as numbers, not text", { + expect_equal( + version_compare(c("1.10", "1.9", NA), c("1.9", "1.10", "1.9"), `>`), + c(TRUE, FALSE, NA) + ) + expect_equal(max_version(c("1.9", "1.10", "1.3")), "1.10") + expect_equal(min_version(c("1.9", "1.10", "1.3")), "1.3") +}) + +test_that("A release newer than the version list isn't reported as past EOL", { + local_mocked_bindings( + get_latest_versions = function(remote = FALSE) { + versions = tibble::tibble( + software = factor("quarto"), + version = c("1.8.27", "1.9.38") + ) + versions$major = get_major(versions$version) + versions$patch = get_patch(versions$version) + versions$version_num = c(2L, 1L) + versions$within_eol = TRUE + dplyr::group_by(versions, .data$software) + } + ) + installed = tibble::tibble(software = "quarto", installed_version = "1.10.18") + augmented = augment_installed(installed, remote = FALSE, verbose = FALSE) + row = augmented[augmented$installed_version %in% "1.10.18", ] + expect_true(row$within_eol) + expect_false(row$upgrade) + expect_message(print_colour_version(row), "newer than") +}) From df9d9823f633b6148a44a2b5fab98de032dade26 Mon Sep 17 00:00:00 2001 From: Colin Gillespie Date: Thu, 15 Oct 2026 13:57:50 +0100 Subject: [PATCH 2/5] chore: Bump version to 0.7.3 --- DESCRIPTION | 2 +- NEWS.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index ab34b5f..ad94618 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: audit.base Title: Base package for Posit Checks -Version: 0.7.2 +Version: 0.7.3 Authors@R: person("Jumping", "Rivers", , "info@jumpingrivers.com", role = c("aut", "cre")) Description: Base package for sharing classes between posit audit diff --git a/NEWS.md b/NEWS.md index 4dc5641..908906d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# audit.base 0.7.3 _2026-10-15_ + * fix: A release newer than the version list is reported as newer than the list, not as past EOL (#55) + * fix: Versions are compared as numbers, so Quarto 1.10 counts as newer than 1.9 + # audit.base 0.7.2 _2026-10-15_ * fix: The Quarto Python test installs on Python 3.14. Its `requirements.txt` lists jupyter, matplotlib and numpy with lower bounds instead of 107 exact pins (#54) * chore: Add Posit Workbench 2026.10.0 to the cached versions From 537feefd7c00b333f95a37cec263e839a9649d47 Mon Sep 17 00:00:00 2001 From: Colin Gillespie Date: Thu, 15 Oct 2026 14:10:59 +0100 Subject: [PATCH 3/5] chore: Test a Quarto version far in the future Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QVzHxqR2weizVuCKLw3fZm --- tests/testthat/test-software-versions.R | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/testthat/test-software-versions.R b/tests/testthat/test-software-versions.R index 0c5a5a7..3d536fd 100644 --- a/tests/testthat/test-software-versions.R +++ b/tests/testthat/test-software-versions.R @@ -124,3 +124,13 @@ test_that("A release newer than the version list isn't reported as past EOL", { expect_false(row$upgrade) expect_message(print_colour_version(row), "newer than") }) + +test_that("A Quarto version far in the future is newer than the list, not past EOL", { + installed = tibble::tibble(software = "quarto", installed_version = "99.99.1") + augmented = augment_installed(installed, remote = FALSE, verbose = FALSE) + row = augmented[augmented$installed_version %in% "99.99.1", ] + expect_equal(nrow(row), 1) + expect_true(row$within_eol) + expect_false(row$upgrade) + expect_message(print_colour_version(row), "newer than") +}) From 6a7dfff73cbb5fd0b6a4e15b730a02edb784049c Mon Sep 17 00:00:00 2001 From: Colin Gillespie Date: Thu, 15 Oct 2026 14:12:14 +0100 Subject: [PATCH 4/5] chore: Test a Quarto version older than the version list Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QVzHxqR2weizVuCKLw3fZm --- tests/testthat/test-software-versions.R | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/testthat/test-software-versions.R b/tests/testthat/test-software-versions.R index 3d536fd..440b865 100644 --- a/tests/testthat/test-software-versions.R +++ b/tests/testthat/test-software-versions.R @@ -134,3 +134,13 @@ test_that("A Quarto version far in the future is newer than the list, not past E expect_false(row$upgrade) expect_message(print_colour_version(row), "newer than") }) + +test_that("A Quarto version older than the list is past EOL and needs upgrading", { + installed = tibble::tibble(software = "quarto", installed_version = "0.0.0") + augmented = augment_installed(installed, remote = FALSE, verbose = FALSE) + row = augmented[augmented$installed_version %in% "0.0.0", ] + expect_equal(nrow(row), 1) + expect_false(row$within_eol) + expect_true(row$upgrade) + expect_message(print_colour_version(row), "EOL has past") +}) From 8efa4d506283fd29ff7551005cc860fc2dd3708c Mon Sep 17 00:00:00 2001 From: Colin Gillespie Date: Thu, 15 Oct 2026 14:15:09 +0100 Subject: [PATCH 5/5] chore: Combine the version list tests and use the real data Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QVzHxqR2weizVuCKLw3fZm --- tests/testthat/test-software-versions.R | 52 +++++++------------------ 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/tests/testthat/test-software-versions.R b/tests/testthat/test-software-versions.R index 440b865..8fd0f3a 100644 --- a/tests/testthat/test-software-versions.R +++ b/tests/testthat/test-software-versions.R @@ -103,44 +103,22 @@ test_that("Versions are compared as numbers, not text", { expect_equal(min_version(c("1.9", "1.10", "1.3")), "1.3") }) -test_that("A release newer than the version list isn't reported as past EOL", { - local_mocked_bindings( - get_latest_versions = function(remote = FALSE) { - versions = tibble::tibble( - software = factor("quarto"), - version = c("1.8.27", "1.9.38") - ) - versions$major = get_major(versions$version) - versions$patch = get_patch(versions$version) - versions$version_num = c(2L, 1L) - versions$within_eol = TRUE - dplyr::group_by(versions, .data$software) - } - ) - installed = tibble::tibble(software = "quarto", installed_version = "1.10.18") +augment_quarto = function(version) { + installed = tibble::tibble(software = "quarto", installed_version = version) augmented = augment_installed(installed, remote = FALSE, verbose = FALSE) - row = augmented[augmented$installed_version %in% "1.10.18", ] - expect_true(row$within_eol) - expect_false(row$upgrade) - expect_message(print_colour_version(row), "newer than") -}) + augmented[augmented$installed_version %in% version, ] +} -test_that("A Quarto version far in the future is newer than the list, not past EOL", { - installed = tibble::tibble(software = "quarto", installed_version = "99.99.1") - augmented = augment_installed(installed, remote = FALSE, verbose = FALSE) - row = augmented[augmented$installed_version %in% "99.99.1", ] - expect_equal(nrow(row), 1) - expect_true(row$within_eol) - expect_false(row$upgrade) - expect_message(print_colour_version(row), "newer than") -}) +test_that("Quarto versions outside the version list", { + # Older than the list: past EOL, so upgrade + old = augment_quarto("0.0.0") + expect_false(old$within_eol) + expect_true(old$upgrade) + expect_message(print_colour_version(old), "EOL has past") -test_that("A Quarto version older than the list is past EOL and needs upgrading", { - installed = tibble::tibble(software = "quarto", installed_version = "0.0.0") - augmented = augment_installed(installed, remote = FALSE, verbose = FALSE) - row = augmented[augmented$installed_version %in% "0.0.0", ] - expect_equal(nrow(row), 1) - expect_false(row$within_eol) - expect_true(row$upgrade) - expect_message(print_colour_version(row), "EOL has past") + # Newer than the list: the list is out of date, not past EOL + new = augment_quarto("99.99.1") + expect_true(new$within_eol) + expect_false(new$upgrade) + expect_message(print_colour_version(new), "newer than") })