@@ -6,9 +6,7 @@ local_mocked_stanc <- function(.local_envir = parent.frame()) {
66 )
77}
88
9- # A mocked compile installs an executable, so anything that compiles for real
10- # (even with a mocked compiler) works on a temporary copy rather than writing
11- # into the package's test resources.
9+ # Mocked compiles use temporary model copies to protect test resources.
1210local_external_model <- function (.local_envir = parent.frame()) {
1311 stan_file <- file.path(
1412 withr :: local_tempdir(.local_envir = .local_envir ),
@@ -26,9 +24,7 @@ user_header_routes <- function(header) {
2624 )
2725}
2826
29- # This test is deliberately placed above the file-level skip_if(os_is_macos())
30- # below: it mocks the stanc call and never compiles, so it needs no toolchain
31- # and should run on every platform.
27+ # Keep mocked compilation tests above the toolchain skip below.
3228test_that(" cpp_options user headers allow undefined functions" , {
3329 stan_file <- testing_stan_file(" bernoulli_external" )
3430 user_header <- withr :: local_tempfile(lines = " " , fileext = " .hpp" )
@@ -61,8 +57,6 @@ test_that("cpp_options user headers allow undefined functions", {
6157 )
6258})
6359
64- # Also above the file-level skip_if() below: the compiler is mocked, so these
65- # need no toolchain either.
6660test_that(" compile() reuses the user header from the previous compilation" , {
6761 stan_file <- file.path(withr :: local_tempdir(), " bernoulli_external.stan" )
6862 file.copy(testing_stan_file(" bernoulli_external" ), stan_file )
@@ -128,8 +122,6 @@ test_that("a no-op compile preserves a header supplied via cpp_options", {
128122 wsl_safe_path(absolute_path(user_header ))
129123 )
130124
131- # The executable is up to date, so this call compiles nothing and must leave
132- # the options describing it alone.
133125 with_mocked_cli(
134126 compile_ret = list (status = 0 ),
135127 info_ret = list (status = 1 ),
@@ -157,8 +149,7 @@ test_that("compile() uses a user header supplied to cmdstan_model()", {
157149 user_header = user_header ,
158150 compile = FALSE
159151 )
160- # A mocked compile rather than a dry run: a dry run builds nothing, so it
161- # records nothing about a compiled artifact.
152+ # Use a successful compile so its options are recorded.
162153 with_mocked_cli(
163154 compile_ret = list (status = 0 ),
164155 info_ret = list (status = 1 ),
@@ -192,12 +183,8 @@ test_that("a header configured over a current executable does not rebuild", {
192183 Sys.setFileTime(header , Sys.time() - 60 )
193184 Sys.setFileTime(exe , Sys.time())
194185
195- # Nothing records which header an executable was built with -- the binary
196- # cannot report it and nothing is written alongside it -- so a fresh object
197- # cannot tell a header it was configured with from the one already compiled
198- # in. Rebuilding on the possibility would recompile in every new R session,
199- # so the up-to-date executable is kept and $cpp_options() does not claim a
200- # header it cannot vouch for. Documented under `force_recompile`.
186+ # A fresh object cannot know which header built an existing executable, so it
187+ # keeps the executable without recording the requested header.
201188 for (route in user_header_routes(header )) {
202189 model <- do.call(
203190 cmdstan_model ,
@@ -210,8 +197,7 @@ test_that("a header configured over a current executable does not rebuild", {
210197 )
211198 expect_null(model $ cpp_options()[[" USER_HEADER" ]])
212199 expect_null(model $ cpp_options()[[" user_header" ]])
213- # Source configuration is a separate axis and still reflects the request:
214- # it is what makes stanc accept the undefined functions the header defines.
200+ # Stanc still uses the configured header.
215201 expect_true(model $ .__enclos_env__ $ private $ using_user_header_ )
216202 }
217203})
@@ -303,8 +289,6 @@ test_that("a bare retry after a failed compile keeps the newly supplied header",
303289 expect_equal(private $ user_header_ , resolve_path(h1 ))
304290 expect_false(private $ user_header_dirty_ )
305291
306- # The usual route to this is a bug in h2 itself, so the header the user just
307- # supplied has to survive the failure.
308292 with_mocked_cli(
309293 compile_ret = list (status = 1 ),
310294 info_ret = list (status = 1 ),
@@ -313,8 +297,7 @@ test_that("a bare retry after a failed compile keeps the newly supplied header",
313297 expect_equal(private $ user_header_ , resolve_path(h2 ))
314298 expect_true(private $ user_header_dirty_ )
315299
316- # A bare retry must build h2 rather than reverting to h1 or no-op'ing: the
317- # reuse branch resolves back to h2, so nothing here looks like a change.
300+ # A bare retry must build h2 rather than reverting to h1.
318301 with_mocked_cli(
319302 compile_ret = list (status = 0 ),
320303 info_ret = list (status = 1 ),
@@ -369,8 +352,7 @@ test_that("user_header = NULL clears a header from every supply route", {
369352 )
370353 expect_true(private $ using_user_header_ )
371354
372- # The executable is up to date but was built against a header the model no
373- # longer uses, so clearing has to force a rebuild rather than no-op.
355+ # Clearing a compiled header must force a rebuild.
374356 with_mocked_cli(
375357 compile_ret = list (status = 0 ),
376358 info_ret = list (status = 1 ),
@@ -387,11 +369,7 @@ test_that("duplicate headers of one spelling take the last, as make does", {
387369 first <- withr :: local_tempfile(lines = " " , fileext = " .hpp" )
388370 second <- withr :: local_tempfile(lines = " " , fileext = " .hpp" )
389371
390- # Every duplicate reaches make and a makefile takes the last, which is what
391- # the cpp_options parser implements. Reading with [["USER_HEADER"]] took the
392- # first instead, so the model compiled against a header make would not have
393- # used -- and removing by name dropped only one occurrence, leaving the other
394- # to reach make alongside the header selected here.
372+ # Use the last duplicate and remove every header entry before calling make.
395373 for (spelling in c(" USER_HEADER" , " user_header" )) {
396374 duplicated <- structure(
397375 list (first , second ),
@@ -402,8 +380,7 @@ test_that("duplicate headers of one spelling take the last, as make does", {
402380 expect_length(resolved $ cpp_options , 0 )
403381 }
404382
405- # Across spellings, the last of each is what make would have seen, and
406- # precedence still picks USER_HEADER. Neither survives the strip.
383+ # USER_HEADER still takes precedence across spellings.
407384 mixed <- structure(
408385 list (first , second , first ),
409386 names = c(" user_header" , " USER_HEADER" , " user_header" )
@@ -417,11 +394,7 @@ test_that("a NULL header entry clears a persisted one rather than being ignored"
417394 persisted <- withr :: local_tempfile(lines = " " , fileext = " .hpp" )
418395 first <- withr :: local_tempfile(lines = " " , fileext = " .hpp" )
419396
420- # A NULL entry stands for an explicit `USER_HEADER=`, which make takes as
421- # clearing whatever came before it. Reading presence off the value made NULL
422- # indistinguishable from absence, so the persisted header was carried forward
423- # and the object described a header the build did not use. Every shape below
424- # went wrong the same way; only the trailing-duplicate one was reported.
397+ # A NULL entry emits USER_HEADER= and clears any previous header.
425398 for (spelling in c(" USER_HEADER" , " user_header" )) {
426399 single <- structure(list (NULL ), names = spelling )
427400 resolved <- resolve_user_header(NULL , FALSE , single , previous = persisted )
@@ -599,64 +572,26 @@ test_that("cmdstan_model works with user_header with mock", {
599572test_that(" wsl path conversion is done as expected" , {
600573 tmp_file <- withr :: local_tempfile(lines = hpp , fileext = " .hpp" )
601574 local_mocked_stanc()
602- # Mocked successful compiles rather than dry runs: only a compilation that
603- # produced an executable records the options describing it.
604575
605- # Case 1: arg
606- with_mocked_cli(
607- compile_ret = list (status = 0 ),
608- info_ret = list (status = 1 ),
609- code = {
610- mod <- cmdstan_model(
611- stan_file = local_external_model(),
612- user_header = tmp_file
613- )
614- }
615- )
616-
617- # USER_HEADER is converted
618- # user_header is NULL
619- expect_equal(mod $ cpp_options()[[' USER_HEADER' ]], w_path(tmp_file ))
620- expect_true(is.null(mod $ cpp_options()[[' user_header' ]]))
621-
622- # Case 2: cpp opt USER_HEADER
623- with_mocked_cli(
624- compile_ret = list (status = 0 ),
625- info_ret = list (status = 1 ),
626- code = {
627- mod <- cmdstan_model(
628- stan_file = local_external_model(),
629- cpp_options = list (
630- USER_HEADER = tmp_file
631- )
632- )
633- }
634- )
635-
636- # USER_HEADER is converted
637- # user_header is unconverted
638- expect_equal(mod $ cpp_options()[[' USER_HEADER' ]], w_path(tmp_file ))
639- expect_true(is.null(mod $ cpp_options()[[' user_header' ]]))
640-
641- # Case # 3: only user_header opt
642- with_mocked_cli(
643- compile_ret = list (status = 0 ),
644- info_ret = list (status = 1 ),
645- code = {
646- mod <- cmdstan_model(
647- stan_file = local_external_model(),
648- cpp_options = list (
649- user_header = tmp_file
576+ routes <- user_header_routes(tmp_file )
577+ expected_names <- c(" USER_HEADER" , " USER_HEADER" , " user_header" )
578+ for (i in seq_along(routes )) {
579+ with_mocked_cli(
580+ compile_ret = list (status = 0 ),
581+ info_ret = list (status = 1 ),
582+ code = {
583+ mod <- do.call(
584+ cmdstan_model ,
585+ c(list (stan_file = local_external_model()), routes [[i ]])
650586 )
651- )
652- }
653- )
654-
587+ }
588+ )
655589
656- # In other cases, in the *output* USER_HEADER is windows style user_header is not.
657- # In this case, USER_HEADER is null.
658- expect_true(is.null(mod $ cpp_options()[[' USER_HEADER' ]]))
659- expect_equal(mod $ cpp_options()[[' user_header' ]], w_path(tmp_file ))
590+ expected_name <- expected_names [[i ]]
591+ other_name <- setdiff(c(" USER_HEADER" , " user_header" ), expected_name )
592+ expect_equal(mod $ cpp_options()[[expected_name ]], w_path(tmp_file ))
593+ expect_null(mod $ cpp_options()[[other_name ]])
594+ }
660595})
661596
662597test_that(" user_header precedence order is correct" , {
@@ -667,12 +602,9 @@ test_that("user_header precedence order is correct", {
667602 ))
668603
669604 local_mocked_stanc()
670- # Asserted after a mocked successful compile rather than a dry run: only a
671- # compilation that produced an executable records the options describing it.
672- # The ignored spelling is dropped in every case, so the next compile has a
673- # single source for the header.
605+ # Successful compiles record the selected header and drop ignored spellings.
674606
675- # Case # 1: all 3 specified
607+ # The explicit argument wins.
676608 mod <- cmdstan_model(local_external_model(), compile = FALSE )
677609 with_mocked_cli(
678610 compile_ret = list (status = 0 ),
@@ -688,16 +620,13 @@ test_that("user_header precedence order is correct", {
688620 )
689621 }, " User header specified both" )
690622 )
691- # In this case:
692- # cpp_options[['USER_HEADER']] == tmp_files[1] <- actually used
693- # tmp_files[2] and tmp_files[3] are not stored
694623 expect_equal(
695624 match(!! (mod $ cpp_options()[[' USER_HEADER' ]]), w_path(tmp_files )),
696625 1
697626 )
698627 expect_null(mod $ cpp_options()[[' user_header' ]])
699628
700- # Case # 2: Both opts, but no arg
629+ # USER_HEADER wins over user_header.
701630 mod <- cmdstan_model(local_external_model(), compile = FALSE )
702631 with_mocked_cli(
703632 compile_ret = list (status = 0 ),
@@ -712,16 +641,13 @@ test_that("user_header precedence order is correct", {
712641 )
713642 }, " User header specified both" )
714643 )
715- # In this case:
716- # cpp_options[['USER_HEADER']] == tmp_files[2] <- actually used
717- # tmp_files[3] is not stored
718644 expect_equal(
719645 match(!! (mod $ cpp_options()[[' USER_HEADER' ]]), w_path(tmp_files )),
720646 2
721647 )
722648 expect_null(mod $ cpp_options()[[' user_header' ]])
723649
724- # Case # 3: Both opts, other order
650+ # Option order does not change precedence.
725651 mod <- cmdstan_model(local_external_model(), compile = FALSE )
726652 with_mocked_cli(
727653 compile_ret = list (status = 0 ),
@@ -736,7 +662,6 @@ test_that("user_header precedence order is correct", {
736662 )
737663 }, " User header specified both" )
738664 )
739- # Same as Case #2: USER_HEADER wins whichever order the two appear in
740665 expect_equal(
741666 match(!! (mod $ cpp_options()[[' USER_HEADER' ]]), w_path(tmp_files )),
742667 2
0 commit comments