test(unit-tests): silence captured stdout in Testing env
The unit-test suite was spending most of its wall clock writing logs. NUnit captures stdout per test and embeds it in the trx; with HttpLogging emitting full request/response per controller call, EF Core SQL at Information level, and ReferenceDataImporter banners running ~500x (once per factory construction), the trx grew to 3.2 GB and the NUnit result-XML serializer OOMed in StringBuilder.ToString() — which the runner reported as one mysteriously failed test, masking a real date-dependent failure underneath. Three sources silenced under environment "Testing": - appsettings.Testing.json drops Default + Microsoft.AspNetCore + HttpLoggingMiddleware + EntityFrameworkCore to Warning. - Program.cs skips app.UseHttpLogging() entirely (avoids the middleware overhead, not just the log emission). - ReferenceDataImporter takes optional TextWriters; the test factory passes TextWriter.Null. Per-importer helpers become instance methods so they can use the injected writer. Result on a fresh run with ParallelScope.Fixtures already in place: - Test duration: 1m46s -> 59s - Wall clock: 2m23s -> 1m00s - trx size: 3.2 GB -> 1.7 MB The previously-masked date-dependent failure (PackControllerFullCatalog .Info_returns_full_35_pack_catalog_from_production_seed asserting 35 active packs as of 2026-05-23 against a live clock) is now visible and can be addressed separately. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -15,14 +15,30 @@ namespace SVSim.Bootstrap.Importers;
|
||||
/// </summary>
|
||||
public class ReferenceDataImporter
|
||||
{
|
||||
private readonly TextWriter _out;
|
||||
private readonly TextWriter _err;
|
||||
|
||||
public ReferenceDataImporter() : this(Console.Out, Console.Error) { }
|
||||
|
||||
/// <summary>
|
||||
/// Pass <see cref="TextWriter.Null"/> for both to silence progress banners (tests
|
||||
/// instantiate this importer ~500 times per run; the captured stdout otherwise OOMs
|
||||
/// the NUnit trx serializer).
|
||||
/// </summary>
|
||||
public ReferenceDataImporter(TextWriter output, TextWriter error)
|
||||
{
|
||||
_out = output;
|
||||
_err = error;
|
||||
}
|
||||
|
||||
public async Task ImportAllAsync(SVSimDbContext context, string dataDir)
|
||||
{
|
||||
if (!Directory.Exists(dataDir))
|
||||
{
|
||||
Console.Error.WriteLine($"[ReferenceDataImporter] Data dir missing: {dataDir}");
|
||||
_err.WriteLine($"[ReferenceDataImporter] Data dir missing: {dataDir}");
|
||||
return;
|
||||
}
|
||||
Console.WriteLine($"[ReferenceDataImporter] Reading CSVs from {dataDir}...");
|
||||
_out.WriteLine($"[ReferenceDataImporter] Reading CSVs from {dataDir}...");
|
||||
|
||||
await ImportClasses(context, dataDir);
|
||||
await ImportLeaderSkins(context, dataDir);
|
||||
@@ -34,10 +50,10 @@ public class ReferenceDataImporter
|
||||
await ImportRankInfo(context, dataDir);
|
||||
await ImportClassExp(context, dataDir);
|
||||
|
||||
Console.WriteLine("[ReferenceDataImporter] Done.");
|
||||
_out.WriteLine("[ReferenceDataImporter] Done.");
|
||||
}
|
||||
|
||||
private static async Task ImportClasses(SVSimDbContext ctx, string dir)
|
||||
private async Task ImportClasses(SVSimDbContext ctx, string dir)
|
||||
{
|
||||
var rows = ReadCsv<ClassEntry, ClassEntryMap>(dir, "classes.csv");
|
||||
var existing = await ctx.Classes.ToDictionaryAsync(c => c.Id);
|
||||
@@ -51,10 +67,10 @@ public class ReferenceDataImporter
|
||||
else { ctx.Classes.Add(r); created++; }
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
Console.WriteLine($"[ReferenceDataImporter] Classes: +{created} / ~{updated}");
|
||||
_out.WriteLine($"[ReferenceDataImporter] Classes: +{created} / ~{updated}");
|
||||
}
|
||||
|
||||
private static async Task ImportLeaderSkins(SVSimDbContext ctx, string dir)
|
||||
private async Task ImportLeaderSkins(SVSimDbContext ctx, string dir)
|
||||
{
|
||||
var rows = ReadCsv<LeaderSkinEntry, LeaderSkinEntryMap>(dir, "leaderskins.csv");
|
||||
// CSV writes class_chara_id=0 for neutral/unassigned; the FK column is nullable.
|
||||
@@ -74,10 +90,10 @@ public class ReferenceDataImporter
|
||||
else { ctx.LeaderSkins.Add(r); created++; }
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
Console.WriteLine($"[ReferenceDataImporter] LeaderSkins: +{created} / ~{updated}");
|
||||
_out.WriteLine($"[ReferenceDataImporter] LeaderSkins: +{created} / ~{updated}");
|
||||
}
|
||||
|
||||
private static async Task ImportSleeves(SVSimDbContext ctx, string dir)
|
||||
private async Task ImportSleeves(SVSimDbContext ctx, string dir)
|
||||
{
|
||||
var rows = ReadCsv<SleeveEntry, SleeveEntryMap>(dir, "sleeves.csv");
|
||||
var existing = (await ctx.Sleeves.ToListAsync()).ToHashSet();
|
||||
@@ -88,10 +104,10 @@ public class ReferenceDataImporter
|
||||
ctx.Sleeves.Add(r); created++;
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
Console.WriteLine($"[ReferenceDataImporter] Sleeves: +{created}");
|
||||
_out.WriteLine($"[ReferenceDataImporter] Sleeves: +{created}");
|
||||
}
|
||||
|
||||
private static async Task ImportEmblems(SVSimDbContext ctx, string dir)
|
||||
private async Task ImportEmblems(SVSimDbContext ctx, string dir)
|
||||
{
|
||||
var rows = ReadCsv<EmblemEntry, EmblemEntryMap>(dir, "emblems.csv");
|
||||
var existing = (await ctx.Emblems.Select(e => e.Id).ToListAsync()).ToHashSet();
|
||||
@@ -102,10 +118,10 @@ public class ReferenceDataImporter
|
||||
ctx.Emblems.Add(r); created++;
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
Console.WriteLine($"[ReferenceDataImporter] Emblems: +{created}");
|
||||
_out.WriteLine($"[ReferenceDataImporter] Emblems: +{created}");
|
||||
}
|
||||
|
||||
private static async Task ImportDegrees(SVSimDbContext ctx, string dir)
|
||||
private async Task ImportDegrees(SVSimDbContext ctx, string dir)
|
||||
{
|
||||
var rows = ReadCsv<DegreeEntry, DegreeEntryMap>(dir, "degrees.csv");
|
||||
var existing = (await ctx.Degrees.Select(e => e.Id).ToListAsync()).ToHashSet();
|
||||
@@ -116,10 +132,10 @@ public class ReferenceDataImporter
|
||||
ctx.Degrees.Add(r); created++;
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
Console.WriteLine($"[ReferenceDataImporter] Degrees: +{created}");
|
||||
_out.WriteLine($"[ReferenceDataImporter] Degrees: +{created}");
|
||||
}
|
||||
|
||||
private static async Task ImportBattlefields(SVSimDbContext ctx, string dir)
|
||||
private async Task ImportBattlefields(SVSimDbContext ctx, string dir)
|
||||
{
|
||||
var rows = ReadCsv<BattlefieldEntry, BattlefieldEntryMap>(dir, "battlefields.csv");
|
||||
var existing = await ctx.Battlefields.ToDictionaryAsync(b => b.Id);
|
||||
@@ -133,10 +149,10 @@ public class ReferenceDataImporter
|
||||
else { ctx.Battlefields.Add(r); created++; }
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
Console.WriteLine($"[ReferenceDataImporter] Battlefields: +{created} / ~{updated}");
|
||||
_out.WriteLine($"[ReferenceDataImporter] Battlefields: +{created} / ~{updated}");
|
||||
}
|
||||
|
||||
private static async Task ImportMyPageBackgrounds(SVSimDbContext ctx, string dir)
|
||||
private async Task ImportMyPageBackgrounds(SVSimDbContext ctx, string dir)
|
||||
{
|
||||
var rows = ReadCsv<MyPageBackgroundEntry, MyPageBackgroundEntryMap>(dir, "mypagebackgrounds.csv");
|
||||
var existing = (await ctx.MyPageBackgrounds.Select(e => e.Id).ToListAsync()).ToHashSet();
|
||||
@@ -147,10 +163,10 @@ public class ReferenceDataImporter
|
||||
ctx.MyPageBackgrounds.Add(r); created++;
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
Console.WriteLine($"[ReferenceDataImporter] MyPageBackgrounds: +{created}");
|
||||
_out.WriteLine($"[ReferenceDataImporter] MyPageBackgrounds: +{created}");
|
||||
}
|
||||
|
||||
private static async Task ImportRankInfo(SVSimDbContext ctx, string dir)
|
||||
private async Task ImportRankInfo(SVSimDbContext ctx, string dir)
|
||||
{
|
||||
var rows = ReadCsv<RankInfoEntry, RankInfoEntryMap>(dir, "ranks.csv");
|
||||
var existing = await ctx.RankInfo.ToDictionaryAsync(r => r.Id);
|
||||
@@ -164,7 +180,7 @@ public class ReferenceDataImporter
|
||||
else { ctx.RankInfo.Add(r); created++; }
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
Console.WriteLine($"[ReferenceDataImporter] RankInfo: +{created} / ~{updated}");
|
||||
_out.WriteLine($"[ReferenceDataImporter] RankInfo: +{created} / ~{updated}");
|
||||
}
|
||||
|
||||
private static bool ApplyRankUpdates(RankInfoEntry e, RankInfoEntry r)
|
||||
@@ -189,7 +205,7 @@ public class ReferenceDataImporter
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static async Task ImportClassExp(SVSimDbContext ctx, string dir)
|
||||
private async Task ImportClassExp(SVSimDbContext ctx, string dir)
|
||||
{
|
||||
var rows = ReadCsv<ClassExpEntry, ClassExpEntryMap>(dir, "classexp.csv");
|
||||
var existing = await ctx.ClassExpCurve.ToDictionaryAsync(c => c.Id);
|
||||
@@ -203,15 +219,15 @@ public class ReferenceDataImporter
|
||||
else { ctx.ClassExpCurve.Add(r); created++; }
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
Console.WriteLine($"[ReferenceDataImporter] ClassExp: +{created} / ~{updated}");
|
||||
_out.WriteLine($"[ReferenceDataImporter] ClassExp: +{created} / ~{updated}");
|
||||
}
|
||||
|
||||
private static List<T> ReadCsv<T, TMap>(string dir, string fileName) where TMap : ClassMap<T>, new()
|
||||
private List<T> ReadCsv<T, TMap>(string dir, string fileName) where TMap : ClassMap<T>, new()
|
||||
{
|
||||
string path = Path.Combine(dir, fileName);
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Console.Error.WriteLine($"[ReferenceDataImporter] Missing CSV: {path}");
|
||||
_err.WriteLine($"[ReferenceDataImporter] Missing CSV: {path}");
|
||||
return new List<T>();
|
||||
}
|
||||
using var reader = new StreamReader(path);
|
||||
|
||||
Reference in New Issue
Block a user