using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using NUnit.Framework;
namespace ProjectM.Tests
{
///
/// Anti-regression guard for the "an edit swallowed the [Test] attribute" hazard — a real dead test shipped at
/// EnemyAIMathTests.cs:107 (a regression guard for the enemy-stuck-on-cover fix that silently never ran). Scans
/// every *Tests.cs source file in this directory and FAILS if a parameterless public-void method (the suite's
/// test-method shape — every helper is static, so `public void` uniquely selects tests) is not immediately
/// preceded by a runner attribute. File I/O is available at EditMode-test time.
///
public class TestAttributeGuardTests
{
static string ThisDir([CallerFilePath] string p = "") => Path.GetDirectoryName(p);
static readonly Regex TestMethod = new Regex(@"^\s*public\s+void\s+[A-Za-z_]\w*\s*\(\s*\)", RegexOptions.Compiled);
static readonly Regex RunnerAttr = new Regex(@"\[\s*(Test|TestCase|TestCaseSource|Theory|SetUp|TearDown|OneTimeSetUp|OneTimeTearDown)\b", RegexOptions.Compiled);
[Test]
public void Every_Public_Void_Test_Method_Has_A_Runner_Attribute()
{
var dir = ThisDir();
Assert.IsTrue(Directory.Exists(dir), $"Test source dir not found: {dir}");
var offenders = new List();
foreach (var file in Directory.GetFiles(dir, "*Tests.cs"))
{
var lines = File.ReadAllLines(file);
for (int i = 0; i < lines.Length; i++)
{
if (!TestMethod.IsMatch(lines[i]))
continue;
// Walk upward past blank / comment lines to the nearest attribute or non-trivial line.
bool attributed = false;
for (int j = i - 1; j >= 0; j--)
{
string t = lines[j].Trim();
if (t.Length == 0 || t.StartsWith("//") || t.StartsWith("/*") || t.StartsWith("*"))
continue;
if (t.StartsWith("["))
{
if (RunnerAttr.IsMatch(t)) { attributed = true; break; }
continue; // a non-runner attribute line — keep scanning the attribute block
}
break; // hit a brace / statement: no attribute block above this method
}
if (!attributed)
offenders.Add($"{Path.GetFileName(file)}:{i + 1} {lines[i].Trim()}");
}
}
Assert.IsEmpty(offenders,
"Test methods missing a runner attribute (swallowed [Test]?):\n" + string.Join("\n", offenders));
}
}
}