Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions BitFaster.Caching.UnitTests/CacheEventProxyBaseTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitFaster.Caching.Lru;
using BitFaster.Caching.Synchronized;
using FluentAssertions;
using Xunit;

namespace BitFaster.Caching.UnitTests
{
public class CacheEventProxyBaseTests
{
private TestCacheEvents<int, int> testCacheEvents;
private EventProxy<int, int> eventProxy;

private List<ItemRemovedEventArgs<int, int>> removedItems = new();

public CacheEventProxyBaseTests()
{
this.testCacheEvents = new TestCacheEvents<int, int>();
this.eventProxy = new EventProxy<int, int>(this.testCacheEvents);
}

[Fact]
public void EventsAreEnabled()
{
this.testCacheEvents.IsEnabled = true;

this.eventProxy.IsEnabled.Should().BeTrue();
}

[Fact]
public void WhenEventHandlerIsRegisteredItIsFired()
{
this.eventProxy.ItemRemoved += OnItemRemoved;

this.testCacheEvents.Fire(1, new AtomicFactory<int, int>(1), ItemRemovedReason.Removed);

this.removedItems.First().Key.Should().Be(1);
}

[Fact]
public void WhenEventHandlerIsAddedThenRemovedItIsNotFired()
{
this.eventProxy.ItemRemoved += OnItemRemoved;
this.eventProxy.ItemRemoved -= OnItemRemoved;

this.testCacheEvents.Fire(1, new AtomicFactory<int, int>(1), ItemRemovedReason.Removed);

this.removedItems.Count.Should().Be(0);
}

[Fact]
public void WhenTwoEventHandlersAddedThenOneRemovedEventIsFired()
{
this.eventProxy.ItemRemoved += OnItemRemoved;
this.eventProxy.ItemRemoved += OnItemRemovedThrow;
this.eventProxy.ItemRemoved -= OnItemRemovedThrow;

this.testCacheEvents.Fire(1, new AtomicFactory<int, int>(1), ItemRemovedReason.Removed);

this.removedItems.First().Key.Should().Be(1);
}

private void OnItemRemoved(object sender, ItemRemovedEventArgs<int, int> e)
{
this.removedItems.Add(e);
}

private void OnItemRemovedThrow(object sender, ItemRemovedEventArgs<int, int> e)
{
throw new Exception("Should never happen");
}

private class TestCacheEvents<K, V> : ICacheEvents<K, AtomicFactory<K, V>>
{
public bool IsEnabled { get; set; }

public event EventHandler<ItemRemovedEventArgs<K, AtomicFactory<K, V>>> ItemRemoved;

public void Fire(K key, AtomicFactory<K, V> value, ItemRemovedReason reason)
{
ItemRemoved?.Invoke(this, new ItemRemovedEventArgs<K, AtomicFactory<K, V>>(key, value, reason));
}
}

private class EventProxy<K, V> : CacheEventProxyBase<K, AtomicFactory<K, V>, V>
{
public EventProxy(ICacheEvents<K, AtomicFactory<K, V>> inner)
: base(inner)
{
}

protected override ItemRemovedEventArgs<K, V> TranslateOnRemoved(ItemRemovedEventArgs<K, AtomicFactory<K, V>> inner)
{
return new ItemRemovedEventArgs<K, V>(inner.Key, inner.Value.ValueIfCreated, inner.Reason);
}
}
}
}
178 changes: 178 additions & 0 deletions BitFaster.Caching.UnitTests/ScopedCacheTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitFaster.Caching.Lru;
using FluentAssertions;
using Xunit;

namespace BitFaster.Caching.UnitTests
{
public abstract class ScopedCacheTestBase
{
protected const int capacity = 6;
protected readonly IScopedCache<int, Disposable> cache;

protected List<ItemRemovedEventArgs<int, Scoped<Disposable>>> removedItems = new();

protected ScopedCacheTestBase(IScopedCache<int, Disposable> cache)
{
this.cache = cache;
}

[Fact]
public void WhenCreatedCapacityPropertyWrapsInnerCache()
{
this.cache.Capacity.Should().Be(capacity);
}

[Fact]
public void WhenItemIsAddedCountIsCorrect()
{
this.cache.Count.Should().Be(0);

this.cache.AddOrUpdate(1, new Disposable());

this.cache.Count.Should().Be(1);
}

[Fact]
public void WhenItemIsAddedThenLookedUpMetricsAreCorrect()
{
this.cache.AddOrUpdate(1, new Disposable());
this.cache.ScopedTryGet(1, out var lifetime);

this.cache.Metrics.Misses.Should().Be(0);
this.cache.Metrics.Hits.Should().Be(1);
}

[Fact]
public void WhenEventHandlerIsRegisteredItIsFired()
{
this.cache.Events.ItemRemoved += OnItemRemoved;

this.cache.AddOrUpdate(1, new Disposable());
this.cache.TryRemove(1);

this.removedItems.First().Key.Should().Be(1);
}

[Fact]
public void WhenKeyDoesNotExistAddOrUpdateAddsNewItem()
{
var d = new Disposable();
this.cache.AddOrUpdate(1, d);

this.cache.ScopedTryGet(1, out var lifetime).Should().BeTrue();
lifetime.Value.Should().Be(d);
}

[Fact]
public void WhenKeyExistsAddOrUpdateUpdatesExistingItem()
{
var d1 = new Disposable();
var d2 = new Disposable();
this.cache.AddOrUpdate(1, d1);
this.cache.AddOrUpdate(1, d2);

this.cache.ScopedTryGet(1, out var lifetime).Should().BeTrue();
lifetime.Value.Should().Be(d2);
}

[Fact]
public void WhenItemUpdatedOldValueIsAliveUntilLifetimeCompletes()
{
var d1 = new Disposable();
var d2 = new Disposable();

// start a lifetime on 1
this.cache.AddOrUpdate(1, d1);
this.cache.ScopedTryGet(1, out var lifetime1).Should().BeTrue();

using (lifetime1)
{
// replace 1
this.cache.AddOrUpdate(1, d2);

// cache reflects replacement
this.cache.ScopedTryGet(1, out var lifetime2).Should().BeTrue();
lifetime2.Value.Should().Be(d2);

d1.IsDisposed.Should().BeFalse();
}

d1.IsDisposed.Should().BeTrue();
}

[Fact]
public void WhenClearedItemsAreDisposed()
{
var d = new Disposable();
this.cache.AddOrUpdate(1, d);

this.cache.Clear();

d.IsDisposed.Should().BeTrue();
}

[Fact]
public void WhenItemExistsTryGetReturnsLifetime()
{
this.cache.AddOrUpdate(1, new Disposable());
this.cache.ScopedTryGet(1, out var lifetime).Should().BeTrue();

lifetime.Should().NotBeNull();
}

[Fact]
public void WhenItemDoesNotExistTryGetReturnsFalse()
{
this.cache.ScopedTryGet(1, out var lifetime).Should().BeFalse();
}

[Fact]
public void WhenCacheContainsValuesTrim1RemovesColdestValue()
{
this.cache.AddOrUpdate(0, new Disposable());
this.cache.AddOrUpdate(1, new Disposable());
this.cache.AddOrUpdate(2, new Disposable());

this.cache.Trim(1);

this.cache.ScopedTryGet(0, out var lifetime).Should().BeFalse();
}

[Fact]
public void WhenKeyDoesNotExistTryRemoveReturnsFalse()
{
this.cache.TryRemove(1).Should().BeFalse();
}

[Fact]
public void WhenKeyExistsTryRemoveReturnsTrue()
{
this.cache.AddOrUpdate(1, new Disposable());
this.cache.TryRemove(1).Should().BeTrue();
}

[Fact]
public void WhenKeyDoesNotExistTryUpdateReturnsFalse()
{
this.cache.TryUpdate(1, new Disposable()).Should().BeFalse();
}

[Fact]
public void WhenKeyExistsTryUpdateReturnsTrue()
{
this.cache.AddOrUpdate(1, new Disposable());

this.cache.TryUpdate(1, new Disposable()).Should().BeTrue();
}

protected void OnItemRemoved(object sender, ItemRemovedEventArgs<int, Scoped<Disposable>> e)
{
this.removedItems.Add(e);
}
}
}
Loading