diff --git a/SafeExamBrowser.Contracts/Behaviour/IShutdownController.cs b/SafeExamBrowser.Contracts/Behaviour/IShutdownController.cs index a10dc4a4..caa1d1df 100644 --- a/SafeExamBrowser.Contracts/Behaviour/IShutdownController.cs +++ b/SafeExamBrowser.Contracts/Behaviour/IShutdownController.cs @@ -15,6 +15,6 @@ namespace SafeExamBrowser.Contracts.Behaviour /// /// Reverts any changes performed during the startup or runtime and releases all used resources. /// - void FinalizeApplication(Stack operations); + void FinalizeApplication(Queue operations); } } diff --git a/SafeExamBrowser.Contracts/Behaviour/IStartupController.cs b/SafeExamBrowser.Contracts/Behaviour/IStartupController.cs index 5218c28b..9f0223b9 100644 --- a/SafeExamBrowser.Contracts/Behaviour/IStartupController.cs +++ b/SafeExamBrowser.Contracts/Behaviour/IStartupController.cs @@ -13,10 +13,9 @@ namespace SafeExamBrowser.Contracts.Behaviour public interface IStartupController { /// - /// Tries to initialize the application. Returns true if the initialization was successful, - /// false otherwise. All operations performed during the startup procedure will be registered - /// to the given out parameter. + /// Tries to initialize the application according to the given queue of operations. + /// Returns true if the initialization was successful, false otherwise. /// - bool TryInitializeApplication(out Stack operations); + bool TryInitializeApplication(Queue operations); } } diff --git a/SafeExamBrowser.Core.UnitTests/Behaviour/ShutdownControllerTests.cs b/SafeExamBrowser.Core.UnitTests/Behaviour/ShutdownControllerTests.cs new file mode 100644 index 00000000..f2ad03f9 --- /dev/null +++ b/SafeExamBrowser.Core.UnitTests/Behaviour/ShutdownControllerTests.cs @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017 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.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using SafeExamBrowser.Contracts.Behaviour; +using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.Monitoring; +using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.Core.Behaviour; + +namespace SafeExamBrowser.Core.UnitTests.Behaviour +{ + [TestClass] + public class ShutdownControllerTests + { + private Mock browserControllerMock; + private Mock browserInfoMock; + private Mock loggerMock; + private Mock messageBoxMock; + private Mock aboutInfoMock; + private Mock processMonitorMock; + private Mock settingsMock; + private Mock taskbarMock; + private Mock textMock; + private Mock uiFactoryMock; + private Mock workingAreaMock; + + private IShutdownController sut; + + [TestInitialize] + public void Initialize() + { + browserControllerMock = new Mock(); + browserInfoMock = new Mock(); + loggerMock = new Mock(); + messageBoxMock = new Mock(); + aboutInfoMock = new Mock(); + processMonitorMock = new Mock(); + settingsMock = new Mock(); + taskbarMock = new Mock(); + textMock = new Mock(); + uiFactoryMock = new Mock(); + workingAreaMock = new Mock(); + + uiFactoryMock.Setup(f => f.CreateSplashScreen(settingsMock.Object, textMock.Object)).Returns(new Mock().Object); + + sut = new ShutdownController( + loggerMock.Object, + messageBoxMock.Object, + processMonitorMock.Object, + settingsMock.Object, + textMock.Object, + uiFactoryMock.Object, + workingAreaMock.Object); + } + + [TestMethod] + public void MustRevertOperations() + { + var operationA = new Mock(); + var operationB = new Mock(); + var operationC = new Mock(); + var operations = new Queue(); + + operations.Enqueue(operationA.Object); + operations.Enqueue(operationB.Object); + operations.Enqueue(operationC.Object); + + sut.FinalizeApplication(operations); + + operationA.Verify(o => o.Revert(), Times.Once); + operationB.Verify(o => o.Revert(), Times.Once); + operationC.Verify(o => o.Revert(), Times.Once); + } + + [TestMethod] + public void MustRevertOperationsInSequence() + { + 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.Revert()).Callback(() => a = ++current); + operationB.Setup(o => o.Revert()).Callback(() => b = ++current); + operationC.Setup(o => o.Revert()).Callback(() => c = ++current); + + operations.Enqueue(operationA.Object); + operations.Enqueue(operationB.Object); + operations.Enqueue(operationC.Object); + + sut.FinalizeApplication(operations); + + Assert.IsTrue(a == 1); + Assert.IsTrue(b == 2); + Assert.IsTrue(c == 3); + } + + [TestMethod] + public void MustNotFailWithEmptyQueue() + { + sut.FinalizeApplication(new Queue()); + } + } +} diff --git a/SafeExamBrowser.Core.UnitTests/Behaviour/StartupControllerTests.cs b/SafeExamBrowser.Core.UnitTests/Behaviour/StartupControllerTests.cs new file mode 100644 index 00000000..281a1996 --- /dev/null +++ b/SafeExamBrowser.Core.UnitTests/Behaviour/StartupControllerTests.cs @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2017 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 Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using SafeExamBrowser.Contracts.Behaviour; +using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.Monitoring; +using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.Core.Behaviour; + +namespace SafeExamBrowser.Core.UnitTests.Behaviour +{ + [TestClass] + public class StartupControllerTests + { + private Mock browserControllerMock; + private Mock browserInfoMock; + private Mock loggerMock; + private Mock messageBoxMock; + private Mock aboutInfoMock; + private Mock processMonitorMock; + private Mock settingsMock; + private Mock taskbarMock; + private Mock textMock; + private Mock uiFactoryMock; + private Mock workingAreaMock; + + private IStartupController sut; + + [TestInitialize] + public void Initialize() + { + browserControllerMock = new Mock(); + browserInfoMock = new Mock(); + loggerMock = new Mock(); + messageBoxMock = new Mock(); + aboutInfoMock = new Mock(); + processMonitorMock = new Mock(); + settingsMock = new Mock(); + taskbarMock = new Mock(); + textMock = new Mock(); + uiFactoryMock = new Mock(); + workingAreaMock = new Mock(); + + uiFactoryMock.Setup(f => f.CreateSplashScreen(settingsMock.Object, textMock.Object)).Returns(new Mock().Object); + + sut = new StartupController( + browserControllerMock.Object, + browserInfoMock.Object, + loggerMock.Object, + messageBoxMock.Object, + aboutInfoMock.Object, + processMonitorMock.Object, + settingsMock.Object, + taskbarMock.Object, + textMock.Object, + uiFactoryMock.Object, + workingAreaMock.Object); + } + + [TestMethod] + public void MustPerformOperations() + { + var operationA = new Mock(); + var operationB = new Mock(); + var operationC = new Mock(); + var operations = new Queue(); + + operations.Enqueue(operationA.Object); + operations.Enqueue(operationB.Object); + operations.Enqueue(operationC.Object); + + var result = sut.TryInitializeApplication(operations); + + operationA.Verify(o => o.Perform(), Times.Once); + operationB.Verify(o => o.Perform(), Times.Once); + operationC.Verify(o => o.Perform(), Times.Once); + + Assert.IsTrue(result); + } + + [TestMethod] + public void MustPerformOperationsInSequence() + { + 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.Perform()).Callback(() => a = ++current); + operationB.Setup(o => o.Perform()).Callback(() => b = ++current); + operationC.Setup(o => o.Perform()).Callback(() => c = ++current); + + operations.Enqueue(operationA.Object); + operations.Enqueue(operationB.Object); + operations.Enqueue(operationC.Object); + + sut.TryInitializeApplication(operations); + + Assert.IsTrue(a == 1); + Assert.IsTrue(b == 2); + Assert.IsTrue(c == 3); + } + + [TestMethod] + public void MustRevertOperationsInCaseOfError() + { + var operationA = new Mock(); + var operationB = new Mock(); + var operationC = new Mock(); + var operationD = new Mock(); + var operations = new Queue(); + + operationC.Setup(o => o.Perform()).Throws(); + + operations.Enqueue(operationA.Object); + operations.Enqueue(operationB.Object); + operations.Enqueue(operationC.Object); + operations.Enqueue(operationD.Object); + + var result = sut.TryInitializeApplication(operations); + + operationA.Verify(o => o.Perform(), Times.Once); + operationA.Verify(o => o.Revert(), Times.Once); + operationB.Verify(o => o.Perform(), Times.Once); + operationB.Verify(o => o.Revert(), Times.Once); + operationC.Verify(o => o.Perform(), Times.Once); + operationC.Verify(o => o.Revert(), Times.Once); + operationD.Verify(o => o.Perform(), Times.Never); + operationD.Verify(o => o.Revert(), Times.Never); + + Assert.IsFalse(result); + } + + [TestMethod] + public void MustRevertOperationsInSequence() + { + int current = 0, a = 0, b = 0, c = 0, d = 0; + var operationA = new Mock(); + var operationB = new Mock(); + var operationC = new Mock(); + var operationD = new Mock(); + var operations = new Queue(); + + operationA.Setup(o => o.Revert()).Callback(() => a = ++current); + operationB.Setup(o => o.Revert()).Callback(() => b = ++current); + operationC.Setup(o => o.Revert()).Callback(() => c = ++current); + operationC.Setup(o => o.Perform()).Throws(); + operationD.Setup(o => o.Revert()).Callback(() => d = ++current); + + operations.Enqueue(operationA.Object); + operations.Enqueue(operationB.Object); + operations.Enqueue(operationC.Object); + operations.Enqueue(operationD.Object); + + sut.TryInitializeApplication(operations); + + Assert.IsTrue(d == 0); + Assert.IsTrue(c == 1); + Assert.IsTrue(b == 2); + Assert.IsTrue(a == 3); + } + + [TestMethod] + public void MustSucceedWithEmptyQueue() + { + var result = sut.TryInitializeApplication(new Queue()); + + Assert.IsTrue(result); + } + } +} diff --git a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj index 7233ac84..6d6a473c 100644 --- a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj +++ b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj @@ -54,6 +54,8 @@ + + diff --git a/SafeExamBrowser.Core/Behaviour/Operations/BrowserInitializationOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/BrowserInitializationOperation.cs index c474329c..c489ca08 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/BrowserInitializationOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/BrowserInitializationOperation.cs @@ -14,7 +14,7 @@ using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Core.Behaviour.Operations { - class BrowserInitializationOperation : IOperation + public class BrowserInitializationOperation : IOperation { private IApplicationController browserController; private IApplicationInfo browserInfo; diff --git a/SafeExamBrowser.Core/Behaviour/Operations/ProcessMonitoringOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/ProcessMonitoringOperation.cs index cba3de03..16c17e63 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/ProcessMonitoringOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/ProcessMonitoringOperation.cs @@ -14,7 +14,7 @@ using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Core.Behaviour.Operations { - class ProcessMonitoringOperation : IOperation + public class ProcessMonitoringOperation : IOperation { private ILogger logger; private IProcessMonitor processMonitor; diff --git a/SafeExamBrowser.Core/Behaviour/Operations/TaskbarInitializationOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/TaskbarInitializationOperation.cs index 1639a2f3..63552b82 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/TaskbarInitializationOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/TaskbarInitializationOperation.cs @@ -14,7 +14,7 @@ using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Core.Behaviour.Operations { - class TaskbarInitializationOperation : IOperation + public class TaskbarInitializationOperation : IOperation { private ILogger logger; private ITaskbar taskbar; diff --git a/SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs index fcda9b01..1f995241 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs @@ -15,7 +15,7 @@ using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.Core.Behaviour.Operations { - class WorkingAreaOperation : IOperation + public class WorkingAreaOperation : IOperation { private ILogger logger; private IProcessMonitor processMonitor; diff --git a/SafeExamBrowser.Core/Behaviour/ShutdownController.cs b/SafeExamBrowser.Core/Behaviour/ShutdownController.cs index 7b41aa68..e238cfa0 100644 --- a/SafeExamBrowser.Core/Behaviour/ShutdownController.cs +++ b/SafeExamBrowser.Core/Behaviour/ShutdownController.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using SafeExamBrowser.Contracts.Behaviour; using SafeExamBrowser.Contracts.Configuration; @@ -48,7 +47,7 @@ namespace SafeExamBrowser.Core.Behaviour this.workingArea = workingArea; } - public void FinalizeApplication(Stack operations) + public void FinalizeApplication(Queue operations) { try { @@ -63,17 +62,13 @@ namespace SafeExamBrowser.Core.Behaviour } } - private void RevertOperations(Stack operations) + private void RevertOperations(Queue operations) { - while (operations.Any()) + foreach (var operation in operations) { - var operation = operations.Pop(); - operation.SplashScreen = splashScreen; operation.Revert(); - splashScreen.Progress(); - // TODO: Remove! Thread.Sleep(250); } diff --git a/SafeExamBrowser.Core/Behaviour/StartupController.cs b/SafeExamBrowser.Core/Behaviour/StartupController.cs index 6bb2bec9..be13b0c0 100644 --- a/SafeExamBrowser.Core/Behaviour/StartupController.cs +++ b/SafeExamBrowser.Core/Behaviour/StartupController.cs @@ -16,7 +16,6 @@ using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.UserInterface; -using SafeExamBrowser.Core.Behaviour.Operations; namespace SafeExamBrowser.Core.Behaviour { @@ -35,7 +34,7 @@ namespace SafeExamBrowser.Core.Behaviour private IUiElementFactory uiFactory; private IWorkingArea workingArea; - private IEnumerable startupOperations; + private Stack stack = new Stack(); public StartupController( IApplicationController browserController, @@ -63,18 +62,14 @@ namespace SafeExamBrowser.Core.Behaviour this.workingArea = workingArea; } - public bool TryInitializeApplication(out Stack operations) + public bool TryInitializeApplication(Queue operations) { - operations = new Stack(); - try { - CreateStartupOperations(); - InitializeApplicationLog(); - InitializeSplashScreen(); + InitializeSplashScreen(operations.Count); - operations = PerformOperations(); + PerformOperations(operations); FinishInitialization(); @@ -83,20 +78,18 @@ namespace SafeExamBrowser.Core.Behaviour catch (Exception e) { LogAndShowException(e); - RevertOperations(operations); + RevertOperations(); FinishInitialization(false); return false; } } - private Stack PerformOperations() + private void PerformOperations(Queue operations) { - var operations = new Stack(); - - foreach (var operation in startupOperations) + foreach (var operation in operations) { - operations.Push(operation); + stack.Push(operation); operation.SplashScreen = splashScreen; operation.Perform(); @@ -106,15 +99,13 @@ namespace SafeExamBrowser.Core.Behaviour // TODO: Remove! Thread.Sleep(250); } - - return operations; } - private void RevertOperations(Stack operations) + private void RevertOperations() { - while (operations.Any()) + while (stack.Any()) { - var operation = operations.Pop(); + var operation = stack.Pop(); operation.Revert(); splashScreen.Regress(); @@ -124,17 +115,6 @@ namespace SafeExamBrowser.Core.Behaviour } } - private void CreateStartupOperations() - { - startupOperations = new IOperation[] - { - new ProcessMonitoringOperation(logger, processMonitor), - new WorkingAreaOperation(logger, processMonitor, taskbar, workingArea), - new TaskbarInitializationOperation(logger, aboutInfo, taskbar, uiFactory), - new BrowserInitializationOperation(browserController, browserInfo, logger, taskbar, uiFactory) - }; - } - private void InitializeApplicationLog() { var titleLine = $"/* {settings.ProgramTitle}, Version {settings.ProgramVersion}{Environment.NewLine}"; @@ -147,10 +127,10 @@ namespace SafeExamBrowser.Core.Behaviour logger.Info("--- Initiating startup procedure ---"); } - private void InitializeSplashScreen() + private void InitializeSplashScreen(int operationCount) { splashScreen = uiFactory.CreateSplashScreen(settings, text); - splashScreen.SetMaxProgress(startupOperations.Count()); + splashScreen.SetMaxProgress(operationCount); splashScreen.UpdateText(Key.SplashScreen_StartupProcedure); splashScreen.InvokeShow(); } diff --git a/SafeExamBrowser/App.cs b/SafeExamBrowser/App.cs index 6a295fb5..9cb14d80 100644 --- a/SafeExamBrowser/App.cs +++ b/SafeExamBrowser/App.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Windows; using SafeExamBrowser.Contracts.Behaviour; @@ -59,12 +60,12 @@ namespace SafeExamBrowser instances.BuildObjectGraph(); - var success = instances.StartupController.TryInitializeApplication(out Stack operations); + var success = instances.StartupController.TryInitializeApplication(instances.StartupOperations); if (success) { MainWindow = instances.Taskbar; - MainWindow.Closing += (o, args) => ShutdownApplication(operations); + MainWindow.Closing += (o, args) => ShutdownApplication(); MainWindow.Show(); } else @@ -73,8 +74,10 @@ namespace SafeExamBrowser } } - private void ShutdownApplication(Stack operations) + private void ShutdownApplication() { + var operations = new Queue(instances.StartupOperations.Reverse()); + MainWindow.Hide(); instances.ShutdownController.FinalizeApplication(operations); } diff --git a/SafeExamBrowser/CompositionRoot.cs b/SafeExamBrowser/CompositionRoot.cs index 6b0cea9a..761bd7b7 100644 --- a/SafeExamBrowser/CompositionRoot.cs +++ b/SafeExamBrowser/CompositionRoot.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System.Collections.Generic; using SafeExamBrowser.Browser; using SafeExamBrowser.Configuration; using SafeExamBrowser.Contracts.Behaviour; @@ -15,6 +16,7 @@ using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Core.Behaviour; +using SafeExamBrowser.Core.Behaviour.Operations; using SafeExamBrowser.Core.I18n; using SafeExamBrowser.Core.Logging; using SafeExamBrowser.Monitoring.Processes; @@ -38,6 +40,7 @@ namespace SafeExamBrowser public IShutdownController ShutdownController { get; private set; } public IStartupController StartupController { get; private set; } + public Queue StartupOperations { get; private set; } public Taskbar Taskbar { get; private set; } public void BuildObjectGraph() @@ -59,6 +62,12 @@ namespace SafeExamBrowser workingArea = new WorkingArea(new ModuleLogger(logger, typeof(WorkingArea))); ShutdownController = new ShutdownController(logger, messageBox, processMonitor, settings, text, uiFactory, workingArea); StartupController = new StartupController(browserController, browserInfo, logger, messageBox, aboutInfo, processMonitor, settings, Taskbar, text, uiFactory, workingArea); + + StartupOperations = new Queue(); + StartupOperations.Enqueue(new ProcessMonitoringOperation(logger, processMonitor)); + StartupOperations.Enqueue(new WorkingAreaOperation(logger, processMonitor, Taskbar, workingArea)); + StartupOperations.Enqueue(new TaskbarInitializationOperation(logger, aboutInfo, Taskbar, uiFactory)); + StartupOperations.Enqueue(new BrowserInitializationOperation(browserController, browserInfo, logger, Taskbar, uiFactory)); } } }