cs-codex-dist-tests/DistTestCore/FileManager.cs

222 lines
6.8 KiB
C#
Raw Normal View History

2023-04-12 14:06:04 +00:00
using Logging;
using NUnit.Framework;
2023-06-07 08:56:25 +00:00
using System.Runtime.InteropServices;
using Utils;
2023-04-12 14:06:04 +00:00
namespace DistTestCore
{
public interface IFileManager
{
TestFile CreateEmptyTestFile(string label = "");
TestFile GenerateTestFile(ByteSize size, string label = "");
2023-04-12 14:06:04 +00:00
void DeleteAllTestFiles();
void PushFileSet();
void PopFileSet();
2023-04-12 14:06:04 +00:00
}
public class FileManager : IFileManager
{
2023-06-07 07:59:00 +00:00
public const int ChunkSize = 1024 * 1024 * 100;
private static NumberSource folderNumberSource = new NumberSource(0);
2023-04-12 14:06:04 +00:00
private readonly Random random = new Random();
private readonly TestLog log;
private readonly string folder;
private readonly List<List<TestFile>> fileSetStack = new List<List<TestFile>>();
2023-04-12 14:06:04 +00:00
public FileManager(TestLog log, Configuration configuration)
{
folder = Path.Combine(configuration.GetFileManagerFolder(), folderNumberSource.GetNextNumber().ToString("D5"));
2023-04-12 14:06:04 +00:00
EnsureDirectory();
2023-04-12 14:06:04 +00:00
this.log = log;
}
public TestFile CreateEmptyTestFile(string label = "")
2023-04-12 14:06:04 +00:00
{
var path = Path.Combine(folder, Guid.NewGuid().ToString() + "_test.bin");
var result = new TestFile(log, path, label);
2023-04-12 14:06:04 +00:00
File.Create(result.Filename).Close();
if (fileSetStack.Any()) fileSetStack.Last().Add(result);
2023-04-12 14:06:04 +00:00
return result;
}
public TestFile GenerateTestFile(ByteSize size, string label)
2023-04-12 14:06:04 +00:00
{
2023-06-07 07:59:00 +00:00
var sw = Stopwatch.Begin(log);
var result = GenerateFile(size, label);
sw.End($"Generated file '{result.Describe()}'.");
2023-04-12 14:06:04 +00:00
return result;
}
public void DeleteAllTestFiles()
{
DeleteDirectory();
2023-04-12 14:06:04 +00:00
}
public void PushFileSet()
{
fileSetStack.Add(new List<TestFile>());
}
public void PopFileSet()
{
if (!fileSetStack.Any()) return;
var pop = fileSetStack.Last();
fileSetStack.Remove(pop);
foreach (var file in pop)
{
try
{
File.Delete(file.Filename);
}
catch { }
}
}
2023-06-07 07:59:00 +00:00
private TestFile GenerateFile(ByteSize size, string label)
{
var result = CreateEmptyTestFile(label);
CheckSpaceAvailable(result, size);
GenerateFileBytes(result, size);
return result;
}
private void CheckSpaceAvailable(TestFile testFile, ByteSize size)
{
var file = new FileInfo(testFile.Filename);
var drive = new DriveInfo(file.Directory!.Root.FullName);
var spaceAvailable = drive.TotalFreeSpace;
if (spaceAvailable < size.SizeInBytes)
{
var msg = $"Inconclusive: Not enough disk space to perform test. " +
$"{Formatter.FormatByteSize(size.SizeInBytes)} required. " +
$"{Formatter.FormatByteSize(spaceAvailable)} available.";
log.Log(msg);
Assert.Inconclusive(msg);
}
}
2023-04-12 14:06:04 +00:00
private void GenerateFileBytes(TestFile result, ByteSize size)
{
long bytesLeft = size.SizeInBytes;
2023-06-07 07:59:00 +00:00
int chunkSize = ChunkSize;
2023-04-12 14:06:04 +00:00
while (bytesLeft > 0)
{
2023-06-07 07:59:00 +00:00
try
{
var length = Math.Min(bytesLeft, chunkSize);
AppendRandomBytesToFile(result, length);
bytesLeft -= length;
}
catch
{
chunkSize = chunkSize / 2;
if (chunkSize < 1024) throw;
}
2023-04-12 14:06:04 +00:00
}
}
private void AppendRandomBytesToFile(TestFile result, long length)
{
var bytes = new byte[length];
random.NextBytes(bytes);
using var stream = new FileStream(result.Filename, FileMode.Append);
stream.Write(bytes, 0, bytes.Length);
}
private void EnsureDirectory()
{
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
}
private void DeleteDirectory()
{
Directory.Delete(folder, true);
}
2023-04-12 14:06:04 +00:00
}
public class TestFile
{
private readonly TestLog log;
public TestFile(TestLog log, string filename, string label)
2023-04-12 14:06:04 +00:00
{
this.log = log;
2023-04-12 14:06:04 +00:00
Filename = filename;
Label = label;
2023-04-12 14:06:04 +00:00
}
public string Filename { get; }
public string Label { get; }
2023-04-12 14:06:04 +00:00
public void AssertIsEqual(TestFile? actual)
2023-06-07 08:56:25 +00:00
{
var sw = Stopwatch.Begin(log);
try
{
AssertEqual(actual);
}
finally
{
sw.End($"{nameof(TestFile)}.{nameof(AssertIsEqual)}");
}
}
public string Describe()
{
if (!string.IsNullOrEmpty(Label)) return Label;
return $"'{Filename}' ({Formatter.FormatByteSize(GetFileSize())})";
}
private void AssertEqual(TestFile? actual)
2023-04-12 14:06:04 +00:00
{
if (actual == null) Assert.Fail("TestFile is null.");
if (actual == this || actual!.Filename == Filename) Assert.Fail("TestFile is compared to itself.");
2023-06-07 08:56:25 +00:00
Stopwatch.Measure(log, "sizes", () =>
{
Assert.That(actual.GetFileSize(), Is.EqualTo(GetFileSize()), "Files are not of equal length.");
});
2023-04-12 14:06:04 +00:00
using var streamExpected = new FileStream(Filename, FileMode.Open, FileAccess.Read);
using var streamActual = new FileStream(actual.Filename, FileMode.Open, FileAccess.Read);
var bytesExpected = new byte[FileManager.ChunkSize];
var bytesActual = new byte[FileManager.ChunkSize];
var readExpected = 0;
var readActual = 0;
while (true)
{
readExpected = streamExpected.Read(bytesExpected, 0, FileManager.ChunkSize);
readActual = streamActual.Read(bytesActual, 0, FileManager.ChunkSize);
if (readExpected == 0 && readActual == 0)
{
log.Log($"OK: '{Describe()}' is equal to '{actual.Describe()}'.");
return;
}
2023-06-07 08:56:25 +00:00
2023-04-12 14:06:04 +00:00
Assert.That(readActual, Is.EqualTo(readExpected), "Unable to read buffers of equal length.");
2023-06-07 08:56:25 +00:00
for (var i = 0; i < readActual; i++)
{
if (bytesExpected[i] != bytesActual[i]) Assert.Fail("File contents not equal.");
}
}
}
private long GetFileSize()
{
var info = new FileInfo(Filename);
return info.Length;
}
2023-04-12 14:06:04 +00:00
}
}