SEBWIN-221: Fixed issues with the operation model by separating repeatable from non-repeatable operations and solved conundrum with session operation sequence.

This commit is contained in:
dbuechel 2018-10-10 09:19:03 +02:00
parent 4ca2fac50e
commit f4631a1a3d
60 changed files with 719 additions and 786 deletions

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
@ -64,12 +63,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
controllerMock.Verify(c => c.Terminate(), Times.Once);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
sut.Repeat();
}
}
}

View file

@ -96,12 +96,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
sut.Repeat();
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
@ -47,12 +46,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
nativeMethodsMock.Verify(n => n.EmptyClipboard(), Times.Once);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
sut.Repeat();
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
@ -39,12 +38,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
{
Assert.Fail();
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
sut.Repeat();
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
@ -64,12 +63,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
displayMonitorMock.Verify(d => d.StopMonitoringDisplayChanges(), Times.Once);
displayMonitorMock.Verify(d => d.ResetPrimaryDisplay(), Times.Once);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
sut.Repeat();
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
@ -50,12 +49,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
nativeMethodsMock.Verify(n => n.DeregisterKeyboardHook(It.IsAny<IKeyboardInterceptor>()), Times.Once);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
sut.Repeat();
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
@ -50,12 +49,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
nativeMethodsMock.Verify(n => n.DeregisterMouseHook(It.IsAny<IMouseInterceptor>()), Times.Once);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
sut.Repeat();
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
@ -71,12 +70,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
processMonitor.Verify(p => p.StartMonitoringExplorer(), Times.Never);
processMonitor.Verify(p => p.StopMonitoringExplorer(), Times.Never);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
sut.Repeat();
}
}
}

View file

@ -6,10 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging;
@ -33,14 +31,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
{
Assert.Fail();
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
var sut = new RuntimeConnectionOperation(logger.Object, runtime.Object, Guid.Empty);
sut.Repeat();
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
@ -94,12 +93,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
powerSupplyMock.Verify(p => p.Terminate(), Times.Once);
wirelessNetworkMock.Verify(w => w.Terminate(), Times.Once);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
sut.Repeat();
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
@ -106,14 +105,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
windowMonitorMock.VerifyNoOtherCalls();
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
var sut = new WindowMonitorOperation(KioskMode.None, loggerMock.Object, windowMonitorMock.Object);
sut.Repeat();
}
}
}

View file

@ -140,7 +140,7 @@ namespace SafeExamBrowser.Client
DeregisterEvents();
var success = operations.TryRevert();
var success = operations.TryRevert() == OperationResult.Success;
if (success)
{

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core;
using SafeExamBrowser.Contracts.Core.OperationModel;
@ -58,17 +57,14 @@ namespace SafeExamBrowser.Client.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
{
throw new InvalidOperationException($"The '{nameof(BrowserOperation)}' is not meant to be repeated!");
}
public void Revert()
public OperationResult Revert()
{
logger.Info("Terminating browser...");
StatusChanged?.Invoke(TextKey.OperationStatus_TerminateBrowser);
browserController.Terminate();
return OperationResult.Success;
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Threading;
using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts;
@ -42,29 +41,32 @@ namespace SafeExamBrowser.Client.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
public OperationResult Revert()
{
throw new InvalidOperationException($"The '{nameof(ClientHostDisconnectionOperation)}' is not meant to be repeated!");
}
public void Revert()
{
var disconnected = false;
var disconnectedEvent = new AutoResetEvent(false);
var disconnectedEventHandler = new CommunicationEventHandler(() => disconnectedEvent.Set());
StatusChanged?.Invoke(TextKey.OperationStatus_WaitRuntimeDisconnection);
clientHost.RuntimeDisconnected += disconnectedEventHandler;
if (clientHost.IsConnected)
{
var disconnected = false;
var disconnectedEvent = new AutoResetEvent(false);
var disconnectedEventHandler = new CommunicationEventHandler(() => disconnectedEvent.Set());
clientHost.RuntimeDisconnected += disconnectedEventHandler;
logger.Info("Waiting for runtime to disconnect from client communication host...");
disconnected = disconnectedEvent.WaitOne(timeout_ms);
if (!disconnected)
clientHost.RuntimeDisconnected -= disconnectedEventHandler;
if (disconnected)
{
logger.Error($"Runtime failed to disconnect within {timeout_ms / 1000} seconds!");
logger.Info("The runtime has successfully disconnected from the client communication host.");
}
else
{
logger.Error($"The runtime failed to disconnect within {timeout_ms / 1000} seconds!");
return OperationResult.Failed;
}
}
else
@ -72,7 +74,7 @@ namespace SafeExamBrowser.Client.Operations
logger.Info("The runtime has already disconnected from the client communication host.");
}
clientHost.RuntimeDisconnected -= disconnectedEventHandler;
return OperationResult.Success;
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.I18n;
@ -36,14 +35,11 @@ namespace SafeExamBrowser.Client.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
{
throw new InvalidOperationException($"The '{nameof(ClipboardOperation)}' is not meant to be repeated!");
}
public void Revert()
public OperationResult Revert()
{
EmptyClipboard();
return OperationResult.Success;
}
private void EmptyClipboard()

View file

@ -61,14 +61,9 @@ namespace SafeExamBrowser.Client.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
public OperationResult Revert()
{
throw new InvalidOperationException($"The '{nameof(ConfigurationOperation)}' is not meant to be repeated!");
}
public void Revert()
{
// Nothing to do here...
return OperationResult.Success;
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.I18n;
@ -44,18 +43,15 @@ namespace SafeExamBrowser.Client.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
{
throw new InvalidOperationException($"The '{nameof(DisplayMonitorOperation)}' is not meant to be repeated!");
}
public void Revert()
public OperationResult Revert()
{
logger.Info("Restoring working area...");
StatusChanged?.Invoke(TextKey.OperationStatus_RestoreWorkingArea);
displayMonitor.StopMonitoringDisplayChanges();
displayMonitor.ResetPrimaryDisplay();
return OperationResult.Success;
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.I18n;
@ -45,17 +44,14 @@ namespace SafeExamBrowser.Client.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
{
throw new InvalidOperationException($"The '{nameof(KeyboardInterceptorOperation)}' is not meant to be repeated!");
}
public void Revert()
public OperationResult Revert()
{
logger.Info("Stopping keyboard interception...");
StatusChanged?.Invoke(TextKey.OperationStatus_StopKeyboardInterception);
nativeMethods.DeregisterKeyboardHook(keyboardInterceptor);
return OperationResult.Success;
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.I18n;
@ -45,17 +44,14 @@ namespace SafeExamBrowser.Client.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
{
throw new InvalidOperationException($"The '{nameof(MouseInterceptorOperation)}' is not meant to be repeated!");
}
public void Revert()
public OperationResult Revert()
{
logger.Info("Stopping mouse interception...");
StatusChanged?.Invoke(TextKey.OperationStatus_StopMouseInterception);
nativeMethods.DeregisterMouseHook(mouseInterceptor);
return OperationResult.Success;
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
@ -45,12 +44,7 @@ namespace SafeExamBrowser.Client.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
{
throw new InvalidOperationException($"The '{nameof(ProcessMonitorOperation)}' is not meant to be repeated!");
}
public void Revert()
public OperationResult Revert()
{
logger.Info("Stopping process monitoring...");
StatusChanged?.Invoke(TextKey.OperationStatus_StopProcessMonitoring);
@ -59,6 +53,8 @@ namespace SafeExamBrowser.Client.Operations
{
processMonitor.StopMonitoringExplorer();
}
return OperationResult.Success;
}
}
}

View file

@ -51,12 +51,7 @@ namespace SafeExamBrowser.Client.Operations
return connected ? OperationResult.Success : OperationResult.Failed;
}
public OperationResult Repeat()
{
throw new InvalidOperationException($"The '{nameof(RuntimeConnectionOperation)}' is not meant to be repeated!");
}
public void Revert()
public OperationResult Revert()
{
logger.Info("Closing runtime connection...");
StatusChanged?.Invoke(TextKey.OperationStatus_CloseRuntimeConnection);
@ -73,7 +68,11 @@ namespace SafeExamBrowser.Client.Operations
{
logger.Error("Failed to disconnect from the runtime!");
}
return success ? OperationResult.Success : OperationResult.Failed;
}
return OperationResult.Success;
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core;
@ -91,12 +90,7 @@ namespace SafeExamBrowser.Client.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
{
throw new InvalidOperationException($"The '{nameof(TaskbarOperation)}' is not meant to be repeated!");
}
public void Revert()
public OperationResult Revert()
{
logger.Info("Terminating taskbar...");
StatusChanged?.Invoke(TextKey.OperationStatus_TerminateTaskbar);
@ -120,6 +114,8 @@ namespace SafeExamBrowser.Client.Operations
{
wirelessNetwork.Terminate();
}
return OperationResult.Success;
}
private void AddKeyboardLayoutControl()

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
@ -50,12 +49,7 @@ namespace SafeExamBrowser.Client.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
{
throw new InvalidOperationException($"The '{nameof(WindowMonitorOperation)}' is not meant to be repeated!");
}
public void Revert()
public OperationResult Revert()
{
logger.Info("Stopping window monitoring...");
StatusChanged?.Invoke(TextKey.OperationStatus_StopWindowMonitoring);
@ -69,6 +63,8 @@ namespace SafeExamBrowser.Client.Operations
{
windowMonitor.RestoreHiddenWindows();
}
return OperationResult.Success;
}
}
}

View file

@ -64,9 +64,9 @@ namespace SafeExamBrowser.Configuration
{
CurrentSession = new SessionData
{
ClientProcess = CurrentSession?.ClientProcess,
ClientProxy = CurrentSession?.ClientProxy,
Id = Guid.NewGuid(),
NewDesktop = CurrentSession?.NewDesktop,
OriginalDesktop = CurrentSession?.OriginalDesktop,
StartupToken = Guid.NewGuid()
};

View file

@ -18,6 +18,8 @@ namespace SafeExamBrowser.Configuration
public IClientProxy ClientProxy { get; set; }
public IProcess ClientProcess { get; set; }
public Guid Id { get; set; }
public IDesktop NewDesktop { get; set; }
public IDesktop OriginalDesktop { get; set; }
public Guid StartupToken { get; set; }
}
}

View file

@ -13,7 +13,7 @@ using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Contracts.Configuration
{
/// <summary>
/// Defines all session-related (configuration) data.
/// Holds all session-related configuration and runtime data.
/// </summary>
public interface ISessionData
{
@ -32,6 +32,16 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary>
Guid Id { get; }
/// <summary>
/// The new desktop, if <see cref="Settings.KioskMode.CreateNewDesktop"/> is active for this session.
/// </summary>
IDesktop NewDesktop { get; set; }
/// <summary>
/// The original desktop, if <see cref="Settings.KioskMode.CreateNewDesktop"/> is active for this session.
/// </summary>
IDesktop OriginalDesktop { get; set; }
/// <summary>
/// The startup token used by the client and runtime components for initial authentication.
/// </summary>

View file

@ -31,13 +31,8 @@ namespace SafeExamBrowser.Contracts.Core.OperationModel
OperationResult Perform();
/// <summary>
/// Repeats the operation.
/// Reverts all changes made when executing the operation.
/// </summary>
OperationResult Repeat();
/// <summary>
/// Reverts all changes which were made when executing the operation.
/// </summary>
void Revert();
OperationResult Revert();
}
}

View file

@ -11,14 +11,13 @@ using SafeExamBrowser.Contracts.Core.OperationModel.Events;
namespace SafeExamBrowser.Contracts.Core.OperationModel
{
/// <summary>
/// A sequence of <see cref="IOperation"/>s which can be used for sequential procedures, e.g. the initialization &amp; finalization of
/// A sequence of <see cref="IOperation"/> which can be used for sequential procedures, e.g. the initialization &amp; finalization of
/// an application component. Each operation will be executed failsafe, i.e. the return value will indicate whether a procedure
/// completed successfully or not.
///
/// Exemplary execution order for a sequence initialized with operations A, B, C, D:
///
/// <see cref="TryPerform"/>: A -> B -> C -> D.
/// <see cref="TryRepeat"/>: A -> B -> C -> D.
/// <see cref="TryRevert"/>: D -> C -> B -> A.
/// </summary>
public interface IOperationSequence
@ -45,15 +44,9 @@ namespace SafeExamBrowser.Contracts.Core.OperationModel
OperationResult TryPerform();
/// <summary>
/// Tries to repeat the operations of this sequence according to their initialized order. If any operation fails, the already
/// performed operations will not be reverted.
/// Tries to revert the operations of this sequence in reversion of their initialized order. The reversion of all operations will
/// continue, even if one or multiple operations fail to revert successfully.
/// </summary>
OperationResult TryRepeat();
/// <summary>
/// Tries to revert the operations of this sequence. Returns <c>true</c> if all operations were reverted without errors,
/// otherwise <c>false</c>. The reversion of all operations will continue, even if one or multiple operations fail.
/// </summary>
bool TryRevert();
OperationResult TryRevert();
}
}

View file

@ -0,0 +1,21 @@
/*
* 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/.
*/
namespace SafeExamBrowser.Contracts.Core.OperationModel
{
/// <summary>
/// Defines an operation which can be executed multiple times as part of an <see cref="IRepeatableOperationSequence"/>.
/// </summary>
public interface IRepeatableOperation : IOperation
{
/// <summary>
/// Repeats the operation.
/// </summary>
OperationResult Repeat();
}
}

View file

@ -0,0 +1,26 @@
/*
* 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/.
*/
namespace SafeExamBrowser.Contracts.Core.OperationModel
{
/// <summary>
/// A sequence of <see cref="IRepeatableOperation"/> which can be used for repeatable sequential procedures.
///
/// Exemplary execution order for a sequence initialized with operations A, B, C, D:
///
/// <see cref="TryRepeat()"/>: A -> B -> C -> D.
/// </summary>
public interface IRepeatableOperationSequence : IOperationSequence
{
/// <summary>
/// Tries to repeat the operations of this sequence according to their initialized order. If any operation fails, the already
/// repeated operations will not be reverted.
/// </summary>
OperationResult TryRepeat();
}
}

View file

@ -9,7 +9,7 @@
namespace SafeExamBrowser.Contracts.Core.OperationModel
{
/// <summary>
/// Defines the result of the sequential execution of <see cref="IOperation"/>s (as part of an <see cref="IOperationSequence"/>).
/// Defines the operation result of the execution of an <see cref="IOperation"/> resp. <see cref="IOperationSequence"/>.
/// </summary>
public enum OperationResult
{

View file

@ -63,6 +63,8 @@
<Compile Include="Core\OperationModel\Events\ProgressChangedEventHandler.cs" />
<Compile Include="Core\OperationModel\Events\StatusChangedEventHandler.cs" />
<Compile Include="Core\OperationModel\IOperationSequence.cs" />
<Compile Include="Core\OperationModel\IRepeatableOperation.cs" />
<Compile Include="Core\OperationModel\IRepeatableOperationSequence.cs" />
<Compile Include="Core\OperationModel\OperationResult.cs" />
<Compile Include="Browser\DownloadEventArgs.cs" />
<Compile Include="Browser\DownloadFinishedCallback.cs" />

View file

@ -303,159 +303,6 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel
#endregion
#region Repeat Tests
[TestMethod]
public void MustCorrectlyAbortRepeat()
{
var operationA = new Mock<IOperation>();
var operationB = new Mock<IOperation>();
var operationC = new Mock<IOperation>();
var operations = new Queue<IOperation>();
operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operationB.Setup(o => o.Repeat()).Returns(OperationResult.Aborted);
operations.Enqueue(operationA.Object);
operations.Enqueue(operationB.Object);
operations.Enqueue(operationC.Object);
var sut = new OperationSequence(loggerMock.Object, operations);
var result = sut.TryRepeat();
operationA.Verify(o => o.Repeat(), Times.Once);
operationA.Verify(o => o.Revert(), Times.Never);
operationB.Verify(o => o.Repeat(), Times.Once);
operationB.Verify(o => o.Revert(), Times.Never);
operationC.Verify(o => o.Repeat(), Times.Never);
operationC.Verify(o => o.Revert(), Times.Never);
Assert.AreEqual(OperationResult.Aborted, result);
}
[TestMethod]
public void MustRepeatOperations()
{
var operationA = new Mock<IOperation>();
var operationB = new Mock<IOperation>();
var operationC = new Mock<IOperation>();
var operations = new Queue<IOperation>();
operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operationB.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operationC.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operations.Enqueue(operationA.Object);
operations.Enqueue(operationB.Object);
operations.Enqueue(operationC.Object);
var sut = new OperationSequence(loggerMock.Object, operations);
var result = sut.TryRepeat();
operationA.Verify(o => o.Perform(), Times.Never);
operationA.Verify(o => o.Repeat(), Times.Once);
operationA.Verify(o => o.Revert(), Times.Never);
operationB.Verify(o => o.Perform(), Times.Never);
operationB.Verify(o => o.Repeat(), Times.Once);
operationB.Verify(o => o.Revert(), Times.Never);
operationC.Verify(o => o.Perform(), Times.Never);
operationC.Verify(o => o.Repeat(), Times.Once);
operationC.Verify(o => o.Revert(), Times.Never);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustRepeatOperationsInSequence()
{
int current = 0, a = 0, b = 0, c = 0;
var operationA = new Mock<IOperation>();
var operationB = new Mock<IOperation>();
var operationC = new Mock<IOperation>();
var operations = new Queue<IOperation>();
operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success).Callback(() => a = ++current);
operationB.Setup(o => o.Repeat()).Returns(OperationResult.Success).Callback(() => b = ++current);
operationC.Setup(o => o.Repeat()).Returns(OperationResult.Success).Callback(() => c = ++current);
operations.Enqueue(operationA.Object);
operations.Enqueue(operationB.Object);
operations.Enqueue(operationC.Object);
var sut = new OperationSequence(loggerMock.Object, operations);
var result = sut.TryRepeat();
Assert.AreEqual(OperationResult.Success, result);
Assert.IsTrue(a == 1);
Assert.IsTrue(b == 2);
Assert.IsTrue(c == 3);
}
[TestMethod]
public void MustNotRevertOperationsInCaseOfError()
{
var operationA = new Mock<IOperation>();
var operationB = new Mock<IOperation>();
var operationC = new Mock<IOperation>();
var operationD = new Mock<IOperation>();
var operations = new Queue<IOperation>();
operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operationB.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operationC.Setup(o => o.Repeat()).Throws<Exception>();
operations.Enqueue(operationA.Object);
operations.Enqueue(operationB.Object);
operations.Enqueue(operationC.Object);
operations.Enqueue(operationD.Object);
var sut = new OperationSequence(loggerMock.Object, operations);
var result = sut.TryRepeat();
operationA.Verify(o => o.Repeat(), Times.Once);
operationA.Verify(o => o.Revert(), Times.Never);
operationB.Verify(o => o.Repeat(), Times.Once);
operationB.Verify(o => o.Revert(), Times.Never);
operationC.Verify(o => o.Repeat(), Times.Once);
operationC.Verify(o => o.Revert(), Times.Never);
operationD.Verify(o => o.Repeat(), Times.Never);
operationD.Verify(o => o.Revert(), Times.Never);
Assert.AreEqual(OperationResult.Failed, result);
}
[TestMethod]
public void MustSucceedRepeatingWithEmptyQueue()
{
var sut = new OperationSequence(loggerMock.Object, new Queue<IOperation>());
var result = sut.TryRepeat();
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustSucceedRepeatingWithoutCallingPerform()
{
var sut = new OperationSequence(loggerMock.Object, new Queue<IOperation>());
var result = sut.TryRepeat();
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustNotFailInCaseOfUnexpectedErrorWhenRepeating()
{
var sut = new OperationSequence(loggerMock.Object, new Queue<IOperation>());
sut.ProgressChanged += (args) => throw new Exception();
var result = sut.TryRepeat();
Assert.AreEqual(OperationResult.Failed, result);
}
#endregion
#region Revert Tests
[TestMethod]
@ -467,11 +314,11 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel
var operations = new Queue<IOperation>();
operationA.Setup(o => o.Perform()).Returns(OperationResult.Success);
operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operationA.Setup(o => o.Revert()).Returns(OperationResult.Success);
operationB.Setup(o => o.Perform()).Returns(OperationResult.Success);
operationB.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operationB.Setup(o => o.Revert()).Returns(OperationResult.Success);
operationC.Setup(o => o.Perform()).Returns(OperationResult.Success);
operationC.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operationC.Setup(o => o.Revert()).Returns(OperationResult.Success);
operations.Enqueue(operationA.Object);
operations.Enqueue(operationB.Object);
@ -481,13 +328,13 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel
sut.TryPerform();
var success = sut.TryRevert();
var result = sut.TryRevert();
operationA.Verify(o => o.Revert(), Times.Once);
operationB.Verify(o => o.Revert(), Times.Once);
operationC.Verify(o => o.Revert(), Times.Once);
Assert.IsTrue(success);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
@ -515,9 +362,9 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel
sut.TryPerform();
var success = sut.TryRevert();
var result = sut.TryRevert();
Assert.IsTrue(success);
Assert.AreEqual(OperationResult.Success, result);
Assert.IsTrue(c == 1);
Assert.IsTrue(b == 2);
Assert.IsTrue(a == 3);
@ -547,13 +394,13 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel
sut.TryPerform();
var success = sut.TryRevert();
var result = sut.TryRevert();
operationA.Verify(o => o.Revert(), Times.Once);
operationB.Verify(o => o.Revert(), Times.Once);
operationC.Verify(o => o.Revert(), Times.Once);
Assert.IsFalse(success);
Assert.AreEqual(OperationResult.Failed, result);
}
[TestMethod]
@ -565,9 +412,9 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel
var operations = new Queue<IOperation>();
operationA.Setup(o => o.Perform()).Returns(OperationResult.Success);
operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operationA.Setup(o => o.Revert()).Returns(OperationResult.Success);
operationB.Setup(o => o.Perform()).Returns(OperationResult.Aborted);
operationB.Setup(o => o.Repeat()).Returns(OperationResult.Success);
operationB.Setup(o => o.Revert()).Returns(OperationResult.Success);
operations.Enqueue(operationA.Object);
operations.Enqueue(operationB.Object);
@ -577,13 +424,13 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel
sut.TryPerform();
var success = sut.TryRevert();
var result = sut.TryRevert();
operationA.Verify(o => o.Revert(), Times.Once);
operationB.Verify(o => o.Revert(), Times.Once);
operationC.Verify(o => o.Revert(), Times.Never);
Assert.IsTrue(success);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
@ -592,16 +439,19 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel
var sut = new OperationSequence(loggerMock.Object, new Queue<IOperation>());
sut.TryPerform();
sut.TryRevert();
var result = sut.TryRevert();
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustSucceedRevertingWithoutCallingPerform()
{
var sut = new OperationSequence(loggerMock.Object, new Queue<IOperation>());
var success = sut.TryRevert();
var result = sut.TryRevert();
Assert.IsTrue(success);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
@ -611,9 +461,9 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel
sut.ProgressChanged += (args) => throw new Exception();
var success = sut.TryRevert();
var result = sut.TryRevert();
Assert.IsFalse(success);
Assert.AreEqual(OperationResult.Failed, result);
}
#endregion

View file

@ -0,0 +1,177 @@
/*
* 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 Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SafeExamBrowser.Core.UnitTests.OperationModel
{
[TestClass]
public class RepeatableOperationSequenceTests
{
[TestInitialize]
public void Initialize()
{
}
[TestMethod]
public void TODO()
{
Assert.Fail();
}
//[TestMethod]
//public void MustCorrectlyAbortRepeat()
//{
// var operationA = new Mock<IOperation>();
// var operationB = new Mock<IOperation>();
// var operationC = new Mock<IOperation>();
// var operations = new Queue<IOperation>();
// operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success);
// operationB.Setup(o => o.Repeat()).Returns(OperationResult.Aborted);
// operations.Enqueue(operationA.Object);
// operations.Enqueue(operationB.Object);
// operations.Enqueue(operationC.Object);
// var sut = new OperationSequence(loggerMock.Object, operations);
// var result = sut.TryRepeat();
// operationA.Verify(o => o.Repeat(), Times.Once);
// operationA.Verify(o => o.Revert(), Times.Never);
// operationB.Verify(o => o.Repeat(), Times.Once);
// operationB.Verify(o => o.Revert(), Times.Never);
// operationC.Verify(o => o.Repeat(), Times.Never);
// operationC.Verify(o => o.Revert(), Times.Never);
// Assert.AreEqual(OperationResult.Aborted, result);
//}
//[TestMethod]
//public void MustRepeatOperations()
//{
// var operationA = new Mock<IOperation>();
// var operationB = new Mock<IOperation>();
// var operationC = new Mock<IOperation>();
// var operations = new Queue<IOperation>();
// operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success);
// operationB.Setup(o => o.Repeat()).Returns(OperationResult.Success);
// operationC.Setup(o => o.Repeat()).Returns(OperationResult.Success);
// operations.Enqueue(operationA.Object);
// operations.Enqueue(operationB.Object);
// operations.Enqueue(operationC.Object);
// var sut = new OperationSequence(loggerMock.Object, operations);
// var result = sut.TryRepeat();
// operationA.Verify(o => o.Perform(), Times.Never);
// operationA.Verify(o => o.Repeat(), Times.Once);
// operationA.Verify(o => o.Revert(), Times.Never);
// operationB.Verify(o => o.Perform(), Times.Never);
// operationB.Verify(o => o.Repeat(), Times.Once);
// operationB.Verify(o => o.Revert(), Times.Never);
// operationC.Verify(o => o.Perform(), Times.Never);
// operationC.Verify(o => o.Repeat(), Times.Once);
// operationC.Verify(o => o.Revert(), Times.Never);
// Assert.AreEqual(OperationResult.Success, result);
//}
//[TestMethod]
//public void MustRepeatOperationsInSequence()
//{
// int current = 0, a = 0, b = 0, c = 0;
// var operationA = new Mock<IOperation>();
// var operationB = new Mock<IOperation>();
// var operationC = new Mock<IOperation>();
// var operations = new Queue<IOperation>();
// operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success).Callback(() => a = ++current);
// operationB.Setup(o => o.Repeat()).Returns(OperationResult.Success).Callback(() => b = ++current);
// operationC.Setup(o => o.Repeat()).Returns(OperationResult.Success).Callback(() => c = ++current);
// operations.Enqueue(operationA.Object);
// operations.Enqueue(operationB.Object);
// operations.Enqueue(operationC.Object);
// var sut = new OperationSequence(loggerMock.Object, operations);
// var result = sut.TryRepeat();
// Assert.AreEqual(OperationResult.Success, result);
// Assert.IsTrue(a == 1);
// Assert.IsTrue(b == 2);
// Assert.IsTrue(c == 3);
//}
//[TestMethod]
//public void MustNotRevertOperationsInCaseOfError()
//{
// var operationA = new Mock<IOperation>();
// var operationB = new Mock<IOperation>();
// var operationC = new Mock<IOperation>();
// var operationD = new Mock<IOperation>();
// var operations = new Queue<IOperation>();
// operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success);
// operationB.Setup(o => o.Repeat()).Returns(OperationResult.Success);
// operationC.Setup(o => o.Repeat()).Throws<Exception>();
// operations.Enqueue(operationA.Object);
// operations.Enqueue(operationB.Object);
// operations.Enqueue(operationC.Object);
// operations.Enqueue(operationD.Object);
// var sut = new OperationSequence(loggerMock.Object, operations);
// var result = sut.TryRepeat();
// operationA.Verify(o => o.Repeat(), Times.Once);
// operationA.Verify(o => o.Revert(), Times.Never);
// operationB.Verify(o => o.Repeat(), Times.Once);
// operationB.Verify(o => o.Revert(), Times.Never);
// operationC.Verify(o => o.Repeat(), Times.Once);
// operationC.Verify(o => o.Revert(), Times.Never);
// operationD.Verify(o => o.Repeat(), Times.Never);
// operationD.Verify(o => o.Revert(), Times.Never);
// Assert.AreEqual(OperationResult.Failed, result);
//}
//[TestMethod]
//public void MustSucceedRepeatingWithEmptyQueue()
//{
// var sut = new OperationSequence(loggerMock.Object, new Queue<IOperation>());
// var result = sut.TryRepeat();
// Assert.AreEqual(OperationResult.Success, result);
//}
//[TestMethod]
//public void MustSucceedRepeatingWithoutCallingPerform()
//{
// var sut = new OperationSequence(loggerMock.Object, new Queue<IOperation>());
// var result = sut.TryRepeat();
// Assert.AreEqual(OperationResult.Success, result);
//}
//[TestMethod]
//public void MustNotFailInCaseOfUnexpectedErrorWhenRepeating()
//{
// var sut = new OperationSequence(loggerMock.Object, new Queue<IOperation>());
// sut.ProgressChanged += (args) => throw new Exception();
// var result = sut.TryRepeat();
// Assert.AreEqual(OperationResult.Failed, result);
//}
}
}

View file

@ -58,11 +58,11 @@ namespace SafeExamBrowser.Core.UnitTests.Operations
var perform = sut.Perform();
var repeat = sut.Repeat();
sut.Revert();
var revert = sut.Revert();
Assert.AreEqual(OperationResult.Success, perform);
Assert.AreEqual(OperationResult.Success, repeat);
Assert.AreEqual(OperationResult.Success, revert);
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Core.OperationModel;
@ -52,12 +51,5 @@ namespace SafeExamBrowser.Core.UnitTests.Operations
text.VerifyNoOtherCalls();
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustNotAllowRepeating()
{
sut.Repeat();
}
}
}

View file

@ -45,20 +45,6 @@ namespace SafeExamBrowser.Core.UnitTests.Operations
Assert.IsTrue(initialized);
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public void MustNotInstantiateOperationOnRepeat()
{
IOperation initialize()
{
return operationMock.Object;
};
var sut = new LazyInitializationOperation(initialize);
sut.Repeat();
}
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public void MustNotInstantiateOperationOnRevert()
@ -82,16 +68,14 @@ namespace SafeExamBrowser.Core.UnitTests.Operations
};
operationMock.Setup(o => o.Perform()).Returns(OperationResult.Success);
operationMock.Setup(o => o.Repeat()).Returns(OperationResult.Failed);
operationMock.Setup(o => o.Revert()).Returns(OperationResult.Failed);
var sut = new LazyInitializationOperation(initialize);
var perform = sut.Perform();
var repeat = sut.Repeat();
sut.Revert();
var revert = sut.Revert();
Assert.AreEqual(OperationResult.Success, perform);
Assert.AreEqual(OperationResult.Failed, repeat);
Assert.AreEqual(OperationResult.Failed, revert);
}
[TestMethod]
@ -147,6 +131,8 @@ namespace SafeExamBrowser.Core.UnitTests.Operations
{
if (first)
{
first = false;
return operation;
}
@ -156,11 +142,9 @@ namespace SafeExamBrowser.Core.UnitTests.Operations
var sut = new LazyInitializationOperation(initialize);
sut.Perform();
sut.Repeat();
sut.Revert();
operationMock.Verify(o => o.Perform(), Times.Once);
operationMock.Verify(o => o.Repeat(), Times.Once);
operationMock.Verify(o => o.Revert(), Times.Once);
}
}

View file

@ -79,6 +79,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="OperationModel\QueueExtensionTests.cs" />
<Compile Include="OperationModel\RepeatableOperationSequenceTests.cs" />
<Compile Include="Operations\CommunicationHostOperationTests.cs" />
<Compile Include="Operations\LazyInitializationOperationTests.cs" />
<Compile Include="Operations\I18nOperationTests.cs" />

View file

@ -20,9 +20,9 @@ namespace SafeExamBrowser.Core.OperationModel
/// </summary>
public class OperationSequence : IOperationSequence
{
private ILogger logger;
private Queue<IOperation> operations = new Queue<IOperation>();
private Stack<IOperation> stack = new Stack<IOperation>();
protected ILogger logger;
protected Queue<IOperation> operations = new Queue<IOperation>();
protected Stack<IOperation> stack = new Stack<IOperation>();
public event ActionRequiredEventHandler ActionRequired
{
@ -44,7 +44,7 @@ namespace SafeExamBrowser.Core.OperationModel
this.operations = new Queue<IOperation>(operations);
}
public OperationResult TryPerform()
public virtual OperationResult TryPerform()
{
var result = OperationResult.Failed;
@ -66,53 +66,36 @@ namespace SafeExamBrowser.Core.OperationModel
return result;
}
public OperationResult TryRepeat()
public virtual OperationResult TryRevert()
{
var result = OperationResult.Failed;
try
{
Initialize();
result = Repeat();
}
catch (Exception e)
{
logger.Error("Failed to repeat operations!", e);
}
return result;
}
public bool TryRevert()
{
var success = false;
try
{
Initialize(true);
success = Revert();
result = Revert();
}
catch (Exception e)
{
logger.Error("Failed to revert operations!", e);
}
return success;
return result;
}
private void Initialize(bool indeterminate = false)
protected virtual void Initialize(bool indeterminate = false)
{
if (indeterminate)
{
ProgressChanged?.Invoke(new ProgressChangedEventArgs { IsIndeterminate = true });
UpdateProgress(new ProgressChangedEventArgs { IsIndeterminate = true });
}
else
{
ProgressChanged?.Invoke(new ProgressChangedEventArgs { CurrentValue = 0, MaxValue = operations.Count });
UpdateProgress(new ProgressChangedEventArgs { CurrentValue = 0, MaxValue = operations.Count });
}
}
private OperationResult Perform()
protected virtual OperationResult Perform()
{
foreach (var operation in operations)
{
@ -134,39 +117,13 @@ namespace SafeExamBrowser.Core.OperationModel
return result;
}
ProgressChanged?.Invoke(new ProgressChangedEventArgs { Progress = true });
UpdateProgress(new ProgressChangedEventArgs { Progress = true });
}
return OperationResult.Success;
}
private OperationResult Repeat()
{
foreach (var operation in operations)
{
var result = OperationResult.Failed;
try
{
result = operation.Repeat();
}
catch (Exception e)
{
logger.Error($"Caught unexpected exception while repeating operation '{operation.GetType().Name}'!", e);
}
if (result != OperationResult.Success)
{
return result;
}
ProgressChanged?.Invoke(new ProgressChangedEventArgs { Progress = true });
}
return OperationResult.Success;
}
private bool Revert(bool regress = false)
protected virtual OperationResult Revert(bool regress = false)
{
var success = true;
@ -176,21 +133,31 @@ namespace SafeExamBrowser.Core.OperationModel
try
{
operation.Revert();
var result = operation.Revert();
if (result != OperationResult.Success)
{
success = false;
}
}
catch (Exception e)
{
logger.Error($"Failed to revert operation '{operation.GetType().Name}'!", e);
logger.Error($"Caught unexpected exception while reverting operation '{operation.GetType().Name}'!", e);
success = false;
}
if (regress)
{
ProgressChanged?.Invoke(new ProgressChangedEventArgs { Regress = true });
UpdateProgress(new ProgressChangedEventArgs { Regress = true });
}
}
return success;
return success ? OperationResult.Success : OperationResult.Failed;
}
protected void UpdateProgress(ProgressChangedEventArgs args)
{
ProgressChanged?.Invoke(args);
}
}
}

View file

@ -0,0 +1,72 @@
/*
* 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;
using System.Collections.Generic;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Core.OperationModel
{
/// <summary>
/// Default implementation of the <see cref="IRepeatableOperationSequence"/>.
/// </summary>
public class RepeatableOperationSequence : OperationSequence, IRepeatableOperationSequence
{
private new Queue<IRepeatableOperation> operations;
public RepeatableOperationSequence(ILogger logger, Queue<IRepeatableOperation> operations) : base(logger, new Queue<IOperation>(operations))
{
this.operations = new Queue<IRepeatableOperation>(operations);
}
public OperationResult TryRepeat()
{
var result = OperationResult.Failed;
try
{
Initialize();
result = Repeat();
}
catch (Exception e)
{
logger.Error("Failed to repeat operations!", e);
}
return result;
}
private OperationResult Repeat()
{
foreach (var operation in operations)
{
var result = OperationResult.Failed;
try
{
result = operation.Repeat();
}
catch (Exception e)
{
logger.Error($"Caught unexpected exception while repeating operation '{operation.GetType().Name}'!", e);
}
if (result != OperationResult.Success)
{
return result;
}
UpdateProgress(new ProgressChangedEventArgs { Progress = true });
}
return OperationResult.Success;
}
}
}

View file

@ -18,7 +18,7 @@ namespace SafeExamBrowser.Core.Operations
/// 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"/>.
/// </summary>
public class CommunicationHostOperation : IOperation
public class CommunicationHostOperation : IRepeatableOperation
{
private ICommunicationHost host;
private ILogger logger;
@ -56,12 +56,14 @@ namespace SafeExamBrowser.Core.Operations
return OperationResult.Success;
}
public void Revert()
public OperationResult Revert()
{
logger.Info("Stopping communication host...");
StatusChanged?.Invoke(TextKey.OperationStatus_StopCommunicationHost);
host.Stop();
return OperationResult.Success;
}
}
}

View file

@ -16,7 +16,7 @@ namespace SafeExamBrowser.Core.Operations
/// A generic operation to allow for the (inline) definition of an operation via delegates. Useful if implementing a complete
/// <see cref="IOperation"/> would be an unnecessary overhead.
/// </summary>
public class DelegateOperation : IOperation
public class DelegateOperation : IRepeatableOperation
{
private Action perform;
private Action repeat;
@ -46,9 +46,11 @@ namespace SafeExamBrowser.Core.Operations
return OperationResult.Success;
}
public void Revert()
public OperationResult Revert()
{
revert?.Invoke();
return OperationResult.Success;
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Globalization;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
@ -43,14 +42,9 @@ namespace SafeExamBrowser.Core.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
public OperationResult Revert()
{
throw new InvalidOperationException($"The '{nameof(I18nOperation)}' is not meant to be repeated!");
}
public void Revert()
{
// Nothing to do here...
return OperationResult.Success;
}
}
}

View file

@ -83,14 +83,9 @@ namespace SafeExamBrowser.Core.Operations
return operation.Perform();
}
public OperationResult Repeat()
public OperationResult Revert()
{
return operation.Repeat();
}
public void Revert()
{
operation.Revert();
return operation.Revert();
}
}
}

View file

@ -55,6 +55,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="OperationModel\QueueExtensions.cs" />
<Compile Include="OperationModel\RepeatableOperationSequence.cs" />
<Compile Include="Operations\CommunicationHostOperation.cs" />
<Compile Include="Operations\LazyInitializationOperation.cs" />
<Compile Include="Operations\I18nOperation.cs" />

View file

@ -117,7 +117,7 @@ namespace SafeExamBrowser.Monitoring.Windows
private void OnWindowChanged(IntPtr window)
{
if (activeWindow != window)
if (window != IntPtr.Zero && activeWindow != window)
{
logger.Debug($"Window has changed from {activeWindow} to {window}.");
activeWindow = window;

View file

@ -84,26 +84,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustStartClientWhenRepeating()
{
var result = default(OperationResult);
var response = new AuthenticationResponse { ProcessId = 1234 };
var communication = new CommunicationResult<AuthenticationResponse>(true, response);
process.SetupGet(p => p.Id).Returns(response.ProcessId);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object).Callback(clientReady);
proxy.Setup(p => p.RequestAuthentication()).Returns(communication);
proxy.Setup(p => p.Connect(It.IsAny<Guid>(), true)).Returns(true);
result = sut.Repeat();
session.VerifySet(s => s.ClientProcess = process.Object, Times.Once);
session.VerifySet(s => s.ClientProxy = proxy.Object, Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustFailStartupIfClientNotStartedWithinTimeout()
{
@ -153,6 +133,87 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
Assert.AreEqual(OperationResult.Failed, result);
}
[TestMethod]
public void MustStartClientWhenRepeating()
{
var result = default(OperationResult);
var response = new AuthenticationResponse { ProcessId = 1234 };
var communication = new CommunicationResult<AuthenticationResponse>(true, response);
process.SetupGet(p => p.Id).Returns(response.ProcessId);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object).Callback(clientReady);
proxy.Setup(p => p.RequestAuthentication()).Returns(communication);
proxy.Setup(p => p.Connect(It.IsAny<Guid>(), true)).Returns(true);
result = sut.Repeat();
session.VerifySet(s => s.ClientProcess = process.Object, Times.Once);
session.VerifySet(s => s.ClientProxy = proxy.Object, Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustTerminateClientOnRepeat()
{
var terminated = new Action(() =>
{
runtimeHost.Raise(h => h.ClientDisconnected += null);
process.Raise(p => p.Terminated += null, 0);
});
proxy.Setup(p => p.Disconnect()).Callback(terminated);
session.SetupGet(s => s.ClientProcess).Returns(process.Object);
var result = sut.Repeat();
proxy.Verify(p => p.InitiateShutdown(), Times.Once);
proxy.Verify(p => p.Disconnect(), Times.Once);
process.Verify(p => p.Kill(), Times.Never);
session.VerifySet(s => s.ClientProcess = null, Times.Once);
session.VerifySet(s => s.ClientProxy = null, Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustDoNothingIfNoClientCreated()
{
session.SetupGet(s => s.ClientProcess).Returns(null as IProcess);
var result = sut.Repeat();
session.VerifyGet(s => s.ClientProcess, Times.Once);
process.VerifyNoOtherCalls();
processFactory.VerifyNoOtherCalls();
proxy.VerifyNoOtherCalls();
proxyFactory.VerifyNoOtherCalls();
runtimeHost.VerifyNoOtherCalls();
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustDoNothingIfNoClientRunning()
{
process.SetupGet(p => p.HasTerminated).Returns(true);
session.SetupGet(s => s.ClientProcess).Returns(process.Object);
var result = sut.Repeat();
process.VerifyGet(p => p.HasTerminated, Times.Once);
session.VerifyGet(s => s.ClientProcess, Times.Exactly(2));
process.VerifyNoOtherCalls();
processFactory.VerifyNoOtherCalls();
proxy.VerifyNoOtherCalls();
proxyFactory.VerifyNoOtherCalls();
runtimeHost.VerifyNoOtherCalls();
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustStopClientWhenReverting()
{

View file

@ -1,144 +0,0 @@
/*
* 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;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
using SafeExamBrowser.Runtime.Operations;
namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
[TestClass]
public class ClientTerminationOperationTests
{
private Action clientReady;
private AppConfig appConfig;
private Mock<IConfigurationRepository> configuration;
private Mock<IClientProxy> proxy;
private Mock<ILogger> logger;
private Mock<IProcess> process;
private Mock<IProcessFactory> processFactory;
private Mock<IProxyFactory> proxyFactory;
private Mock<IRuntimeHost> runtimeHost;
private Mock<ISessionData> session;
private ClientTerminationOperation sut;
[TestInitialize]
public void Initialize()
{
appConfig = new AppConfig();
configuration = new Mock<IConfigurationRepository>();
clientReady = new Action(() => runtimeHost.Raise(h => h.ClientReady += null));
logger = new Mock<ILogger>();
process = new Mock<IProcess>();
processFactory = new Mock<IProcessFactory>();
proxy = new Mock<IClientProxy>();
proxyFactory = new Mock<IProxyFactory>();
runtimeHost = new Mock<IRuntimeHost>();
session = new Mock<ISessionData>();
configuration.SetupGet(c => c.CurrentSession).Returns(session.Object);
configuration.SetupGet(c => c.AppConfig).Returns(appConfig);
proxyFactory.Setup(f => f.CreateClientProxy(It.IsAny<string>())).Returns(proxy.Object);
session.SetupGet(s => s.ClientProxy).Returns(proxy.Object);
sut = new ClientTerminationOperation(configuration.Object, logger.Object, processFactory.Object, proxyFactory.Object, runtimeHost.Object, 0);
}
[TestMethod]
public void MustDoNothingOnPerform()
{
sut.Perform();
process.VerifyNoOtherCalls();
processFactory.VerifyNoOtherCalls();
proxy.VerifyNoOtherCalls();
proxyFactory.VerifyNoOtherCalls();
runtimeHost.VerifyNoOtherCalls();
}
[TestMethod]
public void MustDoNothingOnRevert()
{
sut.Revert();
process.VerifyNoOtherCalls();
processFactory.VerifyNoOtherCalls();
proxy.VerifyNoOtherCalls();
proxyFactory.VerifyNoOtherCalls();
runtimeHost.VerifyNoOtherCalls();
}
[TestMethod]
public void MustTerminateClientOnRepeat()
{
var terminated = new Action(() =>
{
runtimeHost.Raise(h => h.ClientDisconnected += null);
process.Raise(p => p.Terminated += null, 0);
});
proxy.Setup(p => p.Disconnect()).Callback(terminated);
session.SetupGet(s => s.ClientProcess).Returns(process.Object);
var result = sut.Repeat();
proxy.Verify(p => p.InitiateShutdown(), Times.Once);
proxy.Verify(p => p.Disconnect(), Times.Once);
process.Verify(p => p.Kill(), Times.Never);
session.VerifySet(s => s.ClientProcess = null, Times.Once);
session.VerifySet(s => s.ClientProxy = null, Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustDoNothingIfNoClientCreated()
{
session.SetupGet(s => s.ClientProcess).Returns(null as IProcess);
var result = sut.Repeat();
session.VerifyGet(s => s.ClientProcess, Times.Once);
process.VerifyNoOtherCalls();
processFactory.VerifyNoOtherCalls();
proxy.VerifyNoOtherCalls();
proxyFactory.VerifyNoOtherCalls();
runtimeHost.VerifyNoOtherCalls();
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void MustDoNothingIfNoClientRunning()
{
process.SetupGet(p => p.HasTerminated).Returns(true);
session.SetupGet(s => s.ClientProcess).Returns(process.Object);
var result = sut.Repeat();
process.VerifyGet(p => p.HasTerminated, Times.Once);
session.VerifyGet(s => s.ClientProcess, Times.Exactly(2));
process.VerifyNoOtherCalls();
processFactory.VerifyNoOtherCalls();
proxy.VerifyNoOtherCalls();
proxyFactory.VerifyNoOtherCalls();
runtimeHost.VerifyNoOtherCalls();
Assert.AreEqual(OperationResult.Success, result);
}
}
}

View file

@ -90,8 +90,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod]
public void MustCorrectlyRevertCreateNewDesktop()
{
var originalDesktop = new Mock<IDesktop>();
var newDesktop = new Mock<IDesktop>();
var originalDesktop = new Mock<IDesktop>();
var order = 0;
var activate = 0;
var setStartup = 0;
@ -135,8 +135,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod]
public void MustCorrectlySwitchToOtherKioskModeWhenRepeating()
{
var originalDesktop = new Mock<IDesktop>();
var newDesktop = new Mock<IDesktop>();
var originalDesktop = new Mock<IDesktop>();
desktopFactory.Setup(f => f.GetCurrent()).Returns(originalDesktop.Object);
desktopFactory.Setup(f => f.CreateNew(It.IsAny<string>())).Returns(newDesktop.Object);
@ -192,8 +192,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod]
public void MustNotReinitializeCreateNewDesktopWhenRepeating()
{
var originalDesktop = new Mock<IDesktop>();
var newDesktop = new Mock<IDesktop>();
var originalDesktop = new Mock<IDesktop>();
settings.KioskMode = KioskMode.CreateNewDesktop;

View file

@ -83,7 +83,6 @@
<Compile Include="Operations\KioskModeOperationTests.cs" />
<Compile Include="Operations\ServiceOperationTests.cs" />
<Compile Include="Operations\ClientOperationTests.cs" />
<Compile Include="Operations\ClientTerminationOperationTests.cs" />
<Compile Include="Operations\SessionInitializationOperationTests.cs" />
<Compile Include="RuntimeControllerTests.cs" />
<Compile Include="Communication\RuntimeHostTests.cs" />

View file

@ -66,20 +66,21 @@ namespace SafeExamBrowser.Runtime
var uiFactory = new UserInterfaceFactory(text);
var bootstrapOperations = new Queue<IOperation>();
var sessionOperations = new Queue<IOperation>();
var sessionOperations = new Queue<IRepeatableOperation>();
bootstrapOperations.Enqueue(new I18nOperation(logger, text, textResource));
bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger));
sessionOperations.Enqueue(new ConfigurationOperation(appConfig, configuration, logger, resourceLoader, args));
sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, FIFTEEN_SECONDS));
sessionOperations.Enqueue(new KioskModeTerminationOperation(configuration, desktopFactory, explorerShell, logger, processFactory));
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost));
sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy));
sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, FIFTEEN_SECONDS));
sessionOperations.Enqueue(new KioskModeOperation(configuration, desktopFactory, explorerShell, logger, processFactory));
sessionOperations.Enqueue(new ClientOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, FIFTEEN_SECONDS));
var bootstrapSequence = new OperationSequence(logger, bootstrapOperations);
var sessionSequence = new OperationSequence(logger, sessionOperations);
var sessionSequence = new RepeatableOperationSequence(logger, sessionOperations);
RuntimeController = new RuntimeController(appConfig, configuration, logger, messageBox, bootstrapSequence, sessionSequence, runtimeHost, serviceProxy, shutdown, text, uiFactory);
}

View file

@ -20,26 +20,26 @@ using SafeExamBrowser.Contracts.WindowsApi.Events;
namespace SafeExamBrowser.Runtime.Operations
{
internal class ClientOperation : IOperation
internal class ClientOperation : IRepeatableOperation
{
private readonly int timeout_ms;
protected IConfigurationRepository configuration;
protected ILogger logger;
protected IProcessFactory processFactory;
protected IProxyFactory proxyFactory;
protected IRuntimeHost runtimeHost;
private IConfigurationRepository configuration;
private ILogger logger;
private IProcessFactory processFactory;
private IProxyFactory proxyFactory;
private IRuntimeHost runtimeHost;
public event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public event StatusChangedEventHandler StatusChanged;
protected IProcess ClientProcess
private IProcess ClientProcess
{
get { return configuration.CurrentSession.ClientProcess; }
set { configuration.CurrentSession.ClientProcess = value; }
}
protected IClientProxy ClientProxy
private IClientProxy ClientProxy
{
get { return configuration.CurrentSession.ClientProxy; }
set { configuration.CurrentSession.ClientProxy = value; }
@ -84,16 +84,20 @@ namespace SafeExamBrowser.Runtime.Operations
return Perform();
}
public virtual void Revert()
public virtual OperationResult Revert()
{
var success = true;
if (ClientProcess != null && !ClientProcess.HasTerminated)
{
StatusChanged?.Invoke(TextKey.OperationStatus_StopClient);
TryStopClient();
success = TryStopClient();
}
return success ? OperationResult.Success : OperationResult.Failed;
}
protected bool TryStartClient()
private bool TryStartClient()
{
var clientReady = false;
var clientReadyEvent = new AutoResetEvent(false);
@ -146,7 +150,7 @@ namespace SafeExamBrowser.Runtime.Operations
return true;
}
protected bool TryStopClient()
private bool TryStopClient()
{
var success = false;
@ -206,7 +210,7 @@ namespace SafeExamBrowser.Runtime.Operations
return success;
}
protected bool TryKillClient(int attempt = 0)
private bool TryKillClient(int attempt = 0)
{
const int MAX_ATTEMPTS = 5;

View file

@ -10,18 +10,13 @@ using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Operations
{
internal class ClientTerminationOperation : ClientOperation
internal class ClientTerminationOperation : ClientOperation, IRepeatableOperation
{
public new event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public new event StatusChangedEventHandler StatusChanged;
public ClientTerminationOperation(
IConfigurationRepository configuration,
ILogger logger,
@ -39,20 +34,12 @@ namespace SafeExamBrowser.Runtime.Operations
public override OperationResult Repeat()
{
var success = true;
if (ClientProcess != null && !ClientProcess.HasTerminated)
{
StatusChanged?.Invoke(TextKey.OperationStatus_StopClient);
success = TryStopClient();
}
return success ? OperationResult.Success : OperationResult.Failed;
return base.Revert();
}
public override void Revert()
public override OperationResult Revert()
{
// Nothing to do here...
return OperationResult.Success;
}
}
}

View file

@ -19,7 +19,7 @@ using SafeExamBrowser.Runtime.Operations.Events;
namespace SafeExamBrowser.Runtime.Operations
{
internal class ConfigurationOperation : IOperation
internal class ConfigurationOperation : IRepeatableOperation
{
private IConfigurationRepository configuration;
private ILogger logger;
@ -92,9 +92,9 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Failed;
}
public void Revert()
public OperationResult Revert()
{
// Nothing to do here...
return OperationResult.Success;
}
private OperationResult LoadSettings(Uri uri)

View file

@ -16,7 +16,7 @@ using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Operations
{
internal class KioskModeOperation : IOperation
internal class KioskModeOperation : IRepeatableOperation
{
private IConfigurationRepository configuration;
private IDesktopFactory desktopFactory;
@ -24,12 +24,22 @@ namespace SafeExamBrowser.Runtime.Operations
private KioskMode kioskMode;
private ILogger logger;
private IProcessFactory processFactory;
private IDesktop newDesktop;
private IDesktop originalDesktop;
public event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public event StatusChangedEventHandler StatusChanged;
private IDesktop NewDesktop
{
get { return configuration.CurrentSession.NewDesktop; }
set { configuration.CurrentSession.NewDesktop = value; }
}
private IDesktop OriginalDesktop
{
get { return configuration.CurrentSession.OriginalDesktop; }
set { configuration.CurrentSession.OriginalDesktop = value; }
}
public KioskModeOperation(
IConfigurationRepository configuration,
IDesktopFactory desktopFactory,
@ -44,7 +54,7 @@ namespace SafeExamBrowser.Runtime.Operations
this.processFactory = processFactory;
}
public OperationResult Perform()
public virtual OperationResult Perform()
{
kioskMode = configuration.CurrentSettings.KioskMode;
@ -64,7 +74,7 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Success;
}
public OperationResult Repeat()
public virtual OperationResult Repeat()
{
var oldMode = kioskMode;
var newMode = configuration.CurrentSettings.KioskMode;
@ -72,19 +82,14 @@ namespace SafeExamBrowser.Runtime.Operations
if (newMode == oldMode)
{
logger.Info($"New kiosk mode '{newMode}' is equal to the currently active '{oldMode}', skipping re-initialization...");
}
else
{
Revert();
Perform();
return OperationResult.Success;
}
kioskMode = newMode;
return OperationResult.Success;
return Perform();
}
public void Revert()
public virtual OperationResult Revert()
{
logger.Info($"Reverting kiosk mode '{kioskMode}'...");
StatusChanged?.Invoke(TextKey.OperationStatus_RevertKioskMode);
@ -98,18 +103,20 @@ namespace SafeExamBrowser.Runtime.Operations
RestartExplorerShell();
break;
}
return OperationResult.Success;
}
private void CreateNewDesktop()
{
originalDesktop = desktopFactory.GetCurrent();
logger.Info($"Current desktop is {originalDesktop}.");
OriginalDesktop = desktopFactory.GetCurrent();
logger.Info($"Current desktop is {OriginalDesktop}.");
newDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser));
logger.Info($"Created new desktop {newDesktop}.");
NewDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser));
logger.Info($"Created new desktop {NewDesktop}.");
newDesktop.Activate();
processFactory.StartupDesktop = newDesktop;
NewDesktop.Activate();
processFactory.StartupDesktop = NewDesktop;
logger.Info("Successfully activated new desktop.");
explorerShell.Suspend();
@ -117,21 +124,21 @@ namespace SafeExamBrowser.Runtime.Operations
private void CloseNewDesktop()
{
if (originalDesktop != null)
if (OriginalDesktop != null)
{
originalDesktop.Activate();
processFactory.StartupDesktop = originalDesktop;
logger.Info($"Switched back to original desktop {originalDesktop}.");
OriginalDesktop.Activate();
processFactory.StartupDesktop = OriginalDesktop;
logger.Info($"Switched back to original desktop {OriginalDesktop}.");
}
else
{
logger.Warn($"No original desktop found when attempting to close new desktop!");
}
if (newDesktop != null)
if (NewDesktop != null)
{
newDesktop.Close();
logger.Info($"Closed new desktop {newDesktop}.");
NewDesktop.Close();
logger.Info($"Closed new desktop {NewDesktop}.");
}
else
{

View file

@ -0,0 +1,61 @@
/*
* 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 SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Operations
{
internal class KioskModeTerminationOperation : KioskModeOperation, IRepeatableOperation
{
private IConfigurationRepository configuration;
private KioskMode kioskMode;
private ILogger logger;
public KioskModeTerminationOperation(
IConfigurationRepository configuration,
IDesktopFactory desktopFactory,
IExplorerShell explorerShell,
ILogger logger,
IProcessFactory processFactory) : base(configuration, desktopFactory, explorerShell, logger, processFactory)
{
this.configuration = configuration;
this.logger = logger;
}
public override OperationResult Perform()
{
kioskMode = configuration.CurrentSettings.KioskMode;
return OperationResult.Success;
}
public override OperationResult Repeat()
{
var oldMode = kioskMode;
var newMode = configuration.CurrentSettings.KioskMode;
if (newMode == oldMode)
{
logger.Info($"New kiosk mode '{newMode}' is equal to the currently active '{oldMode}', skipping termination...");
return OperationResult.Success;
}
return base.Revert();
}
public override OperationResult Revert()
{
return OperationResult.Success;
}
}
}

View file

@ -16,7 +16,7 @@ using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Runtime.Operations
{
internal class ServiceOperation : IOperation
internal class ServiceOperation : IRepeatableOperation
{
private bool connected, mandatory;
private IConfigurationRepository configuration;
@ -43,7 +43,7 @@ namespace SafeExamBrowser.Runtime.Operations
if (mandatory && !connected)
{
logger.Error("Aborting startup because the service is mandatory but not available!");
logger.Error("Failed to initialize a service session since the service is mandatory but not available!");
return OperationResult.Failed;
}
@ -61,17 +61,17 @@ namespace SafeExamBrowser.Runtime.Operations
public OperationResult Repeat()
{
// TODO: Re-check if mandatory, if so, try to connect (if not connected) - otherwise, no action required (except maybe logging of status?)
if (connected)
var result = Revert();
if (result != OperationResult.Success)
{
StopServiceSession();
StartServiceSession();
return result;
}
return OperationResult.Success;
return Perform();
}
public void Revert()
public OperationResult Revert()
{
logger.Info("Finalizing service session...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession);
@ -91,6 +91,8 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Error("Failed to disconnect from the service!");
}
}
return OperationResult.Success;
}
private void StartServiceSession()

View file

@ -15,7 +15,7 @@ using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Runtime.Operations
{
internal class SessionInitializationOperation : IOperation
internal class SessionInitializationOperation : IRepeatableOperation
{
private IConfigurationRepository configuration;
private ILogger logger;
@ -40,14 +40,12 @@ namespace SafeExamBrowser.Runtime.Operations
public OperationResult Repeat()
{
InitializeSessionConfiguration();
return OperationResult.Success;
return Perform();
}
public void Revert()
public OperationResult Revert()
{
// Nothing to do here...
return OperationResult.Success;
}
private void InitializeSessionConfiguration()

View file

@ -35,7 +35,7 @@ namespace SafeExamBrowser.Runtime
private ILogger logger;
private IMessageBox messageBox;
private IOperationSequence bootstrapSequence;
private IOperationSequence sessionSequence;
private IRepeatableOperationSequence sessionSequence;
private IRuntimeHost runtimeHost;
private IRuntimeWindow runtimeWindow;
private IServiceProxy service;
@ -50,7 +50,7 @@ namespace SafeExamBrowser.Runtime
ILogger logger,
IMessageBox messageBox,
IOperationSequence bootstrapSequence,
IOperationSequence sessionSequence,
IRepeatableOperationSequence sessionSequence,
IRuntimeHost runtimeHost,
IServiceProxy service,
Action shutdown,
@ -127,7 +127,7 @@ namespace SafeExamBrowser.Runtime
logger.Log(string.Empty);
logger.Info("Initiating shutdown procedure...");
var success = bootstrapSequence.TryRevert();
var success = bootstrapSequence.TryRevert() == OperationResult.Success;
if (success)
{
@ -181,8 +181,8 @@ namespace SafeExamBrowser.Runtime
if (result == OperationResult.Failed)
{
// TODO: Check if message box is rendered on new desktop as well! -> E.g. if settings for reconfiguration are invalid
messageBox.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow);
// TODO: Find solution for this; maybe manually switch back to original desktop?
// messageBox.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow);
if (!initial)
{
@ -202,7 +202,7 @@ namespace SafeExamBrowser.Runtime
DeregisterSessionEvents();
var success = sessionSequence.TryRevert();
var success = sessionSequence.TryRevert() == OperationResult.Success;
if (success)
{
@ -236,37 +236,20 @@ namespace SafeExamBrowser.Runtime
private void DeregisterSessionEvents()
{
configuration.CurrentSession.ClientProcess.Terminated -= ClientProcess_Terminated;
configuration.CurrentSession.ClientProxy.ConnectionLost -= Client_ConnectionLost;
if (configuration.CurrentSession.ClientProcess != null)
{
configuration.CurrentSession.ClientProcess.Terminated -= ClientProcess_Terminated;
}
if (configuration.CurrentSession.ClientProxy != null)
{
configuration.CurrentSession.ClientProxy.ConnectionLost -= Client_ConnectionLost;
}
}
private void BootstrapSequence_ProgressChanged(ProgressChangedEventArgs args)
{
// TODO: Duplicated code (for splashScreen as well as runtimeWindow)!
if (args.CurrentValue.HasValue)
{
splashScreen?.SetValue(args.CurrentValue.Value);
}
if (args.IsIndeterminate == true)
{
splashScreen?.SetIndeterminate();
}
if (args.MaxValue.HasValue)
{
splashScreen?.SetMaxValue(args.MaxValue.Value);
}
if (args.Progress == true)
{
splashScreen?.Progress();
}
if (args.Regress == true)
{
splashScreen?.Regress();
}
MapProgress(splashScreen, args);
}
private void BootstrapSequence_StatusChanged(TextKey status)
@ -277,7 +260,12 @@ namespace SafeExamBrowser.Runtime
private void ClientProcess_Terminated(int exitCode)
{
logger.Error($"Client application has unexpectedly terminated with exit code {exitCode}!");
// TODO: Check if message box is rendered on new desktop as well -> otherwise shutdown is blocked! Check if parent needed!
if (sessionRunning)
{
StopSession();
}
messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error);
shutdown.Invoke();
@ -286,7 +274,12 @@ namespace SafeExamBrowser.Runtime
private void Client_ConnectionLost()
{
logger.Error("Lost connection to the client application!");
// TODO: Check if message box is rendered on new desktop as well -> otherwise shutdown is blocked! Check if parent needed!
if (sessionRunning)
{
StopSession();
}
messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error);
shutdown.Invoke();
@ -400,35 +393,40 @@ namespace SafeExamBrowser.Runtime
private void SessionSequence_ProgressChanged(ProgressChangedEventArgs args)
{
if (args.CurrentValue.HasValue)
{
runtimeWindow?.SetValue(args.CurrentValue.Value);
}
if (args.IsIndeterminate == true)
{
runtimeWindow?.SetIndeterminate();
}
if (args.MaxValue.HasValue)
{
runtimeWindow?.SetMaxValue(args.MaxValue.Value);
}
if (args.Progress == true)
{
runtimeWindow?.Progress();
}
if (args.Regress == true)
{
runtimeWindow?.Regress();
}
MapProgress(runtimeWindow, args);
}
private void SessionSequence_StatusChanged(TextKey status)
{
runtimeWindow?.UpdateStatus(status, true);
}
private void MapProgress(IProgressIndicator progressIndicator, ProgressChangedEventArgs args)
{
if (args.CurrentValue.HasValue)
{
progressIndicator?.SetValue(args.CurrentValue.Value);
}
if (args.IsIndeterminate == true)
{
progressIndicator?.SetIndeterminate();
}
if (args.MaxValue.HasValue)
{
progressIndicator?.SetMaxValue(args.MaxValue.Value);
}
if (args.Progress == true)
{
progressIndicator?.Progress();
}
if (args.Regress == true)
{
progressIndicator?.Regress();
}
}
}
}

View file

@ -92,6 +92,7 @@
<Compile Include="Operations\Events\ConfigurationCompletedEventArgs.cs" />
<Compile Include="Operations\Events\PasswordRequiredEventArgs.cs" />
<Compile Include="Operations\KioskModeOperation.cs" />
<Compile Include="Operations\KioskModeTerminationOperation.cs" />
<Compile Include="Operations\ServiceOperation.cs" />
<Compile Include="Operations\SessionInitializationOperation.cs" />
<Compile Include="Communication\ProxyFactory.cs" />