Files
HawkeyeVision/VisionBuilder.UI.Tests/VisionBuilderStatisticsTests.cs
2025-09-16 10:42:43 +02:00

480 lines
19 KiB
C#

using System.Collections.Concurrent;
using System.Reflection;
using VisionBuilder.UI.Common;
using VisionBuilder.UI.Common.Commands;
using VisionBuilder.UI.Common.Processing;
using VisionBuilder.UI.Common.ViewModel.Classes;
using VisionBuilder.UI.Statistics;
namespace VisionBuilder.UI.Tests;
[TestClass]
public class VisionBuilderStatisticsTests
{
private string _tempDirectory;
private VisionBuilderStatisticsSettings _settings;
private TestRecognitionControl _recognitionControl;
private TestLoadingService _loadingService;
[TestInitialize]
public void Setup()
{
_tempDirectory = Path.Combine(Path.GetTempPath(), "VisionBuilderStatisticsTests", Guid.NewGuid().ToString());
Directory.CreateDirectory(_tempDirectory);
_settings = new VisionBuilderStatisticsSettings("TestCamera")
{
PathTemplate = _tempDirectory,
StartDateColumnName = "Start Date",
StartTimeColumnName = "Start time",
EndDateColumnName = "End Date",
EndTimeColumnName = "End time",
ErrorTimeColumnName = "Error time",
ProductColumnName = "Product",
ErrorColumnName = "Error"
};
_recognitionControl = new TestRecognitionControl();
_loadingService = new TestLoadingService();
}
[TestCleanup]
public void Cleanup()
{
if (Directory.Exists(_tempDirectory))
{
Directory.Delete(_tempDirectory, true);
}
}
[TestMethod]
public void SessionStarted_CreatesTemporaryFile_WithCorrectHeaders()
{
// Arrange
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
statistics.InitializeModule();
var sessionStartEvent = new SessionStartedEvent
{
RecipeName = "TestRecipe",
SessionStarted = DateTime.Now
};
// Act
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
statistics.Dispose(); // Ensure file is written and closed
// Assert
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
Assert.AreEqual(1, tmpFiles.Length, "Should create exactly one temporary file");
var lines = File.ReadAllLines(tmpFiles[0]);
Assert.IsTrue(lines.Length >= 2, "Should have header and at least one data line");
var expectedHeaders = new[] { "Start Date", "Start time", "End Date", "End time", "Error time", "Product", "Error" };
var actualHeaders = lines[0].Split(',');
CollectionAssert.AreEqual(expectedHeaders, actualHeaders, "Headers should match settings");
var firstDataLine = lines[1].Split(',');
Assert.IsTrue(DateTime.TryParse(firstDataLine[0], out _), "First column should be a valid date");
Assert.IsTrue(DateTime.TryParse(firstDataLine[1], out _), "Second column should be a valid time");
}
[TestMethod]
public void ImageProcessed_WithError_RecordsDefectInFile()
{
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
statistics.InitializeModule();
// Arrange
var sessionStartEvent = new SessionStartedEvent
{
RecipeName = "TestRecipe",
SessionStarted = DateTime.Now
};
var imageProcessedEvent = new ImageProcessedEvent
{
HasError = true,
ErrorNames = new List<string> { "TestDefect" },
RecipeName = "TestRecipe",
SessionStart = DateTime.Now
};
// Act
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
_recognitionControl.TriggerImageProcessed(imageProcessedEvent);
statistics.Dispose(); // Ensure file is written and closed
// Assert
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
var lines = File.ReadAllLines(tmpFiles[0]);
Assert.IsTrue(lines.Length >= 3, "Should have header, session start, and error record");
var errorLine = lines[2].Split(',');
Assert.AreEqual("TestDefect", errorLine[6], "Error name should be recorded in correct column");
Assert.AreEqual("TestRecipe", errorLine[5], "Recipe name should be recorded in correct column");
Assert.IsTrue(DateTime.TryParse(errorLine[4], out _), "Error time should be valid");
// Verify defect counting using reflection
var defectsField = typeof(VisionBuilderStatistics).GetField("_defects", BindingFlags.NonPublic | BindingFlags.Instance);
var defects = (ConcurrentDictionary<string, int>)defectsField.GetValue(statistics);
Assert.AreEqual(1, defects["TestDefect"], "Defect count should be incremented");
}
[TestMethod]
public void ImageProcessed_WithoutError_DoesNotRecordDefect()
{
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
statistics.InitializeModule();
// Arrange
var sessionStartEvent = new SessionStartedEvent
{
RecipeName = "TestRecipe",
SessionStarted = DateTime.Now
};
var imageProcessedEvent = new ImageProcessedEvent
{
HasError = false,
ErrorNames = new List<string>(),
RecipeName = "TestRecipe",
SessionStart = DateTime.Now
};
// Act
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
_recognitionControl.TriggerImageProcessed(imageProcessedEvent);
statistics.Dispose(); // Ensure file is written and closed
// Assert
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
var lines = File.ReadAllLines(tmpFiles[0]);
Assert.AreEqual(2, lines.Length, "Should only have header and session start line");
// Verify defect counting using reflection
var defectsField = typeof(VisionBuilderStatistics).GetField("_defects", BindingFlags.NonPublic | BindingFlags.Instance);
var defects = (ConcurrentDictionary<string, int>)defectsField.GetValue(statistics);
Assert.AreEqual(0, defects.Count, "No defects should be recorded");
}
[TestMethod]
public void SessionEnded_WritesEndTimeAndDisposesCsv()
{
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService)
{
DoNotDeleteTempFiles = true
};
statistics.InitializeModule();
// Arrange
var sessionStartEvent = new SessionStartedEvent
{
RecipeName = "TestRecipe",
SessionStarted = DateTime.Now
};
var sessionEndEvent = new SessionEndedEvent
{
SessionEnded = DateTime.Now
};
// Act
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
_recognitionControl.TriggerSessionEnded(sessionEndEvent);
statistics.Dispose(); // Ensure file is written and closed
// Assert
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
var lines = File.ReadAllLines(tmpFiles[0]);
Assert.IsTrue(lines.Length >= 3, "Should have header, session start, and session end");
var endLine = lines[2].Split(',');
Assert.IsTrue(DateTime.TryParse(endLine[2], out _), "End date should be valid");
Assert.IsTrue(DateTime.TryParse(endLine[3], out _), "End time should be valid");
Assert.IsTrue(_loadingService.StartLoadingCalled, "Should start loading during session end");
Assert.IsTrue(_loadingService.StopLoadingCalled, "Should stop loading after session end");
}
[TestMethod]
public void WriteFinalStatistics_ConsolidatesTemporaryFiles()
{
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
statistics.InitializeModule();
// Arrange
var sessionStartEvent = new SessionStartedEvent
{
RecipeName = "TestRecipe",
SessionStarted = DateTime.Now
};
var imageProcessedEvent = new ImageProcessedEvent
{
HasError = true,
ErrorNames = new List<string> { "TestDefect" },
RecipeName = "TestRecipe",
SessionStart = DateTime.Now
};
var sessionEndEvent = new SessionEndedEvent
{
SessionEnded = DateTime.Now
};
// Act
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
_recognitionControl.TriggerImageProcessed(imageProcessedEvent);
_recognitionControl.TriggerSessionEnded(sessionEndEvent);
statistics.Dispose(); // Ensure file is written and closed
// Assert
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
Assert.AreEqual(0, tmpFiles.Length, "Temporary files should be deleted after consolidation");
var finalFile = Path.Combine(_tempDirectory, "statistics_by_events.csv");
Assert.IsTrue(File.Exists(finalFile), "Final statistics file should be created");
var finalLines = File.ReadAllLines(finalFile);
Assert.IsTrue(finalLines.Length >= 2, "Final file should contain session data");
// Verify the defect was recorded in the final file
var defectLine = finalLines.FirstOrDefault(l => l.Contains("TestDefect"));
Assert.IsNotNull(defectLine, "Defect should be recorded in final file");
}
[TestMethod]
public void MultipleDefects_CountedCorrectly()
{
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
statistics.InitializeModule();
// Arrange
var sessionStartEvent = new SessionStartedEvent
{
RecipeName = "TestRecipe",
SessionStarted = DateTime.Now
};
// Act
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
// Process multiple images with same defect
for (int i = 0; i < 3; i++)
{
_recognitionControl.TriggerImageProcessed(new ImageProcessedEvent
{
HasError = true,
ErrorNames = new List<string> { "DefectA" },
RecipeName = "TestRecipe",
SessionStart = DateTime.Now
});
}
// Process images with different defect
for (int i = 0; i < 2; i++)
{
_recognitionControl.TriggerImageProcessed(new ImageProcessedEvent
{
HasError = true,
ErrorNames = new List<string> { "DefectB" },
RecipeName = "TestRecipe",
SessionStart = DateTime.Now
});
}
statistics.Dispose(); // Ensure file is written and closed
// Assert
var defectsField = typeof(VisionBuilderStatistics).GetField("_defects", BindingFlags.NonPublic | BindingFlags.Instance);
var defects = (ConcurrentDictionary<string, int>)defectsField.GetValue(statistics);
Assert.AreEqual(3, defects["DefectA"], "DefectA should be counted correctly");
Assert.AreEqual(2, defects["DefectB"], "DefectB should be counted correctly");
}
[TestMethod]
public void PathTemplate_WithVariables_ReplacedCorrectly()
{
// Arrange
var customSettings = new VisionBuilderStatisticsSettings("TestCamera")
{
PathTemplate = Path.Combine(_tempDirectory, "$RECIPE_NAME", "$SS_YEAR", "$SS_MONTH")
};
var customStatistics = new VisionBuilderStatistics(customSettings, _recognitionControl, _loadingService);
customStatistics.InitializeModule();
var testDate = new DateTime(2023, 5, 15, 10, 30, 0);
var sessionStartEvent = new SessionStartedEvent
{
RecipeName = "MyRecipe",
SessionStarted = new DateTime(2023, 5, 15, 10, 30, 0)
};
// Act
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
customStatistics.Dispose(); // Ensure file is written and closed
// Assert
var expectedPath = Path.Combine(_tempDirectory, "MyRecipe", testDate.ToString("yyyy"), testDate.ToString("MMMM"));
Assert.IsTrue(Directory.Exists(expectedPath), "Directory should be created with expanded variables");
var tmpFiles = Directory.GetFiles(expectedPath, "tmp_*.csv");
Assert.AreEqual(1, tmpFiles.Length, "Temporary file should be created in correct directory");
}
[TestMethod]
public void CustomColumnNames_UsedInHeaders()
{
// Arrange
var customSettings = new VisionBuilderStatisticsSettings("TestCamera")
{
PathTemplate = _tempDirectory,
StartDateColumnName = "Custom Start Date",
StartTimeColumnName = "Custom Start Time",
EndDateColumnName = "Custom End Date",
EndTimeColumnName = "Custom End Time",
ErrorTimeColumnName = "Custom Error Time",
ProductColumnName = "Custom Product",
ErrorColumnName = "Custom Error"
};
var customStatistics = new VisionBuilderStatistics(customSettings, _recognitionControl, _loadingService);
customStatistics.InitializeModule();
var sessionStartEvent = new SessionStartedEvent
{
RecipeName = "TestRecipe",
SessionStarted = DateTime.Now
};
// Act
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
customStatistics.Dispose();
// Assert
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
var lines = File.ReadAllLines(tmpFiles[0]);
var expectedHeaders = new[] { "Custom Start Date", "Custom Start Time", "Custom End Date", "Custom End Time", "Custom Error Time", "Custom Product", "Custom Error" };
var actualHeaders = lines[0].Split(',');
CollectionAssert.AreEqual(expectedHeaders, actualHeaders, "Custom column names should be used in headers");
}
[TestMethod]
public void WriteMonthStatistics_CreatesMonthlyFileWithDefectCounts()
{
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
statistics.InitializeModule();
// Arrange
var sessionStartEvent = new SessionStartedEvent
{
RecipeName = "TestRecipe",
SessionStarted = new DateTime(2023, 5, 15, 10, 30, 0)
};
var imageProcessedEvent1 = new ImageProcessedEvent
{
HasError = true,
ErrorNames = new List<string> { "DefectA" },
RecipeName = "TestRecipe",
SessionStart = new DateTime(2023, 5, 15, 10, 30, 0)
};
var imageProcessedEvent2 = new ImageProcessedEvent
{
HasError = true,
ErrorNames = new List<string> { "DefectB" },
RecipeName = "TestRecipe",
SessionStart = new DateTime(2023, 5, 15, 10, 30, 0)
};
var sessionEndEvent = new SessionEndedEvent
{
SessionEnded = new DateTime(2023, 5, 15, 11, 45, 0)
};
// Act
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
_recognitionControl.TriggerImageProcessed(imageProcessedEvent1);
_recognitionControl.TriggerImageProcessed(imageProcessedEvent1); // Same defect again
_recognitionControl.TriggerImageProcessed(imageProcessedEvent2);
_recognitionControl.TriggerSessionEnded(sessionEndEvent);
statistics.Dispose();
// Assert
var monthlyFile = Path.Combine(_tempDirectory, "statistics_month.csv");
Assert.IsTrue(File.Exists(monthlyFile), "Monthly statistics file should be created");
var monthlyLines = File.ReadAllLines(monthlyFile);
Assert.IsTrue(monthlyLines.Length >= 3, "Should have header and defect records");
var expectedHeaders = new[] { "Date start", "Time start", "Date end", "Time end", "Product", "Error type", "Error count" };
var actualHeaders = monthlyLines[0].Split(',');
CollectionAssert.AreEqual(expectedHeaders, actualHeaders, "Monthly file should have correct headers");
// Verify defect counts
var defectALine = monthlyLines.FirstOrDefault(l => l.Contains("DefectA"));
var defectBLine = monthlyLines.FirstOrDefault(l => l.Contains("DefectB"));
Assert.IsNotNull(defectALine, "DefectA should be recorded in monthly file");
Assert.IsNotNull(defectBLine, "DefectB should be recorded in monthly file");
var defectAData = defectALine.Split(',');
var defectBData = defectBLine.Split(',');
Assert.AreEqual("2", defectAData[6], "DefectA count should be 2");
Assert.AreEqual("1", defectBData[6], "DefectB count should be 1");
Assert.AreEqual("TestRecipe", defectAData[4], "Product name should be correct");
Assert.AreEqual("TestRecipe", defectBData[4], "Product name should be correct");
// Verify dates and times
Assert.IsTrue(DateTime.TryParse(defectAData[0], out var startDate), "Start date should be valid");
Assert.IsTrue(DateTime.TryParse(defectAData[1], out var startTime), "Start time should be valid");
Assert.IsTrue(DateTime.TryParse(defectAData[2], out var endDate), "End date should be valid");
Assert.IsTrue(DateTime.TryParse(defectAData[3], out var endTime), "End time should be valid");
}
}
// Test implementation of IRecognitionControl using reflection approach
public class TestRecognitionControl : IRecognitionControl
{
public void Resume()
{
}
public event Action<ImageProcessedEvent> ImageProcessed = delegate { };
public event Action<SessionStartedEvent> SessionStarted = delegate { };
public event Action<SessionEndedEvent> SessionEnded = delegate { };
public List<RecipeData> GetRecipesData() => new List<RecipeData>();
public void SetRecipe(RecipeData recipe) { }
public void Start() { }
public void Stop() { }
public void Pause()
{
}
public void TriggerImageProcessed(ImageProcessedEvent eventArgs) => ImageProcessed(eventArgs);
public void TriggerSessionStarted(SessionStartedEvent eventArgs) => SessionStarted(eventArgs);
public void TriggerSessionEnded(SessionEndedEvent eventArgs) => SessionEnded(eventArgs);
}
// Test implementation of ILoadingService
public class TestLoadingService : ILoadingService
{
public bool StartLoadingCalled { get; private set; }
public bool StopLoadingCalled { get; private set; }
public string LastLoadingMessage { get; private set; }
public void StartLoading(string message)
{
StartLoadingCalled = true;
LastLoadingMessage = message;
}
public void StopLoading(string message)
{
StopLoadingCalled = true;
LastLoadingMessage = message;
}
}