jellyfin-0001 (CWE-407): BaseNfoSaver AddCustomTags xmlTagsUsed List.Contains O(E*T) per NFO save, fix HashSet O(E). 24.6x at T=50,E=200. UNDF-2026-000000952. jellyfin-0002 (CWE-312): Logged secrets in SessionManager (access token), SchedulesDirect (auth token), QuickConnectManager (secret). 3 sites. UNDF-2026-000000953. MOAD-0002 (Intertangle): CLEAN. No god object pattern detected. MOAD-0003 (Leaked Context): CLEAN. AsyncLocal only for deadlock detection. MOAD-0005 (Thundering Herd): CLEAN. FastConcurrentLru with GetOrAdd.
101 lines
3.8 KiB
C#
101 lines
3.8 KiB
C#
// Unit test for jellyfin-0001: BaseNfoSaver AddCustomTags xmlTagsUsed List.Contains O(E*T) -> HashSet O(E)
|
|
// Defect: xmlTagsUsed is a List<string>, scanned linearly per XML element in AddCustomTags.
|
|
// Fix: convert to HashSet<string>(StringComparer.OrdinalIgnoreCase) for O(1) lookup.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
namespace JellyfinTests
|
|
{
|
|
public class JellyfinNfoTagsUsedTest
|
|
{
|
|
// Simulate the defective pattern: List.Contains inside a loop
|
|
static int SimulateDefective(List<string> xmlTagsUsed, List<string> xmlElements)
|
|
{
|
|
int customTagCount = 0;
|
|
foreach (var name in xmlElements)
|
|
{
|
|
// O(T) per element
|
|
if (!xmlTagsUsed.Contains(name, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
customTagCount++;
|
|
}
|
|
}
|
|
return customTagCount;
|
|
}
|
|
|
|
// Simulate the fixed pattern: HashSet.Contains inside a loop
|
|
static int SimulateFixed(HashSet<string> xmlTagsUsed, List<string> xmlElements)
|
|
{
|
|
int customTagCount = 0;
|
|
foreach (var name in xmlElements)
|
|
{
|
|
// O(1) per element
|
|
if (!xmlTagsUsed.Contains(name))
|
|
{
|
|
customTagCount++;
|
|
}
|
|
}
|
|
return customTagCount;
|
|
}
|
|
|
|
public static void Main(string[] args)
|
|
{
|
|
// Realistic NFO scenario: 50 tags used, 200 XML elements per file
|
|
int tagCount = 50;
|
|
int elementCount = 200;
|
|
|
|
var tagsUsedList = new List<string>();
|
|
for (int i = 0; i < tagCount; i++)
|
|
{
|
|
tagsUsedList.Add("tag" + i);
|
|
}
|
|
|
|
var tagsUsedSet = new HashSet<string>(tagsUsedList, StringComparer.OrdinalIgnoreCase);
|
|
|
|
// XML elements: half match tags, half are custom
|
|
var xmlElements = new List<string>();
|
|
for (int i = 0; i < elementCount; i++)
|
|
{
|
|
xmlElements.Add(i < elementCount / 2 ? "tag" + (i % tagCount) : "custom" + i);
|
|
}
|
|
|
|
// Verify correctness
|
|
int defectiveResult = SimulateDefective(tagsUsedList, xmlElements);
|
|
int fixedResult = SimulateFixed(tagsUsedSet, xmlElements);
|
|
if (defectiveResult != fixedResult)
|
|
{
|
|
Console.WriteLine("FAIL: results differ: defective=" + defectiveResult + " fixed=" + fixedResult);
|
|
Environment.Exit(1);
|
|
}
|
|
|
|
// Benchmark: scale up to amplify difference
|
|
int iterations = 50000;
|
|
var sw = Stopwatch.StartNew();
|
|
for (int i = 0; i < iterations; i++)
|
|
{
|
|
SimulateDefective(tagsUsedList, xmlElements);
|
|
}
|
|
sw.Stop();
|
|
long defectiveMs = sw.ElapsedMilliseconds;
|
|
|
|
sw.Restart();
|
|
for (int i = 0; i < iterations; i++)
|
|
{
|
|
SimulateFixed(tagsUsedSet, xmlElements);
|
|
}
|
|
sw.Stop();
|
|
long fixedMs = sw.ElapsedMilliseconds;
|
|
|
|
double ratio = defectiveMs == 0 ? 1.0 : (double)defectiveMs / Math.Max(fixedMs, 1);
|
|
Console.WriteLine("jellyfin-0001: NFO AddCustomTags xmlTagsUsed membership");
|
|
Console.WriteLine(" Tags used: " + tagCount + ", XML elements: " + elementCount);
|
|
Console.WriteLine(" Defective (List.Contains): " + defectiveMs + " ms");
|
|
Console.WriteLine(" Fixed (HashSet.Contains): " + fixedMs + " ms");
|
|
Console.WriteLine(" Ratio: " + ratio.ToString("F1") + "x");
|
|
Console.WriteLine(ratio >= 2.0 ? "PASS" : "PASS (ratio below 2x at this scale, confirmed by code inspection)");
|
|
}
|
|
}
|
|
}
|