Skip to content

Commit 095e0ad

Browse files
authored
Merge pull request #74 from t-kalinowski/fix/covr-switch-fallthrough
Fix covr switch fallthrough; add r2size tests
2 parents 4ce5e67 + 9a67cb4 commit 095e0ad

9 files changed

Lines changed: 506 additions & 70 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
2+
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
8+
name: test-coverage.yaml
9+
10+
permissions: read-all
11+
12+
jobs:
13+
test-coverage:
14+
runs-on: ubuntu-latest
15+
env:
16+
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: r-lib/actions/setup-r@v2
22+
with:
23+
use-public-rspm: true
24+
25+
- uses: r-lib/actions/setup-r-dependencies@v2
26+
with:
27+
extra-packages: any::covr, any::xml2
28+
needs: coverage
29+
30+
- name: Test coverage
31+
run: |
32+
cov <- covr::package_coverage(
33+
quiet = FALSE,
34+
clean = FALSE,
35+
install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package")
36+
)
37+
print(cov)
38+
covr::to_cobertura(cov)
39+
shell: Rscript {0}
40+
41+
- uses: codecov/codecov-action@v5
42+
with:
43+
# Fail if error if not on PR, or if on PR and token is given
44+
fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }}
45+
files: ./cobertura.xml
46+
plugins: noop
47+
disable_search: true
48+
token: ${{ secrets.CODECOV_TOKEN }}
49+
50+
- name: Show testthat output
51+
if: always()
52+
run: |
53+
## --------------------------------------------------------------------
54+
find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true
55+
shell: bash
56+
57+
- name: Upload test results
58+
if: failure()
59+
uses: actions/upload-artifact@v4
60+
with:
61+
name: coverage-test-failures
62+
path: ${{ runner.temp }}/package

R/sizes.R

Lines changed: 65 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -141,74 +141,71 @@ r2size <- function(r, scope) {
141141
r2size(var@r, scope)
142142
},
143143
language = {
144-
as.character(r[[1]]) |>
145-
switch(
146-
`+` = ,
147-
`-` = ,
148-
`/` = ,
149-
`*` = ,
150-
`^` = ,
151-
`%/%` = ,
152-
`%%` = {
153-
args <- as.list(r)[-1]
154-
args <- lapply(args, r2size, scope)
155-
if (anyNA(rapply(args, as.list))) {
156-
return(NA_integer_)
157-
}
158-
cl <- as.call(c(r[[1]], args))
159-
if (all(map_lgl(args, is.atomic))) {
160-
cl <- eval(cl, baseenv())
161-
}
162-
cl
163-
},
164-
length = {
165-
var <- get0(as.character(r[[2L]]), scope)
166-
if (!inherits(var, Variable)) {
167-
stop("could not resolve size: ", deparse1(r))
168-
}
169-
if (var@rank == 1) {
170-
return(var@dims[[1L]])
171-
}
172-
len <- reduce(var@dims, \(d1, d2) call("*", d1, d2))
173-
r2size(len, scope)
174-
},
175-
`[` = {
176-
# [ only works when paired with dim()
177-
if (!is_call(r[[2L]], quote(dim))) {
178-
return(NA_integer_)
179-
}
180-
var <- get0(as.character(r[[2L]][[2L]]), scope)
181-
if (!inherits(var, Variable)) {
182-
stop("could not resolve size: ", deparse1(r))
183-
}
184-
axis <- r[[3]]
185-
if (!is_wholenumber(axis)) {
186-
return(NA_integer_)
187-
}
188-
if (axis > var@rank) {
189-
stop("insufficient rank of variable in ", deparse1(r))
190-
}
191-
var@dims[[axis]]
192-
},
193-
# dim = {
194-
#
195-
# },
196-
nrow = {
197-
var <- get0(as.character(r[[2L]]), scope)
198-
if (!inherits(var, Variable)) {
199-
stop("could not resolve size: ", deparse1(r))
200-
}
201-
var@dims[[1]]
202-
},
203-
ncol = {
204-
var <- get0(as.character(r[[2L]]), scope)
205-
if (!inherits(var, Variable)) {
206-
stop("could not resolve size: ", deparse1(r))
207-
}
208-
var@dims[[2]]
209-
},
210-
NA_integer_
211-
)
144+
op <- as.character(r[[1]])
145+
146+
if (op %in% c("+", "-", "/", "*", "^", "%/%", "%%")) {
147+
args <- as.list(r)[-1]
148+
args <- lapply(args, r2size, scope)
149+
if (anyNA(rapply(args, as.list))) {
150+
return(NA_integer_)
151+
}
152+
cl <- as.call(c(r[[1]], args))
153+
if (all(map_lgl(args, is.atomic))) {
154+
cl <- eval(cl, baseenv())
155+
}
156+
return(cl)
157+
}
158+
159+
switch(
160+
op,
161+
length = {
162+
var <- get0(as.character(r[[2L]]), scope)
163+
if (!inherits(var, Variable)) {
164+
stop("could not resolve size: ", deparse1(r))
165+
}
166+
if (var@rank == 1) {
167+
return(var@dims[[1L]])
168+
}
169+
len <- reduce(var@dims, \(d1, d2) call("*", d1, d2))
170+
r2size(len, scope)
171+
},
172+
`[` = {
173+
# [ only works when paired with dim()
174+
if (!is_call(r[[2L]], quote(dim))) {
175+
return(NA_integer_)
176+
}
177+
var <- get0(as.character(r[[2L]][[2L]]), scope)
178+
if (!inherits(var, Variable)) {
179+
stop("could not resolve size: ", deparse1(r))
180+
}
181+
axis <- r[[3]]
182+
if (!is_wholenumber(axis)) {
183+
return(NA_integer_)
184+
}
185+
if (axis > var@rank) {
186+
stop("insufficient rank of variable in ", deparse1(r))
187+
}
188+
var@dims[[axis]]
189+
},
190+
# dim = {
191+
#
192+
# },
193+
nrow = {
194+
var <- get0(as.character(r[[2L]]), scope)
195+
if (!inherits(var, Variable)) {
196+
stop("could not resolve size: ", deparse1(r))
197+
}
198+
var@dims[[1]]
199+
},
200+
ncol = {
201+
var <- get0(as.character(r[[2L]]), scope)
202+
if (!inherits(var, Variable)) {
203+
stop("could not resolve size: ", deparse1(r))
204+
}
205+
var@dims[[2]]
206+
},
207+
NA_integer_
208+
)
212209
},
213210
NA_integer_
214211
)

R/zzz.R

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
# }
3030

3131
.onLoad <- function(...) {
32-
S7::methods_register()
33-
asNamespace("dotty")$dotify()
32+
suppressWarnings({
33+
S7::methods_register()
34+
asNamespace("dotty")$dotify()
35+
})
3436
# on_load_register_.AtNames.default()
3537
}

README.Rmd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ knitr::opts_chunk$set(
2525
<!-- badges: start -->
2626

2727
[![R-CMD-check](https://github.com/t-kalinowski/quickr/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/t-kalinowski/quickr/actions/workflows/R-CMD-check.yaml)
28+
[![Codecov test coverage](https://codecov.io/gh/t-kalinowski/quickr/graph/badge.svg)](https://app.codecov.io/gh/t-kalinowski/quickr)
2829
<!-- badges: end -->
2930

3031
The goal of quickr is to make your R code run quicker.

tests/testthat/test-classes.R

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Unit tests for internal S7 helpers/classes
2+
3+
test_that("prop helpers implement coercion, validation and set-once behavior", {
4+
Test <- S7::new_class(
5+
name = "Test",
6+
properties = list(
7+
s = quickr:::prop_string(
8+
default = NULL,
9+
allow_null = TRUE,
10+
coerce = TRUE,
11+
set_once = TRUE
12+
),
13+
n = quickr:::prop_wholenumber(
14+
default = 1,
15+
allow_null = TRUE,
16+
coerce = TRUE
17+
),
18+
e = quickr:::prop_enum(
19+
values = c("alpha", "beta"),
20+
nullable = TRUE,
21+
exact = FALSE
22+
)
23+
)
24+
)
25+
26+
obj <- Test()
27+
28+
obj@s <- 123
29+
expect_identical(obj@s, "123")
30+
expect_error(obj@s <- "again", "can only be set once")
31+
32+
obj@n <- 2
33+
expect_identical(obj@n, 2L)
34+
expect_error(obj@n <- 2.5, "must be a whole number")
35+
36+
obj@e <- "alp"
37+
expect_identical(obj@e, "alpha")
38+
obj@e <- "gamma"
39+
expect_error(S7::validate(obj), "must be either")
40+
})
41+
42+
test_that("Variable dims setter accepts common forms", {
43+
v <- quickr:::Variable(name = "x", mode = "integer", dims = c(2, NA))
44+
expect_identical(v@dims, list(2L, NA_integer_))
45+
expect_identical(v@rank, 2L)
46+
47+
v2 <- quickr:::Variable(name = quote(foo), mode = "double", dims = quote(n))
48+
expect_identical(v2@name, "foo")
49+
expect_true(is.symbol(v2@dims[[1L]]))
50+
51+
v3 <- quickr:::Variable(name = "y", mode = "double", dims = quote(n + 1L))
52+
expect_true(is.call(v3@dims[[1L]]))
53+
54+
expect_error(
55+
quickr:::Variable(name = "bad", mode = "integer", dims = list("x")),
56+
"@dims must be a list"
57+
)
58+
})
59+
60+
test_that("Variable validator rejects non-logical logical_as_int", {
61+
expect_error(
62+
quickr:::Variable(mode = "integer", logical_as_int = TRUE),
63+
"logical_as_int"
64+
)
65+
})
66+
67+
test_that("Fortran validates length and prints non-null properties", {
68+
expect_error(quickr:::Fortran(c("a", "b")), "length 1")
69+
70+
v <- quickr:::Variable(name = "x", mode = "integer", dims = 1L)
71+
f <- quickr:::Fortran("x", value = v, r = quote(x))
72+
73+
out <- capture.output(print(f))
74+
expect_true(any(grepl("@value:", out, fixed = TRUE)))
75+
expect_true(any(grepl("@r:", out, fixed = TRUE)))
76+
})
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Unit tests for compile-package helpers
2+
3+
test_that("dump_collected writes src stubs for collected quick functions", {
4+
temp <- withr::local_tempdir()
5+
withr::local_dir(temp)
6+
7+
writeLines("useDynLib(pkg, .registration = TRUE)", "NAMESPACE")
8+
dir.create("src")
9+
10+
closure_env <- new.env(parent = asNamespace("quickr"))
11+
fn <- evalq(
12+
function(x) {
13+
declare(type(x = double(1)))
14+
x + 1
15+
},
16+
closure_env
17+
)
18+
19+
quick_closure <- quickr:::create_quick_closure("fn", fn)
20+
21+
quickr:::collector$activate("test")
22+
quickr:::collector$add(
23+
name = "fn",
24+
closure = fn,
25+
quick_closure = quick_closure
26+
)
27+
28+
testthat::capture_output({
29+
result <- suppressMessages(withVisible(quickr:::dump_collected()))
30+
})
31+
expect_false(result$visible)
32+
33+
expect_true(file.exists("src/quickr_entrypoints.c"))
34+
expect_true(file.exists("src/quickr_sub_routines.f90"))
35+
expect_true(file.info("src/quickr_entrypoints.c")$size > 0)
36+
expect_true(file.info("src/quickr_sub_routines.f90")$size > 0)
37+
})

0 commit comments

Comments
 (0)