tests/testthat/test-no-materialize.R

# Test that common R operations do NOT materialize jlview ALTREP objects.
# These operations call REAL(x)/INTEGER(x) which invokes Dataptr(TRUE),
# but they only read — they should NOT trigger a copy into R's heap.

test_that("colSums does not materialize dense jlview matrix", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    m <- jlview(JuliaCall::julia_eval("randn(100, 50)", need_return = "Julia"))
    expect_false(jlview_info(m)$materialized)

    cs <- colSums(m)
    expect_true(is_jlview(m))
    expect_false(jlview_info(m)$materialized)
    expect_equal(length(cs), 50L)
})

test_that("rowSums does not materialize dense jlview matrix", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    m <- jlview(JuliaCall::julia_eval("randn(100, 50)", need_return = "Julia"))
    rs <- rowSums(m)
    expect_true(is_jlview(m))
    expect_false(jlview_info(m)$materialized)
    expect_equal(length(rs), 100L)
})

test_that("colMeans does not materialize dense jlview matrix", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    m <- jlview(JuliaCall::julia_eval("randn(100, 50)", need_return = "Julia"))
    cm <- colMeans(m)
    expect_true(is_jlview(m))
    expect_false(jlview_info(m)$materialized)
    expect_equal(length(cm), 50L)
})

test_that("rowMeans does not materialize dense jlview matrix", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    m <- jlview(JuliaCall::julia_eval("randn(100, 50)", need_return = "Julia"))
    rm <- rowMeans(m)
    expect_true(is_jlview(m))
    expect_false(jlview_info(m)$materialized)
    expect_equal(length(rm), 100L)
})

test_that("apply does not materialize dense jlview matrix", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    m <- jlview(JuliaCall::julia_eval("randn(20, 10)", need_return = "Julia"))
    a <- apply(m, 2, mean)
    expect_true(is_jlview(m))
    expect_false(jlview_info(m)$materialized)
    expect_equal(length(a), 10L)
})

test_that("matrix multiply does not materialize dense jlview matrix", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    m <- jlview(JuliaCall::julia_eval("randn(20, 10)", need_return = "Julia"))
    v <- m %*% rep(1, 10)
    expect_true(is_jlview(m))
    expect_false(jlview_info(m)$materialized)
    expect_equal(nrow(v), 20L)
})

test_that("t() does not materialize dense jlview matrix", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    m <- jlview(JuliaCall::julia_eval("randn(20, 10)", need_return = "Julia"))
    tr <- t(m)
    expect_true(is_jlview(m))
    expect_false(jlview_info(m)$materialized)
    expect_equal(dim(tr), c(10L, 20L))
})

test_that("range does not materialize dense jlview vector", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    v <- jlview(JuliaCall::julia_eval("randn(1000)", need_return = "Julia"))
    r <- range(v)
    expect_true(is_jlview(v))
    expect_false(jlview_info(v)$materialized)
    expect_equal(length(r), 2L)
})

test_that("which.min does not materialize dense jlview vector", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    v <- jlview(JuliaCall::julia_eval("randn(1000)", need_return = "Julia"))
    w <- which.min(v)
    expect_true(is_jlview(v))
    expect_false(jlview_info(v)$materialized)
    expect_true(w >= 1L && w <= 1000L)
})

test_that("sum does not materialize (uses ALTREP Sum method)", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    v <- jlview(JuliaCall::julia_eval("randn(1000)", need_return = "Julia"))
    s <- sum(v)
    expect_true(is_jlview(v))
    expect_false(jlview_info(v)$materialized)
})

test_that("mean does not materialize", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    v <- jlview(JuliaCall::julia_eval("randn(1000)", need_return = "Julia"))
    m <- mean(v)
    expect_true(is_jlview(v))
    expect_false(jlview_info(v)$materialized)
})

test_that("element access does not materialize", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    v <- jlview(JuliaCall::julia_eval("randn(1000)", need_return = "Julia"))
    x <- v[500]
    expect_true(is_jlview(v))
    expect_false(jlview_info(v)$materialized)
})

test_that("subsetting does not materialize", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    v <- jlview(JuliaCall::julia_eval("randn(1000)", need_return = "Julia"))
    x <- v[1:100]
    expect_true(is_jlview(v))
    expect_false(jlview_info(v)$materialized)
    expect_equal(length(x), 100L)
})

test_that("integer colSums does not materialize", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    m <- jlview(JuliaCall::julia_eval("Int32.(reshape(1:200, 20, 10))", need_return = "Julia"))
    cs <- colSums(m)
    expect_true(is_jlview(m))
    expect_false(jlview_info(m)$materialized)
    expect_equal(length(cs), 10L)
})

test_that("named matrix colSums does not materialize", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    JuliaCall::julia_command("using NamedArrays")
    JuliaCall::julia_command("_nm_cs = NamedArray(randn(50, 20))")
    m <- jlview_named_matrix(JuliaCall::julia_eval("_nm_cs", need_return = "Julia"))

    expect_true(is_jlview(m))
    cs <- colSums(m)
    expect_true(is_jlview(m))
    expect_false(jlview_info(m)$materialized)
    expect_equal(length(cs), 20L)
})

test_that("memory does not increase after colSums on large matrix", {
    skip_if(!JULIA_AVAILABLE, "Julia not available")

    JuliaCall::julia_command("_big_nomat = randn(5000, 1000)")
    m <- jlview(JuliaCall::julia_eval("_big_nomat", need_return = "Julia"))

    gc()
    mem_before <- gc()[2, 2] # Vcells MB

    cs <- colSums(m)

    gc()
    mem_after <- gc()[2, 2]

    # Should not increase by more than a few MB (result vector + overhead)
    # Without the fix this would increase by ~38 MB (5000*1000*8 bytes)
    expect_true(is_jlview(m))
    expect_false(jlview_info(m)$materialized)
    expect_lt(mem_after - mem_before, 5) # less than 5 MB increase
})

Try the jlview package in your browser

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

jlview documentation built on March 24, 2026, 1:07 a.m.