tests/testthat/test-parse-pkgs.R

# Helper function to create a temporary Nix file
create_temp_nix <- function(content, project_path) {
  nix_file_path <- file.path(project_path, "temp_default.nix")
  writeLines(content, nix_file_path)
  return("temp_default.nix")
}

test_that("parse_packages handles various formats and edge cases", {
  path_tmpdir <- tempdir()
  dir.create(path_tmpdir, showWarnings = FALSE)
  on.exit(unlink(path_tmpdir, recursive = TRUE), add = TRUE, after = TRUE)

  nix_content1 <- c(
    "let",
    "  rpkgs = builtins.attrValues {",
    "    inherit (pkgs.rPackages);",
    "    tidyr",
    "    dplyr;",
    "  };",
    "in {}"
  )
  nix_file1 <- create_temp_nix(nix_content1, path_tmpdir)
  on.exit(unlink(nix_file1))

  pkgs1 <- parse_packages(nix_file1, path_tmpdir, "rpkgs", transform_r)
  expect_equal(sort(pkgs1), c("dplyr", "tidyr"))

  nix_content2 <- c(
    "let",
    "  pyconf = builtins.attrValues {",
    "    inherit (pkgs.python312Packages);",
    "    pandas",
    "    numpy",
    "    scikit-learn;",
    "  };",
    "in {}"
  )
  nix_file2 <- create_temp_nix(nix_content2, path_tmpdir)
  on.exit(unlink(nix_file2))

  pkgs2 <- parse_packages(nix_file2, path_tmpdir, "pyconf")
  expect_equal(sort(pkgs2), c("numpy", "pandas", "scikit-learn"))

  pkgs3 <- parse_packages(nix_file1, path_tmpdir, "nonexistent_block")
  expect_null(pkgs3)

  nix_content4 <- c(
    "let",
    "  rpkgs = builtins.attrValues {",
    "    dplyr"
    # Missing "};"
  )
  nix_file4 <- create_temp_nix(nix_content4, path_tmpdir)
  on.exit(unlink(nix_file4))

  expect_error(
    parse_packages(nix_file4, path_tmpdir, "rpkgs"),
    "Could not find the end of the rpkgs block"
  )

  nix_content5 <- c(
    "let",
    "  rpkgs = builtins.attrValues {",
    "    inherit (pkgs.rPackages);",
    "  };",
    "in {}"
  )
  nix_file5 <- create_temp_nix(nix_content5, path_tmpdir)
  on.exit(unlink(nix_file5))

  pkgs5 <- parse_packages(nix_file5, path_tmpdir, "rpkgs")
  expect_equal(pkgs5, character(0))
})

test_that("parse_packages handles pyconf with git packages (} ++ [ ... ];)", {
  path_tmpdir <- tempdir()
  dir.create(path_tmpdir, showWarnings = FALSE)
  on.exit(unlink(path_tmpdir, recursive = TRUE), add = TRUE, after = TRUE)

  nix_content <- c(
    'let',
    ' pkgs = import (fetchTarball "https://github.com/rstats-on-nix/nixpkgs/archive/2025-04-29.tar.gz") {};',
    ' ',
    '  rpkgs = builtins.attrValues {',
    '    inherit (pkgs.rPackages) ',
    '      dplyr',
    '      ggplot2',
    '      reticulate',
    '      yardstick;',
    '  };',
    ' ',
    '    rix = (pkgs.rPackages.buildRPackage {',
    '      name = "rix";',
    '      src = pkgs.fetchgit {',
    '        url = "https://github.com/ropensci/rix/";',
    '        rev = "HEAD";',
    '        sha256 = "sha256-4cn9/BxpGljoS65FcsYrXAI2wMKzOLYvjevsct1/+Ik=";',
    '      };',
    '      propagatedBuildInputs = builtins.attrValues {',
    '        inherit (pkgs.rPackages) ',
    '          codetools',
    '          curl',
    '          jsonlite',
    '          sys;',
    '      };',
    '    });',
    '',
    '    rixpress = (pkgs.rPackages.buildRPackage {',
    '      name = "rixpress";',
    '      src = pkgs.fetchgit {',
    '        url = "https://github.com/ropensci/rixpress";',
    '        rev = "HEAD";',
    '        sha256 = "sha256-JmArZXrLsjj68e8Xo75lQ3j3i9oP5vsNnc+x/IgvspI=";',
    '      };',
    '      propagatedBuildInputs = builtins.attrValues {',
    '        inherit (pkgs.rPackages) ',
    '          cli',
    '          igraph',
    '          jsonlite',
    '          processx;',
    '      };',
    '    });',
    '   ',
    '',
    '    ryxpress = (pkgs.python313Packages.buildPythonPackage {',
    '      pname = "ryxpress";',
    '      version = "HEAD-git";',
    '      src = pkgs.fetchgit {',
    '        url = "https://github.com/b-rodrigues/ryxpress";',
    '        rev = "HEAD";',
    '        sha256 = "sha256-agDRYWC4wV3Oum8KhS78ZXCXPo/4X1OmNER+WWnbxBw=";',
    '      };',
    '      pyproject = true;',
    '      build-system = [ pkgs.python313Packages.setuptools ];',
    '      doCheck = false;',
    '      propagatedBuildInputs = [ ];',
    '    });',
    ' ',
    '  pyconf = builtins.attrValues {',
    '    inherit (pkgs.python313Packages) ',
    '      pip',
    '      ipykernel',
    '      numpy',
    '      pandas',
    '      scikit-learn',
    '      xgboost;',
    '  } ++ [ ryxpress ];',
    '   ',
    '  system_packages = builtins.attrValues {',
    '    inherit (pkgs) ',
    '      glibcLocales',
    '      nix',
    '      python313',
    '      R;',
    '  };',
    '  ',
    '  shell = pkgs.mkShell {',
    '    LOCALE_ARCHIVE = if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "${pkgs.glibcLocales}/lib/locale/locale-archive" else "";',
    '    LANG = "en_US.UTF-8";',
    '    LC_ALL = "en_US.UTF-8";',
    '    LC_TIME = "en_US.UTF-8";',
    '    LC_MONETARY = "en_US.UTF-8";',
    '    LC_PAPER = "en_US.UTF-8";',
    '    LC_MEASUREMENT = "en_US.UTF-8";',
    '    RETICULATE_PYTHON = "${pkgs.python313}/bin/python";',
    '',
    '    buildInputs = [ rix rixpress rpkgs pyconf system_packages ];',
    '    ',
    '  }; ',
    'in',
    '  {',
    '    inherit pkgs shell;',
    '  }'
  )

  nix_file <- create_temp_nix(nix_content, path_tmpdir)
  pkgs <- parse_packages(nix_file, path_tmpdir, "pyconf")

  expected_pkgs <- c(
    "pip",
    "ipykernel",
    "numpy",
    "pandas",
    "scikit-learn",
    "xgboost",
    "ryxpress"
  )
  expect_equal(sort(pkgs), sort(expected_pkgs))
})

test_that("parse_packages handles mixed python (git) and julia blocks", {
  path_tmpdir <- tempdir()
  dir.create(path_tmpdir, showWarnings = FALSE)
  on.exit(unlink(path_tmpdir, recursive = TRUE), add = TRUE, after = TRUE)

  nix_content <- c(
    'let',
    ' pkgs = import (fetchTarball "https://github.com/rstats-on-nix/nixpkgs/archive/2026-01-19.tar.gz") {};',
    ' ',
    '     ryxpress = (pkgs.python313Packages.buildPythonPackage {',
    '       pname = "ryxpress";',
    '       version = "HEAD-git";',
    '       src = pkgs.fetchgit {',
    '         url = "https://github.com/b-rodrigues/ryxpress";',
    '         rev = "HEAD";',
    '         sha256 = "sha256-agDRYWC4wV3Oum8KhS78ZXCXPo/4X1OmNER+WWnbxBw=";',
    '       };',
    '       pyproject = true;',
    '       build-system = [ pkgs.python313Packages.setuptools ];',
    '       doCheck = false;',
    '       propagatedBuildInputs = [ ];',
    '     });',
    ' ',
    '   pyconf = builtins.attrValues {',
    '     inherit (pkgs.python313Packages) ',
    '       pip',
    '       ipykernel',
    '       pandas',
    '       pyarrow',
    '       scikit-learn',
    '       xgboost;',
    '   } ++ [ ryxpress ];',
    '  ',
    '   jlconf = pkgs.julia-lts.withPackages [ ',
    '       "Arrow"',
    '       "DataFrames"',
    '       "Distributions"',
    '       "Random"',
    '   ];',
    '   ',
    'in {}'
  )

  nix_file <- create_temp_nix(nix_content, path_tmpdir)
  pkgs_py <- parse_packages(nix_file, path_tmpdir, "pyconf")

  # Should ONLY contain python packages + ryxpress, NOT julia packages
  expected_py <- c(
    "pip",
    "ipykernel",
    "pandas",
    "pyarrow",
    "scikit-learn",
    "xgboost",
    "ryxpress"
  )
  expect_equal(sort(pkgs_py), sort(expected_py))
})

test_that("generate_libraries_script handles additional files", {
  path_tmpdir <- tempdir()
  dir.create(path_tmpdir, showWarnings = FALSE)
  on.exit(unlink(path_tmpdir, recursive = TRUE), add = TRUE, after = TRUE)

  # Setup additional files
  func_r_path <- file.path(path_tmpdir, "functions.R")
  func_py_path <- file.path(path_tmpdir, "functions.py")
  other_file_path <- file.path(path_tmpdir, "data.csv")
  writeLines("my_r_func <- function() {}", func_r_path)
  writeLines("def my_py_func(): pass", func_py_path)
  writeLines("a,b\n1,2", other_file_path)

  # Case 1: R script with functions.R
  r_outfile1 <- file.path(path_tmpdir, "lib1.R")
  generate_libraries_script(
    packages = c("dplyr"),
    additional_files = c(func_r_path, other_file_path), # Use full paths
    outfile = r_outfile1,
    import_formatter = import_formatter_r,
    additional_file_pattern = "functions\\.[Rr]"
  )
  lines1 <- readLines(r_outfile1)
  expect_equal(lines1, c("library(dplyr)", "my_r_func <- function() {}"))

  # Case 2: Python script with functions.py
  py_outfile1 <- file.path(path_tmpdir, "lib1.py")
  generate_libraries_script(
    packages = c("pandas"),
    additional_files = c(func_py_path, other_file_path), # Use full paths
    outfile = py_outfile1,
    import_formatter = import_formatter_py,
    additional_file_pattern = "functions\\.py"
  )
  lines2 <- readLines(py_outfile1)
  expect_equal(lines2, c("import pandas", "def my_py_func(): pass"))

  # Case 3: No matching additional files
  r_outfile2 <- file.path(path_tmpdir, "lib2.R")
  generate_libraries_script(
    packages = c("tidyr"),
    additional_files = c(other_file_path), # Use full path
    outfile = r_outfile2,
    import_formatter = import_formatter_r,
    additional_file_pattern = "functions\\.[Rr]"
  )
  lines3 <- readLines(r_outfile2)
  expect_equal(lines3, "library(tidyr)")

  # Case 4: Empty additional files
  r_outfile3 <- file.path(path_tmpdir, "lib3.R")
  generate_libraries_script(
    packages = c("stringr"),
    additional_files = "",
    outfile = r_outfile3,
    import_formatter = import_formatter_r,
    additional_file_pattern = "functions\\.[Rr]"
  )
  lines4 <- readLines(r_outfile3)
  expect_equal(lines4, "library(stringr)")
})

test_that("adjust_py_packages works correctly", {
  pkgs_in <- c("pandas", "pip", "numpy", "scikit-learn", "ipykernel")
  pkgs_out <- adjust_py_packages(pkgs_in)
  expect_equal(sort(pkgs_out), c("numpy", "pandas", "pickle", "sklearn"))

  pkgs_in2 <- c("tensorflow")
  pkgs_out2 <- adjust_py_packages(pkgs_in2)
  expect_equal(sort(pkgs_out2), c("pickle", "tensorflow"))

  pkgs_in3 <- character(0)
  pkgs_out3 <- adjust_py_packages(pkgs_in3)
  expect_equal(sort(pkgs_out3), c("pickle"))
})

test_that("transform_r works correctly", {
  expect_equal(transform_r("data_table"), "data.table")
  expect_equal(transform_r("ggplot2"), "ggplot2")
  expect_equal(
    transform_r(c("data_table", "stringi")),
    c("data.table", "stringi")
  )
})

test_that("adjust_import modifies files correctly", {
  path_tmpdir <- tempdir()
  path_rixpress <- file.path(path_tmpdir, "_rixpress")
  dir.create(path_rixpress, recursive = TRUE)
  on.exit(unlink(path_tmpdir, recursive = TRUE), add = TRUE, after = TRUE)

  # Create dummy files
  file1_path <- file.path(path_rixpress, "lib1.py")
  file2_path <- file.path(path_rixpress, "lib2.py")
  file3_path <- file.path(path_rixpress, "other.txt")
  writeLines(c("import pandas", "import numpy"), file1_path)
  writeLines(c("import os", "import pandas"), file2_path)
  writeLines(c("some text"), file3_path)

  # Perform adjustment (need to temporarily set working directory)
  old_wd <- getwd()
  setwd(path_tmpdir)
  adjust_import("import pandas", "import pandas as pd")
  setwd(old_wd)

  # Check file contents
  lines1 <- readLines(file1_path)
  lines2 <- readLines(file2_path)
  lines3 <- readLines(file3_path)

  expect_equal(lines1, c("import pandas as pd", "import numpy"))
  expect_equal(lines2, c("import os", "import pandas as pd"))
  expect_equal(lines3, c("some text")) # Should not change other files

  # Test non-matching import
  setwd(path_tmpdir)
  adjust_import("import non_existent", "import something_else")
  setwd(old_wd)

  lines1_after <- readLines(file1_path)
  expect_equal(lines1_after, c("import pandas as pd", "import numpy")) # No change
})

Try the rixpress package in your browser

Any scripts or data that you put into this service are public.

rixpress documentation built on Feb. 19, 2026, 9:06 a.m.