Skip to content

Instantly share code, notes, and snippets.

@aivascu
Created May 31, 2021 18:44
Show Gist options
  • Save aivascu/b3357f3875f97c80f35a3c23127d6d20 to your computer and use it in GitHub Desktop.
Save aivascu/b3357f3875f97c80f35a3c23127d6d20 to your computer and use it in GitHub Desktop.
AutoFixture depth guard
using System.Collections.Generic;
namespace AutoFixture
{
public interface IDepthHandler
{
object Handle(object request, Stack<object> recordedRequests);
}
}
using System;
using AutoFixture.Kernel;
namespace AutoFixture
{
public class OmitOnRequestDepthBehavior : ISpecimenBuilderTransformation
{
private const int DefaultRequestDepth = 10;
private readonly int requestDepth;
public OmitOnRequestDepthBehavior()
: this(DefaultRequestDepth)
{
}
public OmitOnRequestDepthBehavior(int requestDepth)
{
if (requestDepth < 1)
throw new ArgumentOutOfRangeException(nameof(requestDepth), "Recursion depth must be greater than 0.");
this.requestDepth = requestDepth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
return new RequestDepthGuard(builder, new OmitOnRequestDepthHandler(), this.requestDepth);
}
}
}
using System.Collections.Generic;
using AutoFixture.Kernel;
namespace AutoFixture
{
public class OmitOnRequestDepthHandler : IDepthHandler
{
public object Handle(object request, Stack<object> recordedRequests)
{
return new OmitSpecimen();
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using AutoFixture.Kernel;
namespace AutoFixture
{
public class RequestDepthGuard : ISpecimenBuilderNode
{
private readonly ThreadLocal<Stack<object>> requestsByThread
= new(() => new Stack<object>());
private Stack<object> GetMonitoredRequestsForCurrentThread() => this.requestsByThread.Value;
public RequestDepthGuard(ISpecimenBuilder builder,
IDepthHandler depthHandler,
int requestDepth)
{
if (requestDepth < 1)
throw new ArgumentOutOfRangeException(nameof(requestDepth), "Recursion depth must be greater than 0.");
this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
this.DepthHandler = depthHandler ?? throw new ArgumentNullException(nameof(depthHandler));
this.RequestDepth = requestDepth;
}
public int RequestDepth { get; set; }
public IDepthHandler DepthHandler { get; set; }
public ISpecimenBuilder Builder { get; set; }
public object Create(object request, ISpecimenContext context)
{
var requestsForCurrentThread = this.GetMonitoredRequestsForCurrentThread();
if (requestsForCurrentThread.Count >= this.RequestDepth && request is PropertyInfo)
{
return this.DepthHandler.Handle(request, this.GetMonitoredRequestsForCurrentThread());
}
if(request is PropertyInfo)
requestsForCurrentThread.Push(request);
try
{
return this.Builder.Create(request, context);
}
finally
{
if(request is PropertyInfo)
requestsForCurrentThread.Pop();
}
}
public IEnumerator<ISpecimenBuilder> GetEnumerator()
{
yield return this.Builder;
}
public ISpecimenBuilderNode Compose(IEnumerable<ISpecimenBuilder> builders)
{
if (builders == null) throw new ArgumentNullException(nameof(builders));
var composedBuilder = CompositeSpecimenBuilder.ComposeIfMultiple(builders);
return new RequestDepthGuard(composedBuilder, this.DepthHandler, this.RequestDepth);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment