├── .Rbuildignore ├── .Rprofile ├── .covrignore ├── .github ├── .gitignore ├── CODE_OF_CONDUCT.md └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ ├── pr-commands.yaml │ └── test-coverage.yaml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── aaa-rstudio-detect.R ├── callr-package.R ├── check.R ├── error.R ├── eval-bg.R ├── eval.R ├── hook.R ├── load-client.R ├── options.R ├── package.R ├── presets.R ├── processx-forward.R ├── r-process.R ├── r-session.R ├── rcmd-bg.R ├── rcmd-process.R ├── rcmd.R ├── result.R ├── rscript.R ├── run.R ├── script.R ├── setup.R ├── standalone-errors.R ├── test-helpers.R ├── utils.R └── x-client.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── air.toml ├── callr.Rproj ├── codecov.yml ├── inst ├── COPYRIGHTS ├── WORDLIST ├── developer-notes.md └── header.md ├── man ├── add_hook.Rd ├── callr-package.Rd ├── convert_and_check_my_args.Rd ├── default_repos.Rd ├── figures │ ├── bg-dark.svg │ ├── bg-methods-dark.svg │ ├── bg-methods.svg │ ├── bg.svg │ ├── error1-dark.svg │ ├── error1.svg │ ├── error2-2-dark.svg │ ├── error2-2.svg │ ├── io-2-dark.svg │ ├── io-2.svg │ ├── io-dark.svg │ ├── io.svg │ ├── packages-dark.svg │ ├── packages.svg │ ├── passargsfail-dark.svg │ ├── passargsfail.svg │ ├── passargsok-dark.svg │ ├── passargsok.svg │ ├── poll-2-dark.svg │ ├── poll-2.svg │ ├── poll-3-dark.svg │ ├── poll-3.svg │ ├── poll-4-dark.svg │ ├── poll-4.svg │ ├── poll-dark.svg │ ├── poll.svg │ ├── rcmd-dark.svg │ ├── rcmd.svg │ ├── rsession-4-dark.svg │ ├── rsession-4.svg │ ├── rsession-5-dark.svg │ ├── rsession-5.svg │ ├── rsession-dark.svg │ ├── rsession.svg │ ├── rsession2-2-dark.svg │ ├── rsession2-2.svg │ ├── rsession2-dark.svg │ ├── rsession2.svg │ ├── simple-dark.svg │ └── simple.svg ├── get_result.Rd ├── new_callr_crash_error.Rd ├── r.Rd ├── r_bg.Rd ├── r_copycat.Rd ├── r_process.Rd ├── r_process_options.Rd ├── r_session.Rd ├── r_session_debug.Rd ├── r_session_options.Rd ├── r_vanilla.Rd ├── rcmd.Rd ├── rcmd_bg.Rd ├── rcmd_copycat.Rd ├── rcmd_process.Rd ├── rcmd_process_options.Rd ├── rcmd_safe_env.Rd ├── reexports.Rd ├── roxygen │ └── meta.R ├── rscript.Rd ├── rscript_process.Rd ├── rscript_process_options.Rd └── supported_archs.Rd ├── tests ├── testthat.R └── testthat │ ├── _snaps │ ├── error.md │ ├── eval.md │ ├── load-client.md │ ├── options.md │ ├── r-bg.md │ └── r-session.md │ ├── fixtures │ ├── D1 │ ├── csomag │ │ ├── DESCRIPTION │ │ ├── NAMESPACE │ │ └── R │ │ │ └── libpath.R │ ├── script.R │ ├── script2.R │ └── simple.txt │ ├── helper-mock.R │ ├── helper.R │ ├── test-archs.R │ ├── test-bugs.R │ ├── test-callback.R │ ├── test-clean-subprocess.R │ ├── test-error.R │ ├── test-eval.R │ ├── test-function-env.R │ ├── test-hook.R │ ├── test-libpath.R │ ├── test-load-client.R │ ├── test-messages.R │ ├── test-options.R │ ├── test-package.R │ ├── test-presets.R │ ├── test-quit.R │ ├── test-r-bg.R │ ├── test-r-process.R │ ├── test-r-session-messages.R │ ├── test-r-session.R │ ├── test-rcmd-bg.R │ ├── test-rcmd-process.R │ ├── test-rcmd.R │ ├── test-rscript.R │ ├── test-spelling.R │ ├── test-timeout.R │ └── test-utils.R └── vignettes └── r-session.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^Makefile$ 4 | ^README.Rmd$ 5 | ^revdep$ 6 | ^README.markdown$ 7 | ^src/callr\.so$ 8 | ^src/.*\.o$ 9 | ^src/tools/px$ 10 | ^src/tools/px.exe$ 11 | ^docs$ 12 | ^_pkgdown\.yml$ 13 | ^README\.Rmd$ 14 | ^inst/header\.md$ 15 | ^README\.html$ 16 | ^doc$ 17 | ^Meta$ 18 | ^r-packages$ 19 | ^\.Rprofile$ 20 | ^\.github$ 21 | ^dev-lib$ 22 | ^codecov\.yml$ 23 | ^README_files$ 24 | ^[.]covrignore$ 25 | ^vignettes$ 26 | ^man/_cache$ 27 | ^README_cache$ 28 | ^LICENSE\.md$ 29 | ^[\.]?air\.toml$ 30 | ^\.vscode$ 31 | -------------------------------------------------------------------------------- /.Rprofile: -------------------------------------------------------------------------------- 1 | # This file is created automatically by `pak:::proj_create()` 2 | # Please do not edit this file 3 | .libPaths(unique(c(file.path('r-packages', R.version$platform, getRversion()[,1:2]),.libPaths()))) 4 | if (file.exists('~/.Rprofile')) source('~/.Rprofile') 5 | -------------------------------------------------------------------------------- /.covrignore: -------------------------------------------------------------------------------- 1 | R/errors.R 2 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | workflow_dispatch: 12 | 13 | name: R-CMD-check.yaml 14 | 15 | permissions: read-all 16 | 17 | jobs: 18 | R-CMD-check: 19 | runs-on: ${{ matrix.config.os }} 20 | 21 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | config: 27 | - {os: macos-latest, r: 'release'} 28 | 29 | - {os: windows-latest, r: 'release'} 30 | # use 4.0 or 4.1 to check with rtools40's older compiler 31 | - {os: windows-latest, r: 'oldrel-4'} 32 | 33 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 34 | - {os: ubuntu-latest, r: 'release'} 35 | - {os: ubuntu-latest, r: 'oldrel-1'} 36 | - {os: ubuntu-latest, r: 'oldrel-2'} 37 | - {os: ubuntu-latest, r: 'oldrel-3'} 38 | - {os: ubuntu-latest, r: 'oldrel-4'} 39 | 40 | env: 41 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 42 | R_KEEP_PKG_SOURCE: yes 43 | 44 | steps: 45 | - uses: actions/checkout@v4 46 | 47 | - uses: r-lib/actions/setup-pandoc@v2 48 | 49 | - uses: r-lib/actions/setup-r@v2 50 | with: 51 | r-version: ${{ matrix.config.r }} 52 | http-user-agent: ${{ matrix.config.http-user-agent }} 53 | use-public-rspm: true 54 | 55 | - uses: r-lib/actions/setup-r-dependencies@v2 56 | with: 57 | extra-packages: any::rcmdcheck 58 | needs: check 59 | 60 | - uses: r-hub/actions/debug-shell@v1 61 | 62 | - uses: r-lib/actions/check-r-package@v2 63 | with: 64 | upload-snapshots: true 65 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 66 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown.yaml 12 | 13 | permissions: read-all 14 | 15 | jobs: 16 | pkgdown: 17 | runs-on: ubuntu-latest 18 | # Only restrict concurrency for non-PR jobs 19 | concurrency: 20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | R_CLI_NUM_COLORS: 256 24 | permissions: 25 | contents: write 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - uses: r-lib/actions/setup-pandoc@v2 30 | 31 | - uses: r-lib/actions/setup-r@v2 32 | with: 33 | use-public-rspm: true 34 | 35 | - uses: r-lib/actions/setup-r-dependencies@v2 36 | with: 37 | extra-packages: any::pkgdown, local::. 38 | needs: website 39 | 40 | - name: Build site 41 | run: | 42 | rmarkdown::render("README.Rmd") 43 | pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 44 | shell: Rscript {0} 45 | env: 46 | IN_PKGDOWN: true 47 | 48 | - name: Deploy to GitHub pages 🚀 49 | if: github.event_name != 'pull_request' 50 | uses: JamesIves/github-pages-deploy-action@v4.5.0 51 | with: 52 | clean: false 53 | branch: gh-pages 54 | folder: docs 55 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | name: pr-commands.yaml 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | document: 13 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} 14 | name: document 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | permissions: 19 | contents: write 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: r-lib/actions/pr-fetch@v2 24 | with: 25 | repo-token: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::roxygen2 34 | needs: pr-document 35 | 36 | - name: Document 37 | run: roxygen2::roxygenise() 38 | shell: Rscript {0} 39 | 40 | - name: commit 41 | run: | 42 | git config --local user.name "$GITHUB_ACTOR" 43 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 44 | git add man/\* NAMESPACE 45 | git commit -m 'Document' 46 | 47 | - uses: r-lib/actions/pr-push@v2 48 | with: 49 | repo-token: ${{ secrets.GITHUB_TOKEN }} 50 | 51 | style: 52 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} 53 | name: style 54 | runs-on: ubuntu-latest 55 | env: 56 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 57 | permissions: 58 | contents: write 59 | steps: 60 | - uses: actions/checkout@v4 61 | 62 | - uses: r-lib/actions/pr-fetch@v2 63 | with: 64 | repo-token: ${{ secrets.GITHUB_TOKEN }} 65 | 66 | - uses: r-lib/actions/setup-r@v2 67 | 68 | - name: Install dependencies 69 | run: install.packages("styler") 70 | shell: Rscript {0} 71 | 72 | - name: Style 73 | run: styler::style_pkg() 74 | shell: Rscript {0} 75 | 76 | - name: commit 77 | run: | 78 | git config --local user.name "$GITHUB_ACTOR" 79 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 80 | git add \*.R 81 | git commit -m 'Style' 82 | 83 | - uses: r-lib/actions/pr-push@v2 84 | with: 85 | repo-token: ${{ secrets.GITHUB_TOKEN }} 86 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: test-coverage.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | test-coverage: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::covr, any::xml2 28 | needs: coverage 29 | 30 | - name: Test coverage 31 | run: | 32 | cov <- covr::package_coverage( 33 | quiet = FALSE, 34 | clean = FALSE, 35 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 36 | ) 37 | print(cov) 38 | covr::to_cobertura(cov) 39 | shell: Rscript {0} 40 | 41 | - uses: codecov/codecov-action@v5 42 | with: 43 | # Fail if error if not on PR, or if on PR and token is given 44 | fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} 45 | files: ./cobertura.xml 46 | plugins: noop 47 | disable_search: true 48 | token: ${{ secrets.CODECOV_TOKEN }} 49 | 50 | - name: Show testthat output 51 | if: always() 52 | run: | 53 | ## -------------------------------------------------------------------- 54 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 55 | shell: bash 56 | 57 | - name: Upload test results 58 | if: failure() 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: coverage-test-failures 62 | path: ${{ runner.temp }}/package 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | /revdep 5 | /src/*.o 6 | /src/unix/*.o 7 | /src/win/*.o 8 | /src/supervisor/supervisor 9 | /src/supervisor/supervisor.exe 10 | /src/supervisor/supervisor.dSYM 11 | /src/tools/px 12 | /src/tools/px.exe 13 | /src/tools/px.dSYM 14 | /src/callr.so 15 | /README.html 16 | Meta 17 | /r-packages 18 | /docs 19 | /dev-lib 20 | /README_files 21 | /vignettes/*.md 22 | /vignettes/*.html 23 | /vignettes/*_files 24 | /man/_cache 25 | /README_CACHE 26 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Posit.air-vscode" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[r]": { 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "Posit.air-vscode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: callr 2 | Title: Call R from R 3 | Version: 3.7.6.9000 4 | Authors@R: c( 5 | person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", role = c("aut", "cre", "cph"), 6 | comment = c(ORCID = "0000-0001-7098-9676")), 7 | person("Winston", "Chang", role = "aut"), 8 | person("Posit Software, PBC", role = c("cph", "fnd"), 9 | comment = c(ROR = "03wc8by49")), 10 | person("Ascent Digital Services", role = c("cph", "fnd")) 11 | ) 12 | Description: It is sometimes useful to perform a computation in a separate 13 | R process, without affecting the current R process at all. This 14 | packages does exactly that. 15 | License: MIT + file LICENSE 16 | URL: https://callr.r-lib.org, https://github.com/r-lib/callr 17 | BugReports: https://github.com/r-lib/callr/issues 18 | Depends: 19 | R (>= 3.4) 20 | Imports: 21 | processx (>= 3.6.1), 22 | R6, 23 | utils 24 | Suggests: 25 | asciicast (>= 2.3.1), 26 | cli (>= 1.1.0), 27 | ps, 28 | rprojroot, 29 | spelling, 30 | testthat (>= 3.2.0), 31 | withr (>= 2.3.0) 32 | Config/Needs/website: 33 | r-lib/asciicast, 34 | glue, 35 | htmlwidgets, 36 | igraph, 37 | tibble, 38 | tidyverse/tidytemplate 39 | Config/testthat/edition: 3 40 | Config/usethis/last-upkeep: 2025-04-28 41 | Encoding: UTF-8 42 | Language: en-US 43 | Roxygen: list(markdown = TRUE) 44 | RoxygenNote: 7.3.2 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2025 2 | COPYRIGHT HOLDER: callr authors, see COPYRIGHTS file 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2025 callr authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(format,callr_status_error) 4 | S3method(print,callr_status_error) 5 | export(add_hook) 6 | export(default_repos) 7 | export(poll) 8 | export(process) 9 | export(r) 10 | export(r_bg) 11 | export(r_copycat) 12 | export(r_process) 13 | export(r_process_options) 14 | export(r_safe) 15 | export(r_session) 16 | export(r_session_options) 17 | export(r_vanilla) 18 | export(rcmd) 19 | export(rcmd_bg) 20 | export(rcmd_copycat) 21 | export(rcmd_process) 22 | export(rcmd_process_options) 23 | export(rcmd_safe) 24 | export(rcmd_safe_env) 25 | export(rscript) 26 | export(rscript_process) 27 | export(rscript_process_options) 28 | export(run) 29 | export(supported_archs) 30 | importFrom(processx,poll) 31 | importFrom(processx,process) 32 | importFrom(processx,run) 33 | -------------------------------------------------------------------------------- /R/callr-package.R: -------------------------------------------------------------------------------- 1 | #' Call R from R 2 | #' 3 | #' @description 4 | #' It is sometimes useful to perform a computation in a separate 5 | #' R process, without affecting the current R process at all. This 6 | #' packages does exactly that. 7 | #' 8 | #' # callr 9 | #' 10 | #' ```{r man-readme, child = "README.Rmd"} 11 | #' ``` 12 | #' 13 | #' @keywords internal 14 | "_PACKAGE" 15 | 16 | ## usethis namespace: start 17 | ## usethis namespace: end 18 | NULL 19 | -------------------------------------------------------------------------------- /R/check.R: -------------------------------------------------------------------------------- 1 | #' Convert and check function arguments 2 | #' 3 | #' This function is used for all variants of `r` and `rcmd`. An argument 4 | #' name is only used to refer to one kind of object, to make this possible. 5 | #' 6 | #' The benefit of having a single `options` object is to avoid passing 7 | #' around a lot of arguments all the time. 8 | #' 9 | #' The benefit of making this object internal (i.e. that the `r`, etc. 10 | #' functions have multiple arguments instead of a single `options` list), 11 | #' is that documentation and usage is more user friendly (e.g. command- 12 | #' completion works in the editor. 13 | #' 14 | #' @param options List of options. 15 | #' 16 | #' @keywords internal 17 | 18 | convert_and_check_my_args <- function(options) { 19 | has <- function(x) x %in% names(options) 20 | no <- function(x) !has(x) 21 | 22 | ## Conversions 23 | options <- within(options, { 24 | if (has("libpath")) libpath <- as.character(libpath) 25 | if (has("repos")) repos <- repos 26 | if (has("stdout") && !is.null(stdout)) { 27 | stdout <- as.character(stdout) 28 | } 29 | if (has("stderr") && !is.null(stderr)) { 30 | stderr <- as.character(stderr) 31 | } 32 | if (has("error")) error <- error[1] 33 | if (has("cmdargs")) cmdargs <- as.character(cmdargs) 34 | if (has("timeout") && !inherits(timeout, "difftime")) { 35 | timeout <- as.difftime( 36 | as.double(timeout), 37 | units = "secs" 38 | ) 39 | } 40 | if (no("wd")) wd <- "." 41 | if (no("echo")) echo <- FALSE 42 | if (no("fail_on_status")) fail_on_status <- FALSE 43 | if (no("tmp_files")) tmp_files <- character() 44 | if (no("package")) package <- FALSE 45 | if (no("arch")) arch <- "same" 46 | }) 47 | 48 | ## Checks 49 | with( 50 | options, 51 | stopifnot( 52 | no("func") || is.function(func), 53 | no("func") || is.list(args), 54 | is.character(libpath), 55 | no("stdout") || is.null(stdout) || is_string(stdout), 56 | no("stderr") || is.null(stderr) || is_string(stderr), 57 | no("error") || is_string(error), 58 | is.character(cmdargs), 59 | no("echo") || is_flag(echo), 60 | no("show") || is_flag(show), 61 | no("callback") || is.null(callback) || is.function(callback), 62 | no("block_callback") || 63 | is.null(block_callback) || 64 | is.function(block_callback), 65 | no("spinner") || is_flag(spinner), 66 | is_flag(system_profile), 67 | is_flag(user_profile) || identical(user_profile, "project"), 68 | is.character(env), 69 | no("timeout") || (length(timeout) == 1 && !is.na(timeout)), 70 | no("wd") || is_string(wd), 71 | no("fail_on_status") || is_flag(fail_on_status), 72 | is_string(package) || is_flag(package), 73 | is_string(arch) 74 | ) 75 | ) 76 | 77 | options 78 | } 79 | -------------------------------------------------------------------------------- /R/error.R: -------------------------------------------------------------------------------- 1 | #' Create an error object 2 | #' 3 | #' There are two kinds of errors, both have class `callr_error`: 4 | #' 1. the first one is thrown after a timeout: `callr_timeout_error`. 5 | #' 2. the second one is thrown after an R error (in the other session): 6 | #' `callr_status_error`. 7 | #' 8 | #' @param out The object returned by [run()]. 9 | #' @param msg An extra message to add to the error message. 10 | #' @keywords internal 11 | 12 | new_callr_crash_error <- function(out, msg = NULL) { 13 | error_msg <- paste0( 14 | if (out$timeout) "callr timed out" else "callr subprocess failed", 15 | if (!is.null(msg)) paste0(": ", msg) else if (!out$timeout) ":" 16 | ) 17 | 18 | cond <- new_error(paste(error_msg)) 19 | 20 | class(cond) <- c( 21 | if (out$timeout) "callr_timeout_error" else "callr_status_error", 22 | "callr_error", 23 | class(cond) 24 | ) 25 | 26 | cond$status <- out$status 27 | cond$stdout <- out$stdout 28 | cond$stderr <- out$stderr 29 | 30 | cond 31 | } 32 | 33 | callr_remote_error <- function(remerr, out) { 34 | if (inherits(remerr[[3]], "interrupt")) { 35 | err <- new_error("interrupt sugnal in callr subprocess", call. = FALSE) 36 | class(err) <- c("callr_timeout_error", "callr_error", class(err)) 37 | err$message <- "callr subprocess interrupted" 38 | } else { 39 | err <- new_error("in callr subprocess.", call. = FALSE) 40 | class(err) <- c("callr_status_error", "callr_error", class(err)) 41 | } 42 | 43 | err$status <- out$status 44 | err$stdout <- out$stdout 45 | err$stderr <- out$stderr 46 | 47 | err$parent_trace <- remerr[[2]]$trace 48 | err 49 | } 50 | 51 | callr_remote_error_with_stack <- function(remerr, out) { 52 | err <- new_error("in callr subprocess.", call. = FALSE) 53 | class(err) <- c("callr_status_error", "callr_error", class(err)) 54 | 55 | err$status <- out$status 56 | err$stdout <- out$stdout 57 | err$stderr <- out$stderr 58 | 59 | err$stack <- clean_stack(remerr[[3]]) 60 | err 61 | } 62 | 63 | #' @export 64 | 65 | format.callr_status_error <- function( 66 | x, 67 | trace = FALSE, 68 | class = FALSE, 69 | advice = !trace, 70 | ... 71 | ) { 72 | class(x) <- setdiff(class(x), "callr_status_error") 73 | 74 | lines <- NextMethod( 75 | object = x, 76 | trace = FALSE, 77 | class = class, 78 | advice = FALSE, 79 | ... 80 | ) 81 | 82 | info <- if (err$.internal$has_cli()) { 83 | cli::col_cyan(cli::symbol$info) 84 | } else { 85 | "i" # nocov 86 | } 87 | 88 | if (!is.null(x$stack)) { 89 | lines <- c( 90 | lines, 91 | paste0( 92 | info, 93 | " With remote `$stack`, use `utils::debugger()` to debug it." 94 | ) 95 | ) 96 | } 97 | 98 | notempty <- function(x) !is.null(x) && sum(nchar(x)) > 0 99 | hasout <- notempty(x$stdout) 100 | haserr <- notempty(x$stderr) 101 | if (hasout || haserr) { 102 | if (err$.internal$is_interactive()) { 103 | lines <- c( 104 | lines, 105 | if (hasout && haserr) { 106 | paste0( 107 | info, 108 | " See `$stdout` and `$stderr` for standard output and error." 109 | ) 110 | } else if (hasout) { 111 | paste0(info, " See `$stdout` for standard output.") 112 | } else { 113 | paste0(info, " See `$stderr` for standard error.") 114 | } 115 | ) 116 | } else { 117 | lines <- c( 118 | lines, 119 | if (hasout) { 120 | c( 121 | "---", 122 | "Standard output:", 123 | trimws(x$stdout) 124 | ) 125 | }, 126 | if (haserr) { 127 | c("---", "Standard error:", trimws(x$stderr)) 128 | } 129 | ) 130 | } 131 | } 132 | 133 | lines <- c( 134 | lines, 135 | if (advice) err$format$advice(), 136 | if (trace && !is.null(x$trace)) { 137 | c( 138 | "---", 139 | "Backtrace:", 140 | err$format$trace(x$trace) 141 | ) 142 | } 143 | ) 144 | 145 | cond <- x 146 | while (trace && !is.null(cond$parent_trace)) { 147 | lines <- c( 148 | lines, 149 | c("---", "Subprocess backtrace:", format(cond$parent_trace)) 150 | ) 151 | cond <- cond$parent 152 | } 153 | 154 | lines 155 | } 156 | 157 | #' @export 158 | 159 | print.callr_status_error <- function( 160 | x, 161 | trace = TRUE, 162 | class = TRUE, 163 | advice = !trace, 164 | ... 165 | ) { 166 | writeLines(format(x, trace = trace, class = class, advice = advice, ...)) 167 | } 168 | -------------------------------------------------------------------------------- /R/eval-bg.R: -------------------------------------------------------------------------------- 1 | #' Evaluate an expression in another R session, in the background 2 | #' 3 | #' Starts evaluating an R function call in a background R process, and 4 | #' returns immediately. 5 | #' Use `p$get_result()` to collect the result or to throw an error 6 | #' if the background computation failed. 7 | #' 8 | #' @inheritSection r Security considerations 9 | #' @inheritParams r 10 | #' @param supervise Whether to register the process with a supervisor. If \code{TRUE}, 11 | #' the supervisor will ensure that the process is killed when the R process 12 | #' exits. 13 | #' @param ... Extra arguments are passed to the [processx::process] 14 | #' constructor. 15 | #' @return An `r_process` object, which inherits from [process], 16 | #' so all `process` methods can be called on it, and in addition it also 17 | #' has a `get_result()` method to collect the result. 18 | #' 19 | #' @export 20 | #' @examplesIf FALSE 21 | #' rx <- r_bg(function() 1 + 2) 22 | #' 23 | #' # wait until it is done 24 | #' rx$wait() 25 | #' rx$is_alive() 26 | #' rx$get_result() 27 | 28 | r_bg <- function( 29 | func, 30 | args = list(), 31 | libpath = .libPaths(), 32 | repos = default_repos(), 33 | stdout = "|", 34 | stderr = "|", 35 | poll_connection = TRUE, 36 | error = getOption("callr.error", "error"), 37 | cmdargs = c("--slave", "--no-save", "--no-restore"), 38 | system_profile = FALSE, 39 | user_profile = "project", 40 | env = rcmd_safe_env(), 41 | supervise = FALSE, 42 | package = FALSE, 43 | arch = "same", 44 | ... 45 | ) { 46 | options <- as.list(environment()) 47 | options$extra <- list(...) 48 | options$load_hook <- default_load_hook() 49 | r_process$new(options = options) 50 | } 51 | -------------------------------------------------------------------------------- /R/hook.R: -------------------------------------------------------------------------------- 1 | common_hook <- function() { 2 | substitute( 3 | { 4 | # This should not happen in a new R session, but just to be safe 5 | while ("tools:callr" %in% search()) detach("tools:callr") 6 | env <- readRDS(`__envfile__`) 7 | do.call("attach", list(env, pos = length(search()), name = "tools:callr")) 8 | data <- env$`__callr_data__` 9 | data$pxlib <- data$load_client_lib( 10 | data$sofile[[paste0("arch-", .Platform$r_arch)]], 11 | data$pxdir 12 | ) 13 | options(error = function() invokeRestart("abort")) 14 | rm(list = c("data", "env")) 15 | 16 | lapply( 17 | c( 18 | "R_ENVIRON", 19 | "R_ENVIRON_USER", 20 | "R_PROFILE", 21 | "R_PROFILE_USER", 22 | "R_LIBS", 23 | "R_LIBS_USER", 24 | "R_LIBS_SITE" 25 | ), 26 | function(var) { 27 | bakvar <- paste0("CALLR_", var, "_BAK") 28 | val <- Sys.getenv(bakvar, NA_character_) 29 | if (!is.na(val)) { 30 | do.call("Sys.setenv", structure(list(val), names = var)) 31 | } else { 32 | Sys.unsetenv(var) 33 | } 34 | Sys.unsetenv(bakvar) 35 | } 36 | ) 37 | 38 | Sys.unsetenv("CALLR_CHILD_R_LIBS") 39 | Sys.unsetenv("CALLR_CHILD_R_LIBS_SITE") 40 | Sys.unsetenv("CALLR_CHILD_R_LIBS_USER") 41 | }, 42 | list("__envfile__" = env_file) 43 | ) 44 | } 45 | 46 | default_load_hook <- function(user_hook = NULL) { 47 | prepare_client_files() 48 | hook <- common_hook() 49 | 50 | if (!is.null(user_hook)) { 51 | hook <- substitute( 52 | { 53 | d 54 | u 55 | }, 56 | list(d = hook, u = user_hook) 57 | ) 58 | } 59 | paste0(deparse(hook), "\n") 60 | } 61 | 62 | session_load_hook <- function(user_hook = NULL) { 63 | chook <- common_hook() 64 | ehook <- substitute({ 65 | data <- as.environment("tools:callr")$`__callr_data__` 66 | data$pxlib$disable_fd_inheritance() 67 | rm(data) 68 | }) 69 | 70 | hook <- substitute( 71 | { 72 | c 73 | e 74 | }, 75 | list(c = chook, e = ehook) 76 | ) 77 | 78 | if (!is.null(user_hook)) { 79 | hook <- substitute( 80 | { 81 | d 82 | u 83 | }, 84 | list(d = hook, u = user_hook) 85 | ) 86 | } 87 | 88 | hook <- substitute( 89 | { 90 | err_ <- TRUE 91 | callr_startup_hook <- function() { 92 | on.exit(if (err_) quit("no", 1, TRUE)) 93 | { 94 | `_hook_` 95 | } 96 | err_ <<- FALSE 97 | } 98 | callr_startup_hook() 99 | rm(err_, callr_startup_hook) 100 | }, 101 | list("_hook_" = hook) 102 | ) 103 | 104 | paste0(deparse(hook), "\n") 105 | } 106 | 107 | user_hooks_env <- new.env(parent = emptyenv()) 108 | 109 | #' Add a user hook to be executed before launching an R subprocess 110 | #' 111 | #' This function allows users of `callr` to specify functions that get invoked 112 | #' whenever an R session is launched. The function can modify the environment 113 | #' variables and command line arguments. 114 | #' 115 | #' The prototype of the hook function is `function (options)`, and it is 116 | #' expected to return the modified `options`. 117 | #' @param ... Named argument specifying a hook function to add, or `NULL` to 118 | #' delete the named hook. 119 | #' @return `add_hook` is called for its side-effects. 120 | #' @export 121 | 122 | add_hook <- function(...) { 123 | args <- list(...) 124 | if (length(args) != 1L) { 125 | stop("More than one argument passed to `add_hook`") 126 | } 127 | 128 | name <- names(args) 129 | if (is.null(name)) { 130 | stop("Argument passed to `add_hook` must be named") 131 | } 132 | 133 | hook <- args[[1]] 134 | if (is.null(hook)) { 135 | rm(list = name, envir = user_hooks_env) 136 | } else { 137 | assign(name, match.fun(hook), envir = user_hooks_env) 138 | } 139 | 140 | invisible() 141 | } 142 | 143 | call_user_hooks <- function(options) { 144 | for (name in names(user_hooks_env)) { 145 | hook <- user_hooks_env[[name]] 146 | options <- tryCatch(hook(options), error = function(e) options) 147 | } 148 | options 149 | } 150 | -------------------------------------------------------------------------------- /R/load-client.R: -------------------------------------------------------------------------------- 1 | load_client_lib <- function(sofile = NULL, pxdir = NULL) { 2 | ext <- .Platform$dynlib.ext 3 | sofile_in_processx <- function() { 4 | arch <- .Platform$r_arch 5 | if (!is.null(pxdir)) { 6 | sofile <- file.path(pxdir, "libs", arch, paste0("client", ext)) 7 | if (file.exists(sofile)) return(sofile) 8 | } 9 | 10 | sofile <- system.file( 11 | "libs", 12 | arch, 13 | paste0("client", ext), 14 | package = "processx" 15 | ) 16 | if (sofile != "" && file.exists(sofile)) return(sofile) 17 | 18 | # Try this as well, this is for devtools/pkgload 19 | sofile <- system.file( 20 | "src", 21 | paste0("client", ext), 22 | package = "processx" 23 | ) 24 | if (sofile != "" && file.exists(sofile)) return(sofile) # nocov 25 | 26 | # stop() here and not throw(), because this function should be standalone 27 | stop("Cannot find client file") 28 | } 29 | 30 | # We set this to `FALSE` if we load the library from the processx 31 | # install path since that library might be shared (e.g. in tests) 32 | need_cleanup <- TRUE 33 | 34 | if (is.null(sofile) || Sys.getenv("CALLR_NO_TEMP_DLLS", "false") == "true") { 35 | sofile <- sofile_in_processx() 36 | lib <- dyn.load(sofile) 37 | need_cleanup <- FALSE 38 | } else { 39 | # This is the usual case, first we try loading it from the 40 | # temporary directory. If that fails (e.g. noexec), then 41 | # from processx. We saved the location of processx when we 42 | # loaded callr, just in case the used changes the lib path. 43 | lib <- tryCatch(dyn.load(sofile), error = function(err) err) 44 | if (inherits(lib, "error")) { 45 | sofile <- sofile_in_processx() 46 | tryCatch( 47 | expr = { 48 | lib <- dyn.load(sofile) 49 | need_cleanup <- FALSE 50 | }, 51 | error = function(err2) { 52 | err2$message <- err2$message <- paste0(" after ", lib$message) 53 | stop(err2) 54 | } 55 | ) 56 | } 57 | } 58 | 59 | # cleanup if setup fails 60 | if (need_cleanup) { 61 | on.exit(try(dyn.unload(sofile), silent = TRUE), add = TRUE) 62 | } 63 | 64 | sym_encode <- getNativeSymbolInfo("processx_base64_encode", lib) 65 | sym_decode <- getNativeSymbolInfo("processx_base64_decode", lib) 66 | sym_disinh <- getNativeSymbolInfo("processx_disable_inheritance", lib) 67 | sym_write <- getNativeSymbolInfo("processx_write", lib) 68 | sym_setout <- getNativeSymbolInfo("processx_set_stdout", lib) 69 | sym_seterr <- getNativeSymbolInfo("processx_set_stderr", lib) 70 | sym_setoutf <- getNativeSymbolInfo("processx_set_stdout_to_file", lib) 71 | sym_seterrf <- getNativeSymbolInfo("processx_set_stderr_to_file", lib) 72 | 73 | env <- new.env(parent = emptyenv()) 74 | env$.path <- sofile 75 | env$.lib <- lib 76 | 77 | mycall <- .Call 78 | 79 | env$base64_encode <- function(x) rawToChar(mycall(sym_encode, x)) 80 | env$base64_decode <- function(x) { 81 | if (is.character(x)) { 82 | x <- charToRaw(paste(gsub("\\s+", "", x), collapse = "")) 83 | } 84 | mycall(sym_decode, x) 85 | } 86 | 87 | env$disable_fd_inheritance <- function() mycall(sym_disinh) 88 | 89 | env$write_fd <- function(fd, data) { 90 | if (is.character(data)) data <- charToRaw(paste0(data, collapse = "")) 91 | len <- length(data) 92 | repeat { 93 | written <- mycall(sym_write, fd, data) 94 | len <- len - written 95 | if (len == 0) break 96 | if (written) data <- data[-(1:written)] 97 | Sys.sleep(.1) 98 | } 99 | } 100 | 101 | env$set_stdout <- function(fd, drop = TRUE) { 102 | mycall(sym_setout, as.integer(fd), as.logical(drop)) 103 | } 104 | 105 | env$set_stderr <- function(fd, drop = TRUE) { 106 | mycall(sym_seterr, as.integer(fd), as.logical(drop)) 107 | } 108 | 109 | env$set_stdout_file <- function(path) { 110 | mycall(sym_setoutf, as.character(path)[1]) 111 | } 112 | 113 | env$set_stderr_file <- function(path) { 114 | mycall(sym_seterrf, as.character(path)[1]) 115 | } 116 | 117 | env$.finalize <- function() { 118 | if (need_cleanup) { 119 | dyn.unload(env$.path) 120 | } 121 | rm(list = ls(env, all.names = TRUE), envir = env) 122 | } 123 | 124 | penv <- environment() 125 | parent.env(penv) <- baseenv() 126 | 127 | reg.finalizer( 128 | env, 129 | function(e) if (".finalize" %in% names(e)) e$.finalize(), 130 | onexit = TRUE 131 | ) 132 | 133 | ## Clear the cleanup method 134 | on.exit(NULL) 135 | env 136 | } 137 | -------------------------------------------------------------------------------- /R/options.R: -------------------------------------------------------------------------------- 1 | #' Create options for an [r_process] object 2 | #' 3 | #' @param ... Options to override, named arguments. 4 | #' @return A list of options. 5 | #' 6 | #' `r_process_options()` creates a set of options to initialize a new 7 | #' object from the `r_process` class. Its arguments must be named, the 8 | #' names are used as option names. The options correspond to (some of) 9 | #' the arguments of the [r()] function. At least the `func` option must be 10 | #' specified, this is the R function to run in the background. 11 | #' 12 | #' @export 13 | #' @examples 14 | #' ## List all options and their default values: 15 | #' r_process_options() 16 | 17 | r_process_options <- function(...) { 18 | update_options(r_process_options_default(), ...) 19 | } 20 | 21 | #' Create options for an [rcmd_process] object 22 | #' 23 | #' @param ... Options to override, named arguments. 24 | #' @return A list of options. 25 | #' 26 | #' `rcmd_process_options()` creates a set of options to initialize a new 27 | #' object from the `rcmd_process` class. Its arguments must be named, the 28 | #' names are used as option names. The options correspond to (some of) 29 | #' the arguments of the [rcmd()] function. At least the `cmd` option must 30 | #' be specified, to select the `R CMD` subcommand to run. Typically 31 | #' `cmdargs` is specified as well, to supply more arguments to `R CMD`. 32 | #' 33 | #' @export 34 | #' @examples 35 | #' ## List all options and their default values: 36 | #' rcmd_process_options() 37 | 38 | rcmd_process_options <- function(...) { 39 | update_options(rcmd_process_options_default(), ...) 40 | } 41 | 42 | #' Create options for an [rscript_process] object 43 | #' 44 | #' @param ... Options to override, named arguments. 45 | #' @return A list of options. 46 | #' 47 | #' `rscript_process_options()` creates a set of options to initialize a new 48 | #' object from the `rscript_process` class. Its arguments must be named, 49 | #' the names are used as option names. The options correspond to (some of) 50 | #' the arguments of the [rscript()] function. At least the `script` option 51 | #' must be specified, the script file to run. 52 | #' 53 | #' @export 54 | #' @examples 55 | #' ## List all options and their default values: 56 | #' rscript_process_options() 57 | 58 | rscript_process_options <- function(...) { 59 | update_options(rscript_process_options_default(), ...) 60 | } 61 | 62 | r_process_options_default <- function() { 63 | list( 64 | func = NULL, 65 | args = list(), 66 | libpath = .libPaths(), 67 | repos = default_repos(), 68 | stdout = "|", 69 | stderr = "|", 70 | poll_connection = TRUE, 71 | error = getOption("callr.error", "error"), 72 | cmdargs = c("--slave", "--no-save", "--no-restore"), 73 | system_profile = FALSE, 74 | user_profile = "project", 75 | env = character(), 76 | supervise = FALSE, 77 | load_hook = default_load_hook(), 78 | extra = list(), 79 | package = FALSE, 80 | arch = "same" 81 | ) 82 | } 83 | 84 | rcmd_process_options_default <- function() { 85 | list( 86 | cmd = NULL, 87 | cmdargs = character(), 88 | libpath = .libPaths(), 89 | stdout = "|", 90 | stderr = "|", 91 | poll_connection = TRUE, 92 | repos = default_repos(), 93 | system_profile = FALSE, 94 | user_profile = "project", 95 | env = rcmd_safe_env(), 96 | wd = ".", 97 | supervise = FALSE, 98 | extra = list(), 99 | arch = "same" 100 | ) 101 | } 102 | 103 | rscript_process_options_default <- function() { 104 | list( 105 | script = NULL, 106 | cmdargs = character(), 107 | libpath = .libPaths(), 108 | stdout = "|", 109 | stderr = "|", 110 | poll_connection = TRUE, 111 | repos = default_repos(), 112 | system_profile = FALSE, 113 | user_profile = "project", 114 | env = rcmd_safe_env(), 115 | wd = ".", 116 | color = FALSE, 117 | extra = list(), 118 | arch = "same" 119 | ) 120 | } 121 | 122 | update_options <- function(old_opts, ...) { 123 | new_opts <- list(...) 124 | stopifnot(is.named(new_opts)) 125 | check_for_option_names(old_opts, new_opts) 126 | utils::modifyList(old_opts, new_opts) 127 | } 128 | 129 | check_for_option_names <- function(old, new) { 130 | if (length(miss <- setdiff(names(new), names(old)))) { 131 | throw(new_error( 132 | "Unknown option", 133 | if (length(miss) > 1) "s", 134 | ":", 135 | enumerate(sQuote(miss)) 136 | )) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /R/package.R: -------------------------------------------------------------------------------- 1 | ## R CMD check workaround 2 | dummy_r6 <- function() R6::R6Class 3 | 4 | clients <- NULL 5 | sofiles <- NULL 6 | env_file <- NULL 7 | 8 | ## We save this as an RDS, so it can be loaded quickly 9 | .onLoad <- function(libname, pkgname) { 10 | err$onload_hook() 11 | env_file <<- tempfile("callr-env-") 12 | clients <<- asNamespace("processx")$client 13 | sofiles <<- get_client_files() 14 | client_env$`__callr_data__`$sofile <- sofiles 15 | client_env$`__callr_data__`$pxdir <- system.file(package = "processx") 16 | } 17 | 18 | prepare_client_files <- function() { 19 | for (aa in names(client_env$`__callr_data__`$sofile)) { 20 | fn <- client_env$`__callr_data__`$sofile[[aa]] 21 | if (!file.exists(fn)) { 22 | dir.create(dirname(fn), recursive = TRUE) 23 | writeBin(clients[[aa]]$bytes, fn) 24 | } 25 | } 26 | 27 | if (!file.exists(env_file)) { 28 | saveRDS(client_env, file = env_file, version = 2, compress = FALSE) 29 | } 30 | invisible() 31 | } 32 | 33 | get_client_files <- function() { 34 | archs <- ls(clients) 35 | vapply( 36 | archs, 37 | function(aa) { 38 | hash <- substr(clients[[aa]]$md5, 1, 7) 39 | 40 | # Filename must be `client.ext` so that `dyn.load()` can find 41 | # the init function 42 | file.path( 43 | tempdir(), 44 | "callr", 45 | sub("arch-", "", aa), # Might be empty 46 | hash, 47 | paste0("client", .Platform$dynlib.ext) 48 | ) 49 | }, 50 | character(1) 51 | ) 52 | } 53 | 54 | .onUnload <- function(libpath) { 55 | unlink( 56 | normalizePath(c(sofiles, env_file), mustWork = FALSE), 57 | recursive = TRUE, 58 | force = TRUE 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /R/presets.R: -------------------------------------------------------------------------------- 1 | #' Run an R child process, with no configuration 2 | #' 3 | #' It tries to mimic a fresh R installation. In particular: 4 | #' * No library path setting. 5 | #' * No CRAN(-like) repository is set. 6 | #' * The system and user profiles are not run. 7 | #' 8 | #' @param ... Additional arguments are passed to [r()]. 9 | #' @inheritParams r 10 | #' @inheritSection r Security considerations 11 | #' 12 | #' @family callr functions 13 | #' @export 14 | #' @examplesIf FALSE 15 | #' # Compare to r() 16 | #' r(function() .libPaths()) 17 | #' r_vanilla(function() .libPaths()) 18 | #' 19 | #' r(function() getOption("repos")) 20 | #' r_vanilla(function() getOption("repos")) 21 | 22 | r_vanilla <- function( 23 | func, 24 | args = list(), 25 | libpath = character(), 26 | repos = c(CRAN = "@CRAN@"), 27 | cmdargs = "--slave", 28 | system_profile = FALSE, 29 | user_profile = FALSE, 30 | env = character(), 31 | ... 32 | ) { 33 | r( 34 | func, 35 | args = args, 36 | libpath = libpath, 37 | repos = repos, 38 | cmdargs = cmdargs, 39 | system_profile = system_profile, 40 | user_profile = user_profile, 41 | env = env, 42 | ... 43 | ) 44 | } 45 | 46 | #' @rdname r 47 | #' @export 48 | 49 | r_safe <- r 50 | 51 | #' Run an R process that mimics the current R process 52 | #' 53 | #' Differences to [r()]: 54 | #' * No extra repositories are set up. 55 | #' * The `--no-save`, `--no-restore` 56 | #' command line arguments are not used. (But `--slave` still is.) 57 | #' * The system profile and the user profile are loaded. 58 | #' * No extra environment variables are set up. 59 | #' 60 | #' @inheritSection r Security considerations 61 | #' @inheritParams r 62 | #' @param ... Additional arguments are passed to [r()]. 63 | #' 64 | #' @family callr functions 65 | #' @export 66 | 67 | r_copycat <- function( 68 | func, 69 | args = list(), 70 | libpath = .libPaths(), 71 | repos = getOption("repos"), 72 | cmdargs = "--slave", 73 | system_profile = TRUE, 74 | user_profile = TRUE, 75 | env = character(), 76 | ... 77 | ) { 78 | r( 79 | func, 80 | args = args, 81 | libpath = libpath, 82 | repos = repos, 83 | cmdargs = cmdargs, 84 | system_profile = system_profile, 85 | user_profile = user_profile, 86 | env = env, 87 | ... 88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /R/processx-forward.R: -------------------------------------------------------------------------------- 1 | #' @importFrom processx run 2 | #' @export 3 | processx::run 4 | 5 | #' @importFrom processx process 6 | #' @export 7 | processx::process 8 | 9 | #' @importFrom processx poll 10 | #' @export 11 | processx::poll 12 | -------------------------------------------------------------------------------- /R/r-process.R: -------------------------------------------------------------------------------- 1 | #' External R Process 2 | #' 3 | #' @description 4 | #' An R process that runs in the background. This is an R6 class that 5 | #' extends the [processx::process] class. The process starts in the 6 | #' background, evaluates an R function call, and then quits. 7 | #' 8 | #' @examplesIf FALSE 9 | #' ## List all options and their default values: 10 | #' r_process_options() 11 | #' 12 | #' ## Start an R process in the background, wait for it, get result 13 | #' opts <- r_process_options(func = function() 1 + 1) 14 | #' rp <- r_process$new(opts) 15 | #' rp$wait() 16 | #' rp$get_result() 17 | #' @export 18 | 19 | r_process <- R6::R6Class( 20 | "r_process", 21 | inherit = processx::process, 22 | public = list( 23 | #' @description 24 | #' Start a new R process in the background. 25 | #' @param options A list of options created via [r_process_options()]. 26 | #' @return A new `r_process` object. 27 | initialize = function(options) rp_init(self, private, super, options), 28 | 29 | #' @description 30 | #' Return the result, an R object, from a finished 31 | #' background R process. If the process has not finished yet, it throws 32 | #' an error. (You can use `wait()` method (see [processx::process]) to 33 | #' wait for the process to finish, optionally with a timeout.) You can 34 | #' also use [processx::poll()] to wait for the end of the process, 35 | #' together with other processes or events. 36 | #' 37 | #' @return The return value of the R expression evaluated in the R 38 | #' process. 39 | get_result = function() rp_get_result(self, private) 40 | ), 41 | private = list( 42 | options = NULL, 43 | finalize = function() { 44 | unlink(private$options$tmp_files, recursive = TRUE) 45 | if ("finalize" %in% ls(super)) super$finalize() 46 | } 47 | ) 48 | ) 49 | 50 | rp_init <- function(self, private, super, options) { 51 | ## This contains the context that we set up in steps 52 | options <- convert_and_check_my_args(options) 53 | 54 | options <- setup_script_files(options) 55 | options <- setup_context(options) 56 | options <- setup_r_binary_and_args(options) 57 | 58 | private$options <- options 59 | 60 | with_envvar( 61 | options$env, 62 | do.call( 63 | super$initialize, 64 | c( 65 | list( 66 | options$bin, 67 | options$real_cmdargs, 68 | stdout = options$stdout, 69 | stderr = options$stderr, 70 | poll_connection = options$poll_connection, 71 | supervise = options$supervise 72 | ), 73 | options$extra 74 | ) 75 | ) 76 | ) 77 | 78 | invisible(self) 79 | } 80 | 81 | rp_get_result <- function(self, private) { 82 | if (self$is_alive()) throw(new_error("Still alive")) 83 | 84 | ## This is artificial... 85 | out <- list( 86 | status = self$get_exit_status(), 87 | stdout = "", 88 | stderr = "", 89 | timeout = FALSE 90 | ) 91 | 92 | get_result(out, private$options) 93 | } 94 | -------------------------------------------------------------------------------- /R/rcmd-bg.R: -------------------------------------------------------------------------------- 1 | #' Run an `R CMD` command in the background 2 | #' 3 | #' The child process is started in the background, and the function 4 | #' return immediately. 5 | #' 6 | #' @inheritSection r Security considerations 7 | #' @inheritParams rcmd 8 | #' @param supervise Whether to register the process with a supervisor. If \code{TRUE}, 9 | #' the supervisor will ensure that the process is killed when the R process 10 | #' exits. 11 | #' @param ... Extra arguments are passed to the [processx::process] 12 | #' constructor. 13 | #' @return It returns a [process] object. 14 | #' 15 | #' @family R CMD commands 16 | #' @export 17 | 18 | rcmd_bg <- function( 19 | cmd, 20 | cmdargs = character(), 21 | libpath = .libPaths(), 22 | stdout = "|", 23 | stderr = "|", 24 | poll_connection = TRUE, 25 | repos = default_repos(), 26 | system_profile = FALSE, 27 | user_profile = "project", 28 | env = rcmd_safe_env(), 29 | wd = ".", 30 | supervise = FALSE, 31 | ... 32 | ) { 33 | options <- as.list(environment()) 34 | options$extra <- list(...) 35 | rcmd_process$new(options = options) 36 | } 37 | -------------------------------------------------------------------------------- /R/rcmd-process.R: -------------------------------------------------------------------------------- 1 | #' External `R CMD` Process 2 | #' 3 | #' @description 4 | #' An `R CMD *` command that runs in the background. This is an R6 class 5 | #' that extends the [processx::process] class. 6 | #' 7 | #' @examplesIf FALSE 8 | #' options <- rcmd_process_options(cmd = "config", cmdargs = "CC") 9 | #' rp <- rcmd_process$new(options) 10 | #' rp$wait() 11 | #' rp$read_output_lines() 12 | #' @export 13 | 14 | rcmd_process <- R6::R6Class( 15 | "rcmd_process", 16 | inherit = processx::process, 17 | public = list( 18 | #' @description 19 | #' Start an `R CMD` process. 20 | #' @param options A list of options created via 21 | #' [rcmd_process_options()]. 22 | #' @return A new `rcmd_process` object. 23 | initialize = function(options) rcmdp_init(self, private, super, options) 24 | ), 25 | private = list( 26 | options = NULL, 27 | finalize = function() { 28 | unlink(private$options$tmp_files, recursive = TRUE) 29 | if ("finalize" %in% ls(super)) super$finalize() 30 | } 31 | ) 32 | ) 33 | 34 | rcmdp_init <- function(self, private, super, options) { 35 | ## This contains the context that we set up in steps 36 | options <- convert_and_check_my_args(options) 37 | 38 | options <- setup_context(options) 39 | options <- setup_rcmd_binary_and_args(options) 40 | 41 | private$options <- options 42 | 43 | oldwd <- getwd() 44 | setwd(options$wd) 45 | on.exit(setwd(oldwd), add = TRUE) 46 | 47 | with_envvar( 48 | options$env, 49 | do.call( 50 | super$initialize, 51 | c( 52 | list( 53 | options$bin, 54 | options$real_cmdargs, 55 | stdout = options$stdout, 56 | stderr = options$stderr, 57 | poll_connection = options$poll_connection 58 | ), 59 | options$extra 60 | ) 61 | ) 62 | ) 63 | 64 | invisible(self) 65 | } 66 | -------------------------------------------------------------------------------- /R/rcmd.R: -------------------------------------------------------------------------------- 1 | #' Run an `R CMD` command 2 | #' 3 | #' Run an `R CMD` command form within R. This will usually start 4 | #' another R process, from a shell script. 5 | #' 6 | #' Starting from `callr` 2.0.0, `rcmd()` has safer defaults, the same as 7 | #' the `rcmd_safe()` default values. Use [rcmd_copycat()] for the old 8 | #' defaults. 9 | #' 10 | #' @param cmd Command to run. See `R --help` from the command 11 | #' line for the various commands. In the current version of R (3.2.4) 12 | #' these are: `BATCH`, `COMPILE`, `SHLIB`, `INSTALL`, `REMOVE`, `build`, 13 | #' `check`, `LINK`, `Rprof`, `Rdconv`, `Rd2pdf`, `Rd2txt`, `Stangle`, 14 | #' `Sweave`, `Rdiff`, `config`, `javareconf`, `rtags`. 15 | #' @param cmdargs Command line arguments. 16 | #' @param stdout Optionally a file name to send the standard output to. 17 | #' @param stderr Optionally a file name to send the standard error to. 18 | #' It may be the same as `stdout`, in which case standard error is 19 | #' redirected to standard output. It can also be the special string 20 | #' `"2>&1"`, in which case standard error will be redirected to standard 21 | #' output. 22 | #' @param poll_connection Whether to have a control connection to 23 | #' the process. This is used to transmit messages from the subprocess 24 | #' to the parent. 25 | #' @param echo Whether to echo the complete command run by `rcmd`. 26 | #' @param wd Working directory to use for running the command. Defaults 27 | #' to the current working directory. 28 | #' @param fail_on_status Whether to throw an R error if the command returns 29 | #' with a non-zero status code. By default no error is thrown. 30 | #' @inheritParams r 31 | #' @inheritSection r Security considerations 32 | #' @return A list with the command line `$command`), 33 | #' standard output (`$stdout`), standard error (`stderr`), 34 | #' exit status (`$status`) of the external `R CMD` command, and 35 | #' whether a timeout was reached (`$timeout`). 36 | #' 37 | #' @family R CMD commands 38 | #' @export 39 | #' 40 | #' @examplesIf FALSE 41 | #' rcmd("config", "CC") 42 | 43 | rcmd <- function( 44 | cmd, 45 | cmdargs = character(), 46 | libpath = .libPaths(), 47 | repos = default_repos(), 48 | stdout = NULL, 49 | stderr = NULL, 50 | poll_connection = TRUE, 51 | echo = FALSE, 52 | show = FALSE, 53 | callback = NULL, 54 | block_callback = NULL, 55 | spinner = show && interactive(), 56 | system_profile = FALSE, 57 | user_profile = "project", 58 | env = rcmd_safe_env(), 59 | timeout = Inf, 60 | wd = ".", 61 | fail_on_status = FALSE, 62 | ... 63 | ) { 64 | ## This contains the context that we set up in steps 65 | options <- convert_and_check_my_args(as.list(environment())) 66 | options$extra <- list(...) 67 | 68 | options <- setup_context(options) 69 | options <- setup_callbacks(options) 70 | options <- setup_rcmd_binary_and_args(options) 71 | 72 | ## This cleans up everything... 73 | on.exit(unlink(options$tmp_files, recursive = TRUE), add = TRUE) 74 | 75 | run_r(options) 76 | } 77 | 78 | #' @rdname rcmd 79 | #' @export 80 | 81 | rcmd_safe <- rcmd 82 | 83 | #' `rcmd_safe_env` returns a set of environment variables that are 84 | #' more appropriate for [rcmd_safe()]. It is exported to allow manipulating 85 | #' these variables (e.g. add an extra one), before passing them to the 86 | #' [rcmd()] functions. 87 | #' 88 | #' It currently has the following variables: 89 | #' * `CYGWIN="nodosfilewarning"`: On Windows, do not warn about MS-DOS 90 | #' style file names. 91 | #' * `R_TESTS=""` This variable is set by `R CMD check`, and makes the 92 | #' child R process load a startup file at startup, from the current 93 | #' working directory, that is assumed to be the `/test` directory 94 | #' of the package being checked. If the current working directory is 95 | #' changed to something else (as it typically is by `testthat`, then R 96 | #' cannot start. Setting it to the empty string ensures that `callr` can 97 | #' be used from unit tests. 98 | #' * `R_BROWSER="false"`: typically we don't want to start up a browser 99 | #' from the child R process. 100 | #' * `R_PDFVIEWER="false"`: similarly for the PDF viewer. 101 | #' 102 | #' Note that `callr` also sets the `R_ENVIRON`, `R_ENVIRON_USER`, 103 | #' `R_PROFILE` and `R_PROFILE_USER` environment variables 104 | #' appropriately, unless these are set by the user in the `env` argument 105 | #' of the `r`, etc. calls. 106 | #' 107 | #' @return A named character vector of environment variables. 108 | #' 109 | #' @export 110 | 111 | rcmd_safe_env <- function() { 112 | vars <- c( 113 | CYGWIN = "nodosfilewarning", 114 | R_TESTS = "", 115 | R_BROWSER = "false", 116 | R_PDFVIEWER = "false" 117 | ) 118 | 119 | vars 120 | } 121 | 122 | #' Call and `R CMD` command, while mimicking the current R session 123 | #' 124 | #' This function is similar to [rcmd()], but it has slightly different 125 | #' defaults: 126 | #' * The `repos` options is unchanged. 127 | #' * No extra environment variables are defined. 128 | #' 129 | #' @inheritSection r Security considerations 130 | #' @inheritParams rcmd 131 | #' @param ... Additional arguments are passed to [rcmd()]. 132 | #' 133 | #' @family R CMD commands 134 | #' @export 135 | 136 | rcmd_copycat <- function( 137 | cmd, 138 | cmdargs = character(), 139 | libpath = .libPaths(), 140 | repos = getOption("repos"), 141 | env = character(), 142 | ... 143 | ) { 144 | rcmd(cmd, cmdargs = cmdargs, libpath = libpath, repos = repos, env = env, ...) 145 | } 146 | -------------------------------------------------------------------------------- /R/result.R: -------------------------------------------------------------------------------- 1 | #' Read the result object from the output file, or the error 2 | #' 3 | #' Even if an error happens, the output file might still exist, 4 | #' because [saveRDS()] creates the file before evaluating its object 5 | #' argument. So we need to check for the error file to decide 6 | #' if an error happened. 7 | #' 8 | #' @param output List of the output object from [run()] and 9 | #' the name of the result file to read. For the error file, 10 | #' `.error` is appended to this. 11 | #' @param options The context, including all parameters. 12 | #' @return If no error happened, the result is returned. Otherwise 13 | #' we handle the error. 14 | #' 15 | #' @keywords internal 16 | 17 | get_result <- function(output, options) { 18 | res <- options$result_file 19 | 20 | ## Timeout? 21 | if (output$timeout) throw(new_callr_crash_error(output)) 22 | 23 | ## No output file and no error file? Some other (system?) error then, 24 | ## unless exit status was zero, which is probably just quit(). 25 | ## (Newer R versions do not write a corrupt RDS file in this case.) 26 | ret <- NULL 27 | errorres <- paste0(res, ".error") 28 | killmsg <- paste( 29 | "could not start R, exited with non-zero status,", 30 | "has crashed or was killed" 31 | ) 32 | if (!file.exists(res) && !file.exists(errorres)) { 33 | if (is.na(output$status) || output$status != 0) { 34 | throw(new_callr_crash_error(output, killmsg)) 35 | } else { 36 | return(ret) 37 | } 38 | } 39 | 40 | ## No error file? Then probably all is well, return the output 41 | ## If this is corrupt, then the R process has crashed 42 | ## This cannot happen from R 3.5.0, because that version only writes 43 | ## out the output file if no error or crash has happened. 44 | ## (Older R versions write a corrupt RDS file in this case.) 45 | if (!file.exists(errorres)) { 46 | tryCatch( 47 | ret <- readRDS(res), 48 | error = function(e) { 49 | if (is.na(output$status) || output$status != 0) { 50 | throw(new_callr_crash_error(output, killmsg)) 51 | } else { 52 | throw(new_callr_crash_error( 53 | output, 54 | "could not read result from callr" 55 | )) 56 | } 57 | } 58 | ) 59 | 60 | return(ret) 61 | } 62 | 63 | # To work around an errors.R bug, if the format method is registered 64 | # from another package. errors.R expects that the parent error has a 65 | # message, but if it is an interrupt (and perhaps some other rare cases), 66 | # it does not. 67 | fix_msg <- function(cnd) { 68 | if (is.null(cnd$message)) { 69 | if (inherits(cnd, "interrupt")) { 70 | cnd$message <- "interrupt" 71 | } else { 72 | cnd$message <- "" 73 | } 74 | } 75 | cnd 76 | } 77 | 78 | ## The error RDS might be corrupt, too, if we crashed/got killed after 79 | ## an error 80 | tryCatch( 81 | remerr <- readRDS(errorres), 82 | error = function(e) throw(new_callr_crash_error(output, killmsg)) 83 | ) 84 | 85 | if (remerr[[1]] == "error") { 86 | throw(callr_remote_error(remerr, output), parent = fix_msg(remerr[[3]])) 87 | } else if (remerr[[1]] == "stack") { 88 | throw( 89 | callr_remote_error_with_stack(remerr, output), 90 | parent = fix_msg(remerr[[2]]) 91 | ) 92 | } else if (remerr[[1]] == "debugger") { 93 | utils::debugger(clean_stack(remerr[[3]])) 94 | } else { 95 | throw(new_error("Unknown callr error strategy: ", remerr[[1]])) # nocov 96 | } 97 | } 98 | 99 | clean_stack <- function(stack) { 100 | att <- attributes(stack) 101 | att$names <- utils::head(utils::tail(att$names, -11), -2) 102 | res <- utils::head(utils::tail(stack, -11), -2) 103 | attributes(res) <- att 104 | 105 | res 106 | } 107 | -------------------------------------------------------------------------------- /R/rscript.R: -------------------------------------------------------------------------------- 1 | #' Run an R script 2 | #' 3 | #' It uses the `Rscript` program corresponding to the current R version, 4 | #' to run the script. It streams `stdout` and `stderr` of the process. 5 | #' 6 | #' @inheritSection r Security considerations 7 | #' @inheritParams rcmd 8 | #' @param script Path of the script to run. 9 | #' @param color Whether to use terminal colors in the child process, 10 | #' assuming they are active in the parent process. 11 | #' 12 | #' @export 13 | 14 | rscript <- function( 15 | script, 16 | cmdargs = character(), 17 | libpath = .libPaths(), 18 | repos = default_repos(), 19 | stdout = NULL, 20 | stderr = NULL, 21 | poll_connection = TRUE, 22 | echo = FALSE, 23 | show = TRUE, 24 | callback = NULL, 25 | block_callback = NULL, 26 | spinner = FALSE, 27 | system_profile = FALSE, 28 | user_profile = "project", 29 | env = rcmd_safe_env(), 30 | timeout = Inf, 31 | wd = ".", 32 | fail_on_status = TRUE, 33 | color = TRUE, 34 | ... 35 | ) { 36 | load_hook <- rscript_load_hook_color(color) 37 | 38 | options <- convert_and_check_my_args(as.list(environment())) 39 | options$extra <- list(...) 40 | 41 | options <- setup_context(options) 42 | options <- setup_callbacks(options) 43 | 44 | options <- setup_rscript_binary_and_args(options) 45 | 46 | ## This cleans up everything... 47 | on.exit(unlink(options$tmp_files, recursive = TRUE), add = TRUE) 48 | 49 | invisible(run_r(options)) 50 | } 51 | 52 | rscript_load_hook_color <- function(color) { 53 | if (!color) return("") 54 | 55 | nc <- tryCatch( 56 | cli::num_ansi_colors(), 57 | error = function(e) 1L 58 | ) 59 | if (nc == 1) return("") 60 | 61 | expr <- substitute( 62 | options(crayon.enabled = TRUE, crayon.colors = `_nc_`), 63 | list("_nc_" = nc) 64 | ) 65 | paste0(deparse(expr), "\n") 66 | } 67 | 68 | #' External `Rscript` process 69 | #' 70 | #' @description 71 | #' An `Rscript script.R` command that runs in the background. This is an 72 | #' R6 class that extends the [processx::process] class. 73 | #' 74 | #' @name rscript_process 75 | #' @examplesIf FALSE 76 | #' options <- rscript_process_options(script = "script.R") 77 | #' rp <- rscript_process$new(options) 78 | #' rp$wait() 79 | #' rp$read_output_lines() 80 | #' @export 81 | 82 | rscript_process <- R6::R6Class( 83 | "rscript_process", 84 | inherit = processx::process, 85 | public = list( 86 | #' @description Create a new `Rscript` process. 87 | #' @param options A list of options created via 88 | #' [rscript_process_options()]. 89 | initialize = function(options) rscript_init(self, private, super, options) 90 | ), 91 | private = list( 92 | options = NULL, 93 | finalize = function() { 94 | unlink(private$options$tmp_files, recursive = TRUE) 95 | if ("finalize" %in% ls(super)) super$finalize() 96 | } 97 | ) 98 | ) 99 | 100 | rscript_init <- function(self, private, super, options) { 101 | options$load_hook <- rscript_load_hook_color(options$color) 102 | options <- convert_and_check_my_args(options) 103 | options <- setup_context(options) 104 | options <- setup_rscript_binary_and_args(options) 105 | 106 | private$options <- options 107 | 108 | oldwd <- getwd() 109 | setwd(options$wd) 110 | on.exit(setwd(oldwd), add = TRUE) 111 | 112 | with_envvar( 113 | options$env, 114 | do.call( 115 | super$initialize, 116 | c( 117 | list( 118 | options$bin, 119 | options$real_cmdargs, 120 | stdout = options$stdout, 121 | stderr = options$stderr, 122 | poll_connection = options$poll_connection 123 | ), 124 | options$extra 125 | ) 126 | ) 127 | ) 128 | 129 | invisible(self) 130 | } 131 | -------------------------------------------------------------------------------- /R/run.R: -------------------------------------------------------------------------------- 1 | run_r <- function(options) { 2 | oldwd <- getwd() 3 | setwd(options$wd) 4 | on.exit(setwd(oldwd), add = TRUE) 5 | 6 | ## We redirect stderr to stdout if either of these are true: 7 | ## - stderr is the string "2>&1" 8 | ## - both stdout and stderr are non-null, and they are the same 9 | stderr_to_stdout <- with( 10 | options, 11 | (!is.null(stderr) && stderr == "2>&1") || 12 | (!is.null(stdout) && !is.null(stderr) && stdout == stderr) 13 | ) 14 | 15 | res <- with( 16 | options, 17 | with_envvar( 18 | env, 19 | do.call( 20 | processx::run, 21 | c( 22 | list( 23 | bin, 24 | args = real_cmdargs, 25 | stdout_line_callback = real_callback(stdout), 26 | stderr_line_callback = real_callback(stderr), 27 | stdout_callback = real_block_callback, 28 | stderr_callback = real_block_callback, 29 | stderr_to_stdout = stderr_to_stdout, 30 | echo_cmd = echo, 31 | echo = show, 32 | spinner = spinner, 33 | error_on_status = fail_on_status, 34 | timeout = timeout 35 | ), 36 | extra 37 | ) 38 | ) 39 | ) 40 | ) 41 | 42 | res$command <- c(options$bin, options$real_cmdargs) 43 | res 44 | } 45 | -------------------------------------------------------------------------------- /R/test-helpers.R: -------------------------------------------------------------------------------- 1 | is_true_check_env_var <- function(x, default = "") { 2 | # like utils:::str2logical 3 | val <- Sys.getenv(x, default) 4 | if (isTRUE(as.logical(val))) return(TRUE) 5 | tolower(val) %in% c("1", "yes") 6 | } 7 | 8 | isFALSE <- function(x) { 9 | is.logical(x) && length(x) == 1L && !is.na(x) && !x 10 | } 11 | 12 | is_false_check_env_var <- function(x, default = "") { 13 | # like utils:::str2logical 14 | val <- Sys.getenv(x, default) 15 | if (isFALSE(as.logical(val))) return(TRUE) 16 | tolower(val) %in% c("0", "no") 17 | } 18 | 19 | # Only skip if _R_CHECK_FORCE_SUGGESTS_ is false 20 | 21 | skip_if_not_installed <- function(pkg) { 22 | if (!is_false_check_env_var("_R_CHECK_FORCE_SUGGESTS_")) return() 23 | testthat::skip_if_not_installed(pkg) 24 | } 25 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' Default value for the `repos` option in callr subprocesses 2 | #' 3 | #' callr sets the `repos` option in subprocesses, to make sure that 4 | #' a CRAN mirror is set up. This is because the subprocess cannot bring 5 | #' up the menu of CRAN mirrors for the user to choose from. 6 | #' 7 | #' @return Named character vector, the default value of the `repos` 8 | #' option in callr subprocesses. 9 | #' 10 | #' @export 11 | #' @examples 12 | #' default_repos() 13 | 14 | default_repos <- function() { 15 | opt <- getOption("repos") 16 | was_list <- is.list(opt) 17 | if (!"CRAN" %in% names(opt) || opt[["CRAN"]] == "@CRAN@") { 18 | opt[["CRAN"]] <- "https://cloud.r-project.org" 19 | } 20 | if (!was_list) opt <- unlist(opt) 21 | opt 22 | } 23 | 24 | remove_source <- function(x) { 25 | if (is.function(x)) { 26 | body(x) <- remove_source(body(x)) 27 | x 28 | } else if (is.call(x)) { 29 | attr(x, "srcref") <- NULL 30 | attr(x, "wholeSrcref") <- NULL 31 | attr(x, "srcfile") <- NULL 32 | 33 | x[] <- lapply(x, remove_source) 34 | x 35 | } else { 36 | x 37 | } 38 | } 39 | 40 | `%||%` <- function(l, r) if (is.null(l)) r else l 41 | 42 | is.named <- function(x) { 43 | length(names(x)) == length(x) && all(names(x) != "") 44 | } 45 | 46 | set_envvar <- function(envs) { 47 | if (length(envs) == 0) return() 48 | 49 | stopifnot(is.named(envs)) 50 | 51 | old <- Sys.getenv(names(envs), names = TRUE, unset = NA) 52 | set <- !is.na(envs) 53 | 54 | both_set <- set & !is.na(old) 55 | 56 | if (any(set)) do.call("Sys.setenv", as.list(envs[set])) 57 | if (any(!set)) Sys.unsetenv(names(envs)[!set]) 58 | 59 | invisible(old) 60 | } 61 | 62 | with_envvar <- function(new, code) { 63 | old <- set_envvar(new) 64 | on.exit(set_envvar(old)) 65 | force(code) 66 | } 67 | 68 | os_platform <- function() .Platform$OS.type 69 | 70 | enumerate <- function(x) { 71 | if (length(x) == 0) { 72 | "" 73 | } else if (length(x) == 1) { 74 | x 75 | } else { 76 | l <- length(x) 77 | paste0(paste(x[-l], collapse = ", "), " and ", x[[l]]) 78 | } 79 | } 80 | 81 | ## Thanks to Romain for the idea! 82 | ## https://github.com/romainfrancois/trump/blob/ 83 | ## 7845b83343afa356e4259c054e7c9a910034f170/R/trump.R 84 | 85 | crash <- function() { 86 | get("attach")( 87 | structure( 88 | attr(file(), "conn_id"), 89 | class = "UserDefinedDatabase" 90 | ) 91 | ) 92 | } 93 | 94 | is_flag <- function(x) { 95 | is.logical(x) && length(x) == 1 && !is.na(x) 96 | } 97 | 98 | is_string <- function(x) { 99 | is.character(x) && 100 | length(x) == 1 && 101 | !is.na(x) 102 | } 103 | 104 | read_all <- function(filename) { 105 | con <- file(filename, open = "rb") 106 | on.exit(close(con), add = TRUE) 107 | res <- raw(0) 108 | while (length(more <- readBin(con, what = "raw", 10000)) && length(more)) { 109 | res <- c(res, more) 110 | } 111 | rawToChar(res) 112 | } 113 | 114 | is_complete_expression <- function(x) { 115 | err <- NULL 116 | tryCatch(parse(text = x), error = function(e) err <<- e) 117 | if (is.null(err)) return(TRUE) 118 | exp <- tryCatch(parse(text = "1+"), error = function(e) e$message) 119 | exp1 <- strsplit(exp, "\n")[[1]][[1]] 120 | msg <- sub("^.*:\\s*([^:]+)$", "\\1", exp1, perl = TRUE) 121 | !grepl(msg, conditionMessage(err), fixed = TRUE) 122 | } 123 | 124 | bold <- function(x) { 125 | tryCatch( 126 | cli::style_bold(x), 127 | error = function(e) x 128 | ) 129 | } 130 | 131 | update_history <- function(cmd) { 132 | tmp <- tempfile("callr-hst-") 133 | on.exit(unlink(tmp, recursive = TRUE)) 134 | utils::savehistory(tmp) 135 | cat(cmd, "\n", sep = "", file = tmp, append = TRUE) 136 | utils::loadhistory(tmp) 137 | } 138 | 139 | #' Find supported sub-architectures for the current R installation 140 | #' 141 | #' This function uses a heuristic, which might fail, so its result 142 | #' should be taken as a best guess. 143 | #' 144 | #' @return Character vector of supported architectures. If the 145 | #' current R build is not a multi-architecture build, then an empty 146 | #' string scalar is returned. 147 | #' 148 | #' @export 149 | #' @examples 150 | #' supported_archs() 151 | 152 | supported_archs <- function() { 153 | oldwd <- getwd() 154 | on.exit(setwd(oldwd), add = TRUE) 155 | setwd(file.path(R.home(), "bin")) 156 | archs <- list.dirs(recursive = FALSE) 157 | archs <- sub("^[.]?[/\\\\]", "", archs) 158 | archs <- setdiff(archs, "exec") 159 | if (length(archs) == 0) { 160 | if (nzchar(.Platform$r_arch)) { 161 | archs <- .Platform$r_arch 162 | } else { 163 | archs <- Sys.getenv("R_ARCH") 164 | } 165 | } 166 | archs 167 | } 168 | -------------------------------------------------------------------------------- /R/x-client.R: -------------------------------------------------------------------------------- 1 | client_env <- local({ 2 | env <- new.env(parent = emptyenv()) 3 | env$`__callr_data__` <- new.env(parent = baseenv()) 4 | 5 | rsfile <- file.path("R", "aaa-rstudio-detect.R") 6 | sys.source(rsfile, envir = env$`__callr_data__`, keep.source = FALSE) 7 | errfile <- file.path("R", "standalone-errors.R") 8 | sys.source(errfile, envir = env$`__callr_data__`, keep.source = FALSE) 9 | loadfile <- file.path("R", "load-client.R") 10 | sys.source(loadfile, envir = env$`__callr_data__`, keep.source = FALSE) 11 | 12 | env 13 | }) 14 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://callr.r-lib.org 2 | 3 | template: 4 | package: tidytemplate 5 | bootstrap: 5 6 | includes: 7 | in_header: | 8 | 9 | 10 | development: 11 | mode: auto 12 | 13 | authors: 14 | "Winston Chang": 15 | href: https://github.com/wch 16 | 17 | navbar: 18 | structure: 19 | left: [reference, articles, news] 20 | components: 21 | articles: 22 | text: Articles 23 | menu: 24 | - text: Tutorials 25 | - text: Persistent R sessions 26 | href: articles/r-session.html 27 | - text: ------- 28 | - text: Blog posts 29 | - text: "A multi process task queue in 100 lines of R code" 30 | href: https://www.tidyverse.org/blog/2019/09/callr-task-q 31 | 32 | reference: 33 | - title: Introduction 34 | contents: 35 | - callr 36 | 37 | - title: Run R 38 | contents: 39 | - r 40 | - r_copycat 41 | - r_safe 42 | - r_vanilla 43 | 44 | - title: Run R in the background 45 | contents: 46 | - r_bg 47 | - r_process 48 | - r_process_options 49 | 50 | - title: Persistent background R session 51 | contents: 52 | - r_session 53 | - r_session_options 54 | - r_session_debug 55 | 56 | - title: Run R CMD 57 | contents: 58 | - rcmd 59 | - rcmd_copycat 60 | - rcmd_safe 61 | - rcmd_safe_env 62 | 63 | - title: Run R CMD in the background 64 | contents: 65 | - rcmd_bg 66 | - rcmd_process 67 | - rcmd_process_options 68 | 69 | - title: Run Rscript 70 | contents: 71 | - rscript 72 | 73 | - title: Run Rscript in the background 74 | contents: 75 | - rscript_process 76 | - rscript_process_options 77 | 78 | - title: Miscellaneous utilities 79 | contents: 80 | - default_repos 81 | - supported_archs 82 | - add_hook 83 | 84 | - title: Re-exported functions 85 | contents: 86 | - process 87 | - run 88 | - poll 89 | -------------------------------------------------------------------------------- /air.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/callr/565a5090975b5f3c5cca24304bd46f0f7af2ae0b/air.toml -------------------------------------------------------------------------------- /callr.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: XeLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /inst/COPYRIGHTS: -------------------------------------------------------------------------------- 1 | (c) 2015-2016 Ascent Digital Services (formerly Mango Solutions) 2 | (c) 2017 Gábor Csárdi 3 | (c) 2017-2024 Posit Software, PBC (formerly RStudio) 4 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | CMD 2 | CTRL 3 | Codecov 4 | ESC 5 | Eval 6 | Finalizer 7 | ORCID 8 | PBC 9 | REPL 10 | callr's 11 | cli 12 | cliapp 13 | cloneable 14 | dll 15 | env 16 | eval 17 | finalizer 18 | funder 19 | github 20 | https 21 | igraph 22 | interruptible 23 | keyring 24 | lifecycle 25 | macOS 26 | packrat 27 | pkgdown 28 | processx 29 | renv 30 | stderr 31 | stdout 32 | subcommand 33 | subprocess 34 | subprocesses 35 | -------------------------------------------------------------------------------- /inst/developer-notes.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Design of the internals 4 | 5 | There are a lot of possible ways to implement this package, to minimize 6 | duplication. This is the API we want: 7 | 8 | ```r 9 | r(fun, args, ...) 10 | r_safe(fun, args, ...) 11 | 12 | r_bg(fun, args, ...) 13 | r_bg_safe(fun, args, ...) 14 | 15 | rcmd(cmd, cmdargs, ...) 16 | rcmd_safe(cmd, cmdargs, ...) 17 | 18 | rcmd_bg(cmd, cmdargs, ...) 19 | rcmd_bg_safe(cmd, cmdargs ...) 20 | ``` 21 | 22 | The `_safe` versions are easy to deal with, they just call the non-`_safe` 23 | versions with different arguments. 24 | 25 | For the other versions, this is what they need to do: 26 | 27 | ### `r` 28 | 29 | 1. convert / check arguments 30 | 2. save function to a file 31 | 3. create script file 32 | 4. set up profiles 33 | 5. set up library path 34 | 6. set up LIB and PROFILE env vars 35 | 7. set up callbacks (combine show and callbacks) 36 | 8. run the R process 37 | 9. write stdout & stderr to file, if needed 38 | 10. fail on status, if requested 39 | 11. get the result 40 | 41 | ### `r_bg` 42 | 43 | 1. convert / check arguments 44 | 2. save function to a file 45 | 3. create script file 46 | 4. set up profiles 47 | 5. set up library path 48 | 7. set up LIB and PROFILE env vars 49 | 8. start the R process in the bg 50 | 51 | ### `rcmd` 52 | 53 | 1. convert / check arguments 54 | 2. get the R binary and its arguments 55 | 3. set specified wd 56 | 4. set up profiles 57 | 5. set up library path 58 | 6. set up callbacks (combine show and callbacks) 59 | 7. set up LIB and PROFILE env vars 60 | 8. run the R process 61 | 9. write stdout & stderr to file, if needed 62 | 10. fail on status, if requested 63 | 64 | ### `rcmd_bg` 65 | 66 | 1. convert/check arguments 67 | 2. get the R binary and its arguments 68 | 3. set specified wd 69 | 4. set up profiles 70 | 5. set up library path 71 | 7. set up LIB and PROFILE env vars 72 | 8. run the R process in the bg 73 | 74 | ### Building blocks 75 | 76 | #### Always run, `check_my_args`: 77 | 78 | 1. convert / check arguments 79 | 80 | #### Run by `r` and `r_bg`, `setup_script_files`: 81 | 82 | 1. save function to a file 83 | 2. create script file 84 | 85 | #### Always run, `setup_context`: 86 | 87 | This is run with `.` as the working directory for `r` and `r_bg`. 88 | 89 | 1. set specified wd 90 | 2. set up profiles 91 | 3. set up library path 92 | 4. set up LIB and PROFILE env vars 93 | -------------------------------------------------------------------------------- /inst/header.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # callr 5 | 6 | > Call R from R 7 | 8 | 9 | [![lifecycle](https://lifecycle.r-lib.org/articles/figures/lifecycle-stable.svg)](https://lifecycle.r-lib.org/articles/stages.html) 10 | [![R-CMD-check](https://github.com/r-lib/callr/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/callr/actions/workflows/R-CMD-check.yaml) 11 | [![](https://www.r-pkg.org/badges/version/callr)](https://www.r-pkg.org/pkg/callr) 12 | [![CRAN Posit mirror downloads](https://cranlogs.r-pkg.org/badges/callr)](https://www.r-pkg.org/pkg/callr) 13 | [![Codecov test coverage](https://codecov.io/gh/r-lib/callr/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-lib/callr?branch=main) 14 | 15 | 16 | 17 | It is sometimes useful to perform a computation in a separate R process, 18 | without affecting the current R process at all. This packages does exactly 19 | that. 20 | 21 | --- 22 | -------------------------------------------------------------------------------- /man/add_hook.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hook.R 3 | \name{add_hook} 4 | \alias{add_hook} 5 | \title{Add a user hook to be executed before launching an R subprocess} 6 | \usage{ 7 | add_hook(...) 8 | } 9 | \arguments{ 10 | \item{...}{Named argument specifying a hook function to add, or \code{NULL} to 11 | delete the named hook.} 12 | } 13 | \value{ 14 | \code{add_hook} is called for its side-effects. 15 | } 16 | \description{ 17 | This function allows users of \code{callr} to specify functions that get invoked 18 | whenever an R session is launched. The function can modify the environment 19 | variables and command line arguments. 20 | } 21 | \details{ 22 | The prototype of the hook function is \verb{function (options)}, and it is 23 | expected to return the modified \code{options}. 24 | } 25 | -------------------------------------------------------------------------------- /man/convert_and_check_my_args.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/check.R 3 | \name{convert_and_check_my_args} 4 | \alias{convert_and_check_my_args} 5 | \title{Convert and check function arguments} 6 | \usage{ 7 | convert_and_check_my_args(options) 8 | } 9 | \arguments{ 10 | \item{options}{List of options.} 11 | } 12 | \description{ 13 | This function is used for all variants of \code{r} and \code{rcmd}. An argument 14 | name is only used to refer to one kind of object, to make this possible. 15 | } 16 | \details{ 17 | The benefit of having a single \code{options} object is to avoid passing 18 | around a lot of arguments all the time. 19 | 20 | The benefit of making this object internal (i.e. that the \code{r}, etc. 21 | functions have multiple arguments instead of a single \code{options} list), 22 | is that documentation and usage is more user friendly (e.g. command- 23 | completion works in the editor. 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /man/default_repos.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{default_repos} 4 | \alias{default_repos} 5 | \title{Default value for the \code{repos} option in callr subprocesses} 6 | \usage{ 7 | default_repos() 8 | } 9 | \value{ 10 | Named character vector, the default value of the \code{repos} 11 | option in callr subprocesses. 12 | } 13 | \description{ 14 | callr sets the \code{repos} option in subprocesses, to make sure that 15 | a CRAN mirror is set up. This is because the subprocess cannot bring 16 | up the menu of CRAN mirrors for the user to choose from. 17 | } 18 | \examples{ 19 | default_repos() 20 | } 21 | -------------------------------------------------------------------------------- /man/figures/bg-dark.svg: -------------------------------------------------------------------------------- 1 | PROCESS'R',running,pid98048. -------------------------------------------------------------------------------- /man/figures/bg.svg: -------------------------------------------------------------------------------- 1 | PROCESS'R',running,pid98048. -------------------------------------------------------------------------------- /man/figures/error1-dark.svg: -------------------------------------------------------------------------------- 1 | Error:!incallrsubprocess.Causedbyerrorin`1+"A"`:!non-numericargumenttobinaryoperatorType.Last.errortoseethemoredetails. -------------------------------------------------------------------------------- /man/figures/error1.svg: -------------------------------------------------------------------------------- 1 | Error:!incallrsubprocess.Causedbyerrorin`1+"A"`:!non-numericargumenttobinaryoperatorType.Last.errortoseethemoredetails. -------------------------------------------------------------------------------- /man/figures/io-2-dark.svg: -------------------------------------------------------------------------------- 1 | [1]"helloagain!" -------------------------------------------------------------------------------- /man/figures/io-2.svg: -------------------------------------------------------------------------------- 1 | [1]"helloagain!" -------------------------------------------------------------------------------- /man/figures/io-dark.svg: -------------------------------------------------------------------------------- 1 | [1]"[1]\"helloworld!\"" -------------------------------------------------------------------------------- /man/figures/io.svg: -------------------------------------------------------------------------------- 1 | [1]"[1]\"helloworld!\"" -------------------------------------------------------------------------------- /man/figures/packages-dark.svg: -------------------------------------------------------------------------------- 1 | [1]12 -------------------------------------------------------------------------------- /man/figures/packages.svg: -------------------------------------------------------------------------------- 1 | [1]12 -------------------------------------------------------------------------------- /man/figures/passargsfail-dark.svg: -------------------------------------------------------------------------------- 1 | Error:!incallrsubprocess.Causedbyerrorin`summary(mycars)`:!object'mycars'notfoundType.Last.errortoseethemoredetails. -------------------------------------------------------------------------------- /man/figures/passargsfail.svg: -------------------------------------------------------------------------------- 1 | Error:!incallrsubprocess.Causedbyerrorin`summary(mycars)`:!object'mycars'notfoundType.Last.errortoseethemoredetails. -------------------------------------------------------------------------------- /man/figures/passargsok-dark.svg: -------------------------------------------------------------------------------- 1 | speeddistMin.:4.0Min.:2.001stQu.:12.01stQu.:26.00Median:15.0Median:36.00Mean:15.4Mean:42.983rdQu.:19.03rdQu.:56.00Max.:25.0Max.:120.00 -------------------------------------------------------------------------------- /man/figures/passargsok.svg: -------------------------------------------------------------------------------- 1 | speeddistMin.:4.0Min.:2.001stQu.:12.01stQu.:26.00Median:15.0Median:36.00Mean:15.4Mean:42.983rdQu.:19.03rdQu.:56.00Max.:25.0Max.:120.00 -------------------------------------------------------------------------------- /man/figures/poll-2-dark.svg: -------------------------------------------------------------------------------- 1 | [1]"2done" -------------------------------------------------------------------------------- /man/figures/poll-2.svg: -------------------------------------------------------------------------------- 1 | [1]"2done" -------------------------------------------------------------------------------- /man/figures/poll-3-dark.svg: -------------------------------------------------------------------------------- 1 | [[1]]outputerrorprocess"ready""ready""ready" -------------------------------------------------------------------------------- /man/figures/poll-3.svg: -------------------------------------------------------------------------------- 1 | [[1]]outputerrorprocess"ready""ready""ready" -------------------------------------------------------------------------------- /man/figures/poll-4-dark.svg: -------------------------------------------------------------------------------- 1 | [1]"1done" -------------------------------------------------------------------------------- /man/figures/poll-4.svg: -------------------------------------------------------------------------------- 1 | [1]"1done" -------------------------------------------------------------------------------- /man/figures/poll-dark.svg: -------------------------------------------------------------------------------- 1 | [[1]]outputerrorprocess"silent""silent""silent"[[2]]outputerrorprocess"ready""ready""ready" -------------------------------------------------------------------------------- /man/figures/poll.svg: -------------------------------------------------------------------------------- 1 | [[1]]outputerrorprocess"silent""silent""silent"[[2]]outputerrorprocess"ready""ready""ready" -------------------------------------------------------------------------------- /man/figures/rcmd-dark.svg: -------------------------------------------------------------------------------- 1 | $status[1]0$stdout[1]"clang-archarm64\n"$stderr[1]""$timeout[1]FALSE$command[1]"/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/bin/R"[2]"CMD"[3]"config"[4]"CC" -------------------------------------------------------------------------------- /man/figures/rcmd.svg: -------------------------------------------------------------------------------- 1 | $status[1]0$stdout[1]"clang-archarm64\n"$stderr[1]""$timeout[1]FALSE$command[1]"/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/bin/R"[2]"CMD"[3]"config"[4]"CC" -------------------------------------------------------------------------------- /man/figures/rsession-4-dark.svg: -------------------------------------------------------------------------------- 1 | [1]"ready" -------------------------------------------------------------------------------- /man/figures/rsession-4.svg: -------------------------------------------------------------------------------- 1 | [1]"ready" -------------------------------------------------------------------------------- /man/figures/rsession-5-dark.svg: -------------------------------------------------------------------------------- 1 | [1]""$code[1]200$message[1]"donecallr-rs-result-17eb55fb39cd4"$result[1]-0.043703461.18261173-0.329021950.144230131.935970941.19500910[7]0.238762750.85456492-1.740516321.42462027$stdout$stderr$errorNULLattr(,"class")[1]"callr_session_result" -------------------------------------------------------------------------------- /man/figures/rsession-5.svg: -------------------------------------------------------------------------------- 1 | [1]""$code[1]200$message[1]"donecallr-rs-result-17eb55fb39cd4"$result[1]-0.043703461.18261173-0.329021950.144230131.935970941.19500910[7]0.238762750.85456492-1.740516321.42462027$stdout$stderr$errorNULLattr(,"class")[1]"callr_session_result" -------------------------------------------------------------------------------- /man/figures/rsession-dark.svg: -------------------------------------------------------------------------------- 1 | RSESSION,alive,idle,pid98086. -------------------------------------------------------------------------------- /man/figures/rsession.svg: -------------------------------------------------------------------------------- 1 | RSESSION,alive,idle,pid98086. -------------------------------------------------------------------------------- /man/figures/rsession2-2-dark.svg: -------------------------------------------------------------------------------- 1 | RSESSION,alive,busy,pid98090. -------------------------------------------------------------------------------- /man/figures/rsession2-2.svg: -------------------------------------------------------------------------------- 1 | RSESSION,alive,busy,pid98090. -------------------------------------------------------------------------------- /man/figures/rsession2-dark.svg: -------------------------------------------------------------------------------- 1 | [1]0.93875370.43054120.12512460.98262260.40328680.81394150.9970173[8]0.87883710.29427240.9011616 -------------------------------------------------------------------------------- /man/figures/rsession2.svg: -------------------------------------------------------------------------------- 1 | [1]0.93875370.43054120.12512460.98262260.40328680.81394150.9970173[8]0.87883710.29427240.9011616 -------------------------------------------------------------------------------- /man/figures/simple-dark.svg: -------------------------------------------------------------------------------- 1 | Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSepal.Length0.6856935-0.04243401.27431540.5162707Sepal.Width-0.04243400.1899794-0.3296564-0.1216394Petal.Length1.2743154-0.32965643.11627791.2956094Petal.Width0.5162707-0.12163941.29560940.5810063 -------------------------------------------------------------------------------- /man/figures/simple.svg: -------------------------------------------------------------------------------- 1 | Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSepal.Length0.6856935-0.04243401.27431540.5162707Sepal.Width-0.04243400.1899794-0.3296564-0.1216394Petal.Length1.2743154-0.32965643.11627791.2956094Petal.Width0.5162707-0.12163941.29560940.5810063 -------------------------------------------------------------------------------- /man/get_result.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/result.R 3 | \name{get_result} 4 | \alias{get_result} 5 | \title{Read the result object from the output file, or the error} 6 | \usage{ 7 | get_result(output, options) 8 | } 9 | \arguments{ 10 | \item{output}{List of the output object from \code{\link[=run]{run()}} and 11 | the name of the result file to read. For the error file, 12 | \code{.error} is appended to this.} 13 | 14 | \item{options}{The context, including all parameters.} 15 | } 16 | \value{ 17 | If no error happened, the result is returned. Otherwise 18 | we handle the error. 19 | } 20 | \description{ 21 | Even if an error happens, the output file might still exist, 22 | because \code{\link[=saveRDS]{saveRDS()}} creates the file before evaluating its object 23 | argument. So we need to check for the error file to decide 24 | if an error happened. 25 | } 26 | \keyword{internal} 27 | -------------------------------------------------------------------------------- /man/new_callr_crash_error.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/error.R 3 | \name{new_callr_crash_error} 4 | \alias{new_callr_crash_error} 5 | \title{Create an error object} 6 | \usage{ 7 | new_callr_crash_error(out, msg = NULL) 8 | } 9 | \arguments{ 10 | \item{out}{The object returned by \code{\link[=run]{run()}}.} 11 | 12 | \item{msg}{An extra message to add to the error message.} 13 | } 14 | \description{ 15 | There are two kinds of errors, both have class \code{callr_error}: 16 | \enumerate{ 17 | \item the first one is thrown after a timeout: \code{callr_timeout_error}. 18 | \item the second one is thrown after an R error (in the other session): 19 | \code{callr_status_error}. 20 | } 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/r_copycat.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/presets.R 3 | \name{r_copycat} 4 | \alias{r_copycat} 5 | \title{Run an R process that mimics the current R process} 6 | \usage{ 7 | r_copycat( 8 | func, 9 | args = list(), 10 | libpath = .libPaths(), 11 | repos = getOption("repos"), 12 | cmdargs = "--slave", 13 | system_profile = TRUE, 14 | user_profile = TRUE, 15 | env = character(), 16 | ... 17 | ) 18 | } 19 | \arguments{ 20 | \item{func}{Function object to call in the new R process. 21 | The function should be self-contained and only refer to 22 | other functions and use variables explicitly from other packages 23 | using the \code{::} notation. By default the environment of the function 24 | is set to \code{.GlobalEnv} before passing it to the child process. 25 | (See the \code{package} option if you want to keep the environment.) 26 | Because of this, it is good practice to create an anonymous 27 | function and pass that to \code{callr}, instead of passing 28 | a function object from a (base or other) package. In particular 29 | 30 | \if{html}{\out{
}}\preformatted{r(.libPaths) 31 | }\if{html}{\out{
}} 32 | 33 | does not work, because \code{.libPaths} is defined in a special 34 | environment, but 35 | 36 | \if{html}{\out{
}}\preformatted{r(function() .libPaths()) 37 | }\if{html}{\out{
}} 38 | 39 | works just fine.} 40 | 41 | \item{args}{Arguments to pass to the function. Must be a list.} 42 | 43 | \item{libpath}{The library path.} 44 | 45 | \item{repos}{The \code{repos} option. If \code{NULL}, then no 46 | \code{repos} option is set. This options is only used if 47 | \code{user_profile} or \code{system_profile} is set \code{FALSE}, 48 | as it is set using the system or the user profile.} 49 | 50 | \item{cmdargs}{Command line arguments to pass to the R process. 51 | Note that \code{c("-f", rscript)} is appended to this, \code{rscript} 52 | is the name of the script file to run. This contains a call to the 53 | supplied function and some error handling code.} 54 | 55 | \item{system_profile}{Whether to use the system profile file.} 56 | 57 | \item{user_profile}{Whether to use the user's profile file. 58 | If this is \code{"project"}, then only the profile from the working 59 | directory is used, but the \code{R_PROFILE_USER} environment variable 60 | and the user level profile are not. See also "Security considerations" 61 | below.} 62 | 63 | \item{env}{Environment variables to set for the child process.} 64 | 65 | \item{...}{Additional arguments are passed to \code{\link[=r]{r()}}.} 66 | } 67 | \description{ 68 | Differences to \code{\link[=r]{r()}}: 69 | \itemize{ 70 | \item No extra repositories are set up. 71 | \item The \code{--no-save}, \code{--no-restore} 72 | command line arguments are not used. (But \code{--slave} still is.) 73 | \item The system profile and the user profile are loaded. 74 | \item No extra environment variables are set up. 75 | } 76 | } 77 | \section{Security considerations}{ 78 | 79 | 80 | \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of 81 | the local or user \code{.Rprofile}, in the session temporary 82 | directory. Avoid storing sensitive information such as passwords, in 83 | your environment file or your profile, otherwise this information will 84 | get scattered in various files, at least temporarily, until the 85 | subprocess finishes. You can use the keyring package to avoid passwords 86 | in plain files. 87 | } 88 | 89 | \seealso{ 90 | Other callr functions: 91 | \code{\link{r}()}, 92 | \code{\link{r_vanilla}()} 93 | } 94 | \concept{callr functions} 95 | -------------------------------------------------------------------------------- /man/r_process_options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options.R 3 | \name{r_process_options} 4 | \alias{r_process_options} 5 | \title{Create options for an \link{r_process} object} 6 | \usage{ 7 | r_process_options(...) 8 | } 9 | \arguments{ 10 | \item{...}{Options to override, named arguments.} 11 | } 12 | \value{ 13 | A list of options. 14 | 15 | \code{r_process_options()} creates a set of options to initialize a new 16 | object from the \code{r_process} class. Its arguments must be named, the 17 | names are used as option names. The options correspond to (some of) 18 | the arguments of the \code{\link[=r]{r()}} function. At least the \code{func} option must be 19 | specified, this is the R function to run in the background. 20 | } 21 | \description{ 22 | Create options for an \link{r_process} object 23 | } 24 | \examples{ 25 | ## List all options and their default values: 26 | r_process_options() 27 | } 28 | -------------------------------------------------------------------------------- /man/r_session_debug.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/r-session.R 3 | \name{r_session_debug} 4 | \alias{r_session_debug} 5 | \title{Interactive debugging of persistent R sessions} 6 | \description{ 7 | The \code{r_session$debug()} method is an interactive debugger to inspect 8 | the stack of the background process after an error. 9 | } 10 | \details{ 11 | Note that on callr version 3.8.0 and above, you need to set the 12 | \code{callr.traceback} option to \code{TRUE} (in the main process) to make 13 | the subprocess dump the frames on error. This is because saving 14 | the frames can be costly for large objects passed as arguments. 15 | 16 | \verb{$debug()} starts a REPL (Read-Eval-Print-Loop), that evaluates R 17 | expressions in the subprocess. It is similar to \code{\link[=browser]{browser()}} and 18 | \code{\link[=debugger]{debugger()}} and also has some extra commands: 19 | \itemize{ 20 | \item \code{.help} prints a short help message. 21 | \item \code{.where} prints the complete stack trace of the error. (The same as 22 | the \verb{$traceback()} method. 23 | \item \verb{.inspect } switches the "focus" to frame \verb{}. Frame 0 is the 24 | global environment, so \verb{.inspect 0} will switch back to that. 25 | } 26 | 27 | To exit the debugger, press the usual interrupt key, i.e. \code{CTRL+c} or 28 | \code{ESC} in some GUIs. 29 | 30 | Here is an example session that uses \verb{$debug()} (some output is omitted 31 | for brevity): 32 | 33 | \if{html}{\out{
}}\preformatted{# ---------------------------------------------------------------------- 34 | > rs <- r_session$new() 35 | > rs$run(function() knitr::knit("no-such-file")) 36 | Error in rs_run(self, private, func, args) : 37 | callr subprocess failed: cannot open the connection 38 | 39 | > rs$debug() 40 | Debugging in process 87361, press CTRL+C (ESC) to quit. Commands: 41 | .where -- print stack trace 42 | .inspect -- inspect a frame, 0 resets to .GlobalEnv 43 | .help -- print this message 44 | -- run in frame or .GlobalEnv 45 | 46 | 3: file(con, "r") 47 | 2: readLines(input2, encoding = "UTF-8", warn = FALSE) 48 | 1: knitr::knit("no-such-file") at #1 49 | 50 | RS 87361 > .inspect 1 51 | 52 | RS 87361 (frame 1) > ls() 53 | [1] "encoding" "envir" "ext" "in.file" "input" "input.dir" 54 | [7] "input2" "ocode" "oconc" "oenvir" "oopts" "optc" 55 | [13] "optk" "otangle" "out.purl" "output" "quiet" "tangle" 56 | [19] "text" 57 | 58 | RS 87361 (frame 1) > input 59 | [1] "no-such-file" 60 | 61 | RS 87361 (frame 1) > file.exists(input) 62 | [1] FALSE 63 | 64 | RS 87361 (frame 1) > # 65 | # ---------------------------------------------------------------------- 66 | }\if{html}{\out{
}} 67 | } 68 | -------------------------------------------------------------------------------- /man/r_session_options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/r-session.R 3 | \name{r_session_options} 4 | \alias{r_session_options} 5 | \title{Create options for an \link{r_session} object} 6 | \usage{ 7 | r_session_options(...) 8 | } 9 | \arguments{ 10 | \item{...}{Options to override, named arguments.} 11 | } 12 | \value{ 13 | Named list of options. 14 | 15 | The current options are: 16 | \itemize{ 17 | \item \code{libpath}: Library path for the subprocess. By default the same as the 18 | \emph{current} library path. I.e. \emph{not} necessarily the library path of 19 | a fresh R session.) 20 | \item \code{repos}: \code{repos} option for the subprocess. By default the current 21 | value of the main process. 22 | \item \code{stdout}: Standard output of the sub-process. This can be \code{NULL} or 23 | a pipe: \code{"|"}. If it is a pipe then the output of the subprocess is 24 | not included in the responses, but you need to poll and read it 25 | manually. This is for experts. Note that this option is not used 26 | for the startup phase that currently always runs with \code{stdout = "|"}. 27 | \item \code{stderr}: Similar to \code{stdout}, but for the standard error. Like 28 | \code{stdout}, it is not used for the startup phase, which runs with 29 | \code{stderr = "|"}. 30 | \item \code{error}: See 'Error handling' in \code{\link[=r]{r()}}. 31 | \item \code{cmdargs}: See the same argument of \code{\link[=r]{r()}}. (Its default might be 32 | different, though.) 33 | \item \code{system_profile}: See the same argument of \code{\link[=r]{r()}}. 34 | \item \code{user_profile}: See the same argument of \code{\link[=r]{r()}}. 35 | \item \code{env}: See the same argument of \code{\link[=r]{r()}}. 36 | \item \code{load_hook}: \code{NULL}, or code (quoted) to run in the sub-process 37 | at start up. (I.e. not for every single \code{run()} call.) 38 | \item \code{extra}: List of extra arguments to pass to \link[processx:process]{processx::process}. 39 | } 40 | 41 | Call \code{r_session_options()} to see the default values. 42 | \code{r_session_options()} might contain undocumented entries, you cannot 43 | change these. 44 | } 45 | \description{ 46 | Create options for an \link{r_session} object 47 | } 48 | \examples{ 49 | r_session_options() 50 | } 51 | -------------------------------------------------------------------------------- /man/r_vanilla.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/presets.R 3 | \name{r_vanilla} 4 | \alias{r_vanilla} 5 | \title{Run an R child process, with no configuration} 6 | \usage{ 7 | r_vanilla( 8 | func, 9 | args = list(), 10 | libpath = character(), 11 | repos = c(CRAN = "@CRAN@"), 12 | cmdargs = "--slave", 13 | system_profile = FALSE, 14 | user_profile = FALSE, 15 | env = character(), 16 | ... 17 | ) 18 | } 19 | \arguments{ 20 | \item{func}{Function object to call in the new R process. 21 | The function should be self-contained and only refer to 22 | other functions and use variables explicitly from other packages 23 | using the \code{::} notation. By default the environment of the function 24 | is set to \code{.GlobalEnv} before passing it to the child process. 25 | (See the \code{package} option if you want to keep the environment.) 26 | Because of this, it is good practice to create an anonymous 27 | function and pass that to \code{callr}, instead of passing 28 | a function object from a (base or other) package. In particular 29 | 30 | \if{html}{\out{
}}\preformatted{r(.libPaths) 31 | }\if{html}{\out{
}} 32 | 33 | does not work, because \code{.libPaths} is defined in a special 34 | environment, but 35 | 36 | \if{html}{\out{
}}\preformatted{r(function() .libPaths()) 37 | }\if{html}{\out{
}} 38 | 39 | works just fine.} 40 | 41 | \item{args}{Arguments to pass to the function. Must be a list.} 42 | 43 | \item{libpath}{The library path.} 44 | 45 | \item{repos}{The \code{repos} option. If \code{NULL}, then no 46 | \code{repos} option is set. This options is only used if 47 | \code{user_profile} or \code{system_profile} is set \code{FALSE}, 48 | as it is set using the system or the user profile.} 49 | 50 | \item{cmdargs}{Command line arguments to pass to the R process. 51 | Note that \code{c("-f", rscript)} is appended to this, \code{rscript} 52 | is the name of the script file to run. This contains a call to the 53 | supplied function and some error handling code.} 54 | 55 | \item{system_profile}{Whether to use the system profile file.} 56 | 57 | \item{user_profile}{Whether to use the user's profile file. 58 | If this is \code{"project"}, then only the profile from the working 59 | directory is used, but the \code{R_PROFILE_USER} environment variable 60 | and the user level profile are not. See also "Security considerations" 61 | below.} 62 | 63 | \item{env}{Environment variables to set for the child process.} 64 | 65 | \item{...}{Additional arguments are passed to \code{\link[=r]{r()}}.} 66 | } 67 | \description{ 68 | It tries to mimic a fresh R installation. In particular: 69 | \itemize{ 70 | \item No library path setting. 71 | \item No CRAN(-like) repository is set. 72 | \item The system and user profiles are not run. 73 | } 74 | } 75 | \section{Security considerations}{ 76 | 77 | 78 | \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of 79 | the local or user \code{.Rprofile}, in the session temporary 80 | directory. Avoid storing sensitive information such as passwords, in 81 | your environment file or your profile, otherwise this information will 82 | get scattered in various files, at least temporarily, until the 83 | subprocess finishes. You can use the keyring package to avoid passwords 84 | in plain files. 85 | } 86 | 87 | \examples{ 88 | \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 89 | # Compare to r() 90 | r(function() .libPaths()) 91 | r_vanilla(function() .libPaths()) 92 | 93 | r(function() getOption("repos")) 94 | r_vanilla(function() getOption("repos")) 95 | \dontshow{\}) # examplesIf} 96 | } 97 | \seealso{ 98 | Other callr functions: 99 | \code{\link{r}()}, 100 | \code{\link{r_copycat}()} 101 | } 102 | \concept{callr functions} 103 | -------------------------------------------------------------------------------- /man/rcmd_bg.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rcmd-bg.R 3 | \name{rcmd_bg} 4 | \alias{rcmd_bg} 5 | \title{Run an \verb{R CMD} command in the background} 6 | \usage{ 7 | rcmd_bg( 8 | cmd, 9 | cmdargs = character(), 10 | libpath = .libPaths(), 11 | stdout = "|", 12 | stderr = "|", 13 | poll_connection = TRUE, 14 | repos = default_repos(), 15 | system_profile = FALSE, 16 | user_profile = "project", 17 | env = rcmd_safe_env(), 18 | wd = ".", 19 | supervise = FALSE, 20 | ... 21 | ) 22 | } 23 | \arguments{ 24 | \item{cmd}{Command to run. See \code{R --help} from the command 25 | line for the various commands. In the current version of R (3.2.4) 26 | these are: \code{BATCH}, \code{COMPILE}, \code{SHLIB}, \code{INSTALL}, \code{REMOVE}, \code{build}, 27 | \code{check}, \code{LINK}, \code{Rprof}, \code{Rdconv}, \code{Rd2pdf}, \code{Rd2txt}, \code{Stangle}, 28 | \code{Sweave}, \code{Rdiff}, \code{config}, \code{javareconf}, \code{rtags}.} 29 | 30 | \item{cmdargs}{Command line arguments.} 31 | 32 | \item{libpath}{The library path.} 33 | 34 | \item{stdout}{Optionally a file name to send the standard output to.} 35 | 36 | \item{stderr}{Optionally a file name to send the standard error to. 37 | It may be the same as \code{stdout}, in which case standard error is 38 | redirected to standard output. It can also be the special string 39 | \code{"2>&1"}, in which case standard error will be redirected to standard 40 | output.} 41 | 42 | \item{poll_connection}{Whether to have a control connection to 43 | the process. This is used to transmit messages from the subprocess 44 | to the parent.} 45 | 46 | \item{repos}{The \code{repos} option. If \code{NULL}, then no 47 | \code{repos} option is set. This options is only used if 48 | \code{user_profile} or \code{system_profile} is set \code{FALSE}, 49 | as it is set using the system or the user profile.} 50 | 51 | \item{system_profile}{Whether to use the system profile file.} 52 | 53 | \item{user_profile}{Whether to use the user's profile file. 54 | If this is \code{"project"}, then only the profile from the working 55 | directory is used, but the \code{R_PROFILE_USER} environment variable 56 | and the user level profile are not. See also "Security considerations" 57 | below.} 58 | 59 | \item{env}{Environment variables to set for the child process.} 60 | 61 | \item{wd}{Working directory to use for running the command. Defaults 62 | to the current working directory.} 63 | 64 | \item{supervise}{Whether to register the process with a supervisor. If \code{TRUE}, 65 | the supervisor will ensure that the process is killed when the R process 66 | exits.} 67 | 68 | \item{...}{Extra arguments are passed to the \link[processx:process]{processx::process} 69 | constructor.} 70 | } 71 | \value{ 72 | It returns a \link{process} object. 73 | } 74 | \description{ 75 | The child process is started in the background, and the function 76 | return immediately. 77 | } 78 | \section{Security considerations}{ 79 | 80 | 81 | \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of 82 | the local or user \code{.Rprofile}, in the session temporary 83 | directory. Avoid storing sensitive information such as passwords, in 84 | your environment file or your profile, otherwise this information will 85 | get scattered in various files, at least temporarily, until the 86 | subprocess finishes. You can use the keyring package to avoid passwords 87 | in plain files. 88 | } 89 | 90 | \seealso{ 91 | Other R CMD commands: 92 | \code{\link{rcmd}()}, 93 | \code{\link{rcmd_copycat}()} 94 | } 95 | \concept{R CMD commands} 96 | -------------------------------------------------------------------------------- /man/rcmd_copycat.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rcmd.R 3 | \name{rcmd_copycat} 4 | \alias{rcmd_copycat} 5 | \title{Call and \verb{R CMD} command, while mimicking the current R session} 6 | \usage{ 7 | rcmd_copycat( 8 | cmd, 9 | cmdargs = character(), 10 | libpath = .libPaths(), 11 | repos = getOption("repos"), 12 | env = character(), 13 | ... 14 | ) 15 | } 16 | \arguments{ 17 | \item{cmd}{Command to run. See \code{R --help} from the command 18 | line for the various commands. In the current version of R (3.2.4) 19 | these are: \code{BATCH}, \code{COMPILE}, \code{SHLIB}, \code{INSTALL}, \code{REMOVE}, \code{build}, 20 | \code{check}, \code{LINK}, \code{Rprof}, \code{Rdconv}, \code{Rd2pdf}, \code{Rd2txt}, \code{Stangle}, 21 | \code{Sweave}, \code{Rdiff}, \code{config}, \code{javareconf}, \code{rtags}.} 22 | 23 | \item{cmdargs}{Command line arguments.} 24 | 25 | \item{libpath}{The library path.} 26 | 27 | \item{repos}{The \code{repos} option. If \code{NULL}, then no 28 | \code{repos} option is set. This options is only used if 29 | \code{user_profile} or \code{system_profile} is set \code{FALSE}, 30 | as it is set using the system or the user profile.} 31 | 32 | \item{env}{Environment variables to set for the child process.} 33 | 34 | \item{...}{Additional arguments are passed to \code{\link[=rcmd]{rcmd()}}.} 35 | } 36 | \description{ 37 | This function is similar to \code{\link[=rcmd]{rcmd()}}, but it has slightly different 38 | defaults: 39 | \itemize{ 40 | \item The \code{repos} options is unchanged. 41 | \item No extra environment variables are defined. 42 | } 43 | } 44 | \section{Security considerations}{ 45 | 46 | 47 | \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of 48 | the local or user \code{.Rprofile}, in the session temporary 49 | directory. Avoid storing sensitive information such as passwords, in 50 | your environment file or your profile, otherwise this information will 51 | get scattered in various files, at least temporarily, until the 52 | subprocess finishes. You can use the keyring package to avoid passwords 53 | in plain files. 54 | } 55 | 56 | \seealso{ 57 | Other R CMD commands: 58 | \code{\link{rcmd}()}, 59 | \code{\link{rcmd_bg}()} 60 | } 61 | \concept{R CMD commands} 62 | -------------------------------------------------------------------------------- /man/rcmd_process_options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options.R 3 | \name{rcmd_process_options} 4 | \alias{rcmd_process_options} 5 | \title{Create options for an \link{rcmd_process} object} 6 | \usage{ 7 | rcmd_process_options(...) 8 | } 9 | \arguments{ 10 | \item{...}{Options to override, named arguments.} 11 | } 12 | \value{ 13 | A list of options. 14 | 15 | \code{rcmd_process_options()} creates a set of options to initialize a new 16 | object from the \code{rcmd_process} class. Its arguments must be named, the 17 | names are used as option names. The options correspond to (some of) 18 | the arguments of the \code{\link[=rcmd]{rcmd()}} function. At least the \code{cmd} option must 19 | be specified, to select the \verb{R CMD} subcommand to run. Typically 20 | \code{cmdargs} is specified as well, to supply more arguments to \verb{R CMD}. 21 | } 22 | \description{ 23 | Create options for an \link{rcmd_process} object 24 | } 25 | \examples{ 26 | ## List all options and their default values: 27 | rcmd_process_options() 28 | } 29 | -------------------------------------------------------------------------------- /man/rcmd_safe_env.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rcmd.R 3 | \name{rcmd_safe_env} 4 | \alias{rcmd_safe_env} 5 | \title{\code{rcmd_safe_env} returns a set of environment variables that are 6 | more appropriate for \code{\link[=rcmd_safe]{rcmd_safe()}}. It is exported to allow manipulating 7 | these variables (e.g. add an extra one), before passing them to the 8 | \code{\link[=rcmd]{rcmd()}} functions.} 9 | \usage{ 10 | rcmd_safe_env() 11 | } 12 | \value{ 13 | A named character vector of environment variables. 14 | } 15 | \description{ 16 | It currently has the following variables: 17 | \itemize{ 18 | \item \code{CYGWIN="nodosfilewarning"}: On Windows, do not warn about MS-DOS 19 | style file names. 20 | \item \code{R_TESTS=""} This variable is set by \verb{R CMD check}, and makes the 21 | child R process load a startup file at startup, from the current 22 | working directory, that is assumed to be the \verb{/test} directory 23 | of the package being checked. If the current working directory is 24 | changed to something else (as it typically is by \code{testthat}, then R 25 | cannot start. Setting it to the empty string ensures that \code{callr} can 26 | be used from unit tests. 27 | \item \code{R_BROWSER="false"}: typically we don't want to start up a browser 28 | from the child R process. 29 | \item \code{R_PDFVIEWER="false"}: similarly for the PDF viewer. 30 | } 31 | } 32 | \details{ 33 | Note that \code{callr} also sets the \code{R_ENVIRON}, \code{R_ENVIRON_USER}, 34 | \code{R_PROFILE} and \code{R_PROFILE_USER} environment variables 35 | appropriately, unless these are set by the user in the \code{env} argument 36 | of the \code{r}, etc. calls. 37 | } 38 | -------------------------------------------------------------------------------- /man/reexports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/processx-forward.R 3 | \docType{import} 4 | \name{reexports} 5 | \alias{reexports} 6 | \alias{run} 7 | \alias{process} 8 | \alias{poll} 9 | \title{Objects exported from other packages} 10 | \keyword{internal} 11 | \description{ 12 | These objects are imported from other packages. Follow the links 13 | below to see their documentation. 14 | 15 | \describe{ 16 | \item{processx}{\code{\link[processx]{poll}}, \code{\link[processx]{process}}, \code{\link[processx]{run}}} 17 | }} 18 | 19 | -------------------------------------------------------------------------------- /man/roxygen/meta.R: -------------------------------------------------------------------------------- 1 | if (exists(".knitr_asciicast_process", envir = .GlobalEnv)) { 2 | rm(list = ".knitr_asciicast_process", envir = .GlobalEnv) 3 | } 4 | 5 | asciicast::init_knitr_engine( 6 | echo = TRUE, 7 | echo_input = FALSE, 8 | timeout = as.integer(Sys.getenv("ASCIICAST_TIMEOUT", 10)), 9 | startup = quote(options(cli.num_colors = 256)) 10 | ) 11 | 12 | knitr::opts_chunk$set( 13 | asciicast_knitr_output = "html", 14 | asciicast_include_style = FALSE, 15 | cache = TRUE, 16 | cache.path = file.path(getwd(), "man/_cache/"), 17 | fig.path = file.path(getwd(), "man/figures"), 18 | error = TRUE 19 | ) 20 | 21 | list( 22 | markdown = TRUE, 23 | restrict_image_formats = TRUE 24 | ) 25 | -------------------------------------------------------------------------------- /man/rscript.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rscript.R 3 | \name{rscript} 4 | \alias{rscript} 5 | \title{Run an R script} 6 | \usage{ 7 | rscript( 8 | script, 9 | cmdargs = character(), 10 | libpath = .libPaths(), 11 | repos = default_repos(), 12 | stdout = NULL, 13 | stderr = NULL, 14 | poll_connection = TRUE, 15 | echo = FALSE, 16 | show = TRUE, 17 | callback = NULL, 18 | block_callback = NULL, 19 | spinner = FALSE, 20 | system_profile = FALSE, 21 | user_profile = "project", 22 | env = rcmd_safe_env(), 23 | timeout = Inf, 24 | wd = ".", 25 | fail_on_status = TRUE, 26 | color = TRUE, 27 | ... 28 | ) 29 | } 30 | \arguments{ 31 | \item{script}{Path of the script to run.} 32 | 33 | \item{cmdargs}{Command line arguments.} 34 | 35 | \item{libpath}{The library path.} 36 | 37 | \item{repos}{The \code{repos} option. If \code{NULL}, then no 38 | \code{repos} option is set. This options is only used if 39 | \code{user_profile} or \code{system_profile} is set \code{FALSE}, 40 | as it is set using the system or the user profile.} 41 | 42 | \item{stdout}{Optionally a file name to send the standard output to.} 43 | 44 | \item{stderr}{Optionally a file name to send the standard error to. 45 | It may be the same as \code{stdout}, in which case standard error is 46 | redirected to standard output. It can also be the special string 47 | \code{"2>&1"}, in which case standard error will be redirected to standard 48 | output.} 49 | 50 | \item{poll_connection}{Whether to have a control connection to 51 | the process. This is used to transmit messages from the subprocess 52 | to the parent.} 53 | 54 | \item{echo}{Whether to echo the complete command run by \code{rcmd}.} 55 | 56 | \item{show}{Logical, whether to show the standard output on the screen 57 | while the child process is running. Note that this is independent 58 | of the \code{stdout} and \code{stderr} arguments. The standard 59 | error is not shown currently.} 60 | 61 | \item{callback}{A function to call for each line of the standard 62 | output and standard error from the child process. It works together 63 | with the \code{show} option; i.e. if \code{show = TRUE}, and a 64 | callback is provided, then the output is shown of the screen, and the 65 | callback is also called.} 66 | 67 | \item{block_callback}{A function to call for each block of the standard 68 | output and standard error. This callback is not line oriented, i.e. 69 | multiple lines or half a line can be passed to the callback.} 70 | 71 | \item{spinner}{Whether to show a calming spinner on the screen while 72 | the child R session is running. By default it is shown if 73 | \code{show = TRUE} and the R session is interactive.} 74 | 75 | \item{system_profile}{Whether to use the system profile file.} 76 | 77 | \item{user_profile}{Whether to use the user's profile file. 78 | If this is \code{"project"}, then only the profile from the working 79 | directory is used, but the \code{R_PROFILE_USER} environment variable 80 | and the user level profile are not. See also "Security considerations" 81 | below.} 82 | 83 | \item{env}{Environment variables to set for the child process.} 84 | 85 | \item{timeout}{Timeout for the function call to finish. It can be a 86 | \link[base:difftime]{base::difftime} object, or a real number, meaning seconds. 87 | If the process does not finish before the timeout period expires, 88 | then a \code{system_command_timeout_error} error is thrown. \code{Inf} 89 | means no timeout.} 90 | 91 | \item{wd}{Working directory to use for running the command. Defaults 92 | to the current working directory.} 93 | 94 | \item{fail_on_status}{Whether to throw an R error if the command returns 95 | with a non-zero status code. By default no error is thrown.} 96 | 97 | \item{color}{Whether to use terminal colors in the child process, 98 | assuming they are active in the parent process.} 99 | 100 | \item{...}{Extra arguments are passed to \code{\link[processx:run]{processx::run()}}.} 101 | } 102 | \description{ 103 | It uses the \code{Rscript} program corresponding to the current R version, 104 | to run the script. It streams \code{stdout} and \code{stderr} of the process. 105 | } 106 | \section{Security considerations}{ 107 | 108 | 109 | \code{callr} makes a copy of the user's \code{.Renviron} file and potentially of 110 | the local or user \code{.Rprofile}, in the session temporary 111 | directory. Avoid storing sensitive information such as passwords, in 112 | your environment file or your profile, otherwise this information will 113 | get scattered in various files, at least temporarily, until the 114 | subprocess finishes. You can use the keyring package to avoid passwords 115 | in plain files. 116 | } 117 | 118 | -------------------------------------------------------------------------------- /man/rscript_process_options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options.R 3 | \name{rscript_process_options} 4 | \alias{rscript_process_options} 5 | \title{Create options for an \link{rscript_process} object} 6 | \usage{ 7 | rscript_process_options(...) 8 | } 9 | \arguments{ 10 | \item{...}{Options to override, named arguments.} 11 | } 12 | \value{ 13 | A list of options. 14 | 15 | \code{rscript_process_options()} creates a set of options to initialize a new 16 | object from the \code{rscript_process} class. Its arguments must be named, 17 | the names are used as option names. The options correspond to (some of) 18 | the arguments of the \code{\link[=rscript]{rscript()}} function. At least the \code{script} option 19 | must be specified, the script file to run. 20 | } 21 | \description{ 22 | Create options for an \link{rscript_process} object 23 | } 24 | \examples{ 25 | ## List all options and their default values: 26 | rscript_process_options() 27 | } 28 | -------------------------------------------------------------------------------- /man/supported_archs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{supported_archs} 4 | \alias{supported_archs} 5 | \title{Find supported sub-architectures for the current R installation} 6 | \usage{ 7 | supported_archs() 8 | } 9 | \value{ 10 | Character vector of supported architectures. If the 11 | current R build is not a multi-architecture build, then an empty 12 | string scalar is returned. 13 | } 14 | \description{ 15 | This function uses a heuristic, which might fail, so its result 16 | should be taken as a best guess. 17 | } 18 | \examples{ 19 | supported_archs() 20 | } 21 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(callr) 2 | 3 | if (Sys.getenv("NOT_CRAN") == "true") { 4 | if (callr:::is_false_check_env_var("_R_CHECK_FORCE_SUGGESTS_")) { 5 | if (requireNamespace("testthat", quietly = TRUE)) { 6 | library(testthat) 7 | test_check("callr") 8 | } 9 | } else { 10 | library(testthat) 11 | test_check("callr") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/eval.md: -------------------------------------------------------------------------------- 1 | # errors are printed on stderr 2 | 3 | Code 4 | r(f, stdout = out <- tempfile(), stderr = err <- tempfile()) 5 | Condition 6 | Error: 7 | ! in callr subprocess. 8 | Caused by error: 9 | ! send to stderr 2 10 | 11 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/load-client.md: -------------------------------------------------------------------------------- 1 | # errors 2 | 3 | Code 4 | load_client_lib() 5 | Condition 6 | Error in `sofile_in_processx()`: 7 | ! Cannot find client file 8 | 9 | # errors 2 10 | 11 | Code 12 | load_client_lib(sofile) 13 | Condition 14 | Error in `dyn.load()`: 15 | ! after ooops 16 | 17 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/options.md: -------------------------------------------------------------------------------- 1 | # error for unknown options 2 | 3 | Code 4 | r_process_options(func = function() { }, foo = "bar") 5 | Condition 6 | Error: 7 | ! Unknown optioncharacter(0):'foo' 8 | 9 | --- 10 | 11 | Code 12 | r_process_options(func = function() { }, foo = "bar", bar = "foo") 13 | Condition 14 | Error: 15 | ! Unknown options:'foo' and 'bar' 16 | 17 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/r-bg.md: -------------------------------------------------------------------------------- 1 | # r_bg can be killed 2 | 3 | Code 4 | x$get_result() 5 | Condition 6 | Error: 7 | ! callr subprocess failed: could not start R, exited with non-zero status, has crashed or was killed 8 | 9 | # r_bg can get the error back 10 | 11 | Code 12 | x$get_result() 13 | Condition 14 | Error: 15 | ! in callr subprocess. 16 | Caused by error: 17 | ! non-numeric argument to binary operator 18 | 19 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/r-session.md: -------------------------------------------------------------------------------- 1 | # run throws 2 | 3 | Code 4 | rs$run(function() stop("foobar")) 5 | Condition 6 | Error: 7 | ! in callr subprocess. 8 | Caused by error: 9 | ! foobar 10 | 11 | # traceback 12 | 13 | Code 14 | rs$run(do) 15 | Condition 16 | Error: 17 | ! in callr subprocess. 18 | Caused by error: 19 | ! oops 20 | 21 | # error in the load hook 22 | 23 | Code 24 | local({ 25 | rs <- r_session$new(opts) 26 | on.exit(rs$kill(), add = TRUE) 27 | }) 28 | Condition 29 | Error: 30 | ! callr subprocess failed: Failed to start R session 31 | 32 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/D1: -------------------------------------------------------------------------------- 1 | Package: Foobar 2 | Title: Foo Foo Bar Bar 3 | Version: 1.0.1 4 | Authors@R: c( 5 | person("Gábor", "Csárdi", role = c("aut", "cre", "cph"), 6 | email = "csardi.gabor@gmail.com", 7 | comment = c(ORCID = "0000-0001-7098-9676"))) 8 | Description: Enim laborum enim non aliquip tempor voluptate consectetur 9 | reprehenderit pariatur velit laborum sit culpa cillum. Tempor ullamco 10 | tempor aliqua aliquip commodo non cupidatat non culpa. Dolor fugiat 11 | aute et consectetur in. 12 | License: MIT + file LICENSE 13 | LazyData: true 14 | Encoding: UTF-8 15 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/csomag/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: csomag 2 | Title: Test Package for 'callr' 3 | Version: 0.0.0.9999 4 | Authors@R: c( 5 | person("Gábor", "Csárdi", role = c("aut", "cre", "cph"), 6 | email = "csardi.gabor@gmail.com", 7 | comment = c(ORCID = "0000-0001-7098-9676"))) 8 | Description: Test package for 'callr'. 9 | License: MIT + file LICENSE 10 | LazyData: true 11 | Encoding: UTF-8 12 | Language: en-US 13 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/csomag/NAMESPACE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/callr/565a5090975b5f3c5cca24304bd46f0f7af2ae0b/tests/testthat/fixtures/csomag/NAMESPACE -------------------------------------------------------------------------------- /tests/testthat/fixtures/csomag/R/libpath.R: -------------------------------------------------------------------------------- 1 | file_from_env <- function(var) { 2 | if (file.exists(f <- Sys.getenv(var))) readLines(f) 3 | } 4 | 5 | if (Sys.getenv("CALLR_DUMP_HERE") != "") { 6 | saveRDS( 7 | list( 8 | env = Sys.getenv(), 9 | libpaths = .libPaths(), 10 | search = search(), 11 | R_ENVIRON = file_from_env("R_ENVIRON"), 12 | R_ENVIRON_USER = file_from_env("R_ENVIRON_USER"), 13 | R_PROFILE = file_from_env("R_PROFILE"), 14 | R_PROFILE_USER = file_from_env("R_PROFILE_USER") 15 | ), 16 | file = Sys.getenv("CALLR_DUMP_HERE") 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/script.R: -------------------------------------------------------------------------------- 1 | cat("stdout\n") 2 | message("stderr") 3 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/script2.R: -------------------------------------------------------------------------------- 1 | cat("out1") 2 | message("err1", appendLF = FALSE) 3 | cat("out2") 4 | message("err2") 5 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/simple.txt: -------------------------------------------------------------------------------- 1 | simple text file 2 | -------------------------------------------------------------------------------- /tests/testthat/helper-mock.R: -------------------------------------------------------------------------------- 1 | fake <- local({ 2 | fake_through_tree <- function(tree, what, how) { 3 | for (d in tree) { 4 | for (parent in d) { 5 | parent_env <- parent[["parent_env"]] 6 | func_dict <- parent[["funcs"]] 7 | for (func_name in ls(func_dict, all.names = TRUE)) { 8 | func <- func_dict[[func_name]] 9 | func_env <- new.env(parent = environment(func)) 10 | 11 | what <- override_seperators(what, func_env) 12 | where_name <- override_seperators(func_name, parent_env) 13 | 14 | if (!is.function(how)) { 15 | assign(what, function(...) how, func_env) 16 | } else { 17 | assign(what, how, func_env) 18 | } 19 | 20 | environment(func) <- func_env 21 | locked <- exists(where_name, parent_env, inherits = FALSE) && 22 | bindingIsLocked(where_name, parent_env) 23 | if (locked) { 24 | baseenv()$unlockBinding(where_name, parent_env) 25 | } 26 | assign(where_name, func, parent_env) 27 | if (locked) { 28 | lockBinding(where_name, parent_env) 29 | } 30 | } 31 | } 32 | } 33 | } 34 | 35 | override_seperators <- function(name, env) { 36 | mangled_name <- NULL 37 | for (sep in c("::", "$")) { 38 | if (grepl(sep, name, fixed = TRUE)) { 39 | elements <- strsplit(name, sep, fixed = TRUE) 40 | mangled_name <- paste( 41 | elements[[1L]][1L], 42 | elements[[1L]][2L], 43 | sep = "XXX" 44 | ) 45 | 46 | stub_list <- c(mangled_name) 47 | if ("stub_list" %in% names(attributes(get(sep, env)))) { 48 | stub_list <- c(stub_list, attributes(get(sep, env))[["stub_list"]]) 49 | } 50 | 51 | create_new_name <- create_create_new_name_function( 52 | stub_list, 53 | env, 54 | sep 55 | ) 56 | assign(sep, create_new_name, env) 57 | } 58 | } 59 | mangled_name %||% name 60 | } 61 | 62 | backtick <- function(x) { 63 | encodeString(x, quote = "`", na.encode = FALSE) 64 | } 65 | 66 | create_create_new_name_function <- function(stub_list, env, sep) { 67 | force(stub_list) 68 | force(env) 69 | force(sep) 70 | 71 | create_new_name <- function(pkg, func) { 72 | pkg_name <- deparse(substitute(pkg)) 73 | func_name <- deparse(substitute(func)) 74 | for (stub in stub_list) { 75 | if (paste(pkg_name, func_name, sep = "XXX") == stub) { 76 | return(eval(parse(text = backtick(stub)), env)) 77 | } 78 | } 79 | 80 | # used to avoid recursively calling the replacement function 81 | eval_env <- new.env(parent = parent.frame()) 82 | assign(sep, eval(parse(text = paste0("`", sep, "`"))), eval_env) 83 | 84 | code <- paste(pkg_name, backtick(func_name), sep = sep) 85 | return(eval(parse(text = code), eval_env)) 86 | } 87 | attributes(create_new_name) <- list(stub_list = stub_list) 88 | create_new_name 89 | } 90 | 91 | build_function_tree <- function(test_env, where, where_name) { 92 | func_dict <- new.env() 93 | func_dict[[where_name]] <- where 94 | tree <- list( 95 | list( 96 | list(parent_env = test_env, funcs = func_dict) 97 | ) 98 | ) 99 | 100 | tree 101 | } 102 | 103 | fake <- function(where, what, how) { 104 | where_name <- deparse(substitute(where)) 105 | stopifnot(is.character(what), length(what) == 1) 106 | test_env <- parent.frame() 107 | tree <- build_function_tree(test_env, where, where_name) 108 | fake_through_tree(tree, what, how) 109 | } 110 | }) 111 | -------------------------------------------------------------------------------- /tests/testthat/test-archs.R: -------------------------------------------------------------------------------- 1 | test_that("r() to the other arch", { 2 | skip_on_cran() 3 | archs <- supported_archs() 4 | if (length(archs) < 1) return(expect_true(TRUE)) 5 | ret <- unlist(lapply( 6 | archs, 7 | function(a) r(function() .Platform$r_arch, arch = a) 8 | )) 9 | expect_equal(ret, archs) 10 | }) 11 | 12 | test_that("r_bg() to the other arch", { 13 | skip_on_cran() 14 | archs <- supported_archs() 15 | if (length(archs) < 1) return(expect_true(TRUE)) 16 | procs <- lapply(archs, function(a) { 17 | r_bg(function() .Platform$r_arch, arch = a) 18 | }) 19 | on.exit(lapply(procs, function(p) p$kill()), add = TRUE) 20 | for (p in procs) p$wait(3000) 21 | for (p in procs) expect_false(p$is_alive()) 22 | res <- unlist(lapply(procs, function(p) p$get_result())) 23 | expect_equal(res, archs) 24 | }) 25 | 26 | test_that("r_process to the other arch", { 27 | skip_on_cran() 28 | archs <- supported_archs() 29 | if (length(archs) < 1) return(expect_true(TRUE)) 30 | procs <- lapply(archs, function(a) { 31 | opts <- r_process_options( 32 | func = function() .Platform$r_arch, 33 | arch = a 34 | ) 35 | r_process$new(opts) 36 | }) 37 | on.exit(lapply(procs, function(p) p$kill()), add = TRUE) 38 | for (p in procs) p$wait(3000) 39 | for (p in procs) expect_false(p$is_alive()) 40 | res <- unlist(lapply(procs, function(p) p$get_result())) 41 | expect_equal(res, archs) 42 | }) 43 | 44 | test_that("r_session to the other arch", { 45 | skip_on_cran() 46 | archs <- supported_archs() 47 | if (length(archs) < 1) return(expect_true(TRUE)) 48 | ret <- unlist(lapply(archs, function(a) { 49 | opts <- r_session_options(arch = a) 50 | rs <- r_session$new(opts) 51 | on.exit(rs$close(), add = TRUE) 52 | rs$run(function() .Platform$r_arch) 53 | })) 54 | expect_equal(ret, archs) 55 | }) 56 | -------------------------------------------------------------------------------- /tests/testthat/test-bugs.R: -------------------------------------------------------------------------------- 1 | test_that("repos is a list, #82", { 2 | skip_if_not_installed("withr") 3 | expect_true(withr::with_options( 4 | list(repos = list(CRAN = "https://cloud.r-project.org")), 5 | callr::r(function() inherits(getOption("repos"), "list")) 6 | )) 7 | }) 8 | -------------------------------------------------------------------------------- /tests/testthat/test-callback.R: -------------------------------------------------------------------------------- 1 | test_that("show option works", { 2 | expect_output( 3 | r(function() print("hello"), show = TRUE), 4 | "hello" 5 | ) 6 | gc() 7 | }) 8 | 9 | test_that("callbacks work", { 10 | out <- NULL 11 | r(function() cat("hello\n"), callback = function(x) out <<- x) 12 | expect_equal(out, "hello") 13 | gc() 14 | }) 15 | 16 | test_that("show and callbacks at the same time", { 17 | out <- NULL 18 | 19 | expect_output( 20 | r( 21 | function() cat("hello\n"), 22 | show = TRUE, 23 | callback = function(x) out <<- x 24 | ), 25 | "hello" 26 | ) 27 | 28 | expect_equal(out, "hello") 29 | gc() 30 | }) 31 | -------------------------------------------------------------------------------- /tests/testthat/test-clean-subprocess.R: -------------------------------------------------------------------------------- 1 | test_that("r() does not load anything", { 2 | skip_in_covr() 3 | skip_if_not_installed("withr") 4 | pkgs <- withr::with_envvar( 5 | clean_envvars(), 6 | r(without_env(function() loadedNamespaces())) 7 | ) 8 | if (length(pkgs) > 1) print(pkgs) 9 | ## Some R versions still load compiler... 10 | expect_true(all(pkgs %in% c("base", "compiler"))) 11 | }) 12 | 13 | test_that("r_bg() does not load anything", { 14 | skip_in_covr() 15 | skip_if_not_installed("withr") 16 | p <- withr::with_envvar( 17 | clean_envvars(), 18 | r_bg(without_env(function() loadedNamespaces())) 19 | ) 20 | on.exit(p$kill(), add = TRUE) 21 | p$wait(3000) 22 | pkgs <- p$get_result() 23 | if (length(pkgs) > 1) print(pkgs) 24 | ## Some R versions still load compiler... 25 | expect_true(all(pkgs %in% c("base", "compiler"))) 26 | }) 27 | 28 | test_that("r_session does not load anything", { 29 | skip_in_covr() 30 | skip_if_not_installed("withr") 31 | rs <- withr::with_envvar(clean_envvars(), r_session$new()) 32 | on.exit(rs$close(), add = TRUE) 33 | pkgs <- rs$run(without_env(function() loadedNamespaces())) 34 | if (length(pkgs) > 1) print(pkgs) 35 | ## Some R versions still load compiler... 36 | expect_true(all(pkgs %in% c("base", "compiler"))) 37 | gc() 38 | }) 39 | 40 | test_that("r() does not create objects in global env", { 41 | vars <- r(function() ls(.GlobalEnv)) 42 | expect_identical(vars, character()) 43 | }) 44 | 45 | test_that("r_bg() does not create objects in global env", { 46 | p <- r_bg(function() ls(.GlobalEnv)) 47 | on.exit(p$kill(), add = TRUE) 48 | p$wait(3000) 49 | vars <- p$get_result() 50 | expect_identical(vars, character()) 51 | }) 52 | 53 | test_that("r_session does not create objects in global env", { 54 | rs <- r_session$new() 55 | on.exit(rs$close(), add = TRUE) 56 | vars <- rs$run(function() ls(.GlobalEnv)) 57 | expect_identical(vars, character()) 58 | rs$close() 59 | gc() 60 | }) 61 | -------------------------------------------------------------------------------- /tests/testthat/test-function-env.R: -------------------------------------------------------------------------------- 1 | test_that("r()", { 2 | # not passed by default 3 | error <- tryCatch( 4 | r(callr:::remove_source, list(r)), 5 | error = function(e) e 6 | ) 7 | expect_match(conditionMessage(error), "could not find function") 8 | 9 | # keep it 10 | out <- r(callr:::remove_source, list(r), package = TRUE) 11 | expect_true(is.function(out)) 12 | expect_equal(environmentName(environment(out)), "callr") 13 | 14 | # set it explicitly to package env 15 | out <- r(function(x) remove_source(x), list(r), package = "callr") 16 | expect_true(is.function(out)) 17 | expect_equal(environmentName(environment(out)), "callr") 18 | }) 19 | 20 | test_that("r_bg()", { 21 | # fails in covr :( 22 | testthat::skip_on_covr() 23 | # not passed by default 24 | p1 <- r_bg(callr:::remove_source, list(r)) 25 | on.exit(p1$kill(), add = TRUE) 26 | p2 <- r_bg(callr:::remove_source, list(r), package = TRUE) 27 | on.exit(p2$kill(), add = TRUE) 28 | p3 <- r_bg(function(x) remove_source(x), list(r), package = "callr") 29 | on.exit(p3$kill(), add = TRUE) 30 | 31 | p1$wait(3000) 32 | p1$kill() 33 | error <- tryCatch(p1$get_result(), error = function(e) e) 34 | expect_match(conditionMessage(error), "could not find function") 35 | 36 | p2$wait(3000) 37 | p2$kill() 38 | out <- p2$get_result() 39 | expect_true(is.function(out)) 40 | expect_equal(environmentName(environment(out)), "callr") 41 | 42 | p3$wait(3000) 43 | p3$kill() 44 | out <- p3$get_result() 45 | expect_true(is.function(out)) 46 | expect_equal(environmentName(environment(out)), "callr") 47 | }) 48 | 49 | test_that("r_session", { 50 | rs <- r_session$new() 51 | on.exit(rs$kill(), add = TRUE) 52 | 53 | error <- tryCatch( 54 | rs$run(callr:::remove_source, list(r)), 55 | error = function(e) e 56 | ) 57 | expect_match(conditionMessage(error), "could not find function") 58 | 59 | # keep it 60 | out <- rs$run(callr:::remove_source, list(r), package = TRUE) 61 | expect_true(is.function(out)) 62 | expect_equal(environmentName(environment(out)), "callr") 63 | 64 | # set it explicitly to package env 65 | out <- rs$run(function(x) remove_source(x), list(r), package = "callr") 66 | expect_true(is.function(out)) 67 | expect_equal(environmentName(environment(out)), "callr") 68 | }) 69 | -------------------------------------------------------------------------------- /tests/testthat/test-hook.R: -------------------------------------------------------------------------------- 1 | test_that("set options with hook", { 2 | add_hook( 3 | test_hook = function(options) { 4 | within(options, { 5 | env["TEST_HOOK"] <- "hello" 6 | cmdargs <- c(cmdargs, "extra_arg") 7 | }) 8 | } 9 | ) 10 | 11 | options <- r_process_options( 12 | func = function() { 13 | list(env = Sys.getenv("TEST_HOOK"), args = commandArgs()) 14 | } 15 | ) 16 | x <- r_process$new(options) 17 | x$wait() 18 | res <- x$get_result() 19 | expect_equal(res$env, "hello") 20 | expect_length(grep("^extra_arg$", res$args), 1L) 21 | 22 | # Remove hook 23 | add_hook(test_hook = NULL) 24 | 25 | x <- r_process$new(options) 26 | x$wait() 27 | res <- x$get_result() 28 | expect_equal(res$env, "") 29 | expect_length(grep("^extra_arg$", res$args), 0L) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/testthat/test-load-client.R: -------------------------------------------------------------------------------- 1 | test_that("load_client_lib", { 2 | lib <- load_client_lib() 3 | 4 | funcs <- c("base64_decode", "set_stderr", "write_fd") 5 | expect_true( 6 | all(funcs %in% names(lib)) 7 | ) 8 | 9 | lib <- load_client_lib(pxdir = find.package("processx")) 10 | expect_true( 11 | all(funcs %in% names(lib)) 12 | ) 13 | }) 14 | 15 | test_that("errors", { 16 | fake(load_client_lib, "system.file", "") 17 | expect_snapshot(error = TRUE, load_client_lib()) 18 | }) 19 | 20 | test_that("errors 2", { 21 | sofile <- system.file( 22 | "libs", 23 | .Platform$r_arch, 24 | paste0("client", .Platform$dynlib.ext), 25 | package = "processx" 26 | ) 27 | fake(load_client_lib, "dyn.load", function(...) stop("ooops")) 28 | expect_snapshot(error = TRUE, load_client_lib(sofile)) 29 | }) 30 | 31 | test_that("base64", { 32 | lib <- load_client_lib() 33 | enc <- lib$base64_encode(charToRaw("hello world!")) 34 | expect_equal(lib$base64_decode(enc), charToRaw("hello world!")) 35 | }) 36 | 37 | test_that("write_fd", { 38 | do <- function() { 39 | lib <- asNamespace("callr")$load_client_lib() 40 | f1 <- tempfile() 41 | c1 <- processx::conn_create_file(f1, write = TRUE) 42 | lib$write_fd(processx::conn_get_fileno(c1), "hello world!\n") 43 | close(c1) 44 | readLines(f1) 45 | } 46 | 47 | ret <- callr::r(do) 48 | expect_equal(ret, "hello world!") 49 | }) 50 | 51 | test_that("set_stdout, set_stderr", { 52 | # TODO why does this not work on windows, if `set_std{out,err}_file` work? 53 | # Also, `set_std{out,err}` work in `r_session`. 54 | # But this test crashes at the `set_stdout()` call. 55 | skip_on_os("windows") 56 | 57 | do <- function() { 58 | lib <- asNamespace("callr")$load_client_lib() 59 | f1 <- tempfile() 60 | f2 <- tempfile() 61 | c1 <- processx::conn_create_file(f1, write = TRUE) 62 | c2 <- processx::conn_create_file(f2, write = TRUE) 63 | lib$set_stdout(processx::conn_get_fileno(c1)) 64 | lib$set_stderr(processx::conn_get_fileno(c2)) 65 | cat("this is output") 66 | message("this is error") 67 | close(c1) 68 | close(c2) 69 | c(readLines(f1), readLines(f2)) 70 | } 71 | 72 | ret <- callr::r(do) 73 | expect_equal(ret, c("this is output", "this is error")) 74 | }) 75 | 76 | test_that("set_stdout_file, set_setderr_file", { 77 | do <- function() { 78 | lib <- asNamespace("callr")$load_client_lib() 79 | f1 <- tempfile() 80 | f2 <- tempfile() 81 | lib$set_stdout_file(f1) 82 | lib$set_stderr_file(f2) 83 | cat("this is output") 84 | message("this is error") 85 | c(readLines(f1), readLines(f2)) 86 | } 87 | 88 | ret <- callr::r(do) 89 | expect_equal(ret, c("this is output", "this is error")) 90 | }) 91 | 92 | test_that("init function of client lib is run", { 93 | pxlib <- r(function() { 94 | as.environment("tools:callr")$`__callr_data__`$pxlib 95 | }) 96 | 97 | # File name should be `client.SOEXT` so that R can match the init 98 | # function from the name 99 | expect_true(grepl("^client\\.", basename(pxlib$.path))) 100 | 101 | # In case R removes the `dynamicLookup` field 102 | skip_on_cran() 103 | 104 | # Should be `FALSE` since processx disables dynamic lookup in the 105 | # init function 106 | expect_false(unclass(pxlib$.lib)$dynamicLookup) 107 | }) 108 | 109 | test_that("CALLR_NO_TEMP_DLLS", { 110 | skip_on_cran() 111 | if (.Platform$OS.type != "windows") skip("Windows only") 112 | 113 | # If not set, then it should come from the temporary location 114 | withr::local_envvar(CALLR_NO_TEMP_DLLS = NA_character_) 115 | dlls <- callr::r(function() ps::ps_shared_libs())$path 116 | px <- grep("processx.*client.dll", dlls) 117 | cr <- grep("callr.*client.dll", dlls) 118 | expect_true(length(px) == 0) 119 | expect_true(length(cr) >= 1) 120 | 121 | # If set, then it should come from processx 122 | withr::local_envvar(CALLR_NO_TEMP_DLLS = "true") 123 | dlls <- callr::r(function() ps::ps_shared_libs())$path 124 | px <- grep("processx.*client.dll", dlls) 125 | cr <- grep("callr.*client.dll", dlls) 126 | expect_true(length(px) >= 1) 127 | expect_true(length(cr) == 0) 128 | }) 129 | -------------------------------------------------------------------------------- /tests/testthat/test-messages.R: -------------------------------------------------------------------------------- 1 | test_that("messages in callr::r do not crash session", { 2 | skip_if_not_installed("cli") 3 | ret <- r(function() { 4 | cli::cli_text("fooobar") 5 | 1 + 1 6 | }) 7 | expect_identical(ret, 2) 8 | gc() 9 | }) 10 | 11 | test_that("messages in callr::r_bg do not crash session", { 12 | skip_in_covr() # TODO: what wrong with this on Windows? 13 | skip_on_cran() 14 | skip_if_not_installed("cli") 15 | 16 | rx <- r_bg(function() { 17 | cli::cli_text("fooobar") 18 | 1 + 1 19 | }) 20 | rx$wait(5000) 21 | rx$kill() 22 | expect_equal(rx$get_exit_status(), 0) 23 | 24 | expect_equal(rx$get_result(), 2) 25 | processx::processx_conn_close(rx$get_output_connection()) 26 | processx::processx_conn_close(rx$get_error_connection()) 27 | gc() 28 | }) 29 | -------------------------------------------------------------------------------- /tests/testthat/test-options.R: -------------------------------------------------------------------------------- 1 | test_that("error for unknown options", { 2 | expect_snapshot(error = TRUE, { 3 | r_process_options( 4 | func = function() { 5 | }, 6 | foo = "bar" 7 | ) 8 | }) 9 | 10 | expect_snapshot(error = TRUE, { 11 | r_process_options( 12 | func = function() { 13 | }, 14 | foo = "bar", 15 | bar = "foo" 16 | ) 17 | }) 18 | 19 | gc() 20 | }) 21 | -------------------------------------------------------------------------------- /tests/testthat/test-package.R: -------------------------------------------------------------------------------- 1 | test_that("all figures in man/figures are needed", { 2 | skip_on_cran() 3 | skip_on_covr() 4 | pkg_dir <- test_package_root() 5 | figs <- dir(file.path(pkg_dir, "man", "figures")) 6 | readme <- file.path(pkg_dir, "README.md") 7 | readme_figs <- grep("man/figures/", readLines(readme), value = TRUE) 8 | readme_figs <- sub("^.*man/figures/(.*[.]svg).*$", "\\1", readme_figs) 9 | expect_equal( 10 | sort(figs), 11 | sort(readme_figs) 12 | ) 13 | }) 14 | -------------------------------------------------------------------------------- /tests/testthat/test-presets.R: -------------------------------------------------------------------------------- 1 | test_that("r", { 2 | skip_if_not_installed("withr") 3 | 4 | withr::with_options( 5 | list(repos = "foobar"), 6 | expect_equal( 7 | r_copycat( 8 | function() getOption("repos"), 9 | user_profile = FALSE, 10 | system_profile = FALSE 11 | ), 12 | "foobar" 13 | ) 14 | ) 15 | gc() 16 | }) 17 | 18 | ## Need to supply libpath for covr... 19 | test_that("r_vanilla", { 20 | expect_equal( 21 | r_vanilla(function() getOption("repos"), libpath = .libPaths()), 22 | c(CRAN = "@CRAN@") 23 | ) 24 | gc() 25 | }) 26 | 27 | test_that("r_safe", { 28 | expect_equal( 29 | r_safe(function() Sys.getenv("R_TESTS")), 30 | "" 31 | ) 32 | gc() 33 | }) 34 | 35 | ## https://github.com/r-lib/callr/issues/66 36 | test_that("names of getOption('repos') are preserved", { 37 | skip_if_not_installed("withr") 38 | repos <- withr::with_options( 39 | list(repos = c(foo = "bar")), 40 | callr::r(function() getOption("repos")) 41 | ) 42 | expect_false(is.null(names(repos))) 43 | expect_identical("foo", names(repos)[[1]]) 44 | gc() 45 | }) 46 | -------------------------------------------------------------------------------- /tests/testthat/test-quit.R: -------------------------------------------------------------------------------- 1 | test_that("quit() in the function", { 2 | x <- r(function() quit()) 3 | expect_null(x) 4 | 5 | expect_error( 6 | r(function() quit(status = 2)), 7 | "non-zero status", 8 | class = "callr_status_error" 9 | ) 10 | gc() 11 | }) 12 | 13 | test_that("quit() in bg process", { 14 | p1 <- r_bg(function() quit()) 15 | on.exit(p1$kill()) 16 | p1$wait() 17 | expect_null(p1$get_result()) 18 | 19 | close(p1$get_output_connection()) 20 | close(p1$get_error_connection()) 21 | 22 | p2 <- r_bg(function() quit(status = 2)) 23 | on.exit(p2$kill(), add = TRUE) 24 | p2$wait() 25 | expect_error( 26 | p2$get_result(), 27 | "non-zero status", 28 | class = "callr_status_error" 29 | ) 30 | 31 | close(p2$get_output_connection()) 32 | close(p2$get_error_connection()) 33 | }) 34 | -------------------------------------------------------------------------------- /tests/testthat/test-r-bg.R: -------------------------------------------------------------------------------- 1 | test_that("r_bg runs", { 2 | x <- r_bg(function() 1 + 1) 3 | x$wait() 4 | expect_identical(x$get_result(), 2) 5 | rm(x) 6 | gc() 7 | }) 8 | 9 | test_that("r_bg takes arguments", { 10 | x <- r_bg(function(x) x + 10, args = list(32)) 11 | x$wait() 12 | expect_identical(x$get_result(), 42) 13 | rm(x) 14 | gc() 15 | }) 16 | 17 | test_that("r_bg can be killed", { 18 | x <- r_bg(function() Sys.sleep(2)) 19 | x$kill() 20 | expect_false(x$is_alive()) 21 | expect_snapshot(error = TRUE, x$get_result()) 22 | rm(x) 23 | gc() 24 | }) 25 | 26 | test_that("r_bg can get the error back", { 27 | x <- r_bg(function() 1 + "A", error = "error") 28 | x$wait() 29 | expect_snapshot(error = TRUE, x$get_result()) 30 | rm(x) 31 | gc() 32 | }) 33 | 34 | test_that("can read standard output", { 35 | x <- r_bg(function() cat("Hello world!\n")) 36 | x$wait() 37 | expect_equal(x$read_output_lines(), "Hello world!") 38 | rm(x) 39 | gc() 40 | }) 41 | 42 | test_that("can read standard error", { 43 | x <- r_bg(function() message("Hello world!")) 44 | x$wait() 45 | expect_equal(x$read_error_lines(), "Hello world!") 46 | rm(x) 47 | gc() 48 | }) 49 | 50 | test_that("can read stdout and stderr", { 51 | x <- r_bg(function() { 52 | cat("Hello world!\n") 53 | message("Again!") 54 | }) 55 | x$wait() 56 | expect_equal(x$read_output_lines(), "Hello world!") 57 | expect_equal(x$read_error_lines(), "Again!") 58 | rm(x) 59 | gc() 60 | }) 61 | 62 | test_that("cleans up temporary files", { 63 | skip_on_cran() 64 | 65 | rbg <- function() { 66 | library(callr) 67 | old <- dir(tempdir(), pattern = "^callr-") 68 | rp <- callr::r_bg(function() 1 + 1) 69 | on.exit(tryCatch(rp$kill, error = function(e) NULL), add = TRUE) 70 | rp$wait(5000) 71 | rp$kill() 72 | result <- rp$get_result() 73 | 74 | rm(rp) 75 | gc() 76 | gc() 77 | 78 | unloadNamespace("callr") 79 | new <- setdiff(dir(tempdir(), "^callr-"), old) 80 | 81 | list(result = result, new = new) 82 | } 83 | 84 | out <- r(rbg) 85 | expect_identical(out$result, 2) 86 | expect_identical(out$new, character()) 87 | }) 88 | -------------------------------------------------------------------------------- /tests/testthat/test-r-process.R: -------------------------------------------------------------------------------- 1 | test_that("create and run r_process", { 2 | options <- r_process_options(func = function() 1 + 1) 3 | x <- r_process$new(options) 4 | x$wait() 5 | expect_equal(x$get_result(), 2) 6 | rm(x) 7 | gc() 8 | }) 9 | -------------------------------------------------------------------------------- /tests/testthat/test-r-session-messages.R: -------------------------------------------------------------------------------- 1 | test_that("callr_message, then error", { 2 | rs <- r_session$new() 3 | on.exit(rs$kill(), add = TRUE) 4 | 5 | do <- function() { 6 | msg <- structure( 7 | list(message = "hi"), 8 | class = c("callr_message", "condition") 9 | ) 10 | signalCondition(msg) 11 | signalCondition(msg) 12 | stop("nah-ah") 13 | } 14 | 15 | msg <- err <- NULL 16 | tryCatch( 17 | withCallingHandlers( 18 | rs$run(do), 19 | callr_message = function(m) msg <<- c(msg, list(m)) 20 | ), 21 | error = function(e) err <<- e 22 | ) 23 | 24 | expect_s3_class(msg[[1]], "callr_message") 25 | expect_equal(conditionMessage(msg[[1]]), "hi") 26 | expect_s3_class(msg[[2]], "callr_message") 27 | expect_equal(conditionMessage(msg[[2]]), "hi") 28 | 29 | expect_s3_class(err, "error") 30 | expect_match(conditionMessage(err), "nah-ah") 31 | 32 | expect_true(rs$is_alive()) 33 | expect_equal(rs$get_state(), "idle") 34 | expect_identical(rs$read_error_lines(), character()) 35 | 36 | rs$close() 37 | }) 38 | 39 | test_that("message handlers", { 40 | skip_if_not_installed("withr") 41 | rs <- r_session$new() 42 | on.exit(rs$kill(), add = TRUE) 43 | 44 | do <- function() { 45 | msg <- structure( 46 | list(message = "hi"), 47 | class = c("myclass", "callr_message", "condition") 48 | ) 49 | signalCondition(msg) 50 | } 51 | 52 | cond <- NULL 53 | withr::with_options( 54 | list(callr.condition_handler_myclass = function(x) { 55 | cond <<- x 56 | }), 57 | rs$run(do) 58 | ) 59 | 60 | expect_s3_class(cond, "myclass") 61 | expect_equal(cond$message, "hi") 62 | 63 | rs$close() 64 | }) 65 | 66 | test_that("large messages", { 67 | skip_if_not_installed("withr") 68 | rs <- r_session$new() 69 | on.exit(rs$close(), add = TRUE) 70 | 71 | do <- function() { 72 | msg <- structure( 73 | list(message = paste(1:150000, sep = " ")), 74 | class = c("myclass", "callr_message", "condition") 75 | ) 76 | signalCondition(msg) 77 | for (i in 1:5) { 78 | msg <- structure( 79 | list(message = paste("message", i)), 80 | class = c("myclass", "callr_message", "condition") 81 | ) 82 | signalCondition(msg) 83 | } 84 | } 85 | 86 | cond <- list() 87 | withr::with_options( 88 | list(callr.condition_handler_myclass = function(x) { 89 | cond <<- c(cond, list(x)) 90 | }), 91 | rs$run(do) 92 | ) 93 | 94 | expect_equal(length(cond), 6) 95 | expect_s3_class(cond[[1]], "myclass") 96 | expect_equal(cond[[1]]$message, paste(1:150000, sep = " ")) 97 | for (i in 1:5) { 98 | expect_equal(cond[[i + 1]]$message, paste("message", i)) 99 | } 100 | 101 | rs$close() 102 | }) 103 | -------------------------------------------------------------------------------- /tests/testthat/test-rcmd-bg.R: -------------------------------------------------------------------------------- 1 | test_that("rcmd_bg runs", { 2 | out1 <- gsub("\r?\n", "", rcmd("config", "CC")$stdout) 3 | x <- rcmd_bg("config", "CC") 4 | x$wait() 5 | out2 <- x$read_output_lines() 6 | expect_equal(out1, out2) 7 | expect_equal(x$get_exit_status(), 0) 8 | rm(x) 9 | gc() 10 | }) 11 | 12 | test_that("r_cmd can be killed", { 13 | cat("Sys.sleep(5)", file = tmp <- tempfile()) 14 | on.exit(try_silently(unlink(tmp)), add = TRUE) 15 | 16 | x <- rcmd_bg("BATCH", tmp) 17 | expect_true(x$is_alive()) 18 | 19 | x$kill() 20 | expect_false(x$is_alive()) 21 | rm(x) 22 | gc() 23 | }) 24 | 25 | test_that("r_cmd errors", { 26 | x <- rcmd_bg("config", "axaxaxaxax") 27 | x$wait() 28 | expect_equal(x$get_exit_status(), 1) 29 | rm(x) 30 | gc() 31 | }) 32 | -------------------------------------------------------------------------------- /tests/testthat/test-rcmd-process.R: -------------------------------------------------------------------------------- 1 | test_that("create and run rcmd_process", { 2 | opts <- rcmd_process_options(cmd = "config", cmdargs = "CC") 3 | x <- rcmd_process$new(opts) 4 | x$wait() 5 | expect_equal(x$get_exit_status(), 0) 6 | out <- x$read_output_lines() 7 | expect_match(out, ".") 8 | rm(x) 9 | gc() 10 | }) 11 | 12 | test_that("cleans up temp files", { 13 | skip_on_cran() 14 | 15 | rc <- function() { 16 | library(callr) 17 | scriptfile <- tempfile(fileext = ".R") 18 | 19 | old <- dir(tempdir(), pattern = "^callr-") 20 | 21 | rp <- rcmd_bg("config", "CC") 22 | on.exit(tryCatch(rp$kill, error = function(e) NULL), add = TRUE) 23 | rp$wait(5000) 24 | result <- rp$get_exit_status() 25 | rp$kill() 26 | 27 | rm(rp) 28 | gc() 29 | gc() 30 | new <- setdiff(dir(tempdir(), "^callr-"), old) 31 | 32 | list(result = result, new = new) 33 | } 34 | 35 | out <- r(rc) 36 | expect_identical(out$result, 0L) 37 | expect_identical(out$new, character()) 38 | }) 39 | -------------------------------------------------------------------------------- /tests/testthat/test-rcmd.R: -------------------------------------------------------------------------------- 1 | test_that("rcmd works", { 2 | expect_equal(rcmd("config", "CC")$status, 0) 3 | expect_match(rcmd("config", "CC")$stdout, ".") 4 | gc() 5 | }) 6 | 7 | test_that("rcmd show works", { 8 | expect_output(rcmd("config", "CC", show = TRUE), ".") 9 | gc() 10 | }) 11 | 12 | test_that("rcmd echo works", { 13 | skip_if_not_installed("withr") 14 | withr::local_options(width = 500) 15 | expect_output(rcmd("config", "CC", echo = TRUE), "config\\s+CC") 16 | gc() 17 | }) 18 | 19 | test_that("rcmd_safe", { 20 | expect_equal(rcmd_safe("config", "CC")$status, 0) 21 | gc() 22 | }) 23 | 24 | test_that("wd argument", { 25 | tmp <- tempfile(fileext = ".R") 26 | tmpout <- paste0(tmp, "out") 27 | cat("print(getwd())", file = tmp) 28 | 29 | mywd <- getwd() 30 | 31 | rcmd("BATCH", c(tmp, tmpout), wd = tempdir()) 32 | 33 | expect_equal(mywd, getwd()) 34 | expect_match( 35 | paste(readLines(tmpout), collapse = "\n"), 36 | basename(tempdir()) 37 | ) 38 | gc() 39 | }) 40 | 41 | test_that("fail_on_status", { 42 | skip_if_not_installed("withr") 43 | rand <- tempfile() 44 | expect_error( 45 | withr::with_dir( 46 | tempdir(), 47 | rcmd("BATCH", rand, fail_on_status = TRUE) 48 | ), 49 | "System command .* failed|System command error", 50 | class = "system_command_status_error" 51 | ) 52 | expect_silent( 53 | out <- withr::with_dir( 54 | tempdir(), 55 | rcmd("BATCH", rand, fail_on_status = FALSE) 56 | ) 57 | ) 58 | expect_true(out$status != 0) 59 | gc() 60 | }) 61 | 62 | test_that("command is included in result", { 63 | res <- rcmd_safe("config", "CC") 64 | expect_false(is.null(res$command)) 65 | expect_true(is.character(res$command)) 66 | gc() 67 | }) 68 | 69 | test_that("stderr -> stdout", { 70 | lib <- test_temp_dir() 71 | pkg <- test_temp_dir() 72 | file.copy(test_path("fixtures/D1"), file.path(pkg, "DESCRIPTION")) 73 | out <- rcmd("INSTALL", c("-l", lib, pkg)) 74 | expect_match(out$stdout, "No man pages found", useBytes = TRUE) 75 | expect_match(out$stderr, "installing help indices") 76 | 77 | out2 <- rcmd("INSTALL", c("-l", lib, pkg), stderr = "2>&1") 78 | expect_equal(out2$status, 0L) 79 | expect_match( 80 | out2$stdout, 81 | "installing.*No man pages found.*testing if installed package" 82 | ) 83 | expect_null(out2$stderr) 84 | 85 | out3 <- test_temp_file(create = FALSE) 86 | rcmd("INSTALL", c("-l", lib, pkg), stdout = out3, stderr = out3) 87 | expect_match( 88 | readChar(out3, nchars = file.info(out3)$size), 89 | "installing.*No man pages found.*testing if installed package" 90 | ) 91 | gc() 92 | }) 93 | 94 | test_that("cleans up temp files", { 95 | skip_on_cran() 96 | 97 | rc <- function() { 98 | library(callr) 99 | scriptfile <- tempfile(fileext = ".R") 100 | 101 | old <- dir(tempdir(), pattern = "^callr-") 102 | 103 | result <- callr::rcmd("config", "CC") 104 | 105 | new <- setdiff(dir(tempdir(), "^callr-"), old) 106 | 107 | list(result = result, new = new) 108 | } 109 | 110 | out <- r(rc) 111 | expect_identical(out$result$status, 0L) 112 | expect_identical(out$new, character()) 113 | }) 114 | -------------------------------------------------------------------------------- /tests/testthat/test-rscript.R: -------------------------------------------------------------------------------- 1 | test_that("rscript", { 2 | out <- rscript("fixtures/script.R", show = FALSE) 3 | expect_equal(out$status, 0L) 4 | expect_equal( 5 | out$stdout, 6 | if (os_platform() == "windows") "stdout\r\n" else "stdout\n" 7 | ) 8 | expect_equal( 9 | out$stderr, 10 | if (os_platform() == "windows") "stderr\r\n" else "stderr\n" 11 | ) 12 | gc() 13 | }) 14 | 15 | test_that("rscript_process", { 16 | px <- rscript_process$new( 17 | rscript_process_options(script = "fixtures/script.R") 18 | ) 19 | on.exit(try(px$kill(), silent = TRUE), add = TRUE) 20 | px$wait(5000) 21 | 22 | expect_equal(px$get_exit_status(), 0) 23 | expect_equal(px$read_output_lines(), "stdout") 24 | expect_equal(px$read_error_lines(), "stderr") 25 | rm(px) 26 | gc() 27 | }) 28 | 29 | test_that("stderr -> stdout", { 30 | out <- rscript("fixtures/script2.R", show = FALSE, stderr = "2>&1") 31 | exp <- if (os_platform() == "windows") { 32 | "out1err1out2err2\r\n" 33 | } else { 34 | "out1err1out2err2\n" 35 | } 36 | expect_equal(out$stdout, exp) 37 | expect_null(out$stderr) 38 | 39 | out2 <- test_temp_file(create = FALSE) 40 | rscript("fixtures/script2.R", show = FALSE, stdout = out2, stderr = out2) 41 | expect_equal(readLines(out2), "out1err1out2err2") 42 | gc() 43 | }) 44 | 45 | test_that("cleans up temporary files", { 46 | skip_on_cran() 47 | 48 | rsc <- function() { 49 | library(callr) 50 | scriptfile <- tempfile(fileext = ".R") 51 | cat("cat('foobar')\n", file = scriptfile) 52 | 53 | old <- dir(tempdir(), pattern = "^callr-") 54 | 55 | result <- callr::rscript(scriptfile) 56 | 57 | new <- setdiff(dir(tempdir(), "^callr-"), old) 58 | 59 | list(result = result, new = new) 60 | } 61 | 62 | out <- r(rsc) 63 | expect_identical(out$result$stdout, "foobar") 64 | expect_identical(out$new, character()) 65 | }) 66 | 67 | test_that("bg process cleans up temporary files", { 68 | skip_on_cran() 69 | 70 | rsc <- function() { 71 | library(callr) 72 | scriptfile <- tempfile(fileext = ".R") 73 | cat("cat('foobar')\n", file = scriptfile) 74 | 75 | old <- dir(tempdir(), pattern = "^callr-") 76 | 77 | opts <- rscript_process_options(script = scriptfile) 78 | rp <- rscript_process$new(opts) 79 | on.exit(tryCatch(rp$kill, error = function(e) NULL), add = TRUE) 80 | rp$wait(5000) 81 | result <- rp$read_output() 82 | rp$kill() 83 | 84 | rm(rp) 85 | gc() 86 | gc() 87 | new <- setdiff(dir(tempdir(), "^callr-"), old) 88 | 89 | list(result = result, new = new) 90 | } 91 | 92 | out <- r(rsc) 93 | expect_identical(out$result, "foobar") 94 | expect_identical(out$new, character()) 95 | }) 96 | -------------------------------------------------------------------------------- /tests/testthat/test-spelling.R: -------------------------------------------------------------------------------- 1 | test_that("spell check", { 2 | skip_on_cran() 3 | skip_in_covr() 4 | skip_if_not_installed("spelling") 5 | pkg_dir <- test_package_root() 6 | results <- spelling::spell_check_package(pkg_dir) 7 | 8 | if (nrow(results)) { 9 | output <- sprintf( 10 | "Potential spelling errors: %s\n", 11 | paste(results$word, collapse = ", ") 12 | ) 13 | stop(output, call. = FALSE) 14 | } else { 15 | expect_true(TRUE) 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /tests/testthat/test-timeout.R: -------------------------------------------------------------------------------- 1 | test_that("r with timeout", { 2 | tic <- Sys.time() 3 | e <- tryCatch( 4 | r(function() Sys.sleep(5), timeout = 1), 5 | error = function(e) e 6 | ) 7 | tac <- Sys.time() 8 | 9 | expect_true("callr_timeout_error" %in% class(e)) 10 | expect_true(tac - tic < as.difftime(4, units = "secs")) 11 | gc() 12 | }) 13 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | test_that("is_complete_expression", { 2 | skip_if_not_installed("withr") 3 | do_tests <- function() { 4 | expect_true(is_complete_expression("")) 5 | expect_true(is_complete_expression("1")) 6 | expect_true(is_complete_expression("1+1")) 7 | expect_true(is_complete_expression("foo + \n bar")) 8 | expect_true(is_complete_expression("1 1")) 9 | 10 | expect_false(is_complete_expression("1+")) 11 | expect_false(is_complete_expression("1+1+")) 12 | expect_false(is_complete_expression("1+\n2+")) 13 | } 14 | 15 | do_tests() 16 | 17 | if (has_locale("de_DE")) { 18 | withr::with_envvar(c(LANGUAGE = "de_DE"), do_tests()) 19 | } 20 | }) 21 | 22 | test_that("default_repos", { 23 | skip_if_not_installed("withr") 24 | def <- "https://cloud.r-project.org" 25 | 26 | withr::with_options( 27 | list(repos = NULL), 28 | expect_equal( 29 | default_repos(), 30 | c(CRAN = def) 31 | ) 32 | ) 33 | 34 | withr::with_options( 35 | list(repos = character()), 36 | expect_equal( 37 | default_repos(), 38 | c(CRAN = def) 39 | ) 40 | ) 41 | 42 | withr::with_options( 43 | list(repos = list()), 44 | expect_equal( 45 | default_repos(), 46 | list(CRAN = def) 47 | ) 48 | ) 49 | 50 | withr::with_options( 51 | list(repos = c(foo = "bar")), 52 | expect_equal( 53 | default_repos(), 54 | c(foo = "bar", CRAN = def) 55 | ) 56 | ) 57 | 58 | withr::with_options( 59 | list(repos = list(foo = "bar")), 60 | expect_equal( 61 | default_repos(), 62 | list(foo = "bar", CRAN = def) 63 | ) 64 | ) 65 | 66 | withr::with_options( 67 | list(repos = c(foo = "bar", CRAN = "set")), 68 | expect_equal( 69 | default_repos(), 70 | c(foo = "bar", CRAN = "set") 71 | ) 72 | ) 73 | 74 | withr::with_options( 75 | list(repos = c(foo = "bar", CRAN = "@CRAN@")), 76 | expect_equal( 77 | default_repos(), 78 | c(foo = "bar", CRAN = def) 79 | ) 80 | ) 81 | }) 82 | --------------------------------------------------------------------------------