Use getTime().utc in both message constructors, document the timezone change, and cover concurrent construction and structured formatting. Restore ordinary logging calls in the parallel writer fixture. Validation: 32 unit tests and 29 regression tests pass. Timestamp sanitizer checks pass under UTC and non-UTC timezones. The full suite retains the known allocator reports in factory-state and allocator.
130 lines
7.9 KiB
Markdown
130 lines
7.9 KiB
Markdown
# Regression tests
|
|
|
|
These tests assert the intended behavior. Known bugs are ordinary failures,
|
|
not expected failures: the tasks should become green as the defects are fixed.
|
|
|
|
Run from the repository root:
|
|
|
|
| Command | Coverage |
|
|
| --- | --- |
|
|
| `nimble test` | All unit, regression, and ThreadSanitizer tests under ORC with threads enabled |
|
|
| `nimble testUnit` | Embedded unit tests, including writer submission/shutdown coordination |
|
|
| `nimble testRegression` | Custom-state/formatter ownership, handle lifetime, configuration freshness, refresh failure recovery, appender-state cleanup, and writer shutdown |
|
|
| `nimble testThreadSafety` | Separate ThreadSanitizer processes for service ownership, last-owner teardown, reconfiguration, shared state, factory state, formatter closures, collector snapshots, timestamps, the Nim allocator, and writer lifecycle, plus an independent-service control |
|
|
|
|
`nimble test` attempts every suite even if an earlier suite fails to compile or
|
|
run, summarizes the failing suites, and exits unsuccessfully if any suite fails.
|
|
The subset commands use the same build and execution procedures as the full run.
|
|
Old executables are removed before compilation, and a new executable must exist
|
|
before a suite runs. This prevents stale binaries from hiding compilation errors
|
|
when a nested Nimble command fails to propagate its exit status.
|
|
|
|
Executables are generated in ignored `tests/bin/`. The shutdown child is built
|
|
with `-d:multithreaded`; `--threads:on` alone does not enable background writers.
|
|
The ownership tests do not need background I/O. The embedded writer lifecycle
|
|
tests call the internal writer functions directly; their sanitizer build is
|
|
`tests/bin/writer_lifecycle`.
|
|
|
|
The full run and the sanitizer subset require Linux, Bash, GNU `timeout`, and a
|
|
C compiler and runtime supporting `-fsanitize=thread`. The sanitizer instruments
|
|
the generated Nim C code, including the ORC runtime. A sanitizer report, timeout,
|
|
crash, or runtime startup failure causes a nonzero result; consult the diagnostic
|
|
to distinguish these.
|
|
Each scenario runs separately so one failure does not hide another. To inspect
|
|
one scenario after building:
|
|
|
|
```sh
|
|
TSAN_OPTIONS=halt_on_error=1:exitcode=66 timeout 30s tests/bin/thread_safety service
|
|
TSAN_OPTIONS=halt_on_error=1:exitcode=66 timeout 30s tests/bin/writer_lifecycle 'Background writer lifecycle::*'
|
|
```
|
|
|
|
The worker start barrier ensures argument setup finishes before the concurrent
|
|
operations begin. In `service-lifetime`, workers first acquire local services;
|
|
the parent then releases the original handle before allowing them to proceed.
|
|
Thread arguments contain borrowed pointers so no hidden owning argument keeps
|
|
the service alive. Atomic construction/destruction counters verify that every
|
|
tracked appender is released exactly once after the workers join.
|
|
`service-reconfigure` exercises concurrent updates and local cache refresh.
|
|
`shared-state-lifetime` drops the original state handle before concurrent
|
|
mutations and verifies one final destruction with all updates present.
|
|
`formatter-lifetime` drops an already-used original formatter and checks both
|
|
independent closure counters and exact cleanup on worker threads. `factory-state`
|
|
uses per-thread cyclic reference graphs, explicitly runs the ORC collector, and
|
|
checks that each worker maintains its own counter. `collector` concurrently
|
|
records messages containing JSON and exceptions and mutates returned snapshots.
|
|
These ownership scenarios use explicit timestamps where messages are needed.
|
|
|
|
The separate `timestamp` case exercises both `initLogMessage()` overloads and
|
|
structured JSON formatting concurrently. Generated timestamps use UTC, avoiding
|
|
the libc local-time conversion race previously reached via `std/times.now()`.
|
|
The timestamp regressions verify the captured instant, UTC timezone, and `Z`
|
|
suffix in structured output.
|
|
|
|
`allocator` calls the standalone reproduction in `runtime_allocator.nim`, which
|
|
imports no logging code. It exercises sequence growth and clearing under a lock
|
|
alongside thread-local closure allocations. On Nim 2.2.10 it reproduces a report
|
|
in `system/alloc.nim`'s `compensateCounters` / shared-free-list path. The same
|
|
runtime report can occur in `factory-state` and other allocation-heavy cases.
|
|
The ten ownership scenarios passed debug and release diagnostic builds with
|
|
`-d:useMalloc`;
|
|
`nimble test` deliberately retains the default allocator and exposes these
|
|
reports rather than suppressing them.
|
|
|
|
The original custom-state and formatter scenarios retain parent owners until all
|
|
workers join. They only read application data; their concurrent writes are the
|
|
implicit ownership operations caused by cloning and destruction. The independent
|
|
case creates a separate service inside each worker and should pass. Custom-state
|
|
and formatter cloning is serialized with a lock, matching configuration refresh;
|
|
the copied appenders are used and destroyed outside that lock.
|
|
|
|
`ownership.nim`, imported by `regressions.nim`, checks independent snapshots,
|
|
exception recovery, factory and clone behavior, and the built-in formatter paths.
|
|
Compile-time checks reject direct and nested refs, captured formatter arguments,
|
|
and embedded synchronization primitives. The formerly unsafe custom-state and
|
|
formatter scenarios now exercise the supported shared-handle and factory APIs;
|
|
the unsafe forms are tested as compilation failures.
|
|
|
|
The in-process regression tests are deterministic. The lifetime suite checks
|
|
copying, self-assignment, replacement, moving, container copies, and exporting a
|
|
handle from a local service. `isEnabled` tests verify that namespace thresholds
|
|
remain cached until an explicit refresh, a logging call, or `getLogger`, while
|
|
the root fallback is read from shared state. This behavior is intentional so
|
|
guards can avoid configuration refresh work.
|
|
Refresh tests verify that repeated clone failures
|
|
preserve the old appenders and thresholds, release partial replacements, and
|
|
allow a later retry without another configuration change. A successful refresh
|
|
releases the replaced cache and installs both appenders and thresholds.
|
|
Shutdown tests launch
|
|
a fresh process for each scenario and check all 10,000 numbered messages after
|
|
normal exit, using isolated temporary paths and a 15-second deadline. Scenarios
|
|
cover stdout, stderr, files, empty formatted messages, multiple file destinations,
|
|
and four joined producers. They verify message content and order within each
|
|
producer and destination, using the real exit hook without sleeps or explicit
|
|
flushes. File batches must preserve blank records and finish their final batch
|
|
before the writer stops.
|
|
|
|
The embedded lifecycle tests start producers, release them to submit more
|
|
records while the file writer shuts down, and verify every accepted record.
|
|
They also check repeated shutdown, rejection of sends after shutdown, and
|
|
waking the console writer after an empty message. The sanitizer task runs these
|
|
tests as a separate process even when another scenario fails.
|
|
|
|
Nim 2.2.10's `Channel.close()` destroys its internal mutex while holding it,
|
|
which the lifecycle sanitizer test exposed after the writers had joined.
|
|
Shutdown therefore drains and joins the writers, rejects further submissions,
|
|
and leaves the process-wide channels allocated until process exit. It does not
|
|
call the faulty runtime teardown or suppress its diagnostics.
|
|
|
|
The suite provides targeted regressions; it does not exhaust thread scheduling
|
|
or ORC cycle-collector interleavings.
|
|
|
|
The ownership scenarios, lifetime suite, and appender-state cleanup regression
|
|
exercise the new ownership contracts. Allocator reports remain for subsequent
|
|
work.
|
|
|
|
The standalone `ownership.nim` regression suite passed AddressSanitizer and
|
|
LeakSanitizer with `--mm:orc -d:useMalloc`. An additional worker-thread build found
|
|
no invalid accesses in the shared-state, factory-state, formatter-lifetime, and collector
|
|
scenarios. LeakSanitizer reported per-thread ORC cycle-registration buffers
|
|
allocated by the Nim runtime; this is not a clean leak-check result.
|