Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reactive;
using System;
using System.Reactive.Disposables;
namespace Microsoft.Reactive.Testing
{
class ColdObservable<T> : ITestableObservable<T>
{
readonly TestScheduler scheduler;
readonly Recorded<Notification<T>>[] messages;
readonly List<Subscription> subscriptions = new List<Subscription>();
public ColdObservable(TestScheduler scheduler, params Recorded<Notification<T>>[] messages)
{
if (scheduler == null)
throw new ArgumentNullException("scheduler");
if (messages == null)
throw new ArgumentNullException("messages");
this.scheduler = scheduler;
this.messages = messages;
}
public virtual IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
subscriptions.Add(new Subscription(scheduler.Clock));
var index = subscriptions.Count - 1;
var d = new CompositeDisposable();
for (var i = 0; i < messages.Length; ++i)
{
var notification = messages[i].Value;
d.Add(scheduler.ScheduleRelative(default(object), messages[i].Time, (scheduler1, state1) => { notification.Accept(observer); return Disposable.Empty; }));
}
return Disposable.Create(() =>
{
subscriptions[index] = new Subscription(subscriptions[index].Subscribe, scheduler.Clock);
d.Dispose();
});
}
public IList<Subscription> Subscriptions
{
get { return subscriptions; }
}
public IList<Recorded<Notification<T>>> Messages
{
get { return messages; }
}
}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
//
// To add a suppression to this file, right-click the message in the
// Error List, point to "Suppress Message(s)", and click
// "In Project Suppression File".
// You do not need to add suppressions to this file manually.
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1053:StaticHolderTypesShouldNotHaveConstructors", Scope = "type", Target = "Microsoft.Reactive.Testing.ReactiveTest")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1014:MarkAssembliesWithClsCompliant")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Reactive.Testing.ReactiveAssert.#Throws`1(System.Action,System.String)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Reactive.Testing.ReactiveAssert.#Throws`1(System.Action)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1016:MarkAssembliesWithAssemblyVersion")]

View File

@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reactive;
using System;
using System.Reactive.Disposables;
namespace Microsoft.Reactive.Testing
{
class HotObservable<T> : ITestableObservable<T>
{
readonly TestScheduler scheduler;
readonly List<IObserver<T>> observers = new List<IObserver<T>>();
readonly List<Subscription> subscriptions = new List<Subscription>();
readonly Recorded<Notification<T>>[] messages;
public HotObservable(TestScheduler scheduler, params Recorded<Notification<T>>[] messages)
{
if (scheduler == null)
throw new ArgumentNullException("scheduler");
if (messages == null)
throw new ArgumentNullException("messages");
this.scheduler = scheduler;
this.messages = messages;
for (var i = 0; i < messages.Length; ++i)
{
var notification = messages[i].Value;
scheduler.ScheduleAbsolute(default(object), messages[i].Time, (scheduler1, state1) =>
{
var _observers = observers.ToArray();
for (var j = 0; j < _observers.Length; ++j)
{
notification.Accept(_observers[j]);
}
return Disposable.Empty;
});
}
}
public virtual IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
observers.Add(observer);
subscriptions.Add(new Subscription(scheduler.Clock));
var index = subscriptions.Count - 1;
return Disposable.Create(() =>
{
observers.Remove(observer);
subscriptions[index] = new Subscription(subscriptions[index].Subscribe, scheduler.Clock);
});
}
public IList<Subscription> Subscriptions
{
get { return subscriptions; }
}
public IList<Recorded<Notification<T>>> Messages
{
get { return messages; }
}
}
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Reactive;
namespace Microsoft.Reactive.Testing
{
/// <summary>
/// Observable sequence that records subscription lifetimes and timestamped notification messages sent to observers.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
public interface ITestableObservable<T> : IObservable<T>
{
/// <summary>
/// Gets a list of all the subscriptions to the observable sequence, including their lifetimes.
/// </summary>
IList<Subscription> Subscriptions { get; }
/// <summary>
/// Gets the recorded timestamped notification messages that were sent by the observable sequence to its observers.
/// </summary>
IList<Recorded<Notification<T>>> Messages { get; }
}
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Reactive;
namespace Microsoft.Reactive.Testing
{
/// <summary>
/// Observer that records received notification messages and timestamps those.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
public interface ITestableObserver<T> : IObserver<T>
{
/// <summary>
/// Gets recorded timestamped notification messages received by the observer.
/// </summary>
IList<Recorded<Notification<T>>> Messages { get; }
}
}

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{E7B7408B-B039-4F30-B6CF-CC06218CE4CA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.Reactive.Testing</RootNamespace>
<AssemblyName>Microsoft.Reactive.Testing</AssemblyName>
<FileAlignment>512</FileAlignment>
<ProductSignAssembly>true</ProductSignAssembly>
<CodeAnalysisRuleSet>..\Rx.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseXBLV|AnyCPU'">
<OutputPath>bin\ReleaseXBLV\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugXBLV|AnyCPU'">
<OutputPath>bin\DebugXBLV\</OutputPath>
</PropertyGroup>
<Import Project="..\Common.targets" />
<PropertyGroup>
<DocumentationFile>$(OutputPath)\$(AssemblyName).XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" Condition=" '$(BuildPlatform)' == 'SILVERLIGHT' " />
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight" Condition=" '$(BuildPlatform)' == 'SILVERLIGHT' " />
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" Condition=" '$(BuildPlatform)' == 'DESKTOPCLR' Or '$(BuildPlatform)' == 'WINDOWS_PHONE' " />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Observable" Condition=" '$(BuildFlavor)' == 'SILVERLIGHTM7' " />
</ItemGroup>
<ItemGroup Condition=" '$(BuildFlavor)' == 'WINDOWS8' ">
<SDKReference Include="MSTestFramework, Version=11.0" />
<SDKReference Include="TestPlatform, Version=11.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="ColdObservable.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="HotObservable.cs" />
<Compile Include="ITestObservable.cs" />
<Compile Include="ITestObserver.cs" />
<Compile Include="MockObserver.cs" />
<Compile Include="NamespaceDoc.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ReactiveAssert.cs" />
<Compile Include="ReactiveTest.cs" />
<Compile Include="Recorded.cs" />
<Compile Include="Subscription.cs" />
<Compile Include="TestScheduler.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\System.Reactive.Core\System.Reactive.Core.csproj">
<Project>{4E516F10-DA7A-4D43-963E-A93865ABEA5B}</Project>
<Name>System.Reactive.Core</Name>
</ProjectReference>
<ProjectReference Include="..\System.Reactive.Interfaces\System.Reactive.Interfaces.csproj">
<Project>{9E9B9C60-98B0-40FA-9C2B-1218D417CAA4}</Project>
<Name>System.Reactive.Interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\System.Reactive.Linq\System.Reactive.Linq.csproj">
<Project>{63252AE9-5186-45CA-BFCD-FA51C6B66A43}</Project>
<Name>System.Reactive.Linq</Name>
</ProjectReference>
</ItemGroup>
<Import Project="..\Import.targets" />
</Project>

View File

@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reactive;
using System;
namespace Microsoft.Reactive.Testing
{
class MockObserver<T> : ITestableObserver<T>
{
TestScheduler scheduler;
List<Recorded<Notification<T>>> messages;
public MockObserver(TestScheduler scheduler)
{
if (scheduler == null)
throw new ArgumentNullException("scheduler");
this.scheduler = scheduler;
this.messages = new List<Recorded<Notification<T>>>();
}
public void OnNext(T value)
{
messages.Add(new Recorded<Notification<T>>(scheduler.Clock, Notification.CreateOnNext<T>(value)));
}
public void OnError(Exception exception)
{
messages.Add(new Recorded<Notification<T>>(scheduler.Clock, Notification.CreateOnError<T>(exception)));
}
public void OnCompleted()
{
messages.Add(new Recorded<Notification<T>>(scheduler.Clock, Notification.CreateOnCompleted<T>()));
}
public IList<Recorded<Notification<T>>> Messages
{
get { return messages; }
}
}
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace Microsoft.Reactive.Testing
{
/// <summary>
/// The <b>Microsoft.Reactive.Testing</b> namespace contains interfaces and classes providing functionality to test applications and libraries built using Reactive Extensions.
/// </summary>
[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
class NamespaceDoc
{
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Microsoft.Reactive.Testing")]
[assembly: AssemblyDescription("Reactive Extensions Testing Library used to write unit tests for queries and custom operators over observable sequences.")]
#if DEBUG
[assembly: AssemblyConfiguration("Debug")]
#else
[assembly: AssemblyConfiguration("Retail")]
#endif
[assembly: AssemblyCompany("Microsoft Open Technologies, Inc.")]
[assembly: AssemblyProduct("Reactive Extensions")]
[assembly: AssemblyCopyright("\x00a9 Microsoft Open Technologies, Inc. All rights reserved.")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: ComVisible(false)]
// ===========================================================================
// DO NOT EDIT OR REMOVE ANYTHING BELOW THIS COMMENT.
// Version numbers are automatically generated in the msbuild files based on regular expressions
// ===========================================================================
[assembly: AssemblyVersion("2.2.0.0")]
[assembly: AssemblyFileVersion("2.2.0.0")]
[assembly: AssemblyInformationalVersion("2.2.0.0")]

View File

@@ -0,0 +1,286 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Reactive.Disposables;
using System.Reactive.Linq;
#if NUNIT
using NUnit.Framework;
#elif WINDOWS8
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
#else
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endif
namespace Microsoft.Reactive.Testing
{
/// <summary>
/// Helper class to write asserts in unit tests for applications and libraries built using Reactive Extensions.
/// </summary>
public static class ReactiveAssert
{
static string Message<T>(IEnumerable<T> actual, IEnumerable<T> expected)
{
var sb = new StringBuilder();
sb.AppendLine();
sb.Append("Expected: [");
sb.Append(string.Join(", ", expected.Select(x => x.ToString()).ToArray()));
sb.Append("]");
sb.AppendLine();
sb.Append("Actual..: [");
sb.Append(string.Join(", ", actual.Select(x => x.ToString()).ToArray()));
sb.Append("]");
sb.AppendLine();
return sb.ToString();
}
/// <summary>
/// Asserts that both enumerable sequences have equal length and equal elements.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
/// <param name="expected">Expected sequence.</param>
/// <param name="actual">Actual sequence to compare against the expected one.</param>
/// <exception cref="ArgumentNullException"><paramref name="expected"/> or <paramref name="actual"/> is null.</exception>
public static void AreElementsEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual)
{
if (expected == null)
throw new ArgumentNullException("expected");
if (actual == null)
throw new ArgumentNullException("actual");
if (!expected.SequenceEqual(actual))
Assert.Fail(Message(actual, expected));
}
/// <summary>
/// Asserts that both enumerable sequences have equal length and equal elements.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
/// <param name="expected">Expected sequence.</param>
/// <param name="actual">Actual sequence to compare against the expected one.</param>
/// <param name="message">Error message for assert failure.</param>
/// <exception cref="ArgumentNullException"><paramref name="expected"/> or <paramref name="actual"/> is null.</exception>
public static void AreElementsEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual, string message)
{
if (expected == null)
throw new ArgumentNullException("expected");
if (actual == null)
throw new ArgumentNullException("actual");
if (!expected.SequenceEqual(actual))
Assert.Fail(message);
}
/// <summary>
/// Asserts that both observable sequences have equal length and equal notifications.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
/// <param name="expected">Expected sequence.</param>
/// <param name="actual">Actual sequence to compare against the expected one.</param>
/// <exception cref="ArgumentNullException"><paramref name="expected"/> or <paramref name="actual"/> is null.</exception>
public static void AreElementsEqual<T>(IObservable<T> expected, IObservable<T> actual)
{
if (expected == null)
throw new ArgumentNullException("expected");
if (actual == null)
throw new ArgumentNullException("actual");
AreElementsEqual(expected.Materialize().ToEnumerable(), actual.Materialize().ToEnumerable());
}
/// <summary>
/// Asserts that both observable sequences have equal length and equal elements.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
/// <param name="expected">Expected sequence.</param>
/// <param name="actual">Actual sequence to compare against the expected one.</param>
/// <param name="message">Error message for assert failure.</param>
/// <exception cref="ArgumentNullException"><paramref name="expected"/> or <paramref name="actual"/> is null.</exception>
public static void AreElementsEqual<T>(IObservable<T> expected, IObservable<T> actual, string message)
{
if (expected == null)
throw new ArgumentNullException("expected");
if (actual == null)
throw new ArgumentNullException("actual");
AreElementsEqual(expected.Materialize().ToEnumerable(), actual.Materialize().ToEnumerable(), message);
}
/// <summary>
/// Asserts that the given action throws an exception of the type specified in the generic parameter, or a subtype thereof.
/// </summary>
/// <typeparam name="TException">Type of the exception to check for.</typeparam>
/// <param name="action">Action to run.</param>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
public static void Throws<TException>(Action action) where TException : Exception
{
if (action == null)
throw new ArgumentNullException("action");
var failed = false;
try
{
action();
failed = true;
}
catch (TException)
{
}
catch (Exception ex)
{
Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0} threw {1}.\r\n\r\nStack trace:\r\n{2}", typeof(TException).Name, ex.GetType().Name, ex.StackTrace));
}
if (failed)
Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0}.", typeof(TException).Name));
}
/// <summary>
/// Asserts that the given action throws an exception of the type specified in the generic parameter, or a subtype thereof.
/// </summary>
/// <typeparam name="TException">Type of the exception to check for.</typeparam>
/// <param name="action">Action to run.</param>
/// <param name="message">Error message for assert failure.</param>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
public static void Throws<TException>(Action action, string message) where TException : Exception
{
if (action == null)
throw new ArgumentNullException("action");
var failed = false;
try
{
action();
failed = true;
}
catch (TException)
{
}
catch
{
Assert.Fail(message);
}
if (failed)
Assert.Fail(message);
}
/// <summary>
/// Asserts that the given action throws the specified exception.
/// </summary>
/// <typeparam name="TException">Type of the exception to check for.</typeparam>
/// <param name="exception">Exception to assert being thrown.</param>
/// <param name="action">Action to run.</param>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
public static void Throws<TException>(TException exception, Action action) where TException : Exception
{
if (action == null)
throw new ArgumentNullException("action");
var failed = false;
try
{
action();
failed = true;
}
catch (TException ex)
{
Assert.AreSame(exception, ex);
}
catch (Exception ex)
{
Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0} threw {1}.\r\n\r\nStack trace:\r\n{2}", typeof(TException).Name, ex.GetType().Name, ex.StackTrace));
}
if (failed)
Assert.Fail(string.Format(CultureInfo.CurrentCulture, "Expected {0}.", typeof(TException).Name));
}
/// <summary>
/// Asserts that the given action throws the specified exception.
/// </summary>
/// <typeparam name="TException">Type of the exception to check for.</typeparam>
/// <param name="exception">Exception to assert being thrown.</param>
/// <param name="action">Action to run.</param>
/// <param name="message">Error message for assert failure.</param>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
public static void Throws<TException>(TException exception, Action action, string message) where TException : Exception
{
if (action == null)
throw new ArgumentNullException("action");
var failed = false;
try
{
action();
failed = true;
}
catch (TException ex)
{
Assert.AreSame(exception, ex);
}
catch
{
Assert.Fail(message);
}
if (failed)
Assert.Fail(message);
}
/// <summary>
/// Asserts that both enumerable sequences have equal length and equal elements.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
/// <param name="actual">Actual sequence to compare against the expected one.</param>
/// <param name="expected">Expected sequence.</param>
/// <exception cref="ArgumentNullException"><paramref name="expected"/> or <paramref name="actual"/> is null.</exception>
public static void AssertEqual<T>(this IEnumerable<T> actual, IEnumerable<T> expected)
{
if (actual == null)
throw new ArgumentNullException("actual");
if (expected == null)
throw new ArgumentNullException("expected");
ReactiveAssert.AreElementsEqual(expected, actual);
}
/// <summary>
/// Asserts the enumerable sequence has the expected elements.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
/// <param name="actual">Actual sequence to compare against the expected elements.</param>
/// <param name="expected">Expected elements.</param>
/// <exception cref="ArgumentNullException"><paramref name="expected"/> or <paramref name="actual"/> is null.</exception>
public static void AssertEqual<T>(this IEnumerable<T> actual, params T[] expected)
{
if (actual == null)
throw new ArgumentNullException("actual");
if (expected == null)
throw new ArgumentNullException("expected");
ReactiveAssert.AreElementsEqual(expected, actual);
}
/// <summary>
/// Asserts that both observable sequences have equal length and equal notifications.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
/// <param name="actual">Actual sequence to compare against the expected one.</param>
/// <param name="expected">Expected sequence.</param>
/// <exception cref="ArgumentNullException"><paramref name="expected"/> or <paramref name="actual"/> is null.</exception>
public static void AssertEqual<T>(this IObservable<T> actual, IObservable<T> expected)
{
if (actual == null)
throw new ArgumentNullException("actual");
if (expected == null)
throw new ArgumentNullException("expected");
ReactiveAssert.AreElementsEqual(expected, actual);
}
}
}

View File

@@ -0,0 +1,261 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Reactive;
namespace Microsoft.Reactive.Testing
{
/// <summary>
/// Base class to write unit tests for applications and libraries built using Reactive Extensions.
/// </summary>
public class ReactiveTest
{
/// <summary>
/// Default virtual time used for creation of observable sequences in <see cref="ReactiveTest"/>-based unit tests.
/// </summary>
public const long Created = 100;
/// <summary>
/// Default virtual time used to subscribe to observable sequences in <see cref="ReactiveTest"/>-based unit tests.
/// </summary>
public const long Subscribed = 200;
/// <summary>
/// Default virtual time used to dispose subscriptions in <see cref="ReactiveTest"/>-based unit tests.
/// </summary>
public const long Disposed = 1000;
/// <summary>
/// Factory method for an OnNext notification record at a given time with a given value.
/// </summary>
/// <typeparam name="T">The element type for the resulting notification object.</typeparam>
/// <param name="ticks">Recorded virtual time the OnNext notification occurs.</param>
/// <param name="value">Recorded value stored in the OnNext notification.</param>
/// <returns>Recorded OnNext notification.</returns>
public static Recorded<Notification<T>> OnNext<T>(long ticks, T value)
{
return new Recorded<Notification<T>>(ticks, Notification.CreateOnNext<T>(value));
}
/// <summary>
/// Factory method for writing an assert that checks for an OnNext notification record at a given time, using the specified predicate to check the value.
/// </summary>
/// <typeparam name="T">The element type for the resulting notification object.</typeparam>
/// <param name="ticks">Recorded virtual time the OnNext notification occurs.</param>
/// <param name="predicate">Predicate function to check the OnNext notification value against an expected value.</param>
/// <returns>Recorded OnNext notification with a predicate to assert a given value.</returns>
/// <exception cref="ArgumentNullException"><paramref name="predicate"/> is null.</exception>
public static Recorded<Notification<T>> OnNext<T>(long ticks, Func<T, bool> predicate)
{
if (predicate == null)
throw new ArgumentNullException("predicate");
return new Recorded<Notification<T>>(ticks, new OnNextPredicate<T>(predicate));
}
/// <summary>
/// Factory method for an OnCompleted notification record at a given time.
/// </summary>
/// <typeparam name="T">The element type for the resulting notification object.</typeparam>
/// <param name="ticks">Recorded virtual time the OnCompleted notification occurs.</param>
/// <returns>Recorded OnCompleted notification.</returns>
public static Recorded<Notification<T>> OnCompleted<T>(long ticks)
{
return new Recorded<Notification<T>>(ticks, Notification.CreateOnCompleted<T>());
}
/// <summary>
/// Factory method for an OnCompleted notification record at a given time, using inference to determine the type of <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The element type for the resulting notification object.</typeparam>
/// <param name="ticks">Recorded virtual time the OnCompleted notification occurs.</param>
/// <param name="witness">Object solely used to infer the type of the <typeparamref name="T"/> type parameter. This parameter is typically used when creating a sequence of anonymously typed elements.</param>
/// <returns>Recorded OnCompleted notification.</returns>
public static Recorded<Notification<T>> OnCompleted<T>(long ticks, T witness)
{
return new Recorded<Notification<T>>(ticks, Notification.CreateOnCompleted<T>());
}
/// <summary>
/// Factory method for an OnError notification record at a given time with a given error.
/// </summary>
/// <typeparam name="T">The element type for the resulting notification object.</typeparam>
/// <param name="ticks">Recorded virtual time the OnError notification occurs.</param>
/// <param name="exception">Recorded exception stored in the OnError notification.</param>
/// <returns>Recorded OnError notification.</returns>
/// <exception cref="ArgumentNullException"><paramref name="exception"/> is null.</exception>
public static Recorded<Notification<T>> OnError<T>(long ticks, Exception exception)
{
if (exception == null)
throw new ArgumentNullException("exception");
return new Recorded<Notification<T>>(ticks, Notification.CreateOnError<T>(exception));
}
/// <summary>
/// Factory method for writing an assert that checks for an OnError notification record at a given time, using the specified predicate to check the exception.
/// </summary>
/// <typeparam name="T">The element type for the resulting notification object.</typeparam>
/// <param name="ticks">Recorded virtual time the OnError notification occurs.</param>
/// <param name="predicate">Predicate function to check the OnError notification value against an expected exception.</param>
/// <returns>Recorded OnError notification with a predicate to assert a given exception.</returns>
/// <exception cref="ArgumentNullException"><paramref name="predicate"/> is null.</exception>
public static Recorded<Notification<T>> OnError<T>(long ticks, Func<Exception, bool> predicate)
{
if (predicate == null)
throw new ArgumentNullException("predicate");
return new Recorded<Notification<T>>(ticks, new OnErrorPredicate<T>(predicate));
}
/// <summary>
/// Factory method for an OnError notification record at a given time with a given error, using inference to determine the type of <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The element type for the resulting notification object.</typeparam>
/// <param name="ticks">Recorded virtual time the OnError notification occurs.</param>
/// <param name="exception">Recorded exception stored in the OnError notification.</param>
/// <param name="witness">Object solely used to infer the type of the <typeparamref name="T"/> type parameter. This parameter is typically used when creating a sequence of anonymously typed elements.</param>
/// <returns>Recorded OnError notification.</returns>
/// <exception cref="ArgumentNullException"><paramref name="exception"/> is null.</exception>
public static Recorded<Notification<T>> OnError<T>(long ticks, Exception exception, T witness)
{
if (exception == null)
throw new ArgumentNullException("exception");
return new Recorded<Notification<T>>(ticks, Notification.CreateOnError<T>(exception));
}
/// <summary>
/// Factory method for writing an assert that checks for an OnError notification record at a given time, using the specified predicate to check the exception and inference to determine the type of <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The element type for the resulting notification object.</typeparam>
/// <param name="ticks">Recorded virtual time the OnError notification occurs.</param>
/// <param name="predicate">Predicate function to check the OnError notification value against an expected exception.</param>
/// <param name="witness">Object solely used to infer the type of the <typeparamref name="T"/> type parameter. This parameter is typically used when creating a sequence of anonymously typed elements.</param>
/// <returns>Recorded OnError notification with a predicate to assert a given exception.</returns>
/// <exception cref="ArgumentNullException"><paramref name="predicate"/> is null.</exception>
public static Recorded<Notification<T>> OnError<T>(long ticks, Func<Exception, bool> predicate, T witness)
{
if (predicate == null)
throw new ArgumentNullException("predicate");
return new Recorded<Notification<T>>(ticks, new OnErrorPredicate<T>(predicate));
}
/// <summary>
/// Factory method for a subscription record based on a given subscription and disposal time.
/// </summary>
/// <param name="start">Virtual time indicating when the subscription was created.</param>
/// <param name="end">Virtual time indicating when the subscription was disposed.</param>
/// <returns>Subscription object.</returns>
public static Subscription Subscribe(long start, long end)
{
return new Subscription(start, end);
}
/// <summary>
/// Factory method for a subscription record based on a given subscription time.
/// </summary>
/// <param name="start">Virtual time indicating when the subscription was created.</param>
/// <returns>Subscription object.</returns>
public static Subscription Subscribe(long start)
{
return new Subscription(start);
}
#region Predicate-based notification assert helper classes
class OnNextPredicate<T> : PredicateNotification<T>
{
private readonly Func<T, bool> _predicate;
public OnNextPredicate(Func<T, bool> predicate)
{
_predicate = predicate;
}
public override bool Equals(Notification<T> other)
{
if (Object.ReferenceEquals(this, other))
return true;
if (Object.ReferenceEquals(other, null))
return false;
if (other.Kind != NotificationKind.OnNext)
return false;
return _predicate(other.Value);
}
}
class OnErrorPredicate<T> : PredicateNotification<T>
{
private readonly Func<Exception, bool> _predicate;
public OnErrorPredicate(Func<Exception, bool> predicate)
{
_predicate = predicate;
}
public override bool Equals(Notification<T> other)
{
if (Object.ReferenceEquals(this, other))
return true;
if (Object.ReferenceEquals(other, null))
return false;
if (other.Kind != NotificationKind.OnError)
return false;
return _predicate(other.Exception);
}
}
abstract class PredicateNotification<T> : Notification<T>
{
#region Non-implemented members (by design)
public override T Value
{
get { throw new NotSupportedException(); }
}
public override bool HasValue
{
get { throw new NotSupportedException(); }
}
public override Exception Exception
{
get { throw new NotSupportedException(); }
}
public override NotificationKind Kind
{
get { throw new NotSupportedException(); }
}
public override void Accept(IObserver<T> observer)
{
throw new NotSupportedException();
}
public override TResult Accept<TResult>(IObserver<T, TResult> observer)
{
throw new NotSupportedException();
}
public override void Accept(Action<T> onNext, Action<Exception> onError, Action onCompleted)
{
throw new NotSupportedException();
}
public override TResult Accept<TResult>(Func<T, TResult> onNext, Func<Exception, TResult> onError, Func<TResult> onCompleted)
{
throw new NotSupportedException();
}
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,108 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
namespace Microsoft.Reactive.Testing
{
/// <summary>
/// Record of a value including the virtual time it was produced on.
/// </summary>
/// <typeparam name="T">Type of the value.</typeparam>
#if !NO_DEBUGGER_ATTRIBUTES
[DebuggerDisplay("{Value}@{Time}")]
#endif
#if !NO_SERIALIZABLE
[Serializable]
#endif
public struct Recorded<T> : IEquatable<Recorded<T>>
{
private readonly long _time;
private readonly T _value;
/// <summary>
/// Gets the virtual time the value was produced on.
/// </summary>
public long Time { get { return _time; } }
/// <summary>
/// Gets the recorded value.
/// </summary>
public T Value { get { return _value; } }
/// <summary>
/// Creates a new object recording the production of the specified value at the given virtual time.
/// </summary>
/// <param name="time">Virtual time the value was produced on.</param>
/// <param name="value">Value that was produced.</param>
public Recorded(long time, T value)
{
_time = time;
_value = value;
}
/// <summary>
/// Checks whether the given recorded object is equal to the current instance.
/// </summary>
/// <param name="other">Recorded object to check for equality.</param>
/// <returns>true if both objects are equal; false otherwise.</returns>
public bool Equals(Recorded<T> other)
{
return Time == other.Time && EqualityComparer<T>.Default.Equals(Value, other.Value);
}
/// <summary>
/// Determines whether the two specified Recorded&lt;T&gt; values have the same Time and Value.
/// </summary>
/// <param name="left">The first Recorded&lt;T&gt; value to compare.</param>
/// <param name="right">The second Recorded&lt;T&gt; value to compare.</param>
/// <returns>true if the first Recorded&lt;T&gt; value has the same Time and Value as the second Recorded&lt;T&gt; value; otherwise, false.</returns>
public static bool operator ==(Recorded<T> left, Recorded<T> right)
{
return left.Equals(right);
}
/// <summary>
/// Determines whether the two specified Recorded&lt;T&gt; values don't have the same Time and Value.
/// </summary>
/// <param name="left">The first Recorded&lt;T&gt; value to compare.</param>
/// <param name="right">The second Recorded&lt;T&gt; value to compare.</param>
/// <returns>true if the first Recorded&lt;T&gt; value has a different Time or Value as the second Recorded&lt;T&gt; value; otherwise, false.</returns>
public static bool operator !=(Recorded<T> left, Recorded<T> right)
{
return !left.Equals(right);
}
/// <summary>
/// Determines whether the specified System.Object is equal to the current Recorded&lt;T&gt; value.
/// </summary>
/// <param name="obj">The System.Object to compare with the current Recorded&lt;T&gt; value.</param>
/// <returns>true if the specified System.Object is equal to the current Recorded&lt;T&gt; value; otherwise, false.</returns>
public override bool Equals(object obj)
{
if (obj is Recorded<T>)
return Equals((Recorded<T>)obj);
return false;
}
/// <summary>
/// Returns the hash code for the current Recorded&lt;T&gt; value.
/// </summary>
/// <returns>A hash code for the current Recorded&lt;T&gt; value.</returns>
public override int GetHashCode()
{
return Time.GetHashCode() + EqualityComparer<T>.Default.GetHashCode(Value);
}
/// <summary>
/// Returns a string representation of the current Recorded&lt;T&gt; value.
/// </summary>
/// <returns>String representation of the current Recorded&lt;T&gt; value.</returns>
public override string ToString()
{
return Value.ToString() + "@" + Time.ToString(CultureInfo.CurrentCulture);
}
}
}

View File

@@ -0,0 +1,124 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Globalization;
namespace Microsoft.Reactive.Testing
{
/// <summary>
/// Records information about subscriptions to and unsubscriptions from observable sequences.
/// </summary>
#if !NO_DEBUGGER_ATTRIBUTES
[DebuggerDisplay("({Subscribe}, {Unsubscribe})")]
#endif
#if !NO_SERIALIZABLE
[Serializable]
#endif
public struct Subscription : IEquatable<Subscription>
{
/// <summary>
/// Infinite virtual time value, used to indicate an unsubscription never took place.
/// </summary>
public const long Infinite = long.MaxValue;
private long _subscribe;
private long _unsubscribe;
/// <summary>
/// Gets the subscription virtual time.
/// </summary>
public long Subscribe { get { return _subscribe; } }
/// <summary>
/// Gets the unsubscription virtual time.
/// </summary>
public long Unsubscribe { get { return _unsubscribe; } }
/// <summary>
/// Creates a new subscription object with the given virtual subscription time.
/// </summary>
/// <param name="subscribe">Virtual time at which the subscription occurred.</param>-
public Subscription(long subscribe)
{
_subscribe = subscribe;
_unsubscribe = Infinite;
}
/// <summary>
/// Creates a new subscription object with the given virtual subscription and unsubscription time.
/// </summary>
/// <param name="subscribe">Virtual time at which the subscription occurred.</param>
/// <param name="unsubscribe">Virtual time at which the unsubscription occurred.</param>
public Subscription(long subscribe, long unsubscribe)
{
_subscribe = subscribe;
_unsubscribe = unsubscribe;
}
/// <summary>
/// Checks whether the given subscription is equal to the current instance.
/// </summary>
/// <param name="other">Subscription object to check for equality.</param>
/// <returns>true if both objects are equal; false otherwise.</returns>
public bool Equals(Subscription other)
{
return Subscribe == other.Subscribe && Unsubscribe == other.Unsubscribe;
}
/// <summary>
/// Determines whether the two specified Subscription values have the same Subscribe and Unsubscribe.
/// </summary>
/// <param name="left">The first Subscription value to compare.</param>
/// <param name="right">The second Subscription value to compare.</param>
/// <returns>true if the first Subscription value has the same Subscribe and Unsubscribe as the second Subscription value; otherwise, false.</returns>
public static bool operator==(Subscription left, Subscription right)
{
return left.Equals(right);
}
/// <summary>
/// Determines whether the two specified Subscription values don't have the same Subscribe and Unsubscribe.
/// </summary>
/// <param name="left">The first Subscription value to compare.</param>
/// <param name="right">The second Subscription value to compare.</param>
/// <returns>true if the first Subscription value has a different Subscribe or Unsubscribe as the second Subscription value; otherwise, false.</returns>
public static bool operator !=(Subscription left, Subscription right)
{
return !left.Equals(right);
}
/// <summary>
/// Determines whether the specified System.Object is equal to the current Subscription value.
/// </summary>
/// <param name="obj">The System.Object to compare with the current Subscription value.</param>
/// <returns>true if the specified System.Object is equal to the current Subscription value; otherwise, false.</returns>
public override bool Equals(object obj)
{
if (obj is Subscription)
return Equals((Subscription)obj);
return false;
}
/// <summary>
/// Returns the hash code for the current Subscription value.
/// </summary>
/// <returns>A hash code for the current Subscription value.</returns>
public override int GetHashCode()
{
return Subscribe.GetHashCode() ^ Unsubscribe.GetHashCode();
}
/// <summary>
/// Returns a string representation of the current Subscription value.
/// </summary>
/// <returns>String representation of the current Subscription value.</returns>
public override string ToString()
{
if (Unsubscribe == Infinite)
return string.Format(CultureInfo.CurrentCulture, "({0}, Infinite)", Subscribe);
else
return string.Format(CultureInfo.CurrentCulture, "({0}, {1})", Subscribe, Unsubscribe);
}
}
}

View File

@@ -0,0 +1,163 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
namespace Microsoft.Reactive.Testing
{
/// <summary>
/// Virtual time scheduler used for testing applications and libraries built using Reactive Extensions.
/// </summary>
public class TestScheduler : VirtualTimeScheduler<long, long>
{
/// <summary>
/// Schedules an action to be executed at the specified virtual time.
/// </summary>
/// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
/// <param name="state">State passed to the action to be executed.</param>
/// <param name="action">Action to be executed.</param>
/// <param name="dueTime">Absolute virtual time at which to execute the action.</param>
/// <returns>Disposable object used to cancel the scheduled action (best effort).</returns>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
public override IDisposable ScheduleAbsolute<TState>(TState state, long dueTime, Func<IScheduler, TState, IDisposable> action)
{
if (dueTime <= Clock)
dueTime = Clock + 1;
return base.ScheduleAbsolute<TState>(state, dueTime, action);
}
/// <summary>
/// Adds a relative virtual time to an absolute virtual time value.
/// </summary>
/// <param name="absolute">Absolute virtual time value.</param>
/// <param name="relative">Relative virtual time value to add.</param>
/// <returns>Resulting absolute virtual time sum value.</returns>
protected override long Add(long absolute, long relative)
{
return absolute + relative;
}
/// <summary>
/// Converts the absolute virtual time value to a DateTimeOffset value.
/// </summary>
/// <param name="absolute">Absolute virtual time value to convert.</param>
/// <returns>Corresponding DateTimeOffset value.</returns>
protected override DateTimeOffset ToDateTimeOffset(long absolute)
{
return new DateTimeOffset(absolute, TimeSpan.Zero);
}
/// <summary>
/// Converts the TimeSpan value to a relative virtual time value.
/// </summary>
/// <param name="timeSpan">TimeSpan value to convert.</param>
/// <returns>Corresponding relative virtual time value.</returns>
protected override long ToRelative(TimeSpan timeSpan)
{
return timeSpan.Ticks;
}
/// <summary>
/// Starts the test scheduler and uses the specified virtual times to invoke the factory function, subscribe to the resulting sequence, and dispose the subscription.
/// </summary>
/// <typeparam name="T">The element type of the observable sequence being tested.</typeparam>
/// <param name="create">Factory method to create an observable sequence.</param>
/// <param name="created">Virtual time at which to invoke the factory to create an observable sequence.</param>
/// <param name="subscribed">Virtual time at which to subscribe to the created observable sequence.</param>
/// <param name="disposed">Virtual time at which to dispose the subscription.</param>
/// <returns>Observer with timestamped recordings of notification messages that were received during the virtual time window when the subscription to the source sequence was active.</returns>
/// <exception cref="ArgumentNullException"><paramref name="create"/> is null.</exception>
public ITestableObserver<T> Start<T>(Func<IObservable<T>> create, long created, long subscribed, long disposed)
{
if (create == null)
throw new ArgumentNullException("create");
var source = default(IObservable<T>);
var subscription = default(IDisposable);
var observer = CreateObserver<T>();
ScheduleAbsolute(default(object), created, (scheduler, state) => { source = create(); return Disposable.Empty; });
ScheduleAbsolute(default(object), subscribed, (scheduler, state) => { subscription = source.Subscribe(observer); return Disposable.Empty; });
ScheduleAbsolute(default(object), disposed, (scheduler, state) => { subscription.Dispose(); return Disposable.Empty; });
Start();
return observer;
}
/// <summary>
/// Starts the test scheduler and uses the specified virtual time to dispose the subscription to the sequence obtained through the factory function.
/// Default virtual times are used for <see cref="ReactiveTest.Created">factory invocation</see> and <see cref="ReactiveTest.Subscribed">sequence subscription</see>.
/// </summary>
/// <typeparam name="T">The element type of the observable sequence being tested.</typeparam>
/// <param name="create">Factory method to create an observable sequence.</param>
/// <param name="disposed">Virtual time at which to dispose the subscription.</param>
/// <returns>Observer with timestamped recordings of notification messages that were received during the virtual time window when the subscription to the source sequence was active.</returns>
/// <exception cref="ArgumentNullException"><paramref name="create"/> is null.</exception>
public ITestableObserver<T> Start<T>(Func<IObservable<T>> create, long disposed)
{
if (create == null)
throw new ArgumentNullException("create");
return Start(create, ReactiveTest.Created, ReactiveTest.Subscribed, disposed);
}
/// <summary>
/// Starts the test scheduler and uses default virtual times to <see cref="ReactiveTest.Created">invoke the factory function</see>, to <see cref="ReactiveTest.Subscribed">subscribe to the resulting sequence</see>, and to <see cref="ReactiveTest.Disposed">dispose the subscription</see>.
/// </summary>
/// <typeparam name="T">The element type of the observable sequence being tested.</typeparam>
/// <param name="create">Factory method to create an observable sequence.</param>
/// <returns>Observer with timestamped recordings of notification messages that were received during the virtual time window when the subscription to the source sequence was active.</returns>
/// <exception cref="ArgumentNullException"><paramref name="create"/> is null.</exception>
public ITestableObserver<T> Start<T>(Func<IObservable<T>> create)
{
if (create == null)
throw new ArgumentNullException("create");
return Start(create, ReactiveTest.Created, ReactiveTest.Subscribed, ReactiveTest.Disposed);
}
/// <summary>
/// Creates a hot observable using the specified timestamped notification messages.
/// </summary>
/// <typeparam name="T">The element type of the observable sequence being created.</typeparam>
/// <param name="messages">Notifications to surface through the created sequence at their specified absolute virtual times.</param>
/// <returns>Hot observable sequence that can be used to assert the timing of subscriptions and notifications.</returns>
/// <exception cref="ArgumentNullException"><paramref name="messages"/> is null.</exception>
public ITestableObservable<T> CreateHotObservable<T>(params Recorded<Notification<T>>[] messages)
{
if (messages == null)
throw new ArgumentNullException("messages");
return new HotObservable<T>(this, messages);
}
/// <summary>
/// Creates a cold observable using the specified timestamped notification messages.
/// </summary>
/// <typeparam name="T">The element type of the observable sequence being created.</typeparam>
/// <param name="messages">Notifications to surface through the created sequence at their specified virtual time offsets from the sequence subscription time.</param>
/// <returns>Cold observable sequence that can be used to assert the timing of subscriptions and notifications.</returns>
/// <exception cref="ArgumentNullException"><paramref name="messages"/> is null.</exception>
public ITestableObservable<T> CreateColdObservable<T>(params Recorded<Notification<T>>[] messages)
{
if (messages == null)
throw new ArgumentNullException("messages");
return new ColdObservable<T>(this, messages);
}
/// <summary>
/// Creates an observer that records received notification messages and timestamps those.
/// </summary>
/// <typeparam name="T">The element type of the observer being created.</typeparam>
/// <returns>Observer that can be used to assert the timing of received notifications.</returns>
public ITestableObserver<T> CreateObserver<T>()
{
return new MockObserver<T>(this);
}
}
}