Files
LlamaCasty/ytLive.Tests/AppLogRotationTests.cs
T
gramps b1d6034662 TASK 55 slice 1: the startup log is size-capped
The creator (2026-10-09): 'we need some sort of log-rotation so that we don't
inadvertently fill up a user's drive.' startup.log was uncapped - 23MB and growing
every few seconds, because FfmpegEncoder logs every non-progress ffmpeg stderr line.

The policy lives in a new Helpers/LogRotation.cs as a pure function over a path
(RollIfNeeded(logPath, maxBytes, retainedFiles)), so AppLog's 156 call sites keep
their surface and the rule is testable on its own. At 5MB the live file rolls to
startup.1.log and one rolled copy is kept - the FILE SET is bounded, not just the
live file, so a long stream cannot grow the log without limit. A failed rotation
degrades to the old behaviour instead of taking the app down.

Pattern reference (cited): Serilog's file sink pairs fileSizeLimitBytes (the cap)
with retainedFileCountLimit (how many rolled files to keep) -
https://github.com/serilog/serilog-sinks-file

Guard: ytLive.Tests/AppLogRotationTests.cs - the ONE test drives the real
AppLog.Write against a temp dir (two internal seams: PathOverride, MaxBytes) and
asserts the roll, the fresh live file, and that after a SECOND roll the directory
still holds exactly startup.log + startup.1.log.

Also fixed a stale catalog row: TASK 12 (master limiter) shipped 2026-08-15 and was
still marked 'Queued'.
2026-10-22 06:51:04 -07:00

67 lines
2.5 KiB
C#

using System;
using System.IO;
using System.Linq;
using Xunit;
using ytLive.Helpers;
namespace ytLive.Tests;
/// <summary>TASK 55, slice 1 — the creator (2026-10-09): *"we need some sort of log-rotation
/// so that we don't inadvertently fill up a user's drive."*
///
/// <para>One test: the app's log is size-capped. Past the cap the file rolls to
/// <c>startup.1.log</c>, the live file starts fresh with the newest entry, and the SET of
/// files stays bounded — a long stream cannot grow the log without limit. (The old
/// behaviour appended forever; the file was 23 MB and growing every few seconds.)</para></summary>
public class AppLogRotationTests
{
[Fact]
public void The_startup_log_rolls_at_its_cap_and_the_file_set_stays_bounded()
{
var directory = Path.Combine(Path.GetTempPath(),
"ytlive-log-rotation-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
var live = Path.Combine(directory, "startup.log");
var rolled = Path.Combine(directory, "startup.1.log");
const long cap = 4096;
var first = "first-" + new string('x', 3000);
var second = "second-" + new string('y', 3000);
var third = "third-" + new string('z', 3000);
try
{
AppLog.PathOverride = live;
AppLog.MaxBytes = cap;
AppLog.Write(first);
AppLog.Write(second);
Assert.False(File.Exists(rolled)); // under the cap — nothing rolls yet
AppLog.Write(third); // this write trips the cap
Assert.True(File.Exists(rolled));
Assert.Contains(first, File.ReadAllText(rolled));
Assert.Contains(second, File.ReadAllText(rolled));
Assert.DoesNotContain(first, File.ReadAllText(live)); // the live file is fresh
Assert.Contains(third, File.ReadAllText(live));
Assert.True(new FileInfo(live).Length < cap);
// A second roll must not grow the file set: the live file plus one rolled copy.
AppLog.Write(first);
AppLog.Write(second);
AppLog.Write(third);
var files = Directory.GetFiles(directory)
.Select(Path.GetFileName)
.OrderBy(name => name, StringComparer.Ordinal)
.ToArray();
Assert.Equal(new[] { "startup.1.log", "startup.log" }, files);
}
finally
{
AppLog.PathOverride = null;
AppLog.MaxBytes = 5 * 1024 * 1024;
try { Directory.Delete(directory, true); } catch { }
}
}
}