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)); } } }