From f4631a1a3d0eb785fcc91f44094868c0879ee7ac Mon Sep 17 00:00:00 2001 From: dbuechel Date: Wed, 10 Oct 2018 09:19:03 +0200 Subject: [PATCH] SEBWIN-221: Fixed issues with the operation model by separating repeatable from non-repeatable operations and solved conundrum with session operation sequence. --- .../Operations/BrowserOperationTests.cs | 8 - .../ClientHostDisconnectionOperationTests.cs | 7 - .../Operations/ClipboardOperationTests.cs | 8 - .../Operations/ConfigurationOperationTests.cs | 8 - .../DisplayMonitorOperationTests.cs | 8 - .../KeyboardInterceptorOperationTests.cs | 8 - .../MouseInterceptorOperationTests.cs | 8 - .../ProcessMonitorOperationTests.cs | 8 - .../RuntimeConnectionOperationTests.cs | 11 - .../Operations/TaskbarOperationTests.cs | 8 - .../Operations/WindowMonitorOperationTests.cs | 10 - SafeExamBrowser.Client/ClientController.cs | 2 +- .../Operations/BrowserOperation.cs | 10 +- .../ClientHostDisconnectionOperation.cs | 34 ++-- .../Operations/ClipboardOperation.cs | 10 +- .../Operations/ConfigurationOperation.cs | 9 +- .../Operations/DisplayMonitorOperation.cs | 10 +- .../KeyboardInterceptorOperation.cs | 10 +- .../Operations/MouseInterceptorOperation.cs | 10 +- .../Operations/ProcessMonitorOperation.cs | 10 +- .../Operations/RuntimeConnectionOperation.cs | 11 +- .../Operations/TaskbarOperation.cs | 10 +- .../Operations/WindowMonitorOperation.cs | 10 +- .../ConfigurationRepository.cs | 4 +- SafeExamBrowser.Configuration/SessionData.cs | 2 + .../Configuration/ISessionData.cs | 12 +- .../Core/OperationModel/IOperation.cs | 9 +- .../Core/OperationModel/IOperationSequence.cs | 15 +- .../OperationModel/IRepeatableOperation.cs | 21 ++ .../IRepeatableOperationSequence.cs | 26 +++ .../Core/OperationModel/OperationResult.cs | 2 +- .../SafeExamBrowser.Contracts.csproj | 2 + .../OperationModel/OperationSequenceTests.cs | 192 ++---------------- .../RepeatableOperationSequenceTests.cs | 177 ++++++++++++++++ .../Operations/DelegateOperationTests.cs | 4 +- .../Operations/I18nOperationTests.cs | 8 - .../LazyInitializationOperationTests.cs | 26 +-- .../SafeExamBrowser.Core.UnitTests.csproj | 1 + .../OperationModel/OperationSequence.cs | 87 +++----- .../RepeatableOperationSequence.cs | 72 +++++++ .../Operations/CommunicationHostOperation.cs | 6 +- .../Operations/DelegateOperation.cs | 6 +- .../Operations/I18nOperation.cs | 10 +- .../Operations/LazyInitializationOperation.cs | 9 +- .../SafeExamBrowser.Core.csproj | 1 + .../Windows/WindowMonitor.cs | 2 +- .../Operations/ClientOperationTests.cs | 101 +++++++-- .../ClientTerminationOperationTests.cs | 144 ------------- .../Operations/KioskModeOperationTests.cs | 6 +- .../SafeExamBrowser.Runtime.UnitTests.csproj | 1 - SafeExamBrowser.Runtime/CompositionRoot.cs | 7 +- .../Operations/ClientOperation.cs | 30 +-- .../Operations/ClientTerminationOperation.cs | 21 +- .../Operations/ConfigurationOperation.cs | 6 +- .../Operations/KioskModeOperation.cs | 61 +++--- .../KioskModeTerminationOperation.cs | 61 ++++++ .../Operations/ServiceOperation.cs | 18 +- .../SessionInitializationOperation.cs | 10 +- SafeExamBrowser.Runtime/RuntimeController.cs | 116 ++++++----- .../SafeExamBrowser.Runtime.csproj | 1 + 60 files changed, 719 insertions(+), 786 deletions(-) create mode 100644 SafeExamBrowser.Contracts/Core/OperationModel/IRepeatableOperation.cs create mode 100644 SafeExamBrowser.Contracts/Core/OperationModel/IRepeatableOperationSequence.cs create mode 100644 SafeExamBrowser.Core.UnitTests/OperationModel/RepeatableOperationSequenceTests.cs create mode 100644 SafeExamBrowser.Core/OperationModel/RepeatableOperationSequence.cs delete mode 100644 SafeExamBrowser.Runtime.UnitTests/Operations/ClientTerminationOperationTests.cs create mode 100644 SafeExamBrowser.Runtime/Operations/KioskModeTerminationOperation.cs diff --git a/SafeExamBrowser.Client.UnitTests/Operations/BrowserOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/BrowserOperationTests.cs index 0c3c5477..dea65dfb 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/BrowserOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/BrowserOperationTests.cs @@ -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(); - } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/ClientHostDisconnectionOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/ClientHostDisconnectionOperationTests.cs index 26c838d3..e6a750f0 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/ClientHostDisconnectionOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/ClientHostDisconnectionOperationTests.cs @@ -96,12 +96,5 @@ namespace SafeExamBrowser.Client.UnitTests.Operations Assert.AreEqual(OperationResult.Success, result); } - - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void MustNotAllowRepeating() - { - sut.Repeat(); - } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/ClipboardOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/ClipboardOperationTests.cs index 1fd710d2..638eafab 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/ClipboardOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/ClipboardOperationTests.cs @@ -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(); - } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/ConfigurationOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/ConfigurationOperationTests.cs index 8e274b41..4eb35ee8 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/ConfigurationOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/ConfigurationOperationTests.cs @@ -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(); - } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/DisplayMonitorOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/DisplayMonitorOperationTests.cs index ac5d3045..a35e99bf 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/DisplayMonitorOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/DisplayMonitorOperationTests.cs @@ -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(); - } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/KeyboardInterceptorOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/KeyboardInterceptorOperationTests.cs index b5e74ece..b6f399c7 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/KeyboardInterceptorOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/KeyboardInterceptorOperationTests.cs @@ -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()), Times.Once); } - - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void MustNotAllowRepeating() - { - sut.Repeat(); - } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/MouseInterceptorOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/MouseInterceptorOperationTests.cs index 22bdb47b..1f8815b8 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/MouseInterceptorOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/MouseInterceptorOperationTests.cs @@ -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()), Times.Once); } - - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void MustNotAllowRepeating() - { - sut.Repeat(); - } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/ProcessMonitorOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/ProcessMonitorOperationTests.cs index 13b3da5a..8a3c5259 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/ProcessMonitorOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/ProcessMonitorOperationTests.cs @@ -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(); - } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/RuntimeConnectionOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/RuntimeConnectionOperationTests.cs index 0311cff0..b0d3f4c9 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/RuntimeConnectionOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/RuntimeConnectionOperationTests.cs @@ -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(); - } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/TaskbarOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/TaskbarOperationTests.cs index 97590b21..7c7a954c 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/TaskbarOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/TaskbarOperationTests.cs @@ -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(); - } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/WindowMonitorOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/WindowMonitorOperationTests.cs index 1fac8d16..9b9c59b7 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/WindowMonitorOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/WindowMonitorOperationTests.cs @@ -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(); - } } } diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs index 1fb6b630..c288dd93 100644 --- a/SafeExamBrowser.Client/ClientController.cs +++ b/SafeExamBrowser.Client/ClientController.cs @@ -140,7 +140,7 @@ namespace SafeExamBrowser.Client DeregisterEvents(); - var success = operations.TryRevert(); + var success = operations.TryRevert() == OperationResult.Success; if (success) { diff --git a/SafeExamBrowser.Client/Operations/BrowserOperation.cs b/SafeExamBrowser.Client/Operations/BrowserOperation.cs index 1c89e6e2..2e30cbe7 100644 --- a/SafeExamBrowser.Client/Operations/BrowserOperation.cs +++ b/SafeExamBrowser.Client/Operations/BrowserOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Client/Operations/ClientHostDisconnectionOperation.cs b/SafeExamBrowser.Client/Operations/ClientHostDisconnectionOperation.cs index 82ff3b42..c973b5c9 100644 --- a/SafeExamBrowser.Client/Operations/ClientHostDisconnectionOperation.cs +++ b/SafeExamBrowser.Client/Operations/ClientHostDisconnectionOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Client/Operations/ClipboardOperation.cs b/SafeExamBrowser.Client/Operations/ClipboardOperation.cs index 15931606..a70dbd8a 100644 --- a/SafeExamBrowser.Client/Operations/ClipboardOperation.cs +++ b/SafeExamBrowser.Client/Operations/ClipboardOperation.cs @@ -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() diff --git a/SafeExamBrowser.Client/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Client/Operations/ConfigurationOperation.cs index 48bc5260..52b51a08 100644 --- a/SafeExamBrowser.Client/Operations/ConfigurationOperation.cs +++ b/SafeExamBrowser.Client/Operations/ConfigurationOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs b/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs index 4594f351..4fe33dbc 100644 --- a/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs +++ b/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Client/Operations/KeyboardInterceptorOperation.cs b/SafeExamBrowser.Client/Operations/KeyboardInterceptorOperation.cs index 52eea6ea..52fbeb34 100644 --- a/SafeExamBrowser.Client/Operations/KeyboardInterceptorOperation.cs +++ b/SafeExamBrowser.Client/Operations/KeyboardInterceptorOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Client/Operations/MouseInterceptorOperation.cs b/SafeExamBrowser.Client/Operations/MouseInterceptorOperation.cs index 67ac3df7..71c85707 100644 --- a/SafeExamBrowser.Client/Operations/MouseInterceptorOperation.cs +++ b/SafeExamBrowser.Client/Operations/MouseInterceptorOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Client/Operations/ProcessMonitorOperation.cs b/SafeExamBrowser.Client/Operations/ProcessMonitorOperation.cs index b98bb42c..7bd2d608 100644 --- a/SafeExamBrowser.Client/Operations/ProcessMonitorOperation.cs +++ b/SafeExamBrowser.Client/Operations/ProcessMonitorOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs b/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs index 799bf05b..d66c2182 100644 --- a/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs +++ b/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Client/Operations/TaskbarOperation.cs b/SafeExamBrowser.Client/Operations/TaskbarOperation.cs index d0af78bb..cbd2da9b 100644 --- a/SafeExamBrowser.Client/Operations/TaskbarOperation.cs +++ b/SafeExamBrowser.Client/Operations/TaskbarOperation.cs @@ -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() diff --git a/SafeExamBrowser.Client/Operations/WindowMonitorOperation.cs b/SafeExamBrowser.Client/Operations/WindowMonitorOperation.cs index 8e1baf34..f043cf80 100644 --- a/SafeExamBrowser.Client/Operations/WindowMonitorOperation.cs +++ b/SafeExamBrowser.Client/Operations/WindowMonitorOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Configuration/ConfigurationRepository.cs b/SafeExamBrowser.Configuration/ConfigurationRepository.cs index af18ef18..c6381bcd 100644 --- a/SafeExamBrowser.Configuration/ConfigurationRepository.cs +++ b/SafeExamBrowser.Configuration/ConfigurationRepository.cs @@ -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() }; diff --git a/SafeExamBrowser.Configuration/SessionData.cs b/SafeExamBrowser.Configuration/SessionData.cs index 46059f64..c256d342 100644 --- a/SafeExamBrowser.Configuration/SessionData.cs +++ b/SafeExamBrowser.Configuration/SessionData.cs @@ -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; } } } diff --git a/SafeExamBrowser.Contracts/Configuration/ISessionData.cs b/SafeExamBrowser.Contracts/Configuration/ISessionData.cs index 1abc441d..5b7a27b0 100644 --- a/SafeExamBrowser.Contracts/Configuration/ISessionData.cs +++ b/SafeExamBrowser.Contracts/Configuration/ISessionData.cs @@ -13,7 +13,7 @@ using SafeExamBrowser.Contracts.WindowsApi; namespace SafeExamBrowser.Contracts.Configuration { /// - /// Defines all session-related (configuration) data. + /// Holds all session-related configuration and runtime data. /// public interface ISessionData { @@ -32,6 +32,16 @@ namespace SafeExamBrowser.Contracts.Configuration /// Guid Id { get; } + /// + /// The new desktop, if is active for this session. + /// + IDesktop NewDesktop { get; set; } + + /// + /// The original desktop, if is active for this session. + /// + IDesktop OriginalDesktop { get; set; } + /// /// The startup token used by the client and runtime components for initial authentication. /// diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/IOperation.cs b/SafeExamBrowser.Contracts/Core/OperationModel/IOperation.cs index dad3186c..52ee713a 100644 --- a/SafeExamBrowser.Contracts/Core/OperationModel/IOperation.cs +++ b/SafeExamBrowser.Contracts/Core/OperationModel/IOperation.cs @@ -31,13 +31,8 @@ namespace SafeExamBrowser.Contracts.Core.OperationModel OperationResult Perform(); /// - /// Repeats the operation. + /// Reverts all changes made when executing the operation. /// - OperationResult Repeat(); - - /// - /// Reverts all changes which were made when executing the operation. - /// - void Revert(); + OperationResult Revert(); } } diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/IOperationSequence.cs b/SafeExamBrowser.Contracts/Core/OperationModel/IOperationSequence.cs index a1202c47..3a18df05 100644 --- a/SafeExamBrowser.Contracts/Core/OperationModel/IOperationSequence.cs +++ b/SafeExamBrowser.Contracts/Core/OperationModel/IOperationSequence.cs @@ -11,14 +11,13 @@ using SafeExamBrowser.Contracts.Core.OperationModel.Events; namespace SafeExamBrowser.Contracts.Core.OperationModel { /// - /// A sequence of s which can be used for sequential procedures, e.g. the initialization & finalization of + /// A sequence of which can be used for sequential procedures, e.g. the initialization & 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: /// /// : A -> B -> C -> D. - /// : A -> B -> C -> D. /// : D -> C -> B -> A. /// public interface IOperationSequence @@ -45,15 +44,9 @@ namespace SafeExamBrowser.Contracts.Core.OperationModel OperationResult TryPerform(); /// - /// 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. /// - OperationResult TryRepeat(); - - /// - /// Tries to revert the operations of this sequence. Returns true if all operations were reverted without errors, - /// otherwise false. The reversion of all operations will continue, even if one or multiple operations fail. - /// - bool TryRevert(); + OperationResult TryRevert(); } } diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/IRepeatableOperation.cs b/SafeExamBrowser.Contracts/Core/OperationModel/IRepeatableOperation.cs new file mode 100644 index 00000000..0d0f72ff --- /dev/null +++ b/SafeExamBrowser.Contracts/Core/OperationModel/IRepeatableOperation.cs @@ -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 +{ + /// + /// Defines an operation which can be executed multiple times as part of an . + /// + public interface IRepeatableOperation : IOperation + { + /// + /// Repeats the operation. + /// + OperationResult Repeat(); + } +} diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/IRepeatableOperationSequence.cs b/SafeExamBrowser.Contracts/Core/OperationModel/IRepeatableOperationSequence.cs new file mode 100644 index 00000000..2e2daab6 --- /dev/null +++ b/SafeExamBrowser.Contracts/Core/OperationModel/IRepeatableOperationSequence.cs @@ -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 +{ + /// + /// A sequence of which can be used for repeatable sequential procedures. + /// + /// Exemplary execution order for a sequence initialized with operations A, B, C, D: + /// + /// : A -> B -> C -> D. + /// + public interface IRepeatableOperationSequence : IOperationSequence + { + /// + /// 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. + /// + OperationResult TryRepeat(); + } +} diff --git a/SafeExamBrowser.Contracts/Core/OperationModel/OperationResult.cs b/SafeExamBrowser.Contracts/Core/OperationModel/OperationResult.cs index de69ec60..3ea84637 100644 --- a/SafeExamBrowser.Contracts/Core/OperationModel/OperationResult.cs +++ b/SafeExamBrowser.Contracts/Core/OperationModel/OperationResult.cs @@ -9,7 +9,7 @@ namespace SafeExamBrowser.Contracts.Core.OperationModel { /// - /// Defines the result of the sequential execution of s (as part of an ). + /// Defines the operation result of the execution of an resp. . /// public enum OperationResult { diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index 2103c185..28401664 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -63,6 +63,8 @@ + + diff --git a/SafeExamBrowser.Core.UnitTests/OperationModel/OperationSequenceTests.cs b/SafeExamBrowser.Core.UnitTests/OperationModel/OperationSequenceTests.cs index 7070eab3..ea17bfcf 100644 --- a/SafeExamBrowser.Core.UnitTests/OperationModel/OperationSequenceTests.cs +++ b/SafeExamBrowser.Core.UnitTests/OperationModel/OperationSequenceTests.cs @@ -303,159 +303,6 @@ namespace SafeExamBrowser.Core.UnitTests.OperationModel #endregion - #region Repeat Tests - - [TestMethod] - public void MustCorrectlyAbortRepeat() - { - var operationA = new Mock(); - var operationB = new Mock(); - var operationC = new Mock(); - var operations = new Queue(); - - 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(); - var operationB = new Mock(); - var operationC = new Mock(); - var operations = new Queue(); - - 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(); - var operationB = new Mock(); - var operationC = new Mock(); - var operations = new Queue(); - - 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(); - var operationB = new Mock(); - var operationC = new Mock(); - var operationD = new Mock(); - var operations = new Queue(); - - operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success); - operationB.Setup(o => o.Repeat()).Returns(OperationResult.Success); - operationC.Setup(o => o.Repeat()).Throws(); - - 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()); - var result = sut.TryRepeat(); - - Assert.AreEqual(OperationResult.Success, result); - } - - [TestMethod] - public void MustSucceedRepeatingWithoutCallingPerform() - { - var sut = new OperationSequence(loggerMock.Object, new Queue()); - var result = sut.TryRepeat(); - - Assert.AreEqual(OperationResult.Success, result); - } - - [TestMethod] - public void MustNotFailInCaseOfUnexpectedErrorWhenRepeating() - { - var sut = new OperationSequence(loggerMock.Object, new Queue()); - - 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(); 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(); 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()); 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()); - 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 diff --git a/SafeExamBrowser.Core.UnitTests/OperationModel/RepeatableOperationSequenceTests.cs b/SafeExamBrowser.Core.UnitTests/OperationModel/RepeatableOperationSequenceTests.cs new file mode 100644 index 00000000..9621ca6e --- /dev/null +++ b/SafeExamBrowser.Core.UnitTests/OperationModel/RepeatableOperationSequenceTests.cs @@ -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(); + // var operationB = new Mock(); + // var operationC = new Mock(); + // var operations = new Queue(); + + // 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(); + // var operationB = new Mock(); + // var operationC = new Mock(); + // var operations = new Queue(); + + // 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(); + // var operationB = new Mock(); + // var operationC = new Mock(); + // var operations = new Queue(); + + // 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(); + // var operationB = new Mock(); + // var operationC = new Mock(); + // var operationD = new Mock(); + // var operations = new Queue(); + + // operationA.Setup(o => o.Repeat()).Returns(OperationResult.Success); + // operationB.Setup(o => o.Repeat()).Returns(OperationResult.Success); + // operationC.Setup(o => o.Repeat()).Throws(); + + // 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()); + // var result = sut.TryRepeat(); + + // Assert.AreEqual(OperationResult.Success, result); + //} + + //[TestMethod] + //public void MustSucceedRepeatingWithoutCallingPerform() + //{ + // var sut = new OperationSequence(loggerMock.Object, new Queue()); + // var result = sut.TryRepeat(); + + // Assert.AreEqual(OperationResult.Success, result); + //} + + //[TestMethod] + //public void MustNotFailInCaseOfUnexpectedErrorWhenRepeating() + //{ + // var sut = new OperationSequence(loggerMock.Object, new Queue()); + + // sut.ProgressChanged += (args) => throw new Exception(); + + // var result = sut.TryRepeat(); + + // Assert.AreEqual(OperationResult.Failed, result); + //} + } +} diff --git a/SafeExamBrowser.Core.UnitTests/Operations/DelegateOperationTests.cs b/SafeExamBrowser.Core.UnitTests/Operations/DelegateOperationTests.cs index 83744cd8..4f82db4a 100644 --- a/SafeExamBrowser.Core.UnitTests/Operations/DelegateOperationTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Operations/DelegateOperationTests.cs @@ -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); } } } diff --git a/SafeExamBrowser.Core.UnitTests/Operations/I18nOperationTests.cs b/SafeExamBrowser.Core.UnitTests/Operations/I18nOperationTests.cs index fccadad1..8d012106 100644 --- a/SafeExamBrowser.Core.UnitTests/Operations/I18nOperationTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Operations/I18nOperationTests.cs @@ -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(); - } } } diff --git a/SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs b/SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs index ed33a167..3ce2fcbd 100644 --- a/SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Operations/LazyInitializationOperationTests.cs @@ -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); } } diff --git a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj index d79a245b..603f88c6 100644 --- a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj +++ b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj @@ -79,6 +79,7 @@ + diff --git a/SafeExamBrowser.Core/OperationModel/OperationSequence.cs b/SafeExamBrowser.Core/OperationModel/OperationSequence.cs index c7511d08..feac7850 100644 --- a/SafeExamBrowser.Core/OperationModel/OperationSequence.cs +++ b/SafeExamBrowser.Core/OperationModel/OperationSequence.cs @@ -20,9 +20,9 @@ namespace SafeExamBrowser.Core.OperationModel /// public class OperationSequence : IOperationSequence { - private ILogger logger; - private Queue operations = new Queue(); - private Stack stack = new Stack(); + protected ILogger logger; + protected Queue operations = new Queue(); + protected Stack stack = new Stack(); public event ActionRequiredEventHandler ActionRequired { @@ -44,7 +44,7 @@ namespace SafeExamBrowser.Core.OperationModel this.operations = new Queue(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); } } } diff --git a/SafeExamBrowser.Core/OperationModel/RepeatableOperationSequence.cs b/SafeExamBrowser.Core/OperationModel/RepeatableOperationSequence.cs new file mode 100644 index 00000000..c24803d8 --- /dev/null +++ b/SafeExamBrowser.Core/OperationModel/RepeatableOperationSequence.cs @@ -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 +{ + /// + /// Default implementation of the . + /// + public class RepeatableOperationSequence : OperationSequence, IRepeatableOperationSequence + { + private new Queue operations; + + public RepeatableOperationSequence(ILogger logger, Queue operations) : base(logger, new Queue(operations)) + { + this.operations = new Queue(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; + } + } +} diff --git a/SafeExamBrowser.Core/Operations/CommunicationHostOperation.cs b/SafeExamBrowser.Core/Operations/CommunicationHostOperation.cs index 5ab31a19..9ce27e3f 100644 --- a/SafeExamBrowser.Core/Operations/CommunicationHostOperation.cs +++ b/SafeExamBrowser.Core/Operations/CommunicationHostOperation.cs @@ -18,7 +18,7 @@ namespace SafeExamBrowser.Core.Operations /// An operation to handle the lifetime of an . The host is started during , /// stopped and restarted during (if not running) and stopped during . /// - 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; } } } diff --git a/SafeExamBrowser.Core/Operations/DelegateOperation.cs b/SafeExamBrowser.Core/Operations/DelegateOperation.cs index b77ff039..39580a87 100644 --- a/SafeExamBrowser.Core/Operations/DelegateOperation.cs +++ b/SafeExamBrowser.Core/Operations/DelegateOperation.cs @@ -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 /// would be an unnecessary overhead. /// - 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; } } } diff --git a/SafeExamBrowser.Core/Operations/I18nOperation.cs b/SafeExamBrowser.Core/Operations/I18nOperation.cs index e434edc9..5f53eaa5 100644 --- a/SafeExamBrowser.Core/Operations/I18nOperation.cs +++ b/SafeExamBrowser.Core/Operations/I18nOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs b/SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs index 949b11b2..8c3467f9 100644 --- a/SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs +++ b/SafeExamBrowser.Core/Operations/LazyInitializationOperation.cs @@ -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(); } } } diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj index d5e2dcc1..41a9c093 100644 --- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj +++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj @@ -55,6 +55,7 @@ + diff --git a/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs b/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs index b3eae68e..cca5ebcc 100644 --- a/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs +++ b/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs @@ -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; diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/ClientOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/ClientOperationTests.cs index f2eb43b0..9c0e4f8b 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/ClientOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Operations/ClientOperationTests.cs @@ -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(true, response); - - process.SetupGet(p => p.Id).Returns(response.ProcessId); - processFactory.Setup(f => f.StartNew(It.IsAny(), It.IsAny())).Returns(process.Object).Callback(clientReady); - proxy.Setup(p => p.RequestAuthentication()).Returns(communication); - proxy.Setup(p => p.Connect(It.IsAny(), 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(true, response); + + process.SetupGet(p => p.Id).Returns(response.ProcessId); + processFactory.Setup(f => f.StartNew(It.IsAny(), It.IsAny())).Returns(process.Object).Callback(clientReady); + proxy.Setup(p => p.RequestAuthentication()).Returns(communication); + proxy.Setup(p => p.Connect(It.IsAny(), 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() { diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/ClientTerminationOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/ClientTerminationOperationTests.cs deleted file mode 100644 index ba2dc4a9..00000000 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/ClientTerminationOperationTests.cs +++ /dev/null @@ -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 configuration; - private Mock proxy; - private Mock logger; - private Mock process; - private Mock processFactory; - private Mock proxyFactory; - private Mock runtimeHost; - private Mock session; - private ClientTerminationOperation sut; - - [TestInitialize] - public void Initialize() - { - appConfig = new AppConfig(); - configuration = new Mock(); - clientReady = new Action(() => runtimeHost.Raise(h => h.ClientReady += null)); - logger = new Mock(); - process = new Mock(); - processFactory = new Mock(); - proxy = new Mock(); - proxyFactory = new Mock(); - runtimeHost = new Mock(); - session = new Mock(); - - configuration.SetupGet(c => c.CurrentSession).Returns(session.Object); - configuration.SetupGet(c => c.AppConfig).Returns(appConfig); - proxyFactory.Setup(f => f.CreateClientProxy(It.IsAny())).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); - } - } -} diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/KioskModeOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/KioskModeOperationTests.cs index 9ac349ac..889b4f96 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/KioskModeOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Operations/KioskModeOperationTests.cs @@ -90,8 +90,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void MustCorrectlyRevertCreateNewDesktop() { - var originalDesktop = new Mock(); var newDesktop = new Mock(); + var originalDesktop = new Mock(); 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(); var newDesktop = new Mock(); + var originalDesktop = new Mock(); desktopFactory.Setup(f => f.GetCurrent()).Returns(originalDesktop.Object); desktopFactory.Setup(f => f.CreateNew(It.IsAny())).Returns(newDesktop.Object); @@ -192,8 +192,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void MustNotReinitializeCreateNewDesktopWhenRepeating() { - var originalDesktop = new Mock(); var newDesktop = new Mock(); + var originalDesktop = new Mock(); settings.KioskMode = KioskMode.CreateNewDesktop; diff --git a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj index 3115bdf3..2a4f3ecc 100644 --- a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj +++ b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj @@ -83,7 +83,6 @@ - diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index d86b3179..6588e256 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -66,20 +66,21 @@ namespace SafeExamBrowser.Runtime var uiFactory = new UserInterfaceFactory(text); var bootstrapOperations = new Queue(); - var sessionOperations = new Queue(); + var sessionOperations = new Queue(); 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); } diff --git a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs index 490b6a64..83632123 100644 --- a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs @@ -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; diff --git a/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs b/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs index 7a67ba5b..af3d9e0d 100644 --- a/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs @@ -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; } } } diff --git a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs index 1ed1b7ea..b0f6e9fa 100644 --- a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs @@ -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) diff --git a/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs b/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs index 5f5fb871..0f6a3361 100644 --- a/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs @@ -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 { diff --git a/SafeExamBrowser.Runtime/Operations/KioskModeTerminationOperation.cs b/SafeExamBrowser.Runtime/Operations/KioskModeTerminationOperation.cs new file mode 100644 index 00000000..09f26bae --- /dev/null +++ b/SafeExamBrowser.Runtime/Operations/KioskModeTerminationOperation.cs @@ -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; + } + } +} diff --git a/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs b/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs index 85513736..92aac37a 100644 --- a/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs @@ -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() diff --git a/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs b/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs index d71180fe..4c7a0a48 100644 --- a/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs @@ -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() diff --git a/SafeExamBrowser.Runtime/RuntimeController.cs b/SafeExamBrowser.Runtime/RuntimeController.cs index 81b8afb4..0c3178b1 100644 --- a/SafeExamBrowser.Runtime/RuntimeController.cs +++ b/SafeExamBrowser.Runtime/RuntimeController.cs @@ -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(); + } + } } } diff --git a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj index 158bf775..71bf3f6c 100644 --- a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj +++ b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj @@ -92,6 +92,7 @@ +