Skip to content

Empty class MethodDataSource fails construction in generated mode but supplies null in reflection mode #7442

Open

Description

@thomhurst

Summary

An empty class-level [MethodDataSource] produces a failed test in source-generated mode but a passing test in reflection mode when the class constructor requires a string. Generated construction indexes the empty argument array; reflection supplies null for the missing required argument. The two discovery modes should apply the same missing-argument rule.

Reproduction

using TUnit.Core;

[MethodDataSource(nameof(Rows))]
public class EmptyConstructor(string value)
{
    public static System.Collections.Generic.IEnumerable<string> Rows() => [];

    [Test]
    public void Empty() { }
}

Run the application with --treenode-filter "/*/*/EmptyConstructor/*", then repeat with --reflection.

Expected and actual

Both modes retain the empty-source fallback test and report the missing required constructor argument consistently. Currently generated mode fails with IndexOutOfRangeException in its generated instance factory, while reflection mode passes after supplying null. This also affects method-inferred generic class fallback rows; a constructor that rejects null fails in both modes, with different errors.

Environment

Found while validating #7422. Please make missing required constructor arguments behave consistently in both execution modes.

Acceptance criteria

  • Retain the empty-source fallback test and report a missing required constructor argument as a failure in both source-generated and reflection modes, rather than silently supplying null or exposing an array-index error.
  • Add a regression for the non-generic reproduction above in both modes, asserting the fallback test count and missing-argument failure. Cover the method-inferred generic fallback case described above as well.
  • Preserve valid supplied constructor arguments, including explicit null values where supported; this change concerns missing required arguments.
  • Run affected generator snapshot tests and review intentional changes. Add engine integration coverage for CI; do not run TUnit.Engine.Tests locally. Verify Native AOT publishing if changing reflection paths.

The non-generic fix is independently actionable; #7422 is related context, not a prerequisite.

Activity

  1. thomhurst commented on Nov 3, 2026

    @thomhurst
    OwnerAuthor

    Triaged against origin/main at e8764cd. The mismatch remains: InstanceFactoryGenerator.GenerateTypedConstructorCallBody indexes each required argument directly, while ReflectionTestDataCollector.CreateReflectionInstanceFactory allocates a parameter-sized array and leaves missing arguments null. #7422 explicitly tracks this as separate work; the non-generic reproduction needs no prerequisite. Added acceptance criteria for consistent failure reporting and both-mode regressions. No labels or dependency links need changing.

  2. added
    bugSomething isn't working
    P2Medium: edge case, mode divergence, flaky test, or workaround exists
    on Nov 3, 2026
  3. thomhurst commented on Nov 3, 2026

    @thomhurst
    OwnerAuthor

    The same missing-argument divergence affects method-level deferred sources, independently of constructors. Confirmed against engine code at 67870edd74 while validating #7409:

    public class EmptyDeferred
    {
        public static IEnumerable<int> Rows() => [];
    
        [Test]
        [MethodDataSource(nameof(Rows), DeferEnumeration = true)]
        public void Deferred(int value) { }
    }

    Run --treenode-filter '/*/*/EmptyDeferred/Deferred', then repeat with --reflection. Generated mode fails the fallback child with ArgumentException: Expected exactly 1 argument, but got 0 and its container fails too (2 failures). Reflection mode passes both nodes, supplying the missing value argument. SDK 10.0.403, net10.0, macOS arm64. Both modes should report the missing required method argument consistently. Please consider this method-level counterpart alongside the constructor regression.

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