diff --git a/.github/workflows/test.yml b/.github/workflows/tests.yml similarity index 97% rename from .github/workflows/test.yml rename to .github/workflows/tests.yml index 07c3250..c5f56e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,4 @@ -name: test +name: tests on: push: @@ -7,7 +7,7 @@ on: branches: [ main, dev ] jobs: - test: + tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/README.md b/README.md index c5e20d3..fed70bd 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,7 @@ Code Ocean capsule: MOSuite - normalize counts +[![tests](https://github.com/NIDAP-Community/MOSuite-normalize-counts/actions/workflows/tests.yml/badge.svg)](https://github.com/NIDAP-Community/MOSuite-normalize-counts/actions/workflows/tests.yml) + - [Code Ocean Capsule](https://poc-nci.codeocean.io/capsule/6680510/tree) | [Latest Release](https://poc-nci.codeocean.io/capsule/9169525/tree/latest) - [MOSuite R package docs](https://ccbr.github.io/MOSuite/) diff --git a/code/main.R b/code/main.R index 6ec1f37..143626c 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,34 +12,164 @@ setup_capsule_environment() # parse CLI arguments parser <- ArgumentParser() -parser$add_argument("--count_type", type="character", default="filt") -parser$add_argument("--norm_type", type="character", default="voom") -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("--sample_id_colname", type="character", default=NULL, help="Column name for sample IDs") -parser$add_argument("--group_colname", type="character", default="Group", help="Column name for sample groups") -parser$add_argument("--label_colname", type="character", default=NULL, help="Column name for sample labels") -parser$add_argument("--input_in_log_counts", type="logical", default=FALSE, help="Counts are already log2-transformed") -parser$add_argument("--voom_normalization_method", type="character", default="quantile", help="Normalization method for limma::voom") -parser$add_argument("--samples_to_rename", type="character", default="", help="Sample renaming pairs: old:new,old2:new2") -parser$add_argument("--add_label_to_pca", type="logical", default=TRUE, help="Label points on the PCA plot") -parser$add_argument("--principal_component_on_x_axis", type="integer", default=1, help="PCA component on x-axis") -parser$add_argument("--principal_component_on_y_axis", type="integer", default=2, help="PCA component on y-axis") -parser$add_argument("--legend_position_for_pca", type="character", default="top", help="Legend position for PCA plot") -parser$add_argument("--label_offset_x_", type="double", default=2, help="Label offset x for PCA plot") -parser$add_argument("--label_offset_y_", type="double", default=2, help="Label offset y for PCA plot") -parser$add_argument("--label_font_size", type="double", default=3, help="Label font size for PCA plot") -parser$add_argument("--point_size_for_pca", type="double", default=8, help="Point size for PCA plot") -parser$add_argument("--color_histogram_by_group", type="logical", default=TRUE, help="Color histogram by group") -parser$add_argument("--set_min_max_for_x_axis_for_histogram", type="logical", default=FALSE, help="Set min/max x-axis for histogram") -parser$add_argument("--minimum_for_x_axis_for_histogram", type="double", default=-1, help="Histogram x-axis minimum") -parser$add_argument("--maximum_for_x_axis_for_histogram", type="double", default=1, help="Histogram x-axis maximum") -parser$add_argument("--legend_font_size_for_histogram", type="double", default=10, help="Legend font size for histogram") -parser$add_argument("--legend_position_for_histogram", type="character", default="top", help="Legend position for histogram") -parser$add_argument("--number_of_histogram_legend_columns", type="integer", default=6, help="Number of legend columns for histogram") -parser$add_argument("--plot_corr_matrix_heatmap", type="logical", default=TRUE, help="Plot correlation heatmap") -parser$add_argument("--colors_for_plots", type="character", default="", help="Comma-separated list of colors for plots") -parser$add_argument("--interactive_plots", type="logical", default=FALSE, help="Create interactive plots with plotly") +parser$add_argument("--count_type", type = "character", default = "filt") +parser$add_argument("--norm_type", type = "character", default = "voom") +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( + "--sample_id_colname", + type = "character", + default = NULL, + help = "Column name for sample IDs" +) +parser$add_argument( + "--group_colname", + type = "character", + default = "Group", + help = "Column name for sample groups" +) +parser$add_argument( + "--label_colname", + type = "character", + default = NULL, + help = "Column name for sample labels" +) +parser$add_argument( + "--input_in_log_counts", + type = "logical", + default = FALSE, + help = "Counts are already log2-transformed" +) +parser$add_argument( + "--voom_normalization_method", + type = "character", + default = "quantile", + help = "Normalization method for limma::voom" +) +parser$add_argument( + "--samples_to_rename", + type = "character", + default = "", + help = "Sample renaming pairs: old:new,old2:new2" +) +parser$add_argument( + "--add_label_to_pca", + type = "logical", + default = TRUE, + help = "Label points on the PCA plot" +) +parser$add_argument( + "--principal_component_on_x_axis", + type = "integer", + default = 1, + help = "PCA component on x-axis" +) +parser$add_argument( + "--principal_component_on_y_axis", + type = "integer", + default = 2, + help = "PCA component on y-axis" +) +parser$add_argument( + "--legend_position_for_pca", + type = "character", + default = "top", + help = "Legend position for PCA plot" +) +parser$add_argument( + "--label_offset_x_", + type = "double", + default = 2, + help = "Label offset x for PCA plot" +) +parser$add_argument( + "--label_offset_y_", + type = "double", + default = 2, + help = "Label offset y for PCA plot" +) +parser$add_argument( + "--label_font_size", + type = "double", + default = 3, + help = "Label font size for PCA plot" +) +parser$add_argument( + "--point_size_for_pca", + type = "double", + default = 8, + help = "Point size for PCA plot" +) +parser$add_argument( + "--color_histogram_by_group", + type = "logical", + default = TRUE, + help = "Color histogram by group" +) +parser$add_argument( + "--set_min_max_for_x_axis_for_histogram", + type = "logical", + default = FALSE, + help = "Set min/max x-axis for histogram" +) +parser$add_argument( + "--minimum_for_x_axis_for_histogram", + type = "double", + default = -1, + help = "Histogram x-axis minimum" +) +parser$add_argument( + "--maximum_for_x_axis_for_histogram", + type = "double", + default = 1, + help = "Histogram x-axis maximum" +) +parser$add_argument( + "--legend_font_size_for_histogram", + type = "double", + default = 10, + help = "Legend font size for histogram" +) +parser$add_argument( + "--legend_position_for_histogram", + type = "character", + default = "top", + help = "Legend position for histogram" +) +parser$add_argument( + "--number_of_histogram_legend_columns", + type = "integer", + default = 6, + help = "Number of legend columns for histogram" +) +parser$add_argument( + "--plot_corr_matrix_heatmap", + type = "logical", + default = TRUE, + help = "Plot correlation heatmap" +) +parser$add_argument( + "--colors_for_plots", + type = "character", + default = "", + help = "Comma-separated list of colors for plots" +) +parser$add_argument( + "--interactive_plots", + type = "logical", + default = FALSE, + help = "Create interactive plots with plotly" +) args <- parser$parse_args() @@ -48,35 +177,35 @@ args <- parser$parse_args() moo <- load_moo_from_data_dir() # run MOSuite -moo |> - normalize_counts( - count_type = args$count_type, - norm_type = args$norm_type, - feature_id_colname = args$feature_id_colname, - samples_to_include = parse_optional_vector(args$samples_to_include), - sample_id_colname = args$sample_id_colname, - group_colname = args$group_colname, - label_colname = args$label_colname, - input_in_log_counts = args$input_in_log_counts, - voom_normalization_method = args$voom_normalization_method, - samples_to_rename = parse_samples_to_rename(args$samples_to_rename), - add_label_to_pca = args$add_label_to_pca, - principal_component_on_x_axis = args$principal_component_on_x_axis, - principal_component_on_y_axis = args$principal_component_on_y_axis, - legend_position_for_pca = args$legend_position_for_pca, - label_offset_x_ = args$label_offset_x_, - label_offset_y_ = args$label_offset_y_, - label_font_size = args$label_font_size, - point_size_for_pca = args$point_size_for_pca, - color_histogram_by_group = args$color_histogram_by_group, - set_min_max_for_x_axis_for_histogram = args$set_min_max_for_x_axis_for_histogram, - minimum_for_x_axis_for_histogram = args$minimum_for_x_axis_for_histogram, - maximum_for_x_axis_for_histogram = args$maximum_for_x_axis_for_histogram, - legend_font_size_for_histogram = args$legend_font_size_for_histogram, - legend_position_for_histogram = args$legend_position_for_histogram, - number_of_histogram_legend_columns = args$number_of_histogram_legend_columns, - plot_corr_matrix_heatmap = args$plot_corr_matrix_heatmap, - colors_for_plots = parse_optional_vector(args$colors_for_plots), - interactive_plots = args$interactive_plots - ) |> - write_rds(file.path(getOption("moo_plots_dir"), "..", "moo", "moo.rds")) \ No newline at end of file +moo |> + normalize_counts( + count_type = args$count_type, + norm_type = args$norm_type, + feature_id_colname = args$feature_id_colname, + samples_to_include = parse_optional_vector(args$samples_to_include), + sample_id_colname = args$sample_id_colname, + group_colname = args$group_colname, + label_colname = args$label_colname, + input_in_log_counts = args$input_in_log_counts, + voom_normalization_method = args$voom_normalization_method, + samples_to_rename = parse_samples_to_rename(args$samples_to_rename), + add_label_to_pca = args$add_label_to_pca, + principal_component_on_x_axis = args$principal_component_on_x_axis, + principal_component_on_y_axis = args$principal_component_on_y_axis, + legend_position_for_pca = args$legend_position_for_pca, + label_offset_x_ = args$label_offset_x_, + label_offset_y_ = args$label_offset_y_, + label_font_size = args$label_font_size, + point_size_for_pca = args$point_size_for_pca, + color_histogram_by_group = args$color_histogram_by_group, + set_min_max_for_x_axis_for_histogram = args$set_min_max_for_x_axis_for_histogram, + minimum_for_x_axis_for_histogram = args$minimum_for_x_axis_for_histogram, + maximum_for_x_axis_for_histogram = args$maximum_for_x_axis_for_histogram, + legend_font_size_for_histogram = args$legend_font_size_for_histogram, + legend_position_for_histogram = args$legend_position_for_histogram, + number_of_histogram_legend_columns = args$number_of_histogram_legend_columns, + plot_corr_matrix_heatmap = args$plot_corr_matrix_heatmap, + colors_for_plots = parse_optional_vector(args$colors_for_plots), + interactive_plots = args$interactive_plots + ) |> + write_rds(file.path(getOption("moo_plots_dir"), "..", "moo", "moo-norm.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/test-setup.R b/tests/test-setup.R new file mode 100644 index 0000000..c95fa0b --- /dev/null +++ b/tests/test-setup.R @@ -0,0 +1,45 @@ +#!/usr/bin/env Rscript + +# Simple test to verify the test setup and data exists +cat("Testing basic test setup...\n") + +# Get the current working directory during test execution +cwd <- getwd() +cat("Current directory:", cwd, "\n") + +# Calculate repo root the same way the test does in tests/testthat/test-main.R +# When running from tests/testthat, dirname(getwd()) goes to tests/ +repo_root_test_calculation <- normalizePath(file.path(dirname(cwd), "..")) +cat( + "Calculated repo_root (test calculation):", + repo_root_test_calculation, + "\n" +) + +# From script running in tests directory: +# When this script is in tests/ directory, repo_root should be dirname(getwd()) +repo_root_script <- normalizePath(dirname(cwd)) +cat("Calculated repo_root (script location):", repo_root_script, "\n") + +# Check if test data exists using test calculation +test_data_file <- file.path(repo_root_script, "data", "moo-batch.rds") +cat("\nLooking for test data at:", test_data_file, "\n") +cat("Test data exists:", file.exists(test_data_file), "\n") + +# Check code files +code_main <- file.path(repo_root_script, "..", "code", "main.R") +code_run <- file.path(repo_root_script, "..", "code", "run") +cat("code/main.R exists:", file.exists(code_main), "\n") +cat("code/run exists:", file.exists(code_run), "\n") + +# Try to load the MOO object +if (file.exists(test_data_file)) { + cat("\nTrying to load MOO object...\n") + library(readr) + moo <- readr::read_rds(test_data_file) + cat("MOO object loaded successfully!\n") + cat("MOO class:", class(moo), "\n") + if (hasSlot(moo, "counts")) { + cat("MOO columns in counts:", names(moo@counts), "\n") + } +} 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..235c06a --- /dev/null +++ b/tests/testthat/test-main.R @@ -0,0 +1,149 @@ +test_that("code/run executes successfully with default CLI arguments", { + # Create temporary workspace + workspace <- tempfile("mosuite_normalize_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", + "--count_type=filt", + "--norm_type=voom", + "--voom_normalization_method=quantile", + "--plot_corr_matrix_heatmap=FALSE", + "--interactive_plots=FALSE" + ) + ) + + # 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-norm.rds")), + info = "Output file moo-norm.rds should be created" + ) + + # Validate output is a valid MOO object + moo <- readr::read_rds(file.path(results_dir, "moo", "moo-norm.rds")) + expect_true( + S7::S7_inherits(moo, MOSuite::multiOmicDataSet), + info = "Output should be an S7 multiOmicDataSet object" + ) + + # Validate norm counts exist + expect_true( + "norm" %in% names(moo@counts), + info = "Output should have norm counts in moo@counts" + ) +}) + +test_that("code/run executes with custom CLI arguments", { + # Create temporary workspace + workspace <- tempfile("mosuite_normalize_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", + "--norm_type=voom", + "--voom_normalization_method=none", + "--add_label_to_pca=FALSE", + "--principal_component_on_x_axis=1", + "--principal_component_on_y_axis=3", + "--plot_corr_matrix_heatmap=FALSE", + "--interactive_plots=FALSE" + ) + ) + + # 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-norm.rds")), + info = "Output file moo-norm.rds should be created with custom args" + ) + + # Validate output is a valid MOO object + moo <- readr::read_rds(file.path(results_dir, "moo", "moo-norm.rds")) + expect_true( + S7::S7_inherits(moo, MOSuite::multiOmicDataSet), + info = "Output should be an S7 multiOmicDataSet object" + ) + + # Validate norm counts exist + expect_true( + "norm" %in% names(moo@counts), + info = "Output should have norm counts in moo@counts" + ) +})