tests/testthat/test_job.R

if (rstudioapi::isAvailable()) {
  # Remove everything
  rm(list = ls(all.names = TRUE))
  rm(list = ls(all.names = TRUE, envir = globalenv()), envir = globalenv())
  if (length(sessionInfo()$otherPkgs) > 0)
    invisible(lapply(paste0('package:', names(sessionInfo()$otherPkgs)), detach, character.only = TRUE, unload = TRUE))

  # Explicit libraries for manual testing
  library(rstudioapi)
  library(testthat)

  # Helpers. Save in one obj to better control what's in the environment
  helpers = list(
    # Wait for some variable to be assigned.
    # Expect that it isn't assigned to begin with
    wait_for_job = function(varname) {
      expect_true(exists(varname, envir = parent.frame()) == FALSE)
      while (exists(varname, envir = parent.frame()) == FALSE) {
        Sys.sleep(0.5)
      }
    },
    pkgs = c("stats", "graphics", "grDevices", "utils", "datasets", "methods", "base")
  )

  options(job.mainsession = TRUE)  # Test whether this is imported to job sessions


  #########################
  # TEST DEFAULT BEHAVIOR #
  #########################
  test_that("Default usage with named chunk", {
    # Set env
    a = 123
    b = list(a = a, goat = "baah")
    inner_func = function(x) x^2
    outer_func = function(x) sum(inner_func(x))

    # Launch job
    job::job(default = {
      vars = ls(all.names = TRUE)
      pkgs = .packages()
      searchpaths_job = searchpaths()  # Test that packages are loaded in the correct order
      a_copy = a
      b_copy = b
      a = 123  # same value as imported; do not return
      b = list(a = a, goat = "peep")  # imported, but new value; return
      na_classed_works = is.character(NA_character_)  # Test that NA_character_ is not converted to NA
      inner_outer = outer_func(c(0, 2, 4))  # Tests https://github.com/lindeloev/job/issues/49
      attached_rstudioapi = exists("isAvailable")
      opts = options()
      print("output!")
    })

    # Check result
    helpers$wait_for_job("default")
    expect_identical(default$vars, c(".__jobsettings__", "a", "b", "inner_func", "outer_func"))
    expect_identical(default$pkgs, c("testthat", "rstudioapi", helpers$pkgs))
    expect_identical(default$searchpaths_job, searchpaths()[searchpaths() %in% c("devtools_shims", "SciViews:TempEnv") == FALSE])
    expect_identical(default$a_copy, a)
    expect_identical(default$b_copy, b)
    expect_true(default$na_classed_works)
    expect_true(default$inner_outer == 20)
    expect_true(default$attached_rstudioapi)
    expect_identical(names(default), c("inner_func", "b_copy", "pkgs", "b", ".jobcode", "outer_func", "opts", "a_copy", "vars", "searchpaths_job", "inner_outer", "attached_rstudioapi", "na_classed_works"))
    expect_true(is.null(default$opts$job.mainsession) == FALSE & default$opts$device == options("device"))

    # Cleanup
    rm(default, envir = globalenv())
  })



  ######################
  # TEST UNQUOTED ARGS #
  ######################
  test_that("Set arguments in job::job()", {
    # Set env
    a = 123
    b = list(a = a, goat = "baah")
    q = "some value"

    # Launch job
    returned = job::job(with_args1 = {
      vars = ls(all.names = TRUE)
      pkgs = .packages()
      b_copy = b
      q = list(i_am = "different")  # change value of imported
      attached_rstudioapi = exists("isAvailable")
      opts = options()
      print("output!")
    }, import = c(b, q), packages = c("job"), title = "something weird: #/(¤", opts = list(job.newopt = 59))

    # Check result
    helpers$wait_for_job("with_args1")
    expect_true(is.character(returned))
    expect_identical(with_args1$vars, c(".__jobsettings__", "b", "q"))
    expect_identical(with_args1$pkgs, c("job", helpers$pkgs))
    expect_identical(with_args1$b_copy, b)
    expect_true(with_args1$attached_rstudioapi == FALSE)
    expect_identical(names(with_args1), c("b_copy", "pkgs", ".jobcode", "opts", "vars", "q", "attached_rstudioapi"))
    expect_true(is.null(with_args1$opts$job.mainsession) == TRUE & with_args1$opts$job.newopt == 59)

    # Cleanup
    rm(with_args1, envir = globalenv())
  })



  #################################
  # TEST empty() WITH QUOTED ARGS #
  #################################
  test_that("Set arguments in job::empty()", {
    # Set env
    a = 123
    b = list(a = a, goat = "baah")
    q = "some value"

    # Launch job
    returned = job::empty(with_args2 = {
      vars = ls(all.names = TRUE)
      pkgs = .packages()
      b_copy = b
      q = list(i_am = "different")  # change value of imported
      attached_rstudioapi = exists("isAvailable")
      opts = options()
      print("output!")
    }, import = c("b", "q"), packages = c("job"), title = "something weird: #/(¤", opts = list(job.newopt = 59))

    # Check result
    helpers$wait_for_job("with_args2")
    expect_true(is.character(returned))
    expect_identical(with_args2$vars, c(".__jobsettings__", "b", "q"))
    expect_identical(with_args2$pkgs, c("job", helpers$pkgs))
    expect_identical(with_args2$b_copy, b)
    expect_true(with_args2$attached_rstudioapi == FALSE)
    expect_identical(names(with_args2), c("b_copy", "pkgs", ".jobcode", "opts", "vars", "q", "attached_rstudioapi"))
    expect_true(is.null(with_args2$opts$job.mainsession) == TRUE & with_args2$opts$job.newopt == 59)

    # Cleanup
    rm(with_args2, envir = globalenv())
  })


  ##############
  # TEST EMPTY #
  ##############
  test_that("Empty job via job::job()", {
    # Set env
    a = 123
    b = list(a = a, goat = "baah")

    # Launch job
    job::job(empty1 = {
      vars = ls(all.names = TRUE)
      pkgs = .packages()
      opts = options()
      print("output!")
    }, import = NULL, packages = NULL, opts = NULL)

    # Check results
    helpers$wait_for_job("empty1")
    expect_identical(empty1$vars, ".__jobsettings__")
    expect_identical(empty1$pkgs, helpers$pkgs)
    expect_identical(names(empty1), c("pkgs", ".jobcode", "opts", "vars"))
    expect_true(is.null(empty1$opts$job.mainsession) == TRUE)

    # Cleanup
    rm(empty1, envir = globalenv())
  })

  test_that("Empty job via job::empty()", {
    # Set env
    a = 123
    b = list(a = a, goat = "baah")

    # Launch job
    job::empty(empty2 = {
      vars = ls(all.names = TRUE)
      pkgs = .packages()
      opts = options()
      print("output!")
    })

    # Check results
    helpers$wait_for_job("empty2")
    expect_identical(empty2$vars, ".__jobsettings__")
    expect_identical(empty2$pkgs, helpers$pkgs)
    expect_identical(names(empty2), c("pkgs", ".jobcode", "opts", "vars"))
    expect_true(is.null(empty2$opts$job.mainsession) == TRUE)

    # Cleanup
    rm(empty2, envir = globalenv())
  })


  ####################
  # TEST JOB::EXPORT #
  ####################
  test_that("Default + export('all')", {
    # Set env
    a = 123

    # Launch job
    job::job(export_all = {
      q = 555
      print("output!")
      job::export("all")
    })

    # Check results
    helpers$wait_for_job("export_all")
    expect_identical(export_all$q, 555)
    expect_identical(names(export_all), c("a", ".jobcode", "q"))

    # Cleanup
    rm(export_all, envir = globalenv())
  })


  test_that("Default + export(NULL)", {
    # Set env
    a = 123

    # Launch job
    job::job(export_none = {
      q = 555
      print("output!")
      job::export(NULL)
    })

    # Check results
    helpers$wait_for_job("export_none")
    expect_identical(names(export_none), ".jobcode")

    # Cleanup
    rm(export_none, envir = globalenv())
  })


  test_that("Default + export(c(some, vars))", {
    # Set env
    a = 123
    b = list(a = a, goat = "baah")

    # Launch job
    job::job(export_some = {
      q = 555
      stuff = "don't return me"
      print("output!")
      job::export(c(a, q))
    })

    # Check results
    helpers$wait_for_job("export_some")
    expect_identical(names(export_some), c("a", ".jobcode", "q"))

    # Cleanup
    rm(export_some, envir = globalenv())
  })


  test_that("Default + export('new')", {
    # Set env
    a = 123

    # Launch job
    job::job(export_new = {
      a = 444
      q = 555
      print("output!")
      job::export("new")
    })

    # Check results
    helpers$wait_for_job("export_new")
    expect_identical(export_new$q, 555)
    expect_identical(names(export_new), c(".jobcode", "q"))

    # Cleanup
    rm(export_new, envir = globalenv())
  })


  test_that("Default + export('changed', file = 'myfile.RData')", {
    # Set env
    a = 123

    # Launch job
    job::job(export_file = {
      a = 123
      q = 555
      print("output!")
      job::export(file = "mydata.RData")
    })

    # Check results
    helpers$wait_for_job("export_file")
    expect_identical(names(export_file), c(".jobcode"))

    export_env = new.env()
    load("mydata.RData", envir = export_env)
    expect_identical(names(export_env), c("q"))
    expect_identical(export_env$q, 555)

    # Cleanup
    rm(export_file, envir = globalenv())
    file.remove("mydata.RData")
  })



  ##########################
  # TEST UNNAMED ARGUMENTS #
  ##########################
  # These two jobs are identical
  test_that("All args are unnamed", {
    # Set env
    a = 123
    b = list(a = a, goat = "baah")

    # Launch job
    job::job(args_unnamed = {
      a = 123
      vars = ls(all.names = TRUE)
      pkgs = .packages()
      opts = options()
      print("output!")
    }, c(a), c("rstudioapi"), list(job.newopt = 59))

    # Check results
    helpers$wait_for_job("args_unnamed")
    expect_identical(args_unnamed$vars, c(".__jobsettings__", "a"))
    expect_identical(args_unnamed$pkgs, c("rstudioapi", helpers$pkgs))
    expect_null(args_unnamed$opts$job.mainsession)
    expect_identical(args_unnamed$opts$job.newopt, 59)

    # Cleanup
    rm(args_unnamed, envir = globalenv())
  })

  test_that("Some args are unnamed", {
    # Set env
    a = 123
    b = list(a = a, goat = "baah")

    # Launch job
    job::job(args_onenamed = {
      a = 123
      vars = ls(all.names = TRUE)
      pkgs = .packages()
      opts = options()
      print("output!")
    }, c(a), packages = c("rstudioapi"), list(job.newopt = 59))

    # Check results
    helpers$wait_for_job("args_onenamed")
    expect_identical(args_onenamed$vars, c(".__jobsettings__", "a"))
    expect_identical(args_onenamed$pkgs, c("rstudioapi", helpers$pkgs))
    expect_null(args_onenamed$opts$job.mainsession)
    expect_identical(args_onenamed$opts$job.newopt, 59)

    # Cleanup
    rm(args_onenamed, envir = globalenv())
  })


  #########################
  # TEST RETURN TO GLOBAL #
  #########################
  test_that("Return to global", {
    job::job({
      a = 10  # Try assigning new value
      newvar1 = 1
      newvar5 = 5 * 3
      print("output!")
    }, packages = NULL)  # for speed

    helpers$wait_for_job("newvar1")
    expect_identical(get("a", envir = globalenv()), 10)
    expect_true(newvar1 == 1 & newvar5 == 15)
    #expect_identical(ls(envir = globalenv()), c("a", "newvar1", "newvar5"))  # b and helpers are in local env.

    rm(a, envir = globalenv())
    rm(newvar1, envir = globalenv())
    rm(newvar5, envir = globalenv())
  })



  ############
  # CLEAN UP #
  ############
  rm(list = ls(all.names = TRUE))
  rm(list = ls(all.names = TRUE, envir = globalenv()))
} else {
  expect_error(job::job({a = 1}), "You must run this from the RStudio main session.")
}
lindeloev/job documentation built on Oct. 3, 2024, 4:45 a.m.