tests/testthat/test-adapter.R

# Tests for adapter registry and class-specific traversal

test_that("shard_register_adapter registers adapter correctly", {
    # Clean up any existing adapters
    shard:::shard_clear_adapters()

    adapter <- list(
        class = "TestClass",
        children = function(x) list(data = x$data),
        replace = function(x, children) {
            x$data <- children$data
            x
        }
    )

    result <- shard_register_adapter("TestClass", adapter)

    # First registration returns NULL
    expect_null(result)

    # Check adapter is registered
    expect_true("TestClass" %in% shard_list_adapters())

    # Clean up
    shard:::shard_clear_adapters()
})

test_that("shard_register_adapter returns previous adapter on re-registration", {
    shard:::shard_clear_adapters()

    adapter1 <- list(
        class = "TestClass",
        children = function(x) list(a = 1),
        replace = function(x, children) x
    )

    adapter2 <- list(
        class = "TestClass",
        children = function(x) list(b = 2),
        replace = function(x, children) x
    )

    shard_register_adapter("TestClass", adapter1)
    previous <- shard_register_adapter("TestClass", adapter2)

    expect_identical(previous$class, "TestClass")
    expect_identical(previous$children(NULL), list(a = 1))

    shard:::shard_clear_adapters()
})

test_that("shard_unregister_adapter removes adapter and returns it", {
    shard:::shard_clear_adapters()

    adapter <- list(
        class = "TestClass",
        children = function(x) list(),
        replace = function(x, children) x
    )

    shard_register_adapter("TestClass", adapter)
    expect_true("TestClass" %in% shard_list_adapters())

    removed <- shard_unregister_adapter("TestClass")

    expect_identical(removed$class, "TestClass")
    expect_false("TestClass" %in% shard_list_adapters())

    shard:::shard_clear_adapters()
})

test_that("shard_unregister_adapter returns NULL for non-existent adapter", {
    shard:::shard_clear_adapters()

    result <- shard_unregister_adapter("NonExistentClass")
    expect_null(result)
})

test_that("shard_get_adapter returns adapter for object's class", {
    shard:::shard_clear_adapters()

    adapter <- list(
        class = "myclass",
        children = function(x) list(val = x$value),
        replace = function(x, children) {
            x$value <- children$val
            x
        }
    )

    shard_register_adapter("myclass", adapter)

    obj <- structure(list(value = 42), class = "myclass")
    found <- shard_get_adapter(obj)

    expect_identical(found$class, "myclass")

    shard:::shard_clear_adapters()
})

test_that("shard_get_adapter returns NULL for unregistered class", {
    shard:::shard_clear_adapters()

    obj <- structure(list(value = 42), class = "unregistered")
    found <- shard_get_adapter(obj)

    expect_null(found)
})

test_that("shard_get_adapter checks class hierarchy", {
    shard:::shard_clear_adapters()

    adapter <- list(
        class = "parentclass",
        children = function(x) list(val = x$value),
        replace = function(x, children) {
            x$value <- children$val
            x
        }
    )

    shard_register_adapter("parentclass", adapter)

    # Object with multiple classes - parentclass is second
    obj <- structure(list(value = 42), class = c("childclass", "parentclass"))
    found <- shard_get_adapter(obj)

    expect_identical(found$class, "parentclass")

    shard:::shard_clear_adapters()
})

test_that("adapter validation rejects invalid adapters", {
    shard:::shard_clear_adapters()

    # Missing class
    expect_error(
        shard_register_adapter("Test", list(
            children = function(x) list(),
            replace = function(x, ch) x
        )),
        "class"
    )

    # Class mismatch
    expect_error(
        shard_register_adapter("Test", list(
            class = "Other",
            children = function(x) list(),
            replace = function(x, ch) x
        )),
        "does not match"
    )

    # Missing children
    expect_error(
        shard_register_adapter("Test", list(
            class = "Test",
            replace = function(x, ch) x
        )),
        "children"
    )

    # Missing replace
    expect_error(
        shard_register_adapter("Test", list(
            class = "Test",
            children = function(x) list()
        )),
        "replace"
    )

    # Invalid path_prefix
    expect_error(
        shard_register_adapter("Test", list(
            class = "Test",
            children = function(x) list(),
            replace = function(x, ch) x,
            path_prefix = c("a", "b")  # Should be single string
        )),
        "path_prefix"
    )
})

test_that("deep share uses adapter's children function", {
    shard:::shard_clear_adapters()

    # Create a simple S3 class with internal structure
    create_myobj <- function(data, meta) {
        structure(list(data = data, meta = meta), class = "myobj")
    }

    # Register adapter that only exposes 'data'
    shard_register_adapter("myobj", list(
        class = "myobj",
        children = function(x) list(data = x$data),
        replace = function(x, children) {
            x$data <- children$data
            x
        }
    ))

    big_data <- rnorm(10000)
    obj <- create_myobj(data = big_data, meta = list(name = "test"))

    shared <- share(obj, deep = TRUE, min_bytes = 1000)

    expect_s3_class(shared, "shard_deep_shared")

    # Only the data should be shared (meta is not exposed by adapter)
    expect_equal(shared$summary$shared_count, 1)

    recovered <- fetch(shared)
    expect_equal(recovered$data, big_data)
    expect_equal(recovered$meta, list(name = "test"))  # meta preserved

    close(shared)
    shard:::shard_clear_adapters()
})

test_that("deep share uses adapter's replace function for reconstruction", {
    shard:::shard_clear_adapters()

    # Track calls to replace
    replace_called <- FALSE

    shard_register_adapter("tracked", list(
        class = "tracked",
        children = function(x) list(val = x$value),
        replace = function(x, children) {
            replace_called <<- TRUE
            x$value <- children$val
            x$reconstructed <- TRUE
            x
        }
    ))

    obj <- structure(list(value = rnorm(10000)), class = "tracked")
    shared <- share(obj, deep = TRUE, min_bytes = 1000)

    recovered <- fetch(shared)

    expect_true(replace_called)
    expect_true(recovered$reconstructed)

    close(shared)
    shard:::shard_clear_adapters()
})

test_that("adapter with path_prefix uses custom path", {
    shard:::shard_clear_adapters()

    shard_register_adapter("prefixed", list(
        class = "prefixed",
        children = function(x) list(data = x$data),
        replace = function(x, children) {
            x$data <- children$data
            x
        },
        path_prefix = "/custom"
    ))

    obj <- structure(list(data = rnorm(10000)), class = "prefixed")
    shared <- share(obj, deep = TRUE, min_bytes = 1000)

    # Check that the path includes the custom prefix
    expect_true(grepl("/custom", shared$tree$children$data$path))

    close(shared)
    shard:::shard_clear_adapters()
})

test_that("no adapter falls back to S4 slot traversal", {
    skip_if_not_installed("methods")
    shard:::shard_clear_adapters()

    # Define a simple S4 class
    setClass("TestS4", slots = c(mat = "matrix", info = "character"))

    big_mat <- matrix(rnorm(10000), nrow = 100)
    obj <- new("TestS4", mat = big_mat, info = "test")

    shared <- share(obj, deep = TRUE, min_bytes = 1000)

    expect_s3_class(shared, "shard_deep_shared")
    expect_equal(shared$summary$shared_count, 1)  # matrix should be shared

    recovered <- fetch(shared)
    expect_s4_class(recovered, "TestS4")
    expect_equal(recovered@mat, big_mat)
    expect_equal(recovered@info, "test")

    close(shared)
    removeClass("TestS4")
})

test_that("S4 adapter takes precedence over generic slot traversal", {
    skip_if_not_installed("methods")
    shard:::shard_clear_adapters()

    setClass("AdaptedS4", slots = c(data = "numeric", private = "numeric"))

    # Adapter only exposes 'data', not 'private'
    shard_register_adapter("AdaptedS4", list(
        class = "AdaptedS4",
        children = function(x) list(data = x@data),
        replace = function(x, children) {
            x@data <- children$data
            x
        }
    ))

    obj <- new("AdaptedS4", data = rnorm(10000), private = 1:100)
    shared <- share(obj, deep = TRUE, min_bytes = 1000)

    # Only data should be traversed (via adapter), not private
    expect_equal(shared$summary$shared_count, 1)

    recovered <- fetch(shared)
    expect_equal(recovered@data, obj@data)
    expect_equal(recovered@private, 1:100)  # preserved unchanged

    close(shared)
    shard:::shard_clear_adapters()
    removeClass("AdaptedS4")
})

test_that("shard_list_adapters returns registered classes", {
    shard:::shard_clear_adapters()

    expect_length(shard_list_adapters(), 0)

    shard_register_adapter("ClassA", list(
        class = "ClassA",
        children = function(x) list(),
        replace = function(x, ch) x
    ))

    shard_register_adapter("ClassB", list(
        class = "ClassB",
        children = function(x) list(),
        replace = function(x, ch) x
    ))

    adapters <- shard_list_adapters()
    expect_length(adapters, 2)
    expect_true("ClassA" %in% adapters)
    expect_true("ClassB" %in% adapters)

    shard:::shard_clear_adapters()
})

test_that("shard_clear_adapters removes all adapters", {
    shard:::shard_clear_adapters()

    shard_register_adapter("A", list(class = "A", children = function(x) list(), replace = function(x, ch) x))
    shard_register_adapter("B", list(class = "B", children = function(x) list(), replace = function(x, ch) x))
    shard_register_adapter("C", list(class = "C", children = function(x) list(), replace = function(x, ch) x))

    expect_length(shard_list_adapters(), 3)

    n <- shard:::shard_clear_adapters()

    expect_equal(n, 3)
    expect_length(shard_list_adapters(), 0)
})

Try the shard package in your browser

Any scripts or data that you put into this service are public.

shard documentation built on April 3, 2026, 9:08 a.m.