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
62 changes: 62 additions & 0 deletions .github/workflows/java-live-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# GitHub Actions workflow to run the long-running live Java download tests.
# This test downloads, installs, checks, and clears every supported Java version.
# It is run on a schedule to avoid slowing down pull request checks.

name: R Java Live Integration Tests

on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

schedule:
# Runs at 00:00 UTC on Saturday.
# This corresponds to 1:00 AM in Berlin (CET, UTC+1) during standard time,
# and 2:00 AM (CEST, UTC+2) during daylight saving time.
- cron: '0 0 * * 6'

jobs:
run-live-tests:
name: ${{ matrix.config.os }} (R ${{ matrix.config.r-version }})
runs-on: ${{ matrix.config.os }}

# Strategy to run jobs in parallel for all target platforms
strategy:
fail-fast: false # Important: Ensures all OS tests run even if one fails
matrix:
config:
- {os: windows-latest, r-version: 'release'}
- {os: macos-latest, r-version: 'release'}
- {os: ubuntu-latest, r-version: 'release'}

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up R
uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.config.r-version }}
use-public-rspm: true

- name: Set up Pandoc
uses: r-lib/actions/setup-pandoc@v2

- name: Install package dependencies
uses: r-lib/actions/setup-r-dependencies@v2
with:
packages: |
any::rJava
any::devtools
any::testthat
any::withr
any::cli

- name: Run live Java download and install tests
# This is the key step:
# 1. Set the environment variable to enable the test.
# 2. Run ONLY the specific test file using devtools::test_file().
env:
RUN_JAVA_DOWNLOAD_TESTS: "true"
run: |
devtools::test_file('tests/testthat/test-java_full-cycle-live.R')
shell: Rscript {0}
59 changes: 33 additions & 26 deletions R/java_download.R
Original file line number Diff line number Diff line change
Expand Up @@ -31,31 +31,6 @@ java_download <- function(
force = FALSE,
temp_dir = FALSE
) {
# Download distribution and check MD5 checksum
download_dist_check_md5 <- function(url, dest_file, quiet) {
curl::curl_download(url, dest_file, quiet = FALSE)
curl::curl_download(url_md5, dest_file_md5, quiet = TRUE)

if (!quiet) {
cli::cli_inform("Download completed.", .envir = environment())
}
md5sum <- tools::md5sum(dest_file)
md5sum_expected <- readLines(dest_file_md5, warn = FALSE)

if (md5sum != md5sum_expected) {
cli::cli_abort(
"MD5 checksum mismatch. Please try downloading the file again.",
.envir = environment()
)
unlink(dest_file)
return(NULL)
} else {
if (!quiet) {
cli::cli_inform("MD5 checksum verified.", .envir = environment())
}
}
}

# override cache_path if temp_dir is set to TRUE
if (temp_dir) {
temp_dir <- tempdir()
Expand Down Expand Up @@ -160,7 +135,39 @@ java_download <- function(
file.remove(dest_file)
}

download_dist_check_md5(url, dest_file, quiet)
download_dist_with_md5_check(url, dest_file, url_md5, dest_file_md5, quiet)

return(dest_file)
}

# Helper function to download a distribution and verify its MD5 checksum
download_dist_with_md5_check <- function(
url,
dest_file,
url_md5,
dest_file_md5,
quiet
) {
# Perform downloads
curl::curl_download(url, dest_file, quiet = FALSE)
curl::curl_download(url_md5, dest_file_md5, quiet = TRUE)

if (!quiet) {
cli::cli_inform("Download completed.")
}

# Verify checksum
md5sum_actual <- tools::md5sum(dest_file)
md5sum_expected <- readLines(dest_file_md5, warn = FALSE)

if (md5sum_actual != md5sum_expected) {
unlink(dest_file) # Clean up failed download
cli::cli_abort(
"MD5 checksum mismatch. Please try downloading the file again."
)
}

if (!quiet) {
cli::cli_inform("MD5 checksum verified.")
}
}
3 changes: 2 additions & 1 deletion R/java_install.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#' Install Java from a distribution file
#'
#'
#' @description
#' Unpack Java distribution file into cache directory and link the installation into a project directory, optionally setting the `JAVA_HOME` and `PATH` environment variables to the Java version that was just installed.
#'
Expand Down Expand Up @@ -121,6 +121,7 @@ java_install <- function(
unlink(project_version_path, recursive = TRUE)
}
file.symlink(installed_path, project_version_path)
link_success <- TRUE # <--- THIS IS THE ONLY CHANGE
},
warning = function(w) {
if (!quiet) cli::cli_inform("Warning: {w}")
Expand Down
33 changes: 33 additions & 0 deletions tests/testthat/setup-mock-globals.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This file is run automatically by testthat before tests are run.
# It's a good place for mocks that are used across multiple test files.

# Mock the slow network call to java_valid_versions()
local_mocked_bindings(
java_valid_versions = function(...) c("8", "11", "17", "21")
)

# Mock common dependencies for java_install() tests
local_mocked_bindings(
# Assume user consent is always given in tests
rje_consent_check = function() TRUE,
# Assume java_unpack always succeeds and returns a predictable path.
# We are not testing java_unpack here.
# CORRECTED: This mock is now self-contained and does not depend on global options.
java_unpack = function(java_distrib_path, ...) {
filename <- basename(java_distrib_path)
parts <- strsplit(gsub("\\.tar\\.gz|\\.zip", "", filename), "-")[[1]]
version <- parts[parts %in% c("8", "11", "17", "21")][1]
arch <- parts[parts %in% c("x64", "aarch64")][1]
platform <- parts[parts %in% c("linux", "windows", "macos")][1]

# Use a generic, hardcoded root path instead of calling getOption().
# This makes the mock independent of the state being tested.
file.path(
"/mock/cache/path",
"installed",
platform,
arch,
version
)
}
)
151 changes: 151 additions & 0 deletions tests/testthat/test-java_download-mocked.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Mocked unit tests for java_download()
test_that("java_download handles successful download and checksum", {
local_cache_path <- withr::local_tempdir()

# MOCK THE SLOW NETWORK CALL
local_mocked_bindings(
java_valid_versions = function(...) c("8", "11", "17", "21")
)

local_mocked_bindings(
curl_download = function(url, destfile, ...) {
if (grepl("\\.md5$", destfile)) {
writeLines("dummymd5checksum", destfile)
} else {
writeLines("dummy content", destfile)
}
},
.package = "curl"
)

local_mocked_bindings(
md5sum = function(files) setNames("dummymd5checksum", files),
.package = "tools"
)

expect_silent(
result_path <- java_download(
version = "21",
cache_path = local_cache_path,
quiet = TRUE
)
)
expect_true(file.exists(result_path))
})

test_that("java_download aborts on MD5 checksum mismatch", {
local_cache_path <- withr::local_tempdir()

# MOCK THE SLOW NETWORK CALL
local_mocked_bindings(
java_valid_versions = function(...) c("8", "11", "17", "21")
)

local_mocked_bindings(
curl_download = function(url, destfile, ...) {
if (grepl("\\.md5$", destfile)) {
writeLines("expected_checksum", destfile)
} else {
writeLines("some content", destfile)
}
},
.package = "curl"
)

local_mocked_bindings(
md5sum = function(files) setNames("actual_checksum_mismatch", files),
.package = "tools"
)

expect_error(
java_download(version = "21", cache_path = local_cache_path, quiet = TRUE),
"MD5 checksum mismatch"
)
})

test_that("java_download skips download if file exists and force is FALSE", {
local_cache_path <- withr::local_tempdir()
dest_dir <- file.path(local_cache_path, "distrib")
dir.create(dest_dir, recursive = TRUE)

# MOCK THE SLOW NETWORK CALL
local_mocked_bindings(
java_valid_versions = function(...) c("8", "11", "17", "21")
)

platform <- platform_detect()$os
arch <- platform_detect()$arch
java_urls <- java_urls_load()
url_template <- java_urls[["Corretto"]][[platform]][[arch]]
url <- gsub("\\{version\\}", "21", url_template)
expected_file_path <- file.path(dest_dir, basename(url))
file.create(expected_file_path)

local_mocked_bindings(
curl_download = function(...) {
stop("curl_download should not have been called!")
},
.package = "curl"
)

result <- java_download(
version = "21",
cache_path = local_cache_path,
platform = platform,
arch = arch,
quiet = TRUE,
force = FALSE
)

expect_equal(normalizePath(result), normalizePath(expected_file_path))
})


test_that("java_download overwrites if file exists and force is TRUE", {
local_cache_path <- withr::local_tempdir()
dest_dir <- file.path(local_cache_path, "distrib")
dir.create(dest_dir, recursive = TRUE)

# MOCK THE SLOW NETWORK CALL
local_mocked_bindings(
java_valid_versions = function(...) c("8", "11", "17", "21")
)

platform <- platform_detect()$os
arch <- platform_detect()$arch
java_urls <- java_urls_load()
url_template <- java_urls[["Corretto"]][[platform]][[arch]]
url <- gsub("\\{version\\}", "21", url_template)
expected_file_path <- file.path(dest_dir, basename(url))
writeLines("old content", expected_file_path)

curl_call_count <- 0
local_mocked_bindings(
curl_download = function(url, destfile, ...) {
curl_call_count <<- curl_call_count + 1
if (grepl("\\.md5$", destfile)) {
writeLines("new_checksum", destfile)
} else {
writeLines("new content", destfile)
}
},
.package = "curl"
)

local_mocked_bindings(
md5sum = function(files) setNames("new_checksum", files),
.package = "tools"
)

java_download(
version = "21",
cache_path = local_cache_path,
platform = platform,
arch = arch,
quiet = TRUE,
force = TRUE
)

expect_equal(curl_call_count, 2)
expect_equal(readLines(expected_file_path), "new content")
})
Loading
Loading