using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using CommonWFLibrary;
using System.Workflow.Runtime;
using System.Threading;
using System.Workflow.ComponentModel;
using System.IO;
using System.Diagnostics;
namespace CommonWFLibraryTest
{
/// <summary>
/// Test the long running WF activity
/// </summary>
[TestClass]
public class t_LongRunningActivityBase
{
public t_LongRunningActivityBase()
{
}
private TestContext testContextInstance;
/// <summary>
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///</summary>
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
}
#region Additional test attributes
//
// You can use the following additional attributes as you write your tests:
//
// Use ClassInitialize to run code before running the first test in the class
// [ClassInitialize()]
// public static void MyClassInitialize(TestContext testContext) { }
//
// Use ClassCleanup to run code after all tests in a class have run
// [ClassCleanup()]
// public static void MyClassCleanup() { }
//
// Use TestInitialize to run code before running each test
// [TestInitialize()]
// public void MyTestInitialize() { }
//
// Use TestCleanup to run code after each test has run
// [TestCleanup()]
// public void MyTestCleanup() { }
//
#endregion
class LRSimple : LongRunningActivityBase
{
public static int _result = 0;
[LongRunningMethod]
public static void Run()
{
_result = 10;
}
}
[TestMethod]
public void TestSimpleWF()
{
LRSimple._result = 0;
RunWFByType(typeof(LRSimple));
Assert.IsTrue(LRSimple._result == 10, "The static item for the activity method was not set -- was activity actually run!?");
}
class LRSimpleInherit : LRSimple
{
}
[TestMethod]
public void TestSimpleWFInherrited()
{
/// Make sure we can pick up trivial inherritance activities
LRSimpleInherit._result = 0;
RunWFByType(typeof(LRSimpleInherit));
Assert.IsTrue(LRSimpleInherit._result == 10, "The static item for the activity wasn't right!");
}
/// <summary>
/// Run a test, throw exceptions back to user.
/// </summary>
/// <param name="wfActivity"></param>
/// <param name="dict"></param>
private static void RunWFByType(Type wfActivity, Dictionary<string, object> dict)
{
using (WorkflowRuntime runtime = new WorkflowRuntime())
using (AutoResetEvent reset = new AutoResetEvent(false))
{
Exception exp = null;
runtime.WorkflowCompleted += delegate { reset.Set(); };
runtime.WorkflowTerminated += (o, args) => { exp = args.Exception; reset.Set(); };
LongRunningActivityBase.RegisterService(runtime);
runtime.StartRuntime();
WorkflowInstance instance;
instance = runtime.CreateWorkflow(wfActivity, dict);
instance.Start();
reset.WaitOne();
if (exp != null)
{
throw exp;
}
}
}
/// <summary>
/// Run a WF, with persistance. Once the WF goes into the idle state kill the runtime. Restart it, and then
/// see if the WF is restored or used correctly.
/// </summary>
/// <param name="wfActivity"></param>
/// <param name="dict"></param>
/// <param name="timeout"></param>
/// <param name="cache"></param>
/// <returns></returns>
private static bool RunWFByTypeWithPersistance(Type wfActivity, Dictionary<string, object> dict, int timeout, DirectoryInfo cache)
{
return RunWFByTypeWithPersistance(wfActivity, dict, timeout, cache, 1);
}
/// <summary>
/// Run a WF, with persistance. Once the WF goes into the idle state kill the runtime. We do this "n" times, and
/// then see if the WF is restored and used correctly.
/// </summary>
/// <param name="wfActivity"></param>
/// <param name="dict"></param>
/// <param name="timeout"></param>
/// <param name="cache"></param>
/// <param name="numberCrashToIdles"></param>
/// <returns></returns>
private static bool RunWFByTypeWithPersistance(Type wfActivity, Dictionary<string, object> dict, int timeout, DirectoryInfo cache, int numberCrashToIdles)
{
///
/// Crash the runtime n times.
///
bool start_wf = true;
bool has_entered_idle = false;
while (numberCrashToIdles >= 0)
{
Trace.WriteLine("Starting WF runtime iteration");
numberCrashToIdles = numberCrashToIdles - 1;
bool waitForComplete = numberCrashToIdles < 0;
///
/// Run until the WF enters the idle state. If it crashes, then, of course, we are outta here.
///
using (AutoResetEvent reset = new AutoResetEvent(false))
using (WorkflowRuntime runtime = new WorkflowRuntime())
{
try
{
Exception e = null;
bool completed = false;
if (!waitForComplete)
{
runtime.WorkflowIdled += (o, args) => { reset.Set(); has_entered_idle = true; };
}
runtime.WorkflowCompleted += (o, args) => { completed = true; reset.Set(); };
runtime.WorkflowTerminated += (o, args) => { e = args.Exception; reset.Set(); };
runtime.AddService(new FilePersistenceService(true, cache));
LongRunningActivityBinaryPersister lrp = new LongRunningActivityBinaryPersister(new FileInfo(cache.FullName + "\\longrunning"));
LongRunningActivityBase.RegisterService(runtime, obj => lrp.Save(obj), () => lrp.Restore());
runtime.StartRuntime();
///
/// If this is the first time through, then we should start the WF. Otherwise, it
/// should have been picked up by the persitancy service from the last run.
///
if (start_wf)
{
WorkflowInstance instance;
instance = runtime.CreateWorkflow(wfActivity, dict);
instance.Start();
start_wf = false;
}
///
/// Great. Now wait for it to finish. We expect the WF to enter
/// an idled state within 1 second. On the last one, waiting for something
/// real to happen, we expect that to happen in the timeout parameter passed
/// to us.
///
/// We shut down the runtime here so that we can step through the other statements in the
/// debugger!
///
int waittime = waitForComplete ? timeout : 1000;
bool result = reset.WaitOne(waittime);
runtime.StopRuntime();
runtime.Dispose();
///
/// First, if the WF ended due to an exception, we are outta here no matter what
///
if (e != null)
{
throw e;
}
///
/// Next, if we are waiting for a complete, then we just return the result
///
if (waitForComplete)
{
return result;
}
///
/// Ok -- we were waiting for things to go into the idle state. Bomb if they didn't,
/// otherwise we go around again!
///
if (completed)
{
throw new Exception("The WF unexpectedly completed before going idle!");
}
if (!result && !has_entered_idle)
{
throw new Exception("The workflow was never idled");
}
}
finally
{
if (runtime.IsStarted)
{
runtime.StopRuntime();
}
}
}
}
return false;
}
private static void RunWFByType(Type wfActivity)
{
RunWFByType(wfActivity, null);
}
/// <summary>
/// WF that will wait for some period of time before actually causing the thing
/// to fail.
/// </summary>
class LWFTime : LongRunningActivityBase
{
public int Ticks { get; set; }
public LWFTime()
{
}
[Serializable]
public class cArgs
{
public int _args;
public cArgs(int a)
{
_args = a;
}
}
[LongRunningMethod]
public static void Run(cArgs a)
{
Thread.Sleep(a._args);
}
[LongRunningGatherArguments]
public cArgs Args()
{
return new cArgs(Ticks);
}
}
[TestMethod]
public void TestPrematureShutdown()
{
///
/// If the workflow engine shuts down, make sure we don't cause a crash by trying to reference
/// something that is in there...
///
using (WorkflowRuntime runtime = new WorkflowRuntime())
using (AutoResetEvent reset = new AutoResetEvent(false))
{
runtime.WorkflowIdled += delegate { reset.Set(); };
LongRunningActivityBase.RegisterService(runtime);
runtime.StartRuntime();
WorkflowInstance instance;
instance = runtime.CreateWorkflow(typeof(LWFTime), new Dictionary<string,object> {{"Ticks", 3000}});
instance.Start();
reset.WaitOne();
runtime.StopRuntime();
}
///
/// Now wait for the ticks guy to time out. While this guy may finish, it will cause a crash
/// in the vshost dude.
///
Thread.Sleep(4000);
}
[TestMethod]
public void TestLostService()
{
/// Test the fact that the service was not added in
using (WorkflowRuntime runtime = new WorkflowRuntime())
using (AutoResetEvent reset = new AutoResetEvent(false))
{
Exception exp = null;
runtime.WorkflowCompleted += delegate { reset.Set(); };
runtime.WorkflowTerminated += (o, args) => { exp = args.Exception; reset.Set(); };
runtime.StartRuntime();
WorkflowInstance instance;
instance = runtime.CreateWorkflow(typeof(LRSimple));
instance.Start();
reset.WaitOne();
Assert.IsTrue(exp != null, "No exception was thrown by the wf!");
Assert.IsTrue(exp.Message.Contains("Service"), "Exception thrown is wrong exception for missing WF serice!");
}
}
[TestMethod]
public void TestThrow()
{
/// Make sure we throw an exception because we don't have a "run" method.
try
{
RunWFByType(typeof(LongRunningActivityBase));
}
catch (Exception e)
{
Assert.IsTrue(e.Message.Contains("LongRunningMethod"), "Wrong exception came back for missing Run method!");
return;
}
Assert.Fail("An exception should have been thrown!");
}
[TestMethod]
public void Test05secWF()
{
TimeSpan sp = RunTimeTest(500);
Assert.IsTrue(sp.TotalMilliseconds > 500, "Run took less than half a second - pause didn't work");
}
/// <summary>
/// Run a timer test...
/// </summary>
/// <returns></returns>
private static TimeSpan RunTimeTest(int ticks)
{
/// Wait for 0.5 second and make sure that it actually takes that long to complete.
DateTime now = DateTime.Now;
RunWFByType(typeof(LWFTime), new Dictionary<string,object> {{"Ticks", ticks}});
DateTime then = DateTime.Now;
TimeSpan sp = then - now;
return sp;
}
[TestMethod]
public void TestFastWF()
{
TimeSpan sp = RunTimeTest(0);
Assert.IsTrue(sp.TotalMilliseconds < 300, "Run took more than half a second - pause didn't work");
}
/// <summary>
/// Activity that will set some information on the way back down.
/// </summary>
public class LWASetResult : LongRunningActivityBase
{
public class cArgs
{
public int _arg;
}
public int TheArg { get; set; }
[LongRunningMethod]
public static cArgs Run(cArgs arg)
{
cArgs newarg = new cArgs();
newarg._arg = arg._arg * 2;
return newarg;
}
[LongRunningGatherArguments]
public cArgs GatherArgs()
{
cArgs result = new cArgs();
result._arg = TheArg;
return result;
}
public static int _result;
[LongRunningDistributeArguments]
public void DoneWithResult(cArgs arg)
{
_result = arg._arg;
}
}
[TestMethod]
public void TestArgDistribution()
{
LWASetResult._result = 0;
RunWFByType(typeof(LWASetResult), new Dictionary<string, object> { { "TheArg", 10 } });
Assert.IsTrue(LWASetResult._result == 20, "The callback didn't set the result to the right value!");
}
[TestMethod]
public void TestPersistance()
{
try
{
Assert.IsTrue(RunWFByTypeWithPersistance(typeof(LWFTime), new Dictionary<string, object> { { "Ticks", 3000 } }, 5000, new DirectoryInfo(".\\testPers")), "The task did not complete");
Assert.Fail("The persistance should have thrown a crash - because it didn't restart, but that didn't happen");
}
catch (LongRunningException e)
{
Assert.IsTrue(e.Message.Contains("Activity was waiting for response"), "Incorrect crash came back from aborted WF.");
}
}
[TestMethod]
public void TestAbortedWF()
{
DirectoryInfo cache = new DirectoryInfo (".\\TestAbortedWF");
///
/// Get the runtime going, go into idle state, and then abort the workflow. There should
/// be no files sitting in the cache directory at that point.
///
using (WorkflowRuntime runtime = new WorkflowRuntime())
using (AutoResetEvent reset = new AutoResetEvent(false))
{
Exception exp = null;
runtime.WorkflowCompleted += delegate { reset.Set(); };
runtime.WorkflowTerminated += (o, args) => { exp = args.Exception; reset.Set(); };
runtime.WorkflowIdled += (o, args) => reset.Set();
runtime.AddService(new FilePersistenceService(true, cache));
LongRunningActivityBinaryPersister lrp = new LongRunningActivityBinaryPersister(new FileInfo(cache.FullName + "\\longrunning"));
LongRunningActivityBase.RegisterService(runtime, obj => lrp.Save(obj), () => lrp.Restore());
runtime.StartRuntime();
WorkflowInstance instance;
instance = runtime.CreateWorkflow(typeof(LWFTime), new Dictionary<string, object> { { "Ticks", 5000 } });
instance.Start();
reset.WaitOne();
if (exp != null)
{
throw exp;
}
///
/// Ok -- now check out the cache. Make sure there are no files there!
///
instance.Terminate("Forcing terminate for testing");
Assert.IsTrue(cache.GetFiles().Length == 0, "There were files in the cache after aborted WF!");
}
}
[TestMethod]
public void TestAutoRestart()
{
Assert.IsTrue(RunWFByTypeWithPersistance(typeof(LWFTime), new Dictionary<string, object> { { "Ticks", 3000 }, { "TimesToRetry", 1 } }, 5000, new DirectoryInfo(".\\testAutoRestart")), "The task did not complete");
}
/// <summary>
/// A simple activity that will throw...
/// </summary>
class LRCrashNTimesActivity : LongRunningActivityBase
{
public static int _numberOfTimes = 0;
public int TimesToCrash { get; set; }
[Serializable]
public class cArgs
{
public int _times;
}
[LongRunningMethod]
public static void Run(cArgs args)
{
_numberOfTimes = _numberOfTimes + 1;
if (_numberOfTimes < args._times)
{
Thread.Sleep(2000);
}
else
{
throw new Exception("Testing the throw 12345669");
}
}
[LongRunningGatherArguments]
public cArgs Gather()
{
cArgs c = new cArgs();
c._times = TimesToCrash;
return c;
}
}
[TestMethod]
public void TestNumberCalls()
{
/// Make sure that the WF above is called the number of times we expect it to be called.
/// This is more of a test of the test harness than it is of the long running activity code. :-)
LRCrashNTimesActivity._numberOfTimes = 0;
RunWFByType(typeof(LRCrashNTimesActivity),
new Dictionary<string, object> { { "TimesToCrash", 10 } });
Assert.IsTrue(LRCrashNTimesActivity._numberOfTimes == 1, "Was expecting to have run only once!");
LRCrashNTimesActivity._numberOfTimes = 0;
bool result = RunWFByTypeWithPersistance(typeof(LRCrashNTimesActivity),
new Dictionary<string, object> { { "TimesToCrash", 10 } },
5000,
new DirectoryInfo(".\\TestNumberCallsCache"),
0);
Assert.IsTrue(result, "Expected complex run with 0 retries to come back just fine!");
Assert.IsTrue(LRCrashNTimesActivity._numberOfTimes == 1, "Expected the activity to be called only one time!");
LRCrashNTimesActivity._numberOfTimes = 0;
bool result2 = RunWFByTypeWithPersistance(typeof(LRCrashNTimesActivity),
new Dictionary<string, object> { { "TimesToCrash", 10 }, {"TimesToRetry", 10} },
5000,
new DirectoryInfo(".\\TestNumberCallsCache"),
1);
Assert.IsTrue(result2, "Expected complex run with 1 retries to come back just fine!");
Assert.IsTrue(LRCrashNTimesActivity._numberOfTimes == 2, "Expected the activity to be called exactly 2 times.");
}
[TestMethod]
public void TestForPersFileToBeGone()
{
DirectoryInfo di = new DirectoryInfo(".\\TestNumberCallsCache");
LRCrashNTimesActivity._numberOfTimes = 0;
bool result = RunWFByTypeWithPersistance(typeof(LRCrashNTimesActivity),
new Dictionary<string, object> { { "TimesToCrash", 10 } },
5000,
di,
0);
Assert.IsTrue(result, "Expected complex run with 0 retries to come back just fine!");
Assert.IsTrue(LRCrashNTimesActivity._numberOfTimes == 1, "Expected the activity to be called only one time!");
FileInfo fcache = new FileInfo(di.FullName + "\\longrunning");
Assert.IsFalse(fcache.Exists, "The cache file should not be around after a single successful run!");
}
[TestMethod]
public void TestAutoRerunExactNumberOfTimes()
{
///
/// First, make sure that if we retry several times it gets a chance to complete.
///
LRCrashNTimesActivity._numberOfTimes = 0;
bool result2 = RunWFByTypeWithPersistance(typeof(LRCrashNTimesActivity),
new Dictionary<string, object> { { "TimesToCrash", 10 }, { "TimesToRetry", 3 } }, 5000,
new DirectoryInfo(".\\TestAutoRerunExactNumberOfTimes"), 3);
Assert.IsTrue(result2, "Was expecting things to complete normally");
Assert.IsTrue(LRCrashNTimesActivity._numberOfTimes == 4, "Was expecting to have called the activity exactly 4 times!");
}
[TestMethod]
public void TestExcessiveAutoRestart()
{
///
/// This looks to make sure we are safe against the thing that the task we are doing
/// causes the whole host to crash. So we specify the max number of times to re-try a
/// particular WF item.
///
try
{
LRCrashNTimesActivity._numberOfTimes = 0;
bool result = RunWFByTypeWithPersistance(typeof(LRCrashNTimesActivity),
new Dictionary<string, object> { { "TimesToCrash", 10 }, { "TimesToRetry", 4 } }, 5000,
new DirectoryInfo(".\\TestExcessiveAutoRestart"),
10);
Assert.Fail("We should have thrown an exception and terminated after the 2nd iteration and note made it to the third!");
}
catch (Exception e)
{
Assert.IsTrue(e.Message.Contains("waiting for response"), "Exception that came back was incorrect");
Assert.IsTrue(LRCrashNTimesActivity._numberOfTimes == 5, "Was expecting to have retried only 5 times before a crash!");
}
}
/// <summary>
/// A simple activity that will throw...
/// </summary>
class LRCrashActivity : LongRunningActivityBase
{
[LongRunningMethod]
public static void Run()
{
throw new Exception("Testing the throw 123456789");
}
}
[TestMethod]
public void TestWFThrows()
{
try
{
RunWFByType(typeof(LRCrashActivity));
Assert.Fail("WF completed without an error - it should have thrown!");
}
catch (Exception e)
{
Assert.IsNotNull(e.InnerException, "Inner expcetion is not null!");
Assert.IsTrue(e.InnerException.Message.Contains("123456789"), "Wrong exception came back from crashed WF");
}
}
class LRAsyncActivity : LongRunningActivityBase
{
/// <summary>
/// How many ticks to wait for!
/// </summary>
public int Ticks { get; set; }
public class cArgs
{
public int _ticks;
}
[LongRunningGatherArguments]
public cArgs Gather()
{
var result = new cArgs();
result._ticks = Ticks;
return result;
}
[LongRunningMethodStarter]
public static void StartTimer(LongRunningContext c, cArgs args)
{
Timer t = new Timer(new TimerCallback(timerdone), c, args._ticks, Timeout.Infinite);
}
public static void timerdone(object state)
{
LongRunningContext c = state as LongRunningContext;
c.Complete();
}
}
[TestMethod]
public void TestAsyncCallbackPattern()
{
/// Use a guy that doesn't run synchronosly - rather runs async.
RunWFByType(typeof(LRAsyncActivity), new Dictionary<string, object> { { "Ticks", 1000 } });
}
class LRAsyncNoArgActivity : LongRunningActivityBase
{
[LongRunningMethodStarter]
public static void StartTimer(LongRunningContext c)
{
Timer t = new Timer(new TimerCallback(timerdone), c, 1000, Timeout.Infinite);
}
public static void timerdone(object state)
{
LongRunningContext c = state as LongRunningContext;
c.Complete();
}
}
[TestMethod]
public void TestAsyncCallbackPatternNoArgs()
{
/// WF with a run method that takes no parameters
RunWFByType(typeof(LRAsyncNoArgActivity));
}
class LRAsyncActivityWithArgs : LongRunningActivityBase
{
/// <summary>
/// How many ticks to wait for!
/// </summary>
public int Ticks { get; set; }
public class cArgs
{
public int _ticks;
}
[LongRunningGatherArguments]
public cArgs Gather()
{
var result = new cArgs();
result._ticks = Ticks;
return result;
}
[LongRunningMethodStarter]
public static void StartTimer(LongRunningContext c, cArgs args)
{
Timer t = new Timer(new TimerCallback(timerdone), c, args._ticks, Timeout.Infinite);
}
public static void timerdone(object state)
{
LongRunningContext c = state as LongRunningContext;
Result r = new Result();
r._result = 15;
c.Complete(r);
}
public class Result
{
public int _result;
}
public static int _result = 0;
[LongRunningDistributeArguments]
public void Save(Result r)
{
_result = r._result;
}
}
[TestMethod]
public void TestAsyncWFWithResult()
{
LRAsyncActivityWithArgs._result = 0;
RunWFByType(typeof(LRAsyncActivityWithArgs), new Dictionary<string, object> { { "Ticks", 500 } });
Assert.IsTrue(LRAsyncActivityWithArgs._result == 15, "The saved result was not correct");
}
class LRAsyncActivityWithContext : LongRunningActivityBase
{
/// <summary>
/// How many ticks to wait for!
/// </summary>
public int Ticks { get; set; }
[Serializable]
public class cArgs
{
public int _ticks;
}
[Serializable]
public class MyContext
{
public int _mycounter = 0;
}
[LongRunningGatherArguments]
public cArgs Gather()
{
var result = new cArgs();
result._ticks = Ticks;
return result;
}
[LongRunningMethodStarter]
public static MyContext StartTimer(LongRunningContext c, cArgs args, MyContext me)
{
MyContext newme = new MyContext();
newme._mycounter = 1;
if (me != null)
{
newme._mycounter = newme._mycounter + me._mycounter;
}
Trace.WriteLine("Starting Timer -- counter is " + newme._mycounter.ToString());
Timer t = new Timer(new TimerCallback(timerdone), new object[] { c, newme }, args._ticks, Timeout.Infinite);
return newme;
}
public static void timerdone(object state)
{
object[] arr = state as object[];
LongRunningContext c = arr[0] as LongRunningContext;
MyContext me = arr[1] as MyContext;
Result r = new Result();
r._result = 15;
r._counter = me._mycounter;
c.Complete(r);
}
[Serializable]
public class Result
{
public int _result;
public int _counter;
}
public static int _result = 0;
public static int _counter = 0;
[LongRunningDistributeArguments]
public void Save(Result r)
{
_result = r._result;
_counter = r._counter;
}
}
[TestMethod]
public void TestAsyncWithContext()
{
LRAsyncActivityWithContext._counter = 0;
LRAsyncActivityWithContext._result = 0;
RunWFByType(typeof(LRAsyncActivityWithContext));
Assert.IsTrue(LRAsyncActivityWithContext._result == 15, "The result is not correct");
Assert.IsTrue(LRAsyncActivityWithContext._counter == 1, "The number of times it was called is not four!");
}
[TestMethod]
public void TestAsyncWithContextAndCrash()
{
/// Make sure the updated context is correctly written out to the cache.
/// It doesn't appear to be in one of the long running program tests.
LRAsyncActivityWithContext._counter = 0;
LRAsyncActivityWithContext._result = 0;
RunWFByTypeWithPersistance(typeof(LRAsyncActivityWithContext), new Dictionary<string, object> { { "Ticks", 8000 }, {"TimesToRetry", 5} }, 10000,
new DirectoryInfo(".\\TestAsyncWithContextAndCrash"),
4);
Assert.IsTrue(LRAsyncActivityWithContext._result == 15, "The result is not correct");
Assert.IsTrue(LRAsyncActivityWithContext._counter == 5, "The number of times it was called is not one!");
}
}
}