Nothing
# Tests for error handling in errors.c
# These tests aim to exercise the different error paths in check_result_impl()
# Helper to strip line numbers from C error messages to avoid brittle snapshots
strip_line_numbers <- function(x) {
gsub("(\\w+\\.c):[0-9]+:", "\\1:LINE:", x)
}
test_that("Automerge errors are caught with error messages", {
# Test AM_STATUS_ERROR with non-empty error message
# Loading invalid data should trigger an Automerge C error
# Invalid Automerge format
expect_snapshot(error = TRUE, transform = strip_line_numbers, {
am_load(as.raw(c(0x00, 0x01, 0x02)))
})
# Random bytes that aren't a valid document
set.seed(42) # For reproducibility
expect_snapshot(error = TRUE, transform = strip_line_numbers, {
am_load(as.raw(sample(0:255, 100, replace = TRUE)))
})
})
test_that("am_load validates input type", {
# These test R-level validation before reaching C
expect_snapshot(error = TRUE, {
am_load("not raw")
})
expect_snapshot(error = TRUE, {
am_load(123)
})
expect_snapshot(error = TRUE, {
am_load(list())
})
expect_snapshot(error = TRUE, {
am_load(NULL)
})
})
test_that("Invalid document pointers are caught", {
# Pass invalid objects where documents are expected
expect_snapshot(error = TRUE, {
am_save("not a document")
})
expect_snapshot(error = TRUE, {
am_fork(123)
})
expect_snapshot(error = TRUE, {
am_merge("doc1", "doc2")
})
})
test_that("Invalid operations on documents", {
doc <- am_create()
# Invalid actor ID types
expect_snapshot(error = TRUE, {
am_set_actor(doc, 123)
})
expect_snapshot(error = TRUE, {
am_set_actor(doc, list())
})
# Merge with invalid second document
expect_snapshot(error = TRUE, {
am_merge(doc, "not a doc")
})
})
test_that("Invalid object operations", {
doc <- am_create()
# Try to get from invalid object pointer
expect_snapshot(error = TRUE, {
am_get(doc, "not an objid", "key")
})
# Delete from invalid object
expect_snapshot(error = TRUE, {
am_delete(doc, 123, "key")
})
})
test_that("Commit with invalid parameters", {
doc <- am_create()
# Invalid message type
expect_snapshot(error = TRUE, {
am_commit(doc, 123)
})
expect_snapshot(error = TRUE, {
am_commit(doc, c("a", "b"))
})
# Invalid time type
expect_snapshot(error = TRUE, {
am_commit(doc, NULL, "not a time")
})
expect_snapshot(error = TRUE, {
am_commit(doc, NULL, 123)
})
})
test_that("Text operations with invalid inputs", {
doc <- am_create()
am_put(doc, AM_ROOT, "text", am_text("Hello"))
text_obj <- am_get(doc, AM_ROOT, "text")
# These should work (baseline)
am_text_splice(text_obj, 5, 0, " World")
result <- am_text_content(text_obj)
expect_equal(result, "Hello World")
# Try text operations on non-text object
am_put(doc, AM_ROOT, "map", AM_OBJ_TYPE_MAP)
map_obj <- am_get(doc, AM_ROOT, "map")
# This should fail - trying to do text operations on a map
expect_snapshot(error = TRUE, transform = strip_line_numbers, {
am_text_splice(map_obj, 0, 0, "text")
})
})
test_that("Operations on invalid object types", {
doc <- am_create()
# Create a text object
am_put(doc, AM_ROOT, "text", am_text("content"))
text_obj <- am_get(doc, AM_ROOT, "text")
# Try to use am_keys on text (should work or error gracefully)
# Text objects don't have "keys" in the map sense
result <- am_keys(doc, text_obj)
expect_true(is.character(result) || is.null(result))
})
test_that("Large documents don't cause issues", {
# This tests that we don't hit buffer size limits
doc <- am_create()
# Create a document with many keys
for (i in 1:1000) {
am_put(doc, AM_ROOT, paste0("key", i), i)
}
# Should be able to save and load large documents
bytes <- am_save(doc)
doc2 <- am_load(bytes)
# Verify some values
expect_equal(am_get(doc2, AM_ROOT, "key1"), 1)
expect_equal(am_get(doc2, AM_ROOT, "key1000"), 1000)
})
test_that("Nested errors propagate correctly", {
# Test that errors deep in nested structures are caught
doc <- am_create()
# Create deeply nested structure
am_put(
doc,
AM_ROOT,
"level1",
list(
level2 = list(
level3 = list(
level4 = list(
level5 = "deep"
)
)
)
)
)
# Now try to perform an invalid operation deep in the structure
level1 <- am_get(doc, AM_ROOT, "level1")
level2 <- am_get(doc, level1, "level2")
level3 <- am_get(doc, level2, "level3")
# Valid operations should work
expect_type(level3, "externalptr")
expect_s3_class(level3, "am_object")
})
test_that("Fork and merge error handling", {
doc1 <- am_create()
am_put(doc1, AM_ROOT, "x", 1)
# Fork should work
doc2 <- am_fork(doc1)
expect_s3_class(doc2, "am_doc")
# Make changes
am_put(doc2, AM_ROOT, "y", 2)
# Merge should work
am_merge(doc1, doc2)
expect_equal(am_get(doc1, AM_ROOT, "y"), 2)
# Try to merge with invalid document
expect_snapshot(error = TRUE, {
am_merge(doc1, NULL)
})
})
test_that("Type constructor validation", {
# am_text with invalid input
expect_snapshot(error = TRUE, {
am_text(123)
})
expect_snapshot(error = TRUE, {
am_text(c("a", "b"))
})
expect_snapshot(error = TRUE, {
am_text(NULL)
})
# am_uint64 with negative value
expect_snapshot(error = TRUE, {
am_uint64(-1)
})
})
test_that("Corrupted document state handling", {
doc <- am_create()
am_put(doc, AM_ROOT, "key", "value")
# Save valid document
bytes <- am_save(doc)
# Modify bytes to simulate corruption (change a few bytes in the middle)
if (length(bytes) > 10) {
corrupted <- bytes
corrupted[5:7] <- as.raw(c(255, 255, 255))
# Try to load corrupted document - should error
expect_snapshot(error = TRUE, transform = strip_line_numbers, {
am_load(corrupted)
})
}
})
test_that("Edge case: operations on empty document", {
doc <- am_create()
# Getting non-existent key returns NULL
expect_null(am_get(doc, AM_ROOT, "nonexistent"))
# Keys of empty root
expect_equal(am_keys(doc, AM_ROOT), character(0))
# Length of empty root
expect_equal(am_length(doc, AM_ROOT), 0)
# Save and load empty document
bytes <- am_save(doc)
doc2 <- am_load(bytes)
expect_s3_class(doc2, "am_doc")
expect_equal(am_length(doc2, AM_ROOT), 0)
})
test_that("Error messages include file and line information", {
# When errors occur, they should include source location
# This helps with debugging
# The error message format is: "Automerge error at document.c:LINE: message"
expect_snapshot(error = TRUE, transform = strip_line_numbers, {
am_load(as.raw(c(0xFF)))
})
})
test_that("Multiple error conditions in sequence", {
# Test that error state doesn't persist between calls
# First error
expect_snapshot(error = TRUE, transform = strip_line_numbers, {
am_load(as.raw(c(0x00)))
})
# Should be able to perform valid operations after error
doc <- am_create()
expect_s3_class(doc, "am_doc")
# Second error
expect_snapshot(error = TRUE, transform = strip_line_numbers, {
am_load(as.raw(c(0xFF)))
})
# Valid operation again
am_put(doc, AM_ROOT, "key", "value")
expect_equal(am_get(doc, AM_ROOT, "key"), "value")
})
test_that("Resource cleanup after errors", {
# Test that resources are properly freed even after errors
# This is hard to test directly, but we can verify no crashes
for (i in 1:10) {
# Trigger errors in a loop
set.seed(i) # For reproducibility
expect_snapshot(error = TRUE, transform = strip_line_numbers, {
am_load(as.raw(sample(0:255, 50, replace = TRUE)))
})
}
# Should still be able to create valid documents
doc <- am_create()
am_put(doc, AM_ROOT, "after_errors", "works")
expect_equal(am_get(doc, AM_ROOT, "after_errors"), "works")
})
# Sync validation errors ------------------------------------------------------
test_that("am_sync_decode validates message type", {
doc <- am_create()
sync_state <- am_sync_state_new()
expect_snapshot(error = TRUE, {
am_sync_decode(doc, sync_state, "not raw")
})
expect_snapshot(error = TRUE, {
am_sync_decode(doc, sync_state, 123)
})
expect_snapshot(error = TRUE, {
am_sync_decode(doc, sync_state, list(1, 2, 3))
})
expect_snapshot(error = TRUE, {
am_sync_decode(doc, sync_state, NULL)
})
})
test_that("am_sync validates doc1 parameter", {
doc <- am_create()
expect_snapshot(error = TRUE, {
am_sync("not a doc", doc)
})
expect_snapshot(error = TRUE, {
am_sync(123, doc)
})
expect_snapshot(error = TRUE, {
am_sync(NULL, doc)
})
})
test_that("am_sync validates doc2 parameter", {
doc <- am_create()
expect_snapshot(error = TRUE, {
am_sync(doc, "not a doc")
})
expect_snapshot(error = TRUE, {
am_sync(doc, 456)
})
expect_snapshot(error = TRUE, {
am_sync(doc, NULL)
})
})
test_that("am_get_changes validates heads parameter", {
doc <- am_create()
am_put(doc, AM_ROOT, "x", 1)
am_commit(doc)
expect_snapshot(error = TRUE, {
am_get_changes(doc, "not a list")
})
expect_snapshot(error = TRUE, {
am_get_changes(doc, 123)
})
expect_snapshot(error = TRUE, {
am_get_changes(doc, raw(5))
})
})
test_that("am_apply_changes validates changes parameter", {
doc <- am_create()
expect_snapshot(error = TRUE, {
am_apply_changes(doc, "not a list")
})
expect_snapshot(error = TRUE, {
am_apply_changes(doc, 123)
})
expect_snapshot(error = TRUE, {
am_apply_changes(doc, raw(5))
})
expect_snapshot(error = TRUE, {
am_apply_changes(doc, NULL)
})
})
# Convenience function validation errors --------------------------------------
test_that("am_put_path validates with non-existent intermediate and no create", {
doc <- am_create()
expect_snapshot(error = TRUE, {
am_put_path(doc, c("a", "b", "c"), "value", create_intermediate = FALSE)
})
})
test_that("am_put_path errors on non-object intermediate path component", {
doc <- am_create()
doc$scalar <- "just a string"
expect_snapshot(error = TRUE, {
am_put_path(doc, c("scalar", "nested"), "value")
})
})
test_that("am_put_path errors when trying to create intermediate list element", {
doc <- am_create()
doc$items <- am_list("a", "b")
expect_snapshot(error = TRUE, {
am_put_path(doc, list("items", 99, "nested"), "value")
})
})
test_that("am_delete_path warns on non-existent intermediate path", {
doc <- am_create()
doc$user <- am_map(name = "Alice")
expect_warning(
am_delete_path(doc, c("user", "nonexistent", "key")),
"Path component at position 2 does not exist"
)
})
test_that("am_delete_path warns on non-object intermediate path component", {
doc <- am_create()
doc$scalar <- 42
expect_warning(
am_delete_path(doc, c("scalar", "nested")),
"Path component at position 1 is not an object"
)
})
# Object operations validation errors -----------------------------------------
test_that("am_put with invalid key types for maps", {
doc <- am_create()
# Map keys must be single character strings
expect_snapshot(error = TRUE, {
am_put(doc, AM_ROOT, 123, "value")
})
expect_snapshot(error = TRUE, {
am_put(doc, AM_ROOT, c("a", "b"), "value")
})
expect_snapshot(error = TRUE, {
am_put(doc, AM_ROOT, list("key"), "value")
})
})
test_that("am_put with invalid positions for lists", {
doc <- am_create()
doc$items <- am_list(1, 2, 3)
items <- am_get(doc, AM_ROOT, "items")
# List positions must be positive
expect_snapshot(error = TRUE, {
am_put(doc, items, 0, "value")
})
expect_snapshot(error = TRUE, {
am_put(doc, items, -1, "value")
})
# Position must be scalar
expect_snapshot(error = TRUE, {
am_put(doc, items, c(1, 2), "value")
})
})
test_that("am_put with invalid value types", {
doc <- am_create()
# Non-scalar POSIXct in map
expect_snapshot(error = TRUE, {
am_put(doc, AM_ROOT, "time", as.POSIXct(c("2024-01-01", "2024-01-02")))
})
# Non-scalar POSIXct in list
doc$items <- am_list(1, 2, 3)
items <- am_get(doc, AM_ROOT, "items")
expect_snapshot(error = TRUE, {
am_put(doc, items, 1, as.POSIXct(c("2024-01-01", "2024-01-02")))
})
# Non-scalar counter
counter <- structure(c(1L, 2L), class = "am_counter")
expect_snapshot(error = TRUE, {
am_put(doc, AM_ROOT, "counter", counter)
})
# Non-scalar or non-string am_text
expect_snapshot(error = TRUE, {
am_put(doc, AM_ROOT, "text", structure(123, class = "am_text_type"))
})
expect_snapshot(error = TRUE, {
am_put(doc, AM_ROOT, "text", structure(c("a", "b"), class = "am_text_type"))
})
})
test_that("am_insert validates list-only operation", {
doc <- am_create()
# Cannot insert into root (which is a map)
expect_snapshot(error = TRUE, {
am_insert(doc, AM_ROOT, 1, "value")
})
# Cannot insert into a map
doc$map <- AM_OBJ_TYPE_MAP
map_obj <- am_get(doc, AM_ROOT, "map")
expect_snapshot(error = TRUE, {
am_insert(doc, map_obj, 1, "value")
})
})
test_that("am_delete with invalid positions for lists", {
doc <- am_create()
doc$items <- am_list(1, 2, 3)
items <- am_get(doc, AM_ROOT, "items")
# List positions must be positive
expect_snapshot(error = TRUE, {
am_delete(doc, items, 0)
})
expect_snapshot(error = TRUE, {
am_delete(doc, items, -1)
})
# Position must be scalar
expect_snapshot(error = TRUE, {
am_delete(doc, items, c(1, 2))
})
})
test_that("am_text_splice validation errors", {
doc <- am_create()
doc$text <- am_text("Hello")
text_obj <- am_get(doc, AM_ROOT, "text")
# pos must be numeric
expect_snapshot(error = TRUE, {
am_text_splice(text_obj, "not numeric", 0, "")
})
# del_count must be numeric
expect_snapshot(error = TRUE, {
am_text_splice(text_obj, 0, "not numeric", "")
})
# text must be single character string
expect_snapshot(error = TRUE, {
am_text_splice(text_obj, 0, 0, 123)
})
expect_snapshot(error = TRUE, {
am_text_splice(text_obj, 0, 0, c("a", "b"))
})
# pos must be non-negative
expect_snapshot(error = TRUE, {
am_text_splice(text_obj, -1, 0, "")
})
# del_count must be non-negative
expect_snapshot(error = TRUE, {
am_text_splice(text_obj, 0, -1, "")
})
})
test_that("am_counter_increment validation errors", {
doc <- am_create()
doc$counter <- am_counter(10)
# Delta must be numeric
expect_snapshot(error = TRUE, {
am_counter_increment(doc, AM_ROOT, "counter", "not numeric")
})
# Delta must be scalar
expect_snapshot(error = TRUE, {
am_counter_increment(doc, AM_ROOT, "counter", c(1, 2))
})
# Map key must be single character string
expect_snapshot(error = TRUE, {
am_counter_increment(doc, AM_ROOT, 123, 1)
})
expect_snapshot(error = TRUE, {
am_counter_increment(doc, AM_ROOT, c("a", "b"), 1)
})
# Cannot increment counter in text object
doc$text <- am_text("Hello")
text_obj <- am_get(doc, AM_ROOT, "text")
expect_snapshot(error = TRUE, {
am_counter_increment(doc, text_obj, 0, 1)
})
})
test_that("am_counter_increment with list positions", {
doc <- am_create()
doc$counters <- am_list(am_counter(1), am_counter(2))
counters <- am_get(doc, AM_ROOT, "counters")
# Position must be numeric
expect_snapshot(error = TRUE, {
am_counter_increment(doc, counters, "not numeric", 1)
})
# Position must be scalar
expect_snapshot(error = TRUE, {
am_counter_increment(doc, counters, c(1, 2), 1)
})
# Position must be >= 1 (1-based indexing)
expect_snapshot(error = TRUE, {
am_counter_increment(doc, counters, 0, 1)
})
})
# Cursor validation errors ----------------------------------------------------
test_that("am_cursor validation errors", {
doc <- am_create()
doc$text <- am_text("Hello")
text_obj <- am_get(doc, AM_ROOT, "text")
# position must be numeric
expect_snapshot(error = TRUE, {
am_cursor(text_obj, "not numeric")
})
# position must be scalar
expect_snapshot(error = TRUE, {
am_cursor(text_obj, c(1, 2))
})
# position must be non-negative
expect_snapshot(error = TRUE, {
am_cursor(text_obj, -1)
})
})
test_that("am_cursor_position validation errors", {
doc <- am_create()
doc$text <- am_text("Hello")
text_obj <- am_get(doc, AM_ROOT, "text")
cursor <- am_cursor(text_obj, 0)
# cursor must be external pointer
expect_snapshot(error = TRUE, {
am_cursor_position("not a cursor")
})
expect_snapshot(error = TRUE, {
am_cursor_position(123)
})
})
# Mark validation errors ------------------------------------------------------
test_that("am_mark validation errors", {
doc <- am_create()
doc$text <- am_text("Hello")
text_obj <- am_get(doc, AM_ROOT, "text")
# start must be numeric
expect_snapshot(error = TRUE, {
am_mark(text_obj, "not numeric", 5, "bold", TRUE)
})
# start must be scalar
expect_snapshot(error = TRUE, {
am_mark(text_obj, c(0, 1), 5, "bold", TRUE)
})
# start must be non-negative
expect_snapshot(error = TRUE, {
am_mark(text_obj, -1, 5, "bold", TRUE)
})
# end must be numeric
expect_snapshot(error = TRUE, {
am_mark(text_obj, 0, "not numeric", "bold", TRUE)
})
# end must be scalar
expect_snapshot(error = TRUE, {
am_mark(text_obj, 0, c(3, 5), "bold", TRUE)
})
# end must be non-negative
expect_snapshot(error = TRUE, {
am_mark(text_obj, 0, -1, "bold", TRUE)
})
# end must be greater than start
expect_snapshot(error = TRUE, {
am_mark(text_obj, 5, 5, "bold", TRUE)
})
expect_snapshot(error = TRUE, {
am_mark(text_obj, 5, 3, "bold", TRUE)
})
# name must be single character string
expect_snapshot(error = TRUE, {
am_mark(text_obj, 0, 5, 123, TRUE)
})
expect_snapshot(error = TRUE, {
am_mark(text_obj, 0, 5, c("a", "b"), TRUE)
})
# expand must be valid string
expect_snapshot(error = TRUE, {
am_mark(text_obj, 0, 5, "bold", TRUE, expand = "invalid")
})
expect_snapshot(error = TRUE, {
am_mark(text_obj, 0, 5, "bold", TRUE, expand = 123)
})
# Mark value must be scalar POSIXct
expect_snapshot(error = TRUE, {
am_mark(text_obj, 0, 5, "time", as.POSIXct(c("2024-01-01", "2024-01-02")))
})
# Mark value must be scalar counter
counter <- structure(c(1L, 2L), class = "am_counter")
expect_snapshot(error = TRUE, {
am_mark(text_obj, 0, 5, "counter", counter)
})
})
test_that("am_marks_at validation errors", {
doc <- am_create()
doc$text <- am_text("Hello")
text_obj <- am_get(doc, AM_ROOT, "text")
# position must be numeric
expect_snapshot(error = TRUE, {
am_marks_at(text_obj, "not numeric")
})
# position must be scalar
expect_snapshot(error = TRUE, {
am_marks_at(text_obj, c(0, 1))
})
# position must be non-negative
expect_snapshot(error = TRUE, {
am_marks_at(text_obj, -1)
})
})
# Document lifecycle validation errors ----------------------------------------
test_that("am_create with invalid actor_id types", {
# actor_id must be NULL, character, or raw
expect_snapshot(error = TRUE, {
am_create(actor_id = 123)
})
expect_snapshot(error = TRUE, {
am_create(actor_id = list("id"))
})
expect_snapshot(error = TRUE, {
am_create(actor_id = TRUE)
})
})
test_that("am_fork with invalid heads parameter", {
doc <- am_create()
doc$x <- 1
am_commit(doc)
# heads must be NULL or list of raw vectors
expect_snapshot(error = TRUE, {
am_fork(doc, heads = "not a list")
})
expect_snapshot(error = TRUE, {
am_fork(doc, heads = 123)
})
# Elements must be raw vectors
expect_snapshot(error = TRUE, {
am_fork(doc, heads = list("not raw"))
})
expect_snapshot(error = TRUE, {
am_fork(doc, heads = list(123))
})
})
test_that("am_get_change_by_hash validation errors", {
doc <- am_create()
doc$x <- 1
am_commit(doc)
# hash must be raw vector
expect_snapshot(error = TRUE, {
am_get_change_by_hash(doc, "not raw")
})
expect_snapshot(error = TRUE, {
am_get_change_by_hash(doc, 123)
})
# hash must be exactly 32 bytes
expect_snapshot(error = TRUE, {
am_get_change_by_hash(doc, raw(10))
})
expect_snapshot(error = TRUE, {
am_get_change_by_hash(doc, raw(50))
})
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.