Add failing ORC thread-safety regressions and run all suites with nimble test
This commit is contained in:
1 parent
15af03e5c5
commit
c1c8a3c072
8 files changed
+342
-2
No files matched your search
@@ -0,0 +1,90 @@
|
||||
import std/[os, osproc, strutils, tempfiles, unittest]
|
||||
import ../src/namespaced_logging
|
||||
|
||||
type
|
||||
CloneControl = object
|
||||
failNext: bool
|
||||
FallibleAppender = ref object of LogAppender
|
||||
control: ptr CloneControl
|
||||
|
||||
method clone(app: FallibleAppender): LogAppender {.gcsafe.} =
|
||||
if app.control.failNext:
|
||||
app.control.failNext = false
|
||||
raise newException(ValueError, "injected clone failure")
|
||||
FallibleAppender(control: app.control)
|
||||
|
||||
type
|
||||
LifetimeProbeObj = object
|
||||
destroyed: ptr int
|
||||
LifetimeProbe = ref LifetimeProbeObj
|
||||
|
||||
proc `=destroy`(probe: LifetimeProbeObj) =
|
||||
inc probe.destroyed[]
|
||||
|
||||
proc ignoreMessage(state: LifetimeProbe, msg: LogMessage) {.gcsafe, noconv.} =
|
||||
discard
|
||||
|
||||
proc createAndReleaseService(destroyed: ptr int) =
|
||||
var service = initLogService()
|
||||
service.addAppender(initCustomLogAppender(
|
||||
LifetimeProbe(destroyed: destroyed), ignoreMessage))
|
||||
# All owning handles leave scope here. No thread or cycle is involved.
|
||||
|
||||
suite "ORC ownership and configuration regressions":
|
||||
test "isEnabled observes a namespace change before any log call":
|
||||
var service = initLogService()
|
||||
service.setThreshold("worker", lvlError)
|
||||
let local = service.threadLocalRef()
|
||||
let logger = local.getLogger("worker")
|
||||
check not logger.isEnabled(lvlInfo)
|
||||
|
||||
# Simulate a change from another service handle without introducing a
|
||||
# reference-count race into this deterministic configuration test.
|
||||
service.setThreshold("worker", lvlInfo)
|
||||
check logger.isEnabled(lvlInfo)
|
||||
|
||||
test "a failed refresh is retried without another configuration change":
|
||||
var control: CloneControl
|
||||
var service = initLogService()
|
||||
service.addAppender(FallibleAppender(control: addr control))
|
||||
let local = service.threadLocalRef()
|
||||
discard local.getLogger("worker")
|
||||
service.setRootThreshold(lvlInfo)
|
||||
|
||||
control.failNext = true
|
||||
expect ValueError:
|
||||
local.ensureFreshness()
|
||||
|
||||
# clone now succeeds; the failed refresh must not have marked the
|
||||
# incomplete local configuration as current.
|
||||
local.ensureFreshness()
|
||||
check local.appenders().len == 1
|
||||
|
||||
test "releasing the service releases state owned by its appenders":
|
||||
var destroyed = 0
|
||||
createAndReleaseService(addr destroyed)
|
||||
check destroyed == 1
|
||||
|
||||
suite "Background writer shutdown regressions":
|
||||
for destination in ["file", "console"]:
|
||||
test destination & " writer drains accepted messages at normal process exit":
|
||||
let directory = createTempDir("namespaced_logging_shutdown_", "")
|
||||
let outputPath = directory / "messages.log"
|
||||
defer: removeDir(directory)
|
||||
|
||||
let child = startProcess(parentDir(getAppFilename()) / "writer_child",
|
||||
args = [destination, outputPath], options = {poParentStreams})
|
||||
defer: close(child)
|
||||
let exitCode = child.waitForExit(15_000)
|
||||
if exitCode == -1:
|
||||
child.kill()
|
||||
discard child.waitForExit()
|
||||
check exitCode == 0
|
||||
require fileExists(outputPath)
|
||||
|
||||
let received = readFile(outputPath).splitLines()
|
||||
# Check every record and its order, not just file existence. splitLines
|
||||
# includes one final empty item because each record ends with a newline.
|
||||
check received.len == 10_001
|
||||
for i in 0 ..< min(10_000, received.len - 1):
|
||||
check received[i] == "message " & $i
|
||||
Reference in new issue
Block a user