Skip to content

Prova: Technical Summary (Deep Dive)

Target Audience: Architects, Framework Authors, Performance Engineers.

Project Scope: Prova is a standalone reference implementation and an experimental research project. It is intended to showcase the potential of Roslyn-based testing architectures. It is not associated with Microsoft Corporation but provides a Hybrid MTP Adapter for compatibility with the modern .NET testing ecosystem.

Core Philosophy: "Compile-Time is the new Runtime"

Prova is a fundamental rethink of .NET testing infrastructure, moving the heavy lifting of test discovery and orchestration from Runtime Reflection to Roslyn Source Generators.

1. The Source Generator Architecture

Instead of Assembly.GetTypes().SelectMany(t => t.GetMethods())... at runtime, Prova employs an IIncrementalGenerator pipeline:

csharp
// High-Level Pipeline
var testMethods = context.SyntaxProvider
    .CreateSyntaxProvider(
        predicate: (node, _) => IsCandidate(node),
        transform: (ctx, _) => ModelFactory.Create(ctx) // Expensive semantic analysis
    )
    .Where(model => model != null);

context.RegisterSourceOutput(testMethods, (spc, methods) => 
{
    // Generates a Single Static Entry Point
    SourceEmitter.Emit(spc, methods);
});

Key Advantages:

  • Zero-Cost Abstractions: Attributes like [Fact] and [Trait] are peeled away at compile time. The resulting code is just raw Task invocation.
  • Tree Shaking / Trimming: Since test calls are static references (MethodA()), the IL linker (ILLink) knows exactly what is used. Unused code is aggressively trimmed.
  • Diagnostics: Invalid test signatures (e.g., void instead of Task for async) are caught as Compiler Errors (or Analyzer warnings), not runtime failures.

2. Native AOT Compatibility

Traditional frameworks (xUnit, NUnit, MSTest) rely on:

  • System.Reflection.Emit (Dynamic Proxies)
  • RuntimeHelpers.RunClassConstructor
  • Extensive use of MakeGenericType

This makes AOT challenging (requiring massive rd.xml directives).

Prova is AOT-Native by default.

  • No dynamic code generation.
  • No Type.MakeGenericType at runtime (handled by generating generic instantiations at compile time).
  • No Reflection scanning.

3. Concurrency Model: Task.WhenAll

Prova treats the test suite as a massive async graph.

Generated Code Structure:

csharp
public static async Task RunAllAsync()
{
    var tasks = new List<Task>();
    // Bounded Schedular implemented via SemaphoreSlim(maxParallel)
    using var semaphore = new SemaphoreSlim(maxParallel);

    foreach(var test in tests)
    {
        await semaphore.WaitAsync();
        tasks.Add(Task.Run(async () => {
             try {
                 // Output is bubbled up from the test logic
                 string? output = await test.ExecuteDelegate();
                 reporter.OnSuccess(test, output);
             } finally {
                 semaphore.Release();
             }
        }));
    }

    await Task.WhenAll(tasks);
}

This maximizes thread pool utilization while preventing starvation via the [Parallel(max: n)] setting.

4. Zero-Allocation Testing Goals

We minimize heap allocations in the runner loop:

  • Reuse: The ConsoleReporter uses Interlocked counters rather than locking heavy objects.
  • Structs: Internal models used during execution are kept lean.
  • No Boxing: Assert methods use generic constraints (T) to avoid boxing value types where possible.

5. Compiler-Integrated Features

Because we control the compilation:

  • [Focus]: We filter the test execution set at the entry point. When [Focus] is active, only the targeted tests are added to the execution graph, ensuring zero distractions and immediate feedback.
  • Documentation: We read the SyntaxTree XML trivia (/// <summary>) at compile-time and bake it directly into the runner. No XML parsing or reflection occurs at runtime.

6. Concurrency Boundedness

Prova implements a Bounded Scheduler via SemaphoreSlim in both the Console Runner and the MTP Adapter.

  • Default: Environment.ProcessorCount.
  • Override: Use [Parallel(max: n)] at the class level to specify a custom limit. Prova takes the minimum max found across all tests to ensure the most restrictive environment is honored.

7. Output Capture

Capturing stdout in parallel/async tests is difficult. Console.SetOut is not thread-safe. Prova solves this by injecting a per-test ITestOutputHelper.

  • The generator creates a new TestOutputHelper instance for each test.
  • The ExecuteDelegate signature is modified from Func<Task> to Func<Task<string?>>.
  • Logs are strictly isolated and returned to the runner after execution, ensuring no cross-talk between thread logs.

8. AOT Dependency Injection (Factory Pattern)

To avoid runtime reflection (scanning constructors), Prova uses an Explicit Factory Pattern:

  • Setup: Users mark static factory methods with [TestDependency].
  • Index: The analyzer builds a ReturnType -> MethodCall map at compile time.
  • Wiring: When generating test instantiation (new MyTest(dep)), the emitter resolves dependencies from this map directly in source code.
  • Result: Zero DI container overhead. Zero runtime resolution. 100% Native AOT safe.

9. Governance: Prophetic Allocation Monitoring

To enforce [MaxAlloc(n)], Prova does not use a profiler API or complex instrumentation.

  • Mechanism: The generator simply wraps the test call in a try/finally block.
  • Measurement: GC.GetAllocatedBytesForCurrentThread() is called before and after the test delegate.
  • Cost: If [MaxAlloc] is missing, 0 instructions are emitted. The overhead is strictly pay-for-play.
  • Safety: Because this happens inside the Task.Run delegate, it correctly attributes allocations to the specific test thread (even in parallel execution).

Summary for Infrastructure Teams

Prova is designed to be embedded in high-performance CI/CD pipelines where:

  • Startup time matters (Micro-benchmarking, rapid inner loop).
  • Binary size matters (WASM, Mobile, IoT scenarios).
  • Stability matters (Compiler-verified test definitions).

It is not just a test framework; it is a Static Analysis Tool that happens to run tests.

Released under the MIT License.