mirror of
https://github.com/rstudio/shiny.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
54 Commits
rc-v1.12.1
...
non-blocki
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4c91f62a1 | ||
|
|
88b4facb8f | ||
|
|
957b50d3b6 | ||
|
|
da50bf2249 | ||
|
|
72636ef4a0 | ||
|
|
02a5e0b40f | ||
|
|
0ff93d411f | ||
|
|
bd250962e4 | ||
|
|
3a0e8627a4 | ||
|
|
b813adec56 | ||
|
|
f29fa65af9 | ||
|
|
36e7a330d6 | ||
|
|
6d984266f9 | ||
|
|
c27c186c0f | ||
|
|
2907e83c42 | ||
|
|
75a63716e5 | ||
|
|
b240b0b868 | ||
|
|
3c18aca49b | ||
|
|
45985690b2 | ||
|
|
ce11abe46d | ||
|
|
7e8903f754 | ||
|
|
664cbe2858 | ||
|
|
63af3649c8 | ||
|
|
9b78be1106 | ||
|
|
3cb928e894 | ||
|
|
8e63d08d8a | ||
|
|
1db26f60af | ||
|
|
1432920a7e | ||
|
|
3882d1e4c3 | ||
|
|
532c17081a | ||
|
|
329bc979c6 | ||
|
|
3a130b2015 | ||
|
|
27ddc696dc | ||
|
|
0456847883 | ||
|
|
6bbe29a390 | ||
|
|
c8bfa93747 | ||
|
|
27134d9c66 | ||
|
|
4d787c767c | ||
|
|
48540283a4 | ||
|
|
49b76badcc | ||
|
|
935de77aee | ||
|
|
8b53c6d2fd | ||
|
|
3ccbad7a70 | ||
|
|
13812b45a7 | ||
|
|
08680d9566 | ||
|
|
620e5a277b | ||
|
|
bb26c0f4d3 | ||
|
|
e161f2e4a8 | ||
|
|
ca259ab0f1 | ||
|
|
9e9a3bf80b | ||
|
|
07af5f91c8 | ||
|
|
fda6a9fede | ||
|
|
d2245a2e34 | ||
|
|
a12a8130b8 |
@@ -34,3 +34,4 @@
|
||||
^.claude$
|
||||
^README-npm\.md$
|
||||
^CRAN-SUBMISSION$
|
||||
^LICENSE\.md$
|
||||
|
||||
4
.github/workflows/R-CMD-check.yaml
vendored
4
.github/workflows/R-CMD-check.yaml
vendored
@@ -19,7 +19,3 @@ jobs:
|
||||
uses: rstudio/shiny-workflows/.github/workflows/routine.yaml@v1
|
||||
R-CMD-check:
|
||||
uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1
|
||||
with:
|
||||
# On R 4.2, Cairo has difficulty installing
|
||||
# Remove this line when https://github.com/s-u/Cairo/issues/52 is merged
|
||||
extra-packages: Cairo=?ignore
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Type: Package
|
||||
Package: shiny
|
||||
Title: Web Application Framework for R
|
||||
Version: 1.12.0.9000
|
||||
Version: 1.13.0.9000
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", , "winston@posit.co", role = "aut",
|
||||
comment = c(ORCID = "0000-0002-1576-2126")),
|
||||
@@ -69,12 +69,12 @@ Description: Makes it incredibly easy to build interactive web
|
||||
applications with R. Automatic "reactive" binding between inputs and
|
||||
outputs and extensive prebuilt widgets make it possible to build
|
||||
beautiful, responsive, and powerful applications with minimal effort.
|
||||
License: GPL-3 | file LICENSE
|
||||
License: MIT + file LICENSE
|
||||
URL: https://shiny.posit.co/, https://github.com/rstudio/shiny
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Depends:
|
||||
methods,
|
||||
R (>= 3.0.2)
|
||||
R (>= 3.1.2)
|
||||
Imports:
|
||||
bslib (>= 0.6.0),
|
||||
cachem (>= 1.1.0),
|
||||
@@ -126,6 +126,7 @@ Encoding: UTF-8
|
||||
Roxygen: list(markdown = TRUE)
|
||||
RoxygenNote: 7.3.3
|
||||
Collate:
|
||||
'app-handle.R'
|
||||
'globals.R'
|
||||
'app-state.R'
|
||||
'app_template.R'
|
||||
|
||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# MIT License
|
||||
|
||||
Copyright (c) 2025 shiny authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1011
LICENSE.note
Normal file
1011
LICENSE.note
Normal file
File diff suppressed because it is too large
Load Diff
@@ -276,6 +276,7 @@ export(snapshotPreprocessInput)
|
||||
export(snapshotPreprocessOutput)
|
||||
export(span)
|
||||
export(splitLayout)
|
||||
export(startApp)
|
||||
export(stopApp)
|
||||
export(strong)
|
||||
export(submitButton)
|
||||
|
||||
47
NEWS.md
47
NEWS.md
@@ -1,8 +1,47 @@
|
||||
# shiny (development version)
|
||||
|
||||
* `ExtendedTask` now captures the OpenTelemetry recording state at initialization time rather than at invocation time, ensuring consistent span recording behavior regardless of runtime configuration changes. (#4334)
|
||||
* Added `withOtelCollect()` and `localOtelCollect()` functions to temporarily control OpenTelemetry collection levels during reactive expression creation. These functions allow you to enable or disable telemetry collection for specific modules or sections of code where reactive expressions are being created. (#4333)
|
||||
* OpenTelemetry code attributes now include both preferred (`code.file.path`, `code.line.number`, `code.column.number`) and deprecated (`code.filepath`, `code.lineno`, `code.column`) attribute names to follow OpenTelemetry semantic conventions while maintaining backward compatibility. The deprecated names will be removed in a future release after Logfire supports the preferred names. (#4325)
|
||||
## New features
|
||||
|
||||
* New `startApp()` runs a Shiny app in non-blocking mode, returning a
|
||||
`ShinyAppHandle` object with `stop()`, `status()`, `url()`, and `result()`
|
||||
methods. When a new app is started, any previously running non-blocking app
|
||||
is automatically stopped.
|
||||
|
||||
# shiny 1.13.0
|
||||
|
||||
## New features
|
||||
|
||||
* Shiny now supports interactive breakpoints when used with Ark (e.g. in Positron). (#4352)
|
||||
|
||||
## Bug fixes and minor improvements
|
||||
|
||||
* Stack traces from render functions (e.g., `renderPlot()`, `renderDataTable()`) now hide internal Shiny rendering pipeline frames, making error messages cleaner and more focused on user code. (#4358)
|
||||
|
||||
* Fixed an issue with `actionLink()` that extended the link underline to whitespace around the text. (#4348)
|
||||
|
||||
|
||||
# shiny 1.12.1
|
||||
|
||||
## New features
|
||||
|
||||
* `withOtelCollect()` and `localOtelCollect()` temporarily control
|
||||
OpenTelemetry collection levels during reactive expression creation,
|
||||
allowing you to enable or disable telemetry collection for specific modules
|
||||
or sections of code. (#4333)
|
||||
|
||||
## Bug fixes and minor improvements
|
||||
|
||||
* OpenTelemetry code attributes now include both the preferred attribute names
|
||||
(`code.file.path`, `code.line.number`, `code.column.number`) and the
|
||||
deprecated names (`code.filepath`, `code.lineno`, `code.column`) to follow
|
||||
OpenTelemetry semantic conventions while maintaining backward compatibility.
|
||||
The deprecated names will be removed in a future release after Logfire
|
||||
supports the preferred names. (#4325)
|
||||
|
||||
* `ExtendedTask` now captures the OpenTelemetry recording state at
|
||||
initialization time rather than at invocation time, ensuring consistent span
|
||||
recording behavior regardless of runtime configuration changes. (#4334)
|
||||
|
||||
* Timer tests are now skipped on CRAN. (#4327)
|
||||
|
||||
# shiny 1.12.0
|
||||
@@ -779,7 +818,7 @@ This release features plot caching, an important new tool for improving performa
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Upgrade FontAwesome from 4.7.0 to 5.3.1 and made `icon` tags browsable, which means they will display in a web browser or RStudio viewer by default (#2186). Note that if your application or library depends on FontAwesome directly using custom CSS, you may need to make some or all of the changes recommended in [Upgrade from Version 4](https://docs.fontawesome.com/v5/web/setup/upgrade-from-v4). Font Awesome icons can also now be used in static R Markdown documents.
|
||||
* Upgrade FontAwesome from 4.7.0 to 5.3.1 and made `icon` tags browsable, which means they will display in a web browser or RStudio viewer by default (#2186). Note that if your application or library depends on FontAwesome directly using custom CSS, you may need to make some or all of the changes recommended in [Upgrade from Version 4](https://docs-v5.fontawesome.com/web/setup/upgrade-from-v4). Font Awesome icons can also now be used in static R Markdown documents.
|
||||
|
||||
* Address #174: Added `datesdisabled` and `daysofweekdisabled` as new parameters to `dateInput()`. This resolves #174 and exposes the underlying arguments of [Bootstrap Datepicker](http://bootstrap-datepicker.readthedocs.io/en/latest/options.html#datesdisabled). `datesdisabled` expects a character vector with values in `yyyy/mm/dd` format and `daysofweekdisabled` expects an integer vector with day interger ids (Sunday=0, Saturday=6). The default value for both is `NULL`, which leaves all days selectable. Thanks, @nathancday! (#2147)
|
||||
|
||||
|
||||
73
R/app-handle.R
Normal file
73
R/app-handle.R
Normal file
@@ -0,0 +1,73 @@
|
||||
# Handle returned by startApp()
|
||||
ShinyAppHandle <- R6::R6Class("ShinyAppHandle",
|
||||
cloneable = FALSE,
|
||||
|
||||
public = list(
|
||||
initialize = function(appUrl, cleanupFn) {
|
||||
private$appUrl <- appUrl
|
||||
private$cleanupFn <- cleanupFn
|
||||
|
||||
reg.finalizer(self, function(e) {
|
||||
tryCatch(e$stop(), error = function(cnd) NULL)
|
||||
}, onexit = TRUE)
|
||||
},
|
||||
|
||||
stop = function() {
|
||||
if (self$status() != "running") {
|
||||
return(invisible(self))
|
||||
}
|
||||
private$stopped <- TRUE
|
||||
private$captureResult()
|
||||
private$cleanupFn()
|
||||
private$cleanupFn <- NULL
|
||||
invisible(self)
|
||||
},
|
||||
|
||||
url = function() private$appUrl,
|
||||
|
||||
status = function() {
|
||||
if (!private$stopped) {
|
||||
"running"
|
||||
} else if (!is.null(private$resultError)) {
|
||||
"error"
|
||||
} else {
|
||||
"success"
|
||||
}
|
||||
},
|
||||
|
||||
result = function() {
|
||||
if (self$status() == "running") {
|
||||
stop("App is still running. Use status() to check if the app has stopped.")
|
||||
}
|
||||
if (!is.null(private$resultError)) {
|
||||
stop(private$resultError)
|
||||
}
|
||||
private$resultValue
|
||||
},
|
||||
|
||||
print = function(...) {
|
||||
cat("Shiny app handle\n")
|
||||
cat(" URL: ", private$appUrl, "\n", sep = "")
|
||||
cat(" Status:", self$status(), "\n")
|
||||
invisible(self)
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
appUrl = NULL,
|
||||
cleanupFn = NULL,
|
||||
# Whether this handle has been stopped. Distinct from .globals$stopped
|
||||
# which tracks whether a stop was requested (set by stopApp() or stop()).
|
||||
stopped = FALSE,
|
||||
resultValue = NULL,
|
||||
resultError = NULL,
|
||||
|
||||
captureResult = function() {
|
||||
if (isTRUE(.globals$reterror)) {
|
||||
private$resultError <- .globals$retval
|
||||
} else if (!is.null(.globals$retval)) {
|
||||
private$resultValue <- .globals$retval$value
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
193
R/conditions.R
193
R/conditions.R
@@ -87,13 +87,61 @@ getCallNamesForHash <- function(calls) {
|
||||
})
|
||||
}
|
||||
|
||||
# Get the preferred filename from a srcfile object.
|
||||
#
|
||||
# For user code, prefer the original path (as typed by user, potentially a
|
||||
# symlink or relative path) over the normalized absolute path.
|
||||
#
|
||||
# For package files (under .libPaths()), keep the srcfile$filename because
|
||||
# when a package is installed with keep.source.pkgs = TRUE, the original
|
||||
# srcfilecopy filename may point to a collated build-time path rather than
|
||||
# the real installed package path.
|
||||
getSrcfileFilename <- function(srcfile) {
|
||||
if (!is.null(srcfile$original) &&
|
||||
!is.null(srcfile$original$filename) &&
|
||||
!isPackageFile(srcfile$filename)) {
|
||||
srcfile$original$filename
|
||||
} else {
|
||||
srcfile$filename
|
||||
}
|
||||
}
|
||||
|
||||
# Get the source lines and correct line number from a srcfile + srcref.
|
||||
#
|
||||
# sourceUTF8() wraps user code with a `#line` directive that remaps line
|
||||
# numbers. This means srcref[1] (the remapped line) may not correctly index
|
||||
# into the srcfile's $lines. When a #line directive is present, R extends
|
||||
# the srcref to 8 elements: [7] and [8] are the original (pre-remap) first
|
||||
# and last line numbers in the srcfilecopy's coordinate system.
|
||||
#
|
||||
# Additionally, when the #line path differs from the srcfilecopy filename
|
||||
# (e.g. macOS /tmp -> /private/tmp, or Windows path normalization), R wraps
|
||||
# the srcfile in a srcfilealias whose $lines is NULL. In that case, we
|
||||
# retrieve lines from the original srcfilecopy via $original.
|
||||
getSrcfileLines <- function(srcfile, srcref) {
|
||||
lines <- srcfile$lines
|
||||
line_num <- srcref[1]
|
||||
|
||||
if (is.null(lines) && inherits(srcfile, "srcfilealias")) {
|
||||
lines <- srcfile$original$lines
|
||||
}
|
||||
|
||||
# Use the pre-remap line number when available and different from the
|
||||
# remapped line, indicating a #line directive shifted line numbering.
|
||||
if (isTRUE(length(srcref) >= 7 && srcref[7] != srcref[1])) {
|
||||
line_num <- srcref[7]
|
||||
}
|
||||
|
||||
list(lines = lines, line_num = line_num)
|
||||
}
|
||||
|
||||
getLocs <- function(calls) {
|
||||
vapply(calls, function(call) {
|
||||
srcref <- attr(call, "srcref", exact = TRUE)
|
||||
if (!is.null(srcref)) {
|
||||
srcfile <- attr(srcref, "srcfile", exact = TRUE)
|
||||
if (!is.null(srcfile) && !is.null(srcfile$filename)) {
|
||||
loc <- paste0(srcfile$filename, "#", srcref[[1]])
|
||||
loc <- paste0(getSrcfileFilename(srcfile), "#", srcref[[1]])
|
||||
return(paste0(" [", loc, "]"))
|
||||
}
|
||||
}
|
||||
@@ -101,13 +149,36 @@ getLocs <- function(calls) {
|
||||
}, character(1))
|
||||
}
|
||||
|
||||
# Check if a file path is in an R package library
|
||||
isPackageFile <- function(filepath) {
|
||||
if (is.null(filepath) || filepath == "") {
|
||||
return(FALSE)
|
||||
}
|
||||
|
||||
# Normalize paths for comparison
|
||||
filepath <- normalizePath(filepath, winslash = "/", mustWork = FALSE)
|
||||
lib_paths <- normalizePath(.libPaths(), winslash = "/", mustWork = FALSE)
|
||||
# Ensure trailing slash for proper path-boundary matching, otherwise
|
||||
# e.g. "/usr/lib/R" would incorrectly match "/usr/lib/Rcpp/..."
|
||||
lib_paths <- paste0(sub("/$", "", lib_paths), "/")
|
||||
|
||||
# Check if the file is under any library path
|
||||
any(vapply(
|
||||
lib_paths,
|
||||
function(lib) identical(substr(filepath, 1, nchar(lib)), lib),
|
||||
logical(1)
|
||||
))
|
||||
}
|
||||
|
||||
getCallCategories <- function(calls) {
|
||||
vapply(calls, function(call) {
|
||||
srcref <- attr(call, "srcref", exact = TRUE)
|
||||
if (!is.null(srcref)) {
|
||||
srcfile <- attr(srcref, "srcfile", exact = TRUE)
|
||||
if (!is.null(srcfile)) {
|
||||
if (!is.null(srcfile$original)) {
|
||||
if (!is.null(srcfile) && !is.null(srcfile$filename)) {
|
||||
# Use the absolute path for package detection (srcfile$filename)
|
||||
# rather than the original path which might be relative
|
||||
if (isPackageFile(srcfile$filename)) {
|
||||
return("pkg")
|
||||
} else {
|
||||
return("user")
|
||||
@@ -445,45 +516,93 @@ printOneStackTrace <- function(stackTrace, stripResult, full, offset) {
|
||||
invisible(st)
|
||||
}
|
||||
|
||||
# Filter stack traces using fence markers to hide internal Shiny frames.
|
||||
#
|
||||
# `stackTraces` is a list of character vectors (call names), one per "segment".
|
||||
# A single synchronous error produces one segment (the immediate call stack).
|
||||
# Asynchronous errors (e.g. from promises) produce multiple segments: the deep
|
||||
# stack trace segments come first, then the current segment last. Each deep
|
||||
# segment may begin with frames that overlap the previous segment; a
|
||||
# `..stacktracefloor..` marker delimits this redundant prefix from the active
|
||||
# portion.
|
||||
#
|
||||
# Within the active frames, `..stacktraceon..` / `..stacktraceoff..` markers
|
||||
# act as fences. Frames between a matched off/on pair (reading innermost to
|
||||
# outermost) are hidden — these are the internal rendering pipeline frames
|
||||
# that users don't need to see. The algorithm uses a *reverse clamped cumulative
|
||||
# sum* so that an unmatched `..stacktraceoff..` (one with no corresponding
|
||||
# inner `..stacktraceon..`) is a no-op, preventing it from hiding user frames.
|
||||
# Fence matching works globally across segments so that a `..stacktraceoff..`
|
||||
# at the end of one segment can pair with a `..stacktraceon..` at the start
|
||||
# of the next.
|
||||
stripStackTraces <- function(stackTraces, values = FALSE) {
|
||||
score <- 1L # >=1: show, <=0: hide
|
||||
lapply(seq_along(stackTraces), function(i) {
|
||||
res <- stripOneStackTrace(stackTraces[[i]], i != 1, score)
|
||||
score <<- res$score
|
||||
toShow <- as.logical(res$trace)
|
||||
if (values) {
|
||||
as.character(stackTraces[[i]][toShow])
|
||||
} else {
|
||||
as.logical(toShow)
|
||||
n_segs <- length(stackTraces)
|
||||
if (n_segs == 0L) return(list())
|
||||
|
||||
# Replace NULL segments with empty character vectors
|
||||
stackTraces <- lapply(stackTraces, function(st) st %||% character(0))
|
||||
seg_lengths <- lengths(stackTraces)
|
||||
total <- sum(seg_lengths)
|
||||
|
||||
if (total == 0L) {
|
||||
return(lapply(seg_lengths, function(n) {
|
||||
if (values) character(0) else logical(0)
|
||||
}))
|
||||
}
|
||||
|
||||
# Pre-compute segment boundaries (used in steps 1 and 4)
|
||||
seg_ends <- cumsum(seg_lengths)
|
||||
seg_starts <- c(1L, seg_ends[-n_segs] + 1L)
|
||||
|
||||
# Concatenate all segments into one vector for vectorized operations
|
||||
all <- unlist(stackTraces)
|
||||
|
||||
# 1. Identify prefix elements (at/before last ..stacktracefloor.. in segs 2+)
|
||||
# Prefix elements are always hidden and excluded from fence scoring.
|
||||
is_active <- rep.int(TRUE, total)
|
||||
if (n_segs >= 2L) {
|
||||
for (i in 2:n_segs) {
|
||||
if (seg_lengths[i] == 0L) next
|
||||
seg_idx <- seg_starts[i]:seg_ends[i]
|
||||
floor_pos <- which(all[seg_idx] == "..stacktracefloor..")
|
||||
if (length(floor_pos)) {
|
||||
is_active[seg_idx[seq_len(floor_pos[length(floor_pos)])]] <- FALSE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Compute fence scores and marker mask (vectorized across all segments)
|
||||
is_on <- all == "..stacktraceon.."
|
||||
is_off <- all == "..stacktraceoff.."
|
||||
is_marker <- is_on | is_off | (all == "..stacktracefloor..")
|
||||
scores <- integer(total)
|
||||
scores[is_active & is_on] <- 1L
|
||||
scores[is_active & is_off] <- -1L
|
||||
|
||||
# 3. Reverse clamped cumsum across all segments.
|
||||
# Process from innermost (right) to outermost (left). ..stacktraceon.. (+1)
|
||||
# opens a hidden region working outward, ..stacktraceoff.. (-1) closes it.
|
||||
# Clamping at 0 means an unmatched ..stacktraceoff.. (one with no inner
|
||||
# ..stacktraceon..) is a no-op. Prefix elements have score 0 and pass the
|
||||
# running total through unchanged.
|
||||
#
|
||||
# Vectorized via the identity: clamped_cumsum = cumsum - pmin(0, cummin(cumsum))
|
||||
rs <- rev(scores)
|
||||
cs <- cumsum(rs)
|
||||
depth <- rev(cs - pmin.int(0L, cummin(cs)))
|
||||
|
||||
# 4. Compute visibility (vectorized) and split back into segments
|
||||
toShow <- is_active & depth == 0L & !is_marker
|
||||
|
||||
lapply(seq_len(n_segs), function(i) {
|
||||
if (seg_lengths[i] == 0L) {
|
||||
if (values) return(character(0)) else return(logical(0))
|
||||
}
|
||||
idx <- seg_starts[i]:seg_ends[i]
|
||||
if (values) as.character(all[idx[toShow[idx]]]) else toShow[idx]
|
||||
})
|
||||
}
|
||||
|
||||
stripOneStackTrace <- function(stackTrace, truncateFloor, startingScore) {
|
||||
prefix <- logical(0)
|
||||
if (truncateFloor) {
|
||||
indexOfFloor <- utils::tail(which(stackTrace == "..stacktracefloor.."), 1)
|
||||
if (length(indexOfFloor)) {
|
||||
stackTrace <- stackTrace[(indexOfFloor+1L):length(stackTrace)]
|
||||
prefix <- rep_len(FALSE, indexOfFloor)
|
||||
}
|
||||
}
|
||||
|
||||
if (length(stackTrace) == 0) {
|
||||
return(list(score = startingScore, character(0)))
|
||||
}
|
||||
|
||||
score <- rep.int(0L, length(stackTrace))
|
||||
score[stackTrace == "..stacktraceon.."] <- 1L
|
||||
score[stackTrace == "..stacktraceoff.."] <- -1L
|
||||
score <- startingScore + cumsum(score)
|
||||
|
||||
toShow <- score > 0 & !(stackTrace %in% c("..stacktraceon..", "..stacktraceoff..", "..stacktracefloor.."))
|
||||
|
||||
|
||||
list(score = utils::tail(score, 1), trace = c(prefix, toShow))
|
||||
}
|
||||
|
||||
# Given sys.parents() (which corresponds to sys.calls()), return a logical index
|
||||
# that prunes each subtree so that only the final branch remains. The result,
|
||||
# when applied to sys.calls(), is a linear list of calls without any "wrapper"
|
||||
|
||||
@@ -59,11 +59,11 @@ actionButton <- function(inputId, label, icon = NULL, width = NULL,
|
||||
icon <- validateIcon(icon)
|
||||
|
||||
if (!is.null(icon)) {
|
||||
icon <- span(icon, class = "action-icon")
|
||||
icon <- span(icon, class = "action-icon", .noWS = c("outside", "inside"))
|
||||
}
|
||||
|
||||
if (!is.null(label)) {
|
||||
label <- span(label, class = "action-label")
|
||||
label <- span(label, class = "action-label", .noWS = c("outside", "inside"))
|
||||
}
|
||||
|
||||
tags$button(
|
||||
@@ -74,6 +74,7 @@ actionButton <- function(inputId, label, icon = NULL, width = NULL,
|
||||
`data-val` = value,
|
||||
disabled = if (isTRUE(disabled)) NA else NULL,
|
||||
icon, label,
|
||||
.noWS = "inside",
|
||||
...
|
||||
)
|
||||
}
|
||||
@@ -86,11 +87,11 @@ actionLink <- function(inputId, label, icon = NULL, ...) {
|
||||
icon <- validateIcon(icon)
|
||||
|
||||
if (!is.null(icon)) {
|
||||
icon <- span(icon, class = "action-icon")
|
||||
icon <- span(icon, class = "action-icon", .noWS = c("outside", "inside"))
|
||||
}
|
||||
|
||||
if (!is.null(label)) {
|
||||
label <- span(label, class = "action-label")
|
||||
label <- span(label, class = "action-label", .noWS = c("outside", "inside"))
|
||||
}
|
||||
|
||||
tags$a(
|
||||
@@ -99,6 +100,7 @@ actionLink <- function(inputId, label, icon = NULL, ...) {
|
||||
class = "action-button action-link",
|
||||
`data-val` = value,
|
||||
icon, label,
|
||||
.noWS = "inside",
|
||||
...
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ otel_srcref_attributes <- function(srcref, fn_name = NULL) {
|
||||
# Semantic conventions for code: https://opentelemetry.io/docs/specs/semconv/registry/attributes/code/
|
||||
#
|
||||
# Inspiration from https://github.com/r-lib/testthat/pull/2087/files#diff-92de3306849d93d6f7e76c5aaa1b0c037e2d716f72848f8a1c70536e0c8a1564R123-R124
|
||||
filename <- attr(srcref, "srcfile")$filename
|
||||
srcfile <- attr(srcref, "srcfile")
|
||||
# Prefer the original filename (as user typed it) over the normalized path
|
||||
filename <- getSrcfileFilename(srcfile)
|
||||
dropNulls(list(
|
||||
"code.function.name" = fn_name,
|
||||
# Location attrs
|
||||
|
||||
@@ -304,20 +304,23 @@ rassignSrcrefToLabel <- function(
|
||||
if (is.null(srcfile))
|
||||
return(defaultLabel)
|
||||
|
||||
if (is.null(srcfile$lines))
|
||||
src <- getSrcfileLines(srcfile, srcref)
|
||||
lines <- src$lines
|
||||
line_num <- src$line_num
|
||||
|
||||
if (is.null(lines))
|
||||
return(defaultLabel)
|
||||
|
||||
lines <- srcfile$lines
|
||||
# When pasting at the Console, srcfile$lines is not split
|
||||
if (length(lines) == 1) {
|
||||
lines <- strsplit(lines, "\n")[[1]]
|
||||
}
|
||||
|
||||
if (length(lines) < srcref[1]) {
|
||||
if (length(lines) < line_num) {
|
||||
return(defaultLabel)
|
||||
}
|
||||
|
||||
firstLine <- substring(lines[srcref[1]], srcref[2] - 1)
|
||||
firstLine <- substring(lines[line_num], srcref[2] - 1)
|
||||
|
||||
m <- regexec(
|
||||
# Require the first assignment within the line
|
||||
@@ -1160,20 +1163,23 @@ rexprSrcrefToLabel <- function(srcref, defaultLabel, fnName) {
|
||||
if (is.null(srcfile))
|
||||
return(defaultLabel)
|
||||
|
||||
if (is.null(srcfile$lines))
|
||||
src <- getSrcfileLines(srcfile, srcref)
|
||||
lines <- src$lines
|
||||
line_num <- src$line_num
|
||||
|
||||
if (is.null(lines))
|
||||
return(defaultLabel)
|
||||
|
||||
lines <- srcfile$lines
|
||||
# When pasting at the Console, srcfile$lines is not split
|
||||
if (length(lines) == 1) {
|
||||
lines <- strsplit(lines, "\n")[[1]]
|
||||
}
|
||||
|
||||
if (length(lines) < srcref[1]) {
|
||||
if (length(lines) < line_num) {
|
||||
return(defaultLabel)
|
||||
}
|
||||
|
||||
firstLine <- substring(lines[srcref[1]], 1, srcref[2] - 1)
|
||||
firstLine <- substring(lines[line_num], 1, srcref[2] - 1)
|
||||
|
||||
# Require the assignment to be parsed from the start
|
||||
m <- regexec(paste0("^(.*)(<<-|<-|=)\\s*", fnName, "\\s*\\($"), firstLine)
|
||||
|
||||
315
R/runapp.R
315
R/runapp.R
@@ -46,6 +46,12 @@
|
||||
#' only used for recording or running automated tests. Defaults to the
|
||||
#' `shiny.testmode` option, or FALSE if the option is not set.
|
||||
#'
|
||||
#' @return The value passed to [stopApp()], or throws an error if the app was
|
||||
#' stopped with an error.
|
||||
#'
|
||||
#' @seealso [startApp()] for non-blocking mode, [stopApp()] to stop a running
|
||||
#' app.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Start app in the current working directory
|
||||
@@ -93,18 +99,14 @@ runApp <- function(
|
||||
display.mode=c("auto", "normal", "showcase"),
|
||||
test.mode=getOption('shiny.testmode', FALSE)
|
||||
) {
|
||||
|
||||
# * Wrap **all** execution of the app inside the otel promise domain
|
||||
# * While this could be done at a lower level, it allows for _anything_ within
|
||||
# shiny's control to allow for the opportunity to have otel active spans be
|
||||
# reactivated upon promise domain restoration
|
||||
promises::local_otel_promise_domain()
|
||||
|
||||
on.exit({
|
||||
handlerManager$clear()
|
||||
}, add = TRUE)
|
||||
|
||||
if (isRunning()) {
|
||||
# Check for nested blocking runApp() before sourcing app code
|
||||
if (isRunning() && is.null(.globals$runningHandle)) {
|
||||
stop("Can't call `runApp()` from within `runApp()`. If your ",
|
||||
"application code contains `runApp()`, please remove it.")
|
||||
}
|
||||
@@ -116,14 +118,13 @@ runApp <- function(
|
||||
warn = max(1, getOption("warn", default = 1)),
|
||||
pool.scheduler = scheduleTask
|
||||
)
|
||||
on.exit(options(ops), add = TRUE)
|
||||
|
||||
# ============================================================================
|
||||
# Global onStart/onStop callbacks
|
||||
# ============================================================================
|
||||
# Invoke user-defined onStop callbacks, before the application's internal
|
||||
# onStop callbacks.
|
||||
on.exit({
|
||||
# Ensure options are restored and onStop callbacks fire even if
|
||||
# as.shiny.appobj() errors. Once .setupShinyApp() succeeds, the returned
|
||||
# cleanup function takes over and this guard becomes a no-op.
|
||||
setupComplete <- FALSE
|
||||
on.exit(if (!setupComplete) {
|
||||
options(ops)
|
||||
.globals$onStopCallbacks$invoke()
|
||||
.globals$onStopCallbacks <- Callbacks$new()
|
||||
}, add = TRUE)
|
||||
@@ -135,32 +136,140 @@ runApp <- function(
|
||||
# ============================================================================
|
||||
appParts <- as.shiny.appobj(appDir)
|
||||
|
||||
# ============================================================================
|
||||
# Initialize app state object
|
||||
# ============================================================================
|
||||
# This is so calls to getCurrentAppState() can be used to find (A) whether an
|
||||
# app is running and (B), get options and data associated with the app.
|
||||
initCurrentAppState(appParts)
|
||||
on.exit(clearCurrentAppState(), add = TRUE)
|
||||
# Any shinyOptions set after this point will apply to the current app only
|
||||
# (and will not persist after the app stops).
|
||||
result <- .setupShinyApp(
|
||||
appDir, appParts, port, launch.browser, host,
|
||||
workerId, quiet, display.mode, test.mode, ops = ops
|
||||
)
|
||||
setupComplete <- TRUE
|
||||
on.exit(result$cleanup(), add = TRUE)
|
||||
|
||||
# ============================================================================
|
||||
# shinyOptions
|
||||
# Run event loop via httpuv
|
||||
# ============================================================================
|
||||
# A unique identifier associated with this run of this application. It is
|
||||
# shared across sessions.
|
||||
shinyOptions(appToken = createUniqueId(8))
|
||||
# Top-level ..stacktraceoff..; matches with ..stacktraceon in observe(),
|
||||
# reactive(), Callbacks$invoke(), and others
|
||||
..stacktraceoff..(
|
||||
captureStackTraces({
|
||||
while (!.globals$stopped) {
|
||||
..stacktracefloor..(serviceApp())
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
# Set up default cache for app.
|
||||
if (is.null(getShinyOption("cache", default = NULL))) {
|
||||
shinyOptions(cache = cachem::cache_mem(max_size = 200 * 1024^2))
|
||||
if (isTRUE(.globals$reterror)) {
|
||||
stop(.globals$retval)
|
||||
} else if (.globals$retval$visible) {
|
||||
.globals$retval$value
|
||||
} else {
|
||||
invisible(.globals$retval$value)
|
||||
}
|
||||
}
|
||||
|
||||
# Extract appOptions (which is a list) and store them as shinyOptions, for
|
||||
# this app. (This is the only place we have to store settings that are
|
||||
# accessible both the UI and server portion of the app.)
|
||||
applyCapturedAppOptions(appParts$appOptions)
|
||||
#' Start Shiny Application (Non-Blocking)
|
||||
#'
|
||||
#' Starts a Shiny application in non-blocking mode, returning a
|
||||
#' `ShinyAppHandle` immediately while the app runs in the background.
|
||||
#' The `later` event loop services the app, so the R console remains
|
||||
#' available for interaction.
|
||||
#'
|
||||
#' @inheritParams runApp
|
||||
#'
|
||||
#' @return A `ShinyAppHandle` object with methods `stop()`, `status()`,
|
||||
#' `url()`, and `result()`. The `status()` method returns `"running"`,
|
||||
#' `"success"`, or `"error"`. The `result()` method throws an error if called
|
||||
#' while running, or re-throws the error if the app stopped with an error.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Start app in the background
|
||||
#' handle <- startApp("myapp")
|
||||
#'
|
||||
#' # Check status
|
||||
#' handle$status()
|
||||
#' handle$url()
|
||||
#'
|
||||
#' # Stop the app
|
||||
#' handle$stop()
|
||||
#' }
|
||||
#'
|
||||
#' @seealso [runApp()] for blocking mode, [stopApp()] to stop a running app.
|
||||
#' @export
|
||||
startApp <- function(
|
||||
appDir = getwd(),
|
||||
port = getOption("shiny.port"),
|
||||
launch.browser = getOption("shiny.launch.browser", interactive()),
|
||||
host = getOption("shiny.host", "127.0.0.1"),
|
||||
workerId = "",
|
||||
quiet = FALSE,
|
||||
display.mode = c("auto", "normal", "showcase"),
|
||||
test.mode = getOption("shiny.testmode", FALSE)
|
||||
) {
|
||||
# OTEL: `local_otel_promise_domain()` ties its lifetime to this frame,
|
||||
# which exits as soon as the handle is returned — before any request is
|
||||
# served. A persistent global install would instead leak into unrelated
|
||||
# user promises between ticks. Wrap the synchronous setup below (covers
|
||||
# onStart) and each service iteration in `serviceNonBlocking()` (covers
|
||||
# handlers and observers). The domain is dormant between ticks, so it
|
||||
# stays out of user promises created at the console.
|
||||
|
||||
# Make warnings print immediately
|
||||
# Set pool.scheduler to support pool package
|
||||
ops <- options(
|
||||
# Raise warn level to 1, but don't lower it
|
||||
warn = max(1, getOption("warn", default = 1)),
|
||||
pool.scheduler = scheduleTask
|
||||
)
|
||||
|
||||
# Ensure options are restored and onStop callbacks fire even if
|
||||
# as.shiny.appobj() errors. See matching guard in runApp().
|
||||
setupComplete <- FALSE
|
||||
on.exit(if (!setupComplete) {
|
||||
options(ops)
|
||||
.globals$onStopCallbacks$invoke()
|
||||
.globals$onStopCallbacks <- Callbacks$new()
|
||||
}, add = TRUE)
|
||||
|
||||
require(shiny)
|
||||
|
||||
result <- promises::with_otel_promise_domain({
|
||||
appParts <- as.shiny.appobj(appDir)
|
||||
.setupShinyApp(
|
||||
appDir, appParts, port, launch.browser, host,
|
||||
workerId, quiet, display.mode, test.mode, ops = ops
|
||||
)
|
||||
})
|
||||
setupComplete <- TRUE
|
||||
|
||||
handle <- ShinyAppHandle$new(result$appUrl, result$cleanup)
|
||||
.globals$runningHandle <- handle
|
||||
serviceNonBlocking(handle, .globals$serviceGeneration)
|
||||
handle
|
||||
}
|
||||
|
||||
# Shared initialization for runApp() and startApp().
|
||||
# Handles all app setup: options, state, httpuv server, browser launch, etc.
|
||||
# Returns list(appUrl, cleanup) where cleanup() tears down the app.
|
||||
# On setup failure, internal on.exit handlers clean up partial state.
|
||||
.setupShinyApp <- function(appDir, appParts, port, launch.browser, host,
|
||||
workerId, quiet, display.mode, test.mode, ops,
|
||||
caller = parent.frame()) {
|
||||
# Guard on.exit handlers with this flag so they only fire on setup failure.
|
||||
# On success, cleanup responsibility is handed to the caller via the
|
||||
# returned cleanup function.
|
||||
cleanupOnExit <- TRUE
|
||||
|
||||
on.exit(if (cleanupOnExit) handlerManager$clear(), add = TRUE)
|
||||
|
||||
if (isRunning()) {
|
||||
if (!is.null(.globals$runningHandle)) {
|
||||
message("Stopping running Shiny app.")
|
||||
.globals$runningHandle$stop()
|
||||
} else {
|
||||
stop("Can't start a new app while another is running. ",
|
||||
"If your application code contains `runApp()` or `startApp()`, remove it. ",
|
||||
"Otherwise, stop the current app first with stopApp().")
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# runApp options set via shinyApp(options = list(...))
|
||||
@@ -182,25 +291,55 @@ runApp <- function(
|
||||
# | no | yes | use runApp | if it's not missing (runApp specifies), use those |
|
||||
# | yes | yes | use runApp | if it's not missing (runApp specifies), use those |
|
||||
#
|
||||
# I tried to make this as compact and intuitive as possible,
|
||||
# given that there are four distinct possibilities to check
|
||||
# `missing()` runs in the caller's frame: with defaults on the outer
|
||||
# formals, arguments are no longer missing by the time they reach here.
|
||||
appOps <- appParts$options
|
||||
findVal <- function(arg, default) {
|
||||
if (arg %in% names(appOps)) appOps[[arg]] else default
|
||||
}
|
||||
if (evalq(missing(port), caller)) port <- findVal("port", port)
|
||||
if (evalq(missing(launch.browser), caller)) launch.browser <- findVal("launch.browser", launch.browser)
|
||||
if (evalq(missing(host), caller)) host <- findVal("host", host)
|
||||
if (evalq(missing(quiet), caller)) quiet <- findVal("quiet", quiet)
|
||||
if (evalq(missing(display.mode), caller)) display.mode <- findVal("display.mode", display.mode)
|
||||
if (evalq(missing(test.mode), caller)) test.mode <- findVal("test.mode", test.mode)
|
||||
|
||||
if (missing(port))
|
||||
port <- findVal("port", port)
|
||||
if (missing(launch.browser))
|
||||
launch.browser <- findVal("launch.browser", launch.browser)
|
||||
if (missing(host))
|
||||
host <- findVal("host", host)
|
||||
if (missing(quiet))
|
||||
quiet <- findVal("quiet", quiet)
|
||||
if (missing(display.mode))
|
||||
display.mode <- findVal("display.mode", display.mode)
|
||||
if (missing(test.mode))
|
||||
test.mode <- findVal("test.mode", test.mode)
|
||||
on.exit(if (cleanupOnExit) options(ops), add = TRUE)
|
||||
|
||||
# ============================================================================
|
||||
# Global onStart/onStop callbacks
|
||||
# ============================================================================
|
||||
on.exit(if (cleanupOnExit) {
|
||||
.globals$onStopCallbacks$invoke()
|
||||
.globals$onStopCallbacks <- Callbacks$new()
|
||||
}, add = TRUE)
|
||||
|
||||
# ============================================================================
|
||||
# Initialize app state object
|
||||
# ============================================================================
|
||||
# This is so calls to getCurrentAppState() can be used to find (A) whether an
|
||||
# app is running and (B), get options and data associated with the app.
|
||||
initCurrentAppState(appParts)
|
||||
on.exit(if (cleanupOnExit) clearCurrentAppState(), add = TRUE)
|
||||
# Any shinyOptions set after this point will apply to the current app only
|
||||
# (and will not persist after the app stops).
|
||||
|
||||
# ============================================================================
|
||||
# shinyOptions
|
||||
# ============================================================================
|
||||
# A unique identifier associated with this run of this application. It is
|
||||
# shared across sessions.
|
||||
shinyOptions(appToken = createUniqueId(8))
|
||||
|
||||
# Set up default cache for app.
|
||||
if (is.null(getShinyOption("cache", default = NULL))) {
|
||||
shinyOptions(cache = cachem::cache_mem(max_size = 200 * 1024^2))
|
||||
}
|
||||
|
||||
# Extract appOptions (which is a list) and store them as shinyOptions, for
|
||||
# this app. (This is the only place we have to store settings that are
|
||||
# accessible both the UI and server portion of the app.)
|
||||
applyCapturedAppOptions(appParts$appOptions)
|
||||
|
||||
if (is.null(host) || is.na(host)) host <- '0.0.0.0'
|
||||
|
||||
@@ -216,8 +355,14 @@ runApp <- function(
|
||||
# any valid version.
|
||||
ver <- Sys.getenv('SHINY_SERVER_VERSION')
|
||||
if (utils::compareVersion(ver, .shinyServerMinVersion) < 0) {
|
||||
warning('Shiny Server v', .shinyServerMinVersion,
|
||||
' or later is required; please upgrade!')
|
||||
rlang::warn(c(
|
||||
sprintf(
|
||||
"Shiny Server v%s or later is required; please upgrade.",
|
||||
.shinyServerMinVersion
|
||||
),
|
||||
"i" = "If you are not using Shiny Server, you are likely seeing this message because the `SHINY_PORT` environment variable is set in your environment.",
|
||||
"i" = "Avoid using `SHINY_PORT` to prevent this warning."
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +425,7 @@ runApp <- function(
|
||||
|
||||
# If display mode is specified as an argument, apply it (overriding the
|
||||
# value specified in DESCRIPTION, if any).
|
||||
display.mode <- match.arg(display.mode)
|
||||
display.mode <- match.arg(display.mode, c("auto", "normal", "showcase"))
|
||||
if (display.mode == "normal") {
|
||||
setShowcaseDefault(0)
|
||||
}
|
||||
@@ -334,24 +479,21 @@ runApp <- function(
|
||||
# onStart/onStop callbacks
|
||||
# ============================================================================
|
||||
# Set up the onStop before we call onStart, so that it gets called even if an
|
||||
# error happens in onStart.
|
||||
# error happens in onStart or later during startup.
|
||||
if (!is.null(appParts$onStop))
|
||||
on.exit(appParts$onStop(), add = TRUE)
|
||||
on.exit(if (cleanupOnExit) appParts$onStop(), add = TRUE)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
|
||||
# ============================================================================
|
||||
# Start/stop httpuv app
|
||||
# Start httpuv app
|
||||
# ============================================================================
|
||||
server <- startApp(appParts, port, host, quiet)
|
||||
server <- startHttpuvApp(appParts, port, host, quiet)
|
||||
|
||||
# Make the httpuv server object accessible. Needed for calling
|
||||
# addResourcePath while app is running.
|
||||
shinyOptions(server = server)
|
||||
|
||||
on.exit({
|
||||
stopServer(server)
|
||||
}, add = TRUE)
|
||||
on.exit(if (cleanupOnExit) stopServer(server), add = TRUE)
|
||||
|
||||
# ============================================================================
|
||||
# Launch web browser
|
||||
@@ -382,39 +524,52 @@ runApp <- function(
|
||||
# Application hooks
|
||||
# ============================================================================
|
||||
callAppHook("onAppStart", appUrl)
|
||||
on.exit({
|
||||
callAppHook("onAppStop", appUrl)
|
||||
}, add = TRUE)
|
||||
on.exit(if (cleanupOnExit) callAppHook("onAppStop", appUrl), add = TRUE)
|
||||
|
||||
# ============================================================================
|
||||
# Run event loop via httpuv
|
||||
# ============================================================================
|
||||
# Initialize globals used by the event loop and stopApp()
|
||||
.globals$reterror <- NULL
|
||||
.globals$retval <- NULL
|
||||
.globals$stopped <- FALSE
|
||||
# Top-level ..stacktraceoff..; matches with ..stacktraceon in observe(),
|
||||
# reactive(), Callbacks$invoke(), and others
|
||||
..stacktraceoff..(
|
||||
captureStackTraces({
|
||||
while (!.globals$stopped) {
|
||||
..stacktracefloor..(serviceApp())
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (isTRUE(.globals$reterror)) {
|
||||
stop(.globals$retval)
|
||||
# Invalidate any stale non-blocking service loops from a previous app.
|
||||
# Each app launch gets a fresh generation so old callbacks become no-ops.
|
||||
.globals$serviceGeneration <- (.globals$serviceGeneration %||% 0L) + 1L
|
||||
|
||||
# Setup complete - disable on.exit cleanup, hand off to caller
|
||||
cleanupOnExit <- FALSE
|
||||
|
||||
list(
|
||||
appUrl = appUrl,
|
||||
cleanup = .createCleanup(server, appParts, appUrl, ops)
|
||||
)
|
||||
}
|
||||
|
||||
# Consolidated cleanup function for app teardown
|
||||
.createCleanup <- function(server, appParts, appUrl, ops) {
|
||||
cleanedUp <- FALSE
|
||||
function() {
|
||||
if (cleanedUp) return()
|
||||
cleanedUp <<- TRUE
|
||||
|
||||
.globals$stopped <- TRUE
|
||||
.globals$runningHandle <- NULL
|
||||
handlerManager$clear()
|
||||
options(ops)
|
||||
.globals$onStopCallbacks$invoke()
|
||||
.globals$onStopCallbacks <- Callbacks$new()
|
||||
clearCurrentAppState()
|
||||
if (!is.null(appParts$onStop)) appParts$onStop()
|
||||
stopServer(server)
|
||||
callAppHook("onAppStop", appUrl)
|
||||
}
|
||||
else if (.globals$retval$visible)
|
||||
.globals$retval$value
|
||||
else
|
||||
invisible(.globals$retval$value)
|
||||
}
|
||||
|
||||
#' Stop the currently running Shiny app
|
||||
#'
|
||||
#' Stops the currently running Shiny app, returning control to the caller of
|
||||
#' [runApp()].
|
||||
#' [runApp()]. Despite the similar names, `stopApp()` is not the
|
||||
#' counterpart of [startApp()] — it is the counterpart of [runApp()],
|
||||
#' controlling its return value via `returnValue`.
|
||||
#'
|
||||
#' @param returnValue The value that should be returned from
|
||||
#' [runApp()].
|
||||
|
||||
56
R/server.R
56
R/server.R
@@ -387,7 +387,7 @@ removeSubApp <- function(path) {
|
||||
handlerManager$removeWSHandler(path)
|
||||
}
|
||||
|
||||
startApp <- function(appObj, port, host, quiet) {
|
||||
startHttpuvApp <- function(appObj, port, host, quiet) {
|
||||
appHandlers <- createAppHandlers(appObj$httpHandler, appObj$serverFuncSource)
|
||||
handlerManager$addHandler(appHandlers$http, "/", tail = TRUE)
|
||||
handlerManager$addWSHandler(appHandlers$ws, "/", tail = TRUE)
|
||||
@@ -479,9 +479,12 @@ startApp <- function(appObj, port, host, quiet) {
|
||||
}
|
||||
}
|
||||
|
||||
# Run an application that was created by \code{\link{startApp}}. This
|
||||
# Run an application that was created by \code{\link{startHttpuvApp}}. This
|
||||
# function should normally be called in a \code{while(TRUE)} loop.
|
||||
serviceApp <- function() {
|
||||
serviceApp <- function(
|
||||
# rely on lazy evaluation for maximum efficiency
|
||||
timeout = max(1, min(maxTimeout, timerCallbacks$timeToNextEvent(), later::next_op_secs()))
|
||||
) {
|
||||
timerCallbacks$executeElapsed()
|
||||
|
||||
flushReact()
|
||||
@@ -491,13 +494,58 @@ serviceApp <- function() {
|
||||
# to keep the session responsive to user input
|
||||
maxTimeout <- ifelse(interactive(), 100, 1000)
|
||||
|
||||
timeout <- max(1, min(maxTimeout, timerCallbacks$timeToNextEvent(), later::next_op_secs()))
|
||||
service(timeout)
|
||||
|
||||
flushReact()
|
||||
flushPendingSessions()
|
||||
}
|
||||
|
||||
# Non-blocking service loop using later callbacks.
|
||||
# Uses 1ms delay between iterations to yield CPU for console interaction.
|
||||
# The generation token (incremented on every runApp() call) ensures that when
|
||||
# a new app starts, any stale service loop from a previous non-blocking app
|
||||
# exits cleanly instead of continuing to run.
|
||||
# Each iteration wraps `serviceApp()` in `with_otel_promise_domain()` so the
|
||||
# OTEL domain is active while Shiny processes its own work — handlers,
|
||||
# later callbacks, promise fulfillments — all executed synchronously inside
|
||||
# `serviceApp()`. Span wrapping is attached at promise-registration time, so
|
||||
# callbacks registered inside an iteration stay instrumented when they fire
|
||||
# later. The domain is dormant between ticks, keeping it out of unrelated
|
||||
# user promises created while the console is interactive.
|
||||
serviceNonBlocking <- function(handle, generation) {
|
||||
serviceLoop <- function() {
|
||||
if (!identical(.globals$serviceGeneration, generation)) {
|
||||
return(invisible())
|
||||
}
|
||||
if (!.globals$stopped) {
|
||||
promises::with_otel_promise_domain(
|
||||
..stacktraceoff..(
|
||||
captureStackTraces(
|
||||
tryCatch(
|
||||
..stacktracefloor..(serviceApp(.shinyServiceDelaySecs * 1000)),
|
||||
error = function(e) {
|
||||
.globals$stopped <- TRUE
|
||||
.globals$retval <- e
|
||||
.globals$reterror <- TRUE
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (!identical(.globals$serviceGeneration, generation)) {
|
||||
return(invisible())
|
||||
}
|
||||
if (!.globals$stopped) {
|
||||
later::later(serviceLoop, delay = .shinyServiceDelaySecs)
|
||||
} else {
|
||||
handle$stop()
|
||||
}
|
||||
}
|
||||
later::later(serviceLoop, delay = .shinyServiceDelaySecs)
|
||||
}
|
||||
|
||||
.shinyServiceDelaySecs <- 0.001
|
||||
.shinyServerMinVersion <- '0.3.4'
|
||||
|
||||
#' Check whether a Shiny application is running
|
||||
|
||||
@@ -130,8 +130,10 @@ markRenderFunction <- function(
|
||||
# stop warning from happening again for the same object
|
||||
hasExecuted$set(TRUE)
|
||||
}
|
||||
if (is.null(formals(renderFunc))) renderFunc()
|
||||
else renderFunc(...)
|
||||
..stacktraceoff..(
|
||||
if (is.null(formals(renderFunc))) renderFunc()
|
||||
else renderFunc(...)
|
||||
)
|
||||
}
|
||||
|
||||
otelAttrs <-
|
||||
@@ -275,7 +277,7 @@ createRenderFunction <- function(
|
||||
) {
|
||||
renderFunc <- function(shinysession, name, ...) {
|
||||
hybrid_chain(
|
||||
func(),
|
||||
..stacktraceon..(func()),
|
||||
function(value) {
|
||||
transform(value, shinysession, name, ...)
|
||||
}
|
||||
@@ -628,7 +630,7 @@ renderPrint <- function(expr, env = parent.frame(), quoted = FALSE,
|
||||
domain <- createRenderPrintPromiseDomain(width)
|
||||
hybrid_chain(
|
||||
{
|
||||
with_promise_domain(domain, func())
|
||||
with_promise_domain(domain, ..stacktraceon..(func()))
|
||||
},
|
||||
function(value) {
|
||||
res <- withVisible(value)
|
||||
@@ -963,7 +965,7 @@ legacyRenderDataTable <- function(expr, options = NULL, searchDelay = 500,
|
||||
options <- checkDT9(options)
|
||||
res <- checkAsIs(options)
|
||||
hybrid_chain(
|
||||
func(),
|
||||
..stacktraceon..(func()),
|
||||
function(data) {
|
||||
if (length(dim(data)) != 2) return() # expects a rectangular data object
|
||||
if (is.data.frame(data)) data <- as.data.frame(data)
|
||||
|
||||
77
R/utils.R
77
R/utils.R
@@ -1366,31 +1366,62 @@ tryNativeEncoding <- function(string) {
|
||||
if (identical(enc2utf8(string2), string)) string2 else string
|
||||
}
|
||||
|
||||
# similarly, try to source() a file with UTF-8
|
||||
sourceUTF8 <- function(file, envir = globalenv()) {
|
||||
lines <- readUTF8(file)
|
||||
enc <- if (any(Encoding(lines) == 'UTF-8')) 'UTF-8' else 'unknown'
|
||||
src <- srcfilecopy(file, lines, isFile = TRUE) # source reference info
|
||||
# oddly, parse(file) does not work when file contains multibyte chars that
|
||||
# **can** be encoded natively on Windows (might be a bug in base R); we
|
||||
# rewrite the source code in a natively encoded temp file and parse it in this
|
||||
# case (the source reference is still pointed to the original file, though)
|
||||
if (isWindows() && enc == 'unknown') {
|
||||
file <- tempfile(); on.exit(unlink(file), add = TRUE)
|
||||
writeLines(lines, file)
|
||||
}
|
||||
exprs <- try(parse(file, keep.source = FALSE, srcfile = src, encoding = enc))
|
||||
if (inherits(exprs, "try-error")) {
|
||||
diagnoseCode(file)
|
||||
stop("Error sourcing ", file)
|
||||
maybeAnnotateSourceForArk <- function(file, lines) {
|
||||
ark_annotate_source <- get0(".ark_annotate_source", baseenv())
|
||||
|
||||
if (is.null(ark_annotate_source)) {
|
||||
return(lines)
|
||||
}
|
||||
|
||||
# Wrap the exprs in first `{`, then ..stacktraceon..(). It's only really the
|
||||
# ..stacktraceon..() that we care about, but the `{` is needed to make that
|
||||
# possible.
|
||||
exprs <- makeCall(`{`, exprs)
|
||||
# Need to wrap exprs in a list because we want it treated as a single argument
|
||||
exprs <- makeCall(..stacktraceon.., list(exprs))
|
||||
file <- normalizePath(file, mustWork = TRUE, winslash = "/") # Just to be safe
|
||||
uri <- paste0("file:///", sub("^/", "", file)) # Ark expects URIs
|
||||
lines_str <- paste(lines, collapse = "\n")
|
||||
tryCatch(
|
||||
{
|
||||
annotated <- ark_annotate_source(lines_str, uri)
|
||||
if (!is.null(annotated)) {
|
||||
lines <- strsplit(annotated, "\n", fixed = TRUE)[[1]]
|
||||
}
|
||||
},
|
||||
error = function(cnd) {
|
||||
rlang::warn("Can't inject breakpoints for Ark", parent = cnd)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
# similarly, try to source() a file with UTF-8
|
||||
sourceUTF8 <- function(file, envir = globalenv()) {
|
||||
file_norm <- normalizePath(file, mustWork = TRUE, winslash = "/")
|
||||
lines <- readUTF8(file)
|
||||
enc <- if (any(Encoding(lines) == 'UTF-8')) 'UTF-8' else 'unknown'
|
||||
|
||||
# Inject Ark annotations for breakpoints if available
|
||||
lines <- maybeAnnotateSourceForArk(file, lines)
|
||||
|
||||
# Wrap in `..stacktraceon..({...})` using string manipulation before parsing,
|
||||
# with a `#line` directive to map source references back to the original file
|
||||
lines <- c(
|
||||
"..stacktraceon..({",
|
||||
sprintf('#line 1 "%s"', file_norm),
|
||||
lines,
|
||||
"})"
|
||||
)
|
||||
|
||||
# Create a source file copy, i.e. an in-memory srcfile that contains all the
|
||||
# code but refers to an original file
|
||||
src <- srcfilecopy(file, lines, isFile = TRUE)
|
||||
|
||||
# Parse from our annotated lines
|
||||
exprs <- tryCatch(
|
||||
parse(text = lines, keep.source = FALSE, srcfile = src, encoding = enc),
|
||||
error = function(cnd) {
|
||||
diagnoseCode(file)
|
||||
stop("Error sourcing ", file)
|
||||
}
|
||||
)
|
||||
|
||||
eval(exprs, envir)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ We welcome contributions to the **shiny** package. Please see our [CONTRIBUTING.
|
||||
|
||||
## License
|
||||
|
||||
The shiny package as a whole is licensed under the GPLv3. See the [LICENSE](LICENSE) file for more details.
|
||||
The shiny package as a whole is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
||||
|
||||
## R version support
|
||||
|
||||
|
||||
158
cran-comments.md
158
cran-comments.md
@@ -1,121 +1,129 @@
|
||||
## Comments
|
||||
|
||||
#### 2025-12-01
|
||||
#### 2025-12-08
|
||||
|
||||
Hi CRAN,
|
||||
Test has been removed from CRAN checks.
|
||||
|
||||
We made changes to underlying structures that packages are not suppose to test. PRs were provided for each failing package.
|
||||
Also added a couple bug fixes as found by users.
|
||||
|
||||
Maintainer change: From Winston Chang to Carson Sievert.
|
||||
|
||||
Please let me know if you need any further information.
|
||||
Please let me know if you need any further changes.
|
||||
|
||||
Thank you,
|
||||
Carson
|
||||
|
||||
#### 2025-12-04
|
||||
|
||||
Error:
|
||||
|
||||
```
|
||||
Check Details
|
||||
Version: 1.12.0
|
||||
Check: tests
|
||||
Result: ERROR
|
||||
Running ‘testthat.R’ [100s/394s]
|
||||
Running the tests in ‘tests/testthat.R’ failed.
|
||||
Complete output:
|
||||
> library(testthat)
|
||||
> library(shiny)
|
||||
>
|
||||
> test_check("shiny")
|
||||
Saving _problems/test-timer-35.R
|
||||
[ FAIL 1 | WARN 0 | SKIP 22 | PASS 1981 ]
|
||||
|
||||
══ Skipped tests (22) ══════════════════════════════════════════════════════════
|
||||
• File system is not case-sensitive (1): 'test-app.R:36:5'
|
||||
• I'm not sure of a great way to test this without timers. (1):
|
||||
'test-test-server.R:216:3'
|
||||
• Not testing in CI (1): 'test-devmode.R:17:3'
|
||||
• On CRAN (18): 'test-actionButton.R:59:1', 'test-busy-indication.R:1:1',
|
||||
'test-busy-indication.R:15:1', 'test-busy-indication.R:50:1',
|
||||
'test-otel-error.R:1:1', 'test-otel-mock.R:1:1', 'test-pkgdown.R:3:3',
|
||||
'test-reactivity.r:146:1', 'test-reactivity.r:1240:5',
|
||||
'test-reactivity.r:1240:5', 'test-stacks-deep.R:93:1',
|
||||
'test-stacks-deep.R:141:1', 'test-stacks.R:140:3', 'test-tabPanel.R:46:1',
|
||||
'test-tabPanel.R:66:1', 'test-tabPanel.R:73:1', 'test-tabPanel.R:83:1',
|
||||
'test-utils.R:177:3'
|
||||
• {shinytest2} is not installed (1): 'test-test-shinyAppTemplate.R:2:1'
|
||||
|
||||
══ Failed tests ════════════════════════════════════════════════════════════════
|
||||
── Failure ('test-timer.R:35:3'): Unscheduling works ───────────────────────────
|
||||
Expected `timerCallbacks$.times` to be identical to `origTimes`.
|
||||
Differences:
|
||||
`attr(actual, 'row.names')` is an integer vector ()
|
||||
`attr(expected, 'row.names')` is a character vector ()
|
||||
|
||||
|
||||
[ FAIL 1 | WARN 0 | SKIP 22 | PASS 1981 ]
|
||||
Error:
|
||||
! Test failures.
|
||||
Execution halted
|
||||
```
|
||||
|
||||
|
||||
#### 2025-12-03
|
||||
|
||||
```
|
||||
Dear maintainer,
|
||||
|
||||
Please see the problems shown on
|
||||
<https://cran.r-project.org/web/checks/check_results_shiny.html>.
|
||||
|
||||
Please correct before 2025-12-17 to safely retain your package on CRAN.
|
||||
|
||||
The CRAN Team
|
||||
```
|
||||
|
||||
## `R CMD check` results:
|
||||
|
||||
The maintainer change is correctly detected. The URL check sometimes flags a 429
|
||||
error from Wikipedia, which is a temporary issue since the URL is valid when
|
||||
visited manually.
|
||||
0 errors | 0 warning | 1 note
|
||||
|
||||
```
|
||||
* checking CRAN incoming feasibility ... [19s] NOTE
|
||||
Maintainer: 'Carson Sievert <barret@posit.co>'
|
||||
─ checking CRAN incoming feasibility ... [7s/70s] NOTE (1m 9.5s)
|
||||
Maintainer: ‘Carson Sievert <carson@posit.co>’
|
||||
|
||||
New maintainer:
|
||||
Carson Sievert <barret@posit.co>
|
||||
Old maintainer(s):
|
||||
Winston Chang <winston@posit.co>
|
||||
|
||||
Found the following (possibly) invalid URLs:
|
||||
URL: https://en.wikipedia.org/wiki/Reactive_programming
|
||||
From: README.md
|
||||
Status: 429
|
||||
Message: Too Many Requests
|
||||
Days since last update: 5
|
||||
```
|
||||
|
||||
|
||||
## Reverse dependency fixes
|
||||
|
||||
The revdep checks below are failing due to changes made in https://github.com/rstudio/shiny/pull/4249 .
|
||||
|
||||
Unresolved PRs submitted in 2025/06:
|
||||
* omicsTools - https://github.com/cheemalab/omicsTools/pull/1
|
||||
* shinyGovstyle - https://github.com/dfe-analytical-services/shinyGovstyle/pull/155
|
||||
* ShinyLink - https://github.com/cdc-addm/ShinyLink/pull/3
|
||||
* shinySbm - https://github.com/Jo-Theo/shinySbm/pull/2
|
||||
|
||||
Unresolved PR submitted in 2025/10/29:
|
||||
* biodosetools - PR made 2025/10/29 - https://github.com/biodosetools-team/biodosetools/pull/64
|
||||
* inshiny - PR made 2025/10/29 - https://github.com/nicholasdavies/inshiny/pull/1
|
||||
|
||||
## Reverse dependency false positives
|
||||
|
||||
* SouthParkRshiny - New NOTE about installed package size. This is unrelated to any new changes in Shiny.
|
||||
|
||||
> ```
|
||||
> * checking installed package size ... NOTE
|
||||
> installed size is 8.6Mb
|
||||
> sub-directories of 1Mb or more:
|
||||
> data 8.0Mb
|
||||
> ```
|
||||
|
||||
## revdepcheck results
|
||||
|
||||
We checked 1395 reverse dependencies (1388 from CRAN + 7 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
We checked 1383 reverse dependencies (1376 from CRAN + 7 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
|
||||
* We saw 7 new problems
|
||||
* We failed to check 21 packages
|
||||
* We saw 0 new problems
|
||||
* We failed to check 31 packages
|
||||
|
||||
Issues with CRAN packages are summarised below.
|
||||
|
||||
### New problems
|
||||
(This reports the first line of each new failure)
|
||||
|
||||
* biodosetools
|
||||
checking tests ... ERROR
|
||||
|
||||
* inshiny
|
||||
checking examples ... ERROR
|
||||
checking tests ... ERROR
|
||||
checking re-building of vignette outputs ... ERROR
|
||||
|
||||
* omicsTools
|
||||
checking tests ... ERROR
|
||||
|
||||
* shinyGovstyle
|
||||
checking tests ... ERROR
|
||||
|
||||
* ShinyLink
|
||||
checking tests ... ERROR
|
||||
|
||||
* shinySbm
|
||||
checking tests ... ERROR
|
||||
|
||||
* SouthParkRshiny
|
||||
checking installed package size ... NOTE
|
||||
|
||||
### Failed to check
|
||||
|
||||
* AssumpSure
|
||||
* boinet
|
||||
* brms
|
||||
* cheem
|
||||
* ctsem
|
||||
* detourr
|
||||
* FAfA
|
||||
* fio
|
||||
* fitteR
|
||||
* FossilSimShiny
|
||||
* GDINA
|
||||
* ggsem
|
||||
* grandR
|
||||
* hbsaems
|
||||
* langevitour
|
||||
* lavaan.shiny
|
||||
* lcsm
|
||||
* linkspotter
|
||||
* loon.shiny
|
||||
* MOsemiind
|
||||
* MVN
|
||||
* pandemonium
|
||||
* polarisR
|
||||
* RCTrep
|
||||
* rstanarm
|
||||
* semdrw
|
||||
* shotGroups
|
||||
* sphereML
|
||||
* spinifex
|
||||
* SurprisalAnalysis
|
||||
* TestAnaAPP
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.12.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.13.0.9000 | (c) 2012-2026 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
:where([data-shiny-busy-spinners] .recalculating){position:relative}[data-shiny-busy-spinners] .recalculating{min-height:var(--shiny-spinner-size, 32px)}[data-shiny-busy-spinners] .recalculating:after{position:absolute;content:"";--_shiny-spinner-url: var(--shiny-spinner-url, url(spinners/ring.svg));--_shiny-spinner-color: var(--shiny-spinner-color, var(--bs-primary, #007bc2));--_shiny-spinner-size: var(--shiny-spinner-size, 32px);--_shiny-spinner-delay: var(--shiny-spinner-delay, 1s);background:var(--_shiny-spinner-color);width:var(--_shiny-spinner-size);height:var(--_shiny-spinner-size);inset:calc(50% - var(--_shiny-spinner-size) / 2);mask-image:var(--_shiny-spinner-url);-webkit-mask-image:var(--_shiny-spinner-url);opacity:0;animation-delay:var(--_shiny-spinner-delay);animation-name:fade-in;animation-duration:.25s;animation-fill-mode:forwards}[data-shiny-busy-spinners] .recalculating:has(>*),[data-shiny-busy-spinners] .recalculating:empty{opacity:1}[data-shiny-busy-spinners] .recalculating>*:not(.recalculating){opacity:var(--_shiny-fade-opacity);transition:opacity .25s ease var(--shiny-spinner-delay, 1s)}[data-shiny-busy-spinners] .recalculating.html-widget-output{visibility:inherit!important}[data-shiny-busy-spinners] .recalculating.html-widget-output>*{visibility:hidden}[data-shiny-busy-spinners] .recalculating.html-widget-output :after{visibility:visible}[data-shiny-busy-spinners] .recalculating.shiny-html-output:not(.shiny-table-output):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating:not(.shiny-html-output)):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating.shiny-table-output):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(#shiny-disconnected-overlay):after{display:none}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:has(#shiny-disconnected-overlay):after{display:none}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes busy-page-pulse{0%{left:-14%;right:97%}45%{left:0%;right:14%}55%{left:14%;right:0%}to{left:97%;right:-14%}}.shiny-spinner-output-container{--shiny-spinner-size: 0px}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.12.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.13.0.9000 | (c) 2012-2026 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
"use strict";(()=>{document.documentElement.classList.add("autoreload-enabled");var c=window.location.protocol==="https:"?"wss:":"ws:",s=window.location.pathname.replace(/\/?$/,"/")+"autoreload/",i=`${c}//${window.location.host}${s}`,l=document.currentScript?.dataset?.wsUrl||i;async function u(o){let e=new WebSocket(o),n=!1;return new Promise((a,r)=>{e.onopen=()=>{n=!0},e.onerror=t=>{r(t)},e.onclose=()=>{n?a(!1):r(new Error("WebSocket connection failed"))},e.onmessage=function(t){t.data==="autoreload"&&a(!0)}})}async function d(o){return new Promise(e=>setTimeout(e,o))}async function w(){for(;;){try{if(await u(l)){window.location.reload();return}}catch{console.debug("Giving up on autoreload");return}await d(1e3)}}w().catch(o=>{console.error(o)});})();
|
||||
//# sourceMappingURL=shiny-autoreload.js.map
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.12.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.13.0.9000 | (c) 2012-2026 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,Courier New,monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:400}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav,#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.12.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.13.0.9000 | (c) 2012-2026 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
"use strict";(()=>{var m=400;function c(e,s){let t=0;if(e.nodeType===3){let n=e.nodeValue?.replace(/\n/g,"").length??0;if(n>=s)return{element:e,offset:s};t+=n}else if(e.nodeType===1&&e.firstChild){let n=c(e.firstChild,s);if(n.element!==null)return n;t+=n.offset}return e.nextSibling?c(e.nextSibling,s-t):{element:null,offset:t}}function a(e,s,t){let n=0;for(let l=0;l<e.childNodes.length;l++){let i=e.childNodes[l];if(i.nodeType===3){let o=/\n/g,d;for(;(d=o.exec(i.nodeValue))!==null;)if(n++,n===s)return c(i,d.index+t+1)}else if(i.nodeType===1){let o=a(i,s-n,t);if(o.element!==null)return o;n+=o.offset}}return{element:null,offset:n}}function g(e,s){if(!document.createRange)return;let t=document.getElementById("srcref_"+e);if(!t){t=document.createElement("span"),t.id="srcref_"+e;let n=e,l=document.getElementById(s.replace(/\./g,"_")+"_code");if(!l)return;let i=a(l,n[0],n[4]),o=a(l,n[2],n[5]);if(i.element===null||o.element===null)return;let d=document.createRange();i.element.parentNode?.nodeName==="SPAN"&&i.element!==o.element?d.setStartBefore(i.element.parentNode):d.setStart(i.element,i.offset),o.element.parentNode?.nodeName==="SPAN"&&i.element!==o.element?d.setEndAfter(o.element.parentNode):d.setEnd(o.element,o.offset),d.surroundContents(t)}$(t).stop(!0,!0).effect("highlight",null,1600)}window.Shiny&&window.Shiny.addCustomMessageHandler("showcase-src",function(e){e.srcref&&e.srcfile&&g(e.srcref,e.srcfile)});var r=!1,u=function(e,s){let t=s?m:1,n=e?document.getElementById("showcase-sxs-code"):document.getElementById("showcase-code-inline"),l=e?document.getElementById("showcase-code-inline"):document.getElementById("showcase-sxs-code");if(document.getElementById("showcase-app-metadata")===null){let o=$("#showcase-well");e?o.fadeOut(t):o.fadeIn(t)}if(n===null||l===null){console.warn("Could not find the host elements for the code tabs. This is likely a bug in the showcase app.");return}$(n).hide(),$(l).fadeOut(t,function(){let o=document.getElementById("showcase-code-tabs");if(o===null){console.warn("Could not find the code tabs element. This is likely a bug in the showcase app.");return}if(l.removeChild(o),n.appendChild(o),e?p():document.getElementById("showcase-code-content")?.removeAttribute("style"),$(n).fadeIn(t),!e&&(document.getElementById("showcase-app-container")?.removeAttribute("style"),s)){let f=$(n).offset()?.top;f!==void 0&&$(document.body).animate({scrollTop:f})}let d=document.getElementById("readme-md");d!==null&&(d.parentElement?.removeChild(d),e?(l.appendChild(d),$(l).fadeIn(t)):document.getElementById("showcase-app-metadata")?.appendChild(d)),document.getElementById("showcase-code-position-toggle").innerHTML=e?'<i class="fa fa-level-down"></i> show below':'<i class="fa fa-level-up"></i> show with app'}),e&&$(document.body).animate({scrollTop:0},t),r=e,h(e&&s),$(window).trigger("resize")};function h(e){let t=960,n=1,l=document.getElementById("showcase-app-code").offsetWidth;l/2>960?t=l/2:l*.66>960?t=960:(t=l*.66,n=t/960),$("#showcase-app-container").animate({width:t+"px",zoom:n*100+"%"},e?m:0)}var w=function(){u(!r,!0)},y=function(){document.body.offsetWidth>1350&&u(!0,!1)};function p(){document.getElementById("showcase-code-content").style.height=$(window).height()+"px"}function E(){let e=document.getElementById("showcase-markdown-content");if(e!==null){let s=document.getElementById("readme-md");if(s!==null){let t=e.content.cloneNode(!0);s.appendChild(t)}}}$(window).resize(function(){r&&(h(!1),p())});window.toggleCodePosition=w;$(window).on("load",y);$(window).on("load",E);window.hljs&&window.hljs.initHighlightingOnLoad();})();
|
||||
//# sourceMappingURL=shiny-showcase.js.map
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.12.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.13.0.9000 | (c) 2012-2026 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
"use strict";(()=>{var t=eval;window.addEventListener("message",function(a){let e=a.data;e.code&&t(e.code)});})();
|
||||
//# sourceMappingURL=shiny-testmode.js.map
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*! shiny 1.12.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.13.0.9000 | (c) 2012-2026 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
"use strict";
|
||||
(() => {
|
||||
var __create = Object.create;
|
||||
@@ -7206,7 +7206,7 @@ ${duplicateIdMsg}`;
|
||||
// srcts/src/shiny/index.ts
|
||||
var ShinyClass = class {
|
||||
constructor() {
|
||||
this.version = "1.12.0.9000";
|
||||
this.version = "1.13.0.9000";
|
||||
const { inputBindings, fileInputBinding: fileInputBinding2 } = initInputBindings();
|
||||
const { outputBindings } = initOutputBindings();
|
||||
setFileInputBinding(fileInputBinding2);
|
||||
|
||||
2
inst/www/shared/shiny.min.css
vendored
2
inst/www/shared/shiny.min.css
vendored
File diff suppressed because one or more lines are too long
4
inst/www/shared/shiny.min.js
vendored
4
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -463,10 +463,11 @@ textarea.textarea-autoresize.form-control {
|
||||
}
|
||||
}
|
||||
|
||||
// Add spacing between icon and label for actionButton()
|
||||
.action-button:not(.action-link) {
|
||||
// Add spacing between icon and label for action buttons and links (#4348)
|
||||
// Using margin instead of padding so the underline doesn't extend into the gap for links
|
||||
.action-button {
|
||||
.action-icon + .action-label {
|
||||
padding-left: 0.5ch;
|
||||
margin-left: 1ch;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,10 @@ in its \code{DESCRIPTION} file, if any.}
|
||||
only used for recording or running automated tests. Defaults to the
|
||||
\code{shiny.testmode} option, or FALSE if the option is not set.}
|
||||
}
|
||||
\value{
|
||||
The value passed to \code{\link[=stopApp]{stopApp()}}, or throws an error if the app was
|
||||
stopped with an error.
|
||||
}
|
||||
\description{
|
||||
Runs a Shiny application. This function normally does not return; interrupt R
|
||||
to stop the application (usually by pressing Ctrl+C or Esc).
|
||||
@@ -109,3 +113,7 @@ if (interactive()) {
|
||||
runApp(app)
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\code{\link[=startApp]{startApp()}} for non-blocking mode, \code{\link[=stopApp]{stopApp()}} to stop a running
|
||||
app.
|
||||
}
|
||||
|
||||
91
man/startApp.Rd
Normal file
91
man/startApp.Rd
Normal file
@@ -0,0 +1,91 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/runapp.R
|
||||
\name{startApp}
|
||||
\alias{startApp}
|
||||
\title{Start Shiny Application (Non-Blocking)}
|
||||
\usage{
|
||||
startApp(
|
||||
appDir = getwd(),
|
||||
port = getOption("shiny.port"),
|
||||
launch.browser = getOption("shiny.launch.browser", interactive()),
|
||||
host = getOption("shiny.host", "127.0.0.1"),
|
||||
workerId = "",
|
||||
quiet = FALSE,
|
||||
display.mode = c("auto", "normal", "showcase"),
|
||||
test.mode = getOption("shiny.testmode", FALSE)
|
||||
)
|
||||
}
|
||||
\arguments{
|
||||
\item{appDir}{The application to run. Should be one of the following:
|
||||
\itemize{
|
||||
\item A directory containing \code{server.R}, plus, either \code{ui.R} or
|
||||
a \code{www} directory that contains the file \code{index.html}.
|
||||
\item A directory containing \code{app.R}.
|
||||
\item An \code{.R} file containing a Shiny application, ending with an
|
||||
expression that produces a Shiny app object.
|
||||
\item A list with \code{ui} and \code{server} components.
|
||||
\item A Shiny app object created by \code{\link[=shinyApp]{shinyApp()}}.
|
||||
}}
|
||||
|
||||
\item{port}{The TCP port that the application should listen on. If the
|
||||
\code{port} is not specified, and the \code{shiny.port} option is set (with
|
||||
\code{options(shiny.port = XX)}), then that port will be used. Otherwise,
|
||||
use a random port between 3000:8000, excluding ports that are blocked
|
||||
by Google Chrome for being considered unsafe: 3659, 4045, 5060,
|
||||
5061, 6000, 6566, 6665:6669 and 6697. Up to twenty random
|
||||
ports will be tried.}
|
||||
|
||||
\item{launch.browser}{If true, the system's default web browser will be
|
||||
launched automatically after the app is started. Defaults to true in
|
||||
interactive sessions only. The value of this parameter can also be a
|
||||
function to call with the application's URL.}
|
||||
|
||||
\item{host}{The IPv4 address that the application should listen on. Defaults
|
||||
to the \code{shiny.host} option, if set, or \code{"127.0.0.1"} if not. See
|
||||
Details.}
|
||||
|
||||
\item{workerId}{Can generally be ignored. Exists to help some editions of
|
||||
Shiny Server Pro route requests to the correct process.}
|
||||
|
||||
\item{quiet}{Should Shiny status messages be shown? Defaults to FALSE.}
|
||||
|
||||
\item{display.mode}{The mode in which to display the application. If set to
|
||||
the value \code{"showcase"}, shows application code and metadata from a
|
||||
\code{DESCRIPTION} file in the application directory alongside the
|
||||
application. If set to \code{"normal"}, displays the application normally.
|
||||
Defaults to \code{"auto"}, which displays the application in the mode given
|
||||
in its \code{DESCRIPTION} file, if any.}
|
||||
|
||||
\item{test.mode}{Should the application be launched in test mode? This is
|
||||
only used for recording or running automated tests. Defaults to the
|
||||
\code{shiny.testmode} option, or FALSE if the option is not set.}
|
||||
}
|
||||
\value{
|
||||
A \code{ShinyAppHandle} object with methods \code{stop()}, \code{status()},
|
||||
\code{url()}, and \code{result()}. The \code{status()} method returns \code{"running"},
|
||||
\code{"success"}, or \code{"error"}. The \code{result()} method throws an error if called
|
||||
while running, or re-throws the error if the app stopped with an error.
|
||||
}
|
||||
\description{
|
||||
Starts a Shiny application in non-blocking mode, returning a
|
||||
\code{ShinyAppHandle} immediately while the app runs in the background.
|
||||
The \code{later} event loop services the app, so the R console remains
|
||||
available for interaction.
|
||||
}
|
||||
\examples{
|
||||
\dontrun{
|
||||
# Start app in the background
|
||||
handle <- startApp("myapp")
|
||||
|
||||
# Check status
|
||||
handle$status()
|
||||
handle$url()
|
||||
|
||||
# Stop the app
|
||||
handle$stop()
|
||||
}
|
||||
|
||||
}
|
||||
\seealso{
|
||||
\code{\link[=runApp]{runApp()}} for blocking mode, \code{\link[=stopApp]{stopApp()}} to stop a running app.
|
||||
}
|
||||
@@ -12,5 +12,7 @@ stopApp(returnValue = invisible())
|
||||
}
|
||||
\description{
|
||||
Stops the currently running Shiny app, returning control to the caller of
|
||||
\code{\link[=runApp]{runApp()}}.
|
||||
\code{\link[=runApp]{runApp()}}. Despite the similar names, \code{stopApp()} is not the
|
||||
counterpart of \code{\link[=startApp]{startApp()}} — it is the counterpart of \code{\link[=runApp]{runApp()}},
|
||||
controlling its return value via \code{returnValue}.
|
||||
}
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@posit/shiny",
|
||||
"version": "1.11.1-alpha.9001",
|
||||
"version": "1.12.1-alpha.9000",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@posit/shiny",
|
||||
"version": "1.11.1-alpha.9001",
|
||||
"license": "GPL-3.0-only",
|
||||
"version": "1.12.1-alpha.9000",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/bootstrap": "5.2.x",
|
||||
"@types/bootstrap-datepicker": "1.10.0",
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"url": "git+https://github.com/rstudio/shiny.git"
|
||||
},
|
||||
"name": "@posit/shiny",
|
||||
"version": "1.12.0-alpha.9000",
|
||||
"license": "GPL-3.0-only",
|
||||
"version": "1.13.0-alpha.9000",
|
||||
"license": "MIT",
|
||||
"main": "",
|
||||
"browser": "",
|
||||
"types": "srcts/types/extras/globalShiny.d.ts",
|
||||
|
||||
@@ -1,47 +1,45 @@
|
||||
# Revdeps
|
||||
|
||||
## Failed to check (28)
|
||||
## Failed to check (38)
|
||||
|
||||
|package |version |error |warning |note |
|
||||
|:--------------------|:-------|:-----|:-------|:----|
|
||||
|ADAMgui |? | | | |
|
||||
|AssumpSure |? | | | |
|
||||
|boinet |1.5.0 |1 | | |
|
||||
|brms |? | | | |
|
||||
|cheem |? | | | |
|
||||
|ctsem |3.10.4 |1 | |1 |
|
||||
|detourr |? | | | |
|
||||
|EMMAgeo |? | | | |
|
||||
|FactEff |? | | | |
|
||||
|FAfA |? | | | |
|
||||
|fio |0.1.6 |1 | | |
|
||||
|fitteR |? | | | |
|
||||
|FossilSimShiny |? | | | |
|
||||
|GDINA |? | | | |
|
||||
|ggsem |? | | | |
|
||||
|grandR |? | | | |
|
||||
|GSVA |? | | | |
|
||||
|hbsaems |0.1.1 |1 | | |
|
||||
|hbsaems |? | | | |
|
||||
|langevitour |? | | | |
|
||||
|lavaan.shiny |? | | | |
|
||||
|lcsm |? | | | |
|
||||
|linkspotter |1.3.0 |1 | | |
|
||||
|linkspotter |? | | | |
|
||||
|loon.shiny |? | | | |
|
||||
|MOsemiind |0.1.0 |1 | | |
|
||||
|MVN |6.2 |1 | | |
|
||||
|MVN |? | | | |
|
||||
|pandemonium |? | | | |
|
||||
|polarisR |? | | | |
|
||||
|Prostar |? | | | |
|
||||
|RCTrep |1.2.0 |1 | | |
|
||||
|recmap |? | | | |
|
||||
|rstanarm |2.32.2 |1 | | |
|
||||
|semdrw |? | | | |
|
||||
|shotGroups |? | | | |
|
||||
|sphereML |? | | | |
|
||||
|spinifex |? | | | |
|
||||
|StatTeacherAssistant |? | | | |
|
||||
|SurprisalAnalysis |? | | | |
|
||||
|TestAnaAPP |? | | | |
|
||||
|
||||
## New problems (7)
|
||||
|
||||
|package |version |error |warning |note |
|
||||
|:---------------|:-------|:------|:-------|:--------|
|
||||
|[biodosetools](problems.md#biodosetools)|3.7.1 |__+1__ | | |
|
||||
|[inshiny](problems.md#inshiny)|0.1.0 |__+3__ | | |
|
||||
|[omicsTools](problems.md#omicstools)|1.0.5 |__+1__ | | |
|
||||
|[shinyGovstyle](problems.md#shinygovstyle)|0.1.0 |__+1__ | | |
|
||||
|[ShinyLink](problems.md#shinylink)|0.2.2 |__+1__ | | |
|
||||
|[shinySbm](problems.md#shinysbm)|0.1.5 |__+1__ | |1 |
|
||||
|[SouthParkRshiny](problems.md#southparkrshiny)|1.0.0 | | |1 __+1__ |
|
||||
|
||||
|
||||
@@ -1,58 +1,42 @@
|
||||
## revdepcheck results
|
||||
|
||||
We checked 1395 reverse dependencies (1388 from CRAN + 7 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
We checked 1383 reverse dependencies (1376 from CRAN + 7 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
|
||||
* We saw 7 new problems
|
||||
* We failed to check 21 packages
|
||||
* We saw 0 new problems
|
||||
* We failed to check 31 packages
|
||||
|
||||
Issues with CRAN packages are summarised below.
|
||||
|
||||
### New problems
|
||||
(This reports the first line of each new failure)
|
||||
|
||||
* biodosetools
|
||||
checking tests ... ERROR
|
||||
|
||||
* inshiny
|
||||
checking examples ... ERROR
|
||||
checking tests ... ERROR
|
||||
checking re-building of vignette outputs ... ERROR
|
||||
|
||||
* omicsTools
|
||||
checking tests ... ERROR
|
||||
|
||||
* shinyGovstyle
|
||||
checking tests ... ERROR
|
||||
|
||||
* ShinyLink
|
||||
checking tests ... ERROR
|
||||
|
||||
* shinySbm
|
||||
checking tests ... ERROR
|
||||
|
||||
* SouthParkRshiny
|
||||
checking installed package size ... NOTE
|
||||
|
||||
### Failed to check
|
||||
|
||||
* AssumpSure (NA)
|
||||
* boinet (NA)
|
||||
* brms (NA)
|
||||
* cheem (NA)
|
||||
* ctsem (NA)
|
||||
* detourr (NA)
|
||||
* FAfA (NA)
|
||||
* fio (NA)
|
||||
* fitteR (NA)
|
||||
* FossilSimShiny (NA)
|
||||
* GDINA (NA)
|
||||
* ggsem (NA)
|
||||
* grandR (NA)
|
||||
* hbsaems (NA)
|
||||
* langevitour (NA)
|
||||
* lavaan.shiny (NA)
|
||||
* lcsm (NA)
|
||||
* linkspotter (NA)
|
||||
* loon.shiny (NA)
|
||||
* MOsemiind (NA)
|
||||
* MVN (NA)
|
||||
* pandemonium (NA)
|
||||
* polarisR (NA)
|
||||
* RCTrep (NA)
|
||||
* rstanarm (NA)
|
||||
* semdrw (NA)
|
||||
* shotGroups (NA)
|
||||
* sphereML (NA)
|
||||
* spinifex (NA)
|
||||
* SurprisalAnalysis (NA)
|
||||
* TestAnaAPP (NA)
|
||||
|
||||
@@ -1,353 +1 @@
|
||||
# biodosetools
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 3.7.1
|
||||
* GitHub: https://github.com/biodosetools-team/biodosetools
|
||||
* Source code: https://github.com/cran/biodosetools
|
||||
* Date/Publication: 2025-10-22 08:10:02 UTC
|
||||
* Number of recursive dependencies: 132
|
||||
|
||||
Run `revdepcheck::cloud_details(, "biodosetools")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking tests ... ERROR
|
||||
```
|
||||
Running ‘testthat.R’
|
||||
Running the tests in ‘tests/testthat.R’ failed.
|
||||
Complete output:
|
||||
> library(testthat)
|
||||
> library(biodosetools)
|
||||
Loading required package: shiny
|
||||
Loading required package: golem
|
||||
>
|
||||
> test_check("biodosetools")
|
||||
! Problem with `glm()` -> constraint ML optimization will be used instead
|
||||
...
|
||||
- "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">"
|
||||
- " <span class=\"action-label\">go</span>"
|
||||
- "</button>"
|
||||
+ "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">go</button>"
|
||||
|
||||
|
||||
[ FAIL 2 | WARN 1 | SKIP 1 | PASS 455 ]
|
||||
Error:
|
||||
! Test failures.
|
||||
Execution halted
|
||||
```
|
||||
|
||||
# inshiny
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 0.1.0
|
||||
* GitHub: https://github.com/nicholasdavies/inshiny
|
||||
* Source code: https://github.com/cran/inshiny
|
||||
* Date/Publication: 2025-09-09 14:00:13 UTC
|
||||
* Number of recursive dependencies: 53
|
||||
|
||||
Run `revdepcheck::cloud_details(, "inshiny")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking examples ... ERROR
|
||||
```
|
||||
Running examples in ‘inshiny-Ex.R’ failed
|
||||
The error most likely occurred in:
|
||||
|
||||
> ### Name: inline_button
|
||||
> ### Title: Inline action button
|
||||
> ### Aliases: inline_button
|
||||
>
|
||||
> ### ** Examples
|
||||
>
|
||||
> ui <- bslib::page_fixed(
|
||||
...
|
||||
+ label = shiny::span(style = "font-style:italic", "button"),
|
||||
+ icon = shiny::icon("play"),
|
||||
+ meaning = "Update button", accent = "success"),
|
||||
+ "."
|
||||
+ )
|
||||
+ )
|
||||
Error in check_tags(widget, shiny::tags$button(), "shiny::actionButton()") :
|
||||
Unexpected tag structure from shiny::actionButton(). Please contact the package maintainer.
|
||||
Calls: <Anonymous> ... div -> dots_list -> inline -> inline_button -> check_tags
|
||||
Execution halted
|
||||
```
|
||||
|
||||
* checking tests ... ERROR
|
||||
```
|
||||
Running ‘testthat.R’
|
||||
Running the tests in ‘tests/testthat.R’ failed.
|
||||
Complete output:
|
||||
> # This file is part of the standard setup for testthat.
|
||||
> # It is recommended that you do not modify it.
|
||||
> #
|
||||
> # Where should you do additional test configuration?
|
||||
> # Learn more about the roles of various files in:
|
||||
> # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview
|
||||
> # * https://testthat.r-lib.org/articles/special-files.html
|
||||
...
|
||||
▆
|
||||
1. ├─inshiny:::cc(...)
|
||||
2. │ └─base::cat(as.character(x)) at ./helper.R:2:5
|
||||
3. └─inshiny::inline_button(...)
|
||||
4. └─inshiny:::check_tags(widget, shiny::tags$button(), "shiny::actionButton()")
|
||||
|
||||
[ FAIL 2 | WARN 0 | SKIP 9 | PASS 24 ]
|
||||
Error:
|
||||
! Test failures.
|
||||
Execution halted
|
||||
```
|
||||
|
||||
* checking re-building of vignette outputs ... ERROR
|
||||
```
|
||||
Error(s) in re-building vignettes:
|
||||
--- re-building ‘inshiny.Rmd’ using rmarkdown
|
||||
|
||||
Quitting from inshiny.Rmd:64-86 [unnamed-chunk-3]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<error/rlang_error>
|
||||
Error in `check_tags()`:
|
||||
! Unexpected tag structure from shiny::actionButton(). Please contact the package maintainer.
|
||||
---
|
||||
Backtrace:
|
||||
...
|
||||
|
||||
Error: processing vignette 'inshiny.Rmd' failed with diagnostics:
|
||||
Unexpected tag structure from shiny::actionButton(). Please contact the package maintainer.
|
||||
--- failed re-building ‘inshiny.Rmd’
|
||||
|
||||
SUMMARY: processing the following file failed:
|
||||
‘inshiny.Rmd’
|
||||
|
||||
Error: Vignette re-building failed.
|
||||
Execution halted
|
||||
```
|
||||
|
||||
# omicsTools
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 1.0.5
|
||||
* GitHub: https://github.com/YaoxiangLi/omicsTools
|
||||
* Source code: https://github.com/cran/omicsTools
|
||||
* Date/Publication: 2023-07-03 16:20:02 UTC
|
||||
* Number of recursive dependencies: 88
|
||||
|
||||
Run `revdepcheck::cloud_details(, "omicsTools")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking tests ... ERROR
|
||||
```
|
||||
Running ‘spelling.R’
|
||||
Running ‘testthat.R’
|
||||
Running the tests in ‘tests/testthat.R’ failed.
|
||||
Complete output:
|
||||
> # This file is part of the standard setup for testthat.
|
||||
> # It is recommended that you do not modify it.
|
||||
> #
|
||||
> # Where should you do additional test configuration?
|
||||
> # Learn more about the roles of various files in:
|
||||
> # * https://r-pkgs.org/tests.html
|
||||
...
|
||||
- "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">"
|
||||
- " <span class=\"action-label\">go</span>"
|
||||
- "</button>"
|
||||
+ "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">go</button>"
|
||||
|
||||
|
||||
[ FAIL 2 | WARN 0 | SKIP 1 | PASS 94 ]
|
||||
Error:
|
||||
! Test failures.
|
||||
Execution halted
|
||||
```
|
||||
|
||||
# shinyGovstyle
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 0.1.0
|
||||
* GitHub: https://github.com/moj-analytical-services/shinyGovstyle
|
||||
* Source code: https://github.com/cran/shinyGovstyle
|
||||
* Date/Publication: 2024-09-12 14:40:02 UTC
|
||||
* Number of recursive dependencies: 49
|
||||
|
||||
Run `revdepcheck::cloud_details(, "shinyGovstyle")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking tests ... ERROR
|
||||
```
|
||||
Running ‘testthat.R’
|
||||
Running the tests in ‘tests/testthat.R’ failed.
|
||||
Complete output:
|
||||
> library(testthat)
|
||||
> library(shinyGovstyle)
|
||||
>
|
||||
> test_check("shinyGovstyle")
|
||||
Saving _problems/test-backlink_Input-7.R
|
||||
[ FAIL 1 | WARN 0 | SKIP 0 | PASS 125 ]
|
||||
|
||||
...
|
||||
══ Failed tests ════════════════════════════════════════════════════════════════
|
||||
── Failure ('test-backlink_Input.R:4:3'): backlink works ───────────────────────
|
||||
Expected `backlink_check$children[[1]][[2]]` to be identical to "Back".
|
||||
Differences:
|
||||
target is NULL, current is character
|
||||
|
||||
[ FAIL 1 | WARN 0 | SKIP 0 | PASS 125 ]
|
||||
Error:
|
||||
! Test failures.
|
||||
Execution halted
|
||||
```
|
||||
|
||||
# ShinyLink
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 0.2.2
|
||||
* GitHub: NA
|
||||
* Source code: https://github.com/cran/ShinyLink
|
||||
* Date/Publication: 2023-01-18 11:40:05 UTC
|
||||
* Number of recursive dependencies: 128
|
||||
|
||||
Run `revdepcheck::cloud_details(, "ShinyLink")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking tests ... ERROR
|
||||
```
|
||||
Running ‘spelling.R’
|
||||
Running ‘testthat.R’
|
||||
Running the tests in ‘tests/testthat.R’ failed.
|
||||
Complete output:
|
||||
> # This file is part of the standard setup for testthat.
|
||||
> # It is recommended that you do not modify it.
|
||||
> #
|
||||
> # Where should you do additional test configuration?
|
||||
> # Learn more about the roles of various files in:
|
||||
> # * https://r-pkgs.org/tests.html
|
||||
...
|
||||
- "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">"
|
||||
- " <span class=\"action-label\">go</span>"
|
||||
- "</button>"
|
||||
+ "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">go</button>"
|
||||
|
||||
|
||||
[ FAIL 2 | WARN 0 | SKIP 1 | PASS 145 ]
|
||||
Error:
|
||||
! Test failures.
|
||||
Execution halted
|
||||
```
|
||||
|
||||
# shinySbm
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 0.1.5
|
||||
* GitHub: https://github.com/Jo-Theo/shinySbm
|
||||
* Source code: https://github.com/cran/shinySbm
|
||||
* Date/Publication: 2023-09-07 21:50:02 UTC
|
||||
* Number of recursive dependencies: 134
|
||||
|
||||
Run `revdepcheck::cloud_details(, "shinySbm")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking tests ... ERROR
|
||||
```
|
||||
Running ‘spelling.R’
|
||||
Running ‘testthat.R’
|
||||
Running the tests in ‘tests/testthat.R’ failed.
|
||||
Complete output:
|
||||
> # This file is part of the standard setup for testthat.
|
||||
> # It is recommended that you do not modify it.
|
||||
> #
|
||||
> # Where should you do additional test configuration?
|
||||
> # Learn more about the roles of various files in:
|
||||
> # * https://r-pkgs.org/tests.html
|
||||
...
|
||||
- "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">"
|
||||
- " <span class=\"action-label\">go</span>"
|
||||
- "</button>"
|
||||
+ "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">go</button>"
|
||||
|
||||
|
||||
[ FAIL 2 | WARN 0 | SKIP 1 | PASS 141 ]
|
||||
Error:
|
||||
! Test failures.
|
||||
Execution halted
|
||||
```
|
||||
|
||||
## In both
|
||||
|
||||
* checking Rd files ... NOTE
|
||||
```
|
||||
checkRd: (-1) FungusTreeNetwork.Rd:15-21: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) FungusTreeNetwork.Rd:22-28: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) FungusTreeNetwork.Rd:33-34: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) FungusTreeNetwork.Rd:33: Lost braces; missing escapes or markup?
|
||||
33 | \item{tree_tree}{Results of \code{estimateSimpleSBM} for {sbm}
|
||||
| ^
|
||||
checkRd: (-1) FungusTreeNetwork.Rd:35-36: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) FungusTreeNetwork.Rd:35: Lost braces; missing escapes or markup?
|
||||
35 | \item{fungus_tree}{Results of \code{estimateBipartiteSBM} for {sbm}
|
||||
| ^
|
||||
...
|
||||
checkRd: (-1) visSbm.default.Rd:25: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) visSbm.default.Rd:26: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) visSbm.default.Rd:43-44: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) visSbm.default.Rd:45: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) visSbm.default.Rd:46: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) visSbm.default.Rd:47: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) visSbm.default.Rd:48: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) visSbm.default.Rd:49: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) visSbm.default.Rd:50: Lost braces in \itemize; meant \describe ?
|
||||
checkRd: (-1) visSbm.default.Rd:51: Lost braces in \itemize; meant \describe ?
|
||||
```
|
||||
|
||||
# SouthParkRshiny
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 1.0.0
|
||||
* GitHub: https://github.com/Amalan-ConStat/SouthParkRshiny
|
||||
* Source code: https://github.com/cran/SouthParkRshiny
|
||||
* Date/Publication: 2024-03-09 11:10:08 UTC
|
||||
* Number of recursive dependencies: 112
|
||||
|
||||
Run `revdepcheck::cloud_details(, "SouthParkRshiny")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking installed package size ... NOTE
|
||||
```
|
||||
installed size is 8.6Mb
|
||||
sub-directories of 1Mb or more:
|
||||
data 8.0Mb
|
||||
```
|
||||
|
||||
## In both
|
||||
|
||||
* checking data for non-ASCII characters ... NOTE
|
||||
```
|
||||
Note: found 1562 marked UTF-8 strings
|
||||
```
|
||||
|
||||
*Wow, no problems at all. :)*
|
||||
@@ -3,19 +3,26 @@
|
||||
Code
|
||||
actionButton("foo", "Click me")
|
||||
Output
|
||||
<button id="foo" type="button" class="btn btn-default action-button">
|
||||
<span class="action-label">Click me</span>
|
||||
</button>
|
||||
<button id="foo" type="button" class="btn btn-default action-button"><span class="action-label">Click me</span></button>
|
||||
|
||||
---
|
||||
|
||||
Code
|
||||
actionButton("foo", "Click me", icon = icon("star"))
|
||||
Output
|
||||
<button id="foo" type="button" class="btn btn-default action-button">
|
||||
<span class="action-icon">
|
||||
<i class="far fa-star" role="presentation" aria-label="star icon"></i>
|
||||
</span>
|
||||
<span class="action-label">Click me</span>
|
||||
</button>
|
||||
<button id="foo" type="button" class="btn btn-default action-button"><span class="action-icon"><i class="far fa-star" role="presentation" aria-label="star icon"></i></span><span class="action-label">Click me</span></button>
|
||||
|
||||
# actionLink uses .noWS to prevent underline rendering issues
|
||||
|
||||
Code
|
||||
actionLink("foo", "Click me")
|
||||
Output
|
||||
<a id="foo" href="#" class="action-button action-link"><span class="action-label">Click me</span></a>
|
||||
|
||||
---
|
||||
|
||||
Code
|
||||
actionLink("foo", "Click me", icon = icon("star"))
|
||||
Output
|
||||
<a id="foo" href="#" class="action-button action-link"><span class="action-icon"><i class="far fa-star" role="presentation" aria-label="star icon"></i></span><span class="action-label">Click me</span></a>
|
||||
|
||||
|
||||
@@ -1,111 +1,103 @@
|
||||
# integration tests
|
||||
|
||||
Code
|
||||
df
|
||||
df_integration_slim
|
||||
Output
|
||||
num call loc
|
||||
1 68 A [test-stacks.R#3]
|
||||
2 67 B [test-stacks.R#7]
|
||||
3 66 <reactive:C> [test-stacks.R#11]
|
||||
4 44 C
|
||||
5 43 renderTable [test-stacks.R#18]
|
||||
6 42 func
|
||||
7 41 force
|
||||
8 40 withVisible
|
||||
9 39 withCallingHandlers
|
||||
10 38 domain$wrapSync
|
||||
11 37 promises::with_promise_domain
|
||||
12 36 captureStackTraces
|
||||
13 32 tryCatch
|
||||
14 31 do
|
||||
15 30 hybrid_chain
|
||||
16 29 renderFunc
|
||||
17 28 renderTable({ C() }, server = FALSE)
|
||||
18 10 isolate
|
||||
19 9 withCallingHandlers [test-stacks.R#16]
|
||||
20 8 domain$wrapSync
|
||||
21 7 promises::with_promise_domain
|
||||
22 6 captureStackTraces
|
||||
23 2 tryCatch
|
||||
24 1 try
|
||||
25 0 causeError [test-stacks.R#14]
|
||||
1 70 A [test-stacks.R#3]
|
||||
2 69 B [test-stacks.R#7]
|
||||
3 68 <reactive:C> [test-stacks.R#11]
|
||||
4 46 C
|
||||
5 45 renderTable [test-stacks.R#18]
|
||||
6 44 func
|
||||
7 28 renderTable({ C() }, server = FALSE)
|
||||
8 10 isolate
|
||||
9 9 withCallingHandlers [test-stacks.R#16]
|
||||
10 8 domain$wrapSync
|
||||
11 7 promises::with_promise_domain
|
||||
12 6 captureStackTraces
|
||||
13 2 tryCatch
|
||||
14 1 try
|
||||
15 0 causeError [test-stacks.R#14]
|
||||
|
||||
---
|
||||
|
||||
Code
|
||||
df
|
||||
df_integration_full
|
||||
Output
|
||||
num call loc
|
||||
1 71 h
|
||||
2 70 .handleSimpleError
|
||||
3 69 stop
|
||||
4 68 A [test-stacks.R#3]
|
||||
5 67 B [test-stacks.R#7]
|
||||
6 66 <reactive:C> [test-stacks.R#11]
|
||||
7 65 ..stacktraceon..
|
||||
8 64 .func
|
||||
9 63 withVisible
|
||||
10 62 withCallingHandlers
|
||||
11 61 contextFunc
|
||||
12 60 env$runWith
|
||||
13 59 withCallingHandlers
|
||||
14 58 domain$wrapSync
|
||||
15 57 promises::with_promise_domain
|
||||
16 56 captureStackTraces
|
||||
17 55 force
|
||||
18 54 with_otel_span_context
|
||||
19 53 force
|
||||
20 52 domain$wrapSync
|
||||
21 51 promises::with_promise_domain
|
||||
22 50 withReactiveDomain
|
||||
23 49 domain$wrapSync
|
||||
24 48 promises::with_promise_domain
|
||||
25 47 ctx$run
|
||||
26 46 self$.updateValue
|
||||
27 45 ..stacktraceoff..
|
||||
28 44 C
|
||||
29 43 renderTable [test-stacks.R#18]
|
||||
30 42 func
|
||||
31 41 force
|
||||
32 40 withVisible
|
||||
33 39 withCallingHandlers
|
||||
34 38 domain$wrapSync
|
||||
35 37 promises::with_promise_domain
|
||||
36 36 captureStackTraces
|
||||
37 35 doTryCatch
|
||||
38 34 tryCatchOne
|
||||
39 33 tryCatchList
|
||||
40 32 tryCatch
|
||||
41 31 do
|
||||
42 30 hybrid_chain
|
||||
43 29 renderFunc
|
||||
44 28 renderTable({ C() }, server = FALSE)
|
||||
45 27 ..stacktraceon.. [test-stacks.R#17]
|
||||
46 26 contextFunc
|
||||
47 25 env$runWith
|
||||
48 24 withCallingHandlers
|
||||
49 23 domain$wrapSync
|
||||
50 22 promises::with_promise_domain
|
||||
51 21 captureStackTraces
|
||||
52 20 force
|
||||
53 19 with_otel_span_context
|
||||
54 18 force
|
||||
55 17 domain$wrapSync
|
||||
56 16 promises::with_promise_domain
|
||||
57 15 withReactiveDomain
|
||||
58 14 domain$wrapSync
|
||||
59 13 promises::with_promise_domain
|
||||
60 12 ctx$run
|
||||
61 11 ..stacktraceoff..
|
||||
62 10 isolate
|
||||
63 9 withCallingHandlers [test-stacks.R#16]
|
||||
64 8 domain$wrapSync
|
||||
65 7 promises::with_promise_domain
|
||||
66 6 captureStackTraces
|
||||
67 5 doTryCatch [test-stacks.R#15]
|
||||
68 4 tryCatchOne
|
||||
69 3 tryCatchList
|
||||
70 2 tryCatch
|
||||
71 1 try
|
||||
72 0 causeError [test-stacks.R#14]
|
||||
1 73 h
|
||||
2 72 .handleSimpleError
|
||||
3 71 stop
|
||||
4 70 A [test-stacks.R#3]
|
||||
5 69 B [test-stacks.R#7]
|
||||
6 68 <reactive:C> [test-stacks.R#11]
|
||||
7 67 ..stacktraceon..
|
||||
8 66 .func
|
||||
9 65 withVisible
|
||||
10 64 withCallingHandlers
|
||||
11 63 contextFunc
|
||||
12 62 env$runWith
|
||||
13 61 withCallingHandlers
|
||||
14 60 domain$wrapSync
|
||||
15 59 promises::with_promise_domain
|
||||
16 58 captureStackTraces
|
||||
17 57 force
|
||||
18 56 with_otel_span_context
|
||||
19 55 force
|
||||
20 54 domain$wrapSync
|
||||
21 53 promises::with_promise_domain
|
||||
22 52 withReactiveDomain
|
||||
23 51 domain$wrapSync
|
||||
24 50 promises::with_promise_domain
|
||||
25 49 ctx$run
|
||||
26 48 self$.updateValue
|
||||
27 47 ..stacktraceoff..
|
||||
28 46 C
|
||||
29 45 renderTable [test-stacks.R#18]
|
||||
30 44 func
|
||||
31 43 ..stacktraceon..
|
||||
32 42 force
|
||||
33 41 withVisible
|
||||
34 40 withCallingHandlers
|
||||
35 39 domain$wrapSync
|
||||
36 38 promises::with_promise_domain
|
||||
37 37 captureStackTraces
|
||||
38 36 doTryCatch
|
||||
39 35 tryCatchOne
|
||||
40 34 tryCatchList
|
||||
41 33 tryCatch
|
||||
42 32 do
|
||||
43 31 hybrid_chain
|
||||
44 30 renderFunc
|
||||
45 29 ..stacktraceoff..
|
||||
46 28 renderTable({ C() }, server = FALSE)
|
||||
47 27 ..stacktraceon.. [test-stacks.R#17]
|
||||
48 26 contextFunc
|
||||
49 25 env$runWith
|
||||
50 24 withCallingHandlers
|
||||
51 23 domain$wrapSync
|
||||
52 22 promises::with_promise_domain
|
||||
53 21 captureStackTraces
|
||||
54 20 force
|
||||
55 19 with_otel_span_context
|
||||
56 18 force
|
||||
57 17 domain$wrapSync
|
||||
58 16 promises::with_promise_domain
|
||||
59 15 withReactiveDomain
|
||||
60 14 domain$wrapSync
|
||||
61 13 promises::with_promise_domain
|
||||
62 12 ctx$run
|
||||
63 11 ..stacktraceoff..
|
||||
64 10 isolate
|
||||
65 9 withCallingHandlers [test-stacks.R#16]
|
||||
66 8 domain$wrapSync
|
||||
67 7 promises::with_promise_domain
|
||||
68 6 captureStackTraces
|
||||
69 5 doTryCatch [test-stacks.R#15]
|
||||
70 4 tryCatchOne
|
||||
71 3 tryCatchList
|
||||
72 2 tryCatch
|
||||
73 1 try
|
||||
74 0 causeError [test-stacks.R#14]
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-tabs" data-tabsetid="4785">
|
||||
<li>
|
||||
<a href="#tab-4785-1" data-toggle="tab" data-bs-toggle="tab"></a>
|
||||
<a href="#tab-4785-1" data-toggle="tab" data-bs-toggle="tab" disabled></a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="#tab-4785-2" data-toggle="tab" data-bs-toggle="tab" data-value="A">A</a>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
skip_if_shiny_otel_tracer_is_enabled <- function() {
|
||||
if (shiny_otel_tracer()$is_enabled()) {
|
||||
skip("Skipping stack trace tests when OpenTelemetry is already enabled")
|
||||
}
|
||||
}
|
||||
|
||||
# Helper function to create a mock otel span
|
||||
create_mock_otel_span <- function(name = "test_span") {
|
||||
structure(
|
||||
|
||||
118
tests/testthat/helper-stacks.R
Normal file
118
tests/testthat/helper-stacks.R
Normal file
@@ -0,0 +1,118 @@
|
||||
#' @details `extractStackTrace` takes a list of calls (e.g. as returned
|
||||
#' from `conditionStackTrace(cond)`) and returns a data frame with one
|
||||
#' row for each stack frame and the columns `num` (stack frame number),
|
||||
#' `call` (a function name or similar), and `loc` (source file path
|
||||
#' and line number, if available). It was deprecated after shiny 1.0.5 because
|
||||
#' it doesn't support deep stack traces.
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
extractStackTrace <- function(calls,
|
||||
full = get_devmode_option("shiny.fullstacktrace", FALSE),
|
||||
offset = getOption("shiny.stacktraceoffset", TRUE)) {
|
||||
|
||||
srcrefs <- getSrcRefs(calls)
|
||||
if (offset) {
|
||||
# Offset calls vs. srcrefs by 1 to make them more intuitive.
|
||||
# E.g. for "foo [bar.R:10]", line 10 of bar.R will be part of
|
||||
# the definition of foo().
|
||||
srcrefs <- c(utils::tail(srcrefs, -1), list(NULL))
|
||||
}
|
||||
calls <- setSrcRefs(calls, srcrefs)
|
||||
|
||||
callnames <- getCallNames(calls)
|
||||
|
||||
# Hide and show parts of the callstack based on ..stacktrace(on|off)..
|
||||
if (full) {
|
||||
toShow <- rep.int(TRUE, length(calls))
|
||||
} else {
|
||||
# Remove stop(), .handleSimpleError(), and h() calls from the end of
|
||||
# the calls--they don't add any helpful information. But only remove
|
||||
# the last *contiguous* block of them, and then, only if they are the
|
||||
# last thing in the calls list.
|
||||
hideable <- callnames %in% c("stop", ".handleSimpleError", "h")
|
||||
# What's the last that *didn't* match stop/.handleSimpleError/h?
|
||||
lastGoodCall <- max(which(!hideable))
|
||||
toRemove <- length(calls) - lastGoodCall
|
||||
# But don't remove more than 5 levels--that's an indication we might
|
||||
# have gotten it wrong, I guess
|
||||
if (toRemove > 0 && toRemove < 5) {
|
||||
calls <- utils::head(calls, -toRemove)
|
||||
callnames <- utils::head(callnames, -toRemove)
|
||||
}
|
||||
|
||||
toShow <- stripStackTraces(list(callnames))[[1]]
|
||||
|
||||
toShow <-
|
||||
toShow &
|
||||
# doTryCatch, tryCatchOne, and tryCatchList are not informative--they're
|
||||
# just internals for tryCatch
|
||||
!(callnames %in% c("doTryCatch", "tryCatchOne", "tryCatchList")) &
|
||||
# doWithOneRestart and withOneRestart are not informative--they're
|
||||
# just internals for withRestarts
|
||||
!(callnames %in% c("withOneRestart", "doWithOneRestart"))
|
||||
}
|
||||
calls <- calls[toShow]
|
||||
|
||||
|
||||
calls <- rev(calls) # Show in traceback() order
|
||||
index <- rev(which(toShow))
|
||||
width <- floor(log10(max(index))) + 1
|
||||
|
||||
data.frame(
|
||||
num = index,
|
||||
call = getCallNames(calls),
|
||||
loc = getLocs(calls),
|
||||
# category = getCallCategories(calls),
|
||||
stringsAsFactors = FALSE
|
||||
)
|
||||
}
|
||||
|
||||
cleanLocs <- function(locs) {
|
||||
locs[!grepl("test-stacks\\.R", locs, perl = TRUE)] <- ""
|
||||
# sub("^.*#", "", locs)
|
||||
locs
|
||||
}
|
||||
|
||||
dumpTests <- function(df) {
|
||||
print(bquote({
|
||||
expect_equal(df$num, .(df$num))
|
||||
expect_equal(df$call, .(df$call))
|
||||
expect_equal(nzchar(df$loc), .(nzchar(df$loc)))
|
||||
}))
|
||||
}
|
||||
|
||||
# Helper: run a render function whose body throws an error, capture the
|
||||
# stack trace, apply fence-based filtering, and return the filtered data
|
||||
# frame. The render function body should call a function that calls stop().
|
||||
# `needs_session` indicates whether the render function requires
|
||||
# shinysession/name parameters (TRUE for markRenderFunction-based renders
|
||||
# like renderPlot and renderPrint, FALSE for createRenderFunction-based
|
||||
# renders like renderText/renderTable/renderUI/renderImage which can be
|
||||
# called with no args).
|
||||
captureFilteredRenderTrace <- function(render_fn, needs_session = TRUE) {
|
||||
session <- MockShinySession$new()
|
||||
on.exit(if (!session$isClosed()) session$close())
|
||||
|
||||
res <- try({
|
||||
captureStackTraces({
|
||||
isolate({
|
||||
withReactiveDomain(session, {
|
||||
if (needs_session) {
|
||||
render_fn(shinysession = session, name = "testoutput")
|
||||
} else {
|
||||
render_fn()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
silent = TRUE)
|
||||
|
||||
cond <- attr(res, "condition", exact = TRUE)
|
||||
stopifnot(!is.null(cond))
|
||||
stopifnot(!is.null(conditionStackTrace(cond)))
|
||||
|
||||
suppressMessages(
|
||||
extractStackTrace(conditionStackTrace(cond), full = FALSE)
|
||||
)
|
||||
}
|
||||
@@ -94,3 +94,30 @@ test_that("Action button allows icon customization", {
|
||||
expect_equal(as_character(btn2), as_character(btn3))
|
||||
expect_equal(as_character(btn3), as_character(btn4))
|
||||
})
|
||||
|
||||
test_that("actionLink uses .noWS to prevent underline rendering issues", {
|
||||
# actionLink should generate compact HTML without whitespace between tags
|
||||
# This prevents the underline from extending beyond the visible text
|
||||
|
||||
# Test without icon
|
||||
link <- actionLink("test_link", "Click me")
|
||||
link_html <- as.character(link)
|
||||
|
||||
# Verify no newlines/whitespace between closing > and opening <span
|
||||
expect_false(
|
||||
grepl(">\n\\s+<span", link_html),
|
||||
info = "actionLink should not have whitespace between tags"
|
||||
)
|
||||
|
||||
# Test with icon
|
||||
link_icon <- actionLink("test_link2", "Click me", icon = icon("star"))
|
||||
link_icon_html <- as.character(link_icon)
|
||||
|
||||
# Should also have no whitespace between icon span and label span
|
||||
expect_false(
|
||||
grepl(">\n\\s+<span", link_icon_html),
|
||||
info = "actionLink with icon should not have whitespace between tags"
|
||||
)
|
||||
expect_snapshot(actionLink("foo", "Click me"))
|
||||
expect_snapshot(actionLink("foo", "Click me", icon = icon("star")))
|
||||
})
|
||||
|
||||
@@ -1140,7 +1140,7 @@ test_that("Custom render functions that call installExprFunction", {
|
||||
|
||||
|
||||
test_that("cacheWriteHook and cacheReadHook for render functions", {
|
||||
testthat::skip_if(shiny_otel_tracer()$is_enabled(), "Skipping stack trace tests when OpenTelemetry is already enabled")
|
||||
skip_if_shiny_otel_tracer_is_enabled()
|
||||
|
||||
write_hook_n <- 0
|
||||
read_hook_n <- 0
|
||||
|
||||
275
tests/testthat/test-non-blocking.R
Normal file
275
tests/testthat/test-non-blocking.R
Normal file
@@ -0,0 +1,275 @@
|
||||
# Prevent browser launch in interactive sessions
|
||||
withr::local_options(list(shiny.launch.browser = FALSE), .local_envir = teardown_env())
|
||||
|
||||
test_that("ShinyAppHandle lifecycle and API (success path)", {
|
||||
app <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
|
||||
handle <- startApp(app, launch.browser = FALSE, quiet = TRUE)
|
||||
|
||||
# While running
|
||||
|
||||
expect_equal(handle$status(), "running")
|
||||
expect_match(handle$url(), "^http://")
|
||||
expect_error(handle$result(), "App is still running")
|
||||
|
||||
output <- capture.output(print(handle))
|
||||
expect_match(output[1], "Shiny app handle")
|
||||
expect_match(output[2], "URL:")
|
||||
expect_match(output[3], "running")
|
||||
|
||||
# stop() returns invisible self
|
||||
ret <- withVisible(handle$stop())
|
||||
expect_false(ret$visible)
|
||||
expect_identical(ret$value, handle)
|
||||
|
||||
# After stop
|
||||
expect_equal(handle$status(), "success")
|
||||
expect_null(handle$result())
|
||||
|
||||
output <- capture.output(print(handle))
|
||||
expect_match(output[3], "success")
|
||||
|
||||
# Double stop is a silent no-op
|
||||
expect_no_warning(handle$stop())
|
||||
expect_equal(handle$status(), "success")
|
||||
})
|
||||
|
||||
test_that("ShinyAppHandle lifecycle (error path)", {
|
||||
app <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
|
||||
handle <- startApp(app, launch.browser = FALSE, quiet = TRUE)
|
||||
|
||||
stopApp(stop("test_error", call. = FALSE))
|
||||
while (handle$status() == "running") {
|
||||
later::run_now(timeoutSecs = 1)
|
||||
}
|
||||
|
||||
expect_equal(handle$status(), "error")
|
||||
expect_error(handle$result(), "test_error")
|
||||
|
||||
output <- capture.output(print(handle))
|
||||
expect_match(output[3], "error")
|
||||
})
|
||||
|
||||
test_that("handle captures result from stopApp", {
|
||||
app <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
|
||||
handle <- startApp(app, launch.browser = FALSE, quiet = TRUE)
|
||||
|
||||
stopApp("test_result")
|
||||
while (handle$status() == "running") {
|
||||
later::run_now(timeoutSecs = 1)
|
||||
}
|
||||
|
||||
expect_equal(handle$status(), "success")
|
||||
expect_equal(handle$result(), "test_result")
|
||||
})
|
||||
|
||||
test_that("non-blocking auto-stops previous app when starting new one", {
|
||||
app1 <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
app2 <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
|
||||
handle1 <- startApp(app1, launch.browser = FALSE, quiet = TRUE)
|
||||
expect_equal(handle1$status(), "running")
|
||||
|
||||
# Starting a second non-blocking app should auto-stop the first
|
||||
handle2 <- startApp(app2, launch.browser = FALSE, quiet = TRUE)
|
||||
on.exit(handle2$stop(), add = TRUE)
|
||||
|
||||
expect_equal(handle1$status(), "success")
|
||||
expect_equal(handle2$status(), "running")
|
||||
|
||||
handle2$stop()
|
||||
})
|
||||
|
||||
test_that("replacing a non-blocking app does not leave stale service loops", {
|
||||
generations_seen <- integer(0)
|
||||
|
||||
# Mock serviceApp to record which generation is active when called
|
||||
local_mocked_bindings(
|
||||
serviceApp = function(timeout) {
|
||||
generations_seen[[length(generations_seen) + 1L]] <<-
|
||||
.globals$serviceGeneration
|
||||
},
|
||||
.package = "shiny"
|
||||
)
|
||||
|
||||
app1 <- shinyApp(ui = fluidPage(), server = function(input, output) {})
|
||||
app2 <- shinyApp(ui = fluidPage(), server = function(input, output) {})
|
||||
|
||||
handle1 <- startApp(app1, launch.browser = FALSE, quiet = TRUE)
|
||||
gen1 <- .globals$serviceGeneration
|
||||
|
||||
handle2 <- startApp(app2, launch.browser = FALSE, quiet = TRUE)
|
||||
on.exit(handle2$stop(), add = TRUE)
|
||||
gen2 <- .globals$serviceGeneration
|
||||
|
||||
# Reset and let service loops run
|
||||
generations_seen <- integer(0)
|
||||
while (length(generations_seen) < 5L) later::run_now(timeoutSecs = 1)
|
||||
|
||||
# Only the new generation should be servicing
|
||||
expect_true(length(generations_seen) > 0)
|
||||
expect_true(all(generations_seen == gen2))
|
||||
|
||||
handle2$stop()
|
||||
})
|
||||
|
||||
test_that("starting a blocking app invalidates stale non-blocking service loops", {
|
||||
service_calls <- 0L
|
||||
|
||||
local_mocked_bindings(
|
||||
serviceApp = function(timeout) {
|
||||
service_calls <<- service_calls + 1L
|
||||
},
|
||||
.package = "shiny"
|
||||
)
|
||||
|
||||
ns <- asNamespace("shiny")
|
||||
g <- get(".globals", envir = ns)
|
||||
|
||||
# Simulate a non-blocking app at generation 1
|
||||
assign("serviceGeneration", 1L, envir = g)
|
||||
assign("stopped", FALSE, envir = g)
|
||||
shiny:::serviceNonBlocking(list(stop = function() {}), 1L)
|
||||
|
||||
# Simulate stopping app 1, then starting a blocking app which bumps generation
|
||||
assign("stopped", TRUE, envir = g)
|
||||
assign("serviceGeneration", 2L, envir = g)
|
||||
assign("stopped", FALSE, envir = g)
|
||||
|
||||
later::run_now(timeoutSecs = 1)
|
||||
|
||||
expect_equal(service_calls, 0L)
|
||||
})
|
||||
|
||||
test_that("nested runApp in blocking mode still errors", {
|
||||
inner_app <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
|
||||
outer_app <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {},
|
||||
onStart = function() {
|
||||
runApp(inner_app, launch.browser = FALSE, quiet = TRUE)
|
||||
}
|
||||
)
|
||||
|
||||
expect_error(
|
||||
runApp(outer_app, launch.browser = FALSE, quiet = TRUE),
|
||||
"from within `runApp"
|
||||
)
|
||||
})
|
||||
|
||||
test_that("cleanup callbacks run when stopped", {
|
||||
stopped <- FALSE
|
||||
app <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
onStop(function() stopped <<- TRUE)
|
||||
|
||||
handle <- startApp(app, launch.browser = FALSE, quiet = TRUE)
|
||||
handle$stop()
|
||||
|
||||
expect_true(stopped)
|
||||
})
|
||||
|
||||
test_that("old handle doesn't see new app's result", {
|
||||
app1 <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
|
||||
handle1 <- startApp(app1, launch.browser = FALSE, quiet = TRUE)
|
||||
|
||||
stopApp("result1")
|
||||
while (handle1$status() == "running") {
|
||||
later::run_now(1)
|
||||
}
|
||||
expect_equal(handle1$result(), "result1")
|
||||
|
||||
# Start and stop app2
|
||||
app2 <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
handle2 <- startApp(app2, launch.browser = FALSE, quiet = TRUE)
|
||||
|
||||
stopApp("result2")
|
||||
while (handle2$status() == "running") {
|
||||
later::run_now(timeoutSecs = 1)
|
||||
}
|
||||
expect_equal(handle2$result(), "result2")
|
||||
|
||||
# handle1 should still have its original result
|
||||
expect_equal(handle1$result(), "result1")
|
||||
})
|
||||
|
||||
test_that("global isRunning() works with non-blocking apps", {
|
||||
app <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
|
||||
expect_false(isRunning())
|
||||
|
||||
handle <- startApp(app, launch.browser = FALSE, quiet = TRUE)
|
||||
on.exit(handle$stop(), add = TRUE)
|
||||
|
||||
expect_true(isRunning())
|
||||
|
||||
handle$stop()
|
||||
expect_false(isRunning())
|
||||
})
|
||||
|
||||
test_that("startup failure clears app state (regression test)", {
|
||||
# If startup fails after initCurrentAppState() but before cleanupOnExit <- FALSE,
|
||||
# the app state must be cleared so subsequent runApp() calls don't fail with
|
||||
# "Can't start a new app while another is running"
|
||||
|
||||
# Create an app that fails during onStart (which runs after initCurrentAppState)
|
||||
failing_app <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {},
|
||||
onStart = function() stop("Intentional startup failure")
|
||||
)
|
||||
|
||||
# This should fail
|
||||
expect_error(
|
||||
startApp(failing_app, launch.browser = FALSE, quiet = TRUE),
|
||||
"Intentional startup failure"
|
||||
)
|
||||
|
||||
# isRunning() should return FALSE - no app is actually running
|
||||
expect_false(isRunning())
|
||||
|
||||
# A subsequent runApp() call should work
|
||||
working_app <- shinyApp(
|
||||
ui = fluidPage(),
|
||||
server = function(input, output) {}
|
||||
)
|
||||
|
||||
handle <- startApp(working_app, launch.browser = FALSE, quiet = TRUE)
|
||||
on.exit(handle$stop(), add = TRUE)
|
||||
|
||||
expect_equal(handle$status(), "running")
|
||||
handle$stop()
|
||||
})
|
||||
@@ -127,6 +127,7 @@ test_that("ExtendedTask respects reactive_update level otel collection", {
|
||||
})
|
||||
|
||||
test_that("ExtendedTask creates span only when is_recording_otel is TRUE", {
|
||||
skip_if_not_installed("otelsdk")
|
||||
# Test that span is only created when otel is enabled
|
||||
withr::local_options(list(shiny.otel.collect = "reactivity"))
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ test_that("reactive bindCache labels are created", {
|
||||
})
|
||||
|
||||
test_that("ExtendedTask otel labels are created", {
|
||||
skip_if_not_installed("otelsdk")
|
||||
# Record everything
|
||||
localOtelCollect("all")
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
skip_if_not_installed("ggplot2")
|
||||
library(ggplot2)
|
||||
|
||||
# Sort a list by the names of its keys
|
||||
|
||||
@@ -14,6 +14,140 @@ test_that("can access reactive values directly", {
|
||||
expect_equal(y(), 4)
|
||||
})
|
||||
|
||||
describe("srcfilealias in reactive labels", {
|
||||
# When a #line directive specifies a path that differs from the srcfilecopy
|
||||
# filename, R's parser wraps the srcfile in a srcfilealias whose $lines is
|
||||
# NULL. This is exactly what happens in sourceUTF8() when the normalized path
|
||||
# differs from the original.
|
||||
parse_as_srcfilealias <- function(user_code) {
|
||||
code <- c('#line 1 "/absolute/path/to/app.R"', user_code)
|
||||
src <- base::srcfilecopy("app.R", code, isFile = TRUE)
|
||||
exprs <- parse(text = code, keep.source = TRUE, srcfile = src)
|
||||
list(code = code, exprs = exprs, srcrefs = attr(exprs, "srcref"))
|
||||
}
|
||||
|
||||
it("getSrcfileLines() resolves lines from srcfilealias", {
|
||||
parsed <- parse_as_srcfilealias("my_val <- reactiveVal(1)")
|
||||
|
||||
srcref <- parsed$srcrefs[[1]]
|
||||
srcfile <- attr(srcref, "srcfile", exact = TRUE)
|
||||
|
||||
expect_s3_class(srcfile, "srcfilealias")
|
||||
expect_null(srcfile$lines)
|
||||
|
||||
result <- getSrcfileLines(srcfile, srcref)
|
||||
expect_false(is.null(result$lines))
|
||||
expect_equal(result$lines, parsed$code)
|
||||
expect_match(result$lines[result$line_num], "my_val <- reactiveVal")
|
||||
})
|
||||
|
||||
it("getSrcfileLines() works with regular srcfile", {
|
||||
code <- c("x <- 1", "y <- 2")
|
||||
src <- base::srcfilecopy("test.R", code, isFile = TRUE)
|
||||
exprs <- parse(text = code, keep.source = TRUE, srcfile = src)
|
||||
|
||||
srcref <- attr(exprs, "srcref")[[1]]
|
||||
srcfile <- attr(srcref, "srcfile", exact = TRUE)
|
||||
|
||||
expect_false(inherits(srcfile, "srcfilealias"))
|
||||
|
||||
result <- getSrcfileLines(srcfile, srcref)
|
||||
expect_equal(result$lines, code)
|
||||
expect_equal(result$line_num, 1L)
|
||||
})
|
||||
|
||||
it("rassignSrcrefToLabel() extracts label from srcfilealias", {
|
||||
parsed <- parse_as_srcfilealias("my_val <- reactiveVal(1)")
|
||||
srcref <- parsed$srcrefs[[1]]
|
||||
|
||||
label <- rassignSrcrefToLabel(srcref, defaultLabel = "fallback")
|
||||
expect_equal(label, "my_val")
|
||||
})
|
||||
|
||||
it("rexprSrcrefToLabel() extracts label from srcfilealias", {
|
||||
parsed <- parse_as_srcfilealias("my_r <- reactive({ 1 + 1 })")
|
||||
|
||||
# rexprSrcrefToLabel() expects the srcref of the reactive body (the { }),
|
||||
# not the entire assignment. This mirrors how exprToLabel() calls it with
|
||||
# the srcref from the body of the expression created by installExprFunction.
|
||||
assign_expr <- parsed$exprs[[1]]
|
||||
reactive_body <- assign_expr[[3]][[2]] # reactive( <body> )
|
||||
body_srcrefs <- attr(reactive_body, "srcref")
|
||||
srcref <- body_srcrefs[[1]]
|
||||
|
||||
label <- rexprSrcrefToLabel(srcref, defaultLabel = "fallback", fnName = "reactive")
|
||||
expect_equal(label, "my_r")
|
||||
})
|
||||
})
|
||||
|
||||
test_that("sourceUTF8() auto-labels reactives despite srcfilealias", {
|
||||
# sourceUTF8() uses normalizePath() in its #line directive but the original
|
||||
# path for srcfilecopy. When these differ (e.g. macOS /tmp -> /private/tmp),
|
||||
# R creates a srcfilealias whose $lines is NULL. When they match (e.g.
|
||||
# Ubuntu), the #line directive still remaps line numbers. getSrcfileLines()
|
||||
# handles both cases by using srcref[7] (the pre-remap line number).
|
||||
tmp <- tempfile(fileext = ".R")
|
||||
on.exit(unlink(tmp), add = TRUE)
|
||||
|
||||
reactiveConsole(TRUE)
|
||||
on.exit(reactiveConsole(FALSE), add = TRUE)
|
||||
|
||||
writeLines(c(
|
||||
"my_val <- reactiveVal(1)",
|
||||
"my_react <- reactive({ my_val() + 1 })"
|
||||
), tmp)
|
||||
|
||||
env <- new.env(parent = globalenv())
|
||||
sourceUTF8(tmp, envir = env)
|
||||
|
||||
# reactiveVal label (uses rassignSrcrefToLabel)
|
||||
rv_impl <- attr(env$my_val, ".impl", exact = TRUE)
|
||||
expect_equal(
|
||||
rv_impl$.__enclos_env__$private$label,
|
||||
"my_val"
|
||||
)
|
||||
|
||||
# reactive label (uses rexprSrcrefToLabel via exprToLabel)
|
||||
r_observable <- attr(env$my_react, "observable", exact = TRUE)
|
||||
expect_equal(as.character(r_observable$.label), "my_react")
|
||||
})
|
||||
|
||||
describe("srcfilealias filename selection", {
|
||||
parse_as_srcfilealias <- function(user_code, alias_path = "/absolute/path/to/app.R") {
|
||||
code <- c(sprintf('#line 1 "%s"', alias_path), user_code)
|
||||
src <- base::srcfilecopy("app.R", code, isFile = TRUE)
|
||||
exprs <- parse(text = code, keep.source = TRUE, srcfile = src)
|
||||
list(code = code, exprs = exprs, srcrefs = attr(exprs, "srcref"))
|
||||
}
|
||||
|
||||
it("getSrcfileFilename() prefers original unless package file", {
|
||||
lib <- normalizePath(.libPaths()[[1]], winslash = "/", mustWork = FALSE)
|
||||
pkg_path <- file.path(lib, "pkg", "R", "foo.R")
|
||||
|
||||
parsed_pkg <- parse_as_srcfilealias("x <- 1", alias_path = pkg_path)
|
||||
srcref_pkg <- parsed_pkg$srcrefs[[1]]
|
||||
srcfile_pkg <- attr(srcref_pkg, "srcfile", exact = TRUE)
|
||||
expect_equal(getSrcfileFilename(srcfile_pkg), pkg_path)
|
||||
|
||||
parsed_user <- parse_as_srcfilealias("y <- 2", alias_path = "/tmp/user.R")
|
||||
srcref_user <- parsed_user$srcrefs[[1]]
|
||||
srcfile_user <- attr(srcref_user, "srcfile", exact = TRUE)
|
||||
expect_equal(getSrcfileFilename(srcfile_user), "app.R")
|
||||
})
|
||||
})
|
||||
|
||||
test_that("isPackageFile() uses path-boundary matching", {
|
||||
lib <- normalizePath(.libPaths()[[1]], winslash = "/", mustWork = FALSE)
|
||||
|
||||
# A path like "{lib}Extra/foo.R" shares the prefix but is NOT inside the lib
|
||||
fake_path <- paste0(lib, "Extra/foo.R")
|
||||
expect_false(isPackageFile(fake_path))
|
||||
|
||||
# A path actually inside the library SHOULD match
|
||||
real_path <- file.path(lib, "pkg", "R", "foo.R")
|
||||
expect_true(isPackageFile(real_path))
|
||||
})
|
||||
|
||||
test_that("errors in throttled/debounced reactives are catchable", {
|
||||
reactiveConsole(TRUE)
|
||||
on.exit(reactiveConsole(FALSE))
|
||||
|
||||
@@ -240,6 +240,7 @@ test_that("stack trace stripping works", {
|
||||
})
|
||||
|
||||
test_that("coro async generator deep stack count is low", {
|
||||
skip_if_not_installed("coro")
|
||||
gen <- coro::async_generator(function() {
|
||||
for (i in 1:50) {
|
||||
await(coro::async_sleep(0.001))
|
||||
|
||||
@@ -32,97 +32,6 @@ causeError <- function(full) {
|
||||
df
|
||||
}
|
||||
|
||||
#' @details `extractStackTrace` takes a list of calls (e.g. as returned
|
||||
#' from `conditionStackTrace(cond)`) and returns a data frame with one
|
||||
#' row for each stack frame and the columns `num` (stack frame number),
|
||||
#' `call` (a function name or similar), and `loc` (source file path
|
||||
#' and line number, if available). It was deprecated after shiny 1.0.5 because
|
||||
#' it doesn't support deep stack traces.
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
extractStackTrace <- function(calls,
|
||||
full = get_devmode_option("shiny.fullstacktrace", FALSE),
|
||||
offset = getOption("shiny.stacktraceoffset", TRUE)) {
|
||||
|
||||
srcrefs <- getSrcRefs(calls)
|
||||
if (offset) {
|
||||
# Offset calls vs. srcrefs by 1 to make them more intuitive.
|
||||
# E.g. for "foo [bar.R:10]", line 10 of bar.R will be part of
|
||||
# the definition of foo().
|
||||
srcrefs <- c(utils::tail(srcrefs, -1), list(NULL))
|
||||
}
|
||||
calls <- setSrcRefs(calls, srcrefs)
|
||||
|
||||
callnames <- getCallNames(calls)
|
||||
|
||||
# Hide and show parts of the callstack based on ..stacktrace(on|off)..
|
||||
if (full) {
|
||||
toShow <- rep.int(TRUE, length(calls))
|
||||
} else {
|
||||
# Remove stop(), .handleSimpleError(), and h() calls from the end of
|
||||
# the calls--they don't add any helpful information. But only remove
|
||||
# the last *contiguous* block of them, and then, only if they are the
|
||||
# last thing in the calls list.
|
||||
hideable <- callnames %in% c("stop", ".handleSimpleError", "h")
|
||||
# What's the last that *didn't* match stop/.handleSimpleError/h?
|
||||
lastGoodCall <- max(which(!hideable))
|
||||
toRemove <- length(calls) - lastGoodCall
|
||||
# But don't remove more than 5 levels--that's an indication we might
|
||||
# have gotten it wrong, I guess
|
||||
if (toRemove > 0 && toRemove < 5) {
|
||||
calls <- utils::head(calls, -toRemove)
|
||||
callnames <- utils::head(callnames, -toRemove)
|
||||
}
|
||||
|
||||
# This uses a ref-counting scheme. It might make sense to switch this
|
||||
# to a toggling scheme, so the most recent ..stacktrace(on|off)..
|
||||
# directive wins, regardless of what came before it.
|
||||
# Also explicitly remove ..stacktraceon.. because it can appear with
|
||||
# score > 0 but still should never be shown.
|
||||
score <- rep.int(0, length(callnames))
|
||||
score[callnames == "..stacktraceoff.."] <- -1
|
||||
score[callnames == "..stacktraceon.."] <- 1
|
||||
toShow <- (1 + cumsum(score)) > 0 & !(callnames %in% c("..stacktraceon..", "..stacktraceoff..", "..stacktracefloor.."))
|
||||
|
||||
toShow <-
|
||||
toShow &
|
||||
# doTryCatch, tryCatchOne, and tryCatchList are not informative--they're
|
||||
# just internals for tryCatch
|
||||
!(callnames %in% c("doTryCatch", "tryCatchOne", "tryCatchList")) &
|
||||
# doWithOneRestart and withOneRestart are not informative--they're
|
||||
# just internals for withRestarts
|
||||
!(callnames %in% c("withOneRestart", "doWithOneRestart"))
|
||||
}
|
||||
calls <- calls[toShow]
|
||||
|
||||
|
||||
calls <- rev(calls) # Show in traceback() order
|
||||
index <- rev(which(toShow))
|
||||
width <- floor(log10(max(index))) + 1
|
||||
|
||||
data.frame(
|
||||
num = index,
|
||||
call = getCallNames(calls),
|
||||
loc = getLocs(calls),
|
||||
# category = getCallCategories(calls),
|
||||
stringsAsFactors = FALSE
|
||||
)
|
||||
}
|
||||
|
||||
cleanLocs <- function(locs) {
|
||||
locs[!grepl("test-stacks\\.R", locs, perl = TRUE)] <- ""
|
||||
# sub("^.*#", "", locs)
|
||||
locs
|
||||
}
|
||||
|
||||
dumpTests <- function(df) {
|
||||
print(bquote({
|
||||
expect_equal(df$num, .(df$num))
|
||||
expect_equal(df$call, .(df$call))
|
||||
expect_equal(nzchar(df$loc), .(nzchar(df$loc)))
|
||||
}))
|
||||
}
|
||||
|
||||
test_that("integration tests", {
|
||||
if (shiny_otel_tracer()$is_enabled()) {
|
||||
announce_snapshot_file(name = "stacks.md")
|
||||
@@ -139,15 +48,15 @@ test_that("integration tests", {
|
||||
# problems on CRAN.
|
||||
skip_on_cran()
|
||||
|
||||
df <- causeError(full = FALSE)
|
||||
# dumpTests(df)
|
||||
df_integration_slim <- causeError(full = FALSE)
|
||||
# dumpTests(df_integration_slim)
|
||||
|
||||
expect_snapshot(df)
|
||||
expect_snapshot(df_integration_slim)
|
||||
|
||||
df <- causeError(full = TRUE)
|
||||
df_integration_full <- causeError(full = TRUE)
|
||||
|
||||
expect_snapshot(df)
|
||||
# dumpTests(df)
|
||||
expect_snapshot(df_integration_full)
|
||||
# dumpTests(df_integration_full)
|
||||
})
|
||||
|
||||
test_that("shiny.error", {
|
||||
@@ -272,3 +181,170 @@ test_that("observeEvent is not overly stripped (#4162)", {
|
||||
expect_match(st_str, "A__", all = FALSE)
|
||||
expect_match(st_str, "B__", all = FALSE)
|
||||
})
|
||||
|
||||
test_that("renderPlot stack trace fences hide internal rendering pipeline (#4357)", {
|
||||
skip_on_cran()
|
||||
|
||||
skip_if_shiny_otel_tracer_is_enabled()
|
||||
|
||||
userFunc <- function() {
|
||||
stop("test error in renderPlot")
|
||||
}
|
||||
|
||||
df <- captureFilteredRenderTrace(renderPlot({ userFunc() }))
|
||||
|
||||
expect_true("userFunc" %in% df$call)
|
||||
|
||||
# Internal rendering pipeline frames should NOT appear in the filtered
|
||||
# stack trace. These are Shiny internals between the stack trace fences
|
||||
# that currently leak through due to missing fences.
|
||||
internal_render_frames <- c(
|
||||
"drawPlot",
|
||||
"drawReactive",
|
||||
"renderFunc",
|
||||
"startPNG"
|
||||
)
|
||||
|
||||
leaked <- df$call[df$call %in% internal_render_frames]
|
||||
expect_length(leaked, 0)
|
||||
})
|
||||
|
||||
test_that("renderPrint stack trace fences hide internal rendering pipeline (#4357)", {
|
||||
skip_on_cran()
|
||||
|
||||
skip_if_shiny_otel_tracer_is_enabled()
|
||||
|
||||
userFunc <- function() {
|
||||
stop("test error in renderPrint")
|
||||
}
|
||||
|
||||
df <- captureFilteredRenderTrace(renderPrint({ userFunc() }))
|
||||
|
||||
expect_true("userFunc" %in% df$call)
|
||||
|
||||
internal_render_frames <- c("renderFunc")
|
||||
leaked <- df$call[df$call %in% internal_render_frames]
|
||||
expect_length(leaked, 0)
|
||||
})
|
||||
|
||||
test_that("renderText stack trace fences hide internal rendering pipeline (#4357)", {
|
||||
skip_on_cran()
|
||||
|
||||
skip_if_shiny_otel_tracer_is_enabled()
|
||||
|
||||
userFunc <- function() {
|
||||
stop("test error in renderText")
|
||||
}
|
||||
|
||||
df <- captureFilteredRenderTrace(renderText({ userFunc() }), needs_session = FALSE)
|
||||
|
||||
expect_true("userFunc" %in% df$call)
|
||||
|
||||
internal_render_frames <- c("renderFunc")
|
||||
leaked <- df$call[df$call %in% internal_render_frames]
|
||||
expect_length(leaked, 0)
|
||||
})
|
||||
|
||||
test_that("renderUI stack trace fences hide internal rendering pipeline (#4357)", {
|
||||
skip_on_cran()
|
||||
|
||||
skip_if_shiny_otel_tracer_is_enabled()
|
||||
|
||||
userFunc <- function() {
|
||||
stop("test error in renderUI")
|
||||
}
|
||||
|
||||
df <- captureFilteredRenderTrace(renderUI({ userFunc() }), needs_session = FALSE)
|
||||
|
||||
expect_true("userFunc" %in% df$call)
|
||||
|
||||
internal_render_frames <- c("renderFunc")
|
||||
leaked <- df$call[df$call %in% internal_render_frames]
|
||||
expect_length(leaked, 0)
|
||||
})
|
||||
|
||||
test_that("renderTable stack trace fences hide internal rendering pipeline (#4357)", {
|
||||
skip_on_cran()
|
||||
|
||||
skip_if_shiny_otel_tracer_is_enabled()
|
||||
|
||||
userFunc <- function() {
|
||||
stop("test error in renderTable")
|
||||
}
|
||||
|
||||
df <- captureFilteredRenderTrace(
|
||||
renderTable({ userFunc() }, server = FALSE),
|
||||
needs_session = FALSE
|
||||
)
|
||||
|
||||
expect_true("userFunc" %in% df$call)
|
||||
|
||||
internal_render_frames <- c("renderFunc")
|
||||
leaked <- df$call[df$call %in% internal_render_frames]
|
||||
expect_length(leaked, 0)
|
||||
})
|
||||
|
||||
test_that("renderImage stack trace fences hide internal rendering pipeline (#4357)", {
|
||||
skip_on_cran()
|
||||
|
||||
skip_if_shiny_otel_tracer_is_enabled()
|
||||
|
||||
userFunc <- function() {
|
||||
stop("test error in renderImage")
|
||||
}
|
||||
|
||||
df <- captureFilteredRenderTrace(
|
||||
renderImage({ userFunc() }, deleteFile = FALSE),
|
||||
needs_session = FALSE
|
||||
)
|
||||
|
||||
expect_true("userFunc" %in% df$call)
|
||||
|
||||
internal_render_frames <- c("renderFunc")
|
||||
leaked <- df$call[df$call %in% internal_render_frames]
|
||||
expect_length(leaked, 0)
|
||||
})
|
||||
|
||||
test_that("legacyRenderDataTable stack trace fences hide internal rendering pipeline (#4357)", {
|
||||
skip_on_cran()
|
||||
|
||||
skip_if_shiny_otel_tracer_is_enabled()
|
||||
|
||||
userFunc <- function() {
|
||||
stop("test error in renderDataTable")
|
||||
}
|
||||
|
||||
df <- captureFilteredRenderTrace(
|
||||
legacyRenderDataTable({ userFunc() })
|
||||
)
|
||||
|
||||
expect_true("userFunc" %in% df$call)
|
||||
|
||||
internal_render_frames <- c("renderFunc")
|
||||
leaked <- df$call[df$call %in% internal_render_frames]
|
||||
expect_length(leaked, 0)
|
||||
})
|
||||
|
||||
test_that("markRenderFunction preserves user frames outside reactive domain", {
|
||||
skip_on_cran()
|
||||
|
||||
skip_if_shiny_otel_tracer_is_enabled()
|
||||
|
||||
# htmlwidgets-style: exprToFunction + markRenderFunction, no ..stacktraceon..
|
||||
renderWidgetLike <- function(expr, env = parent.frame(), quoted = FALSE) {
|
||||
if (!quoted) expr <- substitute(expr)
|
||||
func <- exprToFunction(expr, env, TRUE)
|
||||
renderFunc <- function() { func() }
|
||||
markRenderFunction(textOutput, renderFunc)
|
||||
}
|
||||
|
||||
userFunc <- function() stop("boom")
|
||||
render_fn <- renderWidgetLike({ userFunc() })
|
||||
|
||||
res <- try(captureStackTraces({ render_fn() }), silent = TRUE)
|
||||
cond <- attr(res, "condition", exact = TRUE)
|
||||
df <- extractStackTrace(conditionStackTrace(cond), full = FALSE)
|
||||
|
||||
expect_true("userFunc" %in% df$call)
|
||||
})
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ test_that("runTests works with a dir app that calls modules and uses testServer"
|
||||
})
|
||||
|
||||
test_that("runTests works with a dir app that calls modules that return reactives and use brushing", {
|
||||
skip_if_not_installed("ggplot2")
|
||||
app <- test_path("..", "test-modules", "107_scatterplot")
|
||||
run <- testthat::expect_output(
|
||||
print(runTests(app)),
|
||||
|
||||
@@ -288,6 +288,9 @@ test_that("works with async", {
|
||||
})
|
||||
|
||||
test_that("works with multiple promises in parallel", {
|
||||
# This test is inherently about timing which is against CRAN's policy.
|
||||
testthat::skip_on_cran()
|
||||
|
||||
server <- function(input, output, session) {
|
||||
output$txt1 <- renderText({
|
||||
future({
|
||||
|
||||
@@ -139,6 +139,7 @@ reference:
|
||||
desc: Functions that are used to run or stop Shiny applications.
|
||||
contents:
|
||||
- runApp
|
||||
- startApp
|
||||
- runGadget
|
||||
- runExample
|
||||
- runGadget
|
||||
|
||||
Reference in New Issue
Block a user