File: snapshot-github.R

package info (click to toggle)
r-cran-testthat 3.3.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,048 kB
  • sloc: cpp: 9,269; sh: 14; ansic: 14; makefile: 5
file content (139 lines) | stat: -rw-r--r-- 4,297 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#' Download snapshots from GitHub
#'
#' @description
#' If your snapshots fail on GitHub, it can be a pain to figure out exactly
#' why, or to incorporate them into your local package. This function makes it
#' easy, only requiring you to interactively select which job you want to
#' take the artifacts from.
#'
#' Note that you should not generally need to use this function manually;
#' instead copy and paste from the hint emitted on GitHub. This hint is only
#' emitted when running in a job named "R-CMD-check", since that's where the
#' testthat artifact is typically uploaded.
#'
#' @param repository Repository owner/name, e.g. `"r-lib/testthat"`.
#' @param run_id Run ID, e.g. `"47905180716"`. You can find this in the action url.
#' @param dest_dir Package root directory. Defaults to the current directory.
#' @export
snapshot_download_gh <- function(repository, run_id, dest_dir = ".") {
  check_string(repository)
  check_string(run_id)
  check_string(dest_dir)

  check_installed("gh")

  dest_snaps <- file.path(dest_dir, "tests", "testthat", "_snaps")
  if (!dir.exists(dest_snaps)) {
    cli::cli_abort("No snapshot directory found in {.file {dest_dir}}.")
  }

  job_id <- gh_find_job(repository, run_id)
  artifact_id <- gh_find_artifact(repository, job_id)

  path <- withr::local_tempfile(pattern = "gh-snaps-")
  gh_download_artifact(repository, artifact_id, path)

  files <- dir(path, full.names = TRUE)
  if (length(files) != 1) {
    cli::cli_abort("Unexpected artifact format.")
  }
  inner_dir <- files[[1]]
  src_snaps <- file.path(inner_dir, "tests", "testthat", "_snaps")
  dir_copy(src_snaps, dest_snaps)
}

gh_find_job <- function(repository, run_id) {
  jobs_json <- gh::gh(
    "/repos/{repository}/actions/runs/{run_id}/jobs",
    repository = repository,
    run_id = run_id
  )
  jobs <- data.frame(
    id = map_dbl(jobs_json$jobs, \(x) x$id),
    name = map_chr(jobs_json$jobs, \(x) x$name)
  )
  jobs <- jobs[order(jobs$name), ]

  idx <- utils::menu(jobs$name, title = "Which job?")
  if (idx == 0) {
    cli::cli_abort("Selection cancelled.")
  }
  jobs$id[[idx]]
}

gh_find_artifact <- function(repository, job_id) {
  job_logs <- gh::gh(
    "GET /repos/{repository}/actions/jobs/{job_id}/logs",
    repository = repository,
    job_id = job_id,
    .send_headers = c("Accept" = "application/vnd.github.v3+json")
  )

  log_lines <- strsplit(job_logs$message, "\r?\n")[[1]]
  matches <- re_match(log_lines, "Artifact download URL: (?<artifact_url>.*)")
  matches <- matches[!is.na(matches$artifact_url), ]
  if (nrow(matches) == 0) {
    cli::cli_abort("Failed to find artifact.")
  }

  # Take last artifact URL; if the job has failed the previous artifact will
  # be the R CMD check logs
  artifact_url <- matches$artifact_url[nrow(matches)]
  basename(artifact_url)
}

gh_download_artifact <- function(repository, artifact_id, path) {
  zip_path <- withr::local_tempfile(pattern = "gh-zip-")
  gh::gh(
    "/repos/{repository}/actions/artifacts/{artifact_id}/{archive_format}",
    repository = repository,
    artifact_id = artifact_id,
    archive_format = "zip",
    .destfile = zip_path
  )
  utils::unzip(zip_path, exdir = path)
  invisible(path)
}

# Directory helpers ------------------------------------------------------------

dir_create <- function(paths) {
  for (path in paths) {
    dir.create(path, recursive = TRUE, showWarnings = FALSE)
  }
  invisible(paths)
}

dir_copy <- function(src_dir, dst_dir) {
  # First create directories
  dirs <- list.dirs(src_dir, recursive = TRUE, full.names = FALSE)
  dir_create(file.path(dst_dir, dirs))

  # Then copy files
  files <- dir(src_dir, recursive = TRUE)
  src_files <- file.path(src_dir, files)
  dst_files <- file.path(dst_dir, files)
  same <- map_lgl(seq_along(files), \(i) {
    same_file(src_files[[i]], dst_files[[i]])
  })

  n_new <- sum(!same)
  if (n_new == 0) {
    cli::cli_inform(c(i = "No new snapshots."))
  } else {
    cli::cli_inform(c(
      v = "Copying {n_new} new snapshots: {.file {files[!same]}}."
    ))
  }

  file.copy(src_files[!same], dst_files[!same], overwrite = TRUE)
  invisible()
}

same_file <- function(x, y) {
  file.exists(x) && file.exists(y) && hash_file(x) == hash_file(y)
}

on_gh <- function() {
  Sys.getenv("GITHUB_ACTIONS") == "true"
}