Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions JustDummies.Analyzers.UnitTests/AnalyzerTestHarness.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ private static ImmutableArray<MetadataReference> BuildReferences() {
// The JustDummies core, so Any.Reproducibly / Any.ReproduciblyAsync resolve inside the snippet.
references.Add(MetadataReference.CreateFromFile(typeof(Any).Assembly.Location));

// The xUnit adapter and xUnit itself, so [Reproducible], [Fact], [Theory] and TheoryData resolve in the
// snippets the lifecycle rules are tested against.
references.Add(MetadataReference.CreateFromFile(typeof(JustDummies.Xunit.ReproducibleAttribute).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(FactAttribute).Assembly.Location));

return references.ToImmutableArray();
}

Expand Down
184 changes: 184 additions & 0 deletions JustDummies.Analyzers.UnitTests/Jd007DrawOutsideThePinnedScopeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
using System.Collections.Immutable;

using Microsoft.CodeAnalysis;

using NFluent;

namespace JustDummies.Analyzers.UnitTests;

public class Jd007DrawOutsideThePinnedScopeTests {

[Fact]
public async Task Reports_a_draw_in_the_constructor_of_a_reproducible_class() {
const string source = """
using JustDummies;
using JustDummies.Xunit;
using Xunit;

[Reproducible]
public class Sample {
private readonly string _reference;

public Sample() {
_reference = Any.String().NonEmpty().Generate();
}

[Fact]
public void T() { }
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new DrawOutsideThePinnedScopeAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(1);
Check.That(diagnostics[0].Id).IsEqualTo("JD007");
Check.That(diagnostics[0].GetMessage()).Contains("constructor");
}

[Fact]
public async Task Reports_a_draw_in_a_field_initializer() {
const string source = """
using JustDummies;
using JustDummies.Xunit;
using Xunit;

[Reproducible]
public class Sample {
private readonly string _reference = Any.String().NonEmpty().Generate();

[Fact]
public void T() { }
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new DrawOutsideThePinnedScopeAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(1);
Check.That(diagnostics[0].Id).IsEqualTo("JD007");
Check.That(diagnostics[0].GetMessage()).Contains("field initializer");
}

[Fact]
public async Task Reports_a_draw_in_InitializeAsync() {
const string source = """
using System.Threading.Tasks;
using JustDummies;
using JustDummies.Xunit;
using Xunit;

[Reproducible]
public class Sample : IAsyncLifetime {
private string _reference = "";

public ValueTask InitializeAsync() {
_reference = Any.String().NonEmpty().Generate();

return default;
}

public ValueTask DisposeAsync() => default;

[Fact]
public void T() { }
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new DrawOutsideThePinnedScopeAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(1);
Check.That(diagnostics[0].Id).IsEqualTo("JD007");
Check.That(diagnostics[0].GetMessage()).Contains("InitializeAsync");
}

[Fact]
public async Task Does_not_report_a_draw_in_the_test_body() {
// The body IS inside the pinned scope — verified against xunit.v3 by probe before this rule was written.
const string source = """
using JustDummies;
using JustDummies.Xunit;
using Xunit;

[Reproducible]
public class Sample {
[Fact]
public void T() {
string reference = Any.String().NonEmpty().Generate();
}
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new DrawOutsideThePinnedScopeAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(0);
}

[Fact]
public async Task Does_not_report_a_class_without_the_attribute() {
const string source = """
using JustDummies;
using Xunit;

public class Sample {
private readonly string _reference = Any.String().NonEmpty().Generate();

[Fact]
public void T() { }
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new DrawOutsideThePinnedScopeAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(0);
}

[Fact]
public async Task Does_not_report_a_draw_from_an_isolated_context() {
// Any.WithSeed is isolated by design: the ambient scope does not govern it, so the diagnostic would be wrong.
const string source = """
using JustDummies;
using JustDummies.Xunit;
using Xunit;

[Reproducible]
public class Sample {
private readonly string _reference;

public Sample() {
AnyContext context = Any.WithSeed(1234);
_reference = context.String().NonEmpty().Generate();
}

[Fact]
public void T() { }
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new DrawOutsideThePinnedScopeAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(0);
}

[Fact]
public async Task Reports_under_an_assembly_level_attribute() {
const string source = """
using JustDummies;
using JustDummies.Xunit;
using Xunit;

[assembly: Reproducible]

public class Sample {
private readonly string _reference = Any.String().NonEmpty().Generate();

[Fact]
public void T() { }
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new DrawOutsideThePinnedScopeAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(1);
Check.That(diagnostics[0].Id).IsEqualTo("JD007");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System.Collections.Immutable;

using Microsoft.CodeAnalysis;

using NFluent;

namespace JustDummies.Analyzers.UnitTests;

public class Jd008ArbitraryValueInTheoryDataTests {

[Fact]
public async Task Reports_a_draw_in_a_TheoryData_property() {
const string source = """
using JustDummies;
using Xunit;

public class Sample {
public static TheoryData<string> Cases => new() { Any.String().NonEmpty().Generate() };

[Theory]
[MemberData(nameof(Cases))]
public void T(string reference) { }
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new ArbitraryValueInTheoryDataAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(1);
Check.That(diagnostics[0].Id).IsEqualTo("JD008");
Check.That(diagnostics[0].GetMessage()).Contains("discovery");
}

[Fact]
public async Task Reports_a_draw_in_a_member_named_by_MemberData() {
const string source = """
using System.Collections.Generic;
using JustDummies;
using Xunit;

public class Sample {
public static IEnumerable<object[]> Cases() {
yield return new object[] { Any.Int32().Positive().Generate() };
}

[Theory]
[MemberData(nameof(Cases))]
public void T(int quantity) { }
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new ArbitraryValueInTheoryDataAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(1);
Check.That(diagnostics[0].Id).IsEqualTo("JD008");
}

[Fact]
public async Task Reports_a_draw_in_a_ClassData_provider() {
const string source = """
using System.Collections;
using System.Collections.Generic;
using JustDummies;
using Xunit;

public class Cases : IEnumerable<object[]> {
public IEnumerator<object[]> GetEnumerator() {
yield return new object[] { Any.Int32().Positive().Generate() };
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Sample {
[Theory]
[ClassData(typeof(Cases))]
public void T(int quantity) { }
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new ArbitraryValueInTheoryDataAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(1);
Check.That(diagnostics[0].Id).IsEqualTo("JD008");
}

[Fact]
public async Task Does_not_report_a_provider_that_yields_the_generator() {
// The compliant shape: the provider hands over the recipe, the body draws inside the pinned scope.
const string source = """
using JustDummies;
using Xunit;

public class Sample {
public static TheoryData<IAny<string>> Cases => new() { Any.String().NonEmpty() };

[Theory]
[MemberData(nameof(Cases))]
public void T(IAny<string> reference) {
string value = reference.Generate();
}
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new ArbitraryValueInTheoryDataAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(0);
}

[Fact]
public async Task Does_not_report_a_draw_in_an_ordinary_test_body() {
const string source = """
using JustDummies;
using Xunit;

public class Sample {
[Fact]
public void T() {
string reference = Any.String().NonEmpty().Generate();
}
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new ArbitraryValueInTheoryDataAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(0);
}

[Fact]
public async Task Does_not_report_an_ordinary_collection_of_object_arrays() {
// A member returning object[] sequences is only a provider when xUnit is told so; without a [MemberData]
// naming it and without the TheoryData shape, the rule must stay quiet here — this one IS named, so the
// sibling test covers the positive. Here nothing references it.
const string source = """
using System.Collections.Generic;
using JustDummies;

public class Sample {
public static List<string> Values() {
return new List<string> { Any.String().NonEmpty().Generate() };
}
}
""";

ImmutableArray<Diagnostic> diagnostics = await AnalyzerTestHarness.GetDiagnosticsAsync(new ArbitraryValueInTheoryDataAnalyzer(), source);

Check.That(diagnostics.Length).IsEqualTo(0);
}

}
Loading
Loading