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);
}