480 lines
19 KiB
C#
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.RecipeProcessing;
|
|
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;
|
|
}
|
|
} |