test_that("constructor sets attributes", { x <- new_vctr(1:4, class = "x", x = 1) expect_equal(x, structure(1:4, class = c("x", "vctrs_vctr"), x = 1)) x <- new_vctr(1:4, class = "x", x = 1, inherit_base_type = TRUE) expect_equal(x, structure(1:4, class = c("x", "vctrs_vctr", "integer"), x = 1)) }) test_that(".data must be a vector", { expect_error(new_vctr(mean), "vector type") }) test_that("attributes other than names are ignored", { out <- new_vctr(structure(1, a = 1)) expect_null(attributes(out)$a) }) test_that("default format method is internal", { x <- new_vctr(1, class = "x") expect_equal(format(x), format(x)) }) test_that("vctr class is proxied", { expect_identical(vec_proxy(new_vctr(1:3)), new_vctr(1:3)) expect_identical(vec_proxy(new_vctr(as.list(1:3))), unclass(new_vctr(as.list(1:3)))) expect_true(vec_is(new_vctr(as.list(1:3)))) }) test_that("Can opt out of base type", { x <- new_vctr(1, class = "x", inherit_base_type = FALSE) expect_s3_class(x, c("x", "vctrs_vctr"), exact = TRUE) }) test_that("base type is always set for lists", { expect_s3_class(new_vctr(list()), "list") }) test_that("cannot opt out of the base type with lists", { expect_error(new_vctr(list(), inherit_base_type = FALSE), "must inherit from the base type") }) test_that("data frames are not allowed", { expect_error(new_vctr(mtcars), "can't be a data frame") }) test_that("attributes must be named", { expect_error(vec_set_attributes(1, list(1)), "must be named") expect_error(vec_set_attributes(1, list(y = 1, 2)), "2 does not") }) test_that("can strip all attributes without adding new ones", { expect_equal(vec_set_attributes(structure(1, a = 1), NULL), 1) }) test_that("`c.vctrs_vctr()` checks for default method arguments (#791)", { expect_error(c(new_vctr(1), recursive = TRUE), "`recursive` must be") expect_error(c(new_vctr(1), use.names = FALSE), "`use.names` must be") }) # Cast/restore ------------------------------------------------------------ test_that("cast to NULL returns x", { x <- new_vctr(1, class = "x") expect_equal(vec_cast(NULL, x), NULL) }) test_that("cast succeeds if attributes equal", { x1 <- new_vctr(1, class = "x", a = 1, b = 2) x2 <- new_vctr(2, class = "x", a = 1, b = 2) expect_equal(vec_cast(x1, x2), x1) expect_equal(vec_cast(x2, x1), x2) }) test_that("and fails if attributes are different", { x1 <- new_vctr(1, class = "x", a = 1, b = 2) x2 <- new_vctr(2, class = "x", a = 2, b = 2) expect_error(vec_cast(x1, x2), class = "vctrs_error_incompatible_type") }) test_that("restoring to atomic vector of same type preserves attributes", { x1 <- new_vctr(1, class = "x") x2 <- new_vctr(2, class = "x") expect_equal(vec_restore(2, x1), x2) }) test_that("restoring to atomic vector of different type throws error", { x1 <- new_vctr(1, class = "x") expect_error(vec_restore("x", x1), class = "vctrs_error_incompatible_type") }) test_that("base coercion methods mapped to vec_cast", { x <- new_vctr(1, inherit_base_type = FALSE) expect_error(as.logical(x), class = "vctrs_error_incompatible_type") expect_error(as.integer(x), class = "vctrs_error_incompatible_type") expect_error(as.logical(x), class = "vctrs_error_incompatible_type") expect_error(as.double(x), class = "vctrs_error_incompatible_type") expect_error(as.character(x), class = "vctrs_error_incompatible_type") expect_error(as.Date(x), class = "vctrs_error_incompatible_type") expect_error(as.POSIXct(x), class = "vctrs_error_incompatible_type") expect_error(as.POSIXlt(x), class = "vctrs_error_incompatible_type") expect_equal(as.list(x), list(x)) }) test_that("as.data.frame creates data frame", { x <- new_vctr(1:3) df <- as.data.frame(x) expect_s3_class(df, "data.frame") expect_equal(nrow(df), 3) expect_named(df, "x") }) test_that("as.data.frame on shaped vctrs doesn't bring along extra attributes", { x <- new_vctr(1:3, foo = "bar", dim = c(3L, 1L)) df <- as.data.frame(x) expect_null(attr(df, "foo", exact = TRUE)) }) test_that("as.data.frame2() unclasses input to avoid dispatch on as.data.frame()", { x <- structure(1:2, dim = c(1L, 2L), dimnames = list("r1", c("c1", "c2")), class = "foo") expect <- data.frame(c1 = 1L, c2 = 2L, row.names = "r1") local_methods(as.data.frame.foo = function(x, ...) "dispatched!") expect_identical(as.data.frame2(x), expect) }) test_that("as.list() chops vectors", { expect_identical( as.list(new_vctr(1:3)), list(new_vctr(1L), new_vctr(2L), new_vctr(3L)) ) x <- new_vctr(as.list(1:3)) expect_identical(as.list(x), as.list(1:3)) }) # equality + comparison + arith + math --------------------------------------- test_that("equality functions remapped", { x <- new_vctr(c(1, 1, NA), inherit_base_type = FALSE) expect_error(x == 1, class = "vctrs_error_incompatible_type") expect_error(x != 1, class = "vctrs_error_incompatible_type") expect_equal(is.na(x), c(FALSE, FALSE, TRUE)) expect_true(anyNA(x)) expect_equal(unique(x), new_vctr(c(1, NA), inherit_base_type = FALSE)) expect_equal(duplicated(x), c(FALSE, TRUE, FALSE)) expect_true(anyDuplicated(x)) }) test_that("is.na<-() supported", { x <- new_vctr(1:4) is.na(x) <- c(FALSE, FALSE, TRUE, NA) expect_identical(x, new_vctr(c(1:2, NA, 4L))) x <- new_vctr(1:4) is.na(x) <- TRUE expect_identical(x, new_vctr(rep(NA_integer_, 4))) x <- new_vctr(1:4) is.na(x) <- c(2, 3) expect_identical(x, new_vctr(c(1L, NA, NA, 4L))) names <- c("a", "b", "c", "d") x <- set_names(new_vctr(1:4), names) is.na(x) <- c("d", "b", "b") expect_identical(x, set_names(new_vctr(c(1L, NA, 3L, NA)), names)) x <- new_vctr(1:4) expect_error(is.na(x) <- "x", "character names to index an unnamed vector") expect_error(is.na(x) <- 5, class = "vctrs_error_subscript_oob") }) test_that("comparison functions remapped", { local_methods( vec_proxy_compare.bizzaro = function(x, ...) -vec_data(x) ) x1 <- new_vctr(c(1, 2), class = "bizzaro") x2 <- new_vctr(2, class = "bizzaro") expect_equal(order(x1), c(2L, 1L)) expect_equal(x1 < x2, c(FALSE, FALSE)) expect_equal(x1 <= x2, c(FALSE, TRUE)) expect_equal(x1 > x2, c(TRUE, FALSE)) expect_equal(x1 >= x2, c(TRUE, TRUE)) }) test_that("operators remapped", { local_methods( vec_arith.bizzaro = function(op, x, y) 1L ) x <- new_vctr(c(1, 2), class = "bizzaro") expect_equal(x + 1, 1L) expect_equal(x - 1, 1L) expect_equal(x * 1, 1L) expect_equal(x / 1, 1L) expect_equal(x ^ 1, 1L) expect_equal(x %% 1, 1L) expect_equal(x %/% 1, 1L) expect_equal(x & 1, 1L) expect_equal(x | 1, 1L) expect_equal(!x, 1L) expect_equal(+x, 1L) expect_equal(-x, 1L) }) test_that("math functions overridden", { local_methods( vec_math.bizzaro = function(fn, x, ...) vec_math_base(fn, 2L) ) x <- new_vctr(c(1, NA), class = "bizzaro") expect_equal(mean(x), 2L) expect_equal(sum(x), 2L) expect_equal(is.finite(x), TRUE) expect_equal(is.infinite(x), FALSE) expect_equal(is.nan(x), FALSE) }) test_that("diff matches base R", { local_methods( vec_arith.vctrs_minus = function(op, x, y) vec_arith_base(op, x, y) ) x1 <- cumsum(cumsum(1:10)) x2 <- new_vctr(x1, class = "vctrs_minus") expect_equal(diff(x2), diff(x1)) expect_equal(diff(x2, lag = 2L), diff(x1, lag = 2L)) expect_equal(diff(x2, differences = 2L), diff(x1, differences = 2L)) expect_equal(diff(x2, lag = 11), x2[0L]) expect_equal(diff(x2, differences = 11), x2[0L]) }) test_that("na.omit() works and retains metadata", { x <- new_vctr(c(a = 1, b = NA, c = 2)) result <- na.omit(x) expect <- vec_slice(x, c(1, 3)) attr(expect, "na.action") <- structure(c(b = 2L), class = "omit") expect_identical(result, expect) }) test_that("na.omit() returns its input unchanged if there are no missing values", { x <- new_vctr(c(a = 1, b = 2)) expect_identical(na.omit(x), x) }) test_that("na.exclude() works and retains metadata", { x <- new_vctr(c(a = 1, b = NA, c = 2)) result <- na.exclude(x) expect <- vec_slice(x, c(1, 3)) attr(expect, "na.action") <- structure(c(b = 2L), class = "exclude") expect_identical(result, expect) }) test_that("na.fail() works", { x <- new_vctr(c(a = 1, b = 2)) expect_identical(na.fail(x), x) x <- new_vctr(c(a = 1, b = NA, c = 2)) expect_snapshot(error = TRUE, na.fail(x)) }) # names ------------------------------------------------------------------- test_that("`NA_character_` names are repaired to the empty string (#784)", { expect_named(new_vctr(set_names(1, NA_character_)), "") expect_named(new_vctr(set_names(1:2, c("a", NA))), c("a", "")) }) test_that("the empty string is an allowed name (#784)", { expect_named(new_vctr(set_names(1, "")), "") expect_named(new_vctr(set_names(1:2, c("", "x"))), c("", "x")) }) test_that("can not provide invalid names", { x <- new_vctr(c(a = 1, b = 2)) expect_error(names(x) <- "x", "length") expect_error(names(x) <- c("x", "y", "z"), "length") expect_error(names(x) <- NULL, NA) }) test_that("can set names to the empty string (#784)", { x <- new_vctr(c(a = 1, b = 2)) names(x) <- c("", "") expect_named(x, c("", "")) names(x) <- c("", "x") expect_named(x, c("", "x")) }) test_that("setting names to `NA_character_` repairs to the empty string (#784)", { x <- new_vctr(1:2) names(x) <- c(NA_character_, NA_character_) expect_named(x, c("", "")) names(x) <- c("x", NA_character_) expect_named(x, c("x", "")) }) test_that("can use [ and [[ with names", { local_methods( vec_ptype2.vctrs_vctr.double = function(...) dbl(), vec_ptype2.double.vctrs_vctr = function(...) dbl() ) x <- new_vctr(c(a = 1, b = 2)) expect_equal(x["b"], new_vctr(c(b = 2))) expect_equal(x[["b"]], new_vctr(2)) # [[ drops names x[["c"]] <- 3 expect_equal(x[["c"]], new_vctr(3)) x["d"] <- 4 expect_equal(x[["d"]], new_vctr(4)) }) test_that("can use [ and [[ with names - list vctr", { local_methods( vec_ptype2.vctrs_vctr = function(...) list(), vec_ptype2.list.vctrs_vctr = function(...) list() ) y <- new_vctr(list(a = 1, b = 2)) y[["c"]] <- 3 expect_equal(y[["c"]], 3) y["d"] <- list(4) expect_equal(y[["d"]], 4) }) test_that("can use [[<- to replace n-dimensional elements", { local_methods( vec_restore.vctrs_mtrx = function(x, to, ...) x, vec_ptype2.double.vctrs_mtrx = function(...) dbl(), vec_ptype2.vctrs_mtrx = function(...) dbl() ) x <- new_vctr(rep(1, times = 4), dim = c(2, 2), class = "vctrs_mtrx") x[[2, 2]] <- 4 expect_equal(x[[2, 2]], 4) }) test_that("subsetting preserves attributes", { x <- new_vctr(c(a = 1, b = 2)) attr(x, "extra") <- TRUE y <- x[1] expect_equal(attr(x, "extra"), TRUE) }) test_that("$ inherits from underlying vector", { x1 <- new_vctr(c(a = 1, b = 2)) expect_error(x1$a, "atomic vectors") expect_error(x1$a <- 2, "atomic vectors") x2 <- new_vctr(list(a = 1, b = 2)) expect_equal(x2$a, 1) x2$a <- 10 expect_equal(x2$a, 10) }) # unsupported/unimplemented operations -------------------------------------- test_that("can't touch protected attributes", { x <- new_vctr(1:4) expect_error(dim(x) <- c(2, 2), class = "vctrs_error_unsupported") expect_error(dimnames(x) <- list("x"), class = "vctrs_error_unsupported") expect_error(levels(x) <- "x", class = "vctrs_error_unsupported") # It is expected that unimplemented `levels()` returns `NULL` expect_null(levels(x)) # But it's ok to set names to NULL; this happens at least in vec_c # and maybe elsewhere. We may need to back off on this level of # strictness in the future expect_error(names(x) <- NULL, NA) }) test_that("summary is unimplemented", { x <- new_vctr(1:4) expect_error(summary(x), class = "vctrs_error_unimplemented") }) # hidden class ------------------------------------------------------------ # We can't construct classes in test because the methods are not found # when vctr generics call other generics. Instead we rely on a very simple # class implemented in vctr.R test_that("class preserved when subsetting", { h <- new_hidden(1:4) expect_s3_class(h, "hidden") expect_s3_class(h[1], "hidden") expect_s3_class(h[[1]], "hidden") expect_s3_class(rep(h[1], 2), "hidden") expect_s3_class(as.list(h)[[1]], "hidden") length(h) <- 3 expect_s3_class(h, "hidden") }) test_that("RHS cast when using subset assign", { local_hidden() h <- new_hidden(1) expect_error(h[[1]] <- "x", class = "vctrs_error_incompatible_type") expect_error(h[1] <- "x", class = "vctrs_error_incompatible_type") h[2] <- 1 expect_equal(h, new_hidden(c(1, 1))) h[[2]] <- 2 expect_equal(h, new_hidden(c(1, 2))) }) test_that("c passes on to vec_c", { local_hidden() h <- new_hidden(1) expect_equal(c(h), h) expect_equal(c(h, NULL), h) expect_equal(c(h, 1), rep(h, 2)) expect_equal(c(h, h), rep(h, 2)) }) test_that("rbind does not fail with an unclear message (#1186)", { # In general, vec_rbind() should be preferred. In many cases rbind() does # the right thing, this test exists to alert us if this changes in the future. skip_on_cran() local_hidden() h <- new_hidden(1) # A failure in levels() for vctrs_vctr classes was the underlying issue. expect_null(levels(h)) df <- data_frame(h = h) expect_equal(rbind(df), df) expect_equal(rbind(df, NULL), df) expect_equal(rbind(df, data_frame(h = 1)), unrownames(df[c(1, 1), , drop = FALSE])) expect_equal(rbind(df, df), unrownames(df[c(1, 1), , drop = FALSE])) # An example where the result differs, to alert us if the rbind() contract # changes expect_equal(rbind(data_frame(h = 1), df), data_frame(h = c(1, 1))) }) test_that("summaries preserve class", { h <- new_hidden(c(1, 2)) expect_equal(sum(h), new_hidden(3)) expect_equal(mean(h), new_hidden(1.5)) }) test_that("methods using vec_proxy_compare agree with base", { h <- new_hidden(c(1:10)) h_na <- new_hidden(c(NA, 1:10)) expect_agree <- function(f, x, na.rm = FALSE) { f <- enexpr(f) expect_equal(vec_data((!!f)(x, na.rm = na.rm)), (!!f)(vec_data(x), na.rm = na.rm)) } expect_agree(min, h) expect_agree(max, h) expect_agree(range, h) expect_agree(min, h_na) expect_agree(max, h_na) expect_agree(range, h_na) expect_agree(min, h_na, na.rm = TRUE) expect_agree(max, h_na, na.rm = TRUE) expect_agree(range, h_na, na.rm = TRUE) }) test_that("can put in data frame", { h <- new_hidden(1:4) expect_named(as.data.frame(h), "h") expect_named(data.frame(x = h), "x") }) test_that("base coercions default to vec_cast", { local_hidden() h <- new_hidden(1) expect_error(as.character(h), class = "vctrs_error_incompatible_type") expect_error(as.integer(h), class = "vctrs_error_incompatible_type") expect_error(generics::as.factor(h), class = "vctrs_error_incompatible_type") expect_error(generics::as.ordered(h), class = "vctrs_error_incompatible_type") expect_error(generics::as.difftime(h), class = "vctrs_error_incompatible_type") expect_equal(as.logical(h), TRUE) expect_equal(as.double(h), 1) }) test_that("default print and str methods are useful", { h <- new_hidden(1:4) expect_snapshot(h) expect_snapshot(h[0]) expect_snapshot(str(h)) }) test_that("default print method shows names", { h <- new_hidden(c(A = 1, B = 2, C = 3)) expect_snapshot(h) }) test_that("can't transpose", { h <- new_hidden(1:4) expect_error(t(h), class = "vctrs_error_unsupported") }) test_that("shaped vctrs can be cast to data frames", { x <- new_vctr(1:4, dim = 4) expect_identical(as.data.frame(x), data.frame(V1 = 1:4)) x <- new_vctr(1:4, dim = c(2, 2)) expect_identical(as.data.frame(x), data.frame(V1 = 1:2, V2 = 3:4)) }) # slicing ----------------------------------------------------------------- test_that("additional subscripts are handled (#269)", { new_2d <- function(.data, dim) { vctrs::new_vctr(.data, dim = dim, class = "vctrs_2d") } x <- new_2d(c(1, 2), dim = c(2L, 1L)) expect_identical(x[1], new_2d(1, dim = c(1, 1))) expect_identical(x[1, 1], new_2d(1, dim = c(1, 1))) expect_identical(x[, 1], new_2d(c(1, 2), dim = c(2, 1))) }) # summary generics -------------------------------------------------------- test_that("na.rm is forwarded to summary generics", { x <- new_vctr(dbl(1, 2, NA)) expect_identical(mean(x, na.rm = FALSE), new_vctr(dbl(NA))) expect_identical(mean(x, na.rm = TRUE), new_vctr(1.5)) expect_identical(min(x, na.rm = FALSE), new_vctr(dbl(NA))) expect_identical(min(x, na.rm = TRUE), new_vctr(1)) expect_identical(max(x, na.rm = FALSE), new_vctr(dbl(NA))) expect_identical(max(x, na.rm = TRUE), new_vctr(2)) x <- new_vctr(lgl(TRUE, NA)) expect_identical(all(x, na.rm = FALSE), lgl(NA)) expect_identical(all(x, na.rm = TRUE), TRUE) }) test_that("Summary generics behave identically to base for empty vctrs (#88)", { expect_warning( expect_identical( new_vctr(max(numeric())), max(new_vctr(numeric())) ) ) expect_warning( expect_identical( new_vctr(min(numeric())), min(new_vctr(numeric())) ) ) expect_identical( new_vctr(range(1)), range(new_vctr(1)) ) expect_identical( new_vctr(prod(numeric())), prod(new_vctr(numeric())) ) expect_identical( new_vctr(sum(numeric())), sum(new_vctr(numeric())) ) expect_identical( new_vctr(cummax(numeric())), cummax(new_vctr(numeric())) ) expect_identical( new_vctr(cummin(numeric())), cummin(new_vctr(numeric())) ) expect_identical( new_vctr(cumprod(numeric())), cumprod(new_vctr(numeric())) ) expect_identical( new_vctr(cumsum(numeric())), cumsum(new_vctr(numeric())) ) expect_identical( new_vctr(mean(numeric())), mean(new_vctr(numeric())) ) }) test_that("generic predicates return logical vectors (#251)", { x <- new_vctr(c(1, 2)) expect_identical(is.finite(x), c(TRUE, TRUE)) expect_identical(is.infinite(x), c(FALSE, FALSE)) expect_identical(is.nan(x), c(FALSE, FALSE)) x <- new_vctr(TRUE) expect_identical(any(x), TRUE) expect_identical(all(x), TRUE) }) test_that("xtfrm() converts logical types to integer", { expect_identical(xtfrm(new_vctr(c(TRUE, FALSE, NA), foo = "bar")), c(1L, 0L, NA)) }) test_that("xtfrm() unwraps integer and double atomic types", { expect_identical(xtfrm(new_vctr(1:3, foo = "bar")), 1:3) expect_identical(xtfrm(new_vctr(1:3 + 0, foo = "bar")), 1:3 + 0) }) test_that("xtfrm() works with character subclass", { expect_identical(xtfrm(new_vctr(chr())), int()) }) test_that("xtfrm() maintains ties when falling through to vec_rank() (#1354)", { x <- new_vctr(c("F", "F", "M", "A", "M", "A")) expect_identical(xtfrm(x), c(2L, 2L, 3L, 1L, 3L, 1L)) }) test_that("xtfrm() propagates NAs when falling through to vec_rank()", { x <- new_vctr(c("F", NA)) expect_identical(xtfrm(x), c(1L, NA)) }) test_that("xtfrm() uses C locale ordering with character proxies", { x <- new_vctr(c("A", "a", "B")) expect_identical(xtfrm(x), c(1L, 3L, 2L)) }) test_that("xtfrm() works on rcrd types", { x <- new_rcrd(list(x = c(1, 2, 1, NA), y = c(2, 1, 1, NA))) expect_identical(xtfrm(x), c(2L, 3L, 1L, NA)) }) test_that("Summary generics behave as expected if na.rm = TRUE and all values are NA (#1357)", { expect_identical(min(new_vctr(NA_real_), na.rm = TRUE), new_vctr(Inf)) expect_identical(max(new_vctr(NA_real_), na.rm = TRUE), new_vctr(-Inf)) expect_identical(range(new_vctr(NA_real_), na.rm = TRUE), new_vctr(c(Inf, -Inf))) expect_identical(min(new_vctr(NA_integer_), na.rm = TRUE), new_vctr(NA_integer_)) expect_identical(max(new_vctr(NA_integer_), na.rm = TRUE), new_vctr(NA_integer_)) expect_identical(range(new_vctr(NA_integer_), na.rm = TRUE), new_vctr(c(NA_integer_, NA_integer_))) expect_identical(min(new_vctr(NA_character_), na.rm = TRUE), new_vctr(NA_character_)) expect_identical(max(new_vctr(NA_character_), na.rm = TRUE), new_vctr(NA_character_)) expect_identical(range(new_vctr(NA_character_), na.rm = TRUE), new_vctr(c(NA_character_, NA_character_))) expect_identical(min(new_vctr(NA), na.rm = TRUE), new_vctr(NA)) expect_identical(max(new_vctr(NA), na.rm = TRUE), new_vctr(NA)) expect_identical(range(new_vctr(NA), na.rm = TRUE), new_vctr(c(NA, NA))) }) test_that("Summary generics behave as expected for empty vctrs (#1357)", { expect_identical(min(new_vctr(numeric()), na.rm = TRUE), new_vctr(Inf)) expect_identical(max(new_vctr(numeric()), na.rm = TRUE), new_vctr(-Inf)) expect_identical(range(new_vctr(numeric()), na.rm = TRUE), new_vctr(c(Inf, -Inf))) expect_identical(min(new_vctr(integer()), na.rm = TRUE), new_vctr(NA_integer_)) expect_identical(max(new_vctr(integer()), na.rm = TRUE), new_vctr(NA_integer_)) expect_identical(range(new_vctr(integer()), na.rm = TRUE), new_vctr(c(NA_integer_, NA_integer_))) expect_identical(min(new_vctr(character()), na.rm = TRUE), new_vctr(NA_character_)) expect_identical(max(new_vctr(character()), na.rm = TRUE), new_vctr(NA_character_)) expect_identical(range(new_vctr(character()), na.rm = TRUE), new_vctr(c(NA_character_, NA_character_))) expect_identical(min(new_vctr(logical()), na.rm = TRUE), new_vctr(NA)) expect_identical(max(new_vctr(logical()), na.rm = TRUE), new_vctr(NA)) expect_identical(range(new_vctr(logical()), na.rm = TRUE), new_vctr(c(NA, NA))) }) test_that("anyNA(recursive = TRUE) works with lists (#1278)", { x <- list_of(1:4, c(2, NA, 5)) expect_false(anyNA(x)) expect_true(anyNA(x, recursive = TRUE)) x <- new_vctr(list(1:4, list(c(2, NA, 5)))) expect_false(anyNA(x)) expect_true(anyNA(x, recursive = TRUE)) })
Generated by dwww version 1.15 on Thu May 23 07:59:42 CEST 2024.