diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj index 1f7bbd4..3d2726a 100644 --- a/Nuclex.Support.csproj +++ b/Nuclex.Support.csproj @@ -156,15 +156,26 @@ PluginRepository.cs - + + AbortedException.cs + + + Operation.cs + OperationQueue.cs + + ThreadCallbackOperation.cs + + + ThreadOperation.cs + Shared.cs diff --git a/Source/Collections/ReadOnlyCollection.Test.cs b/Source/Collections/ReadOnlyCollection.Test.cs index 15e1309..255dfbf 100644 --- a/Source/Collections/ReadOnlyCollection.Test.cs +++ b/Source/Collections/ReadOnlyCollection.Test.cs @@ -145,7 +145,7 @@ namespace Nuclex.Support.Collections { if(!(testCollection as ICollection).IsSynchronized) { lock((testCollection as ICollection).SyncRoot) { - int count = testCollection.Count; + Assert.AreEqual(0, testCollection.Count); } } } diff --git a/Source/Collections/ReadOnlyDictionary.Test.cs b/Source/Collections/ReadOnlyDictionary.Test.cs index f109d01..755b0e9 100644 --- a/Source/Collections/ReadOnlyDictionary.Test.cs +++ b/Source/Collections/ReadOnlyDictionary.Test.cs @@ -180,6 +180,7 @@ namespace Nuclex.Support.Collections { ReadOnlyDictionary testDictionary = makeReadOnly(numbers); string numberName = testDictionary[24]; + Console.WriteLine(numberName); } /// @@ -423,7 +424,7 @@ namespace Nuclex.Support.Collections { if(!(testDictionary as ICollection).IsSynchronized) { lock((testDictionary as ICollection).SyncRoot) { - int count = testDictionary.Count; + Assert.AreEqual(numbers.Count, testDictionary.Count); } } } diff --git a/Source/Collections/ReadOnlyList.Test.cs b/Source/Collections/ReadOnlyList.Test.cs index 6b4b84f..e001cf1 100644 --- a/Source/Collections/ReadOnlyList.Test.cs +++ b/Source/Collections/ReadOnlyList.Test.cs @@ -345,7 +345,7 @@ namespace Nuclex.Support.Collections { if(!(testList as ICollection).IsSynchronized) { lock((testList as ICollection).SyncRoot) { - int count = testList.Count; + Assert.AreEqual(0, testList.Count); } } } diff --git a/Source/Collections/RingMemoryStream.Test.cs b/Source/Collections/RingMemoryStream.Test.cs index d71794d..a756211 100644 --- a/Source/Collections/RingMemoryStream.Test.cs +++ b/Source/Collections/RingMemoryStream.Test.cs @@ -241,6 +241,7 @@ namespace Nuclex.Support.Collections { [Test, ExpectedException(typeof(NotSupportedException))] public void TestThrowOnRetrievePosition() { long position = new RingMemoryStream(10).Position; + Console.WriteLine(position.ToString()); } /// diff --git a/Source/Collections/TransformingReadOnlyCollection.Test.cs b/Source/Collections/TransformingReadOnlyCollection.Test.cs index 9f6a8c1..7b665c6 100644 --- a/Source/Collections/TransformingReadOnlyCollection.Test.cs +++ b/Source/Collections/TransformingReadOnlyCollection.Test.cs @@ -439,7 +439,7 @@ namespace Nuclex.Support.Collections { if(!(testCollection as ICollection).IsSynchronized) { lock((testCollection as ICollection).SyncRoot) { - int count = testCollection.Count; + Assert.AreEqual(0, testCollection.Count); } } } diff --git a/Source/Licensing/LicenseKey.Test.cs b/Source/Licensing/LicenseKey.Test.cs index 0e72b87..044bb36 100644 --- a/Source/Licensing/LicenseKey.Test.cs +++ b/Source/Licensing/LicenseKey.Test.cs @@ -109,6 +109,7 @@ namespace Nuclex.Support.Licensing { public void TestGetByIndexerWithInvalidIndex() { LicenseKey key = new LicenseKey(); int indexMinusOne = key[-1]; + Console.WriteLine(indexMinusOne.ToString()); } /// diff --git a/Source/Parsing/CommandLineParser.Test.cs b/Source/Parsing/CommandLineParser.Test.cs index c14cef4..14b78b1 100644 --- a/Source/Parsing/CommandLineParser.Test.cs +++ b/Source/Parsing/CommandLineParser.Test.cs @@ -123,6 +123,7 @@ namespace Nuclex.Support.Parsing { [Test] public void TestStringConstructorWithUnfinishedAssignment() { CommandLineParser parser = new CommandLineParser("--hello= --world="); + Assert.AreEqual(0, parser.Values.Count); } } diff --git a/Source/Scheduling/AbortedException.Test.cs b/Source/Scheduling/AbortedException.Test.cs index d72f1e2..cfa2400 100644 --- a/Source/Scheduling/AbortedException.Test.cs +++ b/Source/Scheduling/AbortedException.Test.cs @@ -42,6 +42,7 @@ namespace Nuclex.Support.Scheduling { AbortedException testException = new AbortedException(); string testExceptionString = testException.ToString(); + Assert.IsNotNull(testExceptionString); } /// diff --git a/Source/Scheduling/Operation.Test.cs b/Source/Scheduling/Operation.Test.cs new file mode 100644 index 0000000..3c7391c --- /dev/null +++ b/Source/Scheduling/Operation.Test.cs @@ -0,0 +1,69 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2008 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; + +#if UNITTEST + +using NUnit.Framework; +using NMock2; + +namespace Nuclex.Support.Scheduling { + + /// Unit Test for the operation class + [TestFixture] + public class OperationTest { + + #region class TestOperation + + /// Dummy operation used to run the unit tests + private class TestOperation : Operation { + + /// Launches the background operation + public override void Start() { + // This could become a race condition of this code would be used in a fashion + // different than what current unit tests do with it + if(!base.Ended) { + OnAsyncEnded(); + } + } + + } + + #endregion // class TestOperation + + /// Tests whether operations can be started + [Test] + public void TestOperationStarting() { + TestOperation myOperation = new TestOperation(); + + Assert.IsFalse(myOperation.Ended); + myOperation.Start(); + Assert.IsTrue(myOperation.Ended); + } + + } + +} // namespace Nuclex.Support.Scheduling + +#endif // UNITTEST diff --git a/Source/Scheduling/OperationQueue.Test.cs b/Source/Scheduling/OperationQueue.Test.cs index 01a4a61..d410d70 100644 --- a/Source/Scheduling/OperationQueue.Test.cs +++ b/Source/Scheduling/OperationQueue.Test.cs @@ -209,6 +209,133 @@ namespace Nuclex.Support.Scheduling { this.mockery.VerifyAllExpectationsHaveBeenMet(); } + /// + /// Validates that the queue executes operations sequentially and honors the weights + /// assigned to the individual operations + /// + [Test] + public void TestWeightedSequentialExecution() { + TestOperation operation1 = new TestOperation(); + TestOperation operation2 = new TestOperation(); + + OperationQueue testQueueOperation = + new OperationQueue( + new WeightedTransaction[] { + new WeightedTransaction(operation1, 0.5f), + new WeightedTransaction(operation2, 2.0f) + } + ); + + IOperationQueueSubscriber mockedSubscriber = mockSubscriber(testQueueOperation); + + testQueueOperation.Start(); + + Expect.Once.On(mockedSubscriber). + Method("ProgressChanged"). + With( + new Matcher[] { + new NMock2.Matchers.TypeMatcher(typeof(OperationQueue)), + new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.1f)) + } + ); + + operation1.ChangeProgress(0.5f); + + Expect.Once.On(mockedSubscriber). + Method("ProgressChanged"). + With( + new Matcher[] { + new NMock2.Matchers.TypeMatcher(typeof(OperationQueue)), + new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.2f)) + } + ); + + operation1.SetEnded(); + + this.mockery.VerifyAllExpectationsHaveBeenMet(); + } + + /// + /// Validates that the operation queue propagates the ended event once all contained + /// operations have completed + /// + [Test] + public void TestEndPropagation() { + TestOperation operation1 = new TestOperation(); + TestOperation operation2 = new TestOperation(); + + OperationQueue testQueueOperation = + new OperationQueue( + new TestOperation[] { + operation1, + operation2 + } + ); + + testQueueOperation.Start(); + + Assert.IsFalse(testQueueOperation.Ended); + operation1.SetEnded(); + Assert.IsFalse(testQueueOperation.Ended); + operation2.SetEnded(); + Assert.IsTrue(testQueueOperation.Ended); + + testQueueOperation.Join(); + } + + /// + /// Validates that the operation queue delivers an exception occuring in one of the + /// contained operations to the operation queue joiner + /// + [Test, ExpectedException(typeof(AbortedException))] + public void TestExceptionPropagation() { + TestOperation operation1 = new TestOperation(); + TestOperation operation2 = new TestOperation(); + + OperationQueue testQueueOperation = + new OperationQueue( + new TestOperation[] { + operation1, + operation2 + } + ); + + testQueueOperation.Start(); + + Assert.IsFalse(testQueueOperation.Ended); + operation1.SetEnded(); + Assert.IsFalse(testQueueOperation.Ended); + operation2.SetEnded(new AbortedException("Hello World")); + + testQueueOperation.Join(); + } + + /// + /// Ensures that the operation queue transparently wraps the child operations in + /// an observation wrapper that is not visible to an outside user + /// + [Test] + public void TestTransparentWrapping() { + WeightedTransaction operation1 = new WeightedTransaction( + new TestOperation() + ); + WeightedTransaction operation2 = new WeightedTransaction( + new TestOperation() + ); + + OperationQueue testQueueOperation = + new OperationQueue( + new WeightedTransaction[] { + operation1, + operation2 + } + ); + + // Order is important due to sequential execution! + Assert.AreSame(operation1, testQueueOperation.Children[0]); + Assert.AreSame(operation2, testQueueOperation.Children[1]); + } + /// Mocks a subscriber for the events of an operation /// Operation to mock an event subscriber for /// The mocked event subscriber diff --git a/Source/Scheduling/ThreadCallbackOperation.Test.cs b/Source/Scheduling/ThreadCallbackOperation.Test.cs new file mode 100644 index 0000000..9b49bc5 --- /dev/null +++ b/Source/Scheduling/ThreadCallbackOperation.Test.cs @@ -0,0 +1,150 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2008 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using System.Threading; + +#if UNITTEST + +using NUnit.Framework; +using NMock2; + +namespace Nuclex.Support.Scheduling { + + /// Unit Test for the thread callback operation class + [TestFixture] + public class ThreadCallbackOperationTest { + + #region class TestThreadOperation + + /// + /// Provides a test callback for unit testing the thread callback operation + /// + private class TestCallbackProvider { + + /// Method that can be invoked as a callback + public void Callback() { + this.called = true; + } + + /// Whether the callback has been called + public bool Called { + get { return this.called; } + } + + /// Set to true when the callback has been called + private bool called; + + } + + #endregion // class TestOperation + + /// Verifies that the default constructor for a thread operation works + [Test] + public void TestDefaultConstructor() { + ThreadCallbackOperation test = new ThreadCallbackOperation( + new ThreadStart(errorCallback) + ); + Assert.IsFalse(test.Ended); + } + + /// + /// Verifies that the threaded operation can execute in a thread pool thread + /// + [Test] + public void TestExecutionInThreadPool() { + TestCallbackProvider callbackProvider = new TestCallbackProvider(); + + ThreadCallbackOperation test = new ThreadCallbackOperation( + new ThreadStart(callbackProvider.Callback), true + ); + + Assert.IsFalse(test.Ended); + Assert.IsFalse(callbackProvider.Called); + test.Start(); + test.Join(); + Assert.IsTrue(test.Ended); + Assert.IsTrue(callbackProvider.Called); + } + + /// + /// Verifies that the threaded operation can execute in an explicit thread + /// + [Test] + public void TestExecutionInExplicitThread() { + TestCallbackProvider callbackProvider = new TestCallbackProvider(); + + ThreadCallbackOperation test = new ThreadCallbackOperation( + new ThreadStart(callbackProvider.Callback), false + ); + + Assert.IsFalse(test.Ended); + Assert.IsFalse(callbackProvider.Called); + test.Start(); + test.Join(); + Assert.IsTrue(test.Ended); + Assert.IsTrue(callbackProvider.Called); + } + + /// + /// Verifies that the threaded operation forwards an exception that occurred in + /// a thread pool thread. + /// + [Test, ExpectedException(typeof(AbortedException))] + public void TestForwardExceptionFromThreadPool() { + ThreadCallbackOperation test = new ThreadCallbackOperation( + new ThreadStart(errorCallback), false + ); + + Assert.IsFalse(test.Ended); + test.Start(); + test.Join(); + } + + /// + /// Verifies that the threaded operation forwards an exception that occurred in + /// an explicit thread. + /// + [Test, ExpectedException(typeof(AbortedException))] + public void TestForwardExceptionFromExplicitThread() { + ThreadCallbackOperation test = new ThreadCallbackOperation( + new ThreadStart(errorCallback), false + ); + + Assert.IsFalse(test.Ended); + test.Start(); + test.Join(); + } + + /// + /// Callback which throws an exception to simulate an error during callback execution + /// + private static void errorCallback() { + throw new AbortedException("Hello World"); + } + + } + +} // namespace Nuclex.Support.Scheduling + +#endif // UNITTEST diff --git a/Source/Scheduling/ThreadOperation.Test.cs b/Source/Scheduling/ThreadOperation.Test.cs new file mode 100644 index 0000000..e35456d --- /dev/null +++ b/Source/Scheduling/ThreadOperation.Test.cs @@ -0,0 +1,135 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2008 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; + +#if UNITTEST + +using NUnit.Framework; +using NMock2; + +namespace Nuclex.Support.Scheduling { + + /// Unit Test for the thread operation class + [TestFixture] + public class ThreadOperationTest { + + #region class TestThreadOperation + + /// Dummy operation used to run the unit tests + private class TestThreadOperation : ThreadOperation { + + /// Initializes a dummy operation + public TestThreadOperation() { } + + /// Initializes a dummy operation + /// Whether to use a ThreadPool thread. + public TestThreadOperation(bool useThreadPool) : base(useThreadPool) { } + + /// Contains the payload to be executed in the background thread + protected override void Execute() { } + + } + + #endregion // class TestOperation + + #region class FailingThreadOperation + + /// Dummy operation used to run the unit tests + private class FailingThreadOperation : ThreadOperation { + + /// Initializes a dummy operation + /// Whether to use a ThreadPool thread. + public FailingThreadOperation(bool useThreadPool) : base(useThreadPool) { } + + /// Contains the payload to be executed in the background thread + protected override void Execute() { + throw new AbortedException("Hello World"); + } + + } + + #endregion // class FailingThreadOperation + + /// Verifies that the default constructor for a thread operation works + [Test] + public void TestDefaultConstructor() { + TestThreadOperation test = new TestThreadOperation(); + Assert.IsFalse(test.Ended); + } + + /// + /// Verifies that the threaded operation can execute in a thread pool thread + /// + [Test] + public void TestExecutionInThreadPool() { + TestThreadOperation test = new TestThreadOperation(true); + Assert.IsFalse(test.Ended); + test.Start(); + test.Join(); + Assert.IsTrue(test.Ended); + } + + /// + /// Verifies that the threaded operation can execute in an explicit thread + /// + [Test] + public void TestExecutionInExplicitThread() { + TestThreadOperation test = new TestThreadOperation(false); + Assert.IsFalse(test.Ended); + test.Start(); + test.Join(); + Assert.IsTrue(test.Ended); + } + + /// + /// Verifies that the threaded operation forwards an exception that occurred in + /// a thread pool thread. + /// + [Test, ExpectedException(typeof(AbortedException))] + public void TestForwardExceptionFromThreadPool() { + FailingThreadOperation test = new FailingThreadOperation(true); + + Assert.IsFalse(test.Ended); + test.Start(); + test.Join(); + } + + /// + /// Verifies that the threaded operation forwards an exception that occurred in + /// an explicit thread. + /// + [Test, ExpectedException(typeof(AbortedException))] + public void TestForwardExceptionFromExplicitThread() { + FailingThreadOperation test = new FailingThreadOperation(false); + + Assert.IsFalse(test.Ended); + test.Start(); + test.Join(); + } + + } + +} // namespace Nuclex.Support.Scheduling + +#endif // UNITTEST diff --git a/Source/Scheduling/ThreadOperation.cs b/Source/Scheduling/ThreadOperation.cs index 22624b6..8bb7a5d 100644 --- a/Source/Scheduling/ThreadOperation.cs +++ b/Source/Scheduling/ThreadOperation.cs @@ -20,6 +20,7 @@ License along with this library using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; namespace Nuclex.Support.Scheduling { @@ -50,6 +51,11 @@ namespace Nuclex.Support.Scheduling { /// Launches the background operation public override void Start() { + Debug.Assert( + !Ended, + "Tried to Start an Operation again that has already ended", + "Operations cannot be re-run" + ); if(useThreadPool) { ThreadPool.QueueUserWorkItem(callMethod); } else { @@ -68,6 +74,11 @@ namespace Nuclex.Support.Scheduling { private void callMethod(object state) { try { Execute(); + Debug.Assert( + !Ended, + "Operation unexpectedly ended during Execute()", + "Do not call OnAsyncEnded() yourself when deriving from ThreadOperation" + ); } catch(Exception exception) { this.exception = exception; diff --git a/Source/Tracking/ProgressTracker.Test.cs b/Source/Tracking/ProgressTracker.Test.cs index 68c5cc8..9bd6e0a 100644 --- a/Source/Tracking/ProgressTracker.Test.cs +++ b/Source/Tracking/ProgressTracker.Test.cs @@ -311,6 +311,9 @@ namespace Nuclex.Support.Tracking { public void TestSoleEndedTransaction() { using(ProgressTracker tracker = new ProgressTracker()) { IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker); + + Expect.Never.On(mockedSubscriber).Method("IdleStateChanged").WithAnyArguments(); + Expect.Never.On(mockedSubscriber).Method("ProgressChanged").WithAnyArguments(); tracker.Track(Transaction.EndedDummy); }