Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
671e159
Skeleton
mikechu-optimizely Aug 5, 2022
3d1ae15
LruCache initial implementation
mikechu-optimizely Aug 5, 2022
b987d9d
Add helper extensions
mikechu-optimizely Aug 5, 2022
295fdd2
WIP Fill unit tests
mikechu-optimizely Aug 5, 2022
50fd4e1
WIP corrections based on tests 2 more failing
mikechu-optimizely Aug 5, 2022
22acf8e
Remove SetTimeout, fix for non-passing test
mikechu-optimizely Aug 8, 2022
d2e3373
Add copyright notices
mikechu-optimizely Aug 8, 2022
d758efb
Remove InternalsVisibleTo for testing
mikechu-optimizely Aug 8, 2022
da33c99
Add new line at end of files via .editorconfig
mikechu-optimizely Aug 8, 2022
9fe246e
Readonly and remove excess methods/constructor
mikechu-optimizely Aug 8, 2022
33dc181
Code review corrections
mikechu-optimizely Aug 10, 2022
2f35de3
WIP code review changes
mikechu-optimizely Aug 10, 2022
24737fe
Possibly better solution to cast
mikechu-optimizely Aug 10, 2022
e042d99
Move readonlys into constructor
mikechu-optimizely Aug 10, 2022
6749182
Switch to using TimeSpan + refactors
mikechu-optimizely Aug 11, 2022
2bac003
A few more refactors
mikechu-optimizely Aug 11, 2022
03c9abf
Change underlying implementation of LRU
mikechu-optimizely Aug 12, 2022
209c3bd
Attempt to fulfill a default Timespan
mikechu-optimizely Aug 16, 2022
42a735d
Add logging; use 0s instead of descriptive consts
mikechu-optimizely Aug 16, 2022
722780c
Remove Tuple from LRU Cache
mikechu-optimizely Aug 16, 2022
ea2f32a
Remove System.ValueTuple NuGet package
mikechu-optimizely Aug 16, 2022
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ tab_width = 4

# New line preferences
end_of_line = crlf
insert_final_newline = false
insert_final_newline = true

#### .NET Coding Conventions ####

Expand Down
14 changes: 14 additions & 0 deletions OptimizelySDK.Tests/OdpTests/CollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections;

namespace OptimizelySDK.Tests.OdpTests
{
public static class CollectionTestExtensions
{
public static string[] ToKeyCollection(this ICollection keys)
{
var keyArray = new string[keys.Count];
keys.CopyTo(keyArray, 0);
return keyArray;
}
}
}
201 changes: 201 additions & 0 deletions OptimizelySDK.Tests/OdpTests/LruCacheTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
* Copyright 2022, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using NUnit.Framework;
using OptimizelySDK.Odp;
using System.Collections.Generic;
using System.Threading;

namespace OptimizelySDK.Tests.OdpTests
{
[TestFixture]
public class LruCacheTest
{
private readonly List<string> _segments1And2 = new List<string>
{
"segment1",
"segment2",
};

private readonly List<string> _segments3And4 = new List<string>
{
"segment3",
"segment4",
};

private readonly List<string> _segments5And6 = new List<string>
{
"segment5",
"segment6",
};

[Test]
public void ShouldCreateSaveAndLookupOneItem()
{
var cache = new LruCache<string>();
Assert.IsNull(cache.Lookup("key1"));

cache.Save("key1", "value1");
Assert.AreEqual("value1", cache.Lookup("key1"));
}

[Test]
public void ShouldSaveAndLookupMultipleItems()
{
var cache = new LruCache<List<string>>();

cache.Save("user1", _segments1And2);
cache.Save("user2", _segments3And4);
cache.Save("user3", _segments5And6);

var itemKeys = cache.ReadCurrentCache().Keys.ToKeyCollection();
Assert.AreEqual("user1", itemKeys[0]);
Assert.AreEqual("user2", itemKeys[1]);
Assert.AreEqual("user3", itemKeys[2]);

Assert.AreEqual(_segments1And2, cache.Lookup("user1"));

itemKeys = cache.ReadCurrentCache().Keys.ToKeyCollection();
// Lookup should move user1 to bottom of the list and push up others.
Assert.AreEqual("user2", itemKeys[0]);
Assert.AreEqual("user3", itemKeys[1]);
Assert.AreEqual("user1", itemKeys[2]);

Assert.AreEqual(_segments3And4, cache.Lookup("user2"));

itemKeys = cache.ReadCurrentCache().Keys.ToKeyCollection();
// Lookup should move user2 to bottom of the list and push up others.
Assert.AreEqual("user3", itemKeys[0]);
Assert.AreEqual("user1", itemKeys[1]);
Assert.AreEqual("user2", itemKeys[2]);

Assert.AreEqual(_segments5And6, cache.Lookup("user3"));

itemKeys = cache.ReadCurrentCache().Keys.ToKeyCollection();
// Lookup should move user3 to bottom of the list and push up others.
Assert.AreEqual("user1", itemKeys[0]);
Assert.AreEqual("user2", itemKeys[1]);
Assert.AreEqual("user3", itemKeys[2]);
}

[Test]
public void ShouldReorderListOnSave()
{
var cache = new LruCache<List<string>>();

cache.Save("user1", _segments1And2);
cache.Save("user2", _segments3And4);
cache.Save("user3", _segments5And6);

var itemKeys = cache.ReadCurrentCache().Keys.ToKeyCollection();
Assert.AreEqual("user1", itemKeys[0]);
Assert.AreEqual("user2", itemKeys[1]);
Assert.AreEqual("user3", itemKeys[2]);

cache.Save("user1", _segments1And2);

itemKeys = cache.ReadCurrentCache().Keys.ToKeyCollection();
// save should move user1 to bottom of the list and push up others.
Assert.AreEqual("user2", itemKeys[0]);
Assert.AreEqual("user3", itemKeys[1]);
Assert.AreEqual("user1", itemKeys[2]);

cache.Save("user2", _segments3And4);

itemKeys = cache.ReadCurrentCache().Keys.ToKeyCollection();
// save should move user2 to bottom of the list and push up others.
Assert.AreEqual("user3", itemKeys[0]);
Assert.AreEqual("user1", itemKeys[1]);
Assert.AreEqual("user2", itemKeys[2]);

cache.Save("user3", _segments5And6);

itemKeys = cache.ReadCurrentCache().Keys.ToKeyCollection();
// save should move user3 to bottom of the list and push up others.
Assert.AreEqual("user1", itemKeys[0]);
Assert.AreEqual("user2", itemKeys[1]);
Assert.AreEqual("user3", itemKeys[2]);
}

[Test]
public void ShouldHandleWhenCacheIsDisabled()
{
var cache = new LruCache<List<string>>(0, LruCache<object>.DEFAULT_TIMEOUT_SECONDS);

cache.Save("user1", _segments1And2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this possible if we can return true or false. @jaeopt a question for you?

cache.Save("user2", _segments3And4);
cache.Save("user3", _segments5And6);

Assert.IsNull(cache.Lookup("user1"));
Assert.IsNull(cache.Lookup("user2"));
Assert.IsNull(cache.Lookup("user3"));
}

[Test]
public void ShouldHandleWhenItemsExpire()
{
var cache = new LruCache<List<string>>(LruCache<object>.DEFAULT_MAX_SIZE, 1);

cache.Save("user1", _segments1And2);

Assert.AreEqual(_segments1And2, cache.Lookup("user1"));
Assert.AreEqual(1, cache.ReadCurrentCache().Count);

Thread.Sleep(1200);

Assert.IsNull(cache.Lookup("user1"));
Assert.AreEqual(0, cache.ReadCurrentCache().Count);
}

[Test]
public void ShouldHandleWhenCacheReachesMaxSize()
{
var cache = new LruCache<List<string>>(2, LruCache<object>.DEFAULT_TIMEOUT_SECONDS);

cache.Save("user1", _segments1And2);
cache.Save("user2", _segments3And4);
cache.Save("user3", _segments5And6);

Assert.AreEqual(2, cache.ReadCurrentCache().Count);

Assert.AreEqual(_segments5And6, cache.Lookup("user3"));
Assert.AreEqual(_segments3And4, cache.Lookup("user2"));
Assert.IsNull(cache.Lookup("user1"));
}

[Test]
public void ShouldHandleWhenCacheIsReset() {
var cache = new LruCache<List<string>>();

cache.Save("user1", _segments1And2);
cache.Save("user2", _segments3And4);
cache.Save("user3", _segments5And6);

Assert.AreEqual(_segments1And2, cache.Lookup("user1"));
Assert.AreEqual(_segments3And4, cache.Lookup("user2"));
Assert.AreEqual(_segments5And6, cache.Lookup("user3"));

Assert.AreEqual(3, cache.ReadCurrentCache().Count);

cache.Reset();

Assert.IsNull(cache.Lookup("user1"));
Assert.IsNull(cache.Lookup("user2"));
Assert.IsNull(cache.Lookup("user3"));

Assert.AreEqual(0, cache.ReadCurrentCache().Count);}
}
}
3 changes: 2 additions & 1 deletion OptimizelySDK.Tests/OptimizelySDK.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
<Compile Include="DefaultErrorHandlerTest.cs" />
<Compile Include="EntityTests\IntegrationTest.cs" />
<Compile Include="EventTests\EventProcessorProps.cs" />
<Compile Include="OdpTests\CollectionExtensions.cs" />
<Compile Include="OdpTests\LruCacheTest.cs" />
<Compile Include="OptimizelyConfigTests\OptimizelyConfigTest.cs" />
<Compile Include="OptimizelyDecisions\OptimizelyDecisionTest.cs" />
<Compile Include="OptimizelyJSONTest.cs" />
Expand Down Expand Up @@ -150,7 +152,6 @@
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- 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.
Expand Down
30 changes: 30 additions & 0 deletions OptimizelySDK/Odp/DateTimeExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2022, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;

namespace OptimizelySDK.Odp
{
public static class DateTimeExtension
{
// Because we're only on .NET Framework 4.5
public static long ToUnixTimeMilliseconds(this DateTime dateTime)
{
var unixTimeStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return Convert.ToInt64(dateTime.Subtract(unixTimeStart).TotalMilliseconds);
}
}
}
25 changes: 25 additions & 0 deletions OptimizelySDK/Odp/ICache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2022, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace OptimizelySDK.Odp
{
public interface ICache<T>
{
void Save(string key, T value);
T Lookup(string key);
void Reset();
}
}
Loading