diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..61d89f6 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,29 @@ +name: tests + +on: + push: + branches: [ main, dev ] + pull_request: + branches: [ main, dev ] + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Parse environment.json and set Docker image + id: docker_image + run: | + IMAGE=$(jq -r '.base_image' .codeocean/environment.json) + IMAGE=${IMAGE//codeocean/nciccbr} + echo "image=$IMAGE" >> $GITHUB_OUTPUT + echo "Using Docker image: $IMAGE" + + - name: Run tests in Docker + run: | + docker run --rm \ + -v ${{ github.workspace }}:/workspace \ + -w /workspace \ + ${{ steps.docker_image.outputs.image }} \ + Rscript tests/testthat.R diff --git a/code/main.R b/code/main.R index ca06bfa..e9bff11 100644 --- a/code/main.R +++ b/code/main.R @@ -1,5 +1,4 @@ #!/usr/bin/env Rscript -rlang::global_entrace() library(argparse) library(glue) library(MOSuite) @@ -13,17 +12,67 @@ setup_capsule_environment() # parse CLI arguments parser <- ArgumentParser() -parser$add_argument("--count_type", type="character", default="filt") -parser$add_argument("--sub_count_type", type="character", default=NULL, help="Sub count type if count_type is a list") -parser$add_argument("--sample_id_colname", type="character", default=NULL, help="Column name for sample IDs") -parser$add_argument("--feature_id_colname", type="character", default=NULL, help="Column name for feature IDs") -parser$add_argument("--samples_to_include", type="character", default="", help="Comma-separated list of samples to include") -parser$add_argument("--covariates_colnames", type="character", default="Group", help="Comma-separated list of covariate column names") -parser$add_argument("--contrast_colname", type="character", default="Group", help="Column containing group variables for DE") -parser$add_argument("--contrasts", type="character", default="", help="Contrasts in format group1-group2,group1-group3") -parser$add_argument("--input_in_log_counts", type="logical", default=FALSE, help="Counts are already log2-transformed") -parser$add_argument("--return_mean_and_sd", type="logical", default=FALSE, help="Return mean and SD in addition to DE estimates") -parser$add_argument("--voom_normalization_method", type="character", default="quantile", help="Normalization method for limma::voom") +parser$add_argument("--count_type", type = "character", default = "filt") +parser$add_argument( + "--sub_count_type", + type = "character", + default = NULL, + help = "Sub count type if count_type is a list" +) +parser$add_argument( + "--sample_id_colname", + type = "character", + default = NULL, + help = "Column name for sample IDs" +) +parser$add_argument( + "--feature_id_colname", + type = "character", + default = NULL, + help = "Column name for feature IDs" +) +parser$add_argument( + "--samples_to_include", + type = "character", + default = "", + help = "Comma-separated list of samples to include" +) +parser$add_argument( + "--covariates_colnames", + type = "character", + default = "Group", + help = "Comma-separated list of covariate column names" +) +parser$add_argument( + "--contrast_colname", + type = "character", + default = "Group", + help = "Column containing group variables for DE" +) +parser$add_argument( + "--contrasts", + type = "character", + default = "", + help = "Contrasts in format group1-group2,group1-group3" +) +parser$add_argument( + "--input_in_log_counts", + type = "logical", + default = FALSE, + help = "Counts are already log2-transformed" +) +parser$add_argument( + "--return_mean_and_sd", + type = "logical", + default = FALSE, + help = "Return mean and SD in addition to DE estimates" +) +parser$add_argument( + "--voom_normalization_method", + type = "character", + default = "quantile", + help = "Normalization method for limma::voom" +) args <- parser$parse_args() @@ -32,25 +81,28 @@ moo <- load_moo_from_data_dir() # validate required parameters if (identical(args$covariates_colnames, "")) { - stop("covariates_colnames is required and cannot be empty") + stop("covariates_colnames is required and cannot be empty") } if (identical(args$contrasts, "")) { - stop("contrasts is required and cannot be empty") + stop("contrasts is required and cannot be empty") } # run MOSuite -moo |> - diff_counts( - count_type = args$count_type, - sub_count_type = args$sub_count_type, - sample_id_colname = args$sample_id_colname, - feature_id_colname = args$feature_id_colname, - samples_to_include = parse_optional_vector(args$samples_to_include), - covariates_colnames = parse_vector_with_default(args$covariates_colnames, "Group"), - contrast_colname = args$contrast_colname, - contrasts = parse_optional_vector(args$contrasts), - input_in_log_counts = args$input_in_log_counts, - return_mean_and_sd = args$return_mean_and_sd, - voom_normalization_method = args$voom_normalization_method - ) |> - write_rds(file.path(getOption("moo_plots_dir"), "..", "moo", "moo.rds")) +moo |> + diff_counts( + count_type = args$count_type, + sub_count_type = args$sub_count_type, + sample_id_colname = args$sample_id_colname, + feature_id_colname = args$feature_id_colname, + samples_to_include = parse_optional_vector(args$samples_to_include), + covariates_colnames = parse_vector_with_default( + args$covariates_colnames, + "Group" + ), + contrast_colname = args$contrast_colname, + contrasts = parse_optional_vector(args$contrasts), + input_in_log_counts = args$input_in_log_counts, + return_mean_and_sd = args$return_mean_and_sd, + voom_normalization_method = args$voom_normalization_method + ) |> + write_rds(file.path(getOption("moo_plots_dir"), "..", "moo", "moo-diff.rds")) diff --git a/tests/data/moo-filt.rds b/tests/data/moo-filt.rds new file mode 100644 index 0000000..24a9a52 Binary files /dev/null and b/tests/data/moo-filt.rds differ diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..ca2904a --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,2 @@ +library(testthat) +test_dir(file.path('tests', 'testthat'), stop_on_failure = TRUE) diff --git a/tests/testthat/test-main.R b/tests/testthat/test-main.R new file mode 100644 index 0000000..9738a5c --- /dev/null +++ b/tests/testthat/test-main.R @@ -0,0 +1,146 @@ +test_that("code/run executes successfully with default CLI arguments", { + # Create temporary workspace + workspace <- tempfile("mosuite_diff_counts_test_") + dir.create(workspace) + on.exit(unlink(workspace, recursive = TRUE), add = TRUE) + + # Set up directory structure + code_dir <- file.path(workspace, "code") + data_dir <- file.path(workspace, "data") + results_dir <- file.path(code_dir, "..", "results") + dir.create(code_dir) + dir.create(data_dir) + dir.create(results_dir) + + # Get test data from package tests directory + repo_root <- normalizePath(file.path(dirname(getwd()), "..")) + test_data_file <- file.path(repo_root, "tests", "data", "moo-filt.rds") + + expect_true( + file.exists(test_data_file), + info = paste("Test data file should exist at", test_data_file) + ) + file.copy(test_data_file, file.path(data_dir, "moo.rds")) + + # Copy main.R and run script to workspace + file.copy( + file.path(repo_root, "code", "main.R"), + file.path(code_dir, "main.R") + ) + file.copy( + file.path(repo_root, "code", "run"), + file.path(code_dir, "run") + ) + + # Run the script from code directory + old_wd <- getwd() + setwd(code_dir) + on.exit(setwd(old_wd), add = TRUE) + + # Execute run script with default CLI arguments + exit_code <- system2( + "bash", + args = c( + "run", + "--covariates_colnames=Group", + "--contrast_colname=Group", + "--contrasts=B-A" + ) + ) + + # Check for successful execution + expect_equal(exit_code, 0, info = "run script should execute without error") + expect_true( + file.exists(file.path(results_dir, "moo", "moo-diff.rds")), + info = "Output file moo-diff.rds should be created" + ) + + # Validate output is a valid MOO object + moo <- readr::read_rds(file.path(results_dir, "moo", "moo-diff.rds")) + expect_true( + S7::S7_inherits(moo, MOSuite::multiOmicDataSet), + info = "Output should be an S7 multiOmicDataSet object" + ) + + # Validate diff results exist + expect_true( + "diff" %in% names(moo@analyses), + info = "Output should have diff results in moo@analyses" + ) +}) + +test_that("code/run executes with custom CLI arguments", { + # Create temporary workspace + workspace <- tempfile("mosuite_diff_counts_custom_test_") + dir.create(workspace) + on.exit(unlink(workspace, recursive = TRUE), add = TRUE) + + # Set up directory structure + code_dir <- file.path(workspace, "code") + data_dir <- file.path(workspace, "data") + results_dir <- file.path(code_dir, "..", "results") + dir.create(code_dir) + dir.create(data_dir) + dir.create(results_dir) + + # Get test data from package tests directory + repo_root <- normalizePath(file.path(dirname(getwd()), "..")) + test_data_file <- file.path(repo_root, "tests", "data", "moo-filt.rds") + + # Copy test data to workspace + file.copy(test_data_file, file.path(data_dir, "moo.rds")) + + # Copy main.R and run script to workspace + file.copy( + file.path(repo_root, "code", "main.R"), + file.path(code_dir, "main.R") + ) + file.copy( + file.path(repo_root, "code", "run"), + file.path(code_dir, "run") + ) + + # Run the script from code directory + old_wd <- getwd() + setwd(code_dir) + on.exit(setwd(old_wd), add = TRUE) + + # Execute run script with custom CLI arguments + exit_code <- system2( + "bash", + args = c( + "run", + "--count_type=filt", + "--covariates_colnames=Group", + "--contrast_colname=Group", + "--contrasts=C-A", + "--input_in_log_counts=FALSE", + "--return_mean_and_sd=TRUE", + "--voom_normalization_method=quantile" + ) + ) + + # Check for successful execution + expect_equal( + exit_code, + 0, + info = "run script with custom args should execute without error" + ) + expect_true( + file.exists(file.path(results_dir, "moo", "moo-diff.rds")), + info = "Output file moo-diff.rds should be created with custom args" + ) + + # Validate output is a valid MOO object + moo <- readr::read_rds(file.path(results_dir, "moo", "moo-diff.rds")) + expect_true( + S7::S7_inherits(moo, MOSuite::multiOmicDataSet), + info = "Output should be an S7 multiOmicDataSet object" + ) + + # Validate diff results exist + expect_true( + "diff" %in% names(moo@analyses), + info = "Output should have diff results in moo@analyses" + ) +})