Skip to content

Merge Robust rhinocommon load into master #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# RhinoCommon Unit Testing

## Intro
Example of unit testing RhinoCommon from within the Visual Studio test runner on windows.
Examples of unit testing RhinoCommon and Grasshopper3d.

## Prerequisites
Rhino 7
Expand All @@ -11,23 +11,34 @@ Visual Studio 2019
https://www.visualstudio.com/downloads/

## Test Framework
This project provides examples for XUnit and MS Test,
but the principles would be easily transferable to other frameworks if needs.
This project provides three example projects.
- One using the MsTest Framework `RhinoPlugin.Tests`
- A second one using Xunit `RhinoPlugin.Tests.Xunit`
- And a thrid one using the old .csproj format without PackageReference Migration where Grasshopper3d libraries are loaded directly by Rhino.Inside `RhinoPlugin.Tests.GHAutoLoad`

but the principles would be easily transferable to other frameworks if needed.

## How to Run
- Build the solution
- Test should appear in Visual Studio Test Explorer
- Set test enviroment to x64
`Test > Test Settings > Default Processor Architecture > x64`
- Click `Run All` to run the tests
- Click `Run All` to run the tests.
- Some times you may need to run each project individualy as the testhost tends to get stuck.

## Further Reading
For more info on using Rhino in a headless environment see the Rhino Compute project:
https://github.com/mcneel/compute.rhino3d
For more info on using Rhino in a headless environment or inside other applications please see the projects below:
- https://github.com/mcneel/compute.rhino3d
- https://github.com/mcneel/rhino.inside


## Known issues
Using the new *.csproj* format, the headless version of Rhino fails to load the GH libraries. To tackle this we are loading the library manually to the test context by resolving the specific assembly.

If you are using the old .csproj format and with a packages.config for the references then GH is loading automatically.

## Troubleshooting
- Note that when Rhino is in headless mode there is no document defined.
The static property `RhinoDoc.ActiveDoc` will thus be null which may trip up your plugin code.

- If you have any problems getting this to work or you have a more complex use case,
then please get in touch via the issue tracker.
- If you have any problems getting this to work or you have a more complex use case, please get in touch via the issue tracker.
6 changes: 6 additions & 0 deletions RhinoCommonUnitTesting.sln
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RhinoPlugin.Tests.Xunit", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RhinoPlugin.Tests", "Src\RhinoPlugin.Tests\RhinoPlugin.Tests.csproj", "{5F877072-8EA6-4CE3-9A6B-6CF201E3CB73}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhinoPlugin.Tests.GHAutoLoad", "Src\RhinoPlugin.Tests.GHAutoLoad\RhinoPlugin.Tests.GHAutoLoad.csproj", "{ACE64814-311F-4B9B-9DFE-31AE9A9030E3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -26,6 +28,10 @@ Global
{5F877072-8EA6-4CE3-9A6B-6CF201E3CB73}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F877072-8EA6-4CE3-9A6B-6CF201E3CB73}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F877072-8EA6-4CE3-9A6B-6CF201E3CB73}.Release|Any CPU.Build.0 = Release|Any CPU
{ACE64814-311F-4B9B-9DFE-31AE9A9030E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACE64814-311F-4B9B-9DFE-31AE9A9030E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACE64814-311F-4B9B-9DFE-31AE9A9030E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACE64814-311F-4B9B-9DFE-31AE9A9030E3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
36 changes: 36 additions & 0 deletions Src/RhinoPlugin.Tests.GHAutoLoad/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RhinoPlugin.Tests.RHInside")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RhinoPlugin.Tests.RHInside")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ace64814-311f-4b9b-9dfe-31ae9a9030e3")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
111 changes: 111 additions & 0 deletions Src/RhinoPlugin.Tests.GHAutoLoad/RhinoPlugin.Tests.GHAutoLoad.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props')" />
<Import Project="..\..\packages\xunit.core.2.4.1\build\xunit.core.props" Condition="Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{ACE64814-311F-4B9B-9DFE-31AE9A9030E3}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RhinoPlugin.Tests.RHInside</RootNamespace>
<AssemblyName>RhinoPlugin.Tests.RHInside</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Eto, Version=2.5.0.0, Culture=neutral, PublicKeyToken=552281e97c755530, processorArchitecture=MSIL">
<HintPath>..\..\packages\RhinoCommon.7.0.20314.3001\lib\net45\Eto.dll</HintPath>
</Reference>
<Reference Include="Eto.Wpf, Version=2.5.0.0, Culture=neutral, PublicKeyToken=552281e97c755530, processorArchitecture=MSIL">
<HintPath>..\..\packages\RhinoWindows.7.0.20314.3001\lib\net45\Eto.Wpf.dll</HintPath>
</Reference>
<Reference Include="GH_IO, Version=7.0.20314.3000, Culture=neutral, PublicKeyToken=6a29997d2e6b4f97, processorArchitecture=MSIL">
<HintPath>..\..\packages\Grasshopper.7.0.20314.3001\lib\net45\GH_IO.dll</HintPath>
</Reference>
<Reference Include="Grasshopper, Version=7.0.20314.3000, Culture=neutral, PublicKeyToken=dda4f5ec2cd80803, processorArchitecture=MSIL">
<HintPath>..\..\packages\Grasshopper.7.0.20314.3001\lib\net45\Grasshopper.dll</HintPath>
</Reference>
<Reference Include="Rhino.UI, Version=7.0.20314.3000, Culture=neutral, PublicKeyToken=552281e97c755530, processorArchitecture=MSIL">
<HintPath>..\..\packages\RhinoCommon.7.0.20314.3001\lib\net45\Rhino.UI.dll</HintPath>
</Reference>
<Reference Include="RhinoCommon, Version=7.0.20314.3000, Culture=neutral, PublicKeyToken=552281e97c755530, processorArchitecture=MSIL">
<HintPath>..\..\packages\RhinoCommon.7.0.20314.3001\lib\net45\RhinoCommon.dll</HintPath>
</Reference>
<Reference Include="RhinoInside, Version=7.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<HintPath>..\..\packages\Rhino.Inside.7.0.0\lib\net48\RhinoInside.dll</HintPath>
</Reference>
<Reference Include="RhinoWindows, Version=7.0.0.0, Culture=neutral, PublicKeyToken=552281e97c755530, processorArchitecture=MSIL">
<HintPath>..\..\packages\RhinoWindows.7.0.20314.3001\lib\net45\RhinoWindows.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.assert, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll</HintPath>
</Reference>
<Reference Include="xunit.core, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll</HintPath>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="XunitExampleTestsRI.cs" />
<Compile Include="XunitTestFixtureRI.cs" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\..\packages\xunit.analyzers.0.10.0\analyzers\dotnet\cs\xunit.analyzers.dll" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.4.1\build\xunit.core.props'))" />
<Error Condition="!Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.4.1\build\xunit.core.targets'))" />
<Error Condition="!Exists('..\..\packages\RhinoCommon.7.0.20314.3001\build\RhinoCommon.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\RhinoCommon.7.0.20314.3001\build\RhinoCommon.targets'))" />
<Error Condition="!Exists('..\..\packages\Grasshopper.7.0.20314.3001\build\Grasshopper.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Grasshopper.7.0.20314.3001\build\Grasshopper.targets'))" />
<Error Condition="!Exists('..\..\packages\RhinoWindows.7.0.20314.3001\build\RhinoWindows.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\RhinoWindows.7.0.20314.3001\build\RhinoWindows.targets'))" />
<Error Condition="!Exists('..\..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props'))" />
</Target>
<Import Project="..\..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.targets')" />
<Import Project="..\..\packages\RhinoCommon.7.0.20314.3001\build\RhinoCommon.targets" Condition="Exists('..\..\packages\RhinoCommon.7.0.20314.3001\build\RhinoCommon.targets')" />
<Import Project="..\..\packages\Grasshopper.7.0.20314.3001\build\Grasshopper.targets" Condition="Exists('..\..\packages\Grasshopper.7.0.20314.3001\build\Grasshopper.targets')" />
<Import Project="..\..\packages\RhinoWindows.7.0.20314.3001\build\RhinoWindows.targets" Condition="Exists('..\..\packages\RhinoWindows.7.0.20314.3001\build\RhinoWindows.targets')" />
</Project>
77 changes: 77 additions & 0 deletions Src/RhinoPlugin.Tests.GHAutoLoad/XunitExampleTestsRI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;

using Rhino.Geometry;
using Grasshopper.Kernel.Types;

using Xunit;


namespace RhinoPlugin.Tests.GHAutoLoad
{
/// <summary>
/// XUnit tests
/// </summary>
[Collection("RhinoTestingCollection")]
public class XunitExampleTestsRI
{
XunitTestFixtureRI _fixture;

public XunitExampleTestsRI(XunitTestFixtureRI fixture)
{
fixture = _fixture;
}

[Fact]
public void Brep_Translation()
{

// Arrange
var bb = new BoundingBox(new Point3d(0, 0, 0), new Point3d(100, 100, 100));
var brep = bb.ToBrep();
var t = Transform.Translation(new Vector3d(30, 40, 50));

// Act
brep.Transform(t);

// Assert
Assert.Equal(brep.GetBoundingBox(true).Center, new Point3d(80, 90, 100));

}

/// <summary>
/// Xunit Test to Intersect sphere with a plane to generate a circle
/// </summary>
[Fact]
public void Brep_Intersection()
{
// Arrange
var radius = 4.0;
var brep = Brep.CreateFromSphere(new Sphere(new Point3d(), radius));
var cuttingPlane = Plane.WorldXY;

// Act
Rhino.Geometry.Intersect.Intersection.BrepPlane(brep, cuttingPlane, 0.001, out var curves, out var points);

// Assert
Assert.Single(curves);
Assert.Equal(2 * Math.PI * radius, curves[0].GetLength());
}

/// <summary>
/// Xunit Test to ensure Centroid of GH_Box outputs a GH_Point
/// </summary>
[Fact]
public void GHBox_Centroid_ReturnsGHPoint()
{
// Arrange
var myBox = new GH_Box(new Box());

// Act
var result = myBox.Boundingbox.Center;

// Assert
Assert.IsType<Point3d>(result);
}

}
}
74 changes: 74 additions & 0 deletions Src/RhinoPlugin.Tests.GHAutoLoad/XunitTestFixtureRI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Microsoft.Win32;
using System;
using System.IO;
using System.Reflection;
using Xunit;

namespace RhinoPlugin.Tests.GHAutoLoad
{

/// <summary>
/// Shared test context across unit tests that loads rhinocommon.dll and grasshopper.dll
/// </summary>
public class XunitTestFixtureRI : IDisposable
{

private Rhino.Runtime.InProcess.RhinoCore _rhinoCore;

/// <summary>
/// Empty Constuctor
/// </summary>
public XunitTestFixtureRI()
{
// Use the latest version of RH7 installed in your system
RhinoInside.Resolver.UseLatest = true;

// Make sure we are running the tests as 64x
Assert.True(Environment.Is64BitProcess, "Tests must be run as x64");

//Initialize Rhino.Inside
RhinoInside.Resolver.Initialize();

// Set path to rhino system directory
string envPath = Environment.GetEnvironmentVariable("path");
Environment.SetEnvironmentVariable("path", envPath + ";" + RhinoInside.Resolver.RhinoSystemDirectory);

// Start Rhino and load all libraries
StartRhino();

}

/// <summary>
/// Starting Rhino - loading the relevant libraries
/// </summary>
[STAThread]
public void StartRhino()
{
_rhinoCore = new Rhino.Runtime.InProcess.RhinoCore(null, Rhino.Runtime.InProcess.WindowStyle.NoWindow);
}

/// <summary>
/// Disposing the context after running all the tests
/// </summary>
public void Dispose()
{
// do nothing or...
_rhinoCore?.Dispose();
_rhinoCore = null;
}
}


/// <summary>
/// Collection Fixture - shared context across test classes
/// </summary>
[CollectionDefinition("RhinoTestingCollection")]
public class RhinoCollection : ICollectionFixture<XunitTestFixtureRI>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}

}

15 changes: 15 additions & 0 deletions Src/RhinoPlugin.Tests.GHAutoLoad/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Grasshopper" version="7.0.20314.3001" targetFramework="net48" />
<package id="Rhino.Inside" version="7.0.0" targetFramework="net48" />
<package id="RhinoCommon" version="7.0.20314.3001" targetFramework="net48" />
<package id="RhinoWindows" version="7.0.20314.3001" targetFramework="net48" />
<package id="xunit" version="2.4.1" targetFramework="net48" />
<package id="xunit.abstractions" version="2.0.3" targetFramework="net48" />
<package id="xunit.analyzers" version="0.10.0" targetFramework="net48" />
<package id="xunit.assert" version="2.4.1" targetFramework="net48" />
<package id="xunit.core" version="2.4.1" targetFramework="net48" />
<package id="xunit.extensibility.core" version="2.4.1" targetFramework="net48" />
<package id="xunit.extensibility.execution" version="2.4.1" targetFramework="net48" />
<package id="xunit.runner.visualstudio" version="2.4.1" targetFramework="net48" developmentDependency="true" />
</packages>
Loading