Files
quantconnect--lean/Tests/Common/Storage/LocalObjectStoreTests.cs
T
2026-07-13 13:02:50 +08:00

1074 lines
38 KiB
C#

/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* 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 http://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;
using System.IO;
using System.Linq;
using NUnit.Framework;
using System.Threading;
using QuantConnect.Util;
using QuantConnect.Logging;
using QuantConnect.Packets;
using QuantConnect.Storage;
using QuantConnect.Research;
using System.Threading.Tasks;
using System.Collections.Generic;
using QuantConnect.Lean.Engine.Storage;
using Python.Runtime;
namespace QuantConnect.Tests.Common.Storage
{
[TestFixture]
public class LocalObjectStoreTests
{
private static readonly string TestStorageRoot = $"{Directory.GetCurrentDirectory()}/{nameof(LocalObjectStoreTests)}";
private static readonly string StorageRootConfigurationValue = LocalObjectStore.DefaultObjectStore;
private ObjectStore _store;
private ILogHandler _logHandler;
[OneTimeSetUp]
public void Setup()
{
LocalObjectStore.DefaultObjectStore = TestStorageRoot;
#pragma warning disable CA2000
_store = new ObjectStore(new TestLocalObjectStore());
#pragma warning restore CA2000
_store.Initialize(0, 0, "", new Controls() { StorageLimit = 5 * 1024 * 1024, StorageFileCount = 100 }, AlgorithmMode.Backtesting);
// Store initial Log Handler
_logHandler = Log.LogHandler;
}
[OneTimeTearDown]
public void Cleanup()
{
_store.DisposeSafely();
LocalObjectStore.DefaultObjectStore = StorageRootConfigurationValue;
try
{
Directory.Delete(TestStorageRoot, true);
}
catch
{
}
// Restore initial Log Handler
Log.LogHandler = _logHandler;
}
[Test]
public void DoesNotYieldTwiceSameFile()
{
using var store = new ObjectStore(new TestLocalObjectStore());
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
store.SaveBytes("DoesNotYieldTwiceSameFile", [1, 1]);
var enumerator = store.GetEnumerator();
Assert.IsTrue(enumerator.MoveNext());
Assert.IsFalse(enumerator.MoveNext());
store.Delete("DoesNotYieldTwiceSameFile");
}
[Test]
public void DisposeDoesNotHang()
{
var store = new ObjectStore(new TestLocalObjectStore());
store.Initialize(0, 0, "", new Controls(), AlgorithmMode.Backtesting);
Assert.IsTrue(store.SaveBytes("DisposeDoesNotHang", [1, 1]));
using var testEvent = new AutoResetEvent(false);
_ = Task.Factory.StartNew(() =>
{
var enumerator = store.GetEnumerator();
Assert.IsTrue(enumerator.MoveNext());
testEvent.Set();
Thread.Sleep(Time.OneSecond);
}, TaskCreationOptions.LongRunning);
testEvent.WaitOne();
store.Delete("DisposeDoesNotHang");
_ = Task.Factory.StartNew(() =>
{
store.Dispose();
testEvent.Set();
}, TaskCreationOptions.LongRunning);
Assert.IsTrue(testEvent.WaitOne(TimeSpan.FromSeconds(5)));
}
[Test]
public void ExistingFilesLoadedCorretly()
{
#pragma warning disable CA2000
using (var store = new ObjectStore(new TestLocalObjectStore()))
{
#pragma warning restore CA2000
var dir = Path.Combine(TestStorageRoot, "location-pepe", "test");
Directory.CreateDirectory(dir);
var filename = "Jose";
var filename2 = "rootFile";
File.WriteAllText(Path.Combine(dir, filename), "pinocho the movie");
File.WriteAllText(Path.Combine(TestStorageRoot, filename2), "jiji");
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
var storeContent = store.ToList();
Assert.IsTrue(storeContent.All(kvp => kvp.Value != null));
Assert.AreEqual(2, storeContent.Count);
Assert.AreEqual("location-pepe/test/Jose", storeContent.Single(s => s.Key.Contains("location")).Key.Replace('\\', '/'));
Assert.AreEqual("rootFile", storeContent.Single(s => s.Key.Contains("rootFile")).Key);
Assert.IsTrue(File.Exists(store.GetFilePath("location-pepe/test/Jose")));
Assert.IsTrue(File.Exists(store.GetFilePath("rootFile")));
Assert.IsTrue(store.Delete("location-pepe/test/Jose"));
Assert.IsTrue(store.Delete("rootFile"));
}
}
[Test]
public void GetFilePathPermissions([Values] bool read, [Values] bool write, [Values] bool delete)
{
var permissions = new QuantConnect.Packets.StoragePermissions()
{
Read = read,
Write = write,
Delete = delete
};
using var store = new TestLocalObjectStore();
store.Initialize(0, 0, "", new Controls { StorageAccess = permissions });
if (!read || !write)
{
Assert.Throws<InvalidOperationException>(() => store.GetFilePath("Jose"));
}
else
{
Assert.DoesNotThrow(() => store.GetFilePath("Jose"));
}
}
[Test]
public void ReadBytesPermissions([Values] bool read, [Values] bool write, [Values] bool delete)
{
var permissions = new QuantConnect.Packets.StoragePermissions()
{
Read = read,
Write = write,
Delete = delete
};
using var store = new TestLocalObjectStore();
store.Initialize(0, 0, "", new Controls { StorageAccess = permissions });
if (!read)
{
Assert.Throws<InvalidOperationException>(() => store.ReadBytes("Jose"));
}
else
{
Assert.Throws<KeyNotFoundException>(() => store.ReadBytes("Jose"));
}
}
[Test]
public void SaveBytesPermissions([Values] bool read, [Values] bool write)
{
var permissions = new QuantConnect.Packets.StoragePermissions()
{
Read = read,
Write = write,
Delete = true
};
using var store = new TestLocalObjectStore();
store.Initialize(0, 0, "", new Controls { StorageAccess = permissions });
if (!write)
{
Assert.Throws<InvalidOperationException>(() => store.SaveBytes("Jose", new byte[] { 0 }));
}
else
{
Assert.IsTrue(store.SaveBytes("Jose", new byte[] { 0 }));
Assert.IsTrue(store.Delete("Jose"));
}
}
[Test]
public void DeletePermissions([Values] bool read, [Values] bool write, [Values] bool delete)
{
var permissions = new QuantConnect.Packets.StoragePermissions()
{
Read = read,
Write = write,
Delete = delete
};
using var store = new TestLocalObjectStore();
store.Initialize(0, 0, "", new Controls { StorageAccess = permissions });
if (!delete)
{
Assert.Throws<InvalidOperationException>(() => store.Delete("Jose"));
}
else
{
Assert.IsFalse(store.Delete("Jose"));
}
}
[TestCase("../prefix/")]
[TestCase("..\\prefix/")]
public void InvalidCustomPathsStore(string path)
{
#pragma warning disable CA2000
using (var store = new ObjectStore(new TestLocalObjectStore()))
{
#pragma warning restore CA2000
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
Assert.AreEqual(0, store.Count());
Assert.Throws<ArgumentException>(() => store.SaveString($"{path}ILove", "Pizza"));
}
}
[Test]
public void ValidPaths()
{
#pragma warning disable CA2000
using (var store = new ObjectStore(new TestLocalObjectStore()))
{
#pragma warning restore CA2000
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
store.SaveString("jose-something/pepe/ILove", "Pizza");
Assert.AreEqual(1, store.Count());
Assert.AreEqual(1, Directory.EnumerateFiles(Path.Combine(TestStorageRoot, "jose-something", "pepe")).Count());
store.Delete("jose-something/pepe/ILove");
Assert.AreEqual(0, store.Count());
Assert.AreEqual(0, Directory.EnumerateFiles(TestStorageRoot, "*", SearchOption.AllDirectories).Count());
}
}
[TestCase("prefix/")]
[TestCase("/prefix/")]
[TestCase("/prefix")]
[TestCase("prefix")]
[TestCase("")]
[TestCase(null)]
[TestCase("a/prefix/")]
[TestCase("a/super/prefix/")]
[TestCase("/a/super/prefix/")]
[TestCase("/a/super/prefix")]
[TestCase("./a/su-p_er\\pr##efi$x")]
[TestCase("./a/super/prefix")]
[TestCase("./a/su-p_er\\pr x=")]
public void CustomPrefixStore(string prefix)
{
#pragma warning disable CA2000
using (var store = new ObjectStore(new TestLocalObjectStore()))
{
#pragma warning restore CA2000
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
Assert.AreEqual(0, store.Count());
var key = "ILove";
if (prefix != null)
{
key = Path.Combine(prefix, key);
}
store.SaveString(key, "Pizza");
Assert.AreEqual(1, store.Count());
Assert.AreEqual(1, Directory.EnumerateFiles(TestStorageRoot, "*", SearchOption.AllDirectories).Count());
var data = store.Read(key);
Assert.AreEqual("Pizza", data);
var path = store.GetFilePath(key);
Assert.IsTrue(File.Exists(path));
Assert.IsTrue(store.Delete(key));
Assert.IsFalse(File.Exists(path));
}
}
[TestCase(2)]
[TestCase(1)]
[TestCase(0)]
public void KeysBehavior(int useCase)
{
#pragma warning disable CA2000
using (var store = new ObjectStore(new TestLocalObjectStore()))
{
#pragma warning restore CA2000
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
var key = "ILove";
store.SaveString(key, "Pizza");
var path = store.GetFilePath(key);
if (useCase == 0)
{
// delete
Assert.IsTrue(store.Delete(key));
Assert.IsFalse(File.Exists(path));
Assert.AreEqual(0, store.Keys.Count);
}
else if (useCase == 1)
{
// read
Assert.AreEqual(key, store.Keys.Single());
}
else if (useCase == 2)
{
// new file
File.WriteAllText(Path.Combine(Path.GetDirectoryName(path), "some other-file"), "Pepe");
Assert.AreEqual(2, store.Keys.Count);
Assert.AreEqual(1, store.Keys.Count(k => k == key));
Assert.AreEqual(1, store.Keys.Count(k => k == "some other-file"));
Assert.IsTrue(store.Delete("some other-file"));
}
// clean up
if (File.Exists(path))
{
File.Delete(path);
}
}
}
[TestCase(5)]
[TestCase(4)]
[TestCase(3)]
[TestCase(2)]
[TestCase(1)]
[TestCase(0)]
public void AfterClearState(int useCase)
{
#pragma warning disable CA2000
using (var store = new ObjectStore(new TestLocalObjectStore()))
{
#pragma warning restore CA2000
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
var key = "ILove";
store.SaveString(key, "Pizza");
var path = store.GetFilePath(key);
// CLEAR the state
store.Clear();
if (useCase == 0)
{
// delete
Assert.IsTrue(store.Delete(key));
Assert.IsFalse(File.Exists(path));
}
else if (useCase == 1)
{
// read
Assert.AreEqual("Pizza", store.ReadString(key));
}
else if (useCase == 2)
{
// enumeration
Assert.AreEqual("Pizza", store.Single().Value);
}
else if (useCase == 3)
{
// keys
Assert.AreEqual(key, store.Keys.Single());
}
else if (useCase == 4)
{
// get file path
Assert.AreEqual(path, store.GetFilePath(key));
}
else if (useCase == 5)
{
// new file
File.WriteAllText(Path.Combine(Path.GetDirectoryName(path), "some other-file"), "Pepe");
// read new file
Assert.AreEqual("Pepe", store.ReadString("some other-file"));
Assert.IsTrue(store.Delete("some other-file"));
}
// clean up
if (File.Exists(path))
{
File.Delete(path);
}
}
}
[Test]
public void GetFilePathAndDelete()
{
#pragma warning disable CA2000
using (var store = new ObjectStore(new TestLocalObjectStore()))
{
#pragma warning restore CA2000
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
Assert.IsTrue(Directory.Exists("./LocalObjectStoreTests"));
var key = "ILove";
store.SaveString(key, "Pizza");
var path = store.GetFilePath(key);
Assert.IsTrue(File.Exists(path));
store.Delete(key);
Assert.IsFalse(File.Exists(path));
}
}
[Test]
public void SaveAndDelete()
{
string path;
using (var store = new TestLocalObjectStore())
{
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = 1 }, new TestFileHandler());
Assert.IsTrue(Directory.Exists("./LocalObjectStoreTests"));
var key = "ILove";
path = store.GetFilePath(key);
store.SaveBytes(key, new byte[] { 1 });
Thread.Sleep(2000);
store.Delete(key);
Assert.IsTrue(store.PersistDataCalled, "PersistData() was never called!");
}
Assert.IsFalse(File.Exists(path));
}
[Test]
public void ContainsKeyPermissions([Values] bool read, [Values] bool write, [Values] bool delete)
{
var permissions = new QuantConnect.Packets.StoragePermissions()
{
Read = read,
Write = write,
Delete = delete
};
using var store = new TestLocalObjectStore();
store.Initialize(0, 0, "", new Controls { StorageAccess = permissions });
if (!read)
{
Assert.Throws<InvalidOperationException>(() => store.ContainsKey("Jose"));
}
else
{
Assert.IsFalse(store.ContainsKey("Jose"));
}
}
[Test]
public void InitializationPermissions([Values] bool read, [Values] bool write, [Values] bool delete)
{
var permissions = new QuantConnect.Packets.StoragePermissions()
{
Read = read,
Write = write,
Delete = delete
};
using var store = new TestLocalObjectStore();
var dir = Path.Combine(TestStorageRoot);
Directory.CreateDirectory(dir);
//Determine filename for key "Jose" using Base64
var filename = "Jose";
File.WriteAllText(Path.Combine(dir, filename), "Pepe");
store.Initialize(0, 0, "", new Controls { StorageAccess = permissions });
if (!read)
{
Assert.Throws<InvalidOperationException>(() => store.ContainsKey(filename));
}
else
{
Assert.IsTrue(store.ContainsKey(filename));
}
Directory.Delete(dir, true);
}
[Test]
public void PersistCalledSynchronously()
{
using var store = new TestLocalObjectStore();
store.Initialize(0, 0, "", new Controls
{
PersistenceIntervalSeconds = -1
});
store.SaveBytes("Pepe", new byte[] { 1 });
Assert.AreEqual(1, store.ReadBytes("Pepe").Single());
Assert.IsTrue(store.PersistDataCalled);
store.PersistDataCalled = false;
store.Delete("Pepe");
Assert.IsFalse(File.Exists(Path.Combine(TestStorageRoot, "Pepe")));
Assert.IsFalse(store.ContainsKey("Pepe"));
store.DisposeSafely();
}
[Test]
public void ThrowsKeyNotFoundException_WhenObjectStoreDoesNotContainKey()
{
var error = Assert.Throws<KeyNotFoundException>(
() => _store.ReadBytes("missing.missing")
);
Assert.IsTrue(error.Message.Contains("Please use ObjectStore.ContainsKey(key)"));
}
[TestCase("my_key", "./LocalObjectStoreTests/my_key")]
[TestCase("test/123", "./LocalObjectStoreTests/test/123")]
[TestCase("**abc**", null)]
[TestCase("<random>", null)]
[TestCase("|", null)]
public void GetFilePathReturnsFileName(string key, string expectedRelativePath)
{
if (expectedRelativePath == null)
{
Assert.Throws<ArgumentException>(() => _store.GetFilePath(key));
}
else
{
var expectedPath = Path.GetFullPath(expectedRelativePath).Replace("\\", "/");
Assert.AreEqual(expectedPath, _store.GetFilePath(key).Replace("\\", "/"));
}
}
[Test]
public void SavesAndLoadsText()
{
const string expectedText = "12;26";
Assert.IsTrue(_store.SaveString("my_settings_text", expectedText));
var actualText = _store.Read("my_settings_text");
Assert.AreEqual(expectedText, actualText);
}
[Test]
public void SizeLimitIsRespected()
{
{
var validData = new byte[1024 * 1024 * 4];
Assert.IsTrue(_store.SaveBytes("my_settings_text", validData));
}
{
var invalidData = new byte[1024 * 1024 * 6];
Assert.IsFalse(_store.SaveBytes("my_settings_text", invalidData));
}
_store.Delete("my_settings_text");
}
[Test]
public void SavesAndLoadsJson()
{
var expected = new TestSettings { EmaFastPeriod = 12, EmaSlowPeriod = 26 };
Assert.IsTrue(_store.SaveJson("my_settings_json", expected));
var actual = _store.ReadJson<TestSettings>("my_settings_json");
Assert.AreEqual(expected.EmaFastPeriod, actual.EmaFastPeriod);
Assert.AreEqual(expected.EmaSlowPeriod, actual.EmaSlowPeriod);
}
[Test]
public void SavesAndLoadsXml()
{
var expected = new TestSettings { EmaFastPeriod = 12, EmaSlowPeriod = 26 };
Assert.IsTrue(_store.SaveXml("my_settings_xml", expected));
var actual = _store.ReadXml<TestSettings>("my_settings_xml");
Assert.AreEqual(expected.EmaFastPeriod, actual.EmaFastPeriod);
Assert.AreEqual(expected.EmaSlowPeriod, actual.EmaSlowPeriod);
}
[Test]
public void ThrowsIfKeyIsNull()
{
Assert.Throws<ArgumentNullException>(() => _store.ContainsKey(null));
Assert.Throws<ArgumentNullException>(() => _store.ReadBytes(null));
Assert.Throws<ArgumentNullException>(() => _store.SaveBytes(null, null));
Assert.Throws<ArgumentNullException>(() => _store.Delete(null));
Assert.Throws<ArgumentNullException>(() => _store.GetFilePath(null));
}
[Test]
public void DisposeDoesNotRemovesEmptyStorageFolder()
{
using (var store = new TestLocalObjectStore())
{
store.Initialize(0, 0, "", new Controls());
Assert.IsTrue(Directory.Exists("./LocalObjectStoreTests"));
}
Assert.IsTrue(Directory.Exists("./LocalObjectStoreTests"));
}
[Test]
public void DisposeDoesNotErrorWhenStorageFolderAlreadyDeleted()
{
var testHandler = new QueueLogHandler();
Log.LogHandler = testHandler;
using (var store = new TestLocalObjectStore())
{
store.Initialize(0, 0, "", new Controls());
Directory.Delete("./LocalObjectStoreTests/", true);
}
Assert.IsFalse(testHandler.Logs.Any(message =>
message.Message.Contains("Error deleting storage directory.")));
}
[Test]
public void DisposeDoesNotDeleteStoreFiles()
{
string path;
using (var store = new TestLocalObjectStore())
{
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 });
Assert.IsTrue(Directory.Exists("./LocalObjectStoreTests"));
var validData = new byte[1024 * 1024 * 4];
var saved = store.SaveBytes("a.txt", validData);
Assert.IsTrue(saved);
path = store.GetFilePath("a.txt");
Assert.IsTrue(File.Exists(path));
}
// Check that it still exists
Assert.IsTrue(File.Exists(path));
}
[Test]
public void QuantBookObjectStoreBehavior()
{
// Test for issue #4811, on loop store objects would duplicate
for (int i = 0; i < 3; i++)
{
// Create a QuantBook and save some data
var qb = new QuantBook();
qb.ObjectStore.Save("a.txt", "1010101010101010101010");
Assert.IsTrue(qb.ObjectStore.ContainsKey("a.txt"));
// Assert the store has only a.txt
var store = qb.ObjectStore.GetEnumerator().AsEnumerable().ToList();
Assert.AreEqual(1, store.Count);
// 0 being the project id, default prefix
Assert.AreEqual(Path.Combine("a.txt"), store[0].Key);
// Get the file path and verify it exists
var path = qb.ObjectStore.GetFilePath("a.txt");
Assert.IsTrue(File.Exists(path));
Assert.IsTrue(qb.ObjectStore.Delete("a.txt"));
Assert.IsFalse(File.Exists(path));
}
}
[Test]
public void OversizedObject()
{
// Create a big byte array
var bytesToWrite = new byte[7000000];
// Attempt to save it to local store with 5MB cap
Assert.IsFalse(_store.SaveBytes("test", bytesToWrite));
}
[TestCase(true)]
[TestCase(false)]
public void TooManyObjects(bool usingObjectStore)
{
using (var store = new TestLocalObjectStore())
{
store.Initialize(0, 0, "", new Controls() { StorageLimit = 5 * 1024 * 1024, StorageFileCount = 100 });
// Write 100 Files first, should not throw
var start = store.Count();
for (var i = start; i < 100; i++)
{
if (usingObjectStore)
{
Assert.IsTrue(store.SaveBytes($"{i}", new byte[1]));
}
else
{
File.WriteAllBytes(Path.Combine(TestStorageRoot, $"{i}"), new byte[1]);
}
}
// Write 1 more; should throw
Assert.IsFalse(store.SaveBytes("breaker", new byte[1]));
// cleaup
for (var i = start; i < 100; i++)
{
Assert.IsTrue(store.Delete($"{i}"));
}
}
}
[Test]
public void WriteFromExternalMethodAndSaveFromSource()
{
#pragma warning disable CA2000
using (var store = new ObjectStore(new TestLocalObjectStore()))
{
#pragma warning restore CA2000
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
Assert.IsTrue(Directory.Exists("./LocalObjectStoreTests"));
var key = "Test";
var content = "Example text";
var path = store.GetFilePath(key);
DummyMachineLearning(path, content);
store.Save(key);
var storeContent = store.Read(key);
Assert.AreEqual(content, storeContent);
}
}
[TestCase("/test/", "test")]
[TestCase("test\\", "test")]
[TestCase("test", "LocalObjectStoreTests")]
[TestCase("abc/12 3/test", "12 3")]
[TestCase("abc\\1 23\\test", "1 23")]
[TestCase("/abc\\1 23\\test", "1 23")]
[TestCase("\\abc\\1 23\\test", "1 23")]
public void GetFilePathMethodWorksProperly(string key, string expectedParentName)
{
#pragma warning disable CA2000
using (var store = new ObjectStore(new TestLocalObjectStore()))
{
#pragma warning restore CA2000
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
Assert.IsTrue(Directory.Exists("./LocalObjectStoreTests"));
var path = store.GetFilePath(key);
// paths are always under the object store root path
Assert.IsTrue(path.Contains("LocalObjectStoreTests", StringComparison.InvariantCultureIgnoreCase));
Assert.IsFalse(File.Exists(path));
Assert.IsNull(store.Read(key));
// the parent of the path requested will be created
var parent = Directory.GetParent(path);
Assert.AreEqual(expectedParentName, parent.Name);
Assert.IsTrue(parent.Exists);
}
}
[Test]
public void TrySaveKeyWithNotFileAssociated()
{
#pragma warning disable CA2000
using (var store = new ObjectStore(new TestLocalObjectStore()))
{
#pragma warning restore CA2000
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 }, AlgorithmMode.Backtesting);
Assert.IsTrue(Directory.Exists("./LocalObjectStoreTests"));
var key = "test";
Assert.Throws<ArgumentException>(() => store.Save(key));
}
}
[TestCase(1)]
[TestCase(0)]
public void NewUnregisteredFileIsAvailable(int useCase)
{
using (var store = new TestLocalObjectStore())
{
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 });
// create 'Jose' file in the object store After initialize
var joseFile = Path.Combine(TestStorageRoot, "JoseNew2");
File.WriteAllText(joseFile, "Pepe");
if (useCase == 0)
{
Assert.IsTrue(store.ContainsKey("JoseNew2"));
}
else if (useCase == 1)
{
Assert.IsNotNull(store.ReadBytes("JoseNew2"));
}
// clean up
File.Delete(joseFile);
}
}
[Test]
public void NewUnregisteredFileIsNotDeleted()
{
using (var store = new TestLocalObjectStore())
{
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 });
// create 'Jose' file in the object store After initialize
var joseFile = Path.Combine(TestStorageRoot, "JoseNew");
File.WriteAllText(joseFile, "Pepe");
store.SaveBytes("a.txt", new byte[1024 * 4]);
Assert.IsTrue(store.ContainsKey("a.txt"));
Assert.IsTrue(File.Exists(joseFile));
// clean up
store.Delete("a.txt");
File.Delete(joseFile);
}
}
[Test]
public void NewUnregisteredFileCanBeDeleted()
{
using (var store = new TestLocalObjectStore())
{
store.Initialize(0, 0, "", new Controls() { PersistenceIntervalSeconds = -1 });
// create 'Jose' file in the object store After initialize
var joseFile = Path.Combine(TestStorageRoot, "JoseNew77");
File.WriteAllText(joseFile, "Pepe");
Assert.IsTrue(File.Exists(joseFile));
Assert.IsTrue(store.Delete("JoseNew77"));
Assert.IsFalse(File.Exists(joseFile));
}
}
[Test]
public void DeletedObjectIsNotReloaded()
{
using (var store = new TestLocalObjectStore())
{
store.Initialize(0, 0, "", new Controls());
Assert.IsTrue(Directory.Exists("./LocalObjectStoreTests"));
var validData = new byte[1024 * 4];
store.SaveBytes("a.txt", validData);
Assert.IsTrue(store.ContainsKey("a.txt"));
store.SaveBytes("b.txt", validData);
Assert.IsTrue(store.ContainsKey("b.txt"));
// Assert the store has our two objects
var storedObj = store.GetEnumerator().AsEnumerable().ToList();
Assert.IsTrue(storedObj.Count == 2);
// Delete a.txt and close this store down
store.Delete("a.txt");
Assert.IsFalse(store.ContainsKey("a.txt"));
}
using (var store = new TestLocalObjectStore())
{
// Check that the dir still exists, it had files so it shouldn't have deleted
Assert.IsTrue(Directory.Exists("./LocalObjectStoreTests"));
store.Initialize(0, 0, "", new Controls());
// Check our files; a should be gone, b should be there
Assert.IsFalse(store.ContainsKey("a.txt"));
Assert.IsTrue(store.ContainsKey("b.txt"));
}
}
[Test]
public void StoragePropertiesAreAccessible()
{
var controls = new Controls { StorageLimit = 536870912, StorageFileCount = 500 };
using var store = new TestLocalObjectStore();
store.Initialize(1, 2, "token", controls);
Assert.AreEqual(536870912, store.MaxSize);
Assert.AreEqual(500, store.MaxFiles);
}
[Test]
public void StoragePropertiesUseDefaultsFromControls()
{
using var store = new TestLocalObjectStore();
store.Initialize(1, 2, "token", new Controls());
Assert.AreEqual(10737418240, store.MaxSize);
Assert.AreEqual(10000, store.MaxFiles);
}
[TestCase(1, false)]
[TestCase(2, true)]
public void SavingFilesRespectsStorageFileCountLimit(int filesToAdd, bool expectedLimitReached)
{
var controls = new Controls { StorageFileCount = 2, StorageLimit = long.MaxValue };
using var store = new TestLocalObjectStore();
store.Initialize(1, 2, "token", controls);
for (int i = 0; i < filesToAdd; i++)
{
store.SaveBytes($"/file{i}.txt", new byte[] { 1 });
}
Assert.AreEqual(expectedLimitReached, store.Count() == controls.StorageFileCount);
// Delete all files
for (int i = 0; i < filesToAdd; i++)
{
store.Delete($"/file{i}.txt");
}
}
[Test]
public void ObjectStoreDelegatesToImplementation()
{
using var localStore = new TestLocalObjectStore();
localStore.Initialize(1, 2, "token", new Controls());
using var objectStore = new ObjectStore(localStore);
Assert.AreEqual(localStore.MaxSize, objectStore.MaxSize);
Assert.AreEqual(localStore.MaxFiles, objectStore.MaxFiles);
Assert.AreEqual(localStore.Count(), objectStore.Count());
Assert.AreEqual(localStore.Count() == localStore.MaxFiles, objectStore.Count() == objectStore.MaxFiles);
}
[Test]
public void ResearchModeThrowsExceptionAndCapturesErrorWhenObjectStoreLimitIsReached()
{
var testObjectStore = new LocalObjectStore();
testObjectStore.Initialize(1, 1, "test", new Controls
{
StorageFileCount = 2,
StorageLimit = 100,
PersistenceIntervalSeconds = -1,
StorageAccess = new QuantConnect.Packets.StoragePermissions
{
Read = true,
Write = true,
Delete = true
}
}, AlgorithmMode.Research);
using (Py.GIL())
{
var testModule = PyModule.FromString("PythonCustomDataHistoryCanBeFetchedUsingCSharpApi",
@"
from AlgorithmImports import *
def clean_data():
qb = QuantBook()
for i in range(3):
qb.object_store.delete(f'file_{i}.txt')
def add_data(object_store):
qb = QuantBook()
qb.SetObjectStore(object_store)
max_files = qb.object_store.max_files
for i in range(max_files):
result = qb.object_store.save(f'file_{i}.txt', f'data_{i}')
result = qb.object_store.save(f'file_{max_files}.txt', 'should_fail')
return result
");
dynamic result = testModule.GetAttr("add_data");
var exception = Assert.Catch<Exception>(() => result(testObjectStore));
Assert.That(exception.Message, Does.Contain("ObjectStore limit"));
// Reset
testModule.GetAttr("clean_data").Invoke();
testObjectStore.Dispose();
}
}
private static void DummyMachineLearning(string outputFile, string content)
{
try
{
var sw = new StreamWriter(outputFile);
sw.Write(content);
sw.Close();
}
catch (Exception e)
{
throw e;
}
}
public class TestSettings
{
public int EmaFastPeriod { get; set; }
public int EmaSlowPeriod { get; set; }
}
private class TestLocalObjectStore : LocalObjectStore
{
public bool PersistDataCalled { get; set; }
public override void Initialize(int userId, int projectId, string userToken, Controls controls, AlgorithmMode algorithmMode = AlgorithmMode.Backtesting)
{
base.Initialize(userId, projectId, userToken, controls, algorithmMode);
}
public void Initialize(int userId, int projectId, string userToken, Controls controls, FileHandler fileHandler)
{
FileHandler = fileHandler;
base.Initialize(userId, projectId, userToken, controls, AlgorithmMode.Backtesting);
}
protected override bool PersistData()
{
PersistDataCalled = true;
return base.PersistData();
}
protected override string StorageRoot() => TestStorageRoot;
}
public class TestFileHandler : FileHandler
{
public override void WriteAllBytes(string path, byte[] data)
{
// The thread sleeps for 1 second in order to align with the
// other thread that will try to delete this file (see SaveAndDelete()
// unit test)
Thread.Sleep(1000);
base.WriteAllBytes(path, data);
}
}
}
}