fluffynuts / NUnit.StaticExpect

Provides a mechanism for a static import of NUnit Expect() syntax (deprecated: see https://github.com/fluffynuts/NExpect for the next generation)
BSD 2-Clause "Simplified" License
3 stars 2 forks source link

NUnit reference should not use private hint path #8

Closed synek317 closed 3 years ago

synek317 commented 3 years ago

Hi, I know the project is deprecated, but I work on a project that has thousands of tests that depend on this library and as far as I know, there is no automated way of migrating them.

I'm migrating from .NET 4.5 to .NET 5 and found out that this library forces me to use NUnit 3.10.1

I think this might be the root cause:

<ItemGroup>
  <Reference Include="nunit.framework">
    <HintPath>..\..\..\..\..\Users\davyd.mccoll\.nuget\packages\nunit\3.10.1\lib\net45\nunit.framework.dll</HintPath>
  </Reference>
</ItemGroup>

Do you think you would be able to remove these lines, check if it compiles on your machine, and then publish a new version?

fluffynuts commented 3 years ago

Hi

I don't mind re-releasing; I think that the problem is more likely that there's a dependency in my package.nuspec which is:

        <dependency id="NUnit" version="[3.10.1,4)"/>

Now, the last time I dealt with this package was quite long ago, according to nuspec docs, meaning "any version of NUnit >= 3.10.1 and < 4"; though I could have been wrong (I drew on https://docs.microsoft.com/en-us/nuget/concepts/package-versioning#version-ranges)

Hint paths within the csproj should only refer to when builds happen at my machine - when you use the package, your machine should be trying to resolve the assembly via other means.

What version of NUnit are you using? How are you trying to install NUnit.StaticExpect? Perhaps we can figure out the correct solution together (:

for reference, if I start up a new nunit test project (defaults to net50) with dotnet new nunit -n consume r and install nunit.staticexpect, I can dotnet test with the following UnitTest1.cs:

using NUnit.Framework;
using static NUnit.StaticExpect.Expectations;

namespace consumer
{
    public class Tests
    {
        [Test]
        public void Test1()
        {
            Expect(1, EqualTo(1));
        }
    }
}

(with the minor caveat that I needed to manually install the latest Microsoft.Net.Test.Sdk package as the one installed with dotnet new nunit fails with cert errors)

I'm not sure if you have modern (sdk-style) csproj files or the older style - if so, I'll have to try repro with an older-style one; example above was done on Linux.

fluffynuts commented 3 years ago

for reference, this is the .csproj that dotnet new created (with a little help from me, as outlined above):

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

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>

    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="microsoft.net.test.sdk" Version="16.8.3" />
    <PackageReference Include="NUnit" Version="3.13.1" />
    <PackageReference Include="nunit.staticexpect" Version="1.0.7" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
  </ItemGroup>

</Project>
synek317 commented 3 years ago

My usecase is a bit more complicated - I'm porting a project, also on Linux, that currently uses mono. Its target is currently net45 but can be easily upgraded to net471. I'm using net461 though since, as far as I understand, it is the first that implements netstandard20.

To run tests on CI, I use nunit3-console. To do the development and run tests in the latest MonoDevelop. My runtime is Mono 6.12

This is the minimal example that causes an error:

<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net461</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="NUnit" Version="3.13.1" />
    <PackageReference Include="NUnit.StaticExpect" Version="1.0.7" />
  </ItemGroup>
</Project>
using NUnit.Framework;
using static NUnit.StaticExpect.Expectations;

namespace NUnitStaticExpectTest
{
    class Tests
    {
        [Test]
        public void Foo()
        {
            Expect(1, Is.EqualTo(2));
        }
    }
}

Error:

System.IO.FileNotFoundException : Could not load file or assembly 'nunit.framework, Version=3.10.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb' or one of its dependencies.

It works well if I change the referenced NUnit version from 3.13 to 3.10.1.

fluffynuts commented 3 years ago

Ok, so what I think is going on here is that when I build with dotnet, I'm getting automatic assembly rebinds happening for me such that the version NUnit.StaticExpect is compiled against (the venerable 3.10.1.0) is bound upward to whatever assembly version is in the package 3.13.1 (my asmdeps util shows that to be at 3.12.0.0). So what I'd suggest is adding an assembly rebind to your app.config for this, something like:

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="nunit.framework" publicKeyToken="B03F5F7F11D50A3A" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.12.0.0" newVersion="3.12.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

(you may already have a runtime or assemblyBinding node, so edit as appropriate)

alternatively edit your .csproj, in the PropertyGroup where you have your TargetFramework, add:

<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>

(source: https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/how-to-enable-and-disable-automatic-binding-redirection)

let me know if this helps (:

synek317 commented 3 years ago

Hi, I've tried it before, both ideas. I've also copy-pasted your code and used it in my dummy project and it still doesn't work, unfortunately.

In the meantime, I've published NUnit.StaticExpect without the and to my local nuget and, surprisingly, it didn't help too.

Now I'm confused. I don't understand why MonoDevelop cannot just use the newer version. When I run tests from the command line, using msbuild and nunit3-console, it works just fine. I'm 100% sure there is no other .NET installed on my computer other than mono 6.12.

fluffynuts commented 3 years ago

is it possible to share the repo with the code so I can take a look? more eyes -> maybe we find something?

fluffynuts commented 3 years ago

In the meantime, I've published NUnit.StaticExpect without the and to my local nuget and, surprisingly, it didn't help too.

it's not that surprising really: the error comes because NUnit.StaticExpect assemblies, when loaded, are looking for nunit.framework at version 3.10.1.0 and you have something else on disk. This is why an assembly-rebind should have worked - but the rebind has to happen at the top-most assembly (from my experience) for it to work, which is probably why it's working from the console, but not from MonoDevelop. If you were to upgrade NUnit.StaticExpect's version of nunit.framework in your local copy and put that nugpkg in your local nuget repo, I bet it would work.

fluffynuts commented 3 years ago

I could dig out a new release just with the latest nunit.framework, if that works for you. This package may be deprecated, but I don't like leaving people in the lurch :D

synek317 commented 3 years ago

Wow, thank you SO MUCH!

Meanwhile, I've created a project you requested: https://github.com/synek317/NUnitStaticExpectTest

There, I put two tests that create a file. One uses a function from NUnit.StaticExpect and another one doesn't. The latter always works, the first one works when I use NUnit 3.10.1 OR when I run tests from using the nunit3-console runner.

Thanks to the files created during the tests, I've noticed that the working directory is different in both cases (bin/Debug/net461 when using MonoDevelop vs ./ when using nunit3-console). In the csproj, you can see quite a lot of properties that I've been testing but none of them worked for me.

And btw., I also think that nuget / msbuild are smart enough to understand semver. Even some of the metadata (e.g. project.assets.json in the obj/) marked the dependency as [3.10.1, 4.0.0). It should not require assembly rebind. My guess is it is something with either MonoDevelop or the code used by MD to execute tests (not sure what it is).

fluffynuts commented 3 years ago

I think that MonoDevelop is bypassing your assembly rebind somehow; remember that nuget package versions don't have to mirror the versions of their contained assemblies, so, yes, Nuget is doing the right thing with respect to semver, but, as you've experienced, that throws MD for a loop. I'll re-release, simply bumping the nunit dependency.

fluffynuts commented 3 years ago

btw, one of the reasons I deprecated was because I wrote my own assertions library, NExpect, which does have different syntax (so I don't expect you to adopt it, but give it a go if you're so inclined - it offers some benefits over NUnit's assertions, like deep equality testing and easy extensibility), but the other was because I was releasing a new version of NUnit.StaticExpect at least once a week, and only because NUnit versions were bumped - no changes to NUnit.StaticExpect; so at that point I thought that the assembly rebind route was good enough and opened up the upper package version for NUnit.StaticExpect.

fluffynuts commented 3 years ago

yeah, also looks like NUnit dropped support for netstandard1.4, so I have to bump that to netstandard2.0, which you can consume from net461 :\

fluffynuts commented 3 years ago

ok, I've pushed version 2.0.0 - I did a major increment because I've had to drop support for netstandard1.4 and there's a new overload of Match that upstream NUnit supports, but I don't.

synek317 commented 3 years ago

Wow, thanks again! It works just fine with 2.0.0 and net461 (which I choose exactly having in mind it is the first net4xy that supports netstandard2.0).

MonoDevelop is not getting much love recently but well, that's why I'm moving towards .net core :)

fluffynuts commented 3 years ago

np, glad I could help