Skip to content

Commit

Permalink
Merge pull request #47 from hudl/next
Browse files Browse the repository at this point in the history
Version 2.6.0
  • Loading branch information
robhruska authored Jun 14, 2016
2 parents e9cbbfd + 4c27fd3 commit 65cafb5
Show file tree
Hide file tree
Showing 57 changed files with 4,668 additions and 762 deletions.
1 change: 1 addition & 0 deletions Hudl.Mjolnir.Attributes/FireAndForgetAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Hudl.Mjolnir.Attributes
/// Should only be used on interface methods.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
[Obsolete("Will be removed in a future release. Leaving async calls un-awaited is generally not recommended.")]
public class FireAndForgetAttribute : Attribute
{

Expand Down
28 changes: 24 additions & 4 deletions Hudl.Mjolnir.SystemTests/Hudl.Mjolnir.SystemTests.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\xunit.core.2.0.0\build\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.0.0\build\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.props')" />
<Import Project="..\packages\xunit.runner.visualstudio.2.0.0\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\packages\xunit.runner.visualstudio.2.0.0\build\net20\xunit.runner.visualstudio.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
Expand All @@ -17,6 +19,8 @@
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down Expand Up @@ -54,8 +58,17 @@
</Reference>
<Reference Include="System" />
<Reference Include="System.Net.Http" />
<Reference Include="xunit">
<HintPath>..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.assert, Version=2.0.0.2929, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.core, Version=2.0.0.2929, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<Choose>
Expand Down Expand Up @@ -112,11 +125,18 @@
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\xunit.runner.visualstudio.2.0.0\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.runner.visualstudio.2.0.0\build\net20\xunit.runner.visualstudio.props'))" />
<Error Condition="!Exists('..\packages\xunit.core.2.0.0\build\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.0.0\build\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.props'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>
7 changes: 6 additions & 1 deletion Hudl.Mjolnir.SystemTests/packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@
<package id="Hudl.Config" version="1.6.0" targetFramework="net45" />
<package id="log4net" version="2.0.0" targetFramework="net45" />
<package id="Newtonsoft.Json" version="6.0.2" targetFramework="net45" />
<package id="xunit" version="1.9.2" targetFramework="net45" />
<package id="xunit" version="2.0.0" targetFramework="net45" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net45" />
<package id="xunit.assert" version="2.0.0" targetFramework="net45" />
<package id="xunit.core" version="2.0.0" targetFramework="net45" />
<package id="xunit.extensibility.core" version="2.0.0" targetFramework="net45" />
<package id="xunit.runner.visualstudio" version="2.0.0" targetFramework="net45" />
</packages>
16 changes: 14 additions & 2 deletions Hudl.Mjolnir.Tests/Breaker/FailurePercentageCircuitBreakerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,18 +358,23 @@ public void IsAllowing_WhenAlreadyTripped_DoesntReTripBreaker()

var mockMetrics = CreateMockMetricsWithSnapshot(10, 100); // 10 ops, 100% failing.
var stats = new InternallyCountingStats();
var metricEvents = new Mock<IMetricEvents>();
var breaker = new BreakerBuilder(1, 1, "Test") // Trip at 1 op, 1% failing.
.WithMockMetrics(mockMetrics)
.WithWaitMillis(durationMillis)
.WithStats(stats)
.WithMetricEvents(metricEvents.Object)
.Create();
breaker.IsAllowing(); // Trip the breaker.
Assert.Equal(1, stats.ServicesAndStates.Count(ss => ss.Service == "mjolnir breaker Test" && ss.State == "Tripped"));
metricEvents.Verify(m => m.BreakerTripped("Test"));
metricEvents.ResetCalls();

breaker.IsAllowing(); // Make another call, which should bail immediately (and not re-trip).

// Best way to test this right now is to make sure we don't fire a stat for the state change.
Assert.Equal(1, stats.ServicesAndStates.Count(ss => ss.Service == "mjolnir breaker Test" && ss.State == "Tripped"));
metricEvents.Verify(m => m.BreakerTripped(It.IsAny<string>()), Times.Never);
}

// The following tests compare the metrics to the threshold. The names have been made more concise.
Expand Down Expand Up @@ -468,7 +473,7 @@ public void Run()
{
var mockMetrics = CreateMockMetricsWithSnapshot(_metricsTotal, _metricsPercent);
var properties = CreateBreakerProperties(_breakerTotal, _breakerPercent, 30000);
var breaker = new FailurePercentageCircuitBreaker(GroupKey.Named("Test"), mockMetrics.Object, new IgnoringStats(), properties);
var breaker = new FailurePercentageCircuitBreaker(GroupKey.Named("Test"), mockMetrics.Object, new IgnoringStats(), new IgnoringMetricEvents(), properties);

Assert.NotEqual(_shouldTrip, breaker.IsAllowing());
}
Expand Down Expand Up @@ -505,6 +510,7 @@ internal class BreakerBuilder
private IClock _clock = new SystemClock();
private IMock<ICommandMetrics> _mockMetrics = FailurePercentageCircuitBreakerTests.CreateMockMetricsWithSnapshot(0, 0);
private IStats _stats = new Mock<IStats>().Object;
private IMetricEvents _metricEvents = new Mock<IMetricEvents>().Object;
private TransientConfigurableValue<long> _gaugeIntervalOverrideMillis;

public BreakerBuilder(long minimumOperations, int failurePercent, string key = null)
Expand Down Expand Up @@ -538,6 +544,12 @@ public BreakerBuilder WithStats(IStats stats)
return this;
}

public BreakerBuilder WithMetricEvents(IMetricEvents metricEvents)
{
_metricEvents = metricEvents;
return this;
}

public BreakerBuilder WithGaugeIntervalOverride(long intervalMillis)
{
_gaugeIntervalOverrideMillis = new TransientConfigurableValue<long>(intervalMillis);
Expand All @@ -547,7 +559,7 @@ public BreakerBuilder WithGaugeIntervalOverride(long intervalMillis)
public FailurePercentageCircuitBreaker Create()
{
var properties = FailurePercentageCircuitBreakerTests.CreateBreakerProperties(_minimumOperations, _failurePercent, _waitMillis);
return new FailurePercentageCircuitBreaker(GroupKey.Named(_key), _clock, _mockMetrics.Object, _stats, properties, _gaugeIntervalOverrideMillis);
return new FailurePercentageCircuitBreaker(GroupKey.Named(_key), _clock, _mockMetrics.Object, _stats, _metricEvents, properties, _gaugeIntervalOverrideMillis);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,8 @@ public async Task GenericTask_WithThrowingImplementation_ThrowsCommandFailedExce

var task = proxy.InvokeGenericTask();

try
{
// Using Assert.Throws(async () => {}) doesn't work right here,
// so falling back to the ol' try/catch.
await task;
}
catch (CommandFailedException e)
{
Assert.Equal(exception, e.InnerException);
return;
}

AssertX.FailExpectedException();
var e = await Assert.ThrowsAsync<CommandFailedException>(() => task);
Assert.Equal(exception, e.InnerException);
}

[Fact]
Expand Down Expand Up @@ -246,7 +235,10 @@ public void WithNonNullableCancellationTokenParameter_CancellationTokenIsEmpty_U
Assert.True(instance.ReceivedToken.Value.IsCancellationRequested);
}

[Fact]
// TODO this behavior (using async to FnF, i.e. async void) is undesirable
// and usually an anti-pattern. should we continue to support it, given
// that Mjolnir's intent is to make failure faster and more predictable.
[Fact(Skip = "Flaky")]
public void SlowSleepMethod_FireAndForget_ReturnsImmediatelyButStillCompletes()
{
var instance = new Sleepy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Hudl.Mjolnir.Command.Attribute;
using Hudl.Mjolnir.Tests.Helper;
using Xunit;
using Hudl.Config;

namespace Hudl.Mjolnir.Tests.Command.Attribute
{
Expand All @@ -17,7 +18,7 @@ public interface ICancellableTimeoutPreserved
public class CancellableWithTimeoutPreserved : ICancellableTimeoutPreserved
{
public const int Timeout = 500;
public CancellationToken TokenRecievedFromProxy { get; private set; }
public CancellationToken TokenReceivedFromProxy { get; private set; }
public bool CallMade { get; private set; }
private readonly string _returnResult;
public CancellableWithTimeoutPreserved(string returnResult)
Expand All @@ -28,16 +29,16 @@ public CancellableWithTimeoutPreserved(string returnResult)
public string CancellableMethod(CancellationToken token)
{
CallMade = true;
TokenRecievedFromProxy = token;
TokenReceivedFromProxy = token;
return _returnResult;
}
}

public class CancellableWithOverrunnningMethod : ICancellableTimeoutPreserved
public class CancellableWithOverrunningMethod : ICancellableTimeoutPreserved
{
public string CancellableMethod(CancellationToken token)
{
Thread.Sleep(CancellableWithTimeoutPreserved.Timeout + 50);
Thread.Sleep(CancellableWithTimeoutPreserved.Timeout + 500);
token.ThrowIfCancellationRequested();
return string.Empty;
}
Expand All @@ -52,33 +53,37 @@ public void ProxyPassesATimeoutTokenToMethod_WhenTimeoutsNotIgnored()
var proxy = CommandInterceptor.CreateProxy<ICancellableTimeoutPreserved>(classToProxy);
// If we pass CancellationToken.None to the proxy then it should pass a timeout tokem to the method call.
var result = proxy.CancellableMethod(CancellationToken.None);
Assert.True(classToProxy.CallMade && classToProxy.TokenRecievedFromProxy != CancellationToken.None);
Assert.True(classToProxy.CallMade && classToProxy.TokenReceivedFromProxy != CancellationToken.None);
// This shouldn't be cancelled yet.
Assert.False(classToProxy.TokenRecievedFromProxy.IsCancellationRequested);
Assert.False(classToProxy.TokenReceivedFromProxy.IsCancellationRequested);
// Now sleep past the timeout.
Thread.Sleep(CancellableWithTimeoutPreserved.Timeout + 50);
Assert.True(classToProxy.TokenRecievedFromProxy.IsCancellationRequested);
Assert.True(classToProxy.TokenReceivedFromProxy.IsCancellationRequested);
Assert.Equal(expectedResult, result);
}

[Fact]
public void ProxyPassesOnTokenToMethod_WhenTimeoutsNotIgnored()
{
ConfigProvider.Instance.Set(IgnoreTimeoutsKey, false);

var expectedResult = "test";
var classToProxy = new CancellableWithTimeoutPreserved(expectedResult);
var proxy = CommandInterceptor.CreateProxy<ICancellableTimeoutPreserved>(classToProxy);
// If we pass a valid token to the proxy then it should pass the token to the method call.
var token = new CancellationTokenSource(500).Token;
var result = proxy.CancellableMethod(token);
Assert.True(classToProxy.CallMade);
Assert.Equal(classToProxy.TokenRecievedFromProxy, token);
Assert.Equal(classToProxy.TokenReceivedFromProxy, token);
Assert.Equal(expectedResult, result);
}

[Fact]
public void MethodShouldTimeout_WhenTimeoutsAreNotIgnored()
{
var classToProxy = new CancellableWithOverrunnningMethod();
ConfigProvider.Instance.Set(IgnoreTimeoutsKey, false);

var classToProxy = new CancellableWithOverrunningMethod();
var proxy = CommandInterceptor.CreateProxy<ICancellableTimeoutPreserved>(classToProxy);
Assert.Throws<CommandTimeoutException>(() => proxy.CancellableMethod(CancellationToken.None));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ public string CancellableMethod(CancellationToken token)
}
}

public class CancellableWithOverrunnningMethodTimeoutsIgnored : ICancellableIgnoredTimeout
public class CancellableWithOverrunningMethodTimeoutsIgnored : ICancellableIgnoredTimeout
{
public string CancellableMethod(CancellationToken token)
{
Thread.Sleep(CancellableWithIgnoredTimeout.Timeout + 50);
Thread.Sleep(CancellableWithIgnoredTimeout.Timeout + 500);
token.ThrowIfCancellationRequested();
return string.Empty;
}
Expand Down Expand Up @@ -81,9 +81,12 @@ public void ProxyStillPassesOnTokenToMethod_WhenTimeoutsAreIgnored()
public void MethodShouldNotTimeout_WhenTimeoutsAreIgnored()
{
ConfigProvider.Instance.Set(IgnoreTimeoutsKey, true);
var classToProxy = new CancellableWithOverrunnningMethodTimeoutsIgnored();
var classToProxy = new CancellableWithOverrunningMethodTimeoutsIgnored();
var proxy = CommandInterceptor.CreateProxy<ICancellableIgnoredTimeout>(classToProxy);
Assert.DoesNotThrow(() => proxy.CancellableMethod(CancellationToken.None));

// Should not throw.
proxy.CancellableMethod(CancellationToken.None);

ConfigProvider.Instance.Set(IgnoreTimeoutsKey, false);
}
}
Expand Down
Loading

0 comments on commit 65cafb5

Please sign in to comment.