using System; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; using SqrtSpace.SpaceTime.Analyzers; using Xunit; namespace SqrtSpace.SpaceTime.Tests.Analyzers; public class LargeAllocationAnalyzerTests : CSharpAnalyzerTest { [Fact] public async Task ToList_OnIQueryable_ProducesDiagnostic() { const string source = @" using System.Linq; class TestClass { void TestMethod() { var context = new TestContext(); var result = context.LargeCollection.{|#0:ToList|}(); } } class TestContext { public IQueryable LargeCollection { get; set; } }"; var test = new CSharpAnalyzerTest { TestCode = source }; test.ExpectedDiagnostics.Add( new DiagnosticResult(LargeAllocationAnalyzer.DiagnosticId, DiagnosticSeverity.Warning) .WithLocation(0) .WithArguments("collection")); await test.RunAsync(); } [Fact] public async Task ToArray_OnLargeCollection_ProducesDiagnostic() { const string source = @" using System.Linq; class TestClass { void TestMethod() { var context = new DatabaseContext(); var array = context.Items.{|#0:ToArray|}(); } } class DatabaseContext { public IQueryable Items { get; set; } } class Item { }"; var test = new CSharpAnalyzerTest { TestCode = source }; test.ExpectedDiagnostics.Add( new DiagnosticResult(LargeAllocationAnalyzer.DiagnosticId, DiagnosticSeverity.Warning) .WithLocation(0) .WithArguments("collection")); await test.RunAsync(); } [Fact] public async Task OrderBy_OnLargeCollection_ProducesDiagnostic() { const string source = @" using System.Linq; class TestClass { void TestMethod() { var context = new DataContext(); var sorted = context.Records.{|#0:OrderBy|}(r => r.Date); } } class DataContext { public IQueryable Records { get; set; } } class Record { public DateTime Date { get; set; } }"; var test = new CSharpAnalyzerTest { TestCode = source }; test.ExpectedDiagnostics.Add( new DiagnosticResult(LargeAllocationAnalyzer.DiagnosticId, DiagnosticSeverity.Warning) .WithLocation(0) .WithArguments("OrderBy")); await test.RunAsync(); } [Fact] public async Task GroupBy_OnLargeCollection_ProducesDiagnostic() { const string source = @" using System.Linq; class TestClass { void TestMethod() { var context = new DataContext(); var groups = context.Users.{|#0:GroupBy|}(u => u.Country); } } class DataContext { public IQueryable Users { get; set; } } class User { public string Country { get; set; } }"; var test = new CSharpAnalyzerTest { TestCode = source }; test.ExpectedDiagnostics.Add( new DiagnosticResult(LargeAllocationAnalyzer.DiagnosticId, DiagnosticSeverity.Warning) .WithLocation(0) .WithArguments("GroupBy")); await test.RunAsync(); } [Fact] public async Task LargeArrayAllocation_ProducesDiagnostic() { const string source = @" class TestClass { void TestMethod() { var largeArray = {|#0:new int[100000]|}; } }"; var test = new CSharpAnalyzerTest { TestCode = source }; test.ExpectedDiagnostics.Add( new DiagnosticResult(LargeAllocationAnalyzer.DiagnosticId, DiagnosticSeverity.Warning) .WithLocation(0) .WithArguments("array allocation")); await test.RunAsync(); } [Fact] public async Task LargeListAllocation_ProducesDiagnostic() { const string source = @" using System.Collections.Generic; class TestClass { void TestMethod() { var largeList = {|#0:new List(100000)|}; } }"; var test = new CSharpAnalyzerTest { TestCode = source }; test.ExpectedDiagnostics.Add( new DiagnosticResult(LargeAllocationAnalyzer.DiagnosticId, DiagnosticSeverity.Warning) .WithLocation(0) .WithArguments("list allocation")); await test.RunAsync(); } [Fact] public async Task SmallAllocation_NoDiagnostic() { const string source = @" using System.Collections.Generic; using System.Linq; class TestClass { void TestMethod() { var smallArray = new int[100]; var smallList = new List(100); var items = new[] { 1, 2, 3, 4, 5 }; var sorted = items.OrderBy(x => x).ToList(); } }"; var test = new CSharpAnalyzerTest { TestCode = source }; await test.RunAsync(); } [Fact] public async Task ToList_OnSmallCollection_NoDiagnostic() { const string source = @" using System.Linq; class TestClass { void TestMethod() { var items = new[] { 1, 2, 3, 4, 5 }; var list = items.ToList(); } }"; var test = new CSharpAnalyzerTest { TestCode = source }; await test.RunAsync(); } [Fact] public async Task NestedLinqOperations_ProducesMultipleDiagnostics() { const string source = @" using System; using System.Linq; class TestClass { void TestMethod() { var context = new BigDataContext(); var result = context.Transactions .{|#0:OrderBy|}(t => t.Date) .{|#1:GroupBy|}(t => t.Category) .{|#2:ToList|}(); } } class BigDataContext { public IQueryable Transactions { get; set; } } class Transaction { public DateTime Date { get; set; } public string Category { get; set; } }"; var test = new CSharpAnalyzerTest { TestCode = source }; test.ExpectedDiagnostics.Add( new DiagnosticResult(LargeAllocationAnalyzer.DiagnosticId, DiagnosticSeverity.Warning) .WithLocation(0) .WithArguments("OrderBy")); test.ExpectedDiagnostics.Add( new DiagnosticResult(LargeAllocationAnalyzer.DiagnosticId, DiagnosticSeverity.Warning) .WithLocation(1) .WithArguments("GroupBy")); test.ExpectedDiagnostics.Add( new DiagnosticResult(LargeAllocationAnalyzer.DiagnosticId, DiagnosticSeverity.Warning) .WithLocation(2) .WithArguments("collection")); await test.RunAsync(); } [Fact] public async Task DynamicSizeAllocation_ProducesDiagnostic() { const string source = @" class TestClass { void TestMethod(int size) { // Dynamic size - analyzer assumes it could be large var array = {|#0:new byte[size]|}; } }"; var test = new CSharpAnalyzerTest { TestCode = source }; test.ExpectedDiagnostics.Add( new DiagnosticResult(LargeAllocationAnalyzer.DiagnosticId, DiagnosticSeverity.Warning) .WithLocation(0) .WithArguments("array allocation")); await test.RunAsync(); } }