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
3 changes: 3 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@
^\.history/.*$
^\.\.Rcheck$
^CKutils\.Rcheck$
^CLAUDE\.md$
^\.claude$
^\.claude/.*$
11 changes: 10 additions & 1 deletion .github/workflows/R-CMD-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ on:

name: R-CMD-check

# Cancel in-progress runs when a new run is triggered on the same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
R-CMD-check:
runs-on: ${{ matrix.config.os }}
Expand Down Expand Up @@ -66,11 +71,15 @@ jobs:
- {os: ubuntu-22.04, r: 'release'} # Ubuntu 22.04 LTS for broader compatibility
# - {os: macos-12, r: 'release'} # Intel Mac specifically (ddisabled for now due to slow execution)

# Environment variables for authentication and debugging
# Environment variables for authentication, debugging, and performance
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
R_KEEP_PKG_SOURCE: yes
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
# Enable parallel testing in tinytest
CI: true
# Set number of CPUs for R operations
_R_CHECK_TESTS_NLINES_: 0

steps:
# Step 1: Checkout the repository code
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/rchk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ on:

name: 'rchk'

# Cancel in-progress runs when a new run is triggered on the same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
rchk:
runs-on: ubuntu-latest
Expand Down
31 changes: 26 additions & 5 deletions .github/workflows/test-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ on:

name: test-coverage

# Cancel in-progress runs when a new run is triggered on the same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test-coverage:
runs-on: ubuntu-latest # Use Ubuntu for consistent coverage measurement
Expand All @@ -62,19 +67,35 @@ jobs:
extra-packages: any::covr
needs: coverage # Install dependencies needed for coverage analysis

# Step 4: Generate and upload test coverage
# Step 4: Generate test coverage report
- name: Measure test coverage
run: |
# Run coverage analysis with tinytest
covr::codecov(
# Run coverage analysis and save to file
cov <- covr::package_coverage(
quiet = FALSE,
clean = FALSE,
install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package"),
type = "all"
)
# Save coverage to Cobertura XML format for Codecov
covr::to_cobertura(cov, filename = "coverage.xml")
# Also print summary
print(cov)
shell: Rscript {0}

# Step 5: Show detailed test output if available (for debugging)
# Step 5: Upload coverage to Codecov using official action
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

# Step 6: Show detailed test output if available (for debugging)
- name: Show tinytest output
if: always() # Run even if previous steps failed
run: |
Expand All @@ -88,7 +109,7 @@ jobs:
echo "=== Coverage analysis complete ==="
shell: bash

# Step 6: Upload test results on failure (for debugging)
# Step 7: Upload test results on failure (for debugging)
- name: Upload test results on failure
if: failure() # Only run if the workflow failed
uses: actions/upload-artifact@v4
Expand Down
Empty file added CLAUDE.md
Empty file.
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Package: CKutils
Type: Package
Title: Some Utility Functions I Use Regularly
Version: 0.1.22
Date: 2026-01-20
Version: 0.1.23
Date: 2026-01-21
Authors@R: c(
person("Chris", "Kypridemos", email = "christodoulosk@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-0746-9229")),
person("Max", "Birkett", email = "pp0u8134@liverpool.ac.uk", role = "ctb", comment = c(ORCID = "0000-0002-6076-6820")),
Expand Down
160 changes: 160 additions & 0 deletions inst/tinytest/test-package_ops.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# =============================================================================
# Tests for package_ops.R
# =============================================================================
# Tests for detach_package, dependencies functions
# Note: installLocalPackage and installLocalPackageIfChanged are skipped
# as they require side effects (package installation, roxygen2 processing)

# Note: This test file is designed to run via tinytest::test_package("CKutils")
# which automatically loads the package.

# Ensure CRAN mirror is set for tests that use available.packages()
if (is.null(getOption("repos")) || getOption("repos")["CRAN"] == "@CRAN@") {
options(repos = c(CRAN = "https://cloud.r-project.org"))
}

# =============================================================================
# Tests for detach_package function
# =============================================================================

# Test 1: Error on non-character input
expect_error(
detach_package(123),
pattern = "must be a character string",
info = "detach_package: Error on numeric input"
)

expect_error(
detach_package(NULL),
pattern = "must be a character string",
info = "detach_package: Error on NULL input"
)

# Test 2: detach_package returns FALSE for non-attached package
result <- detach_package("nonexistent_package_xyz")
expect_false(
result,
info = "detach_package: Returns FALSE for non-attached package"
)

# Test 3: Message shown for non-attached package
expect_message(
detach_package("another_nonexistent_pkg"),
pattern = "was not attached",
info = "detach_package: Shows message for non-attached package"
)

# =============================================================================
# Tests for dependencies function
# =============================================================================

# Test 1: Error when no packages specified
expect_error(
dependencies(),
pattern = "No packages specified",
info = "dependencies: Error when no packages specified"
)

# Test 2: Error when pkges is not character
expect_error(
dependencies(pkges = 123),
pattern = "must be character strings",
info = "dependencies: Error when pkges is not character"
)

# Test 3: Basic functionality with already installed package
# Using 'stats' which is always available
result <- dependencies(
"stats",
install = FALSE,
quiet = TRUE,
verbose = TRUE
)
expect_true(is.data.frame(result), info = "dependencies: Returns data.frame")
expect_true("stats" %in% rownames(result), info = "dependencies: rownames OK")
expect_true(
all(c("loaded", "installed", "loaded.version", "available.version") %in%
names(result)),
info = "dependencies: Correct column names"
)
expect_true(
result["stats", "loaded"],
info = "dependencies: stats package loads correctly"
)

# Test 4: Test with multiple base packages (quoted)
result_multi <- dependencies(
c("stats", "utils"),
install = FALSE,
quiet = TRUE,
verbose = TRUE
)
expect_equal(nrow(result_multi), 2, info = "dependencies: Multiple packages")
expect_true(all(result_multi$loaded), info = "dependencies: All loaded")

# Test 5: Test unquoted package names
result_unquoted <- dependencies(
stats, utils,
install = FALSE,
quiet = TRUE,
verbose = TRUE
)
expect_equal(nrow(result_unquoted), 2, info = "dependencies: Unquoted names")
expect_true(
"stats" %in% rownames(result_unquoted),
info = "dependencies: Unquoted stats in rownames"
)
expect_true(
"utils" %in% rownames(result_unquoted),
info = "dependencies: Unquoted utils in rownames"
)

# Test 6: Test quiet parameter
expect_silent(
dependencies("stats", install = FALSE, quiet = TRUE, verbose = FALSE),
info = "dependencies: quiet=TRUE suppresses messages"
)

# Test 7: Duplicate packages are handled
result_dups <- dependencies(
c("stats", "stats", "utils"),
install = FALSE,
quiet = TRUE,
verbose = TRUE
)
expect_equal(nrow(result_dups), 2, info = "dependencies: Duplicates removed")

# Test 8: Test invisible return when verbose = FALSE
result_invisible <- dependencies(
"stats",
install = FALSE,
quiet = TRUE,
verbose = FALSE
)
expect_true(
is.data.frame(result_invisible),
info = "dependencies: Returns df even with verbose=FALSE"
)

# =============================================================================
# Tests for installLocalPackage error conditions
# =============================================================================
# Note: We only test error conditions, not actual installation

# Test: Error when DESCRIPTION file not found
temp_empty_dir <- tempfile("empty_pkg")
dir.create(temp_empty_dir)
on.exit(unlink(temp_empty_dir, recursive = TRUE), add = TRUE)

expect_error(
installLocalPackage(temp_empty_dir),
pattern = "DESCRIPTION file not found",
info = "installLocalPackage: Error when DESCRIPTION missing"
)

# Test: Error for installLocalPackageIfChanged when DESCRIPTION missing
expect_error(
installLocalPackageIfChanged(temp_empty_dir, tempfile()),
pattern = "DESCRIPTION file not found",
info = "installLocalPackageIfChanged: Error when DESCRIPTION missing"
)
22 changes: 21 additions & 1 deletion tests/tinytest.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
# CKutils test runner using tinytest
# Optimized for GitHub Actions CI with parallel test support

if (require("tinytest", quietly = TRUE)) {
tinytest::test_package("CKutils")
# Detect number of CPUs for parallel testing
# Use parallel::detectCores() with fallback
ncpus <- getOption(
"Ncpus",
default = max(1L, parallel::detectCores(logical = FALSE) - 1L)
)

# In CI environments, limit parallelism to avoid resource contention
if (nzchar(Sys.getenv("CI")) || nzchar(Sys.getenv("GITHUB_ACTIONS"))) {
ncpus <- min(ncpus, 2L)
}

# Run tests with parallel support when ncpus > 1
tinytest::test_package(
"CKutils",
ncpu = ncpus,
side_effects = TRUE # Allow tests with side effects
)
}
Loading