|
| 1 | +use ark_test::DummyArkFrontend; |
| 2 | + |
| 3 | +// `parallel` ships with base R, so no extra test dependency is needed. |
| 4 | +// |
| 5 | +// The fork guard only installs on Unix, because forking is the hazard and |
| 6 | +// Windows `parallel` never forks (it ships its own serial/`stop()` stubs in |
| 7 | +// `R/windows/mcdummies.R`). So the error tests below are `#[cfg(unix)]`: on |
| 8 | +// Windows these calls hit R's native behavior, which uses different messages |
| 9 | +// and even runs serially in some cases. The PSOCK tests stay cross-platform. |
| 10 | + |
| 11 | +#[cfg(unix)] |
| 12 | +#[test] |
| 13 | +fn test_fork_functions_error() { |
| 14 | + let frontend = DummyArkFrontend::lock(); |
| 15 | + |
| 16 | + frontend.execute_request_error( |
| 17 | + "parallel::mclapply(1:2, identity, mc.cores = 2)", |
| 18 | + |error_msg| { |
| 19 | + assert!(error_msg.contains("fork the R session")); |
| 20 | + }, |
| 21 | + ); |
| 22 | + |
| 23 | + // `library(parallel)` attaches the package, exercising the `pkg_bind` / |
| 24 | + // attach path. Unqualified `mclapply` should then hit the shim too. |
| 25 | + frontend.execute_request_error("library(parallel); mclapply(1:2, identity)", |error_msg| { |
| 26 | + assert!(error_msg.contains("fork the R session")); |
| 27 | + }); |
| 28 | + |
| 29 | + frontend.execute_request_error("parallel::mcparallel(1)", |error_msg| { |
| 30 | + assert!(error_msg.contains("fork the R session")); |
| 31 | + }); |
| 32 | + |
| 33 | + frontend.execute_request_error("parallel::pvec(1:10, sqrt, mc.cores = 2)", |error_msg| { |
| 34 | + assert!(error_msg.contains("fork the R session")); |
| 35 | + }); |
| 36 | + |
| 37 | + frontend.execute_request_error( |
| 38 | + "parallel::mcmapply(`+`, 1:2, 3:4, mc.cores = 2)", |
| 39 | + |error_msg| { |
| 40 | + assert!(error_msg.contains("fork the R session")); |
| 41 | + }, |
| 42 | + ); |
| 43 | + |
| 44 | + frontend.execute_request_error("parallel::mcMap(`+`, 1:2, 3:4)", |error_msg| { |
| 45 | + assert!(error_msg.contains("fork the R session")); |
| 46 | + }); |
| 47 | + |
| 48 | + frontend.execute_request_error("parallel::makeForkCluster(2)", |error_msg| { |
| 49 | + assert!(error_msg.contains("fork the R session")); |
| 50 | + }); |
| 51 | + |
| 52 | + // `mcfork` is unexported, so triple-colon access exercises the |
| 53 | + // namespace-only binding. |
| 54 | + frontend.execute_request_error("parallel:::mcfork()", |error_msg| { |
| 55 | + assert!(error_msg.contains("fork the R session")); |
| 56 | + }); |
| 57 | + |
| 58 | + // The public `makeCluster(type = "FORK")` dispatches to `makeForkCluster` |
| 59 | + // resolved through parallel's namespace, so it hits our shim too. |
| 60 | + frontend.execute_request_error("parallel::makeCluster(2, type = \"FORK\")", |error_msg| { |
| 61 | + assert!(error_msg.contains("fork the R session")); |
| 62 | + }); |
| 63 | +} |
| 64 | + |
| 65 | +// The multicore family runs serially, without forking, when `mc.cores = 1` or |
| 66 | +// the input has fewer than two elements. Those calls never reach `mcfork()`, so |
| 67 | +// the guard must leave them alone. This matches Windows, where {parallel} always |
| 68 | +// runs these serially. |
| 69 | + |
| 70 | +#[test] |
| 71 | +fn test_serial_execution_works() { |
| 72 | + let frontend = DummyArkFrontend::lock(); |
| 73 | + |
| 74 | + frontend.execute_request( |
| 75 | + "unlist(parallel::mclapply(1:4, function(x) x * 2L, mc.cores = 1))", |
| 76 | + |result| assert_eq!(result, "[1] 2 4 6 8"), |
| 77 | + ); |
| 78 | + |
| 79 | + frontend.execute_request( |
| 80 | + "parallel::pvec(1:4, function(x) x * 2L, mc.cores = 1)", |
| 81 | + |result| assert_eq!(result, "[1] 2 4 6 8"), |
| 82 | + ); |
| 83 | +} |
| 84 | + |
| 85 | +// PSOCK clusters start fresh R worker processes instead of forking the session, |
| 86 | +// so the guard must leave them untouched. These spawn real worker processes and |
| 87 | +// run a small parallel computation, asserting it returns the correct result. |
| 88 | + |
| 89 | +#[test] |
| 90 | +fn test_psock_cluster_works() { |
| 91 | + let frontend = DummyArkFrontend::lock(); |
| 92 | + |
| 93 | + frontend.execute_request( |
| 94 | + "local({ cl <- parallel::makePSOCKcluster(2); on.exit(parallel::stopCluster(cl)); sum(unlist(parallel::parLapply(cl, 1:4, function(x) x * 2L))) })", |
| 95 | + |result| assert_eq!(result, "[1] 20"), |
| 96 | + ); |
| 97 | + |
| 98 | + // `makeCluster()` defaults to a PSOCK cluster, which we don't guard. |
| 99 | + frontend.execute_request( |
| 100 | + "local({ cl <- parallel::makeCluster(2); on.exit(parallel::stopCluster(cl)); unlist(parallel::clusterApply(cl, 1:3, function(x) x + 1L)) })", |
| 101 | + |result| assert_eq!(result, "[1] 2 3 4"), |
| 102 | + ); |
| 103 | +} |
0 commit comments