Sunday, May 31, 2015

Moved Home

I've moved to WordPress, find me @ http://gilescope.ninja

Monday, July 27, 2009

Resharper test live templates

[NUnit.Framework.TestFixture]
public class $CLASSNAME$Test
{
[NUnit.Framework.Test]
public void When_$Action$_Then_$Effect$()
{
$END$
}
}

Friday, January 23, 2009

Assert++++

An assert that will let you assert multiple things at the same time
AND gives good failed assert messages.

Instead of:

Assert.That(x != null);
Assert.That(x.Length > 3);
Assert.That(x[0] == "Foo");

You can write:

Spec.That(() => x != null
&& x.Length > 3
&& x[0] == "Foo");

Enjoy!

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using NUnit.Framework;

namespace SvnTracker.Lang
{
/// <summary>
/// How to use:
///
/// Spec.That(()=> your assertion here);
/// E.g.
/// Spec.That(()=> 1 > 0);
///
/// or you could do:
///
/// 1.Assert(n => n > 0);
///
/// There is also an implementation of like which uses an object initialisation to
/// compare against an existing object, but only equality can be compared and worse,
/// only properties with setters which I think would lead people into bad habbits.
/// </summary>
public static class Spec//ify
{
public static bool Like<T>(this T subject, Expression<Func<T>> template)
{
ApplyLike("", subject, (MemberInitExpression) template.Body, true);
return true;
}

public static bool NotLike<T>(this T subject, Expression<Func<T>> template)
{
ApplyLike("", subject, (MemberInitExpression) template.Body, false);
return true;
}

class Results
{
public Results()
{
Output = new StringBuilder();
}

public bool Fail { get; set; }
public StringBuilder Output { get; private set; }
}

private static void ApplyLike(string level, object subject, MemberInitExpression template, bool success)
{
var results = new Results();
foreach (var binding in template.Bindings)
{
ApplyLike(level, subject, binding, success, results);
}
if (results.Fail)
{
throw new AssertionException("\r\n" + results.Output);
}
}

private static void ApplyLike(string level, object subject, MemberBinding binding, bool success, Results results)
{
var expression = ((MemberAssignment) binding).Expression;

object actual;
if (binding.Member is FieldInfo)
{
var field = (FieldInfo) binding.Member;
actual = field.GetValue(subject);
}
else
{
var prop = (PropertyInfo) binding.Member;
actual = prop.GetGetMethod().Invoke(subject, new object[] {});
}

if (expression.NodeType == ExpressionType.MemberInit)
{
var initExp = (MemberInitExpression) expression;
results.Output.Append(level + "\t" + binding.Member.Name + " = ");
results.Output.AppendLine(initExp.NewExpression.ToString());
foreach (MemberBinding childBinding in initExp.Bindings)
{
ApplyLike(level + "\t", actual, childBinding, success, results);
}
return;
}

if (expression is ListInitExpression)
{
var listExp = (ListInitExpression) expression;
if (actual is IList)
{
foreach (ElementInit element in listExp.Initializers)
{
Step value = new ExpressionEvaluator(null, null).Visit(element.Arguments[0]);
if (!((IList) actual).Contains(value.Value))
{
results.Fail = true;
results.Output.AppendLine(level + "\t" + binding.Member.Name + ": " + value.Value +
" not contained");
}
//Technically only need add + are enumerable
}
}
else
{
var actDict = (IDictionary) actual;
foreach (ElementInit element in listExp.Initializers)
{
Step key = new ExpressionEvaluator(null, null).Visit(element.Arguments[0]);
Step value = new ExpressionEvaluator(null, null).Visit(element.Arguments[1]);
if (!actDict.Contains(key.Value))
{
results.Fail = true;
results.Output.AppendLine(level + "\t" + binding.Member.Name + ": " + value.Value +
" not contained");
}
else
{
var entryValue = actDict[key.Value];
if (element.Arguments[1] is MemberInitExpression)
{
if (entryValue == null)
{
//We expect an object to be here.
results.Fail = true;
string message = binding.Member.Name + "[" + key.Value + "]";
results.Output.AppendLine(level + "\t" + message + " Expected: '" + value.Value +
"'");
results.Output.AppendLine(level + '\t' + Multiply(' ', message.Length) +
" Actual: '" + Display(entryValue) + "'");
}
else
{
var exp = (MemberInitExpression) element.Arguments[1];
string message = binding.Member.Name + "[" + key.Value + "]";
results.Output.AppendLine(level + "\t" + message);
foreach (MemberBinding childBinding in exp.Bindings)
{
ApplyLike(level + "\t", entryValue, childBinding, success, results);
}
}
}
else
{
var equal = ExpressionEvaluator.Eval<bool>(ExpressionType.Equal, value.Value,
entryValue);
//Check values are equal.
if (!equal)
{
results.Fail = true;
string message = binding.Member.Name + "[" + key.Value + "]";
results.Output.AppendLine(level + "\t" + message + " Expected: '" + value.Value +
"'");
results.Output.AppendLine(level + '\t' + Multiply(' ', message.Length) +
" Actual: '" + entryValue + "'");
}
}
}
}
}
return;
}

//Assume dict.
{
Step value = new ExpressionEvaluator(null, null).Visit(expression);
//field or property.

var equal = ExpressionEvaluator.Eval<bool>(ExpressionType.Equal, value.Value, actual);
if (!(equal == success))
{
results.Fail = true;
results.Output.AppendLine(level + "\t" + binding.Member.Name + " Expected: '" + value.Value + "'");
results.Output.AppendLine(level + '\t' + Multiply(' ', binding.Member.Name.Length) + " Actual: '" +
actual + "'");
}
}
return;
}

private static string Display(object value)
{
return value == null ? "null" : value.ToString();
}

private static string Multiply(char ch, int length)
{
var sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
sb.Append(ch);
return sb.ToString();
}

public static bool AssertFalse(Expression<Func<bool>> func)
{
Assert(false, func);
return true;
}

public static bool AssertFalse<T>(this T subject, Expression<Func<T, bool>> func)
{
Assert(subject, false, func);
return true;
}

public static bool That(Expression<Func<bool>> func)
{
Assert(true, func);
return true;
}

public static bool Assert<T>(this T subject, Expression<Func<T, bool>> func)
{
Assert(subject, true, func);
return true;
}

public static void Assert(bool success, Expression<Func<bool>> func)
{
Assert(success, success, null, func.Body);
}

private static void Assert<T>(this T subject, bool success, Expression<Func<T, bool>> func)
{
Assert(subject, success, func.Parameters[0], func.Body);
}

private static void Assert<T>(this T subject, bool success, ParameterExpression parameter, Expression expression)
{
var sb = new StringBuilder();
bool failed = false;
var exps = new List<Expression>();
Expand(expression, exps);
Exception lastCaught = null;
foreach (var exp in exps)
{
var sbb = new StringBuilder();
if (Eval(parameter, exp, subject, sbb, out lastCaught) != success)
{
failed = true;
sb.Append(sbb.ToString());
}
}
string msg = sb.ToString();
if (failed)
{
var message = "\r\n" + subject + " fails these constraints:\r\n\r\n" + msg;
if (lastCaught != null)
{
throw new AssertionException(message, lastCaught);
}
throw new AssertionException(message);
}
}

private static readonly Dictionary<ExpressionType, Func<Expression, UnaryExpression>> uniExps =
new Dictionary<ExpressionType, Func<Expression, UnaryExpression>>{
{ ExpressionType.Not, Expression.Not},
{ ExpressionType.Negate, Expression.Negate},
{ ExpressionType.NegateChecked, Expression.NegateChecked},
{ ExpressionType.UnaryPlus, Expression.UnaryPlus},
//{ ExpressionType.Quote, Expression.Quote},
} ;

static readonly Dictionary<ExpressionType, Func<Expression, Expression, BinaryExpression>> binExps = new Dictionary<ExpressionType, Func<Expression, Expression, BinaryExpression>>
{
{ ExpressionType.AndAlso, Expression.And},
{ ExpressionType.Modulo, Expression.Modulo},
{ ExpressionType.ExclusiveOr, Expression.ExclusiveOr},
{ ExpressionType.Power, Expression.Power},
{ ExpressionType.RightShift, Expression.RightShift},
{ ExpressionType.LeftShift, Expression.LeftShift},
{ ExpressionType.Multiply, Expression.Multiply},
{ ExpressionType.MultiplyChecked, Expression.MultiplyChecked},
{ ExpressionType.Divide, Expression.Divide},
{ ExpressionType.Add, Expression.Add},
{ ExpressionType.AddChecked, Expression.AddChecked},
{ ExpressionType.Subtract, Expression.Subtract},
{ ExpressionType.SubtractChecked, Expression.SubtractChecked},
{ ExpressionType.OrElse, Expression.OrElse},
{ ExpressionType.Or, Expression.Or},
{ ExpressionType.And, Expression.Add},
{ ExpressionType.GreaterThan, Expression.GreaterThan},
{ ExpressionType.GreaterThanOrEqual, Expression.GreaterThanOrEqual},
{ ExpressionType.LessThan, Expression.LessThan},
{ ExpressionType.LessThanOrEqual, Expression.LessThanOrEqual},
{ ExpressionType.NotEqual, Expression.NotEqual},
{ ExpressionType.Equal, Expression.Equal},
{ ExpressionType.ArrayIndex, Expression.ArrayIndex},
};

static readonly Dictionary<ExpressionType, string> m_formatStrings = new Dictionary<ExpressionType, string>
{
//Unary
{ ExpressionType.Not, "!{0}"},
{ ExpressionType.Negate, "-{0}"},
{ ExpressionType.NegateChecked, "-{0}"},
{ ExpressionType.UnaryPlus, "+{0}"},
//{ ExpressionType.Quote, "'{0}'"},

//Binary
{ ExpressionType.AndAlso, "{0} && {1}"},
{ ExpressionType.Modulo, "{0} % {1}"},
{ ExpressionType.ExclusiveOr, "{0} ^ {1}"},
{ ExpressionType.Power, "{0} toThePowerOf {1}"},
{ ExpressionType.RightShift, "{0} >> {1}"},
{ ExpressionType.LeftShift, "{0} << {1}"},
{ ExpressionType.Multiply, "{0} * {1}"},
{ ExpressionType.MultiplyChecked, "{0} * {1}"},
{ ExpressionType.Divide, "{0} / {1}"},
{ ExpressionType.Add, "{0} + {1}"},
{ ExpressionType.AddChecked, "{0} + {1}"},
{ ExpressionType.Subtract, "{0} - {1}"},
{ ExpressionType.SubtractChecked, "{0} - {1}"},
{ ExpressionType.OrElse, "{0} || {1}"},
{ ExpressionType.Or, "{0} | {1}"},
{ ExpressionType.And, "{0} & {1}"},
{ ExpressionType.GreaterThan, "{0} > {1}"},
{ ExpressionType.GreaterThanOrEqual, "{0} >= {1}"},
{ ExpressionType.LessThan, "{0} < {1}"},
{ ExpressionType.LessThanOrEqual, "{0} <= {1}"},
{ ExpressionType.NotEqual, "{0} != {1}"},
{ ExpressionType.Equal, "{0} == {1}"},
{ ExpressionType.ArrayIndex, "{0}[{1}]"},
};

private static void Expand(Expression body, IList<Expression> expantion)
{
if (body.NodeType != ExpressionType.AndAlso)
{
expantion.Add(body);
return;
}
var binExp = (BinaryExpression)body;
Expand(binExp.Left, expantion);
Expand(binExp.Right, expantion);
}

private static bool Eval<T>(ParameterExpression parameterExpression, Expression body, T subject, StringBuilder sb, out Exception lastCaught)
{
lastCaught = null;
var evaluator = new ExpressionEvaluator(subject, parameterExpression);
Step reval;
try
{
reval = evaluator.Visit(body);
sb.AppendLine(reval.Key);
return (bool) reval.Value;
}
catch (IndexOutOfRangeException e)
{
var result = new StringBuilder("BANG!\r\n" + e.Message);
lastCaught = e;
if (evaluator.Subject is ICollection)
{
result.AppendLine("(" + evaluator.Subject + ".Count == " + ((ICollection)evaluator.Subject).Count + ")");
}
evaluator.Result(result.ToString());
return false;
}
catch (TargetInvocationException e)
{
lastCaught = e.InnerException;
var result = new StringBuilder("BANG!\r\n" + e.InnerException.Message);

if (e.InnerException is ArgumentOutOfRangeException && subject is ICollection)
{
result.AppendLine("(" + evaluator.Subject + ".Count == " + ((ICollection)evaluator.Subject).Count + ")");
}
evaluator.Result(result.ToString());
return false;
}
catch (Exception e)
{
throw new AssertionException("While evaluating " + body + "\r\n" + e.Message, e);
}
finally
{
int i = 1;
foreach (string step in evaluator.evaluationSteps)
{
sb.AppendLine("\t" + i++ + ": " + step);
}
sb.AppendLine();
}
}

class Step
{
public Step(string key, object value)
{
Key = key;
Value = value;
}

public string Key { get; set; }
public object Value { get; set; }
}

class ExpressionEvaluator
{
private readonly object m_Parameter;
private readonly Expression m_ParamExp;
public readonly List<string> evaluationSteps = new List<string>();

public ExpressionEvaluator(object parameter, Expression paramExp)
{
m_Parameter = parameter;
m_ParamExp = paramExp;
}

public Step Visit(Expression exp)
{
if (exp is TypeBinaryExpression)
return Visit((TypeBinaryExpression) exp);
if (exp is UnaryExpression)
return Visit((UnaryExpression) exp);
if (exp is NewArrayExpression)
return Visit((NewArrayExpression)exp);
if (exp is ConstantExpression)
return Visit((ConstantExpression)exp);
if (exp is MethodCallExpression)
return Visit((MethodCallExpression) exp);
if (exp is MemberExpression)
return Visit((MemberExpression)exp);
if (exp is BinaryExpression)
return Visit((BinaryExpression) exp);
if (exp is ParameterExpression)
return Visit((ParameterExpression)exp);

return new Step(exp.ToString(), exp.ToString());
}

Step Visit(NewArrayExpression exp)
{
Array array = Array.CreateInstance(exp.Type, exp.Expressions.Count);
for (int i = 0; i < exp.Expressions.Count; i++)
{
array.SetValue(Visit(exp.Expressions[i]), i);
}
return new Step(exp.ToString(), array);
}

Step Visit(TypeBinaryExpression exp)
{
Step step = Visit(exp.Expression);
Subject = step.Value;
switch (exp.NodeType)
{
case ExpressionType.TypeIs:
SetNextStep(Display(Subject) + " is " + exp.TypeOperand);
return new Step(step.Key + " is " + exp.TypeOperand,
Result(exp.TypeOperand.IsAssignableFrom(Subject.GetType())));
}
throw new NotSupportedException(exp.NodeType.ToString());
}

Step Visit(UnaryExpression exp)
{
Step step = Visit(exp.Operand);
Subject = step.Value;
switch (exp.NodeType)
{
// case ExpressionType.Not:
// if (step.Value is bool)
// {
// return new Step("!" + step.Key, !(bool)step.Value);
// }
// return new Step("~" + step.Key, ~(long)step.Value);
// case ExpressionType.Negate:
// case ExpressionType.NegateChecked:
// return new Step("-" + step.Key, -(double)step.Value);
// case ExpressionType.UnaryPlus:
// return new Step("+" + step.Key, step.Value);
// case ExpressionType.Lambda:
// var lambdaExp = (LambdaExpression) exp.Operand;
// return new Step("compiledLamda" , lambdaExp.Compile());
case ExpressionType.Convert:
// try
// {
var type = exp.Type;
var value = step.Value;
object castedObject = Cast(value, type, exp.Method);
return new Step("((" + exp.Type.Name + ")" + step.Key + ")", castedObject);
// }
// catch (TargetInvocationException e)
// {
// if (e.InnerException is InvalidCastException)
// {
// try
// {
// return new Step("((" + exp.Type.Name + ")" + step.Key + ")",
// Convert.ChangeType(step.Value, exp.Type));
// }
// catch (Exception)
// {
// return new Step("((" + exp.Type.Name + ")" + step.Key + ")",
// exp.Method.Invoke(null, new [] { step.Value }));
// }
// }
// }
case ExpressionType.TypeAs:
SetNextStep(Display(Subject) + " as " + exp.Type);
return new Step(step.Key + " as " + exp.Type,
Result(exp.Type.IsAssignableFrom(Subject.GetType()) ? Cast(Subject, exp.Type, exp.Method) : null));
case ExpressionType.Quote:
return new Step("'" + exp.Operand + "'", exp.Operand);
default:
object val = Eval(exp.NodeType, exp.Type, step.Value);
var format = String.Format(m_formatStrings[exp.NodeType], Display(step.Value));
SetNextStep(format);
return new Step(String.Format(m_formatStrings[exp.NodeType], step.Key), Result(val));
}
}

private object Cast(object value, Type type, MethodInfo method)
{
try
{
MethodInfo castMethod = GetType().GetMethod("Cast").MakeGenericMethod(type);
return castMethod.Invoke(null, new[] {value});
}
catch (TargetInvocationException e)
{
if (e.InnerException is InvalidCastException /*e.InnerException.Message.Contains("cast is not valid")*/)
{
try
{
return Convert.ChangeType(value, type);
}
catch(InvalidCastException)
{
if (method != null)
return method.Invoke(null, new[] { value });
throw;
}
}
throw;
}
}

Step Visit(BinaryExpression binExp)
{
Step left = Visit(binExp.Left);
Step right = Visit(binExp.Right);
var lhs = left.Value;
var rhs = right.Value;
var subject = lhs;
MethodInfo method = binExp.Method;
Subject = subject;

if (method == null)
{
object value = Eval(binExp, lhs, rhs);
var format = String.Format(m_formatStrings[binExp.NodeType], Display(left.Value), Display(right.Value));
SetNextStep(format);
return new Step(String.Format(m_formatStrings[binExp.NodeType], left.Key, right.Key), Result(value));
}

if (method.IsStatic)
{
SetNextStep(method.Name + "(" + rhs + ")");
return new Step(method.Name + "(" + right.Key + ")", Result(method.Invoke(null, new [] { Subject, rhs})));
}
SetNextStep(Display(Subject) + "." + method.Name + "(" + rhs + ")");
return new Step(left.Key + "." + method.Name + "(" + right.Key + ")", Result(method.Invoke(Subject, new[] { rhs })));
}

public static object Eval(BinaryExpression binExp, object lhs, object rhs)
{
return Eval(binExp.NodeType, binExp.Type, lhs, rhs);
}

public static T Eval<T>(ExpressionType nodeType, object lhs, object rhs)
{
return (T) Eval(nodeType, typeof(T), lhs, rhs);
}

public static object Eval(ExpressionType nodeType, Type returnType, object lhs, object rhs)
{
var leftType = lhs == null?
rhs == null ?
typeof(object) :
rhs.GetType() :
lhs.GetType();
var rightType = rhs == null?
lhs == null?
typeof(object) :
lhs.GetType() :
rhs.GetType();
var genericMethod = typeof(ExpressionEvaluator).GetMethod("CallBin").MakeGenericMethod(leftType, rightType, returnType);
return genericMethod.Invoke(null, new[] { nodeType, lhs, rhs });
}

// ReSharper disable UnusedMemberInPrivateClass
public static object Eval(ExpressionType nodeType, Type returnType, object lhs)
{
var leftType = lhs == null? typeof(object) : lhs.GetType();

var genericMethod = typeof(ExpressionEvaluator).GetMethod("CallUni").MakeGenericMethod(leftType, returnType);
return genericMethod.Invoke(null, new[] { nodeType, lhs });
}

public static TRet CallBin<TLeft,TRight,TRet>(ExpressionType nodeType, TLeft lhs, TRight rhs)
{
var lambda = Expression.Lambda<Func<TRet>>(binExps[nodeType]
(Expression.Constant(lhs, typeof(TLeft)),
Expression.Constant(rhs, typeof(TRight))));
return lambda.Compile()();
}

public static TRet CallUni<TLeft,TRet>(ExpressionType nodeType, TLeft lhs)
{
Expression expression = Expression.Constant(lhs, typeof (TLeft));
var lambda = Expression.Lambda<Func<TRet>>(uniExps[nodeType](expression));
return lambda.Compile()();
}
// ReSharper restore UnusedMemberInPrivateClass

Step Visit(MethodCallExpression exp)
{
var step = exp.Object == null? new Step("null",null) : Visit(exp.Object);
Subject = step.Value;

if (Subject == null && !exp.Method.IsStatic)
{
SetNextStep("" + Display(Subject) + "." + exp.Method.Name + "(...)");
throw new TargetInvocationException(new Exception(Display(Subject) + " is null."));
}

var argValues = new List<object>();
var argSteps = new List<Step>();
foreach (var arg in exp.Arguments)
{
var stepArg = Visit(arg);
argValues.Add(stepArg.Value);
argSteps.Add(stepArg);
}
Subject = step.Value;

//Indexer
if (exp.Method.Name.StartsWith("get_"))
{
SetNextStep(Display(Subject) + "[" + ToDisplayString(argValues) + "]");
return new Step(step.Key + "[" + ToDisplayString(argSteps) + "]", Result(exp.Method.Invoke(Subject, argValues.ToArray())));
}
SetNextStep(Display(Subject) + "." + exp.Method.Name + "(" + ToDisplayString(argValues) + ")");
return new Step(step.Key + "." + exp.Method.Name + "(" + ToDisplayString(argSteps) + ")", Result(exp.Method.Invoke(Subject, argValues.ToArray())));
}

Step Visit(MemberExpression exp)
{

var step = Visit(exp.Expression);
Subject = step.Value;
SetNextStep("" + Display(Subject) + "." + exp.Member.Name);

if (Subject == null)
{
throw new NullReferenceException(step.Key + " is null. null." + exp.Member.Name + " goes bang.");
}

object result;
if (exp.Member is FieldInfo)
{
var field = (FieldInfo)exp.Member;
result = field.GetValue(Subject);
}
else
{
var prop = (PropertyInfo) exp.Member;
result = prop.GetGetMethod().Invoke(Subject, new object[] {});
}
return new Step(step.Key + "." + exp.Member.Name, Result(result));
}

public string Display(object o)
{
if (o == null) return "null";
if (o == m_Parameter) return m_ParamExp.ToString();

//Anonymous type
if (o.GetType().Name.StartsWith("<"))
{
return "_";
}
if (o is String)
{
return "\"" + o + '"';
}
if (o is char)
{
return "'" + o + "'";
}

//Is toString defined on this type?
if (!HasToStringDefined(o))
{
string name = o.GetType().Name;
return (Char.ToLower(name[0])) + name.Substring(1);
}

return o.ToString();
}

private static bool HasToStringDefined(object o)
{
return (o is string || o.ToString().Length < 20);
//return o.GetType().GetMethod("ToString", BindingFlags.DeclaredOnly) != null;
}

private Step Visit(ConstantExpression c)
{
return new Step(Display(c.Value),c.Value);
}

#pragma warning disable 168
Step Visit(ParameterExpression p)
#pragma warning restore 168
{
return new Step(Display(m_Parameter), m_Parameter);
}

private object m_tryStep;
public object Subject { get; set; }
private void SetNextStep(string from)
{
m_tryStep = from;
}

public object Result(object to)
{
evaluationSteps.Add(m_tryStep + " ==> " + Display(to));
return to;
}

private string ToDisplayString(IEnumerable arguments)
{
var sb = new StringBuilder();
foreach (object arg in arguments)
{
if (sb.Length != 0)
{
sb.Append(", ");
}
sb.Append(Display(arg));
}
return sb.ToString();
}
private static string ToDisplayString(IEnumerable<Step> arguments)
{
var sb = new StringBuilder();
foreach (Step arg in arguments)
{
if (sb.Length != 0)
{
sb.Append(", ");
}
sb.Append(arg.Key);
}
return sb.ToString();
}

// ReSharper disable UnusedMemberInPrivateClass
public static T Cast<T>(object o)
// ReSharper restore UnusedMemberInPrivateClass
{
return (T)o;
}
}
}

[TestFixture]
public class SpecExtentionsSpec
{
#pragma warning disable 184
// ReSharper disable RedundantToStringCall

[Test]
public void TestFailures()
{
const string str = "Fred";
str.AssertFalse(s=>(s as object) == null
&& s is int
&& s.ToString() == "Frd"
&& s[3] == 'e'
&& s.Length > 5
&& "Something really close and long"
== "Something reallly close and long"
);
}

class X : IComparable<X>
{
public string Name { get; set; }
public int CompareTo(X other)
{
return other.Name.CompareTo(Name);
}
}

[Test]
public void TestSuccess()
{
// bool t = true;
// "".Assert(s => (t && !t) || (t && !t));

var x = new X { Name = "Fred" };
var other = new X {Name = "Fred"};
x.Assert(z=>z.CompareTo(other) == 0);

/*
* could we do this?
* var query = from word in words
where word.Length > 4
select word.ToUpper();

* **/
const string str = "Fred";
str.Assert(
s => (s as object) == "Fred"
&& s is string
&& s.ToString() == "Fred"
&& !false
&& s.Length - 2 == 2
&& s[3] == 'd'
&& s.Length > 2
);

}

[Test]
public void BinOperatorTests()
{
int s = 1;
int j = 3;
int [] fib = { 1, 1, 2, 3, 5 };
1.Assert(n => n != j - s
&& fib[0] == 1 );

1.Assert(n => n + 1 == 2
&& n - 1 == 0
&& n % 2 == 1
&& (n ^ 1) == 0
&& n * 2 == 2
&& n / 2 == 0
&& ((double)n) / 2 == 0.5
&& n != 2
);
}

[Test]
public void UnaryOperatorTests()
{
1.Assert(n => -n == -1
&& !(n - 1 == 2)
&& n == +1
);
}

class A
{
public B Bee { get; set; }
}

class B
{
public string C;
}

class HasList
{
public IList<string> MyList { get { return new List<string> {"George", "Bob", "Ringo"}; } set { /*alas requres setter! */} }
}

class HasDict
{
public IDictionary<string, object> MyDict
{
get { return new Dictionary<string, object>
{
{"George", 1}, {"Bob", 2}, {"Ringo", 3}
}
;
} set { /*alas requres setter! */} }
}

class HasDictList
{
public IDictionary<string, B> MyDict
{
get
{
return new Dictionary<string, B>
{
{"George", new B{C = "1"}}, {"Bob", new B()}, {"Ringo", null}
}
;
}
set { /*alas requres setter! */}
}
}


[Test]
public void WhatsNotToLikeSuccess()
{
new HasDictList().Like(() => new HasDictList
{
MyDict = new Dictionary<string, B>
{
{"George", new B{C = "1"}},
{"Ringo", null }
}
});

//IDictionary
new HasDict().Like(() => new HasDict
{
MyDict = new Dictionary<string,object>
{
{"George", 1},
{"Ringo", 3}
}
});

//List
new HasList().Like(() => new HasList
{
MyList = new List<string>
{
"George",
"Ringo",
}
});

//Simple
new NetworkCredential("Fred", "Open sesame")
.Like(() => new NetworkCredential{ UserName = "Fred"} );

//Recursive
new A { Bee = new B { C = "Dr Dee" } }
.Like(() => new A { Bee = new B { C = "Dr Dee" } });

//TODO link a Like with custom asserts...
new NetworkCredential
{
UserName = "".Should(name=> name.Length > 4)
};
}

[Test]
public void WhatsNotToLikeFail()
{
new NetworkCredential("Fred", "Open sesame")
.NotLike(() => new NetworkCredential { UserName = "Freddy" });

new NetworkCredential
{
UserName = "".Should(name => name.Length > 4)
};
}

public class Fixture
{
public string m_field;
public string Property { get; set;}
}

[Test]
public void Throw_null_pointer_message_When_left_of_field_access_is_null()
{
try
{
var fixture = new Fixture();
Spec.That(() => fixture.m_field.Length == 0);
}
catch (AssertionException e)
{
Assert.That(e.Message.Contains("null"));
}
}

[Test]
public void Throw_null_pointer_message_When_left_of_property_access_is_null()
{
try
{
var fixture = new Fixture();
Spec.That(() => fixture.Property.Length == 0);
}
catch (AssertionException e)
{
Assert.That(e.Message.Contains("null"));
}
}


// ReSharper restore RedundantToStringCall
#pragma warning restore 184

[Test]
public void Should_be_able_to_assert_when_implicit_cast_is_called()
{
XElement xdoc = XElement.Parse(@"<xml><child/><child/></xml>");
Spec.That(() => xdoc.Descendants(/* implicit cast to XName */"child").Count() > 0);
}
}

static class YX
{
public static T Should<T>(this T subject, Expression<Func<T,bool>> func)
{
return default(T);
}
}

}

Monday, January 19, 2009

Assert++

My latest extension method allows you to specify your asserts in a more natural way. It also tells you the set of asserts that fail (a nod towards Fit).

Usage:

model.Check(c=>c.User == "Mr Dee"
&& Revision == 5
&& IsRelevant);


And here's the more readable assertion method:

NUnit.Framework.AssertionException: These constraints do not hold:

c => (c.User = "Mr Dee")

c => (c.Revision = 5)

c => c.IsRelevant

For 'SvnTracker.Model.LogEntry'


(Java guys don't get jealous - check out HamCrest). All that is left is to dig a little in the expression tree and say what value c.Revision was. Tomorrow!

Here's the code:
(error handling will come later)

public static void Check<T>(this T subject, params Expression<Func<T,bool>> [] funcs)
{
var sb = new StringBuilder();
foreach (var func in funcs)
{
if (!func.Compile()(subject))
{
sb.AppendLine("\r\n" + func);
}
}
string msg = sb.ToString();
if (!String.IsNullOrEmpty(msg))
{
Assert.Fail("These constraints do not hold:\r\n" + msg + "\r\nFor '" + subject + "'");
}
}

Wednesday, October 29, 2008

c# Event null checking

I've always considered having to check an event to see if it's null (because it has no listeners) a flaw in C#. I'm guessing it aids performance as you don't need to construct an event args object if no one is going to consume it.

Anyway, for the most of us, here's some extension methods that allow you to Fire an event and hide away the null checking:


public static class EventHandlerExtentions
{
public static void Raise(this EventHandler handler, object sender)
{
Raise(handler, sender, EventArgs.Empty);
}

public static void Raise(this EventHandler handler, object sender, EventArgs args)
{
if (handler != null)
{
handler(sender, args);
}
}

public static void Raise(this EventHandler handler, object sender, TA args)
where TA : EventArgs
{
if (handler != null)
{
handler(sender, args);
}
}
}


UPDATE:

The simpler alternative is to add an empty delegate when declaring an event:

event MyEvent = delegate {}

Wednesday, October 22, 2008

Poor Man's Coverage

If you havn't time to bake in a coverage tool to your build, you could just inspect your types and fail if any don't have an associated test.

This was our type exclusion list:

!t.Name.Contains("Test")
&& !t.Name.Contains("Stub")
&& !t.Name.Contains("Spec")
&& !t.IsInterface
&& t.IsClass
&& t.IsPublic
&& !t.ContainsGenericParameters
&& !t.IsNested
&& !t.IsAbstract
&& !t.IsSealed
&& !t.IsSerializable
&& !t.IsSubclassOf(typeof(Attribute))
&& t.GetCustomAttributes(typeof(GeneratedCodeAttribute), true).Length == 0


It's not comprehensive, but all you need is a nudge now and again to remind you to follow the right path.

c# foreach considered harmful in V3

If your using C# 3, don't use the foreach keyword as you can replace it with ForEach on List and extention methods:

public static void ForEach(this IEnumerable items, Action action)
{
if (items == null)
return;

if (action == null)
throw new ArgumentNullException("action");

foreach (var item in items)
action(item);
}

This will give you compile time type checking which the foreach keyword would not pick up until runtime.