Skip to content

Commit cfa7c63

Browse files
klmrArcadeAntics
andauthored
Add support for R 4.6 (#391)
* Add support for R 4.6. R 4.6 removes the C (de-facto-)API function Rf_FindVarInFrame(). This commit updates the C implementation to add support for the new API. * Add more unit tests * Work around breaking change in ‘testthat’ v3.3.0 --------- Co-authored-by: ArcadeAntics <nsnjsnd@gmail.com>
1 parent 5180a34 commit cfa7c63

8 files changed

Lines changed: 156 additions & 34 deletions

File tree

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: box
22
Title: Write Reusable, Composable and Modular R Code
3-
Version: 1.2.1.9000
3+
Version: 1.2.2
44
Authors@R: c(
55
person(
66
'Konrad', 'Rudolph',

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# box 1.2.2
2+
3+
## Miscellaneous
4+
5+
* Update the C implementation to adapt to R C API changes in R 4.6.0 (@ArcadeAntics, #391).
6+
7+
18
# box 1.2.1
29

310
## Bug fixes

src/lookup.c

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,33 @@ SEXP strict_extract(SEXP e1, SEXP e2) {
2929
// Return value of `install` does not need to be protected:
3030
// <https://github.com/kalibera/cran-checks/blob/master/rchk/PROTECT.md>
3131
SEXP name = Rf_installTrChar(STRING_ELT(e2, 0));
32-
SEXP ret = Rf_findVarInFrame(e1, name);
33-
34-
if (ret == R_UnboundValue) {
35-
SEXP parent = PROTECT(parent_frame());
36-
SEXP call = PROTECT(sys_call(parent));
37-
SEXP fst_arg = PROTECT(CADR(call));
3832

39-
Rf_errorcall(
40-
call, "name '%s' not found in '%s'",
41-
Rf_translateChar(STRING_ELT(e2, 0)),
42-
Rf_translateChar(PRINTNAME(fst_arg))
43-
);
33+
#if R_VERSION < R_Version(4, 6, 0)
34+
SEXP ret = Rf_findVarInFrame(e1, name);
35+
if (ret != R_UnboundValue) {
36+
if (TYPEOF(ret) == PROMSXP) {
37+
PROTECT(ret);
38+
ret = Rf_eval(ret, e1);
39+
UNPROTECT(1);
40+
}
41+
return ret;
4442
}
43+
#else
44+
SEXP ret = R_getVarEx(name, e1, /* inherits */ FALSE, /* ifnotfound */ NULL);
45+
if (ret) {
46+
return ret;
47+
}
48+
#endif
49+
50+
SEXP parent = PROTECT(parent_frame());
51+
SEXP call = PROTECT(sys_call(parent));
52+
SEXP fst_arg = PROTECT(CADR(call));
4553

46-
return ret;
54+
Rf_errorcall(
55+
call, "name '%s' not found in '%s'",
56+
Rf_translateChar(STRING_ELT(e2, 0)),
57+
Rf_translateChar(PRINTNAME(fst_arg))
58+
);
4759
}
4860

4961
// Cached version of an R function that calls `sys.frame(-1L)`.

tests/testthat/helper-expect.r

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -71,32 +71,64 @@ expect_messages = function (object, has = NULL, has_not = NULL, info = NULL, lab
7171
act = withCallingHandlers(
7272
testthat::quasi_label(rlang::enquo(object), label, arg = 'object'),
7373
message = function (m) {
74-
self$messages = c(self$messages, m$message)
74+
self$messages = c(self$messages, sub('\\n$', '', conditionMessage(m)))
7575
invokeRestart('muffleMessage')
7676
}
7777
)
7878

79-
pretty_messages = paste('*', messages, collapse = '')
80-
81-
find = function (pattern, x) any(grepl(pattern, x))
82-
83-
testthat::expect(
84-
all(vapply(has, find, logical(1L), messages)),
85-
sprintf(
86-
'%s did not produce the expected message(s). It produced:\n%s',
87-
act$lab, pretty_messages
88-
),
89-
info = info
79+
pretty_messages = function (which) {
80+
paste('*', vapply(messages[which], deparse, character(1L)), collapse = '\n')
81+
}
82+
83+
if (! is.null(has) && length(has) != length(messages)) {
84+
testthat::expect(
85+
FALSE,
86+
sprintf(
87+
'%s did not produce %s message(s). It produced:\n%s\n\nExpected:\n%s',
88+
act$lab,
89+
length(messages),
90+
pretty_messages(TRUE),
91+
paste('*', vapply(has[TRUE], deparse, character(1L)), collapse = '\n')
92+
),
93+
info = info
94+
)
95+
}
96+
97+
expected = unlist(box:::map(grepl, has, messages))
98+
99+
if (! all(expected)) {
100+
# We can’t use `testthat::expect(all(expected), …)` here, since that will cause the subsequent assertion to
101+
# be ignored inside a nested assertion, such as when used inside `expect_failure`. This caused the test of
102+
# this helper itself to fail.
103+
testthat::expect(
104+
FALSE,
105+
sprintf(
106+
'%s did not produce the expected message(s). It produced:\n%s\n\nExpected:\n%s',
107+
act$lab,
108+
pretty_messages(! expected),
109+
paste('*', vapply(has[! expected], deparse, character(1L)), collapse = '\n')
110+
),
111+
info = info
112+
)
113+
}
114+
115+
unexpected = vapply(
116+
messages,
117+
function (m) any(vapply(has_not, grepl, logical(1L), m)),
118+
logical(1L)
90119
)
91120

92-
testthat::expect(
93-
! any(vapply(has_not, find, logical(1L), messages)),
94-
sprintf(
95-
'%s produces unexpected message(s). It produced:\n%s',
96-
act$lab, pretty_messages
97-
),
98-
info = info
99-
)
121+
if (any(unexpected)) {
122+
testthat::expect(
123+
FALSE,
124+
sprintf(
125+
'%s produced unwanted message(s):\n%s',
126+
act$lab,
127+
pretty_messages(unexpected)
128+
),
129+
info = info
130+
)
131+
}
100132
}
101133

102134
in_globalenv = function (expr) {

tests/testthat/mod/active2.r

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.on_load = function (ns) {
2+
makeActiveBinding(
3+
'binding',
4+
function () {
5+
message('get')
6+
1L
7+
},
8+
ns
9+
)
10+
}
11+
12+
box::export(binding)

tests/testthat/test-active.r

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,30 @@ test_that('active bindings can be attached', {
1414
expect_equal(binding, 1L)
1515
expect_message(binding, 'get')
1616
})
17+
18+
test_that('active binding can be exported from .on_load()', {
19+
box::use(mod/active2)
20+
expect_setequal(ls(active2), 'binding')
21+
expect_true(bindingIsActive('binding', active2))
22+
expect_equal(active2$binding, 1L)
23+
expect_message(active2$binding, 'get')
24+
})
25+
26+
test_that('active binding can be attached from .on_load()', {
27+
box::use(mod/active2[...])
28+
expect_true(bindingIsActive('binding', parent.env(environment())))
29+
expect_equal(binding, 1L)
30+
expect_message(binding, 'get')
31+
})
32+
33+
test_that('active binding is lazily evaluted', {
34+
box::use(active = mod/active[...])
35+
36+
f = function (x) {
37+
message('f')
38+
x
39+
}
40+
41+
expect_messages(f(active$binding), c('^f', '^get'))
42+
expect_messages(f(binding), c('^f', '^get'))
43+
})

tests/testthat/test-expectations.r

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,35 @@ test_that('`expect_not_in` works', {
3838
expect_failure(expect_not_in(2, c(1, 2, 3)))
3939
expect_failure(expect_not_in('A', LETTERS))
4040
})
41+
42+
test_that('`expect_messages` works', {
43+
expect_messages(
44+
{
45+
message('foo')
46+
message('bar')
47+
},
48+
c('foo', 'bar')
49+
)
50+
51+
expect_failure(
52+
expect_messages(
53+
{
54+
message('foo')
55+
message('bar')
56+
},
57+
c('foo', 'baz')
58+
),
59+
'did not produce the expected message'
60+
)
61+
62+
expect_failure(
63+
expect_messages(
64+
{
65+
message('foo')
66+
message('bar')
67+
},
68+
has_not = 'foo'
69+
),
70+
'produced unwanted message\\(s\\):\n\\* "foo"'
71+
)
72+
})

tests/testthat/test-reload.r

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ test_that('reload includes transitive dependencies', {
8787
box::use(mod/reload/a)
8888
expect_messages(
8989
box::reload(a),
90-
has = c('^c unloaded', '^c loaded')
90+
has = c('^a unloaded', '^c unloaded', '^c loaded')
9191
)
9292
})
9393

0 commit comments

Comments
 (0)