diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 95234b6..6f8f4b8 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -16,7 +16,6 @@ jobs: - {os: windows-latest, r: 'release'} - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - {os: ubuntu-latest, r: 'release'} - - {os: ubuntu-latest, r: 'oldrel-1'} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 0000000..485433e --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,50 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main] + pull_request: + branches: [main] + release: + types: [published] + workflow_dispatch: + +name: pkgdown + +jobs: + pkgdown: + runs-on: ubuntu-latest + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-pandoc@v2 + + - name: system dependencies + if: runner.os == 'Linux' + run: sudo apt-get install -y libmbedtls-dev + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + cache-version: 2 + + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Deploy to GitHub pages + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.6.1 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/DESCRIPTION b/DESCRIPTION index 63f2de5..06ecd98 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,7 +2,7 @@ Package: multiverse.internals Title: Internal Infrastructure for R-multiverse Description: R-multiverse requires this internal internal infrastructure package to automate contribution reviews and populate universes. -Version: 0.1.4 +Version: 0.2.0 License: MIT + file LICENSE URL: https://github.com/r-multiverse/multiverse.internals BugReports: https://github.com/r-multiverse/multiverse.internals/issues diff --git a/NAMESPACE b/NAMESPACE index 6a2bdc1..f976b55 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,11 @@ export(assert_cran_url) export(assert_package) export(assert_release_exists) export(get_current_versions) +export(issues_checks) +export(issues_descriptions) +export(issues_versions) +export(meta_checks) +export(meta_packages) export(record_issues) export(record_versions) export(review_pull_request) diff --git a/NEWS.md b/NEWS.md index 9a1b292..45edeef 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# multiverse.internals 0.2.0 + +* Add more checks to `record_issues()`: results from the R-universe check API, plus specific results from package `DESCRIPTION` files. +* Refactor and document specific checks in `check_checks()`, `check_descriptions()`, and `check_versions()`. +* Add a `pkgdown` website. + # multiverse.internals 0.1.4 * Do not write `version_issues.json` from `record_versions()`. diff --git a/R/issues_checks.R b/R/issues_checks.R new file mode 100644 index 0000000..d753a3f --- /dev/null +++ b/R/issues_checks.R @@ -0,0 +1,41 @@ +#' @title Report issues from R-universe package check results. +#' @export +#' @family issues +#' @description Check R-universe package check results. +#' @details [issues_checks()] reads output from +#' the R-universe check API +#' to scan all R-multiverse packages for issues that may have +#' happened during building and testing. +#' @inheritSection record_issues Package issues +#' @return A named list of information about packages which do not comply +#' with `DESCRPTION` checks. Each name is a package name, +#' and each element contains specific information about +#' non-compliance. +#' @param meta A data frame with R-universe package check results +#' returned by [meta_checks()]. +#' @examples +#' meta <- meta_checks(repo = "https://wlandau.r-universe.dev") +#' issues <- issues_checks(meta = meta) +#' str(issues) +issues_checks <- function(meta) { + fields_check <- c( + "_linuxdevel", + "_macbinary", + "_wasmbinary", + "_winbinary", + "_status" + ) + fields_info <- c( + "_buildurl" + ) + fields <- c(fields_check, fields_info) + for (field in fields) { + meta[[field]][is.na(meta[[field]])] <- "src-failure" + } + success <- rep(TRUE, nrow(meta)) + for (field in fields_check) { + success <- success & (meta[[field]] %in% c("success", "skipped")) + } + meta <- meta[!success,, drop = FALSE] # nolint + issues_list(meta[, c("package", fields)]) +} diff --git a/R/issues_descriptions.R b/R/issues_descriptions.R new file mode 100644 index 0000000..950bf91 --- /dev/null +++ b/R/issues_descriptions.R @@ -0,0 +1,31 @@ +#' @title Report `DESCRIPTION` file issues. +#' @export +#' @family issues +#' @description Report issues with the `DESCRIPTION` files of packages. +#' @details [issues_descriptions()] scans downloaded metadata from the +#' `PACKAGES.json` file of an R universe and reports issues with a +#' package's description file, such as the presence of a +#' `"Remotes"` field. +#' @inheritSection record_issues Package issues +#' @return A named list of information about packages which do not comply +#' with `DESCRPTION` checks. Each name is a package name, +#' and each element contains specific information about +#' non-compliance. +#' @param meta A data frame with R-universe package check results +#' returned by [meta_checks()]. +#' @examples +#' meta <- meta_packages(repo = "https://wlandau.r-universe.dev") +#' issues <- issues_descriptions(meta = meta) +#' str(issues) +issues_descriptions <- function(meta) { + meta <- issues_descriptions_remotes(meta) + fields <- "remotes" + meta <- meta[, c("package", fields)] + issues_list(meta) +} + +issues_descriptions_remotes <- function(meta) { + meta[["remotes"]] <- meta[["remotes"]] %||% replicate(nrow(meta), NULL) + meta$remotes <- lapply(meta$remotes, function(x) x[nzchar(x)]) + meta[vapply(meta$remotes, length, integer(1L)) > 0L, ] +} diff --git a/R/issues_versions.R b/R/issues_versions.R new file mode 100644 index 0000000..523b643 --- /dev/null +++ b/R/issues_versions.R @@ -0,0 +1,63 @@ +#' @title Check package versions. +#' @export +#' @family issues +#' @description Check package version number history for compliance. +#' @details This function checks the version number history of packages +#' in R-multiverse and reports any packages with issues. The current +#' released version of a given package must be unique, and it must be +#' greater than all the versions of all the previous package releases. +#' @inheritSection record_issues Package issues +#' @return A named list of information about packages which do not comply +#' with version number history checks. Each name is a package name, +#' and each element contains specific information about version +#' non-compliance: the current version number, the current version hash, +#' and the analogous versions and hashes of the highest-versioned +#' release recorded. +#' @inheritParams record_versions +#' @examples +#' # See https://github.com/r-multiverse/checks/blob/main/versions.json +#' # for the official versions JSON for R-multiverse. +#' lines <- c( +#' "[", +#' " {", +#' " \"package\": \"package_unmodified\",", +#' " \"version_current\": \"1.0.0\",", +#' " \"hash_current\": \"hash_1.0.0\",", +#' " \"version_highest\": \"1.0.0\",", +#' " \"hash_highest\": \"hash_1.0.0\"", +#' " },", +#' " {", +#' " \"package\": \"version_decremented\",", +#' " \"version_current\": \"0.0.1\",", +#' " \"hash_current\": \"hash_0.0.1\",", +#' " \"version_highest\": \"1.0.0\",", +#' " \"hash_highest\": \"hash_1.0.0\"", +#' " },", +#' " {", +#' " \"package\": \"version_incremented\",", +#' " \"version_current\": \"2.0.0\",", +#' " \"hash_current\": \"hash_2.0.0\",", +#' " \"version_highest\": \"2.0.0\",", +#' " \"hash_highest\": \"hash_2.0.0\"", +#' " },", +#' " {", +#' " \"package\": \"version_unmodified\",", +#' " \"version_current\": \"1.0.0\",", +#' " \"hash_current\": \"hash_1.0.0-modified\",", +#' " \"version_highest\": \"1.0.0\",", +#' " \"hash_highest\": \"hash_1.0.0\"", +#' " }", +#' "]" +#' ) +#' versions <- tempfile() +#' writeLines(lines, versions) +#' out <- issues_versions(versions) +#' str(out) +issues_versions <- function(versions) { + history <- jsonlite::read_json(path = versions, simplifyVector = TRUE) + aligned <- (history$version_current == history$version_highest) & + (history$hash_current == history$hash_highest) + aligned[is.na(aligned)] <- TRUE + out <- history[!aligned,, drop = FALSE] # nolint + issues_list(out) +} diff --git a/R/meta_checks.R b/R/meta_checks.R new file mode 100644 index 0000000..a1bc56a --- /dev/null +++ b/R/meta_checks.R @@ -0,0 +1,35 @@ +#' @title List metadata about R-universe package checks +#' @export +#' @family list +#' @description List package checks results reported by the +#' R-universe package API. +#' @return A data frame with one row per package and columns with +#' package check results. +#' @param repo Character of length 1, URL of the package repository. +#' R-multiverse uses `"https://multiverse.r-multiverse.org"`. +#' @examples +#' meta_checks(repo = "https://wlandau.r-universe.dev") +meta_checks <- function(repo = "https://multiverse.r-multiverse.org") { + fields <- c( + "_buildurl", + "_linuxdevel", + "_macbinary", + "_wasmbinary", + "_winbinary", + "_status" + ) + listing <- file.path( + repo, + "api", + paste0("packages?stream=true&fields=", paste(fields, collapse = ",")) + ) + out <- jsonlite::stream_in( + con = gzcon(url(listing)), + verbose = FALSE, + simplifyVector = TRUE, + simplifyDataFrame = TRUE, + simplifyMatrix = TRUE + ) + colnames(out) <- tolower(colnames(out)) + out +} diff --git a/R/meta_packages.R b/R/meta_packages.R new file mode 100644 index 0000000..54ed0ca --- /dev/null +++ b/R/meta_packages.R @@ -0,0 +1,25 @@ +#' @title List package metadata +#' @export +#' @family meta +#' @description List package metadata in an R universe. +#' @return A data frame with one row per package and columns with package +#' metadata. +#' @inheritParams meta_checks +#' @examples +#' meta_packages(repo = "https://wlandau.r-universe.dev") +meta_packages <- function(repo = "https://multiverse.r-multiverse.org") { + fields <- c("Version", "Remotes", "RemoteSha") + listing <- file.path( + contrib.url(repos = repo, type = "source"), + paste0("PACKAGES.json?fields=", paste(fields, collapse = ",")) + ) + out <- jsonlite::stream_in( + con = gzcon(url(listing)), + verbose = FALSE, + simplifyVector = TRUE, + simplifyDataFrame = TRUE, + simplifyMatrix = TRUE + ) + colnames(out) <- tolower(colnames(out)) + out +} diff --git a/R/package.R b/R/package.R index c82d1ff..76f04ab 100644 --- a/R/package.R +++ b/R/package.R @@ -1,7 +1,3 @@ -#' multiverse.internals: Internal Infrastructure for R-multiverse. -#' @description Internal Infrastructure for R-multiverse. -#' @name multiverse.internals-package -#' @family help #' @importFrom gh gh #' @importFrom jsonlite parse_json read_json stream_in write_json #' @importFrom nanonext ncurl parse_url status_code diff --git a/R/record_issues.R b/R/record_issues.R index 93f5e5d..be17eb9 100644 --- a/R/record_issues.R +++ b/R/record_issues.R @@ -1,40 +1,116 @@ #' @title Record package issues. #' @export -#' @description Record package check and version issues in individual JSON -#' files. +#' @keywords package check data management +#' @description Record R-multiverse package issues in +#' package-specific JSON files. +#' @section Package issues: +#' Functions like [issues_versions()] and [issues_descriptions()] +#' perform health checks for all packages in R-multiverse. +#' Only packages that pass these checks go to the production repository at +#' . For a complete list of checks, see +#' the `issues_*()` functions listed at +#' . +#' [record_versions()] updates the version number history +#' of releases in R-multiverse, and [record_issues()] gathers +#' together all the issues about R-multiverse packages. +#' @section Issue files: +#' For each package with observed problems, [record_issues()] writes +#' an issue file. This issue file is a JSON list with one element +#' per type of failing check. Each element has an informative name +#' (for example, `checks`, `descriptions`, or `versions`) +#' and a list of diagnostic information. +#' +#' Each issue file also has a `date` field. This date the day that +#' an issue was first noticed. It automatically resets the next time +#' all package are resolved. #' @return `NULL` (invisibly). -#' @param manifest Character of length 1, file path to a JSON manifest -#' tracking release versions of packages. +#' @inheritParams issues_checks +#' @inheritParams issues_versions +#' @inheritParams meta_checks #' @param output Character of length 1, file path to the folder to record #' new package issues. Each call to `record_issues()` overwrites the #' contents of the repo. +#' @param mock For testing purposes only, a named list of data frames +#' for inputs to various intermediate functions. +#' @examples +#' repo <- "https://wlandau.r-universe.dev" +#' output <- tempfile() +#' versions <- tempfile() +#' record_versions( +#' versions = versions, +#' repo = repo +#' ) +#' record_issues( +#' repo = repo, +#' versions = versions, +#' output = output +#' ) +#' files <- list.files(output) +#' print(files) +#' package <- head(files, n = 1) +#' if (length(package)) { +#' print(package) +#' } +#' if (length(package)) { +#' print(readLines(file.path(output, package))) +#' } record_issues <- function( - manifest = "versions.json", - output = "issues" + repo = "https://multiverse.r-multiverse.org", + versions = "versions.json", + output = "issues", + mock = NULL ) { - issues_version <- version_issues(manifest) - issues <- issues_version # Will include check issues too later. - overwrite_issues(issues, output) + today <- mock$today %||% format(Sys.Date(), fmt = "yyyy-mm-dd") + checks <- mock$checks %||% meta_checks(repo = repo) + packages <- mock$packages %||% meta_packages(repo = repo) + issues <- list() |> + add_issues(issues_checks(meta = checks), "checks") |> + add_issues(issues_descriptions(meta = packages), "descriptions") |> + add_issues(issues_versions(versions = versions), "versions") |> + overwrite_issues(output = output, today = today) invisible() } -overwrite_issues <- function(issues, output) { +add_issues <- function(total, subset, category) { + for (package in names(subset)) { + total[[package]][[category]] <- subset[[package]] + } + total +} + +overwrite_issues <- function(issues, output, today) { + packages <- list.files(output) + dates <- lapply( + X = packages, + FUN = function(path) { + jsonlite::read_json(file.path(output, path), simplifyVector = TRUE)$date + } + ) + names(dates) <- packages unlink(output, recursive = TRUE) dir.create(output) - for (index in seq_len(nrow(issues))) { - row <- vctrs::vec_slice(x = issues, i = index) - jsonlite::write_json(row, file.path(output, row$package)) - } + lapply( + X = names(issues), + FUN = overwrite_package_issues, + issues = issues, + output = output, + today = today, + dates = dates + ) } -version_issues <- function(manifest) { - manifest <- jsonlite::read_json(path = manifest, simplifyVector = TRUE) - aligned <- (manifest$version_current == manifest$version_highest) & - (manifest$hash_current == manifest$hash_highest) - aligned[is.na(aligned)] <- TRUE - out <- manifest[!aligned,, drop = FALSE] # nolint - if (nrow(out)) { - out$version_okay <- FALSE - } - out +overwrite_package_issues <- function( + package, + issues, + output, + today, + dates +) { + path <- file.path(output, package) + issues[[package]]$date <- dates[[package]] %||% today + jsonlite::write_json( + x = issues[[package]], + path = file.path(output, package), + pretty = TRUE + ) } diff --git a/R/record_versions.R b/R/record_versions.R index c95bdc6..1b82186 100644 --- a/R/record_versions.R +++ b/R/record_versions.R @@ -1,30 +1,54 @@ #' @title Record the manifest of package versions. #' @export +#' @family package check data management #' @description Record the manifest of versions of packages #' and their hashes. #' @details This function tracks a manifest containing the current version, #' the current hash, the highest version ever released, and -#' the hash of the highest version ever released. It uses this information +#' the hash of the highest version ever released. +#' [issues_versions()] uses this information #' to determine whether the package complies with best #' practices for version numbers. +#' @inheritSection record_issues Package issues #' @return `NULL` (invisibly). Writes a package version manifest #' and a manifest of version issues as JSON files. -#' @param manifest Character of length 1, file path to the JSON manifest. -#' @param repo Character string of package repositories to track. +#' @inheritParams meta_checks +#' @param versions Character of length 1, file path to a JSON manifest +#' tracking the history of released versions of packages. +#' The official versions file for R-multiverse is maintained and +#' updated periodically at +#' . #' @param current A data frame of current versions and hashes of packages #' in `repo`. This argument is exposed for testing only. +#' @examples +#' # R-multiverse uses https://multiverse.r-multiverse.org as the repo. +#' repo <- "https://wlandau.r-universe.dev" # just for testing and examples +#' output <- tempfile() +#' versions <- tempfile() +#' # First snapshot: +#' record_versions( +#' versions = versions, +#' repo = repo +#' ) +#' readLines(versions) +#' # In subsequent snapshots, we have historical information about versions. +#' record_versions( +#' versions = versions, +#' repo = repo +#' ) +#' readLines(versions) record_versions <- function( - manifest = "versions.json", - repo = "https://r-multiverse.r-universe.dev", + versions = "versions.json", + repo = "https://multiverse.r-multiverse.org", current = multiverse.internals::get_current_versions(repo = repo) ) { - if (!file.exists(manifest)) { - jsonlite::write_json(x = current, path = manifest, pretty = TRUE) + if (!file.exists(versions)) { + jsonlite::write_json(x = current, path = versions, pretty = TRUE) return(invisible()) } - previous <- read_versions_previous(manifest = manifest) - new <- update_version_manifest(current = current, previous = previous) - jsonlite::write_json(x = new, path = manifest, pretty = TRUE) + previous <- read_versions_previous(versions = versions) + new <- update_versions(current = current, previous = previous) + jsonlite::write_json(x = new, path = versions, pretty = TRUE) invisible() } @@ -35,27 +59,17 @@ record_versions <- function( #' @return A data frame of packages with their current versions and hashes. #' @inheritParams record_versions get_current_versions <- function( - repo = "https://r-multiverse.r-universe.dev" + repo = "https://multiverse.r-multiverse.org" ) { - listing <- file.path( - contrib.url(repos = repo, type = "source"), - "PACKAGES.json?fields=RemoteSha" - ) - out <- jsonlite::stream_in( - con = gzcon(url(listing)), - verbose = TRUE, - simplifyVector = TRUE, - simplifyDataFrame = TRUE, - simplifyMatrix = TRUE - ) - out <- out[, c("Package", "Version", "RemoteSha")] - colnames(out) <- c("package", "version_current", "hash_current") - rownames(out) <- NULL - out + meta <- meta_packages(repo = repo) + meta <- meta[, c("package", "version", "remotesha")] + colnames(meta) <- c("package", "version_current", "hash_current") + rownames(meta) <- NULL + meta } -read_versions_previous <- function(manifest) { - out <- jsonlite::read_json(path = manifest, simplifyVector = TRUE) +read_versions_previous <- function(versions) { + out <- jsonlite::read_json(path = versions, simplifyVector = TRUE) if (is.null(out$version_highest)) { out$version_highest <- out$version_current } @@ -67,7 +81,7 @@ read_versions_previous <- function(manifest) { out } -update_version_manifest <- function(current, previous) { +update_versions <- function(current, previous) { new <- merge( x = current, y = previous, @@ -75,15 +89,15 @@ update_version_manifest <- function(current, previous) { all.x = TRUE, all.y = FALSE ) - incremented <- manifest_compare_versions(manifest = new) == 1L + incremented <- compare_versions(versions = new) == 1L new$version_highest[incremented] <- new$version_current[incremented] new$hash_highest[incremented] <- new$hash_current[incremented] new } -manifest_compare_versions <- function(manifest) { +compare_versions <- function(versions) { apply( - X = manifest, + X = versions, MARGIN = 1L, FUN = function(row) { utils::compareVersion( diff --git a/R/review_pull_request.R b/R/review_pull_request.R index 5188e70..bdbc003 100644 --- a/R/review_pull_request.R +++ b/R/review_pull_request.R @@ -10,8 +10,8 @@ #' 4. Add a file in a forbidden place (close). #' 5. Add a custom JSON file which can be parsed (manual review). #' @return `NULL` (invisibly). -#' @param owner Character of length 1, name of the universe repository owner. -#' @param repo Character of length 1, name of the universe repository. +#' @inheritParams meta_checks +#' @param owner Character of length 1, name of the package repository owner. #' @param number Positive integer of length 1, index of the pull request #' in the repo. review_pull_request <- function( diff --git a/R/utils_issues.R b/R/utils_issues.R new file mode 100644 index 0000000..4b7bf97 --- /dev/null +++ b/R/utils_issues.R @@ -0,0 +1,10 @@ +issues_list <- function(x) { + colnames(x) <- tolower(colnames(x)) + out <- list() + for (index in seq_len(nrow(x))) { + for (field in setdiff(colnames(x), "package")) { + out[[x$package[index]]][[field]] <- x[[field]][[index]] + } + } + out[order(as.character(names(out)))] +} diff --git a/README.md b/README.md index 05539e9..fcd49e5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,4 @@ [![check](https://github.com/r-multiverse/multiverse.internals/actions/workflows/check.yaml/badge.svg)](https://github.com/r-multiverse/multiverse.internals/actions?query=workflow%3Acheck) [![lint](https://github.com/r-multiverse/multiverse.internals/actions/workflows/lint.yaml/badge.svg)](https://github.com/r-multiverse/multiverse.internals/actions?query=workflow%3Alint) -`multiverse.internals` is an R package to support the internal infrastructure for the R-multiverse project. - -For all matters please refer to . +`multiverse.internals` is an R package to support the internal infrastructure for the R-multiverse project. To learn about specific pieces of the R-multiverse infrastructure for reviewing and checking packages, please visit . For all other matters, please refer to . diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..e5e165f --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,24 @@ +url: https://r-multiverse.org/multiverse.internals/ +template: + bootstrap: 5 + bslib: + primary: "#152640" + +reference: +- title: Package contribution reviews + contents: + - review_pull_request + - review_pull_requests +- title: List metadata + contents: + - meta_checks + - meta_packages +- title: Check issues + contents: + - issues_checks + - issues_descriptions + - issues_versions +- title: Record issues + contents: + - record_issues + - record_versions diff --git a/inst/WORDLIST b/inst/WORDLIST index b5f8619..5d59fa5 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -4,3 +4,4 @@ JSON json repo pkgdown +pre diff --git a/man/get_current_versions.Rd b/man/get_current_versions.Rd index e66f8e4..cdba07c 100644 --- a/man/get_current_versions.Rd +++ b/man/get_current_versions.Rd @@ -4,10 +4,11 @@ \alias{get_current_versions} \title{Get the current versions of packages} \usage{ -get_current_versions(repo = "https://r-multiverse.r-universe.dev") +get_current_versions(repo = "https://multiverse.r-multiverse.org") } \arguments{ -\item{repo}{Character string of package repositories to track.} +\item{repo}{Character of length 1, URL of the package repository. +R-multiverse uses \code{"https://multiverse.r-multiverse.org"}.} } \value{ A data frame of packages with their current versions and hashes. diff --git a/man/issues_checks.Rd b/man/issues_checks.Rd new file mode 100644 index 0000000..8197040 --- /dev/null +++ b/man/issues_checks.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/issues_checks.R +\name{issues_checks} +\alias{issues_checks} +\title{Report issues from R-universe package check results.} +\usage{ +issues_checks(meta) +} +\arguments{ +\item{meta}{A data frame with R-universe package check results +returned by \code{\link[=meta_checks]{meta_checks()}}.} +} +\value{ +A named list of information about packages which do not comply +with \code{DESCRPTION} checks. Each name is a package name, +and each element contains specific information about +non-compliance. +} +\description{ +Check R-universe package check results. +} +\details{ +\code{\link[=issues_checks]{issues_checks()}} reads output from +the R-universe check API +to scan all R-multiverse packages for issues that may have +happened during building and testing. +} +\section{Package issues}{ + +Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} +perform health checks for all packages in R-multiverse. +Only packages that pass these checks go to the production repository at +\url{https://production.r-multiverse.org}. For a complete list of checks, see +the \verb{issues_*()} functions listed at +\url{https://r-multiverse.org/multiverse.internals/reference.html}. +\code{\link[=record_versions]{record_versions()}} updates the version number history +of releases in R-multiverse, and \code{\link[=record_issues]{record_issues()}} gathers +together all the issues about R-multiverse packages. +} + +\examples{ + meta <- meta_checks(repo = "https://wlandau.r-universe.dev") + issues <- issues_checks(meta = meta) + str(issues) +} +\seealso{ +Other issues: +\code{\link{issues_descriptions}()}, +\code{\link{issues_versions}()} +} +\concept{issues} diff --git a/man/issues_descriptions.Rd b/man/issues_descriptions.Rd new file mode 100644 index 0000000..3441404 --- /dev/null +++ b/man/issues_descriptions.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/issues_descriptions.R +\name{issues_descriptions} +\alias{issues_descriptions} +\title{Report \code{DESCRIPTION} file issues.} +\usage{ +issues_descriptions(meta) +} +\arguments{ +\item{meta}{A data frame with R-universe package check results +returned by \code{\link[=meta_checks]{meta_checks()}}.} +} +\value{ +A named list of information about packages which do not comply +with \code{DESCRPTION} checks. Each name is a package name, +and each element contains specific information about +non-compliance. +} +\description{ +Report issues with the \code{DESCRIPTION} files of packages. +} +\details{ +\code{\link[=issues_descriptions]{issues_descriptions()}} scans downloaded metadata from the +\code{PACKAGES.json} file of an R universe and reports issues with a +package's description file, such as the presence of a +\code{"Remotes"} field. +} +\section{Package issues}{ + +Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} +perform health checks for all packages in R-multiverse. +Only packages that pass these checks go to the production repository at +\url{https://production.r-multiverse.org}. For a complete list of checks, see +the \verb{issues_*()} functions listed at +\url{https://r-multiverse.org/multiverse.internals/reference.html}. +\code{\link[=record_versions]{record_versions()}} updates the version number history +of releases in R-multiverse, and \code{\link[=record_issues]{record_issues()}} gathers +together all the issues about R-multiverse packages. +} + +\examples{ + meta <- meta_packages(repo = "https://wlandau.r-universe.dev") + issues <- issues_descriptions(meta = meta) + str(issues) +} +\seealso{ +Other issues: +\code{\link{issues_checks}()}, +\code{\link{issues_versions}()} +} +\concept{issues} diff --git a/man/issues_versions.Rd b/man/issues_versions.Rd new file mode 100644 index 0000000..c6d8796 --- /dev/null +++ b/man/issues_versions.Rd @@ -0,0 +1,91 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/issues_versions.R +\name{issues_versions} +\alias{issues_versions} +\title{Check package versions.} +\usage{ +issues_versions(versions) +} +\arguments{ +\item{versions}{Character of length 1, file path to a JSON manifest +tracking the history of released versions of packages. +The official versions file for R-multiverse is maintained and +updated periodically at +\url{https://github.com/r-multiverse/checks/blob/main/versions.json}.} +} +\value{ +A named list of information about packages which do not comply +with version number history checks. Each name is a package name, +and each element contains specific information about version +non-compliance: the current version number, the current version hash, +and the analogous versions and hashes of the highest-versioned +release recorded. +} +\description{ +Check package version number history for compliance. +} +\details{ +This function checks the version number history of packages +in R-multiverse and reports any packages with issues. The current +released version of a given package must be unique, and it must be +greater than all the versions of all the previous package releases. +} +\section{Package issues}{ + +Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} +perform health checks for all packages in R-multiverse. +Only packages that pass these checks go to the production repository at +\url{https://production.r-multiverse.org}. For a complete list of checks, see +the \verb{issues_*()} functions listed at +\url{https://r-multiverse.org/multiverse.internals/reference.html}. +\code{\link[=record_versions]{record_versions()}} updates the version number history +of releases in R-multiverse, and \code{\link[=record_issues]{record_issues()}} gathers +together all the issues about R-multiverse packages. +} + +\examples{ + # See https://github.com/r-multiverse/checks/blob/main/versions.json + # for the official versions JSON for R-multiverse. + lines <- c( + "[", + " {", + " \"package\": \"package_unmodified\",", + " \"version_current\": \"1.0.0\",", + " \"hash_current\": \"hash_1.0.0\",", + " \"version_highest\": \"1.0.0\",", + " \"hash_highest\": \"hash_1.0.0\"", + " },", + " {", + " \"package\": \"version_decremented\",", + " \"version_current\": \"0.0.1\",", + " \"hash_current\": \"hash_0.0.1\",", + " \"version_highest\": \"1.0.0\",", + " \"hash_highest\": \"hash_1.0.0\"", + " },", + " {", + " \"package\": \"version_incremented\",", + " \"version_current\": \"2.0.0\",", + " \"hash_current\": \"hash_2.0.0\",", + " \"version_highest\": \"2.0.0\",", + " \"hash_highest\": \"hash_2.0.0\"", + " },", + " {", + " \"package\": \"version_unmodified\",", + " \"version_current\": \"1.0.0\",", + " \"hash_current\": \"hash_1.0.0-modified\",", + " \"version_highest\": \"1.0.0\",", + " \"hash_highest\": \"hash_1.0.0\"", + " }", + "]" + ) + versions <- tempfile() + writeLines(lines, versions) + out <- issues_versions(versions) + str(out) +} +\seealso{ +Other issues: +\code{\link{issues_checks}()}, +\code{\link{issues_descriptions}()} +} +\concept{issues} diff --git a/man/meta_checks.Rd b/man/meta_checks.Rd new file mode 100644 index 0000000..eca1627 --- /dev/null +++ b/man/meta_checks.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/meta_checks.R +\name{meta_checks} +\alias{meta_checks} +\title{List metadata about R-universe package checks} +\usage{ +meta_checks(repo = "https://multiverse.r-multiverse.org") +} +\arguments{ +\item{repo}{Character of length 1, URL of the package repository. +R-multiverse uses \code{"https://multiverse.r-multiverse.org"}.} +} +\value{ +A data frame with one row per package and columns with +package check results. +} +\description{ +List package checks results reported by the +R-universe package API. +} +\examples{ +meta_checks(repo = "https://wlandau.r-universe.dev") +} +\concept{list} diff --git a/man/meta_packages.Rd b/man/meta_packages.Rd new file mode 100644 index 0000000..08de109 --- /dev/null +++ b/man/meta_packages.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/meta_packages.R +\name{meta_packages} +\alias{meta_packages} +\title{List package metadata} +\usage{ +meta_packages(repo = "https://multiverse.r-multiverse.org") +} +\arguments{ +\item{repo}{Character of length 1, URL of the package repository. +R-multiverse uses \code{"https://multiverse.r-multiverse.org"}.} +} +\value{ +A data frame with one row per package and columns with package +metadata. +} +\description{ +List package metadata in an R universe. +} +\examples{ +meta_packages(repo = "https://wlandau.r-universe.dev") +} +\concept{meta} diff --git a/man/multiverse.internals-package.Rd b/man/multiverse.internals-package.Rd deleted file mode 100644 index a9c86f0..0000000 --- a/man/multiverse.internals-package.Rd +++ /dev/null @@ -1,9 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/package.R -\name{multiverse.internals-package} -\alias{multiverse.internals-package} -\title{multiverse.internals: Internal Infrastructure for R-multiverse.} -\description{ -Internal Infrastructure for R-multiverse. -} -\concept{help} diff --git a/man/record_issues.Rd b/man/record_issues.Rd index 001b7cc..31d3d97 100644 --- a/man/record_issues.Rd +++ b/man/record_issues.Rd @@ -4,20 +4,87 @@ \alias{record_issues} \title{Record package issues.} \usage{ -record_issues(manifest = "versions.json", output = "issues") +record_issues( + repo = "https://multiverse.r-multiverse.org", + versions = "versions.json", + output = "issues", + mock = NULL +) } \arguments{ -\item{manifest}{Character of length 1, file path to a JSON manifest -tracking release versions of packages.} +\item{repo}{Character of length 1, URL of the package repository. +R-multiverse uses \code{"https://multiverse.r-multiverse.org"}.} + +\item{versions}{Character of length 1, file path to a JSON manifest +tracking the history of released versions of packages. +The official versions file for R-multiverse is maintained and +updated periodically at +\url{https://github.com/r-multiverse/checks/blob/main/versions.json}.} \item{output}{Character of length 1, file path to the folder to record new package issues. Each call to \code{record_issues()} overwrites the contents of the repo.} + +\item{mock}{For testing purposes only, a named list of data frames +for inputs to various intermediate functions.} } \value{ \code{NULL} (invisibly). } \description{ -Record package check and version issues in individual JSON -files. +Record R-multiverse package issues in +package-specific JSON files. +} +\section{Package issues}{ + +Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} +perform health checks for all packages in R-multiverse. +Only packages that pass these checks go to the production repository at +\url{https://production.r-multiverse.org}. For a complete list of checks, see +the \verb{issues_*()} functions listed at +\url{https://r-multiverse.org/multiverse.internals/reference.html}. +\code{\link[=record_versions]{record_versions()}} updates the version number history +of releases in R-multiverse, and \code{\link[=record_issues]{record_issues()}} gathers +together all the issues about R-multiverse packages. +} + +\section{Issue files}{ + +For each package with observed problems, \code{\link[=record_issues]{record_issues()}} writes +an issue file. This issue file is a JSON list with one element +per type of failing check. Each element has an informative name +(for example, \code{checks}, \code{descriptions}, or \code{versions}) +and a list of diagnostic information. + +Each issue file also has a \code{date} field. This date the day that +an issue was first noticed. It automatically resets the next time +all package are resolved. +} + +\examples{ + repo <- "https://wlandau.r-universe.dev" + output <- tempfile() + versions <- tempfile() + record_versions( + versions = versions, + repo = repo + ) + record_issues( + repo = repo, + versions = versions, + output = output + ) + files <- list.files(output) + print(files) + package <- head(files, n = 1) + if (length(package)) { + print(package) + } + if (length(package)) { + print(readLines(file.path(output, package))) + } } +\keyword{check} +\keyword{data} +\keyword{management} +\keyword{package} diff --git a/man/record_versions.Rd b/man/record_versions.Rd index 20cd985..2648fa0 100644 --- a/man/record_versions.Rd +++ b/man/record_versions.Rd @@ -5,15 +5,20 @@ \title{Record the manifest of package versions.} \usage{ record_versions( - manifest = "versions.json", - repo = "https://r-multiverse.r-universe.dev", + versions = "versions.json", + repo = "https://multiverse.r-multiverse.org", current = multiverse.internals::get_current_versions(repo = repo) ) } \arguments{ -\item{manifest}{Character of length 1, file path to the JSON manifest.} +\item{versions}{Character of length 1, file path to a JSON manifest +tracking the history of released versions of packages. +The official versions file for R-multiverse is maintained and +updated periodically at +\url{https://github.com/r-multiverse/checks/blob/main/versions.json}.} -\item{repo}{Character string of package repositories to track.} +\item{repo}{Character of length 1, URL of the package repository. +R-multiverse uses \code{"https://multiverse.r-multiverse.org"}.} \item{current}{A data frame of current versions and hashes of packages in \code{repo}. This argument is exposed for testing only.} @@ -29,7 +34,40 @@ and their hashes. \details{ This function tracks a manifest containing the current version, the current hash, the highest version ever released, and -the hash of the highest version ever released. It uses this information +the hash of the highest version ever released. +\code{\link[=issues_versions]{issues_versions()}} uses this information to determine whether the package complies with best practices for version numbers. } +\section{Package issues}{ + +Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} +perform health checks for all packages in R-multiverse. +Only packages that pass these checks go to the production repository at +\url{https://production.r-multiverse.org}. For a complete list of checks, see +the \verb{issues_*()} functions listed at +\url{https://r-multiverse.org/multiverse.internals/reference.html}. +\code{\link[=record_versions]{record_versions()}} updates the version number history +of releases in R-multiverse, and \code{\link[=record_issues]{record_issues()}} gathers +together all the issues about R-multiverse packages. +} + +\examples{ + # R-multiverse uses https://multiverse.r-multiverse.org as the repo. + repo <- "https://wlandau.r-universe.dev" # just for testing and examples + output <- tempfile() + versions <- tempfile() + # First snapshot: + record_versions( + versions = versions, + repo = repo + ) + readLines(versions) + # In subsequent snapshots, we have historical information about versions. + record_versions( + versions = versions, + repo = repo + ) + readLines(versions) +} +\concept{package check data management} diff --git a/man/review_pull_request.Rd b/man/review_pull_request.Rd index 59fca48..036a8bf 100644 --- a/man/review_pull_request.Rd +++ b/man/review_pull_request.Rd @@ -7,9 +7,10 @@ review_pull_request(owner = "r-multiverse", repo = "contributions", number) } \arguments{ -\item{owner}{Character of length 1, name of the universe repository owner.} +\item{owner}{Character of length 1, name of the package repository owner.} -\item{repo}{Character of length 1, name of the universe repository.} +\item{repo}{Character of length 1, URL of the package repository. +R-multiverse uses \code{"https://multiverse.r-multiverse.org"}.} \item{number}{Positive integer of length 1, index of the pull request in the repo.} diff --git a/man/review_pull_requests.Rd b/man/review_pull_requests.Rd index f4bde69..3499642 100644 --- a/man/review_pull_requests.Rd +++ b/man/review_pull_requests.Rd @@ -7,9 +7,10 @@ review_pull_requests(owner = "r-multiverse", repo = "contributions") } \arguments{ -\item{owner}{Character of length 1, name of the universe repository owner.} +\item{owner}{Character of length 1, name of the package repository owner.} -\item{repo}{Character of length 1, name of the universe repository.} +\item{repo}{Character of length 1, URL of the package repository. +R-multiverse uses \code{"https://multiverse.r-multiverse.org"}.} } \value{ \code{NULL} (invisibly). diff --git a/tests/testthat/helper-mock.R b/tests/testthat/helper-mock.R new file mode 100644 index 0000000..062e116 --- /dev/null +++ b/tests/testthat/helper-mock.R @@ -0,0 +1,165 @@ +mock_meta_checks <- structure( + list( + package = c( + "tinytest", "tidytensor", "secretbase", + "multiverse.internals", "SBC", "duckdb", "httpgd", "targetsketch", + "stantargets", "zstdlite", "INLA", "audio.whisper", "tidypolars", + "multitools", "audio.vadwebrtc", "nanonext", "polars", "cmdstanr", + "string2path" + ), + "_user" = c( + "r-multiverse", "r-multiverse", "r-multiverse", + "r-multiverse", "r-multiverse", "r-multiverse", "r-multiverse", + "r-multiverse", "r-multiverse", "r-multiverse", "r-multiverse", + "r-multiverse", "r-multiverse", "r-multiverse", "r-multiverse", + "r-multiverse", "r-multiverse", "r-multiverse", "r-multiverse" + ), + "_type" = c( + "src", "src", "src", "src", "src", "src", "src", + "src", "src", "src", "failure", "src", "src", "src", "src", "src", + "src", "src", "src" + ), + "_status" = c( + "success", "failure", "success", + "success", "failure", "success", "success", "success", "success", + "success", NA, "success", "success", "success", "success", "success", + "success", "success", "success" + ), + "_winbinary" = c( + "success", "success", "success", "success", + "success", "success", "success", + "success", "success", "success", NA, "success", "success", "success", + "success", "success", "success", "success", "success" + ), + "_macbinary" = c( + "success", "success", "success", "success", + "success", "success", "success", + "success", "success", "success", NA, "success", "success", "success", + "success", "success", "arm64-failure", "success", "success" + ), + "_wasmbinary" = c( + "success", "success", "success", "success", + "success", "success", "none", "success", "success", "success", + NA, "success", "success", "success", "success", "success", + "none", "success", "none" + ), + "_linuxdevel" = c( + "success", "failure", "success", "success", + "failure", "success", "success", + "success", "failure", "success", NA, + "success", "success", + "success", "success", "success", + "failure", "success", "success" + ), + "_buildurl" = c( + "https://github.com/r-universe/r-multiverse/actions/runs/8998731783", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732025", + "https://github.com/r-universe/r-multiverse/actions/runs/8998731915", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732064", + "https://github.com/r-universe/r-multiverse/actions/runs/8998731731", + "https://github.com/r-universe/r-multiverse/actions/runs/8998731753", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732459", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732171", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732490", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732389", + "https://github.com/r-universe/r-multiverse/actions/runs/8487512222", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732607", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732444", + "https://github.com/r-universe/r-multiverse/actions/runs/8998731870", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732026", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732176", + "https://github.com/r-universe/r-multiverse/actions/runs/9005231218", + "https://github.com/r-universe/r-multiverse/actions/runs/9140511697", + "https://github.com/r-universe/r-multiverse/actions/runs/8998732437" + ), + "_indexed" = c( + FALSE, TRUE, FALSE, TRUE, TRUE, FALSE, + FALSE, TRUE, FALSE, FALSE, NA, TRUE, TRUE, TRUE, FALSE, FALSE, + TRUE, TRUE, FALSE + ), + "_binaries" = list( + list(), list(), list(), + list(), list(), list(), list(), list(), list(), list(), + list(), list(), list(), list(), list(), list(), list(), + list(), list() + ), + "_failure" = structure( + list( + buildurl = c( + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, + paste0( + "https://github.com/r-universe/r-multiverse", + "/actions/runs/8487512222" + ), + NA, NA, NA, NA, NA, NA, NA, NA + ) + ), + class = "data.frame", + row.names = c(NA, 19L) + ) + ), + class = "data.frame", + row.names = c(NA, 19L) +) + +mock_meta_packages <- structure( + list( + package = c( + "SBC", "audio.vadwebrtc", "audio.whisper", + "cmdstanr", "duckdb", "httpgd", "multitools", "multiverse.internals", + "nanonext", "polars", "secretbase", "stantargets", "string2path", + "targetsketch", "tidypolars", "tidytensor", "tinytest", "zstdlite" + ), + version = c( + "0.3.0.9000", "0.2", "0.4.1", "0.8.0", "0.10.1", + "2.0.1", "0.1.0", "0.1.4", "1.0.0", "0.16.4", "0.5.0", "0.1.1", + "0.1.6", "0.0.1", "0.7.0", "1.0.0", "1.4.1.1", "0.2.6" + ), + remotes = list( + NULL, NULL, "bnosac/audio.vadwebrtc", NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + c("hyunjimoon/SBC", "stan-dev/cmdstanr", ""), + NULL, NULL, "markvanderloo/tinytest/pkg", NULL, NULL, NULL + ) + ), + row.names = c(NA, 18L), + class = "data.frame" +) + +mock_versions <- function() { + lines <- c( + "[", + " {", + " \"package\": \"package_unmodified\",", + " \"version_current\": \"1.0.0\",", + " \"hash_current\": \"hash_1.0.0\",", + " \"version_highest\": \"1.0.0\",", + " \"hash_highest\": \"hash_1.0.0\"", + " },", + " {", + " \"package\": \"version_decremented\",", + " \"version_current\": \"0.0.1\",", + " \"hash_current\": \"hash_0.0.1\",", + " \"version_highest\": \"1.0.0\",", + " \"hash_highest\": \"hash_1.0.0\"", + " },", + " {", + " \"package\": \"version_incremented\",", + " \"version_current\": \"2.0.0\",", + " \"hash_current\": \"hash_2.0.0\",", + " \"version_highest\": \"2.0.0\",", + " \"hash_highest\": \"hash_2.0.0\"", + " },", + " {", + " \"package\": \"version_unmodified\",", + " \"version_current\": \"1.0.0\",", + " \"hash_current\": \"hash_1.0.0-modified\",", + " \"version_highest\": \"1.0.0\",", + " \"hash_highest\": \"hash_1.0.0\"", + " }", + "]" + ) + versions <- tempfile() + writeLines(lines, versions) + versions +} diff --git a/tests/testthat/test-issues_checks.R b/tests/testthat/test-issues_checks.R new file mode 100644 index 0000000..5babb1e --- /dev/null +++ b/tests/testthat/test-issues_checks.R @@ -0,0 +1,52 @@ +test_that("issues_checks() mocked", { + issues <- issues_checks(meta = mock_meta_checks) + url <- "https://github.com/r-universe/r-multiverse/actions/runs" + expected <- list( + httpgd = list( + "_linuxdevel" = "success", "_macbinary" = "success", + "_wasmbinary" = "none", "_winbinary" = "success", "_status" = "success", + "_buildurl" = file.path(url, "8998732459") + ), + INLA = list( + "_linuxdevel" = "src-failure", "_macbinary" = "src-failure", + "_wasmbinary" = "src-failure", "_winbinary" = "src-failure", + "_status" = "src-failure", + "_buildurl" = file.path(url, "8487512222") + ), + polars = list( + "_linuxdevel" = "failure", "_macbinary" = "arm64-failure", + "_wasmbinary" = "none", "_winbinary" = "success", "_status" = "success", + "_buildurl" = file.path(url, "9005231218") + ), + SBC = list( + "_linuxdevel" = "failure", "_macbinary" = "success", + "_wasmbinary" = "success", "_winbinary" = "success", + "_status" = "failure", + "_buildurl" = file.path(url, "8998731731") + ), + stantargets = list( + "_linuxdevel" = "failure", "_macbinary" = "success", + "_wasmbinary" = "success", "_winbinary" = "success", + "_status" = "success", + "_buildurl" = file.path(url, "8998732490") + ), + string2path = list( + "_linuxdevel" = "success", "_macbinary" = "success", + "_wasmbinary" = "none", "_winbinary" = "success", "_status" = "success", + "_buildurl" = file.path(url, "8998732437") + ), + tidytensor = list( + "_linuxdevel" = "failure", "_macbinary" = "success", + "_wasmbinary" = "success", "_winbinary" = "success", + "_status" = "failure", + "_buildurl" = file.path(url, "8998732025") + ) + ) + expect_equal(issues[order(names(issues))], expected[order(names(expected))]) +}) + +test_that("issues_checks() on a small repo", { + meta <- meta_checks(repo = "https://wlandau.r-universe.dev") + issues <- issues_checks(meta = meta) + expect_true(is.list(issues)) +}) diff --git a/tests/testthat/test-issues_descriptions.R b/tests/testthat/test-issues_descriptions.R new file mode 100644 index 0000000..1ccd4a2 --- /dev/null +++ b/tests/testthat/test-issues_descriptions.R @@ -0,0 +1,17 @@ +test_that("issues_descriptions() mocked", { + issues <- issues_descriptions(meta = mock_meta_packages) + expected <- list( + audio.whisper = list(remotes = "bnosac/audio.vadwebrtc"), + stantargets = list( + remotes = c("hyunjimoon/SBC", "stan-dev/cmdstanr") + ), + tidypolars = list(remotes = "markvanderloo/tinytest/pkg") + ) + expect_equal(issues, expected) +}) + +test_that("issues_descriptions() on a small repo", { + meta <- meta_packages(repo = "https://wlandau.r-universe.dev") + issues <- issues_descriptions(meta = meta) + expect_true(is.list(issues)) +}) diff --git a/tests/testthat/test-issues_versions.R b/tests/testthat/test-issues_versions.R new file mode 100644 index 0000000..12816c8 --- /dev/null +++ b/tests/testthat/test-issues_versions.R @@ -0,0 +1,58 @@ +test_that("issues_versions() mocked", { + # Temporary files used in the mock test. + versions <- tempfile() + # First update to the manifest. + contents <- data.frame( + package = c( + "package_unmodified", + "version_decremented", + "version_incremented", + "version_unmodified" + ), + version_current = rep("1.0.0", 4L), + hash_current = rep("hash_1.0.0", 4L) + ) + record_versions(versions = versions, current = contents) + expect_equal(unname(issues_versions(versions)), list()) + # Update the manifest after no changes to packages or versions. + suppressMessages( + record_versions(versions = versions, current = contents) + ) + expect_equal(unname(issues_versions(versions)), list()) + # Update the packages in all the ways indicated above. + index <- contents$package == "version_decremented" + contents$version_current[index] <- "0.0.1" + contents$hash_current[index] <- "hash_0.0.1" + index <- contents$package == "version_incremented" + contents$version_current[index] <- "2.0.0" + contents$hash_current[index] <- "hash_2.0.0" + index <- contents$package == "version_unmodified" + contents$version_current[index] <- "1.0.0" + contents$hash_current[index] <- "hash_1.0.0-modified" + for (index in seq_len(2L)) { + record_versions( + versions = versions, + current = contents + ) + out <- issues_versions(versions) + expect_equal( + out, + list( + version_decremented = list( + version_current = "0.0.1", + hash_current = "hash_0.0.1", + version_highest = "1.0.0", + hash_highest = "hash_1.0.0" + ), + version_unmodified = list( + version_current = "1.0.0", + hash_current = "hash_1.0.0-modified", + version_highest = "1.0.0", + hash_highest = "hash_1.0.0" + ) + ) + ) + } + # Remove temporary files + unlink(versions) +}) diff --git a/tests/testthat/test-meta_checks.R b/tests/testthat/test-meta_checks.R new file mode 100644 index 0000000..5720e15 --- /dev/null +++ b/tests/testthat/test-meta_checks.R @@ -0,0 +1,14 @@ +test_that("meta_checks()", { + out <- meta_checks(repo = "https://wlandau.r-universe.dev") + expect_true(is.data.frame(out)) + expect_gt(nrow(out), 1L) + fields <- c( + "_status", + "_winbinary", + "_macbinary", + "_wasmbinary", + "_linuxdevel", + "_buildurl" + ) + expect_true(all(fields %in% colnames(out))) +}) diff --git a/tests/testthat/test-meta_packages.R b/tests/testthat/test-meta_packages.R new file mode 100644 index 0000000..712844f --- /dev/null +++ b/tests/testthat/test-meta_packages.R @@ -0,0 +1,11 @@ +test_that("meta_packages()", { + out <- meta_packages(repo = "https://wlandau.r-universe.dev") + expect_true(is.data.frame(out)) + expect_gt(nrow(out), 1L) + fields <- c( + "package", + "version", + "remotesha" + ) + expect_true(all(fields %in% colnames(out))) +}) diff --git a/tests/testthat/test-record_issues.R b/tests/testthat/test-record_issues.R index 2e1a4eb..ff2db97 100644 --- a/tests/testthat/test-record_issues.R +++ b/tests/testthat/test-record_issues.R @@ -1,142 +1,170 @@ -test_that("version_issues() in a mock repo", { - # Temporary files used in the mock test. - manifest <- tempfile() - # First update to the manifest. - contents <- data.frame( - package = c( - "package_unmodified", - "version_decremented", - "version_incremented", - "version_unmodified" +test_that("record_issues() mocked", { + output <- tempfile() + record_issues( + versions = mock_versions(), + mock = list( + checks = mock_meta_checks, + packages = mock_meta_packages, + today = "2024-01-01" ), - version_current = rep("1.0.0", 4L), - hash_current = rep("hash_1.0.0", 4L) + output = output ) - record_versions(manifest = manifest, current = contents) expect_equal( - version_issues(manifest), - data.frame( - package = character(0L), - version_current = character(0L), - hash_current = character(0L) + sort(c(list.files(output))), + sort( + c( + "audio.whisper", + "httpgd", + "INLA", + "polars", + "SBC", + "stantargets", + "string2path", + "tidypolars", + "tidytensor", + "version_decremented", + "version_unmodified" + ) ) ) - # Update the manifest after no changes to packages or versions. - suppressMessages( - record_versions(manifest = manifest, current = contents) - ) + runs <- "https://github.com/r-universe/r-multiverse/actions/runs" expect_equal( - version_issues(manifest), - data.frame( - package = character(0L), - version_current = character(0L), - hash_current = character(0L), - version_highest = character(0L), - hash_highest = character(0L) + jsonlite::read_json(file.path(output, "INLA"), simplifyVector = TRUE), + list( + checks = list( + "_linuxdevel" = "src-failure", + "_macbinary" = "src-failure", + "_wasmbinary" = "src-failure", + "_winbinary" = "src-failure", + "_status" = "src-failure", + "_buildurl" = file.path(runs, "8487512222") + ), + date = "2024-01-01" ) ) - # Update the packages in all the ways indicated above. - index <- contents$package == "version_decremented" - contents$version_current[index] <- "0.0.1" - contents$hash_current[index] <- "hash_0.0.1" - index <- contents$package == "version_incremented" - contents$version_current[index] <- "2.0.0" - contents$hash_current[index] <- "hash_2.0.0" - index <- contents$package == "version_unmodified" - contents$version_current[index] <- "1.0.0" - contents$hash_current[index] <- "hash_1.0.0-modified" - for (index in seq_len(2L)) { - record_versions( - manifest = manifest, - current = contents - ) - out <- version_issues(manifest) - rownames(out) <- NULL - expect_equal( - out, - data.frame( - package = c("version_decremented", "version_unmodified"), - version_current = c("0.0.1", "1.0.0"), - hash_current = c("hash_0.0.1", "hash_1.0.0-modified"), - version_highest = c("1.0.0", "1.0.0"), - hash_highest = c("hash_1.0.0", "hash_1.0.0"), - version_okay = c(FALSE, FALSE) - ) - ) - } - # Remove temporary files - unlink(manifest) -}) - -test_that("record_issues() in a mock repo", { - # Temporary files used in the mock test. - manifest <- tempfile() - output <- tempfile() - # First update to the manifest. - contents <- data.frame( - package = c( - "package_unmodified", - "version_decremented", - "version_incremented", - "version_unmodified" + expect_equal( + jsonlite::read_json( + file.path(output, "stantargets"), + simplifyVector = TRUE ), - version_current = rep("1.0.0", 4L), - hash_current = rep("hash_1.0.0", 4L) - ) - record_versions(manifest = manifest, current = contents) - record_issues(manifest = manifest, output = output) - expect_equal(list.files(output), character(0L)) - # Update the manifest after no changes to packages or versions. - suppressMessages( - record_versions(manifest = manifest, current = contents) - ) - record_issues(manifest = manifest, output = output) - expect_equal(list.files(output), character(0L)) - # Update the packages in all the ways indicated above. - index <- contents$package == "version_decremented" - contents$version_current[index] <- "0.0.1" - contents$hash_current[index] <- "hash_0.0.1" - index <- contents$package == "version_incremented" - contents$version_current[index] <- "2.0.0" - contents$hash_current[index] <- "hash_2.0.0" - index <- contents$package == "version_unmodified" - contents$version_current[index] <- "1.0.0" - contents$hash_current[index] <- "hash_1.0.0-modified" - for (index in seq_len(2L)) { - record_versions( - manifest = manifest, - current = contents + list( + checks = list( + "_linuxdevel" = "failure", + "_macbinary" = "success", + "_wasmbinary" = "success", + "_winbinary" = "success", + "_status" = "success", + "_buildurl" = file.path(runs, "8998732490") + ), + descriptions = list( + remotes = c("hyunjimoon/SBC", "stan-dev/cmdstanr") + ), + date = "2024-01-01" ) - record_issues(manifest = manifest, output = output) - expect_equal( - sort(list.files(output)), - sort(c("version_decremented", "version_unmodified")) - ) - out <- jsonlite::read_json(file.path(output, "version_decremented")) - expect_equal( - unlist(out, recursive = TRUE), - c( - package = "version_decremented", + ) + expect_equal( + jsonlite::read_json( + file.path(output, "version_decremented"), + simplifyVector = TRUE + ), + list( + versions = list( version_current = "0.0.1", hash_current = "hash_0.0.1", version_highest = "1.0.0", - hash_highest = "hash_1.0.0", - version_okay = FALSE - ) + hash_highest = "hash_1.0.0" + ), + date = "2024-01-01" ) - out <- jsonlite::read_json(file.path(output, "version_unmodified")) - expect_equal( - unlist(out, recursive = TRUE), - c( - package = "version_unmodified", + ) + expect_equal( + jsonlite::read_json( + file.path(output, "version_unmodified"), + simplifyVector = TRUE + ), + list( + versions = list( version_current = "1.0.0", hash_current = "hash_1.0.0-modified", version_highest = "1.0.0", - hash_highest = "hash_1.0.0", - version_okay = FALSE - ) + hash_highest = "hash_1.0.0" + ), + date = "2024-01-01" ) + ) +}) + +test_that("record_issues() date works", { + output <- tempfile() + record_issues( + versions = mock_versions(), + mock = list( + checks = mock_meta_checks, + packages = mock_meta_packages, + today = "2024-01-01" + ), + output = output + ) + record_issues( + versions = mock_versions(), + mock = list( + checks = mock_meta_checks, + packages = mock_meta_packages + ), + output = output + ) + for (file in list.files(output, full.names = TRUE)) { + date <- jsonlite::read_json(file, simplifyVector = TRUE)$date + expect_equal(date, "2024-01-01") } - # Remove temporary files - unlink(manifest) + never_fixed <- c( + "httpgd", + "INLA", + "stantargets", + "string2path", + "tidytensor", + "version_decremented" + ) + once_fixed <- c( + "audio.whisper", + "polars", + "SBC", + "tidypolars", + "version_unmodified" + ) + lapply(file.path(output, once_fixed), unlink) + record_issues( + versions = mock_versions(), + mock = list( + checks = mock_meta_checks, + packages = mock_meta_packages + ), + output = output + ) + for (package in never_fixed) { + path <- file.path(output, package) + date <- jsonlite::read_json(path, simplifyVector = TRUE)$date + expect_equal(date, "2024-01-01") + } + today <- format(Sys.Date(), fmt = "yyyy-mm-dd") + for (package in once_fixed) { + path <- file.path(output, package) + date <- jsonlite::read_json(path, simplifyVector = TRUE)$date + expect_equal(date, today) + } +}) + +test_that("record_issues() on a small repo", { + output <- tempfile() + versions <- tempfile() + record_versions( + versions = versions, + repo = "https://wlandau.r-universe.dev" + ) + record_issues( + repo = "https://wlandau.r-universe.dev", + versions = versions, + output = output + ) + expect_true(dir.exists(output)) }) diff --git a/tests/testthat/test-record_versions.R b/tests/testthat/test-record_versions.R index 0e1a453..173de48 100644 --- a/tests/testthat/test-record_versions.R +++ b/tests/testthat/test-record_versions.R @@ -1,7 +1,7 @@ -test_that("record_versions() in a mock repo", { +test_that("record_versions() mocked", { # Temporary files used in the mock test. - manifest <- tempfile() - # First update to the manifest. + versions <- tempfile() + # First update to the version manifest. contents <- data.frame( package = c( "package_unmodified", @@ -13,10 +13,10 @@ test_that("record_versions() in a mock repo", { hash_current = rep("hash_1.0.0", 4L) ) record_versions( - manifest = manifest, + versions = versions, current = contents ) - written <- jsonlite::read_json(manifest) + written <- jsonlite::read_json(versions) expected <- list( list( package = "package_unmodified", @@ -43,11 +43,11 @@ test_that("record_versions() in a mock repo", { # Update the manifest after no changes to packages or versions. suppressMessages( record_versions( - manifest = manifest, + versions = versions, current = contents ) ) - written <- jsonlite::read_json(manifest) + written <- jsonlite::read_json(versions) expected <- list( list( package = "package_unmodified", @@ -91,10 +91,10 @@ test_that("record_versions() in a mock repo", { contents$hash_current[index] <- "hash_1.0.0-modified" for (index in seq_len(2L)) { record_versions( - manifest = manifest, + versions = versions, current = contents ) - written <- jsonlite::read_json(manifest) + written <- jsonlite::read_json(versions) expected <- list( list( package = "package_unmodified", @@ -128,34 +128,34 @@ test_that("record_versions() in a mock repo", { expect_true(identical(written, expected)) } # Remove temporary files - unlink(manifest) + unlink(versions) }) -test_that("manifest can be created and updated from an actual repo", { - manifest <- tempfile() +test_that("version manifest can be created and updated from an actual repo", { + versions <- tempfile() temp <- utils::capture.output( suppressMessages( record_versions( - manifest = manifest, + versions = versions, repo = "https://wlandau.r-universe.dev" ) ) ) - expect_true(file.exists(manifest)) - contents <- do.call(vctrs::vec_rbind, jsonlite::read_json(manifest)) + expect_true(file.exists(versions)) + contents <- do.call(vctrs::vec_rbind, jsonlite::read_json(versions)) contents <- lapply(contents, as.character) lapply(contents, function(x) expect_true(!anyNA(x))) temp <- utils::capture.output( suppressMessages( record_versions( - manifest = manifest, + versions = versions, repo = "https://wlandau.r-universe.dev" ) ) ) - contents <- jsonlite::read_json(manifest) + contents <- jsonlite::read_json(versions) expect_true(is.character(contents[[1L]]$package)) expect_true(length(contents[[1L]]$package) == 1L) - expect_true(file.exists(manifest)) - unlink(manifest) + expect_true(file.exists(versions)) + unlink(versions) })