LinqCheck provides the most important arbitrary types out-of-the-box. We define the built-in implementations for the IArbitrary interface in the DefaultArbitrary static class.
namespace LinqCheck
{
using System;
using System.Collections.Generic;
using System.Linq;
using ExtensionCord;
public static class DefaultArbitrary
{
All of the default arbitrary types are registered automatically in the static constructor. Most of these types rely on the helper methods, which are introduced below. We will discuss the details of each type in context of the helper methods.
internal static void Register ()
{
Characters are randomly selected from a predefined set. This produces simpler and more readable characters than randomly generating ASCII codes.
Arbitrary.Register (new Arbitrary<char> (
Gen.ChooseFrom (CharCandidates ().ToArray ()),
ShrinkChar));
Integral types int
and long
use the same helpers to generate
and shrink the values. The maximum number generated depends on the
size parameter used by the generators.
Arbitrary.Register (new Arbitrary<int> (
Gen.ChooseInt (),
x => ShrinkInteger (x).Distinct ()));
Arbitrary.Register (new Arbitrary<long> (
Gen.ChooseInt ().ToLong (),
x => ShrinkInteger ((int)x).Distinct ().Select (i => (long)i)));
float
and double
types are generated and shrunk with the same
methods. The results are casted to lower precision when necessary.
Arbitrary.Register (new Arbitrary<float> (
Gen.ChooseDouble ().ToFloat (),
x => ShrinkDouble (x).Select (d => (float)d)));
Arbitrary.Register (new Arbitrary<double> (
Gen.ChooseDouble (),
ShrinkDouble));
Strings are arrays of characters, so they can be composed from arbitrary characters with appropriate combinators.
Arbitrary.Register (new Arbitrary<string> (
from a in Arbitrary.Gen<char> ().ArrayOf ()
select new string (a),
x => ShrinkEnumerable (x).Select (cs => new string (cs.ToArray ()))));
The arbitrary implementations for collections are generic. Therefore, they work with any item type. Their implementation is discussed below.
Arbitrary.Register (typeof (Enumerable<>));
Arbitrary.Register (typeof (Array<>));
Arbitrary.Register (typeof (AList<>));
}
The following methods are used in producing and shrinking the primitive types. First, let's examine how we generate and shrink characters.
Characters are selected from the printable part of the ASCII table. If you want to generate characters from the whole ASCII table, or even from the Unicode character set, you can do so by first generating a random integer with appropriate range and using it as a character code.
private static IEnumerable<char> CharCandidates ()
{
for (char c = 'A'; c <= '~'; c++)
yield return c;
for (char c = ' '; c < 'A'; c++)
yield return c;
yield return '\t';
yield return '\n';
}
The rules of character shrinking are a bit arbitrary but agreeable. We prefer to have lowercase letters over uppercase ones, and letters over numbers or whitespace.
private static IEnumerable<char> ShrinkChar (char c)
{
var candidates = new char[]
{ 'a', 'b', 'A', 'B', '1', '2', char.ToLower (c), ' ' };
return candidates.Where (x => x.SimplerThan (c));
}
private static bool SimplerThan (this char x, char y)
{
bool simpler (Func<char, bool> fun) => fun (x) && !fun (y);
return simpler (char.IsLower) || simpler (char.IsUpper) ||
simpler (char.IsDigit) || simpler (c => c == ' ') ||
simpler (char.IsWhiteSpace) || x < y;
}
When shrinking integers we prefer to have values close to zero and positive numbers before negative ones.
private static IEnumerable<int> ShrinkInteger (int x)
{
yield return 0;
if (x < 0) yield return -x;
for (var i = x / 2; Math.Abs (x - i) < Math.Abs (x); i = i / 2)
yield return x - i;
}
We simplify floating point numbers by trying first zero and then the value truncated down or up. Finally we try to make the number positive, if it is negative.
private static IEnumerable<double> ShrinkDouble (double x)
{
yield return 0.0;
yield return Math.Floor (x);
yield return Math.Ceiling (x);
if (x < 0.0) yield return -x;
}
The IEnumerable type has the most involved shrinking procedure. We first try to remove as many items from the enumerable as we can, and then we shrink each individual element at a time. The simplest case, and the first alternative returned, is the empty enumerable.
public static IEnumerable<IEnumerable<T>> ShrinkEnumerable<T> (
this IEnumerable<T> e)
{
return Shorten (e)
.SelectMany (Fun.Identity)
.Concat (ShrinkOne (e))
.Prepend (new T[0]);
}
The shorter versions are produced by removing decreasing number of
elements from the enumerable. At first iteration we remove all but
one element. After each round we decrement the variable k, which
contains the number of elements removed, by one. Eventually we remove
only one element. The elements of the shorter enumerables are
simplified individually by the ShrinkOne
method.
private static IEnumerable<IEnumerable<IEnumerable<T>>> Shorten<T> (
IEnumerable<T> e)
{
var len = e.Count ();
for (var k = len - 1; k > 0; k--)
{
var shrunk = RemoveK (e, k, len);
foreach (var s in shrunk)
yield return ShrinkOne (s);
yield return shrunk;
}
}
The RemoveK
method removes k elements from different positions and
compiles a set of enumerables with the same length that have different
elements removed.
private static IEnumerable<IEnumerable<T>> RemoveK<T> (IEnumerable<T> e,
int k, int len)
{
if (k > len) return Enumerable.Empty<IEnumerable<T>> ();
var xs1 = e.Take (k);
var xs2 = e.Skip (k);
return (from r in RemoveK (xs2, k, len - k)
select xs1.Concat (r))
.Append (xs2);
}
The ShrinkOne
method recursively shrinks each element at a time.
private static IEnumerable<IEnumerable<T>> ShrinkOne<T> (IEnumerable<T> e)
{
if (e.None ())
return Enumerable.Empty<IEnumerable<T>> ();
var first = e.First ();
var rest = e.Skip (1);
return (from x in Arbitrary.Shrink (first)
select rest.Prepend (x))
.Concat (
from xs in ShrinkOne (e.Skip (1))
select xs.Prepend (first));
}
With the ability to generate and shrink enumerables we can implement the IArbitrary interface for collection types. As the collections are generic, their arbitrary counterparts need to be also parameterized by the element type. Therefore we write separate arbitrary classes for collections. All of the classes inherit from ArbitraryBase.
The implementation of arbitrary enumerable is trivial using the generator and shrinker we already defined.
private class Enumerable<T> : ArbitraryBase<IEnumerable<T>>
{
public override Gen<IEnumerable<T>> Generate
{
get { return Arbitrary.Gen<T> ().EnumerableOf (); }
}
public override IEnumerable<IEnumerable<T>> Shrink (IEnumerable<T> value)
{
return ShrinkEnumerable (value);
}
}
An arbitrary array is composed of arbitrary enumerable.
private class Array<T> : ArbitraryBase<T[]>
{
public override Gen<T[]> Generate
{
get { return Arbitrary.Gen<T> ().ArrayOf (); }
}
public override IEnumerable<T[]> Shrink (T[] value)
{
return ShrinkEnumerable (value).Select (Enumerable.ToArray);
}
}
Building an arbitrary list is equally simple using the existing combinators.
private class AList<T> : ArbitraryBase<List<T>>
{
public override Gen<List<T>> Generate
{
get
{
return from e in Arbitrary.Gen<T> ().EnumerableOf ()
select new List<T> (e);
}
}
public override IEnumerable<List<T>> Shrink (List<T> value)
{
return from e in ShrinkEnumerable (value)
select new List<T> (e);
}
}
}
}