SEBWIN-220: Fixed race condition caused by the client stopping its communication host before the runtime had a chance to disconnect from it.
This commit is contained in:
parent
67ba5fcce3
commit
86d6949a6f
14 changed files with 281 additions and 14 deletions
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Moq;
|
using Moq;
|
||||||
using SafeExamBrowser.Client.Communication;
|
using SafeExamBrowser.Client.Communication;
|
||||||
|
@ -53,6 +54,7 @@ namespace SafeExamBrowser.Client.UnitTests.Communication
|
||||||
|
|
||||||
Assert.IsNotNull(response);
|
Assert.IsNotNull(response);
|
||||||
Assert.IsTrue(response.ConnectionEstablished);
|
Assert.IsTrue(response.ConnectionEstablished);
|
||||||
|
Assert.IsTrue(sut.IsConnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -80,8 +82,10 @@ namespace SafeExamBrowser.Client.UnitTests.Communication
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MustCorrectlyDisconnect()
|
public void MustCorrectlyDisconnect()
|
||||||
{
|
{
|
||||||
|
var eventFired = false;
|
||||||
var token = Guid.NewGuid();
|
var token = Guid.NewGuid();
|
||||||
|
|
||||||
|
sut.RuntimeDisconnected += () => eventFired = true;
|
||||||
sut.StartupToken = token;
|
sut.StartupToken = token;
|
||||||
|
|
||||||
var connectionResponse = sut.Connect(token);
|
var connectionResponse = sut.Connect(token);
|
||||||
|
@ -89,6 +93,8 @@ namespace SafeExamBrowser.Client.UnitTests.Communication
|
||||||
|
|
||||||
Assert.IsNotNull(response);
|
Assert.IsNotNull(response);
|
||||||
Assert.IsTrue(response.ConnectionTerminated);
|
Assert.IsTrue(response.ConnectionTerminated);
|
||||||
|
Assert.IsTrue(eventFired);
|
||||||
|
Assert.IsFalse(sut.IsConnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -119,6 +125,59 @@ namespace SafeExamBrowser.Client.UnitTests.Communication
|
||||||
Assert.AreEqual(PROCESS_ID, (response as AuthenticationResponse)?.ProcessId);
|
Assert.AreEqual(PROCESS_ID, (response as AuthenticationResponse)?.ProcessId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustHandlePasswordRequestCorrectly()
|
||||||
|
{
|
||||||
|
var passwordRequested = false;
|
||||||
|
var purpose = PasswordRequestPurpose.Administrator;
|
||||||
|
var requestId = Guid.NewGuid();
|
||||||
|
var resetEvent = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
sut.PasswordRequested += (args) =>
|
||||||
|
{
|
||||||
|
passwordRequested = args.Purpose == purpose && args.RequestId == requestId;
|
||||||
|
resetEvent.Set();
|
||||||
|
};
|
||||||
|
sut.StartupToken = Guid.Empty;
|
||||||
|
|
||||||
|
var token = sut.Connect(Guid.Empty).CommunicationToken.Value;
|
||||||
|
var message = new PasswordRequestMessage(purpose, requestId) { CommunicationToken = token };
|
||||||
|
var response = sut.Send(message);
|
||||||
|
|
||||||
|
resetEvent.WaitOne();
|
||||||
|
|
||||||
|
Assert.IsTrue(passwordRequested);
|
||||||
|
Assert.IsNotNull(response);
|
||||||
|
Assert.IsInstanceOfType(response, typeof(SimpleResponse));
|
||||||
|
Assert.AreEqual(SimpleResponsePurport.Acknowledged, (response as SimpleResponse)?.Purport);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustHandleReconfigurationDenialCorrectly()
|
||||||
|
{
|
||||||
|
var filePath = @"C:\Some\Random\Path\To\A\File.seb";
|
||||||
|
var reconfigurationDenied = false;
|
||||||
|
var resetEvent = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
sut.ReconfigurationDenied += (args) =>
|
||||||
|
{
|
||||||
|
reconfigurationDenied = new Uri(args.ConfigurationPath).Equals(new Uri(filePath));
|
||||||
|
resetEvent.Set();
|
||||||
|
};
|
||||||
|
sut.StartupToken = Guid.Empty;
|
||||||
|
|
||||||
|
var token = sut.Connect(Guid.Empty).CommunicationToken.Value;
|
||||||
|
var message = new ReconfigurationDeniedMessage(filePath) { CommunicationToken = token };
|
||||||
|
var response = sut.Send(message);
|
||||||
|
|
||||||
|
resetEvent.WaitOne();
|
||||||
|
|
||||||
|
Assert.IsTrue(reconfigurationDenied);
|
||||||
|
Assert.IsNotNull(response);
|
||||||
|
Assert.IsInstanceOfType(response, typeof(SimpleResponse));
|
||||||
|
Assert.AreEqual(SimpleResponsePurport.Acknowledged, (response as SimpleResponse)?.Purport);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MustHandleShutdownRequestCorrectly()
|
public void MustHandleShutdownRequestCorrectly()
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using SafeExamBrowser.Client.Operations;
|
||||||
|
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||||
|
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||||
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class ClientHostDisconnectionOperationTests
|
||||||
|
{
|
||||||
|
private Mock<IClientHost> clientHost;
|
||||||
|
private Mock<ILogger> logger;
|
||||||
|
|
||||||
|
private ClientHostDisconnectionOperation sut;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
clientHost = new Mock<IClientHost>();
|
||||||
|
logger = new Mock<ILogger>();
|
||||||
|
|
||||||
|
sut = new ClientHostDisconnectionOperation(clientHost.Object, logger.Object, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustWaitForDisconnectionIfConnectionIsActive()
|
||||||
|
{
|
||||||
|
var stopWatch = new Stopwatch();
|
||||||
|
var timeout_ms = 1000;
|
||||||
|
|
||||||
|
sut = new ClientHostDisconnectionOperation(clientHost.Object, logger.Object, timeout_ms);
|
||||||
|
|
||||||
|
clientHost.SetupGet(h => h.IsConnected).Returns(true).Callback(() => clientHost.Raise(h => h.RuntimeDisconnected += null));
|
||||||
|
|
||||||
|
stopWatch.Start();
|
||||||
|
sut.Revert();
|
||||||
|
stopWatch.Stop();
|
||||||
|
|
||||||
|
clientHost.VerifyGet(h => h.IsConnected);
|
||||||
|
clientHost.VerifyNoOtherCalls();
|
||||||
|
|
||||||
|
Assert.IsFalse(stopWatch.IsRunning);
|
||||||
|
Assert.IsTrue(stopWatch.ElapsedMilliseconds < timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustRespectTimeoutIfWaitingForDisconnection()
|
||||||
|
{
|
||||||
|
var stopWatch = new Stopwatch();
|
||||||
|
var timeout_ms = 50;
|
||||||
|
|
||||||
|
sut = new ClientHostDisconnectionOperation(clientHost.Object, logger.Object, timeout_ms);
|
||||||
|
|
||||||
|
clientHost.SetupGet(h => h.IsConnected).Returns(true);
|
||||||
|
|
||||||
|
stopWatch.Start();
|
||||||
|
sut.Revert();
|
||||||
|
stopWatch.Stop();
|
||||||
|
|
||||||
|
clientHost.VerifyGet(h => h.IsConnected);
|
||||||
|
clientHost.VerifyNoOtherCalls();
|
||||||
|
|
||||||
|
Assert.IsFalse(stopWatch.IsRunning);
|
||||||
|
Assert.IsTrue(stopWatch.ElapsedMilliseconds > timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustDoNothingIfNoConnectionIsActive()
|
||||||
|
{
|
||||||
|
clientHost.SetupGet(h => h.IsConnected).Returns(false);
|
||||||
|
|
||||||
|
sut.Revert();
|
||||||
|
|
||||||
|
clientHost.VerifyGet(h => h.IsConnected);
|
||||||
|
clientHost.VerifyNoOtherCalls();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustDoNothingOnPerform()
|
||||||
|
{
|
||||||
|
var result = sut.Perform();
|
||||||
|
|
||||||
|
clientHost.VerifyNoOtherCalls();
|
||||||
|
|
||||||
|
Assert.AreEqual(OperationResult.Success, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustDoNothingOnRepeat()
|
||||||
|
{
|
||||||
|
var result = sut.Repeat();
|
||||||
|
|
||||||
|
clientHost.VerifyNoOtherCalls();
|
||||||
|
|
||||||
|
Assert.AreEqual(OperationResult.Success, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,6 +78,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Operations\BrowserOperationTests.cs" />
|
<Compile Include="Operations\BrowserOperationTests.cs" />
|
||||||
|
<Compile Include="Operations\ClientHostDisconnectionOperationTests.cs" />
|
||||||
<Compile Include="Operations\ClipboardOperationTests.cs" />
|
<Compile Include="Operations\ClipboardOperationTests.cs" />
|
||||||
<Compile Include="Operations\DisplayMonitorOperationTests.cs" />
|
<Compile Include="Operations\DisplayMonitorOperationTests.cs" />
|
||||||
<Compile Include="Operations\KeyboardInterceptorOperationTests.cs" />
|
<Compile Include="Operations\KeyboardInterceptorOperationTests.cs" />
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using SafeExamBrowser.Communication.Hosts;
|
||||||
using SafeExamBrowser.Contracts.Communication.Data;
|
using SafeExamBrowser.Contracts.Communication.Data;
|
||||||
using SafeExamBrowser.Contracts.Communication.Events;
|
using SafeExamBrowser.Contracts.Communication.Events;
|
||||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||||
using SafeExamBrowser.Contracts.Logging;
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
using SafeExamBrowser.Communication.Hosts;
|
|
||||||
|
|
||||||
namespace SafeExamBrowser.Client.Communication
|
namespace SafeExamBrowser.Client.Communication
|
||||||
{
|
{
|
||||||
|
@ -21,9 +21,11 @@ namespace SafeExamBrowser.Client.Communication
|
||||||
private int processId;
|
private int processId;
|
||||||
|
|
||||||
public Guid StartupToken { private get; set; }
|
public Guid StartupToken { private get; set; }
|
||||||
|
public bool IsConnected { get; private set; }
|
||||||
|
|
||||||
public event CommunicationEventHandler<PasswordRequestEventArgs> PasswordRequested;
|
public event CommunicationEventHandler<PasswordRequestEventArgs> PasswordRequested;
|
||||||
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationDenied;
|
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationDenied;
|
||||||
|
public event CommunicationEventHandler RuntimeDisconnected;
|
||||||
public event CommunicationEventHandler Shutdown;
|
public event CommunicationEventHandler Shutdown;
|
||||||
|
|
||||||
public ClientHost(string address, IHostObjectFactory factory, ILogger logger, int processId) : base(address, factory, logger)
|
public ClientHost(string address, IHostObjectFactory factory, ILogger logger, int processId) : base(address, factory, logger)
|
||||||
|
@ -39,6 +41,7 @@ namespace SafeExamBrowser.Client.Communication
|
||||||
if (accepted)
|
if (accepted)
|
||||||
{
|
{
|
||||||
allowConnection = false;
|
allowConnection = false;
|
||||||
|
IsConnected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return accepted;
|
return accepted;
|
||||||
|
@ -46,7 +49,8 @@ namespace SafeExamBrowser.Client.Communication
|
||||||
|
|
||||||
protected override void OnDisconnect()
|
protected override void OnDisconnect()
|
||||||
{
|
{
|
||||||
// Nothing to do here...
|
RuntimeDisconnected?.Invoke();
|
||||||
|
IsConnected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Response OnReceive(Message message)
|
protected override Response OnReceive(Message message)
|
||||||
|
|
|
@ -97,7 +97,8 @@ namespace SafeExamBrowser.Client
|
||||||
operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, startupToken));
|
operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, startupToken));
|
||||||
operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy));
|
operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy));
|
||||||
operations.Enqueue(new DelegateOperation(UpdateAppConfig));
|
operations.Enqueue(new DelegateOperation(UpdateAppConfig));
|
||||||
operations.Enqueue(new LazyInitializationOperation(BuildCommunicationHostOperation));
|
operations.Enqueue(new LazyInitializationOperation(BuildClientHostOperation));
|
||||||
|
operations.Enqueue(new LazyInitializationOperation(BuildClientHostDisconnectionOperation));
|
||||||
operations.Enqueue(new LazyInitializationOperation(BuildKeyboardInterceptorOperation));
|
operations.Enqueue(new LazyInitializationOperation(BuildKeyboardInterceptorOperation));
|
||||||
operations.Enqueue(new LazyInitializationOperation(BuildWindowMonitorOperation));
|
operations.Enqueue(new LazyInitializationOperation(BuildWindowMonitorOperation));
|
||||||
operations.Enqueue(new LazyInitializationOperation(BuildProcessMonitorOperation));
|
operations.Enqueue(new LazyInitializationOperation(BuildProcessMonitorOperation));
|
||||||
|
@ -177,12 +178,12 @@ namespace SafeExamBrowser.Client
|
||||||
return operation;
|
return operation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IOperation BuildCommunicationHostOperation()
|
private IOperation BuildClientHostOperation()
|
||||||
{
|
{
|
||||||
var processId = Process.GetCurrentProcess().Id;
|
var processId = Process.GetCurrentProcess().Id;
|
||||||
var factory = new HostObjectFactory();
|
var factory = new HostObjectFactory();
|
||||||
var host = new ClientHost(configuration.AppConfig.ClientAddress, factory, new ModuleLogger(logger, nameof(ClientHost)), processId);
|
var host = new ClientHost(configuration.AppConfig.ClientAddress, factory, new ModuleLogger(logger, nameof(ClientHost)), processId);
|
||||||
var operation = new CommunicationOperation(host, logger);
|
var operation = new CommunicationHostOperation(host, logger);
|
||||||
|
|
||||||
clientHost = host;
|
clientHost = host;
|
||||||
clientHost.StartupToken = startupToken;
|
clientHost.StartupToken = startupToken;
|
||||||
|
@ -190,6 +191,14 @@ namespace SafeExamBrowser.Client
|
||||||
return operation;
|
return operation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IOperation BuildClientHostDisconnectionOperation()
|
||||||
|
{
|
||||||
|
var timeout_ms = 5000;
|
||||||
|
var operation = new ClientHostDisconnectionOperation(clientHost, logger, timeout_ms);
|
||||||
|
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
private IOperation BuildKeyboardInterceptorOperation()
|
private IOperation BuildKeyboardInterceptorOperation()
|
||||||
{
|
{
|
||||||
var keyboardInterceptor = new KeyboardInterceptor(configuration.Settings.Keyboard, new ModuleLogger(logger, nameof(KeyboardInterceptor)));
|
var keyboardInterceptor = new KeyboardInterceptor(configuration.Settings.Keyboard, new ModuleLogger(logger, nameof(KeyboardInterceptor)));
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
|
using SafeExamBrowser.Contracts.Communication.Events;
|
||||||
|
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||||
|
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||||
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
|
using SafeExamBrowser.Contracts.UserInterface;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Client.Operations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// During application shutdown, it could happen that the client stops its communication host before the runtime had the chance to
|
||||||
|
/// disconnect from it. This operation prevents the described race condition by waiting on the runtime to disconnect from the client.
|
||||||
|
/// </summary>
|
||||||
|
internal class ClientHostDisconnectionOperation : IOperation
|
||||||
|
{
|
||||||
|
private IClientHost clientHost;
|
||||||
|
private ILogger logger;
|
||||||
|
private int timeout_ms;
|
||||||
|
|
||||||
|
public IProgressIndicator ProgressIndicator { private get; set; }
|
||||||
|
|
||||||
|
public ClientHostDisconnectionOperation(IClientHost clientHost, ILogger logger, int timeout_ms)
|
||||||
|
{
|
||||||
|
this.clientHost = clientHost;
|
||||||
|
this.logger = logger;
|
||||||
|
this.timeout_ms = timeout_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationResult Perform()
|
||||||
|
{
|
||||||
|
return OperationResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationResult Repeat()
|
||||||
|
{
|
||||||
|
return OperationResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Revert()
|
||||||
|
{
|
||||||
|
var disconnected = false;
|
||||||
|
var disconnectedEvent = new AutoResetEvent(false);
|
||||||
|
var disconnectedEventHandler = new CommunicationEventHandler(() => disconnectedEvent.Set());
|
||||||
|
|
||||||
|
clientHost.RuntimeDisconnected += disconnectedEventHandler;
|
||||||
|
|
||||||
|
if (clientHost.IsConnected)
|
||||||
|
{
|
||||||
|
logger.Info("Waiting for runtime to disconnect from client communication host...");
|
||||||
|
disconnected = disconnectedEvent.WaitOne(timeout_ms);
|
||||||
|
|
||||||
|
if (!disconnected)
|
||||||
|
{
|
||||||
|
logger.Error($"Runtime failed to disconnect within {timeout_ms / 1000} seconds!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Info("The runtime has already disconnected from the client communication host.");
|
||||||
|
}
|
||||||
|
|
||||||
|
clientHost.RuntimeDisconnected -= disconnectedEventHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,6 +72,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="App.cs" />
|
<Compile Include="App.cs" />
|
||||||
<Compile Include="ClientController.cs" />
|
<Compile Include="ClientController.cs" />
|
||||||
|
<Compile Include="Operations\ClientHostDisconnectionOperation.cs" />
|
||||||
<Compile Include="Operations\ConfigurationOperation.cs" />
|
<Compile Include="Operations\ConfigurationOperation.cs" />
|
||||||
<Compile Include="Operations\RuntimeConnectionOperation.cs" />
|
<Compile Include="Operations\RuntimeConnectionOperation.cs" />
|
||||||
<Compile Include="Communication\ClientHost.cs" />
|
<Compile Include="Communication\ClientHost.cs" />
|
||||||
|
|
|
@ -121,10 +121,11 @@ namespace SafeExamBrowser.Communication.Proxies
|
||||||
FailIfNotConnected();
|
FailIfNotConnected();
|
||||||
|
|
||||||
message.CommunicationToken = communicationToken.Value;
|
message.CommunicationToken = communicationToken.Value;
|
||||||
|
Logger.Debug($"Sending message '{ToString(message)}'...");
|
||||||
|
|
||||||
var response = proxy.Send(message);
|
var response = proxy.Send(message);
|
||||||
|
|
||||||
Logger.Debug($"Sent message '{ToString(message)}', got response '{ToString(response)}'.");
|
Logger.Debug($"Received response '{ToString(response)}' for message '{ToString(message)}'.");
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,11 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IClientHost : ICommunicationHost
|
public interface IClientHost : ICommunicationHost
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the runtime has established a connection to this host.
|
||||||
|
/// </summary>
|
||||||
|
bool IsConnected { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The startup token used for initial authentication.
|
/// The startup token used for initial authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -31,6 +36,11 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationDenied;
|
event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationDenied;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when the runtime disconnected from the client.
|
||||||
|
/// </summary>
|
||||||
|
event CommunicationEventHandler RuntimeDisconnected;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when the runtime commands the client to shutdown.
|
/// Event fired when the runtime commands the client to shutdown.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -16,11 +16,11 @@ using SafeExamBrowser.Core.Operations;
|
||||||
namespace SafeExamBrowser.Core.UnitTests.Operations
|
namespace SafeExamBrowser.Core.UnitTests.Operations
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class CommunicationOperationTests
|
public class CommunicationHostOperationTests
|
||||||
{
|
{
|
||||||
private Mock<ICommunicationHost> hostMock;
|
private Mock<ICommunicationHost> hostMock;
|
||||||
private Mock<ILogger> loggerMock;
|
private Mock<ILogger> loggerMock;
|
||||||
private CommunicationOperation sut;
|
private CommunicationHostOperation sut;
|
||||||
|
|
||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
|
@ -28,7 +28,7 @@ namespace SafeExamBrowser.Core.UnitTests.Operations
|
||||||
hostMock = new Mock<ICommunicationHost>();
|
hostMock = new Mock<ICommunicationHost>();
|
||||||
loggerMock = new Mock<ILogger>();
|
loggerMock = new Mock<ILogger>();
|
||||||
|
|
||||||
sut = new CommunicationOperation(hostMock.Object, loggerMock.Object);
|
sut = new CommunicationHostOperation(hostMock.Object, loggerMock.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
|
@ -78,7 +78,7 @@
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Operations\CommunicationOperationTests.cs" />
|
<Compile Include="Operations\CommunicationHostOperationTests.cs" />
|
||||||
<Compile Include="Operations\LazyInitializationOperationTests.cs" />
|
<Compile Include="Operations\LazyInitializationOperationTests.cs" />
|
||||||
<Compile Include="Operations\I18nOperationTests.cs" />
|
<Compile Include="Operations\I18nOperationTests.cs" />
|
||||||
<Compile Include="Operations\DelegateOperationTests.cs" />
|
<Compile Include="Operations\DelegateOperationTests.cs" />
|
||||||
|
|
|
@ -18,14 +18,14 @@ namespace SafeExamBrowser.Core.Operations
|
||||||
/// An operation to handle the lifetime of an <see cref="ICommunicationHost"/>. The host is started during <see cref="Perform"/>,
|
/// An operation to handle the lifetime of an <see cref="ICommunicationHost"/>. The host is started during <see cref="Perform"/>,
|
||||||
/// stopped and restarted during <see cref="Repeat"/> (if not running) and stopped during <see cref="Revert"/>.
|
/// stopped and restarted during <see cref="Repeat"/> (if not running) and stopped during <see cref="Revert"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CommunicationOperation : IOperation
|
public class CommunicationHostOperation : IOperation
|
||||||
{
|
{
|
||||||
private ICommunicationHost host;
|
private ICommunicationHost host;
|
||||||
private ILogger logger;
|
private ILogger logger;
|
||||||
|
|
||||||
public IProgressIndicator ProgressIndicator { private get; set; }
|
public IProgressIndicator ProgressIndicator { private get; set; }
|
||||||
|
|
||||||
public CommunicationOperation(ICommunicationHost host, ILogger logger)
|
public CommunicationHostOperation(ICommunicationHost host, ILogger logger)
|
||||||
{
|
{
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
|
@ -54,7 +54,7 @@
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Operations\CommunicationOperation.cs" />
|
<Compile Include="Operations\CommunicationHostOperation.cs" />
|
||||||
<Compile Include="Operations\LazyInitializationOperation.cs" />
|
<Compile Include="Operations\LazyInitializationOperation.cs" />
|
||||||
<Compile Include="Operations\I18nOperation.cs" />
|
<Compile Include="Operations\I18nOperation.cs" />
|
||||||
<Compile Include="Operations\DelegateOperation.cs" />
|
<Compile Include="Operations\DelegateOperation.cs" />
|
||||||
|
|
|
@ -68,7 +68,7 @@ namespace SafeExamBrowser.Runtime
|
||||||
var sessionOperations = new Queue<IOperation>();
|
var sessionOperations = new Queue<IOperation>();
|
||||||
|
|
||||||
bootstrapOperations.Enqueue(new I18nOperation(logger, text, textResource));
|
bootstrapOperations.Enqueue(new I18nOperation(logger, text, textResource));
|
||||||
bootstrapOperations.Enqueue(new CommunicationOperation(runtimeHost, logger));
|
bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger));
|
||||||
|
|
||||||
sessionOperations.Enqueue(new ConfigurationOperation(appConfig, configuration, logger, messageBox, resourceLoader, runtimeHost, text, uiFactory, args));
|
sessionOperations.Enqueue(new ConfigurationOperation(appConfig, configuration, logger, messageBox, resourceLoader, runtimeHost, text, uiFactory, args));
|
||||||
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost));
|
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost));
|
||||||
|
|
Loading…
Reference in a new issue