tests/testthat/test_Appender.R

context("Appender")


event <- LogEvent$new(
  logger = Logger$new("dummy"),
  level = 200L,
  timestamp = structure(1541175573.9308, class = c("POSIXct", "POSIXt")),
  caller = NA_character_,
  msg = "foo bar"
)




# Appender --------------------------------------------------------------------
test_that("Appender: $append() works", {
  app <- Appender$new()
  expect_match(app$append(event), "foo bar")
})




test_that("Appender: $set_threshold() works", {
  app <- Appender$new()

  app$set_threshold(200)
  expect_identical(app$threshold, 200L)
  app$set_threshold("info")
  expect_identical(app$threshold, 400L)
  app$set_threshold(NA)
  expect_identical(app$threshold, NA_integer_)
  expect_error(app$set_threshold("blubb"), "log levels")
})




# AppenderFile ---------------------------------------------------------
test_that("AppenderFile: logging with LayoutFormat", {
  tf <- tempfile()
  on.exit(unlink(tf))

  app <- AppenderFile$new(file = tf)
  app$append(event)
  app$append(event)
  res <- readLines(tf)
  expect_true(grepl("foo", res[[1]]))
  expect_true(grepl("bar", res[[2]]))
})



test_that("AppenderFile: logging with LayoutJson", {
  tf <- tempfile()
  on.exit(unlink(tf))

  app <- AppenderFile$new(file = tf, layout = LayoutJson$new())
  app$append(event)
  app$append(event)
  tres <- read_json_lines(tf)
  eres <- data.table::rbindlist(list(event$values, event$values))

  expect_identical(tres[["level"]], eres[["level"]])
  expect_identical(tres[["msg"]], eres[["msg"]])
  expect_true(all(is.na(tres[["caller"]])) && all(is.na(eres[["caller"]])))
  expect_equal(as.POSIXct(tres[["timestamp"]]), eres[["timestamp"]], tolerance = 1)
})



test_that("AppenderFile: creates empty log file on init", {
  tf <- tempfile()
  on.exit(unlink(tf))

  expect_error(AppenderFile$new(
    file = file.path(tempdir(), "non", "existing", "directory" )
  ))

  AppenderFile$new(file = file.path(tf))
  expect_true(file.exists(tf))
})



test_that("AppenderFile: $show() works", {
  tf <- tempfile()
  on.exit(unlink(tf))

  tf2 <- tempfile()
  on.exit(unlink(tf2), add = TRUE)

  lg <- get_logger("test")$
    set_propagate(FALSE)$
    set_threshold(NA)$
    set_appenders(list(
      char = AppenderFile$new(file = tf),
      num  = AppenderFile$new(file = tf2, layout = LayoutFormat$new("%n %m"))
    ))

  on.exit(get_logger("test", reset = TRUE), add = TRUE)

  lg$info("foo bar")
  lg$info("blah blubb")
  lg$warn("warnwarn")
  lg$debug("bugbug")

  expect_output({
    expect_length(lg$appenders$char$show("warn"), 1)
    expect_length(lg$appenders$char$show("info"), 3)
    expect_length(lg$appenders$char$show("debug"), 4)

    expect_length(lg$appenders$num$show("warn"), 1)
    expect_length(lg$appenders$num$show("info"), 3)
    expect_length(lg$appenders$num$show("debug"), 4)
  })
})



test_that("AppenderFile$data throws an error", {
  tf <- tempfile()
  on.exit(unlink(tf))

  lg <- get_logger("test")$
    set_propagate(FALSE)$
    set_threshold(NA)$
    set_appenders(list(file = AppenderFile$new(file = tf)))

  on.exit(get_logger("test", reset = TRUE), add = TRUE)

  lg$info("foo bar")
  lg$info("blah blubb")

  expect_error(lg$appenders$file$data, class = "CannotParseLogError")
})





test_that("AppenderFile: creates empty log file on init", {
  tf <- tempfile()
  on.exit(unlink(tf))

  app <- AppenderFile$new(file = tf)

  lg <-
    get_logger("lgr/testAppenderFileCreatesEmptyLogFile")$
    set_appenders(app)$
    set_propagate(FALSE)

  lg$fatal("foo", foo = "bar")

  res <- readLines(tf)
  expect_match(res, '"bar"}')
})


# AppenderJson ------------------------------------------------------------

test_that("AppenderJson: AppenderFile with LayoutJson$show() and $data() work", {
  tf <- tempfile()
  on.exit(unlink(tf))

  # with default format
  app <- AppenderFile$new(file = tf, layout = LayoutJson$new())

  for (i in 1:10)
    app$append(event)

  # show shows the correct number of lines
  r <- utils::capture.output(app$show(n = 3))
  expect_true(grepl( "(level.*){3}", paste(r, collapse = "\n")))
  expect_false(grepl("(level.*){4}", paste(r, collapse = "\n")))

  r <- utils::capture.output(app$show(threshold = 100))
  expect_identical(r, "")

  expect_identical(nrow(app$data), 10L)
})


# AppenderConsole ---------------------------------------------------------

test_that("AppenderConsole: $append() works", {
  app <- AppenderConsole$new()
  expect_match(
    capture.output(app$append(event)),
    "ERROR.*:19:33.*foo.*bar"
  )
})




test_that("AppenderConsole: $filter() works", {
  app1 <- AppenderConsole$new()
  expect_true(app1$filter(event))
  app1$set_filters(list(function(event) FALSE))
  expect_false(app1$filter(event))
})


test_that("AppenderConsole - with connection = stderr() - outputs to stderr", {
  app <- AppenderConsole$new(connection = stderr())

  std_out <- textConnection("msg_out", open = "w", local = TRUE)
  sink(std_out, append = TRUE, type = "message")
  on.exit(sink(NULL, type = "message"), add = TRUE)

  expect_silent(
    app$append(event)
  )

  expect_match(
    msg_out,
    "ERROR.*:19:33.*foo.*bar"
  )
})


test_that("AppenderConsole: chooses stderr by default when in knitr", {
  opts <- options(knitr.in.progress = TRUE)
  on.exit(options(opts), add = TRUE)

  app <- AppenderConsole$new()
  std_out <- textConnection("msg_out", open = "w", local = TRUE)
  sink(std_out, append = TRUE, type = "message")
  on.exit(sink(NULL, type = "message"), add = TRUE)

  expect_silent(
    app$append(event)
  )

  expect_match(
    msg_out,
    "ERROR.*:19:33.*foo.*bar"
  )
})


# AppenderBuffer ----------------------------------------------------
# Tests must be executed in sequence
# setup
  buffer_log <- tempfile()
  teardown(unlink(buffer_log))
  l <- Logger$new(
    "dummy",
    appenders = list(
      buffer = AppenderBuffer$new(
        appenders = list(file = AppenderFile$new(file = buffer_log)),
        buffer_size = 10,
        flush_threshold = "fatal"
      )
    ), propagate = FALSE
  )


test_that("AppenderBuffer: FATAL log level triggers flush", {
  l$info(LETTERS[1:3])
  expect_length(l$appenders$buffer$buffer_events, 1)
  l$info(LETTERS[4:7])
  expect_identical(length(l$appenders$buffer$buffer_events), 2L)

  # FATAL triggers flush with default filters
  l$fatal(letters[1:3])
  expect_identical(l$appenders$buffer$buffer_events, event_list())
  expect_match(
    paste(readLines(buffer_log), collapse = "#"),
    "INFO.*A#INFO.*B#INFO.*C#INFO.*D#INFO.*E#INFO.*F#INFO.*G#FATAL.*a#FATAL.*b#FATAL.*c"
  )

  # Does the next flush flush the correct event?
  l$fatal("x")
  expect_identical(length(readLines(buffer_log)), 11L)
  expect_identical(l$appenders$buffer$buffer_events, event_list())
  expect_match(paste(readLines(buffer_log), collapse = "#"), ".*A#.*B#.*C#.*a#.*b#.*c#.*x")
})




test_that("AppenderBuffer: buffer cycling triggers flush", {
  replicate(10, l$info("z"))
  expect_identical(length(l$appenders$buffer$buffer_events), 10L)
  l$info(c("y", "y", "y"))
  expect_identical(length(readLines(buffer_log)), 24L)
  expect_identical(l$appenders$buffer$buffer_events, event_list())
  expect_match(
    paste(readLines(buffer_log), collapse = "#"),
    ".*A#.*B#.*C#.*a#.*b#.*c#.*x(.*z.*){10}(.*y.*){3}"
  )
})




test_that("AppenderBuffer: manual flush trigger works", {
  l$info(c("y", "y", "y"))
  l$appenders$buffer$flush()
  expect_identical(length(readLines(buffer_log)), 27L)
  eres <- readLines(buffer_log)
  expect_match(paste(eres, collapse = "#"), "(.*z.*){10}(.*y.*){6}")
})




test_that("AppenderBuffer: flush on buffer cycling can be suppressed", {
  eres <- readLines(buffer_log)
  l$appenders$buffer$set_flush_on_rotate(FALSE)
  for (i in 1:15) l$info(i)

  # theres a 10% tolerance for flushing if no flush on rotate is set
  expect_true(length(l$appenders$buffer$buffer_events) >= 10L)
  # Nothing should have been flushed to the log file
  expect_identical(readLines(buffer_log), eres)
})




test_that("AppenderBuffer: object destruction triggers flush", {
  # this test also cleans up behind the logger created at the beginning
  # of this section
  l$appenders$buffer$flush()  # ensure empty appender
  l$info(c("destruction", "destruction"))
  expect_identical(length(l$appenders$buffer$buffer_events), 1L)
  rm(l, inherits = TRUE)
  gc()
  expect_match(
    paste(readLines(buffer_log), collapse = "#"),
    "(.*destruction){2}"
  )
  try(file.remove(buffer_log), silent = TRUE)
})




# the following AppenderBuffer tests are self contained again
test_that("AppenderBuffer: flush on object destruction can be suppressed", {
  # must re-initilaize logger
  buffer_log <- tempfile()
  l <- Logger$new(
    "dummy",
    appenders = list(
      buffer = AppenderBuffer$new(
        appenders = list(file = AppenderFile$new(file = buffer_log)),
        buffer_size = 10
      )
    ),
    propagate = FALSE
  )

  l$info(LETTERS[1:3])
  l$appenders$buffer$set_flush_on_exit(FALSE)
  rm(l)
  gc()
  expect_true(file.exists(buffer_log))
  expect_equal(file.size(buffer_log), 0)

  expect_true(file.remove(buffer_log))
})




test_that("AppenderBuffer: $add_appender()/$remove_appender()", {
  sapp  <- AppenderBuffer$new()
  app1 <- AppenderConsole$new(threshold = 100)
  app2 <- AppenderConsole$new(threshold = 300)

  # add
  expect_silent({
    sapp$add_appender(app1)
    sapp$add_appender(app2, "blah")
    sapp$add_appender(AppenderBuffer$new(), "blubb")
  })
  expect_identical(sapp$appenders[[1]], app1)
  expect_identical(sapp$appenders$blah, app2)

  # remove
  sapp$remove_appender(1)
  expect_length(sapp$appenders, 2L)
  sapp$remove_appender(c("blah", "blubb"))

  # set appenders
  sapp$set_appenders(list(app1, app2))
  expect_length(sapp$appenders, 2)
  sapp$set_appenders(list(app1, app2))
  expect_length(sapp$appenders, 2)
  expect_identical(sapp$appenders[[1]], app1)
  expect_identical(sapp$appenders[[2]], app2)
})




test_that("AppenderBuffer: $show()", {
  l <- Logger$new(
    "buffer test",
    appenders = AppenderBuffer$new(should_flush = NULL),
    propagate = FALSE
  )

  l$fatal("foo")
  l$warn("foo", bar = "foo")
  l$info(1:3)
  l$error("and a list column", df = head(iris), env = environment())

  expect_identical(nrow(l$appenders[[1]]$buffer_df), 6L)
  expect_identical(nrow(l$appenders[[1]]$buffer_dt), 6L)
  expect_length(capture.output(l$appenders[[1]]$show(n = 5, threshold = "warn")), 3L)
})



test_that("AppenderBuffer: cycling works", {
  l <- Logger$new(
    "buffer test",
    appenders = AppenderBuffer$new(buffer_size = 3L, flush_on_rotate = FALSE),
    propagate = FALSE
  )

  l$info("test1")
  l$info("test2")
  l$info("test3")
  l$info("test4")
  l$info("test5")

  expect_identical(
    sapply(l$appenders[[1]]$buffer_events, `[[`, "msg"),
    paste0("test", 2:5)
  )
})




test_that("AppenderBuffer: Custom $should_flush works", {
  l <- Logger$new(
    "buffer test",
    appenders = AppenderBuffer$new(),
    propagate = FALSE
  )

  # FALSE
  l$appenders[[1]]$set_should_flush(function(event) FALSE)
  l$fatal("test")
  expect_length(l$appenders[[1]]$buffer_events, 1L)

  # TRUE
  l$appenders[[1]]$set_should_flush(
    function(event) {
      inherits(event, "LogEvent") && inherits(.obj(), "AppenderBuffer")
    }
  )
  l$fatal("test")
  expect_length(l$appenders[[1]]$buffer_events, 0L)

  # Undefined
  l$appenders[[1]]$set_should_flush(function(event) NA)
  expect_warning(l$fatal("test"))
  expect_length(l$appenders[[1]]$buffer_events, 1L)
  l$appenders[[1]]$set_should_flush(function(event) iris)
  expect_warning(l$fatal("test"))
  expect_length(l$appenders[[1]]$buffer_events, 2L)

  # illegal filter
  expect_error(l$appenders[[1]]$set_should_flush(mean))
})




# self contained buffer tests
test_that("AppenderBuffer: buffer_size 0 works as expected", {
  l <- Logger$new(
    "0 buffer test",
    appenders = list(buffer = AppenderBuffer$new()$
      set_appenders(list(file = AppenderFile$new(file = tempfile())))),
    propagate = FALSE
  )
  on.exit(unlink(l$appenders$buffer$appenders$file$file))

  l$appenders$buffer$set_buffer_size(0)
  expect_silent(l$info(LETTERS[1:3]))
  expect_length(readLines(l$appenders$buffer$appenders$file$file), 3L)

  expect_silent({
    expect_identical(nrow(l$appenders$buffer$buffer_df), 0L)
    expect_identical(nrow(l$appenders$buffer$buffer_dt), 0L)
    expect_length(l$appenders$buffer$buffer_events, 0L)
  })
})




# utils -------------------------------------------------------------------

test_that("default_file_reader() works", {
  tf <- tempfile()
  on.exit(unlink(tf))

  expect_error(expect_warning(default_file_reader(tf, threshold = NA, n = 0)))

  writeLines(LETTERS, tf)
  expect_identical(default_file_reader(tf, threshold = NA, n = 3), c("X", "Y", "Z"))

  writeLines(LETTERS, tf)
  expect_warning(default_file_reader(tf, threshold = 4, n = 3))
})




test_that("standardize_should_flush_output() works", {
  expect_identical(standardize_should_flush_output(TRUE), TRUE)
  expect_identical(standardize_should_flush_output(FALSE), FALSE)
  expect_warning(
    expect_identical(standardize_should_flush_output(NA), FALSE),
    class = "ValueIsNotBoolError"
  )
  expect_warning(
    expect_identical(standardize_should_flush_output(iris), FALSE),
    class = "ValueIsNotBoolError"
  )
})
s-fleck/memlog documentation built on March 6, 2023, 6:52 p.m.