diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 00d00e1e3..6c010e1ef 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -164,73 +164,74 @@ private void CalculateCoverage() { foreach (var result in _results) { - if (!File.Exists(result.HitsFilePath)) + if (!Directory.Exists(result.HitsDirectoryPath)) { - // File not instrumented, or nothing in it called. Warn about this? + // File not instrumented. Warn about this? continue; } List documents = result.Documents.Values.ToList(); - using (var fs = new FileStream(result.HitsFilePath, FileMode.Open)) - using (var br = new BinaryReader(fs)) + foreach (var file in Directory.EnumerateFiles(result.HitsDirectoryPath)) { - int hitCandidatesCount = br.ReadInt32(); - - // TODO: hitCandidatesCount should be verified against result.HitCandidates.Count + using (var fs = new FileStream(Path.Combine(result.HitsDirectoryPath, file), FileMode.Open)) + using (var br = new BinaryReader(fs)) + { + int hitCandidatesCount = br.ReadInt32(); - var documentsList = result.Documents.Values.ToList(); + // TODO: hitCandidatesCount should be verified against result.HitCandidates.Count - for (int i = 0; i < hitCandidatesCount; ++i) - { - var hitLocation = result.HitCandidates[i]; + for (int i = 0; i < hitCandidatesCount; ++i) + { + var hitLocation = result.HitCandidates[i]; - var document = documentsList[hitLocation.docIndex]; + var document = documents[hitLocation.docIndex]; - int hits = br.ReadInt32(); + int hits = br.ReadInt32(); - if (hitLocation.isBranch) - { - var branch = document.Branches[(hitLocation.start, hitLocation.end)]; - branch.Hits += hits; - } - else - { - for (int j = hitLocation.start; j <= hitLocation.end; j++) + if (hitLocation.isBranch) + { + var branch = document.Branches[(hitLocation.start, hitLocation.end)]; + branch.Hits += hits; + } + else { - var line = document.Lines[j]; - line.Hits += hits; + for (int j = hitLocation.start; j <= hitLocation.end; j++) + { + var line = document.Lines[j]; + line.Hits += hits; + } } } } } - // for MoveNext() compiler autogenerated method we need to patch false positive (IAsyncStateMachine for instance) - // we'll remove all MoveNext() not covered branch - foreach (var document in result.Documents) - { - List> branchesToRemove = new List>(); - foreach (var branch in document.Value.Branches) - { - //if one branch is covered we search the other one only if it's not covered - if (CecilSymbolHelper.IsMoveNext(branch.Value.Method) && branch.Value.Hits > 0) - { - foreach (var moveNextBranch in document.Value.Branches) - { - if (moveNextBranch.Value.Method == branch.Value.Method && moveNextBranch.Value != branch.Value && moveNextBranch.Value.Hits == 0) - { - branchesToRemove.Add(moveNextBranch); - } - } - } - } - foreach (var branchToRemove in branchesToRemove) - { - document.Value.Branches.Remove(branchToRemove.Key); - } + // for MoveNext() compiler autogenerated method we need to patch false positive (IAsyncStateMachine for instance) + // we'll remove all MoveNext() not covered branch + foreach (var document in result.Documents) + { + List> branchesToRemove = new List>(); + foreach (var branch in document.Value.Branches) + { + //if one branch is covered we search the other one only if it's not covered + if (CecilSymbolHelper.IsMoveNext(branch.Value.Method) && branch.Value.Hits > 0) + { + foreach (var moveNextBranch in document.Value.Branches) + { + if (moveNextBranch.Value.Method == branch.Value.Method && moveNextBranch.Value != branch.Value && moveNextBranch.Value.Hits == 0) + { + branchesToRemove.Add(moveNextBranch); + } + } + } + } + foreach (var branchToRemove in branchesToRemove) + { + document.Value.Branches.Remove(branchToRemove.Key); + } } - InstrumentationHelper.DeleteHitsFile(result.HitsFilePath); + InstrumentationHelper.DeleteHitsDirectory(result.HitsDirectoryPath); } } } diff --git a/src/coverlet.core/Helpers/InstrumentationHelper.cs b/src/coverlet.core/Helpers/InstrumentationHelper.cs index ea1fc19e1..500067a4f 100644 --- a/src/coverlet.core/Helpers/InstrumentationHelper.cs +++ b/src/coverlet.core/Helpers/InstrumentationHelper.cs @@ -61,12 +61,12 @@ public static void RestoreOriginalModule(string module, string identifier) }, retryStrategy, 10); } - public static void DeleteHitsFile(string path) + public static void DeleteHitsDirectory(string path) { - // Retry hitting the hits file - retry up to 10 times, since the file could be locked + // Retry hitting the hits directory - retry up to 10 times, since the files could be locked // See: https://github.com/tonerdo/coverlet/issues/25 var retryStrategy = CreateRetryStrategy(); - RetryHelper.Retry(() => File.Delete(path), retryStrategy, 10); + RetryHelper.Retry(() => Directory.Delete(path, true), retryStrategy, 10); } public static bool IsValidFilterExpression(string filter) diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index a72a2bcf3..d738e210f 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Reflection; using Coverlet.Core.Attributes; using Coverlet.Core.Helpers; @@ -23,8 +22,8 @@ internal class Instrumenter private readonly string[] _includeFilters; private readonly string[] _excludedFiles; private InstrumenterResult _result; - private FieldDefinition _customTrackerHitsArray; - private FieldDefinition _customTrackerHitsFilePath; + private FieldDefinition _customTrackerHitsArraySize; + private FieldDefinition _customTrackerHitsDirectoryPath; private ILProcessor _customTrackerClassConstructorIl; private TypeDefinition _customTrackerTypeDef; private MethodReference _customTrackerRecordHitMethod; @@ -42,15 +41,17 @@ public Instrumenter(string module, string identifier, string[] excludeFilters, s public InstrumenterResult Instrument() { - string hitsFilePath = Path.Combine( + string hitsDirectoryPath = Path.Combine( Path.GetTempPath(), Path.GetFileNameWithoutExtension(_module) + "_" + _identifier ); + Directory.CreateDirectory(hitsDirectoryPath); + _result = new InstrumenterResult { Module = Path.GetFileNameWithoutExtension(_module), - HitsFilePath = hitsFilePath, + HitsDirectoryPath = hitsDirectoryPath, ModulePath = _module }; @@ -88,10 +89,9 @@ private void InstrumentModule() // Fixup the custom tracker class constructor, according to all instrumented types Instruction lastInstr = _customTrackerClassConstructorIl.Body.Instructions.Last(); _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4, _result.HitCandidates.Count)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Newarr, module.TypeSystem.Int32)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArraySize)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsDirectoryPath)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsDirectoryPath)); module.Write(stream); } @@ -116,17 +116,17 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module) _customTrackerTypeDef.Fields.Add(fieldClone); - if (fieldClone.Name == "HitsArray") - _customTrackerHitsArray = fieldClone; - else if (fieldClone.Name == "HitsFilePath") - _customTrackerHitsFilePath = fieldClone; + if (fieldClone.Name == nameof(ModuleTrackerTemplate.hitsArraySize)) + _customTrackerHitsArraySize = fieldClone; + else if (fieldClone.Name == nameof(ModuleTrackerTemplate.hitsDirectoryPath)) + _customTrackerHitsDirectoryPath = fieldClone; } foreach (MethodDefinition methodDef in moduleTrackerTemplate.Methods) { MethodDefinition methodOnCustomType = new MethodDefinition(methodDef.Name, methodDef.Attributes, methodDef.ReturnType); - if (methodDef.Name == "RecordHit") + if (methodDef.Name == nameof(ModuleTrackerTemplate.RecordHit)) { foreach (var parameter in methodDef.Parameters) { @@ -179,7 +179,7 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module) { handler.CatchType = module.ImportReference(handler.CatchType); } - + methodOnCustomType.Body.ExceptionHandlers.Add(handler); } @@ -189,7 +189,7 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module) module.Types.Add(_customTrackerTypeDef); } - Debug.Assert(_customTrackerHitsArray != null); + Debug.Assert(_customTrackerHitsArraySize != null); Debug.Assert(_customTrackerClassConstructorIl != null); } diff --git a/src/coverlet.core/Instrumentation/InstrumenterResult.cs b/src/coverlet.core/Instrumentation/InstrumenterResult.cs index 0060b47a8..40de913c8 100644 --- a/src/coverlet.core/Instrumentation/InstrumenterResult.cs +++ b/src/coverlet.core/Instrumentation/InstrumenterResult.cs @@ -41,7 +41,7 @@ public InstrumenterResult() } public string Module; - public string HitsFilePath; + public string HitsDirectoryPath; public string ModulePath; public Dictionary Documents { get; private set; } public List<(bool isBranch, int docIndex, int start, int end)> HitCandidates { get; private set; } diff --git a/src/coverlet.core/Instrumentation/ModuleTrackerTemplate.cs b/src/coverlet.core/Instrumentation/ModuleTrackerTemplate.cs index d55a6174b..66eff4810 100644 --- a/src/coverlet.core/Instrumentation/ModuleTrackerTemplate.cs +++ b/src/coverlet.core/Instrumentation/ModuleTrackerTemplate.cs @@ -1,7 +1,8 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.IO.MemoryMappedFiles; +using System.Text; using System.Threading; namespace Coverlet.Core.Instrumentation @@ -17,129 +18,51 @@ namespace Coverlet.Core.Instrumentation [ExcludeFromCodeCoverage] public static class ModuleTrackerTemplate { - public static string HitsFilePath; - public static int[] HitsArray; + public static string hitsDirectoryPath; + public static int hitsArraySize; - // Special case when instrumenting CoreLib, the thread static below prevents infinite loop in CoreLib - // while allowing the tracker template to call any of its types and functions. [ThreadStatic] - private static bool t_isTracking; - - [ThreadStatic] - private static int[] t_threadHits; - - private static List _threads; + public static MemoryMappedViewAccessor memoryMappedViewAccessor; static ModuleTrackerTemplate() { - t_isTracking = true; - _threads = new List(2 * Environment.ProcessorCount); - - AppDomain.CurrentDomain.ProcessExit += new EventHandler(UnloadModule); - AppDomain.CurrentDomain.DomainUnload += new EventHandler(UnloadModule); - t_isTracking = false; // At the end of the instrumentation of a module, the instrumenter needs to add code here - // to initialize the static fields according to the values derived from the instrumentation of + // to initialize the static setup fields according to the values derived from the instrumentation of // the module. } - public static void RecordHit(int hitLocationIndex) + static void SetupThread() { - if (t_isTracking) - return; + var threadHitsFile = Path.Combine(hitsDirectoryPath, Guid.NewGuid().ToString()); + var fileStream = new FileStream(threadHitsFile, FileMode.CreateNew, FileAccess.ReadWrite); + try + { + //write the header + using (var writer = new BinaryWriter(fileStream, Encoding.Default, true)) + writer.Write(hitsArraySize); - if (t_threadHits == null) + var bytesRequired = (hitsArraySize + 1) * sizeof(int); + using (var memoryMappedFile = MemoryMappedFile.CreateFromFile(fileStream, null, bytesRequired, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, false)) + memoryMappedViewAccessor = memoryMappedFile.CreateViewAccessor(); + } + catch { - t_isTracking = true; - lock (_threads) - { - if (t_threadHits == null) - { - t_threadHits = new int[HitsArray.Length]; - _threads.Add(t_threadHits); - } - } - t_isTracking = false; + fileStream.Dispose(); + File.Delete(threadHitsFile); + throw; } - - ++t_threadHits[hitLocationIndex]; } - public static void UnloadModule(object sender, EventArgs e) + public static void RecordHit(int hitLocationIndex) { - t_isTracking = true; + if (memoryMappedViewAccessor == null) + SetupThread(); - // Update the global hits array from data from all the threads - lock (_threads) - { - foreach (var threadHits in _threads) - { - for (int i = 0; i < HitsArray.Length; ++i) - HitsArray[i] += threadHits[i]; - } + var buffer = memoryMappedViewAccessor.SafeMemoryMappedViewHandle; - // Prevent any double counting scenario, i.e.: UnloadModule called twice (not sure if this can happen in practice ...) - // Only an issue if DomainUnload and ProcessExit can both happens, perhaps can be removed... - _threads.Clear(); - } - - // The same module can be unloaded multiple times in the same process via different app domains. - // Use a global mutex to ensure no concurrent access. - using (var mutex = new Mutex(true, Path.GetFileNameWithoutExtension(HitsFilePath) + "_Mutex", out bool createdNew)) - { - if (!createdNew) - mutex.WaitOne(); - - bool failedToCreateNewHitsFile = false; - try - { - using (var fs = new FileStream(HitsFilePath, FileMode.CreateNew)) - using (var bw = new BinaryWriter(fs)) - { - bw.Write(HitsArray.Length); - foreach (int hitCount in HitsArray) - { - bw.Write(hitCount); - } - } - } - catch - { - failedToCreateNewHitsFile = true; - } - - if (failedToCreateNewHitsFile) - { - // Update the number of hits by adding value on disk with the ones on memory. - // This path should be triggered only in the case of multiple AppDomain unloads. - using (var fs = new FileStream(HitsFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) - using (var br = new BinaryReader(fs)) - using (var bw = new BinaryWriter(fs)) - { - int hitsLength = br.ReadInt32(); - if (hitsLength != HitsArray.Length) - { - throw new InvalidOperationException( - $"{HitsFilePath} has {hitsLength} entries but on memory {nameof(HitsArray)} has {HitsArray.Length}"); - } - - for (int i = 0; i < hitsLength; ++i) - { - int oldHitCount = br.ReadInt32(); - bw.Seek(-sizeof(int), SeekOrigin.Current); - bw.Write(HitsArray[i] + oldHitCount); - } - } - } - - // Prevent any double counting scenario, i.e.: UnloadModule called twice (not sure if this can happen in practice ...) - // Only an issue if DomainUnload and ProcessExit can both happens, perhaps can be removed... - Array.Clear(HitsArray, 0, HitsArray.Length); - - // On purpose this is not under a try-finally: it is better to have an exception if there was any error writing the hits file - // this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll. - mutex.ReleaseMutex(); - } + //+1 for header + var locationIndex = ((uint)hitLocationIndex + 1) * sizeof(int); + buffer.Write(locationIndex, buffer.Read(locationIndex) + 1); } } } diff --git a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs index 44e1021c8..bfe948637 100644 --- a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs +++ b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs @@ -54,13 +54,14 @@ public void TestIsValidFilterExpression() } [Fact] - public void TestDeleteHitsFile() + public void TestDeleteHitsDirectory() { - var tempFile = Path.GetTempFileName(); - Assert.True(File.Exists(tempFile)); + var tempDir = Path.GetTempFileName(); + File.Delete(tempDir); + Directory.CreateDirectory(tempDir); - InstrumentationHelper.DeleteHitsFile(tempFile); - Assert.False(File.Exists(tempFile)); + InstrumentationHelper.DeleteHitsDirectory(tempDir); + Assert.False(Directory.Exists(tempDir)); } diff --git a/test/coverlet.core.tests/Instrumentation/ModuleTrackerTemplateTests.cs b/test/coverlet.core.tests/Instrumentation/ModuleTrackerTemplateTests.cs index 4a157855f..bdb34aeaa 100644 --- a/test/coverlet.core.tests/Instrumentation/ModuleTrackerTemplateTests.cs +++ b/test/coverlet.core.tests/Instrumentation/ModuleTrackerTemplateTests.cs @@ -1,122 +1,123 @@ -using Coverlet.Core.Instrumentation; -using System; +using System; using System.IO; using System.Threading; -using System.Threading.Tasks; using Xunit; -namespace coverlet.core.tests.Instrumentation +namespace Coverlet.Core.Instrumentation.Tests { - public class ModuleTrackerTemplateTestsFixture : IDisposable - { - public ModuleTrackerTemplateTestsFixture() - { - ModuleTrackerTemplate.HitsFilePath = Path.Combine(Path.GetTempPath(), nameof(ModuleTrackerTemplateTests)); - } - - public void Dispose() - { - AppDomain.CurrentDomain.ProcessExit -= ModuleTrackerTemplate.UnloadModule; - AppDomain.CurrentDomain.DomainUnload -= ModuleTrackerTemplate.UnloadModule; - } - } - - public class ModuleTrackerTemplateTests : IClassFixture, IDisposable + public class ModuleTrackerTemplateTests : IDisposable { + readonly string tempDirName = Path.Combine(Path.GetTempPath(), nameof(ModuleTrackerTemplateTests)); public ModuleTrackerTemplateTests() { - File.Delete(ModuleTrackerTemplate.HitsFilePath); + Dispose(); + Directory.CreateDirectory(tempDirName); + ModuleTrackerTemplate.hitsDirectoryPath = tempDirName; + ModuleTrackerTemplate.hitsArraySize = 4; } public void Dispose() { - File.Delete(ModuleTrackerTemplate.HitsFilePath); + if (Directory.Exists(tempDirName)) + Directory.Delete(tempDirName, true); } [Fact] public void HitsFileCorrectlyWritten() { - ModuleTrackerTemplate.HitsArray = new[] { 1, 2, 0, 3 }; - ModuleTrackerTemplate.UnloadModule(null, null); + try + { + ModuleTrackerTemplate.RecordHit(3); + ModuleTrackerTemplate.RecordHit(3); + ModuleTrackerTemplate.RecordHit(1); + ModuleTrackerTemplate.RecordHit(1); + ModuleTrackerTemplate.RecordHit(0); + ModuleTrackerTemplate.RecordHit(3); + } + finally + { + CloseMemoryMapping(); + } var expectedHitsArray = new[] { 1, 2, 0, 3 }; - Assert.Equal(expectedHitsArray, ReadHitsFile()); - } - - [Fact] - public void HitsFileWithDifferentNumberOfEntriesCausesExceptionOnUnload() - { - WriteHitsFile(new[] { 1, 2, 3 }); - ModuleTrackerTemplate.HitsArray = new[] { 1 }; - Assert.Throws(() => ModuleTrackerTemplate.UnloadModule(null, null)); + Assert.Equal(expectedHitsArray, ReadHitsFiles()); } [Fact] public void HitsOnMultipleThreadsCorrectlyCounted() { - ModuleTrackerTemplate.HitsArray = new[] { 0, 0, 0, 0 }; - for (int i = 0; i < ModuleTrackerTemplate.HitsArray.Length; ++i) - { - var t = new Thread(HitIndex); - t.Start(i); - } - - ModuleTrackerTemplate.UnloadModule(null, null); - var expectedHitsArray = new[] { 4, 3, 2, 1 }; - Assert.Equal(expectedHitsArray, ReadHitsFile()); - - void HitIndex(object index) + using (var semaphore = new SemaphoreSlim(1)) { - var hitIndex = (int)index; - for (int i = 0; i <= hitIndex; ++i) + semaphore.Wait(); + int joinCount = 0; + void HitIndex(object index) { - ModuleTrackerTemplate.RecordHit(i); + try + { + var hitIndex = (int)index; + for (int i = 0; i <= hitIndex; ++i) + ModuleTrackerTemplate.RecordHit(i); + } + finally + { + CloseMemoryMapping(); + } + if (Interlocked.Increment(ref joinCount) == 4) + semaphore.Release(); } - } - } - [Fact] - public void MultipleSequentialUnloadsHaveCorrectTotalData() - { - ModuleTrackerTemplate.HitsArray = new[] { 0, 3, 2, 1 }; - ModuleTrackerTemplate.UnloadModule(null, null); + for (int i = 0; i < 4; ++i) + { + var t = new Thread(HitIndex); + t.Start(i); + } - ModuleTrackerTemplate.HitsArray = new[] { 0, 1, 2, 3 }; - ModuleTrackerTemplate.UnloadModule(null, null); + semaphore.Wait(); + } - var expectedHitsArray = new[] { 0, 4, 4, 4 }; - Assert.Equal(expectedHitsArray, ReadHitsFile()); + var expectedHitsArray = new[] { 4, 3, 2, 1 }; + Assert.Equal(expectedHitsArray, ReadHitsFiles()); } - + [Fact] - public async void MutexBlocksMultipleWriters() + public void MultipleRecordingsHaveCorrectTotalData() { - using (var mutex = new Mutex( - true, Path.GetFileNameWithoutExtension(ModuleTrackerTemplate.HitsFilePath) + "_Mutex", out bool createdNew)) + try { - Assert.True(createdNew); - - ModuleTrackerTemplate.HitsArray = new[] { 0, 1, 2, 3 }; - var unloadTask = Task.Run(() => ModuleTrackerTemplate.UnloadModule(null, null)); - - Assert.False(unloadTask.Wait(5)); - - WriteHitsFile(new[] { 0, 3, 2, 1 }); - - Assert.False(unloadTask.Wait(5)); - - mutex.ReleaseMutex(); - await unloadTask; + ModuleTrackerTemplate.RecordHit(1); + ModuleTrackerTemplate.RecordHit(3); + ModuleTrackerTemplate.RecordHit(2); + ModuleTrackerTemplate.RecordHit(2); + ModuleTrackerTemplate.RecordHit(3); + } + finally + { + CloseMemoryMapping(); + } - var expectedHitsArray = new[] { 0, 4, 4, 4 }; - Assert.Equal(expectedHitsArray, ReadHitsFile()); + try + { + ModuleTrackerTemplate.RecordHit(1); + ModuleTrackerTemplate.RecordHit(1); + ModuleTrackerTemplate.RecordHit(3); + ModuleTrackerTemplate.RecordHit(2); + ModuleTrackerTemplate.RecordHit(2); + ModuleTrackerTemplate.RecordHit(1); + ModuleTrackerTemplate.RecordHit(3); } + finally + { + CloseMemoryMapping(); + } + + var expectedHitsArray = new[] { 0, 4, 4, 4 }; + Assert.Equal(expectedHitsArray, ReadHitsFiles()); } private void WriteHitsFile(int[] hitsArray) { - using (var fs = new FileStream(ModuleTrackerTemplate.HitsFilePath, FileMode.Create)) + using (var fs = new FileStream(Path.Combine(tempDirName, 1.ToString()), FileMode.Create)) using (var bw = new BinaryWriter(fs)) { bw.Write(hitsArray.Length); @@ -127,19 +128,34 @@ private void WriteHitsFile(int[] hitsArray) } } - private int[] ReadHitsFile() + private int[] ReadHitsFiles() { - using (var fs = new FileStream(ModuleTrackerTemplate.HitsFilePath, FileMode.Open)) - using (var br = new BinaryReader(fs)) - { - var hitsArray = new int[br.ReadInt32()]; - for (int i = 0; i < hitsArray.Length; ++i) + int[] hitsArray = null; + foreach (var file in Directory.EnumerateFiles(tempDirName)) + using (var fs = new FileStream(Path.Combine(tempDirName, file), FileMode.Open)) + using (var br = new BinaryReader(fs)) { - hitsArray[i] = br.ReadInt32(); + var expectedHitsArraySize = br.ReadInt32(); + if (hitsArray == null) + hitsArray = new int[expectedHitsArraySize]; + else + Assert.Equal(hitsArray.Length, expectedHitsArraySize); + + Assert.Equal(fs.Length, sizeof(int) * (expectedHitsArraySize + 1)); + + for (int i = 0; i < hitsArray.Length; ++i) + { + hitsArray[i] += br.ReadInt32(); + } } - return hitsArray; - } + return hitsArray; + } + + private void CloseMemoryMapping() + { + ModuleTrackerTemplate.memoryMappedViewAccessor?.Dispose(); + ModuleTrackerTemplate.memoryMappedViewAccessor = null; } } }