Commit 5633bbb2 authored by RogerBest's avatar RogerBest

Updated to NUnit 3.0.0 Beta 5

parent 2bca2a52
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BookSleeve; using BookSleeve;
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
[TestFixture] [TestFixture]
public class BasicOpsTests : TestBase public class BasicOpsTests : TestBase
{ {
[Test] [Test]
[TestCase(true)] [TestCase(true)]
[TestCase(false)] [TestCase(false)]
public void PingOnce(bool preserveOrder) public void PingOnce(bool preserveOrder)
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
muxer.PreserveAsyncOrder = preserveOrder; muxer.PreserveAsyncOrder = preserveOrder;
var conn = muxer.GetDatabase(); var conn = muxer.GetDatabase();
var task = conn.PingAsync(); var task = conn.PingAsync();
var duration = muxer.Wait(task); var duration = muxer.Wait(task);
Console.WriteLine("Ping took: " + duration); Console.WriteLine("Ping took: " + duration);
Assert.IsTrue(duration.TotalMilliseconds > 0); Assert.IsTrue(duration.TotalMilliseconds > 0);
} }
} }
[Test] [Test]
[TestCase(true)] [TestCase(true)]
[TestCase(false)] [TestCase(false)]
public void RapidDispose(bool preserverOrder) public void RapidDispose(bool preserverOrder)
{ {
RedisKey key = Me(); RedisKey key = Me();
using (var primary = Create()) using (var primary = Create())
{ {
var conn = primary.GetDatabase(); var conn = primary.GetDatabase();
conn.KeyDelete(key); conn.KeyDelete(key);
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
using (var secondary = Create(fail: true)) using (var secondary = Create(fail: true))
{ {
secondary.GetDatabase().StringIncrement(key, flags: CommandFlags.FireAndForget); secondary.GetDatabase().StringIncrement(key, flags: CommandFlags.FireAndForget);
} }
} }
Assert.AreEqual(10, (int)conn.StringGet(key)); Assert.AreEqual(10, (int)conn.StringGet(key));
} }
} }
[Test] [Test]
[TestCase(true)] [TestCase(true)]
[TestCase(false)] [TestCase(false)]
public void PingMany(bool preserveOrder) public void PingMany(bool preserveOrder)
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
muxer.PreserveAsyncOrder = preserveOrder; muxer.PreserveAsyncOrder = preserveOrder;
var conn = muxer.GetDatabase(); var conn = muxer.GetDatabase();
var tasks = new Task<TimeSpan>[10000]; var tasks = new Task<TimeSpan>[10000];
for (int i = 0; i < tasks.Length; i++) for (int i = 0; i < tasks.Length; i++)
{ {
tasks[i] = conn.PingAsync(); tasks[i] = conn.PingAsync();
} }
muxer.WaitAll(tasks); muxer.WaitAll(tasks);
Assert.IsTrue(tasks[0].Result.TotalMilliseconds > 0); Assert.IsTrue(tasks[0].Result.TotalMilliseconds > 0);
Assert.IsTrue(tasks[tasks.Length - 1].Result.TotalMilliseconds > 0); Assert.IsTrue(tasks[tasks.Length - 1].Result.TotalMilliseconds > 0);
} }
} }
[Test] [Test]
[ExpectedException(typeof(ArgumentException), ExpectedMessage = @"A null key is not valid in this context")] // [ExpectedException(typeof(ArgumentException), ExpectedMessage = @"A null key is not valid in this context")]
public void GetWithNullKey() public void GetWithNullKey()
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
var db = muxer.GetDatabase(); var db = muxer.GetDatabase();
string key = null; string key = null;
db.StringGet(key); //db.StringGet(key);
} ArgumentException ex = Assert.Throws(typeof(ArgumentException), delegate { db.StringGet(key); }) as ArgumentException;
} Assert.That(ex.Message.Equals( @"A null key is not valid in this context"));
}
[Test] }
[ExpectedException(typeof(ArgumentException), ExpectedMessage = @"A null key is not valid in this context")]
public void SetWithNullKey() [Test]
{ // [ExpectedException(typeof(ArgumentException), ExpectedMessage = @"A null key is not valid in this context")]
using (var muxer = Create()) public void SetWithNullKey()
{ {
var db = muxer.GetDatabase(); using (var muxer = Create())
string key = null, value = "abc"; {
db.StringSet(key, value); var db = muxer.GetDatabase();
} string key = null, value = "abc";
} // db.StringSet(key, value);
ArgumentException ex = Assert.Throws(typeof(ArgumentException), delegate { db.StringSet(key, value); }) as ArgumentException;
[Test] Assert.That(ex.Message.Equals(@"A null key is not valid in this context"));
public void SetWithNullValue()
{ }
using (var muxer = Create()) }
{
var db = muxer.GetDatabase(); [Test]
string key = Me(), value = null; public void SetWithNullValue()
db.KeyDelete(key, CommandFlags.FireAndForget); {
using (var muxer = Create())
db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); {
Assert.IsTrue(db.KeyExists(key)); var db = muxer.GetDatabase();
db.StringSet(key, value); string key = Me(), value = null;
db.KeyDelete(key, CommandFlags.FireAndForget);
var actual = (string)db.StringGet(key);
Assert.IsNull(actual); db.StringSet(key, "abc", flags: CommandFlags.FireAndForget);
Assert.IsFalse(db.KeyExists(key)); Assert.IsTrue(db.KeyExists(key));
} db.StringSet(key, value);
}
var actual = (string)db.StringGet(key);
[Test] Assert.IsNull(actual);
public void SetWithDefaultValue() Assert.IsFalse(db.KeyExists(key));
{ }
using (var muxer = Create()) }
{
var db = muxer.GetDatabase(); [Test]
string key = Me(); public void SetWithDefaultValue()
var value = default(RedisValue); // this is kinda 0... ish {
db.KeyDelete(key, CommandFlags.FireAndForget); using (var muxer = Create())
{
db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); var db = muxer.GetDatabase();
Assert.IsTrue(db.KeyExists(key)); string key = Me();
db.StringSet(key, value); var value = default(RedisValue); // this is kinda 0... ish
db.KeyDelete(key, CommandFlags.FireAndForget);
var actual = (string)db.StringGet(key);
Assert.IsNull(actual); db.StringSet(key, "abc", flags: CommandFlags.FireAndForget);
Assert.IsFalse(db.KeyExists(key)); Assert.IsTrue(db.KeyExists(key));
} db.StringSet(key, value);
}
var actual = (string)db.StringGet(key);
[Test] Assert.IsNull(actual);
public void SetWithZeroValue() Assert.IsFalse(db.KeyExists(key));
{ }
using (var muxer = Create()) }
{
var db = muxer.GetDatabase(); [Test]
string key = Me(); public void SetWithZeroValue()
long value = 0; {
db.KeyDelete(key, CommandFlags.FireAndForget); using (var muxer = Create())
{
db.StringSet(key, "abc", flags: CommandFlags.FireAndForget); var db = muxer.GetDatabase();
Assert.IsTrue(db.KeyExists(key)); string key = Me();
db.StringSet(key, value); long value = 0;
db.KeyDelete(key, CommandFlags.FireAndForget);
var actual = (string)db.StringGet(key);
Assert.AreEqual("0", actual); db.StringSet(key, "abc", flags: CommandFlags.FireAndForget);
Assert.IsTrue(db.KeyExists(key)); Assert.IsTrue(db.KeyExists(key));
} db.StringSet(key, value);
}
var actual = (string)db.StringGet(key);
[Test] Assert.AreEqual("0", actual);
[TestCase(true)] Assert.IsTrue(db.KeyExists(key));
[TestCase(false)] }
public void GetSetAsync(bool preserveOrder) }
{
using (var muxer = Create()) [Test]
{ [TestCase(true)]
muxer.PreserveAsyncOrder = preserveOrder; [TestCase(false)]
var conn = muxer.GetDatabase(); public void GetSetAsync(bool preserveOrder)
{
RedisKey key = Me(); using (var muxer = Create())
var d0 = conn.KeyDeleteAsync(key); {
var d1 = conn.KeyDeleteAsync(key); muxer.PreserveAsyncOrder = preserveOrder;
var g1 = conn.StringGetAsync(key); var conn = muxer.GetDatabase();
var s1 = conn.StringSetAsync(key, "123");
var g2 = conn.StringGetAsync(key); RedisKey key = Me();
var d2 = conn.KeyDeleteAsync(key); var d0 = conn.KeyDeleteAsync(key);
var d1 = conn.KeyDeleteAsync(key);
muxer.Wait(d0); var g1 = conn.StringGetAsync(key);
Assert.IsFalse(muxer.Wait(d1)); var s1 = conn.StringSetAsync(key, "123");
Assert.IsNull((string)muxer.Wait(g1)); var g2 = conn.StringGetAsync(key);
Assert.IsTrue(muxer.Wait(g1).IsNull); var d2 = conn.KeyDeleteAsync(key);
muxer.Wait(s1);
Assert.AreEqual("123", (string)muxer.Wait(g2)); muxer.Wait(d0);
Assert.AreEqual(123, (int)muxer.Wait(g2)); Assert.IsFalse(muxer.Wait(d1));
Assert.IsFalse(muxer.Wait(g2).IsNull); Assert.IsNull((string)muxer.Wait(g1));
Assert.IsTrue(muxer.Wait(d2)); Assert.IsTrue(muxer.Wait(g1).IsNull);
} muxer.Wait(s1);
} Assert.AreEqual("123", (string)muxer.Wait(g2));
Assert.AreEqual(123, (int)muxer.Wait(g2));
[Test] Assert.IsFalse(muxer.Wait(g2).IsNull);
[TestCase(true)] Assert.IsTrue(muxer.Wait(d2));
[TestCase(false)] }
public void GetSetSync(bool preserveOrder) }
{
using (var muxer = Create()) [Test]
{ [TestCase(true)]
muxer.PreserveAsyncOrder = preserveOrder; [TestCase(false)]
var conn = muxer.GetDatabase(); public void GetSetSync(bool preserveOrder)
{
RedisKey key = Me(); using (var muxer = Create())
conn.KeyDelete(key); {
var d1 = conn.KeyDelete(key); muxer.PreserveAsyncOrder = preserveOrder;
var g1 = conn.StringGet(key); var conn = muxer.GetDatabase();
conn.StringSet(key, "123");
var g2 = conn.StringGet(key); RedisKey key = Me();
var d2 = conn.KeyDelete(key); conn.KeyDelete(key);
var d1 = conn.KeyDelete(key);
Assert.IsFalse(d1); var g1 = conn.StringGet(key);
Assert.IsNull((string)g1); conn.StringSet(key, "123");
Assert.IsTrue(g1.IsNull); var g2 = conn.StringGet(key);
var d2 = conn.KeyDelete(key);
Assert.AreEqual("123", (string)g2);
Assert.AreEqual(123, (int)g2); Assert.IsFalse(d1);
Assert.IsFalse(g2.IsNull); Assert.IsNull((string)g1);
Assert.IsTrue(d2); Assert.IsTrue(g1.IsNull);
}
} Assert.AreEqual("123", (string)g2);
Assert.AreEqual(123, (int)g2);
[Test] Assert.IsFalse(g2.IsNull);
[TestCase(true, true)] Assert.IsTrue(d2);
[TestCase(true, false)] }
[TestCase(false, true)] }
[TestCase(false, false)]
public void MassiveBulkOpsAsync(bool preserveOrder, bool withContinuation) [Test]
{ [TestCase(true, true)]
#if DEBUG [TestCase(true, false)]
var oldAsyncCompletionCount = ConnectionMultiplexer.GetAsyncCompletionWorkerCount(); [TestCase(false, true)]
#endif [TestCase(false, false)]
using (var muxer = Create()) public void MassiveBulkOpsAsync(bool preserveOrder, bool withContinuation)
{ {
muxer.PreserveAsyncOrder = preserveOrder; #if DEBUG
RedisKey key = "MBOA"; var oldAsyncCompletionCount = ConnectionMultiplexer.GetAsyncCompletionWorkerCount();
var conn = muxer.GetDatabase(); #endif
muxer.Wait(conn.PingAsync()); using (var muxer = Create())
{
Action<Task> nonTrivial = delegate muxer.PreserveAsyncOrder = preserveOrder;
{ RedisKey key = "MBOA";
Thread.SpinWait(5); var conn = muxer.GetDatabase();
}; muxer.Wait(conn.PingAsync());
var watch = Stopwatch.StartNew();
for (int i = 0; i <= AsyncOpsQty; i++) Action<Task> nonTrivial = delegate
{ {
var t = conn.StringSetAsync(key, i); Thread.SpinWait(5);
if (withContinuation) t.ContinueWith(nonTrivial); };
} var watch = Stopwatch.StartNew();
int val = (int)muxer.Wait(conn.StringGetAsync(key)); for (int i = 0; i <= AsyncOpsQty; i++)
Assert.AreEqual(AsyncOpsQty, val); {
watch.Stop(); var t = conn.StringSetAsync(key, i);
Console.WriteLine("{2}: Time for {0} ops: {1}ms ({3}, {4}); ops/s: {5}", AsyncOpsQty, watch.ElapsedMilliseconds, Me(), if (withContinuation) t.ContinueWith(nonTrivial);
withContinuation ? "with continuation" : "no continuation", preserveOrder ? "preserve order" : "any order", }
AsyncOpsQty / watch.Elapsed.TotalSeconds); int val = (int)muxer.Wait(conn.StringGetAsync(key));
#if DEBUG Assert.AreEqual(AsyncOpsQty, val);
Console.WriteLine("Async completion workers: " + (ConnectionMultiplexer.GetAsyncCompletionWorkerCount() - oldAsyncCompletionCount)); watch.Stop();
#endif Console.WriteLine("{2}: Time for {0} ops: {1}ms ({3}, {4}); ops/s: {5}", AsyncOpsQty, watch.ElapsedMilliseconds, Me(),
} withContinuation ? "with continuation" : "no continuation", preserveOrder ? "preserve order" : "any order",
} AsyncOpsQty / watch.Elapsed.TotalSeconds);
#if DEBUG
[Test] Console.WriteLine("Async completion workers: " + (ConnectionMultiplexer.GetAsyncCompletionWorkerCount() - oldAsyncCompletionCount));
[TestCase(false, false)] #endif
[TestCase(true, true)] }
[TestCase(true, false)] }
public void GetWithExpiry(bool exists, bool hasExpiry)
{ [Test]
using(var conn = Create()) [TestCase(false, false)]
{ [TestCase(true, true)]
var db = conn.GetDatabase(); [TestCase(true, false)]
RedisKey key = Me(); public void GetWithExpiry(bool exists, bool hasExpiry)
db.KeyDelete(key); {
if (exists) using(var conn = Create())
{ {
if (hasExpiry) var db = conn.GetDatabase();
db.StringSet(key, "val", TimeSpan.FromMinutes(5)); RedisKey key = Me();
else db.KeyDelete(key);
db.StringSet(key, "val"); if (exists)
} {
var async = db.StringGetWithExpiryAsync(key); if (hasExpiry)
var syncResult = db.StringGetWithExpiry(key); db.StringSet(key, "val", TimeSpan.FromMinutes(5));
var asyncResult = db.Wait(async); else
db.StringSet(key, "val");
if(exists) }
{ var async = db.StringGetWithExpiryAsync(key);
Assert.AreEqual("val", (string)asyncResult.Value); var syncResult = db.StringGetWithExpiry(key);
Assert.AreEqual(hasExpiry, asyncResult.Expiry.HasValue); var asyncResult = db.Wait(async);
if (hasExpiry) Assert.IsTrue(asyncResult.Expiry.Value.TotalMinutes >= 4.9 && asyncResult.Expiry.Value.TotalMinutes <= 5);
Assert.AreEqual("val", (string)syncResult.Value); if(exists)
Assert.AreEqual(hasExpiry, syncResult.Expiry.HasValue); {
if (hasExpiry) Assert.IsTrue(syncResult.Expiry.Value.TotalMinutes >= 4.9 && syncResult.Expiry.Value.TotalMinutes <= 5); Assert.AreEqual("val", (string)asyncResult.Value);
} Assert.AreEqual(hasExpiry, asyncResult.Expiry.HasValue);
else if (hasExpiry) Assert.IsTrue(asyncResult.Expiry.Value.TotalMinutes >= 4.9 && asyncResult.Expiry.Value.TotalMinutes <= 5);
{ Assert.AreEqual("val", (string)syncResult.Value);
Assert.IsTrue(asyncResult.Value.IsNull); Assert.AreEqual(hasExpiry, syncResult.Expiry.HasValue);
Assert.IsFalse(asyncResult.Expiry.HasValue); if (hasExpiry) Assert.IsTrue(syncResult.Expiry.Value.TotalMinutes >= 4.9 && syncResult.Expiry.Value.TotalMinutes <= 5);
Assert.IsTrue(syncResult.Value.IsNull); }
Assert.IsFalse(syncResult.Expiry.HasValue); else
} {
} Assert.IsTrue(asyncResult.Value.IsNull);
} Assert.IsFalse(asyncResult.Expiry.HasValue);
[Test] Assert.IsTrue(syncResult.Value.IsNull);
[ExpectedException(typeof(RedisServerException), ExpectedMessage = "WRONGTYPE Operation against a key holding the wrong kind of value")] Assert.IsFalse(syncResult.Expiry.HasValue);
public void GetWithExpiryWrongTypeAsync() }
{ }
using (var conn = Create()) }
{ [Test]
var db = conn.GetDatabase(); // [ExpectedException(typeof(RedisServerException), ExpectedMessage = @"A null key is not valid in this context")]
RedisKey key = Me(); public void GetWithExpiryWrongTypeAsync()
db.KeyDelete(key); {
db.SetAdd(key, "abc"); using (var conn = Create())
try {
{ var db = conn.GetDatabase();
var async = db.Wait(db.StringGetWithExpiryAsync(key)); RedisKey key = Me();
} db.KeyDelete(key);
catch(AggregateException ex) db.SetAdd(key, "abc");
{ try
throw ex.InnerExceptions[0]; {
} var async = db.Wait(db.StringGetWithExpiryAsync(key));
Assert.Fail(); }
} catch(AggregateException ex)
} {
//throw ex.InnerExceptions[0];
[Test] Assert.That(ex.GetType().Equals(typeof(RedisServerException)));
[ExpectedException(typeof(RedisServerException), ExpectedMessage = "WRONGTYPE Operation against a key holding the wrong kind of value")] Assert.That(ex.InnerExceptions[0].Equals(@"A null key is not valid in this context"));
public void GetWithExpiryWrongTypeSync() }
{ Assert.Fail();
using (var conn = Create()) }
{ }
var db = conn.GetDatabase();
RedisKey key = Me(); [Test]
db.KeyDelete(key); // [ExpectedException(typeof(RedisServerException), ExpectedMessage = "WRONGTYPE Operation against a key holding the wrong kind of value")]
db.SetAdd(key, "abc"); public void GetWithExpiryWrongTypeSync()
db.StringGetWithExpiry(key); {
Assert.Fail(); Exception ex = Assert.Throws(typeof(RedisServerException), delegate
} {
} using (var conn = Create())
{
[Test] var db = conn.GetDatabase();
[TestCase(true, true, ResultCompletionMode.ConcurrentIfContinuation)] RedisKey key = Me();
[TestCase(true, false, ResultCompletionMode.ConcurrentIfContinuation)] db.KeyDelete(key);
[TestCase(false, true, ResultCompletionMode.ConcurrentIfContinuation)] db.SetAdd(key, "abc");
[TestCase(false, false, ResultCompletionMode.ConcurrentIfContinuation)] db.StringGetWithExpiry(key);
[TestCase(true, true, ResultCompletionMode.Concurrent)] Assert.Fail();
[TestCase(true, false, ResultCompletionMode.Concurrent)] }
[TestCase(false, true, ResultCompletionMode.Concurrent)] });
[TestCase(false, false, ResultCompletionMode.Concurrent)] Assert.That(ex.Message.Equals("WRONGTYPE Operation against a key holding the wrong kind of value"));
[TestCase(true, true, ResultCompletionMode.PreserveOrder)] }
[TestCase(true, false, ResultCompletionMode.PreserveOrder)]
[TestCase(false, true, ResultCompletionMode.PreserveOrder)] [Test]
[TestCase(false, false, ResultCompletionMode.PreserveOrder)] [TestCase(true, true, ResultCompletionMode.ConcurrentIfContinuation)]
public void MassiveBulkOpsAsyncOldStyle(bool withContinuation, bool suspendFlush, ResultCompletionMode completionMode) [TestCase(true, false, ResultCompletionMode.ConcurrentIfContinuation)]
{ [TestCase(false, true, ResultCompletionMode.ConcurrentIfContinuation)]
using (var conn = GetOldStyleConnection()) [TestCase(false, false, ResultCompletionMode.ConcurrentIfContinuation)]
{ [TestCase(true, true, ResultCompletionMode.Concurrent)]
const int db = 0; [TestCase(true, false, ResultCompletionMode.Concurrent)]
string key = "MBOQ"; [TestCase(false, true, ResultCompletionMode.Concurrent)]
conn.CompletionMode = completionMode; [TestCase(false, false, ResultCompletionMode.Concurrent)]
conn.Wait(conn.Server.Ping()); [TestCase(true, true, ResultCompletionMode.PreserveOrder)]
Action<Task> nonTrivial = delegate [TestCase(true, false, ResultCompletionMode.PreserveOrder)]
{ [TestCase(false, true, ResultCompletionMode.PreserveOrder)]
Thread.SpinWait(5); [TestCase(false, false, ResultCompletionMode.PreserveOrder)]
}; public void MassiveBulkOpsAsyncOldStyle(bool withContinuation, bool suspendFlush, ResultCompletionMode completionMode)
var watch = Stopwatch.StartNew(); {
using (var conn = GetOldStyleConnection())
if (suspendFlush) conn.SuspendFlush(); {
try const int db = 0;
{ string key = "MBOQ";
conn.CompletionMode = completionMode;
for (int i = 0; i <= AsyncOpsQty; i++) conn.Wait(conn.Server.Ping());
{ Action<Task> nonTrivial = delegate
var t = conn.Strings.Set(db, key, i); {
if (withContinuation) t.ContinueWith(nonTrivial); Thread.SpinWait(5);
} };
} finally var watch = Stopwatch.StartNew();
{
if (suspendFlush) conn.ResumeFlush(); if (suspendFlush) conn.SuspendFlush();
} try
int val = (int)conn.Wait(conn.Strings.GetInt64(db, key)); {
Assert.AreEqual(AsyncOpsQty, val);
watch.Stop(); for (int i = 0; i <= AsyncOpsQty; i++)
Console.WriteLine("{2}: Time for {0} ops: {1}ms ({3}, {4}, {5}); ops/s: {6}", AsyncOpsQty, watch.ElapsedMilliseconds, Me(), {
withContinuation ? "with continuation" : "no continuation", var t = conn.Strings.Set(db, key, i);
suspendFlush ? "suspend flush" : "flush at whim", if (withContinuation) t.ContinueWith(nonTrivial);
completionMode, AsyncOpsQty / watch.Elapsed.TotalSeconds); }
} } finally
} {
if (suspendFlush) conn.ResumeFlush();
}
[Test] int val = (int)conn.Wait(conn.Strings.GetInt64(db, key));
[TestCase(true, 1)] Assert.AreEqual(AsyncOpsQty, val);
[TestCase(false, 1)] watch.Stop();
[TestCase(true, 5)] Console.WriteLine("{2}: Time for {0} ops: {1}ms ({3}, {4}, {5}); ops/s: {6}", AsyncOpsQty, watch.ElapsedMilliseconds, Me(),
[TestCase(false, 5)] withContinuation ? "with continuation" : "no continuation",
[TestCase(true, 10)] suspendFlush ? "suspend flush" : "flush at whim",
[TestCase(false, 10)] completionMode, AsyncOpsQty / watch.Elapsed.TotalSeconds);
[TestCase(true, 50)] }
[TestCase(false, 50)] }
public void MassiveBulkOpsSync(bool preserveOrder, int threads)
{
int workPerThread = SyncOpsQty / threads; [Test]
using (var muxer = Create()) [TestCase(true, 1)]
{ [TestCase(false, 1)]
muxer.PreserveAsyncOrder = preserveOrder; [TestCase(true, 5)]
RedisKey key = "MBOS"; [TestCase(false, 5)]
var conn = muxer.GetDatabase(); [TestCase(true, 10)]
conn.KeyDelete(key); [TestCase(false, 10)]
#if DEBUG [TestCase(true, 50)]
long oldAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount(); [TestCase(false, 50)]
long oldWorkerCount = ConnectionMultiplexer.GetAsyncCompletionWorkerCount(); public void MassiveBulkOpsSync(bool preserveOrder, int threads)
#endif {
var timeTaken = RunConcurrent(delegate int workPerThread = SyncOpsQty / threads;
{ using (var muxer = Create())
for (int i = 0; i < workPerThread; i++) {
{ muxer.PreserveAsyncOrder = preserveOrder;
conn.StringIncrement(key); RedisKey key = "MBOS";
} var conn = muxer.GetDatabase();
}, threads); conn.KeyDelete(key);
#if DEBUG
int val = (int)conn.StringGet(key); long oldAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount();
Assert.AreEqual(workPerThread * threads, val); long oldWorkerCount = ConnectionMultiplexer.GetAsyncCompletionWorkerCount();
Console.WriteLine("{2}: Time for {0} ops on {4} threads: {1}ms ({3}); ops/s: {5}", #endif
threads * workPerThread, timeTaken.TotalMilliseconds, Me() var timeTaken = RunConcurrent(delegate
, preserveOrder ? "preserve order" : "any order", threads, (workPerThread * threads) / timeTaken.TotalSeconds); {
#if DEBUG for (int i = 0; i < workPerThread; i++)
long newAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount(); {
long newWorkerCount = ConnectionMultiplexer.GetAsyncCompletionWorkerCount(); conn.StringIncrement(key);
Console.WriteLine("ResultBox allocations: {0}; workers {1}", newAlloc - oldAlloc, newWorkerCount - oldWorkerCount); }
Assert.IsTrue(newAlloc - oldAlloc <= 2 * threads, "number of box allocations"); }, threads);
#endif
} int val = (int)conn.StringGet(key);
} Assert.AreEqual(workPerThread * threads, val);
[Test] Console.WriteLine("{2}: Time for {0} ops on {4} threads: {1}ms ({3}); ops/s: {5}",
[TestCase(ResultCompletionMode.Concurrent, 1)] threads * workPerThread, timeTaken.TotalMilliseconds, Me()
[TestCase(ResultCompletionMode.ConcurrentIfContinuation, 1)] , preserveOrder ? "preserve order" : "any order", threads, (workPerThread * threads) / timeTaken.TotalSeconds);
[TestCase(ResultCompletionMode.PreserveOrder, 1)] #if DEBUG
[TestCase(ResultCompletionMode.Concurrent, 5)] long newAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount();
[TestCase(ResultCompletionMode.ConcurrentIfContinuation, 5)] long newWorkerCount = ConnectionMultiplexer.GetAsyncCompletionWorkerCount();
[TestCase(ResultCompletionMode.PreserveOrder, 5)] Console.WriteLine("ResultBox allocations: {0}; workers {1}", newAlloc - oldAlloc, newWorkerCount - oldWorkerCount);
[TestCase(ResultCompletionMode.Concurrent, 10)] Assert.IsTrue(newAlloc - oldAlloc <= 2 * threads, "number of box allocations");
[TestCase(ResultCompletionMode.ConcurrentIfContinuation, 10)] #endif
[TestCase(ResultCompletionMode.PreserveOrder, 10)] }
[TestCase(ResultCompletionMode.Concurrent, 50)] }
[TestCase(ResultCompletionMode.ConcurrentIfContinuation, 50)] [Test]
[TestCase(ResultCompletionMode.PreserveOrder, 50)] [TestCase(ResultCompletionMode.Concurrent, 1)]
public void MassiveBulkOpsSyncOldStyle(ResultCompletionMode completionMode, int threads) [TestCase(ResultCompletionMode.ConcurrentIfContinuation, 1)]
{ [TestCase(ResultCompletionMode.PreserveOrder, 1)]
int workPerThread = SyncOpsQty / threads; [TestCase(ResultCompletionMode.Concurrent, 5)]
[TestCase(ResultCompletionMode.ConcurrentIfContinuation, 5)]
using (var conn = GetOldStyleConnection()) [TestCase(ResultCompletionMode.PreserveOrder, 5)]
{ [TestCase(ResultCompletionMode.Concurrent, 10)]
const int db = 0; [TestCase(ResultCompletionMode.ConcurrentIfContinuation, 10)]
string key = "MBOQ"; [TestCase(ResultCompletionMode.PreserveOrder, 10)]
conn.CompletionMode = completionMode; [TestCase(ResultCompletionMode.Concurrent, 50)]
conn.Wait(conn.Keys.Remove(db, key)); [TestCase(ResultCompletionMode.ConcurrentIfContinuation, 50)]
[TestCase(ResultCompletionMode.PreserveOrder, 50)]
var timeTaken = RunConcurrent(delegate public void MassiveBulkOpsSyncOldStyle(ResultCompletionMode completionMode, int threads)
{ {
for (int i = 0; i < workPerThread; i++) int workPerThread = SyncOpsQty / threads;
{
conn.Wait(conn.Strings.Increment(db, key)); using (var conn = GetOldStyleConnection())
} {
}, threads); const int db = 0;
string key = "MBOQ";
int val = (int)conn.Wait(conn.Strings.GetInt64(db, key)); conn.CompletionMode = completionMode;
Assert.AreEqual(workPerThread * threads, val); conn.Wait(conn.Keys.Remove(db, key));
Console.WriteLine("{2}: Time for {0} ops on {4} threads: {1}ms ({3}); ops/s: {5}", workPerThread * threads, timeTaken.TotalMilliseconds, Me(), var timeTaken = RunConcurrent(delegate
completionMode, threads, (workPerThread * threads) / timeTaken.TotalSeconds); {
} for (int i = 0; i < workPerThread; i++)
} {
conn.Wait(conn.Strings.Increment(db, key));
[Test] }
[TestCase(true, 1)] }, threads);
[TestCase(false, 1)]
[TestCase(true, 5)] int val = (int)conn.Wait(conn.Strings.GetInt64(db, key));
[TestCase(false, 5)] Assert.AreEqual(workPerThread * threads, val);
public void MassiveBulkOpsFireAndForget(bool preserveOrder, int threads)
{ Console.WriteLine("{2}: Time for {0} ops on {4} threads: {1}ms ({3}); ops/s: {5}", workPerThread * threads, timeTaken.TotalMilliseconds, Me(),
using (var muxer = Create()) completionMode, threads, (workPerThread * threads) / timeTaken.TotalSeconds);
{ }
muxer.PreserveAsyncOrder = preserveOrder; }
#if DEBUG
long oldAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount(); [Test]
#endif [TestCase(true, 1)]
RedisKey key = "MBOF"; [TestCase(false, 1)]
var conn = muxer.GetDatabase(); [TestCase(true, 5)]
conn.Ping(); [TestCase(false, 5)]
public void MassiveBulkOpsFireAndForget(bool preserveOrder, int threads)
conn.KeyDelete(key, CommandFlags.FireAndForget); {
int perThread = AsyncOpsQty / threads; using (var muxer = Create())
var elapsed = RunConcurrent(delegate {
{ muxer.PreserveAsyncOrder = preserveOrder;
for (int i = 0; i < perThread; i++) #if DEBUG
{ long oldAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount();
conn.StringIncrement(key, flags: CommandFlags.FireAndForget); #endif
} RedisKey key = "MBOF";
conn.Ping(); var conn = muxer.GetDatabase();
}, threads); conn.Ping();
var val = (long)conn.StringGet(key);
Assert.AreEqual(perThread * threads, val); conn.KeyDelete(key, CommandFlags.FireAndForget);
int perThread = AsyncOpsQty / threads;
Console.WriteLine("{2}: Time for {0} ops over {5} threads: {1:###,###}ms ({3}); ops/s: {4:###,###,##0}", var elapsed = RunConcurrent(delegate
val, elapsed.TotalMilliseconds, Me(), {
preserveOrder ? "preserve order" : "any order", for (int i = 0; i < perThread; i++)
val / elapsed.TotalSeconds, threads); {
#if DEBUG conn.StringIncrement(key, flags: CommandFlags.FireAndForget);
long newAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount(); }
Console.WriteLine("ResultBox allocations: {0}", conn.Ping();
newAlloc - oldAlloc); }, threads);
Assert.IsTrue(newAlloc - oldAlloc <= 4); var val = (long)conn.StringGet(key);
#endif Assert.AreEqual(perThread * threads, val);
}
} Console.WriteLine("{2}: Time for {0} ops over {5} threads: {1:###,###}ms ({3}); ops/s: {4:###,###,##0}",
val, elapsed.TotalMilliseconds, Me(),
preserveOrder ? "preserve order" : "any order",
#if DEBUG val / elapsed.TotalSeconds, threads);
[Test] #if DEBUG
[TestCase(true)] long newAlloc = ConnectionMultiplexer.GetResultBoxAllocationCount();
[TestCase(false)] Console.WriteLine("ResultBox allocations: {0}",
public void TestQuit(bool preserveOrder) newAlloc - oldAlloc);
{ Assert.IsTrue(newAlloc - oldAlloc <= 4);
SetExpectedAmbientFailureCount(1); #endif
using (var muxer = Create(allowAdmin: true)) }
{ }
muxer.PreserveAsyncOrder = preserveOrder;
var db = muxer.GetDatabase();
string key = Guid.NewGuid().ToString(); #if DEBUG
db.KeyDelete(key, CommandFlags.FireAndForget); [Test]
db.StringSet(key, key, flags: CommandFlags.FireAndForget); [TestCase(true)]
GetServer(muxer).Quit(CommandFlags.FireAndForget); [TestCase(false)]
var watch = Stopwatch.StartNew(); public void TestQuit(bool preserveOrder)
try {
{ SetExpectedAmbientFailureCount(1);
db.Ping(); using (var muxer = Create(allowAdmin: true))
Assert.Fail(); {
} muxer.PreserveAsyncOrder = preserveOrder;
catch (RedisConnectionException) { } var db = muxer.GetDatabase();
watch.Stop(); string key = Guid.NewGuid().ToString();
Console.WriteLine("Time to notice quit: {0}ms ({1})", watch.ElapsedMilliseconds, db.KeyDelete(key, CommandFlags.FireAndForget);
preserveOrder ? "preserve order" : "any order"); db.StringSet(key, key, flags: CommandFlags.FireAndForget);
Thread.Sleep(20); GetServer(muxer).Quit(CommandFlags.FireAndForget);
Debug.WriteLine("Pinging..."); var watch = Stopwatch.StartNew();
Assert.AreEqual(key, (string)db.StringGet(key)); try
} {
} db.Ping();
[Test] Assert.Fail();
[TestCase(true)] }
[TestCase(false)] catch (RedisConnectionException) { }
public void TestSevered(bool preserveOrder) watch.Stop();
{ Console.WriteLine("Time to notice quit: {0}ms ({1})", watch.ElapsedMilliseconds,
SetExpectedAmbientFailureCount(2); preserveOrder ? "preserve order" : "any order");
using (var muxer = Create(allowAdmin: true)) Thread.Sleep(20);
{ Debug.WriteLine("Pinging...");
muxer.PreserveAsyncOrder = preserveOrder; Assert.AreEqual(key, (string)db.StringGet(key));
var db = muxer.GetDatabase(); }
string key = Guid.NewGuid().ToString(); }
db.KeyDelete(key, CommandFlags.FireAndForget); [Test]
db.StringSet(key, key, flags: CommandFlags.FireAndForget); [TestCase(true)]
GetServer(muxer).SimulateConnectionFailure(); [TestCase(false)]
var watch = Stopwatch.StartNew(); public void TestSevered(bool preserveOrder)
db.Ping(); {
watch.Stop(); SetExpectedAmbientFailureCount(2);
Console.WriteLine("Time to re-establish: {0}ms ({1})", watch.ElapsedMilliseconds, using (var muxer = Create(allowAdmin: true))
preserveOrder ? "preserve order" : "any order"); {
Thread.Sleep(20); muxer.PreserveAsyncOrder = preserveOrder;
Debug.WriteLine("Pinging..."); var db = muxer.GetDatabase();
Assert.AreEqual(key, (string)db.StringGet(key)); string key = Guid.NewGuid().ToString();
} db.KeyDelete(key, CommandFlags.FireAndForget);
} db.StringSet(key, key, flags: CommandFlags.FireAndForget);
#endif GetServer(muxer).SimulateConnectionFailure();
var watch = Stopwatch.StartNew();
db.Ping();
watch.Stop();
[Test] Console.WriteLine("Time to re-establish: {0}ms ({1})", watch.ElapsedMilliseconds,
[TestCase(true)] preserveOrder ? "preserve order" : "any order");
[TestCase(false)] Thread.Sleep(20);
public void IncrAsync(bool preserveOrder) Debug.WriteLine("Pinging...");
{ Assert.AreEqual(key, (string)db.StringGet(key));
using (var muxer = Create()) }
{ }
muxer.PreserveAsyncOrder = preserveOrder; #endif
var conn = muxer.GetDatabase();
RedisKey key = Me();
conn.KeyDelete(key, CommandFlags.FireAndForget);
var nix = conn.KeyExistsAsync(key); [Test]
var a = conn.StringGetAsync(key); [TestCase(true)]
var b = conn.StringIncrementAsync(key); [TestCase(false)]
var c = conn.StringGetAsync(key); public void IncrAsync(bool preserveOrder)
var d = conn.StringIncrementAsync(key, 10); {
var e = conn.StringGetAsync(key); using (var muxer = Create())
var f = conn.StringDecrementAsync(key, 11); {
var g = conn.StringGetAsync(key); muxer.PreserveAsyncOrder = preserveOrder;
var h = conn.KeyExistsAsync(key); var conn = muxer.GetDatabase();
Assert.IsFalse(muxer.Wait(nix)); RedisKey key = Me();
Assert.IsTrue(muxer.Wait(a).IsNull); conn.KeyDelete(key, CommandFlags.FireAndForget);
Assert.AreEqual(0, (long)muxer.Wait(a)); var nix = conn.KeyExistsAsync(key);
Assert.AreEqual(1, muxer.Wait(b)); var a = conn.StringGetAsync(key);
Assert.AreEqual(1, (long)muxer.Wait(c)); var b = conn.StringIncrementAsync(key);
Assert.AreEqual(11, muxer.Wait(d)); var c = conn.StringGetAsync(key);
Assert.AreEqual(11, (long)muxer.Wait(e)); var d = conn.StringIncrementAsync(key, 10);
Assert.AreEqual(0, muxer.Wait(f)); var e = conn.StringGetAsync(key);
Assert.AreEqual(0, (long)muxer.Wait(g)); var f = conn.StringDecrementAsync(key, 11);
Assert.IsTrue(muxer.Wait(h)); var g = conn.StringGetAsync(key);
} var h = conn.KeyExistsAsync(key);
} Assert.IsFalse(muxer.Wait(nix));
[Test] Assert.IsTrue(muxer.Wait(a).IsNull);
[TestCase(true)] Assert.AreEqual(0, (long)muxer.Wait(a));
[TestCase(false)] Assert.AreEqual(1, muxer.Wait(b));
public void IncrSync(bool preserveOrder) Assert.AreEqual(1, (long)muxer.Wait(c));
{ Assert.AreEqual(11, muxer.Wait(d));
using (var muxer = Create()) Assert.AreEqual(11, (long)muxer.Wait(e));
{ Assert.AreEqual(0, muxer.Wait(f));
muxer.PreserveAsyncOrder = preserveOrder; Assert.AreEqual(0, (long)muxer.Wait(g));
var conn = muxer.GetDatabase(); Assert.IsTrue(muxer.Wait(h));
RedisKey key = Me(); }
conn.KeyDelete(key, CommandFlags.FireAndForget); }
var nix = conn.KeyExists(key); [Test]
var a = conn.StringGet(key); [TestCase(true)]
var b = conn.StringIncrement(key); [TestCase(false)]
var c = conn.StringGet(key); public void IncrSync(bool preserveOrder)
var d = conn.StringIncrement(key, 10); {
var e = conn.StringGet(key); using (var muxer = Create())
var f = conn.StringDecrement(key, 11); {
var g = conn.StringGet(key); muxer.PreserveAsyncOrder = preserveOrder;
var h = conn.KeyExists(key); var conn = muxer.GetDatabase();
Assert.IsFalse(nix); RedisKey key = Me();
Assert.IsTrue(a.IsNull); conn.KeyDelete(key, CommandFlags.FireAndForget);
Assert.AreEqual(0, (long)a); var nix = conn.KeyExists(key);
Assert.AreEqual(1, b); var a = conn.StringGet(key);
Assert.AreEqual(1, (long)c); var b = conn.StringIncrement(key);
Assert.AreEqual(11, d); var c = conn.StringGet(key);
Assert.AreEqual(11, (long)e); var d = conn.StringIncrement(key, 10);
Assert.AreEqual(0, f); var e = conn.StringGet(key);
Assert.AreEqual(0, (long)g); var f = conn.StringDecrement(key, 11);
Assert.IsTrue(h); var g = conn.StringGet(key);
} var h = conn.KeyExists(key);
} Assert.IsFalse(nix);
Assert.IsTrue(a.IsNull);
[Test] Assert.AreEqual(0, (long)a);
public void IncrDifferentSizes() Assert.AreEqual(1, b);
{ Assert.AreEqual(1, (long)c);
using (var muxer = Create()) Assert.AreEqual(11, d);
{ Assert.AreEqual(11, (long)e);
var db = muxer.GetDatabase(); Assert.AreEqual(0, f);
RedisKey key = Me(); Assert.AreEqual(0, (long)g);
db.KeyDelete(key, CommandFlags.FireAndForget); Assert.IsTrue(h);
int expected = 0; }
Incr(db, key, -129019, ref expected); }
Incr(db, key, -10023, ref expected);
Incr(db, key, -9933, ref expected); [Test]
Incr(db, key, -23, ref expected); public void IncrDifferentSizes()
Incr(db, key, -7, ref expected); {
Incr(db, key, -1, ref expected); using (var muxer = Create())
Incr(db, key, 0, ref expected); {
Incr(db, key, 1, ref expected); var db = muxer.GetDatabase();
Incr(db, key, 9, ref expected); RedisKey key = Me();
Incr(db, key, 11, ref expected); db.KeyDelete(key, CommandFlags.FireAndForget);
Incr(db, key, 345, ref expected); int expected = 0;
Incr(db, key, 4982, ref expected); Incr(db, key, -129019, ref expected);
Incr(db, key, 13091, ref expected); Incr(db, key, -10023, ref expected);
Incr(db, key, 324092, ref expected); Incr(db, key, -9933, ref expected);
Assert.AreNotEqual(0, expected); Incr(db, key, -23, ref expected);
var sum = (long)db.StringGet(key); Incr(db, key, -7, ref expected);
Assert.AreEqual(expected, sum); Incr(db, key, -1, ref expected);
} Incr(db, key, 0, ref expected);
} Incr(db, key, 1, ref expected);
Incr(db, key, 9, ref expected);
private void Incr(IDatabase database, RedisKey key, int delta, ref int total) Incr(db, key, 11, ref expected);
{ Incr(db, key, 345, ref expected);
database.StringIncrement(key, delta, CommandFlags.FireAndForget); Incr(db, key, 4982, ref expected);
total += delta; Incr(db, key, 13091, ref expected);
} Incr(db, key, 324092, ref expected);
Assert.AreNotEqual(0, expected);
[Test] var sum = (long)db.StringGet(key);
public void WrappedDatabasePrefixIntegration() Assert.AreEqual(expected, sum);
{ }
using (var conn = Create()) }
{
var db = conn.GetDatabase().WithKeyPrefix("abc"); private void Incr(IDatabase database, RedisKey key, int delta, ref int total)
db.KeyDelete("count"); {
db.StringIncrement("count"); database.StringIncrement(key, delta, CommandFlags.FireAndForget);
db.StringIncrement("count"); total += delta;
db.StringIncrement("count"); }
int count = (int)conn.GetDatabase().StringGet("abccount"); [Test]
Assert.AreEqual(3, count); public void WrappedDatabasePrefixIntegration()
} {
} using (var conn = Create())
} {
var db = conn.GetDatabase().WithKeyPrefix("abc");
} db.KeyDelete("count");
db.StringIncrement("count");
db.StringIncrement("count");
db.StringIncrement("count");
int count = (int)conn.GetDatabase().StringGet("abccount");
Assert.AreEqual(3, count);
}
}
}
}
...@@ -12,7 +12,8 @@ public sealed class BatchWrapperTests ...@@ -12,7 +12,8 @@ public sealed class BatchWrapperTests
private Mock<IBatch> mock; private Mock<IBatch> mock;
private BatchWrapper wrapper; private BatchWrapper wrapper;
[TestFixtureSetUp] //[TestFixtureSetUp]
[OneTimeSetUpAttribute]
public void Initialize() public void Initialize()
{ {
mock = new Mock<IBatch>(); mock = new Mock<IBatch>();
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
[TestFixture] [TestFixture]
public class Cluster : TestBase public class Cluster : TestBase
{ {
//private const string ClusterIp = "192.168.0.15"; // marc //private const string ClusterIp = "192.168.0.15"; // marc
private const string ClusterIp = "10.110.11.102"; // kmontrose private const string ClusterIp = "10.110.11.102"; // kmontrose
private const int ServerCount = 6, FirstPort = 7000; private const int ServerCount = 6, FirstPort = 7000;
protected override string GetConfiguration() protected override string GetConfiguration()
{ {
var server = ClusterIp; var server = ClusterIp;
if (string.Equals(Environment.MachineName, "MARC-LAPTOP", StringComparison.InvariantCultureIgnoreCase)) if (string.Equals(Environment.MachineName, "MARC-LAPTOP", StringComparison.InvariantCultureIgnoreCase))
{ {
server = "192.168.56.101"; server = "192.168.56.101";
} }
return string.Join(",", return string.Join(",",
from port in Enumerable.Range(FirstPort, ServerCount) from port in Enumerable.Range(FirstPort, ServerCount)
select server + ":" + port) + ",connectTimeout=10000"; select server + ":" + port) + ",connectTimeout=10000";
} }
[Test] [Test]
public void ExportConfiguration() public void ExportConfiguration()
{ {
if (File.Exists("cluster.zip")) File.Delete("cluster.zip"); if (File.Exists("cluster.zip")) File.Delete("cluster.zip");
Assert.IsFalse(File.Exists("cluster.zip")); Assert.IsFalse(File.Exists("cluster.zip"));
using (var muxer = Create(allowAdmin: true)) using (var muxer = Create(allowAdmin: true))
using(var file = File.Create("cluster.zip")) using(var file = File.Create("cluster.zip"))
{ {
muxer.ExportConfiguration(file); muxer.ExportConfiguration(file);
} }
Assert.IsTrue(File.Exists("cluster.zip")); Assert.IsTrue(File.Exists("cluster.zip"));
} }
[Test] [Test]
public void ConnectUsesSingleSocket() public void ConnectUsesSingleSocket()
{ {
for(int i = 0; i<10;i++) for(int i = 0; i<10;i++)
{ {
using (var muxer = Create(failMessage: i + ": ")) using (var muxer = Create(failMessage: i + ": "))
{ {
var eps = muxer.GetEndPoints(); var eps = muxer.GetEndPoints();
foreach (var ep in eps) foreach (var ep in eps)
{ {
var srv = muxer.GetServer(ep); var srv = muxer.GetServer(ep);
var counters = srv.GetCounters(); var counters = srv.GetCounters();
Assert.AreEqual(1, counters.Interactive.SocketCount, i + "; interactive, " + ep.ToString()); Assert.AreEqual(1, counters.Interactive.SocketCount, i + "; interactive, " + ep.ToString());
Assert.AreEqual(1, counters.Subscription.SocketCount, i + "; subscription, " + ep.ToString()); Assert.AreEqual(1, counters.Subscription.SocketCount, i + "; subscription, " + ep.ToString());
} }
} }
} }
} }
[Test] [Test]
public void CanGetTotalStats() public void CanGetTotalStats()
{ {
using(var muxer = Create()) using(var muxer = Create())
{ {
var counters = muxer.GetCounters(); var counters = muxer.GetCounters();
Console.WriteLine(counters); Console.WriteLine(counters);
} }
} }
[Test] [Test]
public void Connect() public void Connect()
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
var endpoints = muxer.GetEndPoints(); var endpoints = muxer.GetEndPoints();
Assert.AreEqual(ServerCount, endpoints.Length); Assert.AreEqual(ServerCount, endpoints.Length);
var expectedPorts = new HashSet<int>(Enumerable.Range(FirstPort, ServerCount)); var expectedPorts = new HashSet<int>(Enumerable.Range(FirstPort, ServerCount));
int masters = 0, slaves = 0; int masters = 0, slaves = 0;
var failed = new List<EndPoint>(); var failed = new List<EndPoint>();
foreach (var endpoint in endpoints) foreach (var endpoint in endpoints)
{ {
var server = muxer.GetServer(endpoint); var server = muxer.GetServer(endpoint);
if (!server.IsConnected) if (!server.IsConnected)
{ {
failed.Add(endpoint); failed.Add(endpoint);
} }
Assert.AreEqual(endpoint, server.EndPoint, "endpoint:" + endpoint); Assert.AreEqual(endpoint, server.EndPoint, "endpoint:" + endpoint);
Assert.IsInstanceOf<IPEndPoint>(endpoint, "endpoint-type:" + endpoint); Assert.IsInstanceOf<IPEndPoint>(endpoint, "endpoint-type:" + endpoint);
Assert.IsTrue(expectedPorts.Remove(((IPEndPoint)endpoint).Port), "port:" + endpoint); Assert.IsTrue(expectedPorts.Remove(((IPEndPoint)endpoint).Port), "port:" + endpoint);
Assert.AreEqual(ServerType.Cluster, server.ServerType, "server-type:" + endpoint); Assert.AreEqual(ServerType.Cluster, server.ServerType, "server-type:" + endpoint);
if (server.IsSlave) slaves++; if (server.IsSlave) slaves++;
else masters++; else masters++;
} }
if (failed.Count != 0) if (failed.Count != 0)
{ {
Console.WriteLine("{0} failues", failed.Count); Console.WriteLine("{0} failues", failed.Count);
foreach (var fail in failed) foreach (var fail in failed)
{ {
Console.WriteLine(fail); Console.WriteLine(fail);
} }
Assert.Fail("not all servers connected"); Assert.Fail("not all servers connected");
} }
Assert.AreEqual(ServerCount / 2, slaves, "slaves"); Assert.AreEqual(ServerCount / 2, slaves, "slaves");
Assert.AreEqual(ServerCount / 2, masters, "masters"); Assert.AreEqual(ServerCount / 2, masters, "masters");
} }
} }
[Test] [Test]
public void TestIdentity() public void TestIdentity()
{ {
using(var conn = Create()) using(var conn = Create())
{ {
RedisKey key = Guid.NewGuid().ToByteArray(); RedisKey key = Guid.NewGuid().ToByteArray();
var ep = conn.GetDatabase().IdentifyEndpoint(key); var ep = conn.GetDatabase().IdentifyEndpoint(key);
Assert.AreEqual(ep, conn.GetServer(ep).ClusterConfiguration.GetBySlot(key).EndPoint); Assert.AreEqual(ep, conn.GetServer(ep).ClusterConfiguration.GetBySlot(key).EndPoint);
} }
} }
[Test] [Test]
public void IntentionalWrongServer() public void IntentionalWrongServer()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
var endpoints = conn.GetEndPoints(); var endpoints = conn.GetEndPoints();
var servers = Array.ConvertAll(endpoints, e => conn.GetServer(e)); var servers = Array.ConvertAll(endpoints, e => conn.GetServer(e));
var key = Me(); var key = Me();
const string value = "abc"; const string value = "abc";
var db = conn.GetDatabase(); var db = conn.GetDatabase();
db.KeyDelete(key); db.KeyDelete(key);
db.StringSet(key, value); db.StringSet(key, value);
servers.First().Ping(); servers.First().Ping();
var config = servers.First().ClusterConfiguration; var config = servers.First().ClusterConfiguration;
Assert.IsNotNull(config); Assert.IsNotNull(config);
int slot = conn.HashSlot(key); int slot = conn.HashSlot(key);
var rightMasterNode = config.GetBySlot(key); var rightMasterNode = config.GetBySlot(key);
Assert.IsNotNull(rightMasterNode); Assert.IsNotNull(rightMasterNode);
#if DEBUG #if DEBUG
string a = conn.GetServer(rightMasterNode.EndPoint).StringGet(db.Database, key); string a = conn.GetServer(rightMasterNode.EndPoint).StringGet(db.Database, key);
Assert.AreEqual(value, a, "right master"); Assert.AreEqual(value, a, "right master");
var node = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId); var node = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId);
Assert.IsNotNull(node); Assert.IsNotNull(node);
if (node != null) if (node != null)
{ {
string b = conn.GetServer(node.EndPoint).StringGet(db.Database, key); string b = conn.GetServer(node.EndPoint).StringGet(db.Database, key);
Assert.AreEqual(value, b, "wrong master, allow redirect"); Assert.AreEqual(value, b, "wrong master, allow redirect");
try try
{ {
string c = conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect); string c = conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect);
Assert.Fail("wrong master, no redirect"); Assert.Fail("wrong master, no redirect");
} catch (RedisServerException ex) } catch (RedisServerException ex)
{ {
Assert.AreEqual("MOVED " + slot + " " + rightMasterNode.EndPoint.ToString(), ex.Message, "wrong master, no redirect"); Assert.AreEqual("MOVED " + slot + " " + rightMasterNode.EndPoint.ToString(), ex.Message, "wrong master, no redirect");
} }
} }
node = config.Nodes.FirstOrDefault(x => x.IsSlave && x.ParentNodeId == rightMasterNode.NodeId); node = config.Nodes.FirstOrDefault(x => x.IsSlave && x.ParentNodeId == rightMasterNode.NodeId);
Assert.IsNotNull(node); Assert.IsNotNull(node);
if (node != null) if (node != null)
{ {
string d = conn.GetServer(node.EndPoint).StringGet(db.Database, key); string d = conn.GetServer(node.EndPoint).StringGet(db.Database, key);
Assert.AreEqual(value, d, "right slave"); Assert.AreEqual(value, d, "right slave");
} }
node = config.Nodes.FirstOrDefault(x => x.IsSlave && x.ParentNodeId != rightMasterNode.NodeId); node = config.Nodes.FirstOrDefault(x => x.IsSlave && x.ParentNodeId != rightMasterNode.NodeId);
Assert.IsNotNull(node); Assert.IsNotNull(node);
if (node != null) if (node != null)
{ {
string e = conn.GetServer(node.EndPoint).StringGet(db.Database, key); string e = conn.GetServer(node.EndPoint).StringGet(db.Database, key);
Assert.AreEqual(value, e, "wrong slave, allow redirect"); Assert.AreEqual(value, e, "wrong slave, allow redirect");
try try
{ {
string f = conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect); string f = conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect);
Assert.Fail("wrong slave, no redirect"); Assert.Fail("wrong slave, no redirect");
} }
catch (RedisServerException ex) catch (RedisServerException ex)
{ {
Assert.AreEqual("MOVED " + slot + " " + rightMasterNode.EndPoint.ToString(), ex.Message, "wrong slave, no redirect"); Assert.AreEqual("MOVED " + slot + " " + rightMasterNode.EndPoint.ToString(), ex.Message, "wrong slave, no redirect");
} }
} }
#endif #endif
} }
} }
[Test] [Test]
[ExpectedException(typeof(RedisCommandException), ExpectedMessage = "Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot")] //[ExpectedException(typeof(RedisCommandException), ExpectedMessage = "Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot")]
public void TransactionWithMultiServerKeys() public void TransactionWithMultiServerKeys()
{ {
using(var muxer = Create()) Exception ex = Assert.Throws(typeof(RedisCommandException), delegate {
{ using (var muxer = Create())
// connect {
var cluster = muxer.GetDatabase(); // connect
var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]); var cluster = muxer.GetDatabase();
anyServer.Ping(); var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]);
Assert.AreEqual(ServerType.Cluster, anyServer.ServerType); anyServer.Ping();
var config = anyServer.ClusterConfiguration; Assert.AreEqual(ServerType.Cluster, anyServer.ServerType);
Assert.IsNotNull(config); var config = anyServer.ClusterConfiguration;
Assert.IsNotNull(config);
// invent 2 keys that we believe are served by different nodes
string x = Guid.NewGuid().ToString(), y; // invent 2 keys that we believe are served by different nodes
var xNode = config.GetBySlot(x); string x = Guid.NewGuid().ToString(), y;
int abort = 1000; var xNode = config.GetBySlot(x);
do int abort = 1000;
{ do
y = Guid.NewGuid().ToString(); {
} while (--abort > 0 && config.GetBySlot(y) == xNode); y = Guid.NewGuid().ToString();
if (abort == 0) Assert.Inconclusive("failed to find a different node to use"); } while (--abort > 0 && config.GetBySlot(y) == xNode);
var yNode = config.GetBySlot(y); if (abort == 0) Assert.Inconclusive("failed to find a different node to use");
Console.WriteLine("x={0}, served by {1}", x, xNode.NodeId); var yNode = config.GetBySlot(y);
Console.WriteLine("y={0}, served by {1}", y, yNode.NodeId); Console.WriteLine("x={0}, served by {1}", x, xNode.NodeId);
Assert.AreNotEqual(xNode.NodeId, yNode.NodeId, "same node"); Console.WriteLine("y={0}, served by {1}", y, yNode.NodeId);
Assert.AreNotEqual(xNode.NodeId, yNode.NodeId, "same node");
// wipe those keys
cluster.KeyDelete(x, CommandFlags.FireAndForget); // wipe those keys
cluster.KeyDelete(y, CommandFlags.FireAndForget); cluster.KeyDelete(x, CommandFlags.FireAndForget);
cluster.KeyDelete(y, CommandFlags.FireAndForget);
// create a transaction that attempts to assign both keys
var tran = cluster.CreateTransaction(); // create a transaction that attempts to assign both keys
tran.AddCondition(Condition.KeyNotExists(x)); var tran = cluster.CreateTransaction();
tran.AddCondition(Condition.KeyNotExists(y)); tran.AddCondition(Condition.KeyNotExists(x));
var setX = tran.StringSetAsync(x, "x-val"); tran.AddCondition(Condition.KeyNotExists(y));
var setY = tran.StringSetAsync(y, "y-val"); var setX = tran.StringSetAsync(x, "x-val");
bool success = tran.Execute(); var setY = tran.StringSetAsync(y, "y-val");
bool success = tran.Execute();
Assert.Fail("Expected single-slot rules to apply");
// the rest no longer applies while we are following single-slot rules Assert.Fail("Expected single-slot rules to apply");
// the rest no longer applies while we are following single-slot rules
//// check that everything was aborted
//Assert.IsFalse(success, "tran aborted"); //// check that everything was aborted
//Assert.IsTrue(setX.IsCanceled, "set x cancelled"); //Assert.IsFalse(success, "tran aborted");
//Assert.IsTrue(setY.IsCanceled, "set y cancelled"); //Assert.IsTrue(setX.IsCanceled, "set x cancelled");
//var existsX = cluster.KeyExistsAsync(x); //Assert.IsTrue(setY.IsCanceled, "set y cancelled");
//var existsY = cluster.KeyExistsAsync(y); //var existsX = cluster.KeyExistsAsync(x);
//Assert.IsFalse(cluster.Wait(existsX), "x exists"); //var existsY = cluster.KeyExistsAsync(y);
//Assert.IsFalse(cluster.Wait(existsY), "y exists"); //Assert.IsFalse(cluster.Wait(existsX), "x exists");
} //Assert.IsFalse(cluster.Wait(existsY), "y exists");
} }
});
[Test] Assert.That(ex.Message.Equals("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot"));
[ExpectedException(typeof(RedisCommandException), ExpectedMessage = "Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot")] }
public void TransactionWithSameServerKeys()
{ [Test]
using (var muxer = Create()) //[ExpectedException(typeof(RedisCommandException), ExpectedMessage = "Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot")]
{ public void TransactionWithSameServerKeys()
// connect {
var cluster = muxer.GetDatabase(); Exception ex = Assert.Throws(typeof(RedisCommandException), delegate
var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]); {
anyServer.Ping(); using (var muxer = Create())
var config = anyServer.ClusterConfiguration; {
Assert.IsNotNull(config); // connect
var cluster = muxer.GetDatabase();
// invent 2 keys that we believe are served by different nodes var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]);
string x = Guid.NewGuid().ToString(), y; anyServer.Ping();
var xNode = config.GetBySlot(x); var config = anyServer.ClusterConfiguration;
int abort = 1000; Assert.IsNotNull(config);
do
{ // invent 2 keys that we believe are served by different nodes
y = Guid.NewGuid().ToString(); string x = Guid.NewGuid().ToString(), y;
} while (--abort > 0 && config.GetBySlot(y) != xNode); var xNode = config.GetBySlot(x);
if (abort == 0) Assert.Inconclusive("failed to find a key with the same node to use"); int abort = 1000;
var yNode = config.GetBySlot(y); do
Console.WriteLine("x={0}, served by {1}", x, xNode.NodeId); {
Console.WriteLine("y={0}, served by {1}", y, yNode.NodeId); y = Guid.NewGuid().ToString();
Assert.AreEqual(xNode.NodeId, yNode.NodeId, "same node"); } while (--abort > 0 && config.GetBySlot(y) != xNode);
if (abort == 0) Assert.Inconclusive("failed to find a key with the same node to use");
// wipe those keys var yNode = config.GetBySlot(y);
cluster.KeyDelete(x, CommandFlags.FireAndForget); Console.WriteLine("x={0}, served by {1}", x, xNode.NodeId);
cluster.KeyDelete(y, CommandFlags.FireAndForget); Console.WriteLine("y={0}, served by {1}", y, yNode.NodeId);
Assert.AreEqual(xNode.NodeId, yNode.NodeId, "same node");
// create a transaction that attempts to assign both keys
var tran = cluster.CreateTransaction(); // wipe those keys
tran.AddCondition(Condition.KeyNotExists(x)); cluster.KeyDelete(x, CommandFlags.FireAndForget);
tran.AddCondition(Condition.KeyNotExists(y)); cluster.KeyDelete(y, CommandFlags.FireAndForget);
var setX = tran.StringSetAsync(x, "x-val");
var setY = tran.StringSetAsync(y, "y-val"); // create a transaction that attempts to assign both keys
bool success = tran.Execute(); var tran = cluster.CreateTransaction();
tran.AddCondition(Condition.KeyNotExists(x));
Assert.Fail("Expected single-slot rules to apply"); tran.AddCondition(Condition.KeyNotExists(y));
// the rest no longer applies while we are following single-slot rules var setX = tran.StringSetAsync(x, "x-val");
var setY = tran.StringSetAsync(y, "y-val");
//// check that everything was aborted bool success = tran.Execute();
//Assert.IsTrue(success, "tran aborted");
//Assert.IsFalse(setX.IsCanceled, "set x cancelled"); Assert.Fail("Expected single-slot rules to apply");
//Assert.IsFalse(setY.IsCanceled, "set y cancelled"); // the rest no longer applies while we are following single-slot rules
//var existsX = cluster.KeyExistsAsync(x);
//var existsY = cluster.KeyExistsAsync(y); //// check that everything was aborted
//Assert.IsTrue(cluster.Wait(existsX), "x exists"); //Assert.IsTrue(success, "tran aborted");
//Assert.IsTrue(cluster.Wait(existsY), "y exists"); //Assert.IsFalse(setX.IsCanceled, "set x cancelled");
} //Assert.IsFalse(setY.IsCanceled, "set y cancelled");
} //var existsX = cluster.KeyExistsAsync(x);
//var existsY = cluster.KeyExistsAsync(y);
[Test] //Assert.IsTrue(cluster.Wait(existsX), "x exists");
public void TransactionWithSameSlotKeys() //Assert.IsTrue(cluster.Wait(existsY), "y exists");
{ }
using (var muxer = Create()) });
{ Assert.That(ex.Message.Equals("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot"));
// connect }
var cluster = muxer.GetDatabase();
var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]); [Test]
anyServer.Ping(); public void TransactionWithSameSlotKeys()
var config = anyServer.ClusterConfiguration; {
Assert.IsNotNull(config); using (var muxer = Create())
{
// invent 2 keys that we believe are in the same slot // connect
var guid = Guid.NewGuid().ToString(); var cluster = muxer.GetDatabase();
string x = "/{" + guid + "}/foo", y = "/{" + guid + "}/bar"; var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]);
anyServer.Ping();
Assert.AreEqual(muxer.HashSlot(x), muxer.HashSlot(y)); var config = anyServer.ClusterConfiguration;
var xNode = config.GetBySlot(x); Assert.IsNotNull(config);
var yNode = config.GetBySlot(y);
Console.WriteLine("x={0}, served by {1}", x, xNode.NodeId); // invent 2 keys that we believe are in the same slot
Console.WriteLine("y={0}, served by {1}", y, yNode.NodeId); var guid = Guid.NewGuid().ToString();
Assert.AreEqual(xNode.NodeId, yNode.NodeId, "same node"); string x = "/{" + guid + "}/foo", y = "/{" + guid + "}/bar";
// wipe those keys Assert.AreEqual(muxer.HashSlot(x), muxer.HashSlot(y));
cluster.KeyDelete(x, CommandFlags.FireAndForget); var xNode = config.GetBySlot(x);
cluster.KeyDelete(y, CommandFlags.FireAndForget); var yNode = config.GetBySlot(y);
Console.WriteLine("x={0}, served by {1}", x, xNode.NodeId);
// create a transaction that attempts to assign both keys Console.WriteLine("y={0}, served by {1}", y, yNode.NodeId);
var tran = cluster.CreateTransaction(); Assert.AreEqual(xNode.NodeId, yNode.NodeId, "same node");
tran.AddCondition(Condition.KeyNotExists(x));
tran.AddCondition(Condition.KeyNotExists(y)); // wipe those keys
var setX = tran.StringSetAsync(x, "x-val"); cluster.KeyDelete(x, CommandFlags.FireAndForget);
var setY = tran.StringSetAsync(y, "y-val"); cluster.KeyDelete(y, CommandFlags.FireAndForget);
bool success = tran.Execute();
// create a transaction that attempts to assign both keys
// check that everything was aborted var tran = cluster.CreateTransaction();
Assert.IsTrue(success, "tran aborted"); tran.AddCondition(Condition.KeyNotExists(x));
Assert.IsFalse(setX.IsCanceled, "set x cancelled"); tran.AddCondition(Condition.KeyNotExists(y));
Assert.IsFalse(setY.IsCanceled, "set y cancelled"); var setX = tran.StringSetAsync(x, "x-val");
var existsX = cluster.KeyExistsAsync(x); var setY = tran.StringSetAsync(y, "y-val");
var existsY = cluster.KeyExistsAsync(y); bool success = tran.Execute();
Assert.IsTrue(cluster.Wait(existsX), "x exists");
Assert.IsTrue(cluster.Wait(existsY), "y exists"); // check that everything was aborted
} Assert.IsTrue(success, "tran aborted");
} Assert.IsFalse(setX.IsCanceled, "set x cancelled");
Assert.IsFalse(setY.IsCanceled, "set y cancelled");
[Test] var existsX = cluster.KeyExistsAsync(x);
[TestCase(null, 10)] var existsY = cluster.KeyExistsAsync(y);
[TestCase(null, 100)] Assert.IsTrue(cluster.Wait(existsX), "x exists");
[TestCase("abc", 10)] Assert.IsTrue(cluster.Wait(existsY), "y exists");
[TestCase("abc", 100)] }
}
public void Keys(string pattern, int pageSize)
{ [Test]
using (var conn = Create(allowAdmin: true)) [TestCase(null, 10)]
{ [TestCase(null, 100)]
var cluster = conn.GetDatabase(); [TestCase("abc", 10)]
var server = Array.ConvertAll(conn.GetEndPoints(), x => conn.GetServer(x)).First(x => !x.IsSlave); [TestCase("abc", 100)]
server.FlushAllDatabases();
try public void Keys(string pattern, int pageSize)
{ {
Assert.IsFalse(server.Keys(pattern: pattern, pageSize: pageSize).Any()); using (var conn = Create(allowAdmin: true))
Console.WriteLine("Complete: '{0}' / {1}", pattern, pageSize); {
} catch var cluster = conn.GetDatabase();
{ var server = Array.ConvertAll(conn.GetEndPoints(), x => conn.GetServer(x)).First(x => !x.IsSlave);
Console.WriteLine("Failed: '{0}' / {1}", pattern, pageSize); server.FlushAllDatabases();
throw; try
} {
} Assert.IsFalse(server.Keys(pattern: pattern, pageSize: pageSize).Any());
} Console.WriteLine("Complete: '{0}' / {1}", pattern, pageSize);
} catch
[Test] {
[TestCase("", 0)] Console.WriteLine("Failed: '{0}' / {1}", pattern, pageSize);
[TestCase("abc", 7638)] throw;
[TestCase("{abc}", 7638)] }
[TestCase("abcdef", 15101)] }
[TestCase("abc{abc}def", 7638)] }
[TestCase("c", 7365)]
[TestCase("g", 7233)] [Test]
[TestCase("d", 11298)] [TestCase("", 0)]
[TestCase("abc", 7638)]
[TestCase("user1000", 3443)] [TestCase("{abc}", 7638)]
[TestCase("{user1000}", 3443)] [TestCase("abcdef", 15101)]
[TestCase("abc{user1000}", 3443)] [TestCase("abc{abc}def", 7638)]
[TestCase("abc{user1000}def", 3443)] [TestCase("c", 7365)]
[TestCase("{user1000}.following", 3443)] [TestCase("g", 7233)]
[TestCase("{user1000}.followers", 3443)] [TestCase("d", 11298)]
[TestCase("foo{}{bar}", 8363)] [TestCase("user1000", 3443)]
[TestCase("{user1000}", 3443)]
[TestCase("foo{{bar}}zap", 4015)] [TestCase("abc{user1000}", 3443)]
[TestCase("{bar", 4015)] [TestCase("abc{user1000}def", 3443)]
[TestCase("{user1000}.following", 3443)]
[TestCase("foo{bar}{zap}", 5061)] [TestCase("{user1000}.followers", 3443)]
[TestCase("bar", 5061)]
[TestCase("foo{}{bar}", 8363)]
public void HashSlots(string key, int slot)
{ [TestCase("foo{{bar}}zap", 4015)]
using(var muxer = Create(connectTimeout: 500, pause: false)) [TestCase("{bar", 4015)]
{
Assert.AreEqual(slot, muxer.HashSlot(key)); [TestCase("foo{bar}{zap}", 5061)]
} [TestCase("bar", 5061)]
}
public void HashSlots(string key, int slot)
{
[Test] using(var muxer = Create(connectTimeout: 500, pause: false))
public void SScan() {
{ Assert.AreEqual(slot, muxer.HashSlot(key));
using (var conn = Create()) }
{ }
RedisKey key = "a";
var db = conn.GetDatabase();
db.KeyDelete(key); [Test]
public void SScan()
int totalUnfiltered = 0, totalFiltered = 0; {
for (int i = 0; i < 1000; i++) using (var conn = Create())
{ {
db.SetAdd(key, i); RedisKey key = "a";
totalUnfiltered += i; var db = conn.GetDatabase();
if (i.ToString().Contains("3")) totalFiltered += i; db.KeyDelete(key);
}
var unfilteredActual = db.SetScan(key).Select(x => (int)x).Sum(); int totalUnfiltered = 0, totalFiltered = 0;
var filteredActual = db.SetScan(key, "*3*").Select(x => (int)x).Sum(); for (int i = 0; i < 1000; i++)
Assert.AreEqual(totalUnfiltered, unfilteredActual); {
Assert.AreEqual(totalFiltered, filteredActual); db.SetAdd(key, i);
} totalUnfiltered += i;
} if (i.ToString().Contains("3")) totalFiltered += i;
}
[Test] var unfilteredActual = db.SetScan(key).Select(x => (int)x).Sum();
public void GetConfig() var filteredActual = db.SetScan(key, "*3*").Select(x => (int)x).Sum();
{ Assert.AreEqual(totalUnfiltered, unfilteredActual);
using(var muxer = Create(allowAdmin: true)) Assert.AreEqual(totalFiltered, filteredActual);
{ }
var endpoints = muxer.GetEndPoints(); }
var server = muxer.GetServer(endpoints.First());
var nodes = server.ClusterNodes(); [Test]
public void GetConfig()
Assert.AreEqual(endpoints.Length, nodes.Nodes.Count); {
foreach(var node in nodes.Nodes.OrderBy(x => x)) using(var muxer = Create(allowAdmin: true))
{ {
Console.WriteLine(node); var endpoints = muxer.GetEndPoints();
} var server = muxer.GetServer(endpoints.First());
} var nodes = server.ClusterNodes();
}
Assert.AreEqual(endpoints.Length, nodes.Nodes.Count);
[Test] foreach(var node in nodes.Nodes.OrderBy(x => x))
public void AccessRandomKeys() {
{ Console.WriteLine(node);
using(var conn = Create(allowAdmin: true)) }
{ }
}
var cluster = conn.GetDatabase();
int slotMovedCount = 0; [Test]
conn.HashSlotMoved += (s, a) => public void AccessRandomKeys()
{ {
Console.WriteLine("{0} moved from {1} to {2}", a.HashSlot, Describe(a.OldEndPoint), Describe(a.NewEndPoint)); using(var conn = Create(allowAdmin: true))
Interlocked.Increment(ref slotMovedCount); {
};
var pairs = new Dictionary<string, string>(); var cluster = conn.GetDatabase();
const int COUNT = 500; int slotMovedCount = 0;
Task[] send = new Task[COUNT]; conn.HashSlotMoved += (s, a) =>
int index = 0; {
Console.WriteLine("{0} moved from {1} to {2}", a.HashSlot, Describe(a.OldEndPoint), Describe(a.NewEndPoint));
var servers = Array.ConvertAll(conn.GetEndPoints(), x => conn.GetServer(x)); Interlocked.Increment(ref slotMovedCount);
foreach (var server in servers) };
{ var pairs = new Dictionary<string, string>();
if (!server.IsSlave) const int COUNT = 500;
{ Task[] send = new Task[COUNT];
server.Ping(); int index = 0;
server.FlushAllDatabases();
} var servers = Array.ConvertAll(conn.GetEndPoints(), x => conn.GetServer(x));
} foreach (var server in servers)
{
for(int i = 0; i < COUNT; i++) if (!server.IsSlave)
{ {
var key = Guid.NewGuid().ToString(); server.Ping();
var value = Guid.NewGuid().ToString(); server.FlushAllDatabases();
pairs.Add(key, value); }
send[index++] = cluster.StringSetAsync(key, value); }
}
conn.WaitAll(send); for(int i = 0; i < COUNT; i++)
{
var expected = new string[COUNT]; var key = Guid.NewGuid().ToString();
var actual = new Task<RedisValue>[COUNT]; var value = Guid.NewGuid().ToString();
index = 0; pairs.Add(key, value);
foreach (var pair in pairs) send[index++] = cluster.StringSetAsync(key, value);
{ }
expected[index] = pair.Value; conn.WaitAll(send);
actual[index] = cluster.StringGetAsync(pair.Key);
index++; var expected = new string[COUNT];
} var actual = new Task<RedisValue>[COUNT];
cluster.WaitAll(actual); index = 0;
for(int i = 0; i < COUNT; i++) foreach (var pair in pairs)
{ {
Assert.AreEqual(expected[i], (string)actual[i].Result); expected[index] = pair.Value;
} actual[index] = cluster.StringGetAsync(pair.Key);
index++;
int total = 0; }
Parallel.ForEach(servers, server => cluster.WaitAll(actual);
{ for(int i = 0; i < COUNT; i++)
if (!server.IsSlave) {
{ Assert.AreEqual(expected[i], (string)actual[i].Result);
int count = server.Keys(pageSize: 100).Count(); }
Console.WriteLine("{0} has {1} keys", server.EndPoint, count);
Interlocked.Add(ref total, count); int total = 0;
} Parallel.ForEach(servers, server =>
}); {
if (!server.IsSlave)
foreach (var server in servers) {
{ int count = server.Keys(pageSize: 100).Count();
var counters = server.GetCounters(); Console.WriteLine("{0} has {1} keys", server.EndPoint, count);
Console.WriteLine(counters); Interlocked.Add(ref total, count);
} }
int final = Interlocked.CompareExchange(ref total, 0, 0); });
Assert.AreEqual(COUNT, final);
Assert.AreEqual(0, Interlocked.CompareExchange(ref slotMovedCount, 0, 0), "slot moved count"); foreach (var server in servers)
} {
} var counters = server.GetCounters();
Console.WriteLine(counters);
[Test] }
[TestCase(CommandFlags.DemandMaster, false)] int final = Interlocked.CompareExchange(ref total, 0, 0);
[TestCase(CommandFlags.DemandSlave, true)] Assert.AreEqual(COUNT, final);
[TestCase(CommandFlags.PreferMaster, false)] Assert.AreEqual(0, Interlocked.CompareExchange(ref slotMovedCount, 0, 0), "slot moved count");
[TestCase(CommandFlags.PreferSlave, true)] }
public void GetFromRightNodeBasedOnFlags(CommandFlags flags, bool isSlave) }
{
using(var muxer = Create(allowAdmin: true)) [Test]
{ [TestCase(CommandFlags.DemandMaster, false)]
var db = muxer.GetDatabase(); [TestCase(CommandFlags.DemandSlave, true)]
for(int i = 0; i < 1000; i++) [TestCase(CommandFlags.PreferMaster, false)]
{ [TestCase(CommandFlags.PreferSlave, true)]
var key = Guid.NewGuid().ToString(); public void GetFromRightNodeBasedOnFlags(CommandFlags flags, bool isSlave)
var endpoint = db.IdentifyEndpoint(key, flags); {
var server = muxer.GetServer(endpoint); using(var muxer = Create(allowAdmin: true))
Assert.AreEqual(isSlave, server.IsSlave, key); {
} var db = muxer.GetDatabase();
} for(int i = 0; i < 1000; i++)
} {
var key = Guid.NewGuid().ToString();
private static string Describe(EndPoint endpoint) var endpoint = db.IdentifyEndpoint(key, flags);
{ var server = muxer.GetServer(endpoint);
return endpoint == null ? "(unknown)" : endpoint.ToString(); Assert.AreEqual(isSlave, server.IsSlave, key);
}
}
}
private static string Describe(EndPoint endpoint)
{
return endpoint == null ? "(unknown)" : endpoint.ToString();
} }
class TestProfiler : IProfiler class TestProfiler : IProfiler
...@@ -556,8 +563,8 @@ public object GetContext() ...@@ -556,8 +563,8 @@ public object GetContext()
{ {
return MyContext; return MyContext;
} }
} }
[Test] [Test]
public void SimpleProfiling() public void SimpleProfiling()
{ {
...@@ -577,10 +584,10 @@ public void SimpleProfiling() ...@@ -577,10 +584,10 @@ public void SimpleProfiling()
Assert.IsTrue(msgs.Any(m => m.Command == "GET")); Assert.IsTrue(msgs.Any(m => m.Command == "GET"));
Assert.IsTrue(msgs.Any(m => m.Command == "SET")); Assert.IsTrue(msgs.Any(m => m.Command == "SET"));
} }
} }
#if DEBUG #if DEBUG
[Test] [Test]
public void MovedProfiling() public void MovedProfiling()
{ {
const string Key = "redirected-key"; const string Key = "redirected-key";
...@@ -661,7 +668,7 @@ public void MovedProfiling() ...@@ -661,7 +668,7 @@ public void MovedProfiling()
} }
} }
} }
} }
#endif #endif
} }
} }
...@@ -125,7 +125,7 @@ public void ClientName() ...@@ -125,7 +125,7 @@ public void ClientName()
} }
[Test] [Test]
[ExpectedException(typeof(RedisCommandException), ExpectedMessage = "This operation has been disabled in the command-map and cannot be used: CONFIG")] // [ExpectedException(typeof(RedisCommandException), ExpectedMessage = "This operation has been disabled in the command-map and cannot be used: CONFIG")]
public void ReadConfigWithConfigDisabled() public void ReadConfigWithConfigDisabled()
{ {
using (var muxer = Create(allowAdmin: true, disabledCommands: new[] { "config", "info" })) using (var muxer = Create(allowAdmin: true, disabledCommands: new[] { "config", "info" }))
......
...@@ -15,7 +15,8 @@ public sealed class DatabaseWrapperTests ...@@ -15,7 +15,8 @@ public sealed class DatabaseWrapperTests
private Mock<IDatabase> mock; private Mock<IDatabase> mock;
private DatabaseWrapper wrapper; private DatabaseWrapper wrapper;
[TestFixtureSetUp] //[TestFixtureSetUp]
[OneTimeSetUp]
public void Initialize() public void Initialize()
{ {
mock = new Mock<IDatabase>(); mock = new Mock<IDatabase>();
...@@ -289,10 +290,10 @@ public void KeyPersist() ...@@ -289,10 +290,10 @@ public void KeyPersist()
} }
[Test] [Test]
[ExpectedException(typeof(NotSupportedException))] //[ExpectedException(typeof(NotSupportedException))]
public void KeyRandom() public void KeyRandom()
{ {
wrapper.KeyRandom(); Assert.Throws(typeof(NotSupportedException), delegate { wrapper.KeyRandom(); });
} }
[Test] [Test]
......
...@@ -23,15 +23,23 @@ public void UnkonwnKeywordHandling_Ignore() ...@@ -23,15 +23,23 @@ public void UnkonwnKeywordHandling_Ignore()
{ {
var options = ConfigurationOptions.Parse("ssl2=true", true); var options = ConfigurationOptions.Parse("ssl2=true", true);
} }
[Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "Keyword 'ssl2' is not supported")] [Test]
//, ExpectedException(typeof(ArgumentException), ExpectedMessage = "Keyword 'ssl2' is not supported")]
public void UnkonwnKeywordHandling_ExplicitFail() public void UnkonwnKeywordHandling_ExplicitFail()
{ {
var options = ConfigurationOptions.Parse("ssl2=true", false); Exception ex = Assert.Throws(typeof(ArgumentException), delegate {
var options = ConfigurationOptions.Parse("ssl2=true", false);
});
Assert.That(ex.Message.Equals("Keyword 'ssl2' is not supported"));
} }
[Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "Keyword 'ssl2' is not supported")] [Test]
//, ExpectedException(typeof(ArgumentException), ExpectedMessage = "Keyword 'ssl2' is not supported")]
public void UnkonwnKeywordHandling_ImplicitFail() public void UnkonwnKeywordHandling_ImplicitFail()
{ {
var options = ConfigurationOptions.Parse("ssl2=true"); Exception ex = Assert.Throws(typeof(ArgumentException), delegate {
var options = ConfigurationOptions.Parse("ssl2=true");
});
Assert.That(ex.Message.Equals("Keyword 'ssl2' is not supported"));
} }
} }
} }
...@@ -14,16 +14,21 @@ protected override string GetConfiguration() ...@@ -14,16 +14,21 @@ protected override string GetConfiguration()
return PrimaryServer + ":" + SecurePort + "," + PrimaryServer + ":" + PrimaryPort + ",password=" + SecurePassword; return PrimaryServer + ":" + SecurePort + "," + PrimaryServer + ":" + PrimaryPort + ",password=" + SecurePassword;
} }
[Test, ExpectedException(typeof(RedisCommandException), ExpectedMessage = "Command cannot be issued to a slave: FLUSHDB")] [Test]
//, ExpectedException(typeof(RedisCommandException), ExpectedMessage = "Command cannot be issued to a slave: FLUSHDB")]
public void CannotFlushSlave() public void CannotFlushSlave()
{ {
ConfigurationOptions config = GetMasterSlaveConfig(); Exception ex = Assert.Throws(typeof(RedisCommandException), delegate {
using (var conn = ConnectionMultiplexer.Connect(config)) ConfigurationOptions config = GetMasterSlaveConfig();
{ using (var conn = ConnectionMultiplexer.Connect(config))
var servers = Array.ConvertAll(conn.GetEndPoints(), e => conn.GetServer(e)); {
var slave = servers.First(x => x.IsSlave); var servers = Array.ConvertAll(conn.GetEndPoints(), e => conn.GetServer(e));
slave.FlushDatabase(); var slave = servers.First(x => x.IsSlave);
} slave.FlushDatabase();
}
});
Assert.That(ex.Message.Equals("Command cannot be issued to a slave: FLUSHDB"));
} }
[Test] [Test]
......
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
[TestFixture] [TestFixture]
public class Scripting : TestBase public class Scripting : TestBase
{ {
[Test] [Test]
public void TestBasicScripting() public void TestBasicScripting()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
RedisValue newId = Guid.NewGuid().ToString(); RedisValue newId = Guid.NewGuid().ToString();
RedisKey custKey = Me(); RedisKey custKey = Me();
var db = conn.GetDatabase(); var db = conn.GetDatabase();
db.KeyDelete(custKey); db.KeyDelete(custKey);
db.HashSet(custKey, "id", 123); db.HashSet(custKey, "id", 123);
var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end",
new RedisKey[] { custKey }, new RedisValue[] { newId }); new RedisKey[] { custKey }, new RedisValue[] { newId });
Assert.IsTrue(wasSet); Assert.IsTrue(wasSet);
wasSet = (bool)db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end", wasSet = (bool)db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end",
new RedisKey[] { custKey }, new RedisValue[] { newId }); new RedisKey[] { custKey }, new RedisValue[] { newId });
Assert.IsFalse(wasSet); Assert.IsFalse(wasSet);
} }
} }
[Test] [Test]
[TestCase(true)] [TestCase(true)]
[TestCase(false)] [TestCase(false)]
public void CheckLoads(bool async) public void CheckLoads(bool async)
{ {
using (var conn0 = Create(allowAdmin: true)) using (var conn0 = Create(allowAdmin: true))
using (var conn1 = Create(allowAdmin: true)) using (var conn1 = Create(allowAdmin: true))
{ {
// note that these are on different connections (so we wouldn't expect // note that these are on different connections (so we wouldn't expect
// the flush to drop the local cache - assume it is a surprise!) // the flush to drop the local cache - assume it is a surprise!)
var server = conn0.GetServer(PrimaryServer, PrimaryPort); var server = conn0.GetServer(PrimaryServer, PrimaryPort);
var db = conn1.GetDatabase(); var db = conn1.GetDatabase();
const string script = "return 1;"; const string script = "return 1;";
// start empty // start empty
server.ScriptFlush(); server.ScriptFlush();
Assert.IsFalse(server.ScriptExists(script)); Assert.IsFalse(server.ScriptExists(script));
// run once, causes to be cached // run once, causes to be cached
Assert.IsTrue((bool)db.ScriptEvaluate(script)); Assert.IsTrue((bool)db.ScriptEvaluate(script));
Assert.IsTrue(server.ScriptExists(script)); Assert.IsTrue(server.ScriptExists(script));
// can run again // can run again
Assert.IsTrue((bool)db.ScriptEvaluate(script)); Assert.IsTrue((bool)db.ScriptEvaluate(script));
// ditch the scripts; should no longer exist // ditch the scripts; should no longer exist
db.Ping(); db.Ping();
server.ScriptFlush(); server.ScriptFlush();
Assert.IsFalse(server.ScriptExists(script)); Assert.IsFalse(server.ScriptExists(script));
db.Ping(); db.Ping();
if (async) if (async)
{ {
// now: fails the first time // now: fails the first time
try try
{ {
db.Wait(db.ScriptEvaluateAsync(script)); db.Wait(db.ScriptEvaluateAsync(script));
Assert.Fail(); Assert.Fail();
} }
catch(AggregateException ex) catch(AggregateException ex)
{ {
Assert.AreEqual(1, ex.InnerExceptions.Count); Assert.AreEqual(1, ex.InnerExceptions.Count);
Assert.IsInstanceOf<RedisServerException>(ex.InnerExceptions[0]); Assert.IsInstanceOf<RedisServerException>(ex.InnerExceptions[0]);
Assert.AreEqual("NOSCRIPT No matching script. Please use EVAL.", ex.InnerExceptions[0].Message); Assert.AreEqual("NOSCRIPT No matching script. Please use EVAL.", ex.InnerExceptions[0].Message);
} }
} else } else
{ {
// just works; magic // just works; magic
Assert.IsTrue((bool)db.ScriptEvaluate(script)); Assert.IsTrue((bool)db.ScriptEvaluate(script));
} }
// but gets marked as unloaded, so we can use it again... // but gets marked as unloaded, so we can use it again...
Assert.IsTrue((bool)db.ScriptEvaluate(script)); Assert.IsTrue((bool)db.ScriptEvaluate(script));
// which will cause it to be cached // which will cause it to be cached
Assert.IsTrue(server.ScriptExists(script)); Assert.IsTrue(server.ScriptExists(script));
} }
} }
[Test] [Test]
public void CompareScriptToDirect() public void CompareScriptToDirect()
{ {
const string Script = "return redis.call('incr', KEYS[1])"; const string Script = "return redis.call('incr', KEYS[1])";
using (var conn = Create(allowAdmin: true)) using (var conn = Create(allowAdmin: true))
{ {
var server = conn.GetServer(PrimaryServer, PrimaryPort); var server = conn.GetServer(PrimaryServer, PrimaryPort);
server.FlushAllDatabases(); server.FlushAllDatabases();
server.ScriptFlush(); server.ScriptFlush();
server.ScriptLoad(Script); server.ScriptLoad(Script);
var db = conn.GetDatabase(); var db = conn.GetDatabase();
db.Ping(); // k, we're all up to date now; clean db, minimal script cache db.Ping(); // k, we're all up to date now; clean db, minimal script cache
// we're using a pipeline here, so send 1000 messages, but for timing: only care about the last // we're using a pipeline here, so send 1000 messages, but for timing: only care about the last
const int LOOP = 5000; const int LOOP = 5000;
RedisKey key = "foo"; RedisKey key = "foo";
RedisKey[] keys = new[] { key }; // script takes an array RedisKey[] keys = new[] { key }; // script takes an array
// run via script // run via script
db.KeyDelete(key); db.KeyDelete(key);
CollectGarbage(); CollectGarbage();
var watch = Stopwatch.StartNew(); var watch = Stopwatch.StartNew();
for(int i = 1; i < LOOP; i++) // the i=1 is to do all-but-one for(int i = 1; i < LOOP; i++) // the i=1 is to do all-but-one
{ {
db.ScriptEvaluate(Script, keys, flags: CommandFlags.FireAndForget); db.ScriptEvaluate(Script, keys, flags: CommandFlags.FireAndForget);
} }
var scriptResult = db.ScriptEvaluate(Script, keys); // last one we wait for (no F+F) var scriptResult = db.ScriptEvaluate(Script, keys); // last one we wait for (no F+F)
watch.Stop(); watch.Stop();
TimeSpan scriptTime = watch.Elapsed; TimeSpan scriptTime = watch.Elapsed;
// run via raw op // run via raw op
db.KeyDelete(key); db.KeyDelete(key);
CollectGarbage(); CollectGarbage();
watch = Stopwatch.StartNew(); watch = Stopwatch.StartNew();
for (int i = 1; i < LOOP; i++) // the i=1 is to do all-but-one for (int i = 1; i < LOOP; i++) // the i=1 is to do all-but-one
{ {
db.StringIncrement(key, flags: CommandFlags.FireAndForget); db.StringIncrement(key, flags: CommandFlags.FireAndForget);
} }
var directResult = db.StringIncrement(key); // last one we wait for (no F+F) var directResult = db.StringIncrement(key); // last one we wait for (no F+F)
watch.Stop(); watch.Stop();
TimeSpan directTime = watch.Elapsed; TimeSpan directTime = watch.Elapsed;
Assert.AreEqual(LOOP, (long)scriptResult, "script result"); Assert.AreEqual(LOOP, (long)scriptResult, "script result");
Assert.AreEqual(LOOP, (long)directResult, "direct result"); Assert.AreEqual(LOOP, (long)directResult, "direct result");
Console.WriteLine("script: {0}ms; direct: {1}ms", Console.WriteLine("script: {0}ms; direct: {1}ms",
scriptTime.TotalMilliseconds, scriptTime.TotalMilliseconds,
directTime.TotalMilliseconds); directTime.TotalMilliseconds);
} }
} }
[Test] [Test]
public void TestCallByHash() public void TestCallByHash()
{ {
const string Script = "return redis.call('incr', KEYS[1])"; const string Script = "return redis.call('incr', KEYS[1])";
using (var conn = Create(allowAdmin: true)) using (var conn = Create(allowAdmin: true))
{ {
var server = conn.GetServer(PrimaryServer, PrimaryPort); var server = conn.GetServer(PrimaryServer, PrimaryPort);
server.FlushAllDatabases(); server.FlushAllDatabases();
server.ScriptFlush(); server.ScriptFlush();
byte[] hash = server.ScriptLoad(Script); byte[] hash = server.ScriptLoad(Script);
var db = conn.GetDatabase(); var db = conn.GetDatabase();
RedisKey[] keys = { Me() }; RedisKey[] keys = { Me() };
string hexHash = string.Concat(Array.ConvertAll(hash, x => x.ToString("X2"))); string hexHash = string.Concat(Array.ConvertAll(hash, x => x.ToString("X2")));
Assert.AreEqual("2BAB3B661081DB58BD2341920E0BA7CF5DC77B25", hexHash); Assert.AreEqual("2BAB3B661081DB58BD2341920E0BA7CF5DC77B25", hexHash);
db.ScriptEvaluate(hexHash, keys); db.ScriptEvaluate(hexHash, keys);
db.ScriptEvaluate(hash, keys); db.ScriptEvaluate(hash, keys);
var count = (int)db.StringGet(keys)[0]; var count = (int)db.StringGet(keys)[0];
Assert.AreEqual(2, count); Assert.AreEqual(2, count);
} }
} }
[Test] [Test]
public void SimpleLuaScript() public void SimpleLuaScript()
{ {
...@@ -359,8 +359,8 @@ public void LoadedLuaScriptWithKeys() ...@@ -359,8 +359,8 @@ public void LoadedLuaScriptWithKeys()
Assert.AreEqual(1, keys.Length); Assert.AreEqual(1, keys.Length);
Assert.AreEqual("testkey", (string)keys[0]); Assert.AreEqual("testkey", (string)keys[0]);
} }
} }
[Test] [Test]
public void PurgeLuaScriptCache() public void PurgeLuaScriptCache()
{ {
...@@ -402,8 +402,8 @@ public void PurgeLuaScriptOnFinalize() ...@@ -402,8 +402,8 @@ public void PurgeLuaScriptOnFinalize()
var shouldBeNew = LuaScript.Prepare(Script); var shouldBeNew = LuaScript.Prepare(Script);
Assert.AreEqual(1, LuaScript.GetCachedScriptCount()); Assert.AreEqual(1, LuaScript.GetCachedScriptCount());
} }
[Test] [Test]
public void IDatabaseLuaScriptConvenienceMethods() public void IDatabaseLuaScriptConvenienceMethods()
{ {
...@@ -442,8 +442,8 @@ public void IServerLuaScriptConvenienceMethods() ...@@ -442,8 +442,8 @@ public void IServerLuaScriptConvenienceMethods()
var val = db.StringGet("key3"); var val = db.StringGet("key3");
Assert.AreEqual("value3", (string)val); Assert.AreEqual("value3", (string)val);
} }
} }
[Test] [Test]
public void LuaScriptPrefixedKeys() public void LuaScriptPrefixedKeys()
{ {
...@@ -461,8 +461,8 @@ public void LuaScriptPrefixedKeys() ...@@ -461,8 +461,8 @@ public void LuaScriptPrefixedKeys()
Assert.AreEqual(2, args.Length); Assert.AreEqual(2, args.Length);
Assert.AreEqual("prefix-key", (string)args[0]); Assert.AreEqual("prefix-key", (string)args[0]);
Assert.AreEqual("hello", (string)args[1]); Assert.AreEqual("hello", (string)args[1]);
} }
[Test] [Test]
public void LuaScriptWithWrappedDatabase() public void LuaScriptWithWrappedDatabase()
{ {
...@@ -508,6 +508,6 @@ public void LoadedLuaScriptWithWrappedDatabase() ...@@ -508,6 +508,6 @@ public void LoadedLuaScriptWithWrappedDatabase()
var val3 = db.StringGet("mykey"); var val3 = db.StringGet("mykey");
Assert.IsTrue(val3.IsNull); Assert.IsTrue(val3.IsNull);
} }
} }
} }
} }
...@@ -68,14 +68,17 @@ public void Connect() ...@@ -68,14 +68,17 @@ public void Connect()
[Test] [Test]
[TestCase("wrong")] [TestCase("wrong")]
[TestCase("")] [TestCase("")]
[ExpectedException(typeof(RedisConnectionException), ExpectedMessage = "No connection is available to service this operation: PING")] //[ExpectedException(typeof(RedisConnectionException), ExpectedMessage = "No connection is available to service this operation: PING")]
public void ConnectWithWrongPassword(string password) public void ConnectWithWrongPassword(string password)
{ {
SetExpectedAmbientFailureCount(-1); Exception ex = Assert.Throws(typeof(RedisConnectionException), delegate {
using (var server = Create(password: password, checkConnect: false)) SetExpectedAmbientFailureCount(-1);
{ using (var server = Create(password: password, checkConnect: false))
server.GetDatabase().Ping(); {
} server.GetDatabase().Ping();
}
});
Assert.That(ex.Message.Equals("No connection is available to service this operation: PING"));
} }
} }
} }
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
[TestFixture, Ignore] [TestFixture, Ignore("reason?")]
public class Sentinel public class Sentinel
{ {
// TODO fill in these constants before running tests // TODO fill in these constants before running tests
...@@ -100,7 +100,7 @@ public void SentinelSlavesTest() ...@@ -100,7 +100,7 @@ public void SentinelSlavesTest()
} }
} }
[Test, Ignore] [Test, Ignore("reason?")]
public void SentinelFailoverTest() public void SentinelFailoverTest()
{ {
Server.SentinelFailover(ServiceName); Server.SentinelFailover(ServiceName);
......
...@@ -66,9 +66,9 @@ ...@@ -66,9 +66,9 @@
<HintPath>..\packages\Moq.4.2.1502.0911\lib\net40\Moq.dll</HintPath> <HintPath>..\packages\Moq.4.2.1502.0911\lib\net40\Moq.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.0.5715.30856, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <HintPath>..\packages\NUnit.3.0.0-beta-5\lib\net45\nunit.framework.dll</HintPath>
<HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
......
...@@ -11,7 +11,8 @@ public sealed class TransactionWrapperTests ...@@ -11,7 +11,8 @@ public sealed class TransactionWrapperTests
private Mock<ITransaction> mock; private Mock<ITransaction> mock;
private TransactionWrapper wrapper; private TransactionWrapper wrapper;
[TestFixtureSetUp] //[TestFixtureSetUp]
[OneTimeSetUp]
public void Initialize() public void Initialize()
{ {
mock = new Mock<ITransaction>(); mock = new Mock<ITransaction>();
......
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
[TestFixture] [TestFixture]
public class WithKeyPrefixTests : TestBase public class WithKeyPrefixTests : TestBase
{ {
[Test] [Test]
public void BlankPrefixYieldsSame_Bytes() public void BlankPrefixYieldsSame_Bytes()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
var raw = conn.GetDatabase(1); var raw = conn.GetDatabase(1);
var prefixed = raw.WithKeyPrefix(new byte[0]); var prefixed = raw.WithKeyPrefix(new byte[0]);
Assert.AreSame(raw, prefixed); Assert.AreSame(raw, prefixed);
} }
} }
[Test] [Test]
public void BlankPrefixYieldsSame_String() public void BlankPrefixYieldsSame_String()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
var raw = conn.GetDatabase(1); var raw = conn.GetDatabase(1);
var prefixed = raw.WithKeyPrefix(""); var prefixed = raw.WithKeyPrefix("");
Assert.AreSame(raw, prefixed); Assert.AreSame(raw, prefixed);
} }
} }
[Test, ExpectedException(typeof(ArgumentNullException))] [Test]
public void NullPrefixIsError_Bytes() //, ExpectedException(typeof(ArgumentNullException))]
{ public void NullPrefixIsError_Bytes()
using (var conn = Create()) {
{ Assert.Throws(typeof(ArgumentNullException), delegate {
var raw = conn.GetDatabase(1); using (var conn = Create())
var prefixed = raw.WithKeyPrefix((byte[])null); {
} var raw = conn.GetDatabase(1);
} var prefixed = raw.WithKeyPrefix((byte[])null);
[Test, ExpectedException(typeof(ArgumentNullException))] }
public void NullPrefixIsError_String() });
{ }
using (var conn = Create()) [Test]
{ //, ExpectedException(typeof(ArgumentNullException))]
var raw = conn.GetDatabase(1); public void NullPrefixIsError_String()
var prefixed = raw.WithKeyPrefix((string)null); {
} Assert.Throws(typeof(ArgumentNullException), delegate {
} using (var conn = Create())
{
[Test, ExpectedException(typeof(ArgumentNullException))] var raw = conn.GetDatabase(1);
[TestCase("abc")] var prefixed = raw.WithKeyPrefix((string)null);
[TestCase("")] }
[TestCase(null)] });
public void NullDatabaseIsError(string prefix) }
{
IDatabase raw = null; [Test]
var prefixed = raw.WithKeyPrefix(prefix); //, ExpectedException(typeof(ArgumentNullException))]
} [TestCase("abc")]
[Test] [TestCase("")]
public void BasicSmokeTest() [TestCase(null)]
{ public void NullDatabaseIsError(string prefix)
using(var conn = Create()) {
{ Assert.Throws(typeof(ArgumentNullException), delegate {
var raw = conn.GetDatabase(1); IDatabase raw = null;
var prefixed = raw.WithKeyPrefix(prefix);
var foo = raw.WithKeyPrefix("foo"); });
var foobar = foo.WithKeyPrefix("bar"); }
[Test]
string key = Me(); public void BasicSmokeTest()
{
string s = Guid.NewGuid().ToString(), t = Guid.NewGuid().ToString(); using(var conn = Create())
{
foo.StringSet(key, s); var raw = conn.GetDatabase(1);
var val = (string)foo.StringGet(key);
Assert.AreEqual(s, val); // fooBasicSmokeTest var foo = raw.WithKeyPrefix("foo");
var foobar = foo.WithKeyPrefix("bar");
foobar.StringSet(key, t);
val = (string)foobar.StringGet(key); string key = Me();
Assert.AreEqual(t, val); // foobarBasicSmokeTest
string s = Guid.NewGuid().ToString(), t = Guid.NewGuid().ToString();
val = (string)foo.StringGet("bar" + key);
Assert.AreEqual(t, val); // foobarBasicSmokeTest foo.StringSet(key, s);
var val = (string)foo.StringGet(key);
val = (string)raw.StringGet("foo" + key); Assert.AreEqual(s, val); // fooBasicSmokeTest
Assert.AreEqual(s, val); // fooBasicSmokeTest
foobar.StringSet(key, t);
val = (string)raw.StringGet("foobar" + key); val = (string)foobar.StringGet(key);
Assert.AreEqual(t, val); // foobarBasicSmokeTest Assert.AreEqual(t, val); // foobarBasicSmokeTest
}
} val = (string)foo.StringGet("bar" + key);
[Test] Assert.AreEqual(t, val); // foobarBasicSmokeTest
public void ConditionTest()
{ val = (string)raw.StringGet("foo" + key);
using(var conn = Create()) Assert.AreEqual(s, val); // fooBasicSmokeTest
{
var raw = conn.GetDatabase(2); val = (string)raw.StringGet("foobar" + key);
Assert.AreEqual(t, val); // foobarBasicSmokeTest
var foo = raw.WithKeyPrefix("tran:"); }
}
raw.KeyDelete("tran:abc"); [Test]
raw.KeyDelete("tran:i"); public void ConditionTest()
{
// execute while key exists using(var conn = Create())
raw.StringSet("tran:abc", "def"); {
var tran = foo.CreateTransaction(); var raw = conn.GetDatabase(2);
tran.AddCondition(Condition.KeyExists("abc"));
tran.StringIncrementAsync("i"); var foo = raw.WithKeyPrefix("tran:");
tran.Execute();
raw.KeyDelete("tran:abc");
int i = (int)raw.StringGet("tran:i"); raw.KeyDelete("tran:i");
Assert.AreEqual(1, i);
// execute while key exists
// repeat without key raw.StringSet("tran:abc", "def");
raw.KeyDelete("tran:abc"); var tran = foo.CreateTransaction();
tran = foo.CreateTransaction(); tran.AddCondition(Condition.KeyExists("abc"));
tran.AddCondition(Condition.KeyExists("abc")); tran.StringIncrementAsync("i");
tran.StringIncrementAsync("i"); tran.Execute();
tran.Execute();
int i = (int)raw.StringGet("tran:i");
i = (int)raw.StringGet("tran:i"); Assert.AreEqual(1, i);
Assert.AreEqual(1, i);
} // repeat without key
} raw.KeyDelete("tran:abc");
} tran = foo.CreateTransaction();
} tran.AddCondition(Condition.KeyExists("abc"));
tran.StringIncrementAsync("i");
tran.Execute();
i = (int)raw.StringGet("tran:i");
Assert.AreEqual(1, i);
}
}
}
}
...@@ -15,7 +15,8 @@ public sealed class WrapperBaseTests ...@@ -15,7 +15,8 @@ public sealed class WrapperBaseTests
private Mock<IDatabaseAsync> mock; private Mock<IDatabaseAsync> mock;
private WrapperBase<IDatabaseAsync> wrapper; private WrapperBase<IDatabaseAsync> wrapper;
[TestFixtureSetUp] //[TestFixtureSetUp]
[OneTimeSetUp]
public void Initialize() public void Initialize()
{ {
mock = new Mock<IDatabaseAsync>(); mock = new Mock<IDatabaseAsync>();
...@@ -258,10 +259,12 @@ public void KeyPersistAsync() ...@@ -258,10 +259,12 @@ public void KeyPersistAsync()
} }
[Test] [Test]
[ExpectedException(typeof(NotSupportedException))] //[ExpectedException(typeof(NotSupportedException))]
public void KeyRandomAsync() public void KeyRandomAsync()
{ {
wrapper.KeyRandomAsync(); Assert.Throws(typeof(NotSupportedException), delegate {
wrapper.KeyRandomAsync();
});
} }
[Test] [Test]
......
...@@ -2,5 +2,5 @@ ...@@ -2,5 +2,5 @@
<packages> <packages>
<package id="BookSleeve" version="1.3.41" targetFramework="net45" /> <package id="BookSleeve" version="1.3.41" targetFramework="net45" />
<package id="Moq" version="4.2.1502.0911" targetFramework="net45" /> <package id="Moq" version="4.2.1502.0911" targetFramework="net45" />
<package id="NUnit" version="2.6.4" targetFramework="net45" /> <package id="NUnit" version="3.0.0-beta-5" targetFramework="net45" />
</packages> </packages>
\ No newline at end of file
Copyright © 2002-2014 Charlie Poole
Copyright © 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov
Copyright © 2000-2002 Philip A. Craig
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment (see the following) in the product documentation is required.
Portions Copyright © 2002-2014 Charlie Poole or Copyright © 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov or Copyright © 2000-2002 Philip A. Craig
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
NUnit 3.0.0 Beta 4 - August 25, 2015
Framework
* A new RetryAttribute allows retrying of failing tests.
* New SupersetConstraint and Is.SupersetOf syntax complement SubsetConstraint.
* Tests skipped due to ExplicitAttribute are now reported as skipped.
Engine
* We now use Cecil to examine assemblies prior to loading them.
* Extensions are no longer based on Mono.Addins but use our own extension framework.
Issues Resolved
* 125 3rd-party dependencies should be downloaded on demand
* 283 What should we do when a user extension does something bad?
* 585 RetryAttribute
* 642 Restructure MSBuild script
* 649 Change how we zip packages
* 654 ReflectionOnlyLoad and ReflectionOnlyLoadFrom
* 664 Invalid "id" attribute in the report for case "test started"
* 685 In the some cases when tests cannot be started NUnit returns exit code "0"
* 728 Missing Assert.That overload
* 741 Explicit Tests get run when using --exclude
* 746 Framework should send events for all tests
* 747 NUnit should apply attributes even if test is non-runnable
* 749 Review Use of Mono.Addins for Engine Extensibility
* 750 Include Explicit Tests in Test Results
* 753 Feature request: Is.SupersetOf() assertion constraint
* 755 TimeOut attribute doesn't work with TestCaseSource Attribute
* 757 Implement some way to wait for execution to complete in ITestEngineRunner
* 760 Packaging targets do not run on Linux
* 766 Added overloads for True()/False() accepting booleans
* 778 Build and build.cmd scripts invoke nuget.exe improperly
* 780 Teamcity fix
* 782 No sources for 2.6.4
NUnit 3.0.0 Beta 3 - July 15, 2015
Framework
* The RangeAttribute has been extended to support more data types including
uint, long and ulong
* Added platform support for Windows 10 and fixed issues with Windows 8 and
8.1 support
* Added async support to the portable version of NUnit Framework
* The named members of the TestCaseSource and ValueSource attributes must now be
static.
* RandomAttribute has been extended to add support for new data types including
uint, long, ulong, short, ushort, float, byte and sbyte
* TestContext.Random has also been extended to add support for new data types including
uint, long, ulong, short, ushort, float, byte, sbyte and decimal
* Removed the dependency on Microsoft.Bcl.Async from the NUnit Framework assembly
targeting .NET 4.0. If you want to write async tests in .NET 4.0, you will need
to reference the NuGet package yourself.
* Added a new TestFixtureSource attribute which is the equivalent to TestCaseSource
but provides for instantiation of fixtures.
* Significant improvements have been made in how NUnit deduces the type arguments of
generic methods based on the arguments provided.
Engine
* If the target framework is not specified, test assemblies that are compiled
to target .NET 4.5 will no longer run in .NET 4.0 compatibility mode
Console
* If the console is run without arguments, it will now display help
Issues Resolved
* 47 Extensions to RangeAttribute
* 237 System.Uri .ctor works not properly under Nunit
* 244 NUnit should properly distinguish between .NET 4.0 and 4.5
* 310 Target framework not specified on the AppDomain when running against .Net 4.5
* 321 Rationalize how we count tests
* 472 Overflow exception and DivideByZero exception from the RangeAttribute
* 524 int and char do not compare correctly?
* 539 Truncation of string arguments
* 544 AsyncTestMethodTests for 4.5 Framework fails frequently on Travis CI
* 656 Unused parameter in Console.WriteLine found
* 670 Failing Tests in TeamCity Build
* 673 Ensure proper disposal of engine objects
* 674 Engine does not release test assemblies
* 679 Windows 10 Support
* 682 Add Async Support to Portable Framework
* 683 Make FrameworkController available in portable build
* 687 TestAgency does not launch agent process correctly if runtime type is not specified (i.e. v4.0)
* 692 PlatformAttribute_OperatingSystemBitNess fails when running in 32-bit process
* 693 Generic Test<T> Method cannot determine type arguments for fixture when passed as IEnumerable<T>
* 698 Require TestCaseSource and ValueSource named members to be static
* 703 TeamCity non-equal flowid for 'testStarted' and 'testFinished' messages
* 712 Extensions to RandomAttribute
* 715 Provide a data source attribute at TestFixture Level
* 718 RangeConstraint gives error with from and two args of differing types
* 723 Does nunit.nuspec require dependency on Microsoft.Bcl.Async?
* 724 Adds support for Nullable<bool> to Assert.IsTrue and Assert.IsFalse
* 734 Console without parameters doesn't show help
NUnit 3.0.0 Beta 2 - May 12, 2015
Framework
* The Compact Framework version of the framework is now packaged separately
and will be distributed as a ZIP file and as a NuGet package.
* The NUnit 2.x RepeatAttribute was added back into the framework.
* Added Throws.ArgumentNullException
* Added GetString methods to NUnit.Framework.Internal.RandomGenerator to
create repeatable random strings for testing
* When checking the equality of DateTimeOffset, you can now use the
WithSameOffset modifier
* Some classes intended for internal usage that were public for testing
have now been made internal. Additional classes will be made internal
for the final 3.0 release.
Engine
* Added a core engine which is a non-extensible, minimal engine for use by
devices and similar situations where reduced functionality is compensated
for by reduced size and simplicity of usage. See
https://github.com/nunit/dev/wiki/Core-Engine for more information.
Issues Resolved
* 22 Add OSArchitecture Attribute to Environment node in result xml
* 24 Assert on Dictionary Content
* 48 Explicit seems to conflict with Ignore
* 168 Create NUnit 3.0 documentation
* 196 Compare DateTimeOffsets including the offset in the comparison
* 217 New icon for the 3.0 release
* 316 NUnitLite TextUI Runner
* 320 No Tests found: Using parametrized Fixture and TestCaseSource
* 360 Better exception message when using non-BCL class in property
* 454 Rare registry configurations may cause NUnit to fail
* 478 RepeatAttribute
* 481 Testing multiple assemblies in nunitlite
* 538 Potential bug using TestContext in constructors
* 546 Enable Parallel in NUnitLite/CF (or more) builds
* 551 TextRunner not passing the NumWorkers option to the ITestAssemblyRunner
* 556 Executed tests should always return a non-zero duration
* 559 Fix text of NuGet packages
* 560 Fix PackageVersion property on wix install projects
* 562 Program.cs in NUnitLite NuGet package is incorrect
* 564 NUnitLite Nuget package is Beta 1a, Framework is Beta 1
* 565 NUnitLite Nuget package adds Program.cs to a VB Project
* 568 Isolate packaging from building
* 570 ThrowsConstraint failure message should include stack trace of actual exception
* 576 Throws.ArgumentNullException would be nice
* 577 Documentation on some members of Throws falsely claims that they return `TargetInvocationException` constraints
* 579 No documentation for recommended usage of TestCaseSourceAttribute
* 580 TeamCity Service Message Uses Incorrect Test Name with NUnit2Driver
* 582 Test Ids Are Not Unique
* 583 TeamCity service messages to support parallel test execution
* 584 Non-runnable assembly has incorrect ResultState
* 609 Add support for integration with TeamCity
* 611 Remove unused --teamcity option from CF build of NUnitLite
* 612 MaxTime doesn't work when used for TestCase
* 621 Core Engine
* 622 nunit-console fails when use --output
* 628 Modify IService interface and simplify ServiceContext
* 631 Separate packaging for the compact framework
* 646 ConfigurationManager.AppSettings Params Return Null under Beta 1
* 648 Passing 2 or more test assemblies targeting > .NET 2.0 to nunit-console fails
NUnit 3.0.0 Beta 1 - March 25, 2015
General
* There is now a master windows installer for the framework, engine and console runner.
Framework
* We no longer create a separate framework build for .NET 3.5. The 2.0 and
3.5 builds were essentially the same, so the former should now be used
under both runtimes.
* A new Constraint, DictionaryContainsKeyConstraint, may be used to test
that a specified key is present in a dictionary.
* LevelOfParallelizationAttribute has been renamed to LevelOfParallelismAttribute.
* The Silverlight runner now displays output in color and includes any
text output created by the tests.
* The class and method names of each test are included in the output xml
where applicable.
* String arguments used in test case names are now truncated to 40 rather
than 20 characters.
Engine
* The engine API has now been finalized. It permits specifying a minimum
version of the engine that a runner is able to use. The best installed
version of the engine will be loaded. Third-party runners may override
the selection process by including a copy of the engine in their
installation directory and specifying that it must be used.
* The V2 framework driver now uses the event listener and test listener
passed to it by the runner. This corrects several outstanding issues
caused by events not being received and allows selecting V2 tests to
be run from the command-line, in the same way that V3 tests are selected.
Console
* The console now defaults to not using shadowcopy. There is a new option
--shadowcopy to turn it on if needed.
Issues Resolved
* 224 Silverlight Support
* 318 TestActionAttribute: Retrieving the TestFixture
* 428 Add ExpectedExceptionAttribute to C# samples
* 440 Automatic selection of Test Engine to use
* 450 Create master install that includes the framework, engine and console installs
* 477 Assert does not work with ArraySegment
* 482 nunit-console has multiple errors related to -framework option
* 483 Adds constraint for asserting that a dictionary contains a particular key
* 484 Missing file in NUnit.Console nuget package
* 485 Can't run v2 tests with nunit-console 3.0
* 487 NUnitLite can't load assemblies by their file name
* 488 Async setup and teardown still don't work
* 497 Framework installer shold register the portable framework
* 504 Option --workers:0 is ignored
* 508 Travis builds with failure in engine tests show as successful
* 509 Under linux, not all mono profiles are listed as available
* 512 Drop the .NET 3.5 build
* 517 V2 FrameworkDriver does not make use of passed in TestEventListener
* 523 Provide an option to disable shadowcopy in NUnit v3
* 528 V2 FrameworkDriver does not make use of passed in TestFilter
* 530 Color display for Silverlight runner
* 531 Display text output from tests in Silverlight runner
* 534 Add classname and methodname to test result xml
* 541 Console help doesn't indicate defaults
NUnit 3.0.0 Alpha 5 - January 30, 2015
General
* A Windows installer is now included in the release packages.
Framework
* TestCaseAttribute now allows arguments with default values to be omitted. Additionaly, it accepts a Platform property to specify the platforms on which the test case should be run.
* TestFixture and TestCase attributes now enforce the requirement that a reason needs to be provided when ignoring a test.
* SetUp, TearDown, OneTimeSetUp and OneTimeTearDown methods may now be async.
* String arguments over 20 characters in length are truncated when used as part of a test name.
Engine
* The engine is now extensible using Mono.Addins. In this release, extension points are provided for FrameworkDrivers, ProjectLoaders and OutputWriters. The following addins are bundled as a part of NUnit:
* A FrameworkDriver that allows running NUnit V2 tests under NUnit 3.0.
* ProjectLoaders for NUnit and Visual Studio projects.
* An OutputWriter that creates XML output in NUnit V2 format.
* DomainUsage now defaults to Multiple if not specified by the runner
Console
* New options supported:
* --testlist provides a list of tests to run in a file
* --stoponerror indicates that the run should terminate when any test fails.
Issues Resolved
* 20 TestCaseAttribute needs Platform property.
* 60 NUnit should support async setup, teardown, fixture setup and fixture teardown.
* 257 TestCaseAttribute should not require parameters with default values to be specified.
* 266 Pluggable framework drivers.
* 368 Create addin model.
* 369 Project loader addins
* 370 OutputWriter addins
* 403 Move ConsoleOptions.cs and Options.cs to Common and share...
* 419 Create Windows Installer for NUnit.
* 427 [TestFixture(Ignore=true)] should not be allowed.
* 437 Errors in tests under Linux due to hard-coded paths.
* 441 NUnit-Console should support --testlist option
* 442 Add --stoponerror option back to nunit-console.
* 456 Fix memory leak in RuntimeFramework.
* 459 Remove the Mixed Platforms build configuration.
* 468 Change default domain usage to multiple.
* 469 Truncate string arguments in test names in order to limit the length.
NUnit 3.0.0 Alpha 4 - December 30, 2014
Framework
* ApartmentAttribute has been added, replacing STAAttribute and MTAAttribute.
* Unnecessary overloads of Assert.That and Assume.That have been removed.
* Multiple SetUpFixtures may be specified in a single namespace.
* Improvements to the Pairwise strategy test case generation algorithm.
* The new NUnitLite runner --testlist option, allows a list of tests to be kept in a file.
Engine
* A driver is now included, which allows running NUnit 2.x tests under NUnit 3.0.
* The engine can now load and run tests specified in a number of project formats:
* NUnit (.nunit)
* Visual Studio C# projects (.csproj)
* Visual Studio F# projects (.vjsproj)
* Visual Studio Visual Basic projects (.vbproj)
* Visual Studio solutions (.sln)
* Legacy C++ and Visual JScript projects (.csproj and .vjsproj) are also supported
* Support for the current C++ format (.csxproj) is not yet available
* Creation of output files like TestResult.xml in various formats is now a
service of the engine, available to any runner.
Console
* The command-line may now include any number of assemblies and/or supported projects.
Issues Resolved
* 37 Multiple SetUpFixtures should be permitted on same namespace
* 210 TestContext.WriteLine in an AppDomain causes an error
* 227 Add support for VS projects and solutions
* 231 Update C# samples to use NUnit 3.0
* 233 Update F# samples to use NUnit 3.0
* 234 Update C++ samples to use NUnit 3.0
* 265 Reorganize console reports for nunit-console and nunitlite
* 299 No full path to assembly in XML file under Compact Framework
* 301 Command-line length
* 363 Make Xml result output an engine service
* 377 CombiningStrategyAttributes don't work correctly on generic methods
* 388 Improvements to NUnitLite runner output
* 390 Specify exactly what happens when a test times out
* 396 ApartmentAttribute
* 397 CF nunitlite runner assembly has the wrong name
* 407 Assert.Pass() with ]]> in message crashes console runner
* 414 Simplify Assert overloads
* 416 NUnit 2.x Framework Driver
* 417 Complete work on NUnit projects
* 420 Create Settings file in proper location
NUnit 3.0.0 Alpha 3 - November 29, 2014
Breaking Changes
* NUnitLite tests must reference both the nunit.framework and nunitlite assemblies.
Framework
* The NUnit and NUnitLite frameworks have now been merged. There is no longer any distinction
between them in terms of features, although some features are not available on all platforms.
* The release includes two new framework builds: compact framework 3.5 and portable. The portable
library is compatible with .NET 4.5, Silverlight 5.0, Windows 8, Windows Phone 8.1,
Windows Phone Silverlight 8, Mono for Android and MonoTouch.
* A number of previously unsupported features are available for the Compact Framework:
- Generic methods as tests
- RegexConstraint
- TimeoutAttribute
- FileAssert, DirectoryAssert and file-related constraints
Engine
* The logic of runtime selection has now changed so that each assembly runs by default
in a separate process using the runtime for which it was built.
* On 64-bit systems, each test process is automatically created as 32-bit or 64-bit,
depending on the platform specified for the test assembly.
Console
* The console runner now runs tests in a separate process per assembly by default. They may
still be run in process or in a single separate process by use of command-line options.
* The console runner now starts in the highest version of the .NET runtime available, making
it simpler to debug tests by specifying that they should run in-process on the command-line.
* The -x86 command-line option is provided to force execution in a 32-bit process on a 64-bit system.
* A writeability check is performed for each output result file before trying to run the tests.
* The -teamcity option is now supported.
Issues Resolved
* 12 Compact framework should support generic methods
* 145 NUnit-console fails if test result message contains invalid xml characters
* 155 Create utility classes for platform-specific code
* 223 Common code for NUnitLite console runner and NUnit-Console
* 225 Compact Framework Support
* 238 Improvements to running 32 bit tests on a 64 bit system
* 261 Add portable nunitlite build
* 284 NUnitLite Unification
* 293 CF does not have a CurrentDirectory
* 306 Assure NUnit can write resultfile
* 308 Early disposal of runners
* 309 NUnit-Console should support incremental output under TeamCity
* 325 Add RegexConstraint to compact framework build
* 326 Add TimeoutAttribute to compact framework build
* 327 Allow generic test methods in the compact framework
* 328 Use .NET Stopwatch class for compact framework builds
* 331 Alpha 2 CF does not build
* 333 Add parallel execution to desktop builds of NUnitLite
* 334 Include File-related constraints and syntax in NUnitLite builds
* 335 Re-introduce 'Classic' NUnit syntax in NUnitLite
* 336 Document use of separate obj directories per build in our projects
* 337 Update Standard Defines page for .NET 3.0
* 341 Move the NUnitLite runners to separate assemblies
* 367 Refactor XML Escaping Tests
* 372 CF Build TestAssemblyRunnerTests
* 373 Minor CF Test Fixes
* 378 Correct documentation for PairwiseAttribute
* 386 Console Output Improvements
NUnit 3.0.0 Alpha 2 - November 2, 2014
Breaking Changes
* The console runner no longer displays test results in the debugger.
* The NUnitLite compact framework 2.0 build has been removed.
* All addin support has been removed from the framework. Documentation of NUnit 3.0 extensibility features will be published in time for the beta release. In the interim, please ask for support on the nunit-discuss list.
General
* A separate solution has been created for Linux
* We now have continuous integration builds under both Travis and Appveyor
* The compact framework 3.5 build is now working and will be supported in future releases.
New Features
* The console runner now automatically detects 32- versus 64-bit test assemblies.
* The NUnitLite report output has been standardized to match that of nunit-console.
* The NUnitLite command-line has been standardized to match that of nunit-console where they share the same options.
* Both nunit-console and NUnitLite now display output in color.
* ActionAttributes now allow specification of multiple targets on the attribute as designed. This didn't work in the first alpha.
* OneTimeSetUp and OneTimeTearDown failures are now shown on the test report. Individual test failures after OneTimeSetUp failure are no longer shown.
* The console runner refuses to run tests build with older versions of NUnit. A plugin will be available to run older tests in the future.
Issues Resolved
* 222 Color console for NUnitLite
* 229 Timing failures in tests
* 241 Remove reference to Microslft BCL packages
* 243 Create solution for Linux
* 245 Multiple targets on action attributes not implemented
* 246 C++ tests do not compile in VS2013
* 247 Eliminate trace display when running tests in debug
* 255 Add new result states for more precision in where failures occur
* 256 ContainsConstraint break when used with AndConstraint
* 264 Stacktrace displays too many entries
* 269 Add manifest to nunit-console and nunit-agent
* 270 OneTimeSetUp failure results in too much output
* 271 Invalid tests should be treated as errors
* 274 Command line options should be case insensitive
* 276 NUnit-console should not reference nunit.framework
* 278 New result states (ChildFailure and SetupFailure) break NUnit2XmlOutputWriter
* 282 Get tests for NUnit2XmlOutputWriter working
* 288 Set up Appveyor CI build
* 290 Stack trace still displays too many items
* 315 NUnit 3.0 alpha: Cannot run in console on my assembly
* 319 CI builds are not treating test failures as failures of the build
* 322 Remove Stopwatch tests where they test the real .NET Stopwatch
NUnit 3.0.0 Alpha 1 - September 22, 2014
Breaking Changes
* Legacy suites are no longer supported
* Assert.NullOrEmpty is no longer supported (Use Is.Null.Or.Empty)
General
* MsBuild is now used for the build rather than NAnt
* The framework test harness has been removed now that nunit-console is at a point where it can run the tests.
New Features
* Action Attributes have been added with the same features as in NUnit 2.6.3.
* TestContext now has a method that allows writing to the XML output.
* TestContext.CurrentContext.Result now provides the error message and stack trace during teardown.
* Does prefix operator supplies several added constraints.
Issues Resolved
* 6 Log4net not working with NUnit
* 13 Standardize commandline options for nunitlite runner
* 17 No allowance is currently made for nullable arguents in TestCase parameter conversions
* 33 TestCaseSource cannot refer to a parameterized test fixture
* 54 Store message and stack trace in TestContext for use in TearDown
* 111 Implement Changes to File, Directory and Path Assertions
* 112 Implement Action Attributes
* 156 Accessing multiple AppDomains within unit tests result in SerializationException
* 163 Add --trace option to NUnitLite
* 167 Create interim documentation for the alpha release
* 169 Design and implement distribution of NUnit packages
* 171 Assert.That should work with any lambda returning bool
* 175 Test Harness should return an error if any tests fail
* 180 Errors in Linux CI build
* 181 Replace NAnt with MsBuild / XBuild
* 183 Standardize commandline options for test harness
* 188 No output from NUnitLite when selected test is not found
* 189 Add string operators to Does prefix
* 193 TestWorkerTests.BusyExecutedIdleEventsCalledInSequence fails occasionally
* 197 Deprecate or remove Assert.NullOrEmpty
* 202 Eliminate legacy suites
* 203 Combine framework, engine and console runner in a single solution and repository
* 209 Make Ignore attribute's reason mandatory
* 215 Running 32-bit tests on a 64-bit OS
* 219 Teardown failures are not reported
Console Issues Resolved (Old nunit-console project, now combined with nunit)
* 2 Failure in TestFixtureSetUp is not reported correctly
* 5 CI Server for nunit-console
* 6 System.NullReferenceException on start nunit-console-x86
* 21 NUnitFrameworkDriverTests fail if not run from same directory
* 24 'Debug' value for /trace option is deprecated in 2.6.3
* 38 Confusing Excluded categories output
NUnit 2.9.7 - August 8, 2014
Breaking Changes
* NUnit no longer supports void async test methods. You should use a Task return Type instead.
* The ExpectedExceptionAttribute is no longer supported. Use Assert.Throws() or Assert.That(..., Throws) instead for a more precise specification of where the exception is expected to be thrown.
New Features
* Parallel test execution is supported down to the Fixture level. Use ParallelizableAttribute to indicate types that may be run in parallel.
* Async tests are supported for .NET 4.0 if the user has installed support for them.
* A new FileExistsConstraint has been added along with FileAssert.Exists and FileAssert.DoesNotExist
* ExpectedResult is now supported on simple (non-TestCase) tests.
* The Ignore attribute now takes a named parameter Until, which allows specifying a date after which the test is no longer ignored.
* The following new values are now recognized by PlatformAttribute: Win7, Win8, Win8.1, Win2012Server, Win2012ServerR2, NT6.1, NT6.2, 32-bit, 64-bit
* TimeoutAttribute is now supported under Silverlight
* ValuesAttribute may be used without any values on an enum or boolean argument. All possible values are used.
* You may now specify a tolerance using Within when testing equality of DateTimeOffset values.
* The XML output now includes a start and end time for each test.
Issues Resolved
* 8 [SetUpFixture] is not working as expected
* 14 CI Server for NUnit Framework
* 21 Is.InRange Constraint Ambiguity
* 27 Values attribute support for enum types
* 29 Specifying a tolerance with "Within" doesn't work for DateTimeOffset data types
* 31 Report start and end time of test execution
* 36 Make RequiresThread, RequiresSTA, RequiresMTA inheritable
* 45 Need of Enddate together with Ignore
* 55 Incorrect XML comments for CollectionAssert.IsSubsetOf
* 62 Matches(Constraint) does not work as expected
* 63 Async support should handle Task return type without state machine
* 64 AsyncStateMachineAttribute should only be checked by name
* 65 Update NUnit Wiki to show the new location of samples
* 66 Parallel Test Execution within test assemblies
* 67 Allow Expected Result on simple tests
* 70 EquivalentTo isn't compatible with IgnoreCase for dictioneries
* 75 Async tests should be supported for projects that target .NET 4.0
* 82 nunit-framework tests are timing out on Linux
* 83 Path-related tests fail on Linux
* 85 Culture-dependent NUnit tests fail on non-English machine
* 88 TestCaseSourceAttribute documentation
* 90 EquivalentTo isn't compatible with IgnoreCase for char
* 100 Changes to Tolerance definitions
* 110 Add new platforms to PlatformAttribute
* 113 Remove ExpectedException
* 118 Workarounds for missing InternalPreserveStackTrace in mono
* 121 Test harness does not honor the --worker option when set to zero
* 129 Standardize Timeout in the Silverlight build
* 130 Add FileAssert.Exists and FileAssert.DoesNotExist
* 132 Drop support for void async methods
* 153 Surprising behavior of DelayedConstraint pollingInterval
* 161 Update API to support stopping an ongoing test run
NOTE: Bug Fixes below this point refer to the number of the bug in Launchpad.
NUnit 2.9.6 - October 4, 2013
Main Features
* Separate projects for nunit-console and nunit.engine
* New builds for .NET 4.5 and Silverlight
* TestContext is now supported
* External API is now stable; internal interfaces are separate from API
* Tests may be run in parallel on separate threads
* Solutions and projects now use VS2012 (except for Compact framework)
Bug Fixes
* 463470 We should encapsulate references to pre-2.0 collections
* 498690 Assert.That() doesn't like properties with scoped setters
* 501784 Theory tests do not work correctly when using null parameters
* 531873 Feature: Extraction of unit tests from NUnit test assembly and calling appropriate one
* 611325 Allow Teardown to detect if last test failed
* 611938 Generic Test Instances disappear
* 655882 Make CategoryAttribute inherited
* 664081 Add Server2008 R2 and Windows 7 to PlatformAttribute
* 671432 Upgrade NAnt to Latest Release
* 676560 Assert.AreEqual does not support IEquatable<T>
* 691129 Add Category parameter to TestFixture
* 697069 Feature request: dynamic location for TestResult.xml
* 708173 NUnit's logic for comparing arrays - use Comparer<T[]> if it is provided
* 709062 "System.ArgumentException : Cannot compare" when the element is a list
* 712156 Tests cannot use AppDomain.SetPrincipalPolicy
* 719184 Platformdependency in src/ClientUtilities/util/Services/DomainManager.cs:40
* 719187 Using Path.GetTempPath() causes conflicts in shared temporary folders
* 735851 Add detection of 3.0, 3.5 and 4.0 frameworks to PlatformAttribute
* 736062 Deadlock when EventListener performs a Trace call + EventPump synchronisation
* 756843 Failing assertion does not show non-linear tolerance mode
* 766749 net-2.0\nunit-console-x86.exe.config should have a <startup /> element and also enable loadFromRemoteSources
* 770471 Assert.IsEmpty does not support IEnumerable
* 785460 Add Category parameter to TestCaseSourceAttribute
* 787106 EqualConstraint provides inadequate failure information for IEnumerables
* 792466 TestContext MethodName
* 794115 HashSet incorrectly reported
* 800089 Assert.Throws() hides details of inner AssertionException
* 848713 Feature request: Add switch for console to break on any test case error
* 878376 Add 'Exactly(n)' to the NUnit constraint syntax
* 882137 When no tests are run, higher level suites display as Inconclusive
* 882517 NUnit 2.5.10 doesn't recognize TestFixture if there are only TestCaseSource inside
* 885173 Tests are still executed after cancellation by user
* 885277 Exception when project calls for a runtime using only 2 digits
* 885604 Feature request: Explicit named parameter to TestCaseAttribute
* 890129 DelayedConstraint doesn't appear to poll properties of objects
* 892844 Not using Mono 4.0 profile under Windows
* 893919 DelayedConstraint fails polling properties on references which are initially null
* 896973 Console output lines are run together under Linux
* 897289 Is.Empty constraint has unclear failure message
* 898192 Feature Request: Is.Negative, Is.Positive
* 898256 IEnumerable<T> for Datapoints doesn't work
* 899178 Wrong failure message for parameterized tests that expect exceptions
* 904841 After exiting for timeout the teardown method is not executed
* 908829 TestCase attribute does not play well with variadic test functions
* 910218 NUnit should add a trailing separator to the ApplicationBase
* 920472 CollectionAssert.IsNotEmpty must dispose Enumerator
* 922455 Add Support for Windows 8 and Windows 2012 Server to PlatformAttribute
* 928246 Use assembly.Location instead of assembly.CodeBase
* 958766 For development work under TeamCity, we need to support nunit2 formatted output under direct-runner
* 1000181 Parameterized TestFixture with System.Type as constructor arguments fails
* 1000213 Inconclusive message Not in report output
* 1023084 Add Enum support to RandomAttribute
* 1028188 Add Support for Silverlight
* 1029785 Test loaded from remote folder failed to run with exception System.IODirectory
* 1037144 Add MonoTouch support to PlatformAttribute
* 1041365 Add MaxOsX and Xbox support to platform attribute
* 1057981 C#5 async tests are not supported
* 1060631 Add .NET 4.5 build
* 1064014 Simple async tests should not return Task<T>
* 1071164 Support async methods in usage scenarios of Throws constraints
* 1071343 Runner.Load fails on CF if the test assembly contains a generic method
* 1071861 Error in Path Constraints
* 1072379 Report test execution time at a higher resolution
* 1074568 Assert/Assume should support an async method for the ActualValueDelegate
* 1082330 Better Exception if SetCulture attribute is applied multiple times
* 1111834 Expose Random Object as part of the test context
* 1111838 Include Random Seed in Test Report
* 1172979 Add Category Support to nunitlite Runner
* 1203361 Randomizer uniqueness tests sometimes fail
* 1221712 When non-existing test method is specified in -test, result is still "Tests run: 1, Passed: 1"
* 1223294 System.NullReferenceException thrown when ExpectedExceptionAttribute is used in a static class
* 1225542 Standardize commandline options for test harness
Bug Fixes in 2.9.6 But Not Listed Here in the Release
* 541699 Silverlight Support
* 1222148 /framework switch does not recognize net-4.5
* 1228979 Theories with all test cases inconclusive are not reported as failures
NUnit 2.9.5 - July 30, 2010
Bug Fixes
* 483836 Allow non-public test fixtures consistently
* 487878 Tests in generic class without proper TestFixture attribute should be invalid
* 498656 TestCase should show array values in GUI
* 513989 Is.Empty should work for directories
* 519912 Thread.CurrentPrincipal Set In TestFixtureSetUp Not Maintained Between Tests
* 532488 constraints from ConstraintExpression/ConstraintBuilder are not reusable
* 590717 categorie contains dash or trail spaces is not selectable
* 590970 static TestFixtureSetUp/TestFixtureTearDown methods in base classes are not run
* 595683 NUnit console runner fails to load assemblies
* 600627 Assertion message formatted poorly by PropertyConstraint
* 601108 Duplicate test using abstract test fixtures
* 601645 Parametered test should try to convert data type from source to parameter
* 605432 ToString not working properly for some properties
* 606548 Deprecate Directory Assert in 2.5 and remove it in 3.0
* 608875 NUnit Equality Comparer incorrectly defines equality for Dictionary objects
NUnit 2.9.4 - May 4, 2010
Bug Fixes
* 419411 Fixture With No Tests Shows as Non-Runnable
* 459219 Changes to thread princpal cause failures under .NET 4.0
* 459224 Culture test failure under .NET 4.0
* 462019 Line endings needs to be better controlled in source
* 462418 Assume.That() fails if I specify a message
* 483845 TestCase expected return value cannot be null
* 488002 Should not report tests in abstract class as invalid
* 490679 Category in TestCaseData clashes with Category on ParameterizedMethodSuite
* 501352 VS2010 projects have not been updated for new directory structure
* 504018 Automatic Values For Theory Test Parameters Not Provided For bool And enum
* 505899 'Description' parameter in both TestAttribute and TestCaseAttribute is not allowed
* 523335 TestFixtureTearDown in static class not executed
* 556971 Datapoint(s)Attribute should work on IEnumerable<T> as well as on Arrays
* 561436 SetCulture broken with 2.5.4
* 563532 DatapointsAttribute should be allowed on properties and methods
NUnit 2.9.3 - October 26, 2009
Main Features
* Created new API for controlling framework
* New builds for .Net 3.5 and 4.0, compact framework 3.5
* Support for old style tests has been removed
* New adhoc runner for testing the framework
Bug Fixes
* 432805 Some Framework Tests don't run on Linux
* 440109 Full Framework does not support "Contains"
NUnit 2.9.2 - September 19, 2009
Main Features
* NUnitLite code is now merged with NUnit
* Added NUnitLite runner to the framework code
* Added Compact framework builds
Bug Fixes
* 430100 Assert.Catch<T> should return T
* 432566 NUnitLite shows empty string as argument
* 432573 Mono test should be at runtime
NUnit 2.9.1 - August 27, 2009
General
* Created a separate project for the framework and framework tests
* Changed license to MIT / X11
* Created Windows installer for the framework
Bug Fixes
* 400502 NUnitEqualityComparer.StreamsE­qual fails for same stream
* 400508 TestCaseSource attirbute is not working when Type is given
* 400510 TestCaseData variable length ctor drops values
* 417557 Add SetUICultureAttribute from NUnit 2.5.2
* 417559 Add Ignore to TestFixture, TestCase and TestCaseData
* 417560 Merge Assert.Throws and Assert.Catch changes from NUnit 2.5.2
* 417564 TimeoutAttribute on Assembly
Copyright (c) 2015 Charlie Poole
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
NUnit 3.0 is based on earlier versions of NUnit, with Portions
Copyright (c) 2002-2014 Charlie Poole or
Copyright (c) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov or
Copyright (c) 2000-2002 Philip A. Craig
<?xml version="1.0" encoding="utf-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" /><Default Extension="nuspec" ContentType="application/octet" /><Default Extension="txt" ContentType="application/octet" /><Default Extension="dll" ContentType="application/octet" /><Default Extension="xml" ContentType="application/octet" /><Default Extension="psmdcp" ContentType="application/vnd.openxmlformats-package.core-properties+xml" /></Types>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment