Skip to content

Commit b7ff173

Browse files
Merge branch 'main' into rc-0.1.0
2 parents b486cb9 + 432dc1e commit b7ff173

23 files changed

+368
-86
lines changed

R/analyze.R

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ benchmark_verbalize <- function(benchmark, timings, refs, ci) {
9595
confint <- confint_relative_get(timings, refs, tbl$mean[1], ci = ci)
9696

9797
text <- glue::glue(
98-
"* {benchmark}: {tbl$mean[1]}s -> {tbl$mean[2]}s {confint}"
98+
"* {confint$emoji}{benchmark}: {tbl$mean[1]}s -> {tbl$mean[2]}s {confint$string}"
9999
)
100100
cat(
101101
text,
@@ -110,6 +110,10 @@ set_sign <- function(x) {
110110
}
111111

112112
confint_relative_get <- function(timings, refs, reference, ci) {
113+
no_change <- "&nbsp;&nbsp;:ballot_box_with_check:"
114+
slower <- ":exclamation::snail:"
115+
faster <- "&nbsp;&nbsp;:rocket:"
116+
113117
timings_with_factors <- timings %>%
114118
dplyr::mutate(
115119
block = factor(.data$block), ref = factor(.data$ref, levels = refs)
@@ -118,7 +122,22 @@ confint_relative_get <- function(timings, refs, reference, ci) {
118122
fit <- stats::aov(elapsed ~ ref, data = timings_with_factors)
119123
var <- paste0("ref", refs[2])
120124
confint <- confint(fit, var, level = ci)
121-
paste0("[", paste0(set_sign(round(100 * confint / reference, 2)), collapse = "%, "), "%]")
125+
confint <- round(100 * confint / reference, 2)
126+
emoji <- confint %>%
127+
purrr::when(
128+
all(. < 0) ~ faster,
129+
all(. > 0) ~ slower,
130+
~ no_change
131+
)
132+
133+
list(
134+
string = paste0(
135+
"[",
136+
paste0(set_sign(confint), collapse = "%, "),
137+
"%]"
138+
),
139+
emoji = emoji
140+
)
122141
}
123142

124143

@@ -134,4 +153,4 @@ benchmark_plot <- function(benchmark, timings) {
134153
fs::path(dir_touchstone(), "plots", benchmark) %>%
135154
fs::path_ext_set("png") %>%
136155
ggplot2::ggsave()
137-
}
156+
}

R/core.R

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,50 @@
11
#' Run a benchmark iteration
22
#' @param expr_before_benchmark Character vector with code to run before
33
#' the benchmark is ran, will be evaluated with [exprs_eval()].
4-
#' @param ... Named character vector of length one with code to benchmark, will
5-
#' be evaluated with [exprs_eval()].
64
#' @param n Number of iterations to run a benchmark within an iteration.
5+
#' @param dots list of quoted expressions (length 1).
76
#' @inheritParams benchmark_write
87
#' @return
98
#' A tibble with the benchmarks.
109
#' @importFrom tibble lst tibble
1110
#' @keywords internal
1211
benchmark_run_iteration <- function(expr_before_benchmark,
13-
...,
12+
dots,
1413
ref,
1514
block,
1615
n = getOption("touchstone.n_iterations", 1)) {
1716
if (rlang::is_missing(expr_before_benchmark)) {
1817
expr_before_benchmark <- ""
1918
}
20-
if (length(rlang::list2(...)) > 1) {
21-
rlang::abort("Can only pass one expression to benchmark")
22-
}
19+
2320
args <- rlang::list2(
2421
expr_before_benchmark = expr_before_benchmark,
25-
...,
22+
dots = dots,
2623
ref = ref,
2724
block = block
2825
)
2926
for (iteration in seq_len(n)) { # iterations
3027
callr::r(
31-
function(expr_before_benchmark, ..., ref, block, iteration) {
28+
function(expr_before_benchmark, dots, ref, block, iteration) {
3229
new_name <- "masked_touchstone"
3330
attach(loadNamespace("touchstone"), name = new_name)
3431
on.exit(detach(new_name, character.only = TRUE), add = TRUE)
35-
exprs_eval(expr_before_benchmark)
36-
benchmark <- bench::mark(exprs_eval(...), memory = FALSE, iterations = 1)
37-
benchmark_write(benchmark, names(rlang::list2(...)), ref = ref, block = block, iteration = iteration)
32+
exprs_eval(!!expr_before_benchmark)
33+
benchmark <- bench::mark(exprs_eval(!!dots[[1]]), memory = FALSE, iterations = 1)
34+
benchmark_write(benchmark, names(dots), ref = ref, block = block, iteration = iteration)
3835
},
3936
args = append(args, lst(iteration)),
4037
libpath = c(libpath_touchstone(ref), .libPaths())
4138
)
4239
}
4340
usethis::ui_done("Ran {n} iterations of ref `{ref}`.")
44-
benchmark_read(names(rlang::list2(...)), ref)
41+
benchmark_read(names(dots), ref)
4542
}
4643

4744
#' Run a benchmark for git refs
4845
#'
46+
#' @param ... Named expression or named character vector of length one with code to benchmark, will
47+
#' be evaluated with [exprs_eval()].
4948
#' @param refs Character vector with branch names to benchmark. The package
5049
#' must be built for each benchmarked branch beforehand with [refs_install()].
5150
#' The base ref is the target branch of the pull request in a workflow run,
@@ -78,14 +77,20 @@ benchmark_run_ref <- function(expr_before_benchmark,
7877
n = 100,
7978
path_pkg = ".") {
8079
force(refs)
80+
expr_before_benchmark <- rlang::enexpr(expr_before_benchmark)
81+
dots <- rlang::enexprs(...)
82+
83+
if (length(dots) > 1) {
84+
rlang::abort("Can only pass one expression to benchmark")
85+
}
8186
# touchstone libraries must be removed from the path temporarily
8287
# and the one to benchmark will be added in benchmark_run_ref_impl()
8388
local_without_touchstone_lib()
8489
# libpaths <- refs_install(refs, path_pkg, install_dependencies) # potentially not needed anymroe
8590
refs <- ref_upsample(refs, n = n)
8691
out_list <- purrr::pmap(refs, benchmark_run_ref_impl,
8792
expr_before_benchmark = expr_before_benchmark,
88-
...,
93+
dots = dots,
8994
path_pkg = path_pkg
9095
)
9196
vctrs::vec_rbind(!!!out_list)
@@ -101,12 +106,12 @@ benchmark_run_ref <- function(expr_before_benchmark,
101106
benchmark_run_ref_impl <- function(ref,
102107
block,
103108
expr_before_benchmark,
104-
...,
109+
dots,
105110
path_pkg) {
106111
local_git_checkout(ref, path_pkg)
107112
benchmark_run_iteration(
108113
expr_before_benchmark = expr_before_benchmark,
109-
...,
114+
dots = dots,
110115
ref = ref,
111116
block = block
112117
)

R/prepare.R

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#' @return
99
#' A character vector with library paths.
1010
#' @keywords internal
11-
ref_install <- function(ref = "master",
11+
ref_install <- function(ref = "main",
1212
path_pkg = ".",
1313
install_dependencies = FALSE) {
1414
local_git_checkout(ref, path_pkg)
@@ -23,10 +23,13 @@ ref_install <- function(ref = "master",
2323
.libPaths()
2424
)
2525
withr::local_libpaths(libpath)
26+
withr::local_options(warn = 2)
2627
remotes::install_local(path_pkg,
2728
upgrade = "never", quiet = TRUE,
28-
dependencies = install_dependencies
29+
dependencies = install_dependencies,
30+
force = !cache_up_to_date(ref, path_pkg)
2931
)
32+
cache_update(ref, path_pkg)
3033
usethis::ui_done("Installed branch {ref} into {libpath[1]}.")
3134
libpath
3235
}
@@ -69,3 +72,49 @@ refs_install <- function(refs = c(
6972
libpath_touchstone <- function(ref) {
7073
fs::path(dir_touchstone(), "lib", ref)
7174
}
75+
76+
#' When did the package sources change last?
77+
#' @inheritParams ref_install
78+
#' @keywords internal
79+
hash_pkg <- function(path_pkg) {
80+
withr::local_dir(path_pkg)
81+
list(
82+
tools::md5sum(c(
83+
if (fs::dir_exists("R")) fs::dir_ls("R"),
84+
if (fs::file_exists("DESCRIPTION")) "DESCRIPTION",
85+
if (fs::dir_exists("scr")) fs::dir_info("scr")
86+
))
87+
)
88+
}
89+
90+
#' Cache package sources within a session
91+
#'
92+
#' This is required to make sure [remotes::install_local()] installs again
93+
#' when source code changed.
94+
#' @inheritParams ref_install
95+
#' @keywords internal
96+
cache_up_to_date <- function(ref, path_pkg) {
97+
md5_hashes <- hash_pkg(path_pkg)
98+
cache <- cache_get()
99+
identical(md5_hashes, cache$md5_hashes[cache$ref == ref & cache$path_pkg == path_pkg])
100+
}
101+
102+
#' @rdname cache_up_to_date
103+
#' @keywords internal
104+
cache_update <- function(ref, path_pkg) {
105+
md5_hashes <- hash_pkg(path_pkg)
106+
cache <- cache_get()
107+
stopifnot(sum(cache$ref[cache$path_pkg == path_pkg] == ref) <= 1)
108+
cache <- cache[(!(cache$ref == ref) & (cache$path_pkg == path_pkg)), ]
109+
cache <- vctrs::vec_rbind(
110+
cache, tibble::tibble(ref, md5_hashes, path_pkg)
111+
)
112+
options("touchstone.hash_source_package" = cache)
113+
}
114+
115+
116+
#' @rdname cache_up_to_date
117+
#' @keywords internal
118+
cache_get <- function() {
119+
getOption("touchstone.hash_source_package")
120+
}

R/source.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
#' @export
3838
#' @examples
3939
#' \dontrun{
40-
#' # assuming you want to compare the branch master with the branch devel
41-
#' if (requireNamespace("withr")) {
40+
#' # assuming you want to compare the branch main with the branch devel
41+
#' if (rlang::is_installed("withr")) {
4242
#' withr::with_envvar(
4343
#' c("GITHUB_BASE_REF" = "main", "GITHUB_HEAD_REF" = "devel"),
4444
#' run_script("touchstone/script.R")

R/testing.R

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ local_package <- function(pkg_name = fs::path_file(tempfile("pkg")),
4343
withr::local_options(
4444
usethis.quiet = TRUE,
4545
touchstone.n_iterations = 2,
46-
.local_envir = envir
46+
.local_envir = envir,
47+
touchstone.hash_source_package = tibble::tibble(
48+
ref = character(), md5_hashes = list(), path_pkg = character()
49+
)
4750
)
4851
usethis::create_package(path, open = FALSE)
4952
withr::local_dir(path, .local_envir = if (setwd) envir else rlang::current_env())
@@ -54,6 +57,9 @@ local_package <- function(pkg_name = fs::path_file(tempfile("pkg")),
5457
writeLines(if (is.null(r_sample)) "" else r_sample, fs::path("R", "sample.R"))
5558
gert::git_add("R/")
5659
gert::git_commit("[init]")
60+
branches <- gert::git_branch_list() %>%
61+
dplyr::pull(name) %>%
62+
dplyr::setdiff(branches, .)
5763
purrr::walk(branches, gert::git_branch_create)
5864
withr::defer(unlink(path), envir = envir)
5965
install_check <- is_installed(path)

R/utils.R

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,32 @@ touchstone_clear <- function(all = FALSE) {
5252
fs::dir_delete(paths)
5353
}
5454

55-
#' Evaluate an expression
55+
#' Evaluate an expression for sideeffects
5656
#'
57-
#' @param text Character vector with code to evaluate.
58-
#' @return
59-
#' The input, parsed and evaluated.
57+
#'
58+
#' @param ... Character vector of length 1 or expression with code to evaluate. This will be quoted using
59+
#' [rlang::enexprs()], so you can use `!!`.
60+
#' @param env Environment in which the expression will be evaluated.
61+
#' @return The quoted input (invisibly).
6062
#' @keywords internal
61-
exprs_eval <- function(...) {
62-
eval(parse(text = unlist(rlang::list2(...))))
63+
exprs_eval <- function(..., env = parent.frame()) {
64+
expr <- rlang::enexprs(...)[[1]]
65+
66+
if (is.symbol(expr)) {
67+
expr <- rlang::eval_tidy(expr, env = env)
68+
}
69+
70+
if (is.character(expr)) {
71+
expr <- rlang::parse_exprs(expr)
72+
}
73+
74+
if (is.list(expr)) {
75+
purrr::map(expr, eval, envir = env)
76+
} else {
77+
eval(expr, envir = env)
78+
}
79+
80+
invisible(expr)
6381
}
6482

6583
#' Samples `ref`

R/zzz.R

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
.onLoad <- function(libname, pkgname) {
22
op <- options()
3+
cache <- tibble::tibble(
4+
ref = character(), md5_hashes = list(), path_pkg = character()
5+
)
36
op.touchstone <- list(
47
"touchstone.skip_install" = FALSE,
58
"touchstone.dir" = "touchstone",
69
# how many times should inner loop be ran in benchmark_run_iteration
7-
"touchstone.n_iterations" = 1
10+
"touchstone.n_iterations" = 1,
11+
"touchstone.hash_source_package" = cache
812
)
913
toset <- !(names(op.touchstone) %in% names(op))
1014
if (any(toset)) options(op.touchstone[toset])

README.Rmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ touchstone::refs_install() # installs branches to benchmark
128128
129129
# benchmark a function call from your package (two calls per branch)
130130
touchstone::benchmark_run_ref(
131-
random_test = "yourpkg::fun()",
131+
random_test = yourpkg::fun(),
132132
n = 2
133133
)
134134

0 commit comments

Comments
 (0)