Skip to content

Source-gen: generic test class does not infer its type arguments from a class-level [MethodDataSource]; reflection does #7333

Open
1 of 2 issues completed

Description

@thomhurst

What happens

A generic test class whose only type information is a class-level [MethodDataSource] runs in reflection mode, but source-gen mode emits no test for it. Nothing is reported: the test is missing from the run.

public static class Sources
{
    public static IEnumerable<int> Ints() => [3];
}

[MethodDataSource(typeof(Sources), nameof(Sources.Ints))]
public class ExternalNoMethodRows<T>(T a)
{
    [Test]
    public void Test() { _ = a; }
}

[MethodDataSource(nameof(Ints))]
public class OwnNoMethodRows<T>(T a)
{
    public static IEnumerable<int> Ints() => [3];

    [Test]
    public void Test() { _ = a; }
}

Added to tests/TUnit.TestProject and run with --treenode-filter "/*/<namespace>/<class>/*":

Class source-gen --reflection
ExternalNoMethodRows 0 tests 1 passed (T = int)
OwnNoMethodRows 0 tests 1 passed (T = int, after the fix for #7208)

A class-level [MethodDataSource] whose declared rows name the class's own type parameter (IEnumerable<T> Values()), with nothing else that supplies T, also emits nothing in source-gen. Reflection reports a failed test that explains the problem.

Where

TestMetadataGenerator infers a generic class's type arguments from class-level [Arguments] rows and typed class data sources (InferTypesFromTypedDataSourceForClass), but not from a class-level [MethodDataSource]. Reflection (ReflectionTestDataCollector.GroupClassRowsByTypeArgumentsAsync) reads the source's rows and groups them by the type of their values.

Expected

Both modes behave the same. For example, source-gen infers the class type arguments from the declared row type of the class-level [MethodDataSource] member, as it already does for method-level [MethodDataSource] members. If it cannot infer them, it reports a diagnostic or a failed test instead of silently emitting nothing.

Found while working on #7208.

Activity

  1. added
    P2Medium: edge case, mode divergence, flaky test, or workaround exists
    on Nov 1, 2026
  2. thomhurst commented on Nov 1, 2026

    @thomhurst
    OwnerAuthor

    Another case of this gap, found while fixing #7336. A [GenerateGenericTest] instantiation that class [Arguments] rows also infer, with method rows that infer the class type:

    [GenerateGenericTest(typeof(int))]
    [Arguments(1)]
    [MethodDataSource(nameof(Values))]
    public class C<T>(T value)
    {
        public static IEnumerable<T> Values() => [(T)(object)5];
    
        [Test]
        [Arguments(2)]
        public void Test(T arg) { }
    }

    Source-gen runs one test, with value = 1. The Values() row (5) never runs. Reflection runs two, with values 1 and 5, after #7336. The repro is tests/TUnit.TestProject/Bugs/_7208/OwnClassTypeParameterMethodDataSourceWithArgumentsAndGenerateGenericTest. Issue7208Tests asserts each mode's count, so a fix here must update that test's source-gen expectation to 2.

  3. thomhurst commented on Nov 1, 2026

    @thomhurst
    OwnerAuthor

    Update: #7336 (bc1b447) fixes the [GenerateGenericTest] + class [Arguments] + method rows case I added to this issue earlier. Source-gen now reads the own-class member on that instantiation, as reflection does. What remains here is what the body describes: source-gen doesn't infer a generic class's type arguments from a class-level [MethodDataSource], so a class whose types only that source supplies emits no test.

  4. thomhurst commented on Nov 1, 2026

    @thomhurst
    OwnerAuthor

    A related case came up while fixing a Codex finding on #7336. When class [Arguments] rows already supply the types, source-gen still drops the rows of a class-level [MethodDataSource]:

    [Arguments(1)]
    [MethodDataSource(nameof(Rows))]
    public class C<T>(T value)
    {
        public static IEnumerable<int> Rows() => [5];
    
        [Test]
        public void Test() { }
    }

    Reflection groups both rows into C<int> and runs 2 tests. In source-gen, a concrete own-class member joins the C<int> instantiation only when a class-level [GenerateGenericTest(typeof(int))] also names it, through GetOwnClassMethodDataSourcesForInstantiation in TestMetadataGenerator (#7336). Without that attribute, only the [Arguments] row runs, according to the generator code; I haven't run this exact class. Fixing #7333 should cover this case as well.

  5. 7 remaining items

  6. thomhurst commented on Nov 3, 2026

    @thomhurst
    OwnerAuthor

    Validation while implementing #7443 on main at 07a554f: the existing _7208 fixtures run 43 tests in both modes. Source-generated discovery has 39 passes and four type-inference failures; reflection has 42 passes and one intentional discovery failure. The three additional generated-mode failures remain within this issue’s class-level MethodDataSource inference scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium: edge case, mode divergence, flaky test, or workaround existsbugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions