-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Breaking changes to Appdriver tests with new version 0.2.1 #330
Comments
Hello, did you manage to fix this issue? I'm currently struggling with the same thing. To maybe add more context to this issue, I am using the utils::globalVariables(c(
"matches", "rok", "thead", "tr", "th", "red_izo"
)) as suggested by the R Packages (2e) book.
but it's basically identical. |
Hello @MichalLauer, I did not end up resolving the issue yet, I simply downgraded back to v0.2.0 for my CI/CD tests. |
Thanks for letting me know @matt-sd-watson. |
The biggest hurdle we currently in our path toward fixing this is that of needing a minimal, reproducible example. If anyone facing this issue could help us by providing a self-contained minimal example that reproduces this issue, it would be much appreciated. |
Sorry, sure thing! Here is a reprex. Note that the Local library(shiny)
#> Warning: package 'shiny' was built under R version 4.3.1
library(dplyr)
#> Warning: package 'dplyr' was built under R version 4.3.1
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
app_run <- function() {
ui <- fluidPage(
textOutput("txt")
)
server <- function(input, output, session) {
output$txt <- renderText({
data <-
mtcars |>
filter(cyl == 6)
paste0("For ", data$cyl, " the mean mpg is ", mean(data$mpg))
})
}
shinyApp(ui, server)
}
library(testthat)
#> Warning: package 'testthat' was built under R version 4.3.1
#>
#> Attaching package: 'testthat'
#> The following object is masked from 'package:dplyr':
#>
#> matches
library(shinytest2)
#> Warning: package 'shinytest2' was built under R version 4.3.1
Sys.setenv("CHROMOTE_CHROME" = r"(C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe)")
test_that("Shiny app works", {
app <- app_run()
driver <- AppDriver$new(app)
})
#> ── Error: Shiny app works ──────────────────────────────────────────────────────
#> Error in `app_initialize(self, private, app_dir = app_dir, ..., load_timeout = load_timeout,
#> timeout = timeout, wait = wait, expect_values_screenshot_args = expect_values_screenshot_args,
#> screenshot_args = screenshot_args, check_names = check_names,
#> name = name, variant = variant, view = view, height = height,
#> width = width, seed = seed, clean_logs = clean_logs, shiny_args = shiny_args,
#> render_args = render_args, options = options)`: Identified global objects via static code inspection (function (input, output, session); {; output$txt <- renderText({; data <- filter(mtcars, cyl == 6); paste0("For ", data$cyl, " the mean mpg is ", mean(data$mpg)); }); }). Failed to locate global object in the relevant environments: 'cyl'
#>
#>
#> ℹ You can inspect the failed AppDriver object via `rlang::last_error()$app`
#> ℹ AppDriver logs:
#> {shinytest2} R info 10:23:01.81 Start AppDriver initialization
#> {shinytest2} R info 10:23:01.87 Error while initializing AppDriver:
#> Identified global objects via static code inspection (function (input, output, session); {; output$txt <- renderText({; data <- filter(mtcars, cyl == 6); paste0("For ", data$cyl, " the mean mpg is ", mean(data$mpg)); }); }). Failed to locate global object in the relevant environments: 'cyl'
#>
#>
#> Caused by error in `globalsByName()`:
#> ! Identified global objects via static code inspection (function (input, output, session); {; output$txt <- renderText({; data <- filter(mtcars, cyl == 6); paste0("For ", data$cyl, " the mean mpg is ", mean(data$mpg)); }); }). Failed to locate global object in the relevant environments: 'cyl'
#> Backtrace:
#> ▆
#> 1. ├─AppDriver$new(app)
#> 2. │ └─shinytest2 (local) initialize(...)
#> 3. │ └─shinytest2:::app_initialize(...)
#> 4. │ ├─base::withCallingHandlers(...)
#> 5. │ └─shinytest2:::app_initialize_(self, private, ..., view = view)
#> 6. │ └─shinytest2:::app_save(app_dir)
#> 7. │ └─shinytest2:::app_data(app)
#> 8. │ └─shinytest2:::app_server_globals(server)
#> 9. │ └─globals::globalsOf(server, envir = environment(server), recursive = TRUE)
#> 10. │ └─base::tryCatch(...)
#> 11. │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#> 12. │ └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> 13. │ └─value[[3L]](cond)
#> 14. │ └─base::stop(ex)
#> 15. └─shinytest2 (local) `<fn>`(`<smplErrr>`)
#> 16. └─shinytest2:::app_abort(...)
#> 17. └─rlang::abort(..., app = self, call = call)
#> Error:
#> ! Test failed
#> Backtrace:
#> ▆
#> 1. └─testthat::test_that(...)
#> 2. └─withr (local) `<fn>`(`<env>`)
#> 3. ├─base::tryCatch(...)
#> 4. │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#> 5. │ └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> 6. │ └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#> 7. └─base::eval(handler$expr, handler$envir)
#> 8. └─base::eval(handler$expr, handler$envir)
#> 9. └─reporter$stop_if_needed()
#> 10. └─rlang::abort("Test failed", call = NULL) Created on 2023-10-07 with reprex v2.0.2 |
Thanks for the reprex! The source of the problem is that shinytest2 will search your code to find any global variables that might be needed to run your code. But because dplyr uses meta-programming, our static analysis of the app code sees There are two quick ways that you can deal with this problem:
The above is a way out of the current problem, but we may be able to solve this inside shinytest2. @schloerke, this error comes from |
Thanks for the update. I can confirm that both solutions work for me. Looking forward to a potential fix:) What I find interesting is that even tho app_run <- function() {
utils::globalVariables("cyl") # <- Defining as a global variable
ui <- fluidPage(...) # same content...
server <- function(input, output, session) {...} # same content...
shinyApp(ui, server)
} Shouldn't this be picked up also as a global variable? |
@MichalLauer It was a reasonable thing to try, for sure! We're using the globals package, specifically |
@schloerke I've confirmed that setting |
Thanks for the quick update @gadenbuie. I raised a feature request over at HenrikBengtsson/globals, so let's see if this feature is possible. |
tl/dr; No. It is not a global value. It is just to stop the warning raised during R CMD check. You should mask the variables with The bug can be traced up to Reprex: fn <- local({
utils::globalVariables("Species", rlang::current_env());
function() {
subset(iris, Species == "setosa")
};
});
globals::globalsOf(fn, environment(fn), mustExist=T)
#> Error in globalsByName(names, envir = envir, mustExist = mustExist) :
#> Identified global objects via static code inspection (function (); {; subset(iris, Species == "setosa"); }). Failed to locate global object in the relevant environments: ‘Species’
globals::globalsOf(fn, environment(fn), mustExist=F) %>% str()
#> List of 5
#> $ { :.Primitive("{")
#> $ subset :function (x, ...)
#> $ iris :'data.frame': 150 obs. of 5 variables:
#> ..$ Sepal.Length: num [1:150] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#> ..$ Sepal.Width : num [1:150] 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#> ..$ Petal.Length: num [1:150] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#> ..$ Petal.Width : num [1:150] 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#> ..$ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
#> $ == :function (e1, e2)
#> $ Species: NULL
#> - attr(*, "where")=List of 5
#> ..$ { :<environment: base>
#> ..$ subset :<environment: base>
#> ..$ iris :<environment: package:datasets>
#> .. ..- attr(*, "name")= chr "package:datasets"
#> .. ..- attr(*, "path")= chr "/Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library/datasets"
#> ..$ == :<environment: base>
#> ..$ Species: NULL
#> - attr(*, "class")= chr [1:2] "Globals" "list" The important part here is
Looking at the definition for
it just registers a value of Motivation:
We need to have it serialized to load it in a background R process. To properly serialize the app, we need to find all global variables that are used within the function and reconstruct them when running your app in a new R process. Implied question:
Context: (TIL) See Henrik's answer from 2018: HenrikBengtsson/globals#35 (comment) I'd recommend using Garrick's solutions from above or using a third option:
I understand there is no clean solution, but non-standard evaluation makes static code checking impossible. If a file is used to init the |
Thanks for the detailed answer @schloerke! So the final resolution is that Edit: Would it be maybe possible to control the behavior of the mustExist parameter via a global variable? (e.g., to call |
@MichalLauer Using @schloerke thanks for all of the context. I think there's an argument to be made that we can use |
I'd rather not do a sysenv if possible. I'd be ok allowing the user to opt-in to 🦶🔫 via an R option. (😆) @gadenbuie How does updating Line 45 in 176131d
globals <- globals::globalsOf(server, envir = environment(server), recursive = TRUE, mustExist = isTRUE(getOption("shinytest2.app_save.globals_must_exist", TRUE))) sound to you? |
We could also wrap the line in the trycatch and provide a better error and notes on how to set the shinytest2 option.
I'd like to default to The headache of trying to understand why a value doesn't exist is no fun and hard to chase. I'd rather have the error message and notes on how to disable it with an option |
@schloerke I'm all for making it a user opt-in and giving help in the instructions. Given where and how Shiny apps are tested, I think we should consider using envvars rather than an R option. They're easier to set in CI like GitHub actions and they're passed from the parent process to child processes, whereas R options are not. options(foo = "bar")
Sys.setenv(foo = "baz")
r <- callr::r_bg(function() {
list(
opt = getOption("foo"),
env = Sys.getenv("foo")
)
})
r$wait()
r$get_result()
#> $opt
#> NULL
#>
#> $env
#> [1] "baz" |
The app is being saved during local testing as well. So the the CI environment variable wont exist locally and there should be no different behavior for the two testing locations. I believe the option should be set within the test file or shinytest2 setup file. We can discuss offline about details. |
@schloerke and I talked to today and agreed to create an option, named |
Hello guys, thank you very much! Really appriciate the work you are doing. Looking forward to a new release :) |
Hello, Thanks, and Merry Christmas!🎄 |
Hello @gadenbuie @schloerke, any news regarding the implementation? Can I be of some help? :) Edit: will try to create a PR and we'll see if it's the correct implementation |
Here's a small reprex based on the PR to DT (rstudio/DT#1136) library(shiny)
app_run <- function() {
server <- function(input, output, session) {
two <- function() one()
one <- function() "first"
}
shinyApp(fluidPage(), server)
}
app <- app_run()
shinytest2::AppDriver$new(app)
#> Caused by error in `globalsByName()`:
#> ! Identified global objects via static code inspection (function (input, output, session); {; two <- function() one(); one <- function() "first"; }). Failed to locate global object in the relevant environments: 'one' |
The "failed to locate" error is thrown by globals[name] <- list(NULL)
where[name] <- list(NULL)
if (mustExist) {
stop(sprintf("Failed to locate global object in the relevant environments: %s", sQuote(name))) #nolint
} We call Line 45 in 967b669
|
With the release of 0.2.1, my R package test (which contains a Shiny app) is breaking. Below is the code for the test:
Where `cytosel' is the name of the package and the function that calls the Shiny app.
Previously, this test passed with v0.2.0 in Github Actions, but now the following error occurs:
The text was updated successfully, but these errors were encountered: