From 5359cd701e6a4fd796b110eb393e5dcb8c007f6d Mon Sep 17 00:00:00 2001 From: novica Date: Sun, 1 Nov 2026 20:21:03 +0100 Subject: [PATCH 1/4] test: check that the smoke test's scheduled run succeeds The check only looked for a START line, so a failing scheduled run passed. Assisted-by: Claude Opus 5.5 --- dev/smoke-test.R | 49 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/dev/smoke-test.R b/dev/smoke-test.R index 0ae54ff..be22de0 100644 --- a/dev/smoke-test.R +++ b/dev/smoke-test.R @@ -137,10 +137,26 @@ local({ check(length(roles_left) == 0, "no roles left") }) + # A scheduled run calls the handler with no arguments, so give `number` a + # default. + runtime_path <- file.path(tempdir(), "parity.R") + writeLines( + sub( + "function(number)", + "function(number = 2)", + readLines(system.file("parity.R", package = "r2lambda")), + fixed = TRUE + ), + runtime_path + ) + check( + any(grepl("function(number = 2)", readLines(runtime_path), fixed = TRUE)), + "runtime has a default for number" + ) build_lambda( tag = tag, runtime_function = "parity", - runtime_path = system.file("parity.R", package = "r2lambda"), + runtime_path = runtime_path, architecture = architecture ) @@ -171,27 +187,32 @@ local({ "deployed invocation returns even" ) - # Scheduled invocations send an EventBridge event, which parity() can't - # take, so they fail. Each attempt still writes a START line to the log. schedule_lambda(tag, "rate(1 minute)") logs <- aws_connect("cloudwatchlogs") started_at <- as.numeric(Sys.time()) * 1000 + scheduled_events <- function(pattern) { + tryCatch( + logs$filter_log_events( + logGroupName = paste0("/aws/lambda/", tag), + startTime = started_at, + filterPattern = pattern + )$events, + error = function(e) list() + ) + } + # REPORT is the last line of a finished run, whether it failed or not. wait_until( - function() { - events <- tryCatch( - logs$filter_log_events( - logGroupName = paste0("/aws/lambda/", tag), - startTime = started_at, - filterPattern = "START" - )$events, - error = function(e) list() - ) - length(events) > 0 - }, + function() length(scheduled_events("REPORT")) > 0, "scheduled invocation ran", timeout = 240, every = 30 ) + # lambdr logs a failed run's error with logger::log_error(). + errors <- scheduled_events("ERROR") + for (event in errors) { + message(event$message) + } + check(length(errors) == 0, "scheduled invocation succeeded") }) message("Smoke test passed.") From 017a585a1482fbf1566a31184c6eb5c46a06728e Mon Sep 17 00:00:00 2001 From: novica Date: Sun, 1 Nov 2026 20:21:57 +0100 Subject: [PATCH 2/4] test: fail the smoke test when the error log query fails Assisted-by: Claude Opus 5.5 --- dev/smoke-test.R | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dev/smoke-test.R b/dev/smoke-test.R index be22de0..58ad102 100644 --- a/dev/smoke-test.R +++ b/dev/smoke-test.R @@ -191,18 +191,18 @@ local({ logs <- aws_connect("cloudwatchlogs") started_at <- as.numeric(Sys.time()) * 1000 scheduled_events <- function(pattern) { - tryCatch( - logs$filter_log_events( - logGroupName = paste0("/aws/lambda/", tag), - startTime = started_at, - filterPattern = pattern - )$events, - error = function(e) list() - ) + logs$filter_log_events( + logGroupName = paste0("/aws/lambda/", tag), + startTime = started_at, + filterPattern = pattern + )$events } - # REPORT is the last line of a finished run, whether it failed or not. + # REPORT is the last line of a finished run, whether it failed or not. The + # log group may not exist yet, so a failed query means "not yet". wait_until( - function() length(scheduled_events("REPORT")) > 0, + function() { + length(tryCatch(scheduled_events("REPORT"), error = function(e) NULL)) > 0 + }, "scheduled invocation ran", timeout = 240, every = 30 From 10ff3a7f0e23f43a5650054485f3ab01f6209b02 Mon Sep 17 00:00:00 2001 From: novica Date: Sun, 1 Nov 2026 20:22:34 +0100 Subject: [PATCH 3/4] test: read every page of the smoke test's log query Assisted-by: Claude Opus 5.5 --- dev/smoke-test.R | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/dev/smoke-test.R b/dev/smoke-test.R index 58ad102..f7ade5b 100644 --- a/dev/smoke-test.R +++ b/dev/smoke-test.R @@ -191,11 +191,20 @@ local({ logs <- aws_connect("cloudwatchlogs") started_at <- as.numeric(Sys.time()) * 1000 scheduled_events <- function(pattern) { - logs$filter_log_events( - logGroupName = paste0("/aws/lambda/", tag), - startTime = started_at, - filterPattern = pattern - )$events + events <- list() + token <- NULL + repeat { + page <- logs$filter_log_events( + logGroupName = paste0("/aws/lambda/", tag), + startTime = started_at, + filterPattern = pattern, + nextToken = token + ) + events <- c(events, page$events) + token <- page$nextToken + if (length(token) == 0 || !nzchar(token)) break + } + events } # REPORT is the last line of a finished run, whether it failed or not. The # log group may not exist yet, so a failed query means "not yet". From 0c0d7a1ec7dd581748b46803f41314d96cd93449 Mon Sep 17 00:00:00 2001 From: novica Date: Sun, 1 Nov 2026 20:23:14 +0100 Subject: [PATCH 4/4] refactor: share the log paging loop in the smoke test Assisted-by: Claude Opus 5.5 --- dev/smoke-test.R | 48 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/dev/smoke-test.R b/dev/smoke-test.R index f7ade5b..561ed0b 100644 --- a/dev/smoke-test.R +++ b/dev/smoke-test.R @@ -40,23 +40,31 @@ wait_until <- function(condition, what, timeout = 300, every = 10) { message("OK: ", what) } +# All CloudWatch log events of the function, across result pages. `...` goes +# to filter_log_events(), e.g. startTime or filterPattern. +log_events <- function(tag, ...) { + logs <- aws_connect("cloudwatchlogs") + events <- list() + token <- NULL + repeat { + page <- logs$filter_log_events( + logGroupName = paste0("/aws/lambda/", tag), + nextToken = token, + ... + ) + events <- c(events, page$events) + token <- page$nextToken + if (length(token) == 0 || !nzchar(token)) break + } + events +} + # Writes the function's CloudWatch log events to a file before # delete_lambda() removes the log group. Never fails, so cleanup still runs. save_function_logs <- function(tag, path) { result <- tryCatch( { - logs <- aws_connect("cloudwatchlogs") - events <- list() - token <- NULL - repeat { - page <- logs$filter_log_events( - logGroupName = paste0("/aws/lambda/", tag), - nextToken = token - ) - events <- c(events, page$events) - token <- page$nextToken - if (length(token) == 0 || !nzchar(token)) break - } + events <- log_events(tag) lines <- vapply( events, function(e) { @@ -188,23 +196,9 @@ local({ ) schedule_lambda(tag, "rate(1 minute)") - logs <- aws_connect("cloudwatchlogs") started_at <- as.numeric(Sys.time()) * 1000 scheduled_events <- function(pattern) { - events <- list() - token <- NULL - repeat { - page <- logs$filter_log_events( - logGroupName = paste0("/aws/lambda/", tag), - startTime = started_at, - filterPattern = pattern, - nextToken = token - ) - events <- c(events, page$events) - token <- page$nextToken - if (length(token) == 0 || !nzchar(token)) break - } - events + log_events(tag, startTime = started_at, filterPattern = pattern) } # REPORT is the last line of a finished run, whether it failed or not. The # log group may not exist yet, so a failed query means "not yet".