Skip to content

Run Unit Tests from VBA or other COM-Aware programs #6270

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

Closed
wants to merge 11 commits into from
41 changes: 41 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Build Rubberduck in GitHub Actions using windows-2019
# For now we don't run the tests, but might add it in the future.
# This is a simple build script that builds the Rubberduck project using MSBuild.
# It uses the latest version of Visual Studio 2019 and the .NET Framework 4.6.2

name: Build Rubberduck

on:
workflow_dispatch:

jobs:
build:
runs-on: windows-2019

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Setup NuGet
uses: NuGet/[email protected]

- name: Restore NuGet packages
run: |
nuget RubberduckMeta.sln
nuget restore Rubberduck.sln

- name: Build Solution (Release)
run: msbuild Rubberduck.sln /p:Configuration=Release /p:Platform="Any CPU" /p:TargetFrameworkVersion=v4.6.2 /verbosity:minimal

- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
name: rubberduck-Release
path: |
Rubberduck.Main/bin/Release/net462/
if-no-files-found: error

24 changes: 24 additions & 0 deletions Rubberduck.InternalApi/Common/StringLineBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rubberduck.InternalApi.Common
{
/// <summary>
/// Extension to StringBuilder to allow adding text line by line.
/// </summary>
public class StringLineBuilder
{
private readonly StringBuilder _document = new StringBuilder();

public override string ToString() => _document.ToString();

public void AppendLine(string value = "")
=> _document.Append(value + "\r\n");

public void AppendLineNoNullChars(string value)
=> AppendLine(value.Replace("\0", string.Empty));
}
}
19 changes: 15 additions & 4 deletions Rubberduck.Main/Extension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
using Extensibility;
using NLog;
using Rubberduck.Common.WinAPI;
using Rubberduck.ExternalApi;
using Rubberduck.Resources;
using Rubberduck.Resources.Registration;
using Rubberduck.Root;
using Rubberduck.Runtime;
using Rubberduck.Settings;
using Rubberduck.SettingsProvider;
using Rubberduck.UI;
using Rubberduck.UnitTesting;
using Rubberduck.VBEditor.ComManagement;
using Rubberduck.VBEditor.ComManagement.TypeLibs;
using Rubberduck.VBEditor.Events;
Expand Down Expand Up @@ -50,12 +52,16 @@ public class _Extension : IDTExtensibility2
private bool _isInitialized;
private bool _isBeginShutdownExecuted;

private IExternalAPI _externalAPI;

private GeneralSettings _initialSettings;

private IWindsorContainer _container;
private App _app;
private readonly Logger _logger = LogManager.GetCurrentClassLogger();



public void OnAddInsUpdate(ref Array custom) { }

[SuppressMessage("ReSharper", "InconsistentNaming")]
Expand Down Expand Up @@ -91,12 +97,10 @@ public void OnConnection(object Application, ext_ConnectMode ConnectMode, object
Console.WriteLine(e);
}
}

[Conditional("DEBUG")]
private void SetAddInObject()
{
// FOR DEBUGGING/DEVELOPMENT PURPOSES, ALLOW ACCESS TO SOME VBETypeLibsAPI FEATURES FROM VBA
_addin.Object = new VBETypeLibsAPI_Object(_vbe);
_externalAPI = new ExternalAPI(new VBETypeLibsAPI_Object(_vbe));
_addin.Object = _externalAPI;
}

private Assembly LoadFromSameFolder(object sender, ResolveEventArgs args)
Expand Down Expand Up @@ -239,6 +243,8 @@ private void Startup()
_app = _container.Resolve<App>();
_app.Startup();

InitializeExternalAPI();

_isInitialized = true;
}
catch (Exception e)
Expand All @@ -248,6 +254,11 @@ private void Startup()
}
}

private void InitializeExternalAPI()
{
_externalAPI.InitializeAPIs(_container.Resolve<ITestEngine>());
}

private void HandleAppDomainException(object sender, UnhandledExceptionEventArgs e)
{
var message = e.IsTerminating
Expand Down
66 changes: 66 additions & 0 deletions Rubberduck.Main/ExternalAPIs/ExternalAPI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Rubberduck.Resources.Registration;
using Rubberduck.UnitTesting;
using Rubberduck.VBEditor.ComManagement.TypeLibs;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Rubberduck.ExternalApi
{
[
ComVisible(true),
Guid(RubberduckGuid.IExternalAPIInterfaceGuid),
InterfaceType(ComInterfaceType.InterfaceIsDual),
EditorBrowsable(EditorBrowsableState.Always)
]
public interface IExternalAPI
{
[DispId(1)]
void InitializeAPIs(ITestEngine testEngine);
[DispId(2)]
ITestEngineAPI TestEngineAPI { get; }
[DispId(3)]
IVBETypeLibsAPI_Object VBETypeLibsAPI { get; }
}

[
ComVisible(true),
Guid(RubberduckGuid.ExternalAPIObjectGuid),
ProgId(RubberduckProgId.ExternalAPIObject),
ClassInterface(ClassInterfaceType.None),
ComDefaultInterface(typeof(IExternalAPI)),
EditorBrowsable(EditorBrowsableState.Always)
]
public class ExternalAPI : IExternalAPI
{
private readonly IVBETypeLibsAPI_Object _vbeTypeLibsAPI_Object;
private ITestEngineAPI _testEngineAPI;

public ExternalAPI(IVBETypeLibsAPI_Object vbeTypeLibsAPI_Object)
{
_vbeTypeLibsAPI_Object = vbeTypeLibsAPI_Object;
}

public void InitializeAPIs(ITestEngine testEngine)
{
_testEngineAPI = new TestEngineAPI(testEngine);
}

public IVBETypeLibsAPI_Object VBETypeLibsAPI { get => _vbeTypeLibsAPI_Object; }
public ITestEngineAPI TestEngineAPI
{
get
{
if (_testEngineAPI == null)
{
throw new InvalidOperationException("TestEngineAPI is not initialized.");
}
return _testEngineAPI;
}
}
}
}
66 changes: 66 additions & 0 deletions Rubberduck.Main/ExternalAPIs/TestEngineAPI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Rubberduck.InternalApi.Common;
using Rubberduck.Resources.Registration;
using Rubberduck.UnitTesting;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Rubberduck.ExternalApi
{
[
ComVisible(true),
Guid(RubberduckGuid.ITestEngineAPIInterfaceGuid),
InterfaceType(ComInterfaceType.InterfaceIsDual),
EditorBrowsable(EditorBrowsableState.Always)
]
public interface ITestEngineAPI
{
[DispId(1)]
string RunAllTestsAndGetResults(string filePath);
}

[
ComVisible(true),
Guid(RubberduckGuid.TestEngineAPIObjectGuid),
ProgId(RubberduckProgId.TestEngineAPIProgId),
ClassInterface(ClassInterfaceType.None),
ComDefaultInterface(typeof(ITestEngineAPI)),
EditorBrowsable(EditorBrowsableState.Always)
]
public class TestEngineAPI : ITestEngineAPI
{
private readonly ITestEngine _testEngine;

public TestEngineAPI(ITestEngine testEngine)
{
_testEngine = testEngine;
}

/// <summary>
/// Runs all unit tests and returns the results as a formatted string.
/// </summary>
/// <returns>A string containing the test results.</returns>
public string RunAllTestsAndGetResults(string logPath)
{

// Note that we can't use CanRun in case we are triggering the test via VBA since DesignMode is always set to false when you run a macro.
// and CanRun interprets this as "not ready to run tests".

var task = Task.Run(() => {
var output = _testEngine.RunWithResults(_testEngine.Tests);
if (!string.IsNullOrEmpty(logPath))
{
FileSystemProvider.FileSystem.File.WriteAllText(logPath, output.ToString());
}
});

return "Task started to run tests asynchronously. Check the log file for results.";

}
}

}
8 changes: 7 additions & 1 deletion Rubberduck.Resources/Registration/RubberduckGuid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static class RubberduckGuid
public const string ITimesGuid = UnitTestingGuidspace + "EE" + GuidSuffix;
public const string TimesGuid = UnitTestingGuidspace + "EF" + GuidSuffix;

// Rubberduck API Guids:
// Rubberduck Internal API Guids:
private const string ApiGuidspace = "69E0F7";
public const string IDeclarationGuid = ApiGuidspace + "81" + GuidSuffix;
public const string DeclarationClassGuid = ApiGuidspace + "82" + GuidSuffix;
Expand All @@ -87,6 +87,12 @@ public static class RubberduckGuid
public const string IIdentifierReferencesGuid = ApiGuidspace + "8C" + GuidSuffix;
public const string IdentifierReferencesClassGuid = ApiGuidspace + "8D" + GuidSuffix;

// Rubberduck External API Guids:
public const string ExternalAPIObjectGuid = ApiGuidspace + "A0" + GuidSuffix;
public const string IExternalAPIInterfaceGuid = ApiGuidspace + "A1" + GuidSuffix;
public const string TestEngineAPIObjectGuid = ApiGuidspace + "A2" + GuidSuffix;
public const string ITestEngineAPIInterfaceGuid = ApiGuidspace + "A3" + GuidSuffix;

// Enum Guids:
private const string RecordGuidspace = "69E100";
public const string DeclarationTypeGuid = RecordGuidspace + "23" + GuidSuffix;
Expand Down
3 changes: 3 additions & 0 deletions Rubberduck.Resources/Registration/RubberduckProgId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,8 @@ public static class RubberduckProgId
public const string TimesProgId = BaseNamespace + "Times";

public const string DebugAddinObject = BaseNamespace + "VBETypeLibsAPI";

public const string ExternalAPIObject = BaseNamespace + "ExternalAPI";
public const string TestEngineAPIProgId = BaseNamespace + "TestEngineAPI";
}
}
1 change: 1 addition & 0 deletions Rubberduck.UnitTesting/UnitTesting/ITestEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public interface ITestEngine
bool CanRun { get; }
bool CanRepeatLastRun { get; }
void Run(IEnumerable<TestMethod> tests);
string RunWithResults(IEnumerable<TestMethod> tests);
void RunByOutcome(TestOutcome outcome);
void RepeatLastRun();
void RequestCancellation();
Expand Down
Loading