pact-net icon indicating copy to clipboard operation
pact-net copied to clipboard

PACT tests do not run under Mono on Linux

Open drew-cooper opened this issue 1 year ago • 4 comments

We have a multi-targeted test project (net472 and netcoreapp3.1) containing PACT Consumer tests. The tests fail to run for the net472 on our Ubuntu-based build image. The image has dotnet SDK 3.1 and 6.0 and Mono 6.6.0 installed.

Minimal viable repro:

PactMultitargetTest.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>net472;netcoreapp3.1</TargetFrameworks>
    <LangVersion>10.0</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
    <PackageReference Include="NUnit" Version="3.13.3" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
    <PackageReference Include="PactNet" Version="4.5.0" />
  </ItemGroup>

</Project>

SimpleTest.cs:

using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using NUnit.Framework;
using PactNet;

namespace PactMultitargetTest;

public class SimpleTest
{
    [Test]
    public async Task TestConsumerPactExecution()
    {
        var pact = Pact.V3("Consumer", "Provider").WithHttpInteractions();

        pact.UponReceiving("A request").WithRequest(HttpMethod.Get, "/")
            .WillRespond().WithStatus(HttpStatusCode.OK);

        await pact.VerifyAsync(async ctx =>
            {
                using var http = new HttpClient();
                await http.GetAsync(ctx.MockServerUri);
            });
    }
}

This runs as expected on Windows:

> dotnet test
<snip>
Test run for C:\my-repo-dir\PactMultitargetTest\bin\Debug\net472\PactMultitargetTest.dll (.NETFramework,Version=v4.7.2)
Microsoft (R) Test Execution Command Line Tool Version 17.4.0 (x64)
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.

Passed!  - Failed:     0, Passed:     1, Skipped:     0, Total:     1, Duration: 696 ms - PactMultitargetTest.dll (net472)
Test run for C:\my-repo-dir\PactMultitargetTest\bin\Debug\netcoreapp3.1\PactMultitargetTest.dll (.NETCoreApp,Version=v3.1)
Microsoft (R) Test Execution Command Line Tool Version 17.4.0 (x64)
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.

Passed!  - Failed:     0, Passed:     1, Skipped:     0, Total:     1, Duration: 100 ms - PactMultitargetTest.dll (netcoreapp3.1)

On Ubuntu I get this:

$ dotnet test
  Determining projects to restore...
  All projects are up-to-date for restore.
  PactMultitargetTest -> /mnt/c/Users/andrew.cooper/repos/test/PactMultitargetTest/bin/Debug/net472/PactMultitargetTest.dll
  PactMultitargetTest -> /mnt/c/Users/andrew.cooper/repos/test/PactMultitargetTest/bin/Debug/netcoreapp3.1/PactMultitargetTest.dll
  PactMultitargetTest -> /mnt/c/Users/andrew.cooper/repos/test/PactMultitargetTest/bin/Debug/net6.0/PactMultitargetTest.dll
Test run for /mnt/c/Users/andrew.cooper/repos/test/PactMultitargetTest/bin/Debug/net472/PactMultitargetTest.dll (.NETFramework,Version=v4.7.2)
Microsoft (R) Test Execution Command Line Tool Version 17.5.0 (x64)
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
  Failed TestConsumerPactExecution [126 ms]
  Error Message:
   System.DllNotFoundException : pact_ffi assembly:<unknown assembly> type:<unknown type> member:(null)
  Stack Trace:
    at (wrapper managed-to-native) PactNet.Interop.NativeInterop.LogToBuffer(PactNet.Interop.LevelFilter)
  at PactNet.PactExtensions.InitialiseLogging (PactNet.PactLogLevel level) [0x00067] in <c3835e33e6c34624a3900fd28f0ae514>:0
  at PactNet.PactExtensions.WithHttpInteractions (PactNet.IPactV3 pact, System.Nullable`1[T] port, PactNet.Models.IPAddress host) [0x0000b] in <c3835e33e6c34624a3900fd28f0ae514>:0
  at PactMultitargetTest.SimpleTest.TestConsumerPactExecution () [0x00022] in <25f0af484314489ebafdc4e264ba94be>:0
  at NUnit.Framework.Internal.TaskAwaitAdapter+GenericAdapter`1[T].GetResult () [0x00008] in <c804c596ec8e46f396062449e02bacdd>:0
  at NUnit.Framework.Internal.AsyncToSyncAdapter.Await (System.Func`1[TResult] invoke) [0x0003d] in <c804c596ec8e46f396062449e02bacdd>:0
  at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod (NUnit.Framework.Internal.TestExecutionContext context) [0x00031] in <c804c596ec8e46f396062449e02bacdd>:0
  at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute (NUnit.Framework.Internal.TestExecutionContext context) [0x00001] in <c804c596ec8e46f396062449e02bacdd>:0
  at NUnit.Framework.Internal.Execution.SimpleWorkItem+<>c__DisplayClass4_0.<PerformWork>b__0 () [0x00011] in <c804c596ec8e46f396062449e02bacdd>:0

  at NUnit.Framework.Internal.ContextUtils+<>c__DisplayClass1_0`1[T].<DoIsolated>b__0 (System.Object _) [0x00000] in <c804c596ec8e46f396062449e02bacdd>:0
  at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in <de882a77e7c14f8ba5d298093dde82b2>:0
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <de882a77e7c14f8ba5d298093dde82b2>:0
  at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state) [0x0002b] in <de882a77e7c14f8ba5d298093dde82b2>:0
  at NUnit.Framework.Internal.ContextUtils.DoIsolated (System.Threading.ContextCallback callback, System.Object state) [0x00025] in <c804c596ec8e46f396062449e02bacdd>:0
  at NUnit.Framework.Internal.ContextUtils.DoIsolated[T] (System.Func`1[TResult] func) [0x0001a] in <c804c596ec8e46f396062449e02bacdd>:0
  at NUnit.Framework.Internal.Execution.SimpleWorkItem.PerformWork () [0x0001b] in <c804c596ec8e46f396062449e02bacdd>:0

Failed!  - Failed:     1, Passed:     0, Skipped:     0, Total:     1, Duration: 126 ms - PactMultitargetTest.dll (net472)
Test run for /mnt/c/Users/andrew.cooper/repos/test/PactMultitargetTest/bin/Debug/netcoreapp3.1/PactMultitargetTest.dll (.NETCoreApp,Version=v3.1)
Microsoft (R) Test Execution Command Line Tool Version 17.5.0 (x64)
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.

Passed!  - Failed:     0, Passed:     1, Skipped:     0, Total:     1, Duration: 428 ms - PactMultitargetTest.dll (netcoreapp3.1)

drew-cooper avatar Apr 17 '23 10:04 drew-cooper

Looking at the build artifacts on Ubuntu I see this:

$ ls bin/Debug/net472
Microsoft.VisualStudio.CodeCoverage.Shim.dll  Newtonsoft.Json.dll      PactNet.Abstractions.dll  nunit.engine.core.dll  pact_ffi.dll
NUnit3.TestAdapter.dll                        PactMultitargetTest.dll  PactNet.dll               nunit.engine.dll
NUnit3.TestAdapter.pdb                        PactMultitargetTest.pdb  nunit.engine.api.dll      nunit.framework.dll

Note the pact_ffi.dll which is the Windows native component.

The culprit appears to be this build/net461/PactNet.targets file in the Nuget package:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- This file supports .Net Framework, which doesn't automatically unpack the runtimes folder -->
  <ItemGroup>
    <Content Include="$(MSBuildThisFileDirectory)..\..\runtimes\win-x64\native\pact_ffi.dll">
      <Link>pact_ffi.dll</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <Visible>false</Visible>
    </Content>
  </ItemGroup>
</Project>

So, when building net461 or higher (in our case net472) we unconditionally copy in the Windows native component.

Manually copying in the Linux component fixes the test issue:

$ cp ~/.nuget/packages/pactnet/4.5.0/runtimes/linux-x64/native/libpact_ff
i.so bin/Debug/net472
$ dotnet test --no-build
Test run for /mnt/c/my-repo-dir/PactMultitargetTest/bin/Debug/net472/PactMultitargetTest.dll (.NETFramework,Version=v4.7.2)
Microsoft (R) Test Execution Command Line Tool Version 17.5.0 (x64)
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.

Passed!  - Failed:     0, Passed:     1, Skipped:     0, Total:     1, Duration: 595 ms - PactMultitargetTest.dll (net472)
Test run for /mnt/c/my-repo-dir/PactMultitargetTest/bin/Debug/netcoreapp3.1/PactMultitargetTest.dll (.NETCoreApp,Version=v3.1)
Microsoft (R) Test Execution Command Line Tool Version 17.5.0 (x64)
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.

Passed!  - Failed:     0, Passed:     1, Skipped:     0, Total:     1, Duration: 507 ms - PactMultitargetTest.dll (netcoreapp3.1)

drew-cooper avatar Apr 17 '23 11:04 drew-cooper

I would be happy to provide a PR to add similar platform detection as in PactNet.csproj to the PactNet.targets file to copy the appropriate binary for the system on which the tests are being built.

drew-cooper avatar Apr 17 '23 11:04 drew-cooper

Currently .Net Framework is only supported on Windows, hence the unconditional copy in the targets file that you've found.

I've marked the issue as a feature request if Mono support is needed, but unsure on the relative priority of that given .Net Core has full Linux support and is the recommended framework going forwards.

adamrodger avatar Apr 17 '23 13:04 adamrodger

Thanks for the response. I'll have a workaround available to me soon so probably not that critical.

drew-cooper avatar Apr 19 '23 05:04 drew-cooper