1 // ***********************************************************************
2 // Copyright (c) 2008 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
31 using System.Diagnostics;
32 using System.Threading;
33 using NUnit.Compatibility;
34 using NUnit.Framework.Internal;
36 namespace NUnit.Framework.Constraints
39 /// Applies a delay to the match so that a match can be evaluated in the future.
41 public class DelayedConstraint : PrefixConstraint
43 // TODO: Needs error message tests
45 private readonly int delayInMilliseconds;
46 private readonly int pollingInterval;
49 /// Creates a new DelayedConstraint
51 ///<param name="baseConstraint">The inner constraint to decorate</param>
52 ///<param name="delayInMilliseconds">The time interval after which the match is performed</param>
53 ///<exception cref="InvalidOperationException">If the value of <paramref name="delayInMilliseconds"/> is less than 0</exception>
54 public DelayedConstraint(IConstraint baseConstraint, int delayInMilliseconds)
55 : this(baseConstraint, delayInMilliseconds, 0) { }
58 /// Creates a new DelayedConstraint
60 ///<param name="baseConstraint">The inner constraint to decorate</param>
61 ///<param name="delayInMilliseconds">The time interval after which the match is performed, in milliseconds</param>
62 ///<param name="pollingInterval">The time interval used for polling, in milliseconds</param>
63 ///<exception cref="InvalidOperationException">If the value of <paramref name="delayInMilliseconds"/> is less than 0</exception>
64 public DelayedConstraint(IConstraint baseConstraint, int delayInMilliseconds, int pollingInterval)
65 : base(baseConstraint)
67 if (delayInMilliseconds < 0)
68 throw new ArgumentException("Cannot check a condition in the past", "delayInMilliseconds");
70 this.delayInMilliseconds = delayInMilliseconds;
71 this.pollingInterval = pollingInterval;
75 /// Gets text describing a constraint
77 public override string Description
79 get { return string.Format("{0} after {1} millisecond delay", BaseConstraint.Description, delayInMilliseconds); }
83 /// Test whether the constraint is satisfied by a given value
85 /// <param name="actual">The value to be tested</param>
86 /// <returns>True for if the base constraint fails, false if it succeeds</returns>
87 public override ConstraintResult ApplyTo<TActual>(TActual actual)
89 long now = Stopwatch.GetTimestamp();
90 long delayEnd = TimestampOffset(now, TimeSpan.FromMilliseconds(delayInMilliseconds));
92 if (pollingInterval > 0)
94 long nextPoll = TimestampOffset(now, TimeSpan.FromMilliseconds(pollingInterval));
95 while ((now = Stopwatch.GetTimestamp()) < delayEnd)
98 Thread.Sleep((int)TimestampDiff(delayEnd < nextPoll ? delayEnd : nextPoll, now).TotalMilliseconds);
99 nextPoll = TimestampOffset(now, TimeSpan.FromMilliseconds(pollingInterval));
101 ConstraintResult result = BaseConstraint.ApplyTo(actual);
102 if (result.IsSuccess)
103 return new ConstraintResult(this, actual, true);
106 if ((now = Stopwatch.GetTimestamp()) < delayEnd)
107 Thread.Sleep((int)TimestampDiff(delayEnd, now).TotalMilliseconds);
109 return new ConstraintResult(this, actual, BaseConstraint.ApplyTo(actual).IsSuccess);
113 /// Test whether the constraint is satisfied by a delegate
115 /// <param name="del">The delegate whose value is to be tested</param>
116 /// <returns>A ConstraintResult</returns>
117 public override ConstraintResult ApplyTo<TActual>(ActualValueDelegate<TActual> del)
119 long now = Stopwatch.GetTimestamp();
120 long delayEnd = TimestampOffset(now, TimeSpan.FromMilliseconds(delayInMilliseconds));
123 if (pollingInterval > 0)
125 long nextPoll = TimestampOffset(now, TimeSpan.FromMilliseconds(pollingInterval));
126 while ((now = Stopwatch.GetTimestamp()) < delayEnd)
129 Thread.Sleep((int)TimestampDiff(delayEnd < nextPoll ? delayEnd : nextPoll, now).TotalMilliseconds);
130 nextPoll = TimestampOffset(now, TimeSpan.FromMilliseconds(pollingInterval));
132 actual = InvokeDelegate(del);
136 ConstraintResult result = BaseConstraint.ApplyTo(actual);
137 if (result.IsSuccess)
138 return new ConstraintResult(this, actual, true);
142 // Ignore any exceptions when polling
146 if ((now = Stopwatch.GetTimestamp()) < delayEnd)
147 Thread.Sleep((int)TimestampDiff(delayEnd, now).TotalMilliseconds);
149 actual = InvokeDelegate(del);
150 return new ConstraintResult(this, actual, BaseConstraint.ApplyTo(actual).IsSuccess);
153 private static object InvokeDelegate<T>(ActualValueDelegate<T> del)
155 #if NET_4_0 || NET_4_5
156 if (AsyncInvocationRegion.IsAsyncOperation(del))
157 using (AsyncInvocationRegion region = AsyncInvocationRegion.Create(del))
158 return region.WaitForPendingOperationsToComplete(del());
165 /// Test whether the constraint is satisfied by a given reference.
166 /// Overridden to wait for the specified delay period before
167 /// calling the base constraint with the dereferenced value.
169 /// <param name="actual">A reference to the value to be tested</param>
170 /// <returns>True for success, false for failure</returns>
171 public override ConstraintResult ApplyTo<TActual>(ref TActual actual)
173 long now = Stopwatch.GetTimestamp();
174 long delayEnd = TimestampOffset(now, TimeSpan.FromMilliseconds(delayInMilliseconds));
176 if (pollingInterval > 0)
178 long nextPoll = TimestampOffset(now, TimeSpan.FromMilliseconds(pollingInterval));
179 while ((now = Stopwatch.GetTimestamp()) < delayEnd)
182 Thread.Sleep((int)TimestampDiff(delayEnd < nextPoll ? delayEnd : nextPoll, now).TotalMilliseconds);
183 nextPoll = TimestampOffset(now, TimeSpan.FromMilliseconds(pollingInterval));
187 ConstraintResult result = BaseConstraint.ApplyTo(actual);
188 if (result.IsSuccess)
189 return new ConstraintResult(this, actual, true);
193 // Ignore any exceptions when polling
197 if ((now = Stopwatch.GetTimestamp()) < delayEnd)
198 Thread.Sleep((int)TimestampDiff(delayEnd, now).TotalMilliseconds);
200 return new ConstraintResult(this, actual, BaseConstraint.ApplyTo(actual).IsSuccess);
204 /// Returns the string representation of the constraint.
206 protected override string GetStringRepresentation()
208 return string.Format("<after {0} {1}>", delayInMilliseconds, BaseConstraint);
212 /// Adjusts a Timestamp by a given TimeSpan
214 /// <param name="timestamp"></param>
215 /// <param name="offset"></param>
216 /// <returns></returns>
217 private static long TimestampOffset(long timestamp, TimeSpan offset)
219 return timestamp + (long)(offset.TotalSeconds * Stopwatch.Frequency);
223 /// Returns the difference between two Timestamps as a TimeSpan
225 /// <param name="timestamp1"></param>
226 /// <param name="timestamp2"></param>
227 /// <returns></returns>
228 private static TimeSpan TimestampDiff(long timestamp1, long timestamp2)
230 return TimeSpan.FromSeconds((double)(timestamp1 - timestamp2) / Stopwatch.Frequency);