Skip to content

Commit

Permalink
Add project files.
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexMacocian committed Sep 15, 2023
1 parent 58012f7 commit d870e6c
Show file tree
Hide file tree
Showing 22 changed files with 1,077 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [email protected]
50 changes: 50 additions & 0 deletions .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Plumsy CD Pipeline

on:
push:
branches:
- master

jobs:
build:
environment: Default
strategy:
matrix:
targetplatform: [x64]

runs-on: windows-latest

env:
Configuration: Release
Solution_Path: Plumsy.sln
Test_Project_Path: Plumsy.Tests\Plumsy.Tests.csproj
Source_Project_Path: Plumsy\Plumsy.csproj
Actions_Allow_Unsecure_Commands: true

steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0

- name: Install .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '7.x'

- name: Setup MSBuild.exe
uses: microsoft/[email protected]

- name: Restore project
run: msbuild $env:Solution_Path /t:Restore /p:Configuration=$env:Configuration /p:RuntimeIdentifier=$env:RuntimeIdentifier
env:
RuntimeIdentifier: win-${{ matrix.targetplatform }}

- name: Build Plumsy project
run: dotnet build Plumsy -c $env:Configuration

- name: Package Plumsy
run: dotnet pack -c Release -o . $env:Source_Project_Path

- name: Publish
run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
59 changes: 59 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Plumsy CI Pipeline

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
build:
strategy:
matrix:
targetplatform: [x64]

runs-on: windows-latest

env:
Solution_Path: Plumsy.sln
Test_Project_Path: Plumsy.Tests\Plumsy.Tests.csproj
Test_Plugin_Project_Path: Plumsy.Tests.SimplePlugin\Plumsy.Tests.SimplePlugin.csproj
Source_Project_Path: Plumsy\Plumsy.csproj
Actions_Allow_Unsecure_Commands: true

steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0

- name: Install .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.x'

- name: Setup MSBuild.exe
uses: microsoft/[email protected]

- name: Build SimplePlugin project
run: dotnet build $env:Test_Plugin_Project_Path

- name: Prepare plugin files # Needs to be done manually in this pipeline
run: |
mkdir ${{ github.workspace }}\SimplePlugin
cp ${{ github.workspace }}\Plumsy.Tests.SimplePlugin\bin\Debug\net6.0\Plumsy.Tests.SimplePlugin.dll ${{ github.workspace }}\SimplePlugin\Plumsy.Tests.SimplePlugin.dll
cp ${{ github.workspace }}\Plumsy.Tests.SimplePlugin\bin\Debug\net6.0\SystemExtensions.NetStandard.dll ${{ github.workspace }}\SimplePlugin\SystemExtensions.NetStandard.dll
- name: Echo solution path
run: echo /p:SolutionDir=${{ github.workspace }}\

- name: Execute Plumsy Unit Tests
run: dotnet test $env:Test_Project_Path /p:SolutionDir=${{ github.workspace }}\ --logger:"console;verbosity=normal" # Need to manually set the SolutionDir, otherwise it's unspecified

- name: Restore Project
run: msbuild $env:Solution_Path /t:Restore /p:Configuration=$env:Configuration /p:RuntimeIdentifier=$env:RuntimeIdentifier
env:
Configuration: Debug
RuntimeIdentifier: win-${{ matrix.targetplatform }}
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Macocian Alexandru Victor

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
13 changes: 13 additions & 0 deletions Plumsy.Tests.SimplePlugin/Main.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Extensions;

namespace Plum.Net.Tests.SimplePlugin;

public sealed class Main
{
public Main()
{
this.ThrowIfNull("this");
}

public bool ReturnTrue() => true;
}
22 changes: 22 additions & 0 deletions Plumsy.Tests.SimplePlugin/Plumsy.Tests.SimplePlugin.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<PropertyGroup>
<DestinationFolder>$(SolutionDir)SimplePlugin</DestinationFolder>
</PropertyGroup>

<Target Name="CopyAssembliesToSolution" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(OutputPath)$(AssemblyName).dll" DestinationFolder="$(DestinationFolder)" />
<Copy SourceFiles="$(OutputPath)SystemExtensions.NetStandard.dll" DestinationFolder="$(DestinationFolder)" />
</Target>

<ItemGroup>
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.2" />
</ItemGroup>
</Project>
209 changes: 209 additions & 0 deletions Plumsy.Tests/PluginManagerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NSubstitute;
using Plum.Net.Models;
using Plum.Net.Tests.Resolvers;
using Plum.Net.Tests.SimplePlugin;
using Plum.Net.Validators;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.Loader;

namespace Plum.Net.Tests;

[TestClass]
public class PluginManagerTests
{
private readonly string pluginDirectory;
private readonly PluginManager pluginManager;

public PluginManagerTests()
{
this.pluginDirectory = Path.Combine(Environment.CurrentDirectory, "Plugins");
this.pluginManager = new(this.pluginDirectory);
this.pluginManager.WithDependencyResolver(new SimpleDependencyResolver());
}

[TestInitialize]
public void TestInitialize()
{
var assemblyLoadEventHandlerField = typeof(AssemblyLoadContext).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).First(f => f.Name == "AssemblyLoad");
assemblyLoadEventHandlerField.SetValue(AssemblyLoadContext.Default, null);

var resolveDependencyEventHandlerField = typeof(AssemblyLoadContext).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).First(f => f.Name == "AssemblyResolve");
resolveDependencyEventHandlerField.SetValue(AssemblyLoadContext.Default, null);
}

[TestMethod]
public void PluginManager_MissingPluginDirectory_ShouldReturnNoPlugins()
{
var pluginManager = new PluginManager(Path.Combine(this.pluginDirectory, Guid.NewGuid().ToString()));

var plugins = pluginManager.GetAvailablePlugins();

plugins.Should().HaveCount(0);
}

[TestMethod]
public void PluginManager_ExistingPlugin_ShouldReturnExpectedPlugin()
{
var plugins = this.pluginManager.GetAvailablePlugins();

plugins.Should().HaveCount(1);
plugins.First().Name.Should().Be("Plum.Net.Tests.SimplePlugin");
}

[TestMethod]
public void PluginManager_VersionValidator_ValidatesVersion()
{
var called = false;
var validator = Substitute.For<IEnvironmentVersionValidator>();
this.pluginManager.WithEnvironmentVersionValidator(validator);
validator.Validate(Arg.Any<Version>(), Arg.Any<Version>()).Returns(callinfo =>
{
called = true;
return true;
});

var plugins = this.pluginManager.GetAvailablePlugins().ToList();

called.Should().BeTrue();
}

[TestMethod]
public void PluginManager_VersionValidator_CurrentVersionMatchesEnvironment()
{
var validator = Substitute.For<IEnvironmentVersionValidator>();
this.pluginManager.WithEnvironmentVersionValidator(validator);
validator.Validate(Arg.Any<Version>(), Arg.Any<Version>()).Returns(callinfo =>
{
var currentVersion = callinfo.ArgAt<Version>(0);
currentVersion.Should().Be(Environment.Version);
return true;
});

var plugins = this.pluginManager.GetAvailablePlugins().ToList();
}

[TestMethod]
public void PluginManager_VersionValidatorDenies_ReturnsNoPlugins()
{
var validator = Substitute.For<IEnvironmentVersionValidator>();
this.pluginManager.WithEnvironmentVersionValidator(validator);
validator.Validate(Arg.Any<Version>(), Arg.Any<Version>()).Returns(callinfo =>
{
return false;
});

var plugins = this.pluginManager.GetAvailablePlugins().ToList();

plugins.Should().HaveCount(0);
}

[TestMethod]
public void PluginManager_TypeDefinitionsValidator_CallsValidator()
{
var called = false;
var validator = Substitute.For<ITypeDefinitionsValidator>();
this.pluginManager.WithTypeDefinitionsValidator(validator);
validator.Validate(Arg.Any<IEnumerable<TypeDefinition>>(), Arg.Any<MetadataReader>()).Returns(callinfo =>
{
called = true;
return true;
});

var plugins = this.pluginManager.GetAvailablePlugins().ToList();
called.Should().BeTrue();
}

[TestMethod]
public void PluginManager_TypeDefinitionsValidator_ReceivesMainType()
{
var validator = Substitute.For<ITypeDefinitionsValidator>();
this.pluginManager.WithTypeDefinitionsValidator(validator);
validator.Validate(Arg.Any<IEnumerable<TypeDefinition>>(), Arg.Any<MetadataReader>()).Returns(callinfo =>
{
var typeDefinitions = callinfo.ArgAt<IEnumerable<TypeDefinition>>(0);
var metadataReader = callinfo.ArgAt<MetadataReader>(1);
typeDefinitions.Select(t => metadataReader.GetString(t.Name)).Any(t => t == "Main").Should().BeTrue();
return true;
});

var plugins = this.pluginManager.GetAvailablePlugins().ToList();
}

[TestMethod]
public void PluginManager_MetadataValidator_CallsValidator()
{
var called = false;
var validator = Substitute.For<IMetadataValidator>();
this.pluginManager.WithMetadataValidator(validator);
validator.Validate(Arg.Any<MetadataReader>()).Returns(callinfo =>
{
var metadataReader = callinfo.ArgAt<MetadataReader>(0);
metadataReader.Should().NotBeNull();
called = true;
return true;
});

var plugins = this.pluginManager.GetAvailablePlugins().ToList();
called.Should().BeTrue();
}

[TestMethod]
public void PluginManager_LoadPlugins_ShouldSucceed()
{
var plugins = this.pluginManager.GetAvailablePlugins();

var results = this.pluginManager.LoadPlugins(plugins);

results.Should().HaveCount(1);
results.First().Should().BeOfType<PluginLoadOperation.Success>();
}

[TestMethod]
public void PluginManager_LoadPlugins_ReturnsExpectedAssembly()
{
this.pluginManager.WithForceLoadDependencies(true);
var plugins = this.pluginManager.GetAvailablePlugins();

var results = this.pluginManager.LoadPlugins(plugins);
var assembly = results.OfType<PluginLoadOperation.Success>().First().Plugin.Assembly;
var mainType = assembly.GetTypes().First(t => t.Name == nameof(Main));
var main = Activator.CreateInstance(mainType).As<Main>();

main.Should().NotBeNull();
main.ReturnTrue().Should().BeTrue();
}

[TestMethod]
[Ignore("This test fails when run together with the other tests, as the dependent assembly is loaded into the AppDomain by other tests")]
public void PluginManager_LoadPlugins_WithNoResolver_FailsToResolveAssembly()
{
var pluginManager = new PluginManager(this.pluginDirectory);
pluginManager.WithForceLoadDependencies(true);
var plugins = pluginManager.GetAvailablePlugins();

var results = pluginManager.LoadPlugins(plugins);

results.First().Should().BeOfType<PluginLoadOperation.ExceptionEncountered>();
}

[TestMethod]
public void PluginManager_LoadPlugins_WithNoResolverAndNoForcedDependencyResolve_Succeeds()
{
var pluginManager = new PluginManager(this.pluginDirectory);
pluginManager.WithForceLoadDependencies(false);
var plugins = pluginManager.GetAvailablePlugins();

var results = pluginManager.LoadPlugins(plugins);

results.First().Should().BeOfType<PluginLoadOperation.Success>();
}
}
Loading

0 comments on commit d870e6c

Please sign in to comment.