134 lines
4.3 KiB
C#
134 lines
4.3 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using MxGateway.Contracts.Proto;
|
|
using MxGateway.Worker.MxAccess;
|
|
using MxGateway.Worker.Sta;
|
|
|
|
namespace MxGateway.Worker.Tests.MxAccess;
|
|
|
|
public sealed class MxAccessStaSessionTests
|
|
{
|
|
[Fact]
|
|
public async Task StartAsync_CreatesComObjectAndAttachesEventSinkOnStaThread()
|
|
{
|
|
FakeMxAccessComObjectFactory factory = new();
|
|
FakeMxAccessEventSink eventSink = new();
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, eventSink);
|
|
|
|
WorkerReady ready = await session.StartAsync(workerProcessId: 1234);
|
|
|
|
Assert.Equal(1234, ready.WorkerProcessId);
|
|
Assert.Equal(MxAccessInteropInfo.ProgId, ready.MxaccessProgid);
|
|
Assert.Equal(MxAccessInteropInfo.Clsid, ready.MxaccessClsid);
|
|
Assert.NotNull(ready.ReadyTimestamp);
|
|
Assert.Equal(runtime.StaThreadId, factory.CreateThreadId);
|
|
Assert.Equal(runtime.StaThreadId, eventSink.AttachThreadId);
|
|
Assert.Equal(ApartmentState.STA, factory.CreateApartmentState);
|
|
Assert.Same(factory.CreatedObject, eventSink.AttachedObject);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartAsync_WhenFactoryFails_MapsCreationExceptionWithHResult()
|
|
{
|
|
const int hresult = unchecked((int)0x80040154);
|
|
FakeMxAccessComObjectFactory factory = new(new COMException("Class not registered.", hresult));
|
|
FakeMxAccessEventSink eventSink = new();
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, eventSink);
|
|
|
|
MxAccessCreationException exception = await Assert.ThrowsAsync<MxAccessCreationException>(
|
|
() => session.StartAsync(workerProcessId: 1234));
|
|
|
|
Assert.Equal(hresult, exception.CapturedHResult);
|
|
Assert.Equal(MxAccessInteropInfo.ProgId, exception.AttemptedProgId);
|
|
Assert.Equal(MxAccessInteropInfo.Clsid, exception.AttemptedClsid);
|
|
Assert.Null(eventSink.AttachedObject);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Dispose_DetachesEventSinkOnStaThread()
|
|
{
|
|
FakeMxAccessComObjectFactory factory = new();
|
|
FakeMxAccessEventSink eventSink = new();
|
|
using StaRuntime runtime = CreateRuntime();
|
|
MxAccessStaSession session = new(runtime, factory, eventSink);
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
|
|
session.Dispose();
|
|
|
|
Assert.Equal(runtime.StaThreadId, eventSink.DetachThreadId);
|
|
}
|
|
|
|
private static StaRuntime CreateRuntime()
|
|
{
|
|
return new StaRuntime(
|
|
new NoopComApartmentInitializer(),
|
|
new StaMessagePump(),
|
|
TimeSpan.FromMilliseconds(25));
|
|
}
|
|
|
|
private sealed class FakeMxAccessComObjectFactory : IMxAccessComObjectFactory
|
|
{
|
|
private readonly Exception? exception;
|
|
|
|
public FakeMxAccessComObjectFactory(Exception? exception = null)
|
|
{
|
|
this.exception = exception;
|
|
}
|
|
|
|
public object CreatedObject { get; } = new();
|
|
|
|
public int? CreateThreadId { get; private set; }
|
|
|
|
public ApartmentState? CreateApartmentState { get; private set; }
|
|
|
|
public object Create()
|
|
{
|
|
CreateThreadId = Thread.CurrentThread.ManagedThreadId;
|
|
CreateApartmentState = Thread.CurrentThread.GetApartmentState();
|
|
|
|
if (exception is not null)
|
|
{
|
|
throw exception;
|
|
}
|
|
|
|
return CreatedObject;
|
|
}
|
|
}
|
|
|
|
private sealed class FakeMxAccessEventSink : IMxAccessEventSink
|
|
{
|
|
public object? AttachedObject { get; private set; }
|
|
|
|
public int? AttachThreadId { get; private set; }
|
|
|
|
public int? DetachThreadId { get; private set; }
|
|
|
|
public void Attach(object mxAccessComObject)
|
|
{
|
|
AttachedObject = mxAccessComObject;
|
|
AttachThreadId = Thread.CurrentThread.ManagedThreadId;
|
|
}
|
|
|
|
public void Detach()
|
|
{
|
|
DetachThreadId = Thread.CurrentThread.ManagedThreadId;
|
|
AttachedObject = null;
|
|
}
|
|
}
|
|
|
|
private sealed class NoopComApartmentInitializer : IStaComApartmentInitializer
|
|
{
|
|
public void Initialize()
|
|
{
|
|
}
|
|
|
|
public void Uninitialize()
|
|
{
|
|
}
|
|
}
|
|
}
|