pact-foundation / pact-net

.NET version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
https://pact.io
MIT License
823 stars 225 forks source link

NativeInterop.CleanupMockServer() takes more than 1 minute #470

Closed sbekeris closed 8 months ago

sbekeris commented 11 months ago

The test that normally runs in 4 seconds sometimes takes 1.3 min. After debugging pact-net I see that a call to NativeInterop.CleanupMockServer() sometimes takes so long. Any ideas how to fix it?

Sample code: https://github.com/sbekeris/slow-tests

The sample contains multiple identical tests, and some of them take 1.3 min. You may need to run it multiple times, as it is happening not every time.

image

Tests source code:

[Fact]
public async Task Test01() => await Test();

private static async Task Test()
{
    var api1PactBuilder = CreatePactBuilder("API-1", 9091);
    AddGetRequest(api1PactBuilder, "/api-1-endpoint-1");
    AddGetRequest(api1PactBuilder, "/api-1-endpoint-2");

    var api2PactBuilder = CreatePactBuilder("API-2", 9092);
    AddGetRequest(api2PactBuilder, "/api-2-endpoint-1");

    await api1PactBuilder.VerifyAsync(async _ =>
    {
        await api2PactBuilder.VerifyAsync(async _ =>
        {
            var request = new HttpRequestMessage(HttpMethod.Get, new Uri("/slow-tests", UriKind.RelativeOrAbsolute));
            var httpClient = new WebApplicationFactory<Program>().CreateClient();
            await httpClient.SendAsync(request);
        });
    });
}

private static void AddGetRequest(IPactBuilderV3 pactBuilder, string url)
{
    pactBuilder
        .UponReceiving("Test")
        .Given("Test")
            .WithRequest(HttpMethod.Get, url)
        .WillRespond();
}

private static IPactBuilderV3 CreatePactBuilder(string provider, int port)
{
    var pact = Pact.V3("Test", provider);

    return pact.WithHttpInteractions(port);
}

API source code:

public class SlowTestsController : ControllerBase
{
    [HttpGet()]
    public async Task<IActionResult> Get()
    {
        var httpClient = new HttpClient();

        var api1Endpoint2Task = HttpRequest(httpClient, "http://localhost:9091/api-1-endpoint-1");
        var api1Endpoint3Task = HttpRequest(httpClient, "http://localhost:9091/api-1-endpoint-2");
        await Task.WhenAll(api1Endpoint2Task, api1Endpoint3Task);

        await HttpRequest(httpClient, "http://localhost:9092/api-2-endpoint-1");

        return Ok();
    }

    public async Task HttpRequest(HttpClient httpClient, string url)
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(url));
        await httpClient.SendAsync(request);
    }
}
adamrodger commented 8 months ago

There are a number of things wrong with your sample project:

  1. The test doesn't actually verify anything. It ends with WillRespond() but then never actually configures any expectations on the response. There happens to be a default response defined in case you do that (HTTP 200 with an empty body), but that's not really expected usage.
  2. You are using Microsoft.AspNetCore.Mvc.Testing which is not supported, as explained in the PactNet README
  3. You are testing two different providers in the same single test and interleaving their tests. This is not supported - you are supposed to test a single provider at a time.
  4. You've not enabled any logging

I've never seen this reproduced when following the expected behaviour of verifying a single provider per test using a standard HttpClient (instead of the WebApplicationFactory version) and so I'm closing this for now. If you can create a sample project which does that and still reproduces the problem then please feel free to reopen the issue and attach the logs from the test run that takes the longest.

@mefellows There doesn't appear to be any logging when disposing mock servers so if this issue can be reproduced then we'd not really have a way of tracking down where the FFI is taking so long. It might be worth adding some trace level logging around that area.