From b92bac6363e76ee9bc29485f8cded3f3de590a3f Mon Sep 17 00:00:00 2001
From: Michael Krisper <michael.krisper@tugraz.at>
Date: Tue, 12 May 2015 14:06:14 +0200
Subject: [PATCH] VECTO-101 moddata engine only fields, sum file corrected
 header, tests

---
 .../Models/Simulation/Data/ModalDataWriter.cs | 63 +++++++++++++-
 .../Simulation/Data/SummaryFileWriter.cs      | 50 +++++------
 .../Models/Simulation/Impl/JobContainer.cs    |  2 +-
 .../Simulation/Impl/SimulatorFactory.cs       | 51 +++++++-----
 VectoCore/Utils/VectoCSVFile.cs               | 28 +++----
 .../Models/Simulation/SimulationTests.cs      | 77 ++++-------------
 .../MockPorts.cs                              |  0
 VectoCoreTest/Utils/ResultFileHelper.cs       | 82 +++++++++++++++++++
 .../Simulation => Utils}/TestSumWriter.cs     |  0
 VectoCoreTest/VectoCoreTest.csproj            |  7 +-
 10 files changed, 232 insertions(+), 128 deletions(-)
 rename VectoCoreTest/{Models/SimulationComponent => Utils}/MockPorts.cs (100%)
 create mode 100644 VectoCoreTest/Utils/ResultFileHelper.cs
 rename VectoCoreTest/{Models/Simulation => Utils}/TestSumWriter.cs (100%)

diff --git a/VectoCore/Models/Simulation/Data/ModalDataWriter.cs b/VectoCore/Models/Simulation/Data/ModalDataWriter.cs
index 56ad553c78..86b3fdda83 100644
--- a/VectoCore/Models/Simulation/Data/ModalDataWriter.cs
+++ b/VectoCore/Models/Simulation/Data/ModalDataWriter.cs
@@ -1,22 +1,24 @@
-using System.Collections.Generic;
-using System.Data;
+using System.Data;
 using System.Linq;
+using System.Collections.Generic;
 using TUGraz.VectoCore.Utils;
 
 namespace TUGraz.VectoCore.Models.Simulation.Data
 {
 	public class ModalDataWriter : IModalDataWriter
 	{
+		private readonly bool _engineOnly;
 		private ModalResults Data { get; set; }
 		private DataRow CurrentRow { get; set; }
 		private string ModFileName { get; set; }
 
 
-		public ModalDataWriter(string modFileName)
+		public ModalDataWriter(string modFileName, bool engineOnly)
 		{
 			ModFileName = modFileName;
 			Data = new ModalResults();
 			CurrentRow = Data.NewRow();
+			_engineOnly = engineOnly;
 		}
 
 		public void CommitSimulationStep()
@@ -27,9 +29,62 @@ namespace TUGraz.VectoCore.Models.Simulation.Data
 
 		public void Finish()
 		{
-			VectoCSVFile.Write(ModFileName, Data);
+			var dataColumns = new List<ModalResultField> { ModalResultField.time };
+
+			if (!_engineOnly) {
+				dataColumns.AddRange(new[] {
+					ModalResultField.time,
+					ModalResultField.dist,
+					ModalResultField.v_act,
+					ModalResultField.v_targ,
+					ModalResultField.acc,
+					ModalResultField.grad
+				});
+			}
+
+			dataColumns.AddRange(new[] {
+				ModalResultField.n,
+				ModalResultField.Tq_eng,
+				ModalResultField.Tq_clutch,
+				ModalResultField.Tq_full,
+				ModalResultField.Tq_drag,
+				ModalResultField.Pe_eng,
+				ModalResultField.Pe_full,
+				ModalResultField.Pe_drag,
+				ModalResultField.Pe_clutch,
+				ModalResultField.PaEng,
+				ModalResultField.Paux
+			});
+
+			if (!_engineOnly) {
+				dataColumns.AddRange(new[] {
+					ModalResultField.Gear,
+					ModalResultField.PlossGB,
+					ModalResultField.PlossDiff,
+					ModalResultField.PlossRetarder,
+					ModalResultField.PaGB,
+					ModalResultField.PaVeh,
+					ModalResultField.Proll,
+					ModalResultField.Pair,
+					ModalResultField.Pgrad,
+					ModalResultField.Pwheel,
+					ModalResultField.Pbrake
+				});
+
+				//todo only add if (GBX.TCon)
+				dataColumns.AddRange(new[] {
+					ModalResultField.TCν,
+					ModalResultField.TCmu,
+					ModalResultField.TC_M_Out,
+					ModalResultField.TC_n_Out
+				});
+
+				//todo: auxiliaries
+			}
+			VectoCSVFile.Write(ModFileName, new DataView(Data).ToTable(false, dataColumns.Select(x => x.GetName()).ToArray()));
 		}
 
+
 		public object Compute(string expression, string filter)
 		{
 			return Data.Compute(expression, filter);
diff --git a/VectoCore/Models/Simulation/Data/SummaryFileWriter.cs b/VectoCore/Models/Simulation/Data/SummaryFileWriter.cs
index cc0bcde5e2..1ec8730c7c 100644
--- a/VectoCore/Models/Simulation/Data/SummaryFileWriter.cs
+++ b/VectoCore/Models/Simulation/Data/SummaryFileWriter.cs
@@ -15,26 +15,26 @@ namespace TUGraz.VectoCore.Models.Simulation.Data
 		private const string JOB = "Job [-]";
 		private const string INPUTFILE = "Input File [-]";
 		private const string CYCLE = "Cycle [-]";
-		private const string TIME = "Time [s]";
+		private const string TIME = "time [s]";
 		private const string DISTANCE = "distance [km]";
 		private const string SPEED = "speed [km/h]";
 		private const string ALTITUDE = "∆altitude [m]";
-		private const string PPOS = "Ppos [kw]";
-		private const string PNEG = "Pneg [kw]";
-		private const string FC = "FC [g/km]";
-		private const string FCAUXC = "FC-AUXc [g/km]";
-		private const string FCWHTCC = "FC-WHTCc [g/km]";
-		private const string PBRAKE = "Pbrake [kw]";
-		private const string EPOSICE = "EposICE [kwh]";
-		private const string ENEGICE = "EnegICE [kwh]";
-		private const string EAIR = "Eair [kwh]";
-		private const string EROLL = "Eroll [kwh]";
-		private const string EGRAD = "Egrad [kwh]";
-		private const string EACC = "Eacc [kwh]";
-		private const string EAUX = "Eaux [kwh]";
-		private const string EBRAKE = "Ebrake [kwh]";
-		private const string ETRANSM = "Etransm [kwh]";
-		private const string ERETARDER = "Eretarder [kwh]";
+		private const string PPOS = "Ppos [kW]";
+		private const string PNEG = "Pneg [kW]";
+		private const string FC = "FC [g/h]";
+		private const string FCAUXC = "FC-AUXc [g/h]";
+		private const string FCWHTCC = "FC-WHTCc [g/h]";
+		private const string PBRAKE = "Pbrake [kW]";
+		private const string EPOSICE = "EposICE [kWh]";
+		private const string ENEGICE = "EnegICE [kWh]";
+		private const string EAIR = "Eair [kWh]";
+		private const string EROLL = "Eroll [kWh]";
+		private const string EGRAD = "Egrad [kWh]";
+		private const string EACC = "Eacc [kWh]";
+		private const string EAUX = "Eaux [kWh]";
+		private const string EBRAKE = "Ebrake [kWh]";
+		private const string ETRANSM = "Etransm [kWh]";
+		private const string ERETARDER = "Eretarder [kWh]";
 		private const string MASS = "Mass [kg]";
 		private const string LOADING = "Loading [kg]";
 		private const string A = "a [m/s2]";
@@ -233,10 +233,10 @@ namespace TUGraz.VectoCore.Models.Simulation.Data
 /// </summary>
 public class SumWriterDecoratorFullPowertrain : SummaryFileWriter, ISummaryDataWriter
 {
-	protected SummaryFileWriter _writer;
-	protected string _jobFileName;
-	protected string _jobName;
-	protected string _cycleFileName;
+	private readonly SummaryFileWriter _writer;
+	private readonly string _jobFileName;
+	private readonly string _jobName;
+	private readonly string _cycleFileName;
 
 	public SumWriterDecoratorFullPowertrain(SummaryFileWriter writer, string jobFileName, string jobName,
 		string cycleFileName)
@@ -259,10 +259,10 @@ public class SumWriterDecoratorFullPowertrain : SummaryFileWriter, ISummaryDataW
 /// </summary>
 public class SumWriterDecoratorEngineOnly : SummaryFileWriter, ISummaryDataWriter
 {
-	protected SummaryFileWriter _writer;
-	protected string _jobFileName;
-	protected string _jobName;
-	protected string _cycleFileName;
+	private readonly SummaryFileWriter _writer;
+	private readonly string _jobFileName;
+	private readonly string _jobName;
+	private readonly string _cycleFileName;
 
 	public SumWriterDecoratorEngineOnly(SummaryFileWriter writer, string jobFileName, string jobName, string cycleFileName)
 	{
diff --git a/VectoCore/Models/Simulation/Impl/JobContainer.cs b/VectoCore/Models/Simulation/Impl/JobContainer.cs
index 231bf70250..93b3a56df7 100644
--- a/VectoCore/Models/Simulation/Impl/JobContainer.cs
+++ b/VectoCore/Models/Simulation/Impl/JobContainer.cs
@@ -38,8 +38,8 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
 		{
 			var sumFileName = Path.GetFileNameWithoutExtension(data.JobFileName);
 			var sumFilePath = Path.GetDirectoryName(data.JobFileName);
-
 			_sumWriter = new SummaryFileWriter(string.Format("{0}.vsum", Path.Combine(sumFilePath, sumFileName)));
+
 			AddJobs(data);
 		}
 
diff --git a/VectoCore/Models/Simulation/Impl/SimulatorFactory.cs b/VectoCore/Models/Simulation/Impl/SimulatorFactory.cs
index 15fb6d027d..5015f3f24d 100644
--- a/VectoCore/Models/Simulation/Impl/SimulatorFactory.cs
+++ b/VectoCore/Models/Simulation/Impl/SimulatorFactory.cs
@@ -1,5 +1,5 @@
-using System.Collections.Generic;
 using System.IO;
+using System.Collections.Generic;
 using TUGraz.VectoCore.Models.Simulation.Data;
 using TUGraz.VectoCore.Models.SimulationComponent;
 using TUGraz.VectoCore.Models.SimulationComponent.Data;
@@ -15,16 +15,14 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
 		/// Creates a simulation job for time based engine only powertrain.
 		/// </summary>
 		public static IVectoSimulator CreateTimeBasedEngineOnlyJob(string engineFile, string cycleFile, string jobFileName,
-			string jobName,
-			IModalDataWriter dataWriter, SummaryFileWriter sumWriter)
+			string jobName, IModalDataWriter dataWriter, SummaryFileWriter sumWriter)
 		{
-			var builder = new SimulatorBuilder(dataWriter,
-				new SumWriterDecoratorEngineOnly(sumWriter, jobFileName, jobName, cycleFile), engineOnly: true);
+			var sumWriterDecorator = new SumWriterDecoratorEngineOnly(sumWriter, jobFileName, jobName, cycleFile);
+			var builder = new SimulatorBuilder(dataWriter, sumWriterDecorator, engineOnly: true);
 
 			builder.AddEngine(engineFile);
 
-			var simulator = builder.Build(cycleFile);
-			return simulator;
+			return builder.Build(cycleFile);
 		}
 
 		/// <summary>
@@ -41,14 +39,10 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
 				var jobName = string.Format("{0}-{1}", jobNumber, i);
 				var modFileName = string.Format("{0}_{1}.vmod", Path.GetFileNameWithoutExtension(data.JobFileName),
 					Path.GetFileNameWithoutExtension(cycleFile));
-				_dataWriter = new ModalDataWriter(modFileName);
 
-				ISummaryDataWriter sumWriterDecorator;
-				if (data.IsEngineOnly) {
-					sumWriterDecorator = new SumWriterDecoratorEngineOnly(sumWriter, data.JobFileName, jobName, cycleFile);
-				} else {
-					sumWriterDecorator = new SumWriterDecoratorFullPowertrain(sumWriter, data.JobFileName, jobName, cycleFile);
-				}
+				_dataWriter = new ModalDataWriter(modFileName, data.IsEngineOnly);
+
+				var sumWriterDecorator = DecorateSumWriter(data.IsEngineOnly, sumWriter, data.JobFileName, jobName, cycleFile);
 				var builder = new SimulatorBuilder(_dataWriter, sumWriterDecorator, data.IsEngineOnly);
 
 				builder.AddEngine(data.EngineFile);
@@ -57,21 +51,38 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
 					builder.AddVehicle(data.VehicleFile);
 					builder.AddGearbox(data.GearboxFile);
 
-
 					foreach (var aux in data.Aux) {
 						builder.AddAuxiliary(aux.Path, aux.ID);
 					}
 
-					builder.AddDriver(data.StartStop, data.OverSpeedEcoRoll, data.LookAheadCoasting,
-						data.AccelerationLimitingFile);
+					builder.AddDriver(data.StartStop, data.OverSpeedEcoRoll, data.LookAheadCoasting, data.AccelerationLimitingFile);
 				}
+				yield return builder.Build(cycleFile);
+			}
+		}
 
-				var job = builder.Build(cycleFile);
-
-				yield return job;
+		/// <summary>
+		/// Decorates the sum writer with a correct decorator (either EngineOnly or FullPowertrain).
+		/// </summary>
+		/// <param name="engineOnly">if set to <c>true</c> [engine only].</param>
+		/// <param name="sumWriter">The sum writer.</param>
+		/// <param name="jobFileName">Name of the job file.</param>
+		/// <param name="jobName">Name of the job.</param>
+		/// <param name="cycleFile">The cycle file.</param>
+		/// <returns></returns>
+		private static ISummaryDataWriter DecorateSumWriter(bool engineOnly, SummaryFileWriter sumWriter,
+			string jobFileName, string jobName, string cycleFile)
+		{
+			if (engineOnly) {
+				return new SumWriterDecoratorEngineOnly(sumWriter, jobFileName, jobName, cycleFile);
 			}
+
+			return new SumWriterDecoratorFullPowertrain(sumWriter, jobFileName, jobName, cycleFile);
 		}
 
+		/// <summary>
+		/// Provides Methods to build a simulator with a powertrain step by step.
+		/// </summary>
 		public class SimulatorBuilder
 		{
 			private readonly bool _engineOnly;
diff --git a/VectoCore/Utils/VectoCSVFile.cs b/VectoCore/Utils/VectoCSVFile.cs
index c230e3756c..ee2d56b886 100644
--- a/VectoCore/Utils/VectoCSVFile.cs
+++ b/VectoCore/Utils/VectoCSVFile.cs
@@ -17,7 +17,7 @@ namespace TUGraz.VectoCore.Utils
 	/// </summary>
 	/// <remarks>
 	///     The following format applies to all CSV (Comma-separated values) Input Files used in VECTO:
-	///     List Separator: Comma ","
+	///     List DELIMITER: Comma ","
 	///     Decimal-Mark: Dot "."
 	///     Comments: "#" at the beginning of the comment line. Number and position of comment lines is not limited.
 	///     Header: One header line (not a comment line) at the beginning of the file.
@@ -27,7 +27,7 @@ namespace TUGraz.VectoCore.Utils
 	/// </remarks>
 	public static class VectoCSVFile
 	{
-		private const char Separator = ',';
+		private const char Delimiter = ',';
 		private const char Comment = '#';
 
 		/// <summary>
@@ -48,7 +48,7 @@ namespace TUGraz.VectoCore.Utils
 					// Valid Columns found => header was valid => skip header line
 					lines = lines.Skip(1).ToArray();
 				} else {
-					var log = LogManager.GetLogger(typeof (VectoCSVFile));
+					var log = LogManager.GetLogger(typeof(VectoCSVFile));
 					log.Warn("No valid Data Header found. Interpreting the first line as data line.");
 					// set the validColumns to: {"0", "1", "2", "3", ...} for all columns in first line.
 					validColumns = GetColumns(lines.First()).Select((_, index) => index.ToString()).ToArray();
@@ -62,13 +62,13 @@ namespace TUGraz.VectoCore.Utils
 				for (var i = 0; i < lines.Length; i++) {
 					var line = lines[i];
 
-					var cells = line.Split(Separator);
+					var cells = line.Split(Delimiter);
 					if (!ignoreEmptyColumns && cells.Length != table.Columns.Count) {
 						throw new CSVReadException(string.Format("Line {0}: The number of values is not correct.", i));
 					}
 
 					try {
-						table.Rows.Add(line.Split(Separator));
+						table.Rows.Add(line.Split(Delimiter));
 					} catch (InvalidCastException e) {
 						throw new CSVReadException(
 							string.Format("Line {0}: The data format of a value is not correct. {1}", i, e.Message), e);
@@ -98,7 +98,7 @@ namespace TUGraz.VectoCore.Utils
 			line = Regex.Replace(line, @"\(.*?\)", "");
 			line = line.Replace("<", "");
 			line = line.Replace(">", "");
-			return line.Split(Separator).Select(col => col.Trim());
+			return line.Split(Delimiter).Select(col => col.Trim());
 		}
 
 		private static string[] RemoveComments(string[] lines)
@@ -112,18 +112,18 @@ namespace TUGraz.VectoCore.Utils
 			return lines;
 		}
 
-        /// <summary>
-        /// Writes the datatable to the csv file.
-        /// Uses the column caption as header (with fallback to column name) for the csv header.
-        /// </summary>
-        /// <param name="fileName">Path to the file.</param>
-        /// <param name="table">The Datatable.</param>
+		/// <summary>
+		/// Writes the datatable to the csv file.
+		/// Uses the column caption as header (with fallback to column name) for the csv header.
+		/// </summary>
+		/// <param name="fileName">Path to the file.</param>
+		/// <param name="table">The Datatable.</param>
 		public static void Write(string fileName, DataTable table)
 		{
 			var sb = new StringBuilder();
 
 			var header = table.Columns.Cast<DataColumn>().Select(col => col.Caption ?? col.ColumnName);
-			sb.AppendLine(string.Join(", ", header));
+			sb.AppendLine(string.Join(Delimiter.ToString(), header));
 
 			foreach (DataRow row in table.Rows) {
 				var formattedList = new List<string>();
@@ -134,7 +134,7 @@ namespace TUGraz.VectoCore.Utils
 						: item.ToString();
 					formattedList.Add(formattedValue);
 				}
-				sb.AppendLine(string.Join(Separator.ToString(), formattedList));
+				sb.AppendLine(string.Join(Delimiter.ToString(), formattedList));
 			}
 
 			File.WriteAllText(fileName, sb.ToString());
diff --git a/VectoCoreTest/Models/Simulation/SimulationTests.cs b/VectoCoreTest/Models/Simulation/SimulationTests.cs
index b8abaec841..16ac702ffd 100644
--- a/VectoCoreTest/Models/Simulation/SimulationTests.cs
+++ b/VectoCoreTest/Models/Simulation/SimulationTests.cs
@@ -1,11 +1,13 @@
 using System.IO;
 using System.Linq;
+using System.Security.Cryptography.X509Certificates;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using TUGraz.VectoCore.Exceptions;
 using TUGraz.VectoCore.Models.Simulation;
 using TUGraz.VectoCore.Models.Simulation.Data;
 using TUGraz.VectoCore.Models.Simulation.Impl;
 using TUGraz.VectoCore.Utils;
+using TUGraz.VectoCore.Tests.Utils;
 
 namespace TUGraz.VectoCore.Tests.Models.Simulation
 {
@@ -27,53 +29,38 @@ namespace TUGraz.VectoCore.Tests.Models.Simulation
 			Assert.AreEqual(0U, container.Gear());
 		}
 
-
 		[TestMethod]
 		public void TestEngineOnly_JobRun()
 		{
-			var resultFileName = "TestEngineOnly_JobRun-result.vmod";
-			var expectedResultsName = @"TestData\Results\EngineOnlyCycles\24tCoach_EngineOnly short.vmod";
+			var actual = "TestEngineOnly_JobRun-result.vmod";
+			var expected = @"TestData\Results\EngineOnlyCycles\24tCoach_EngineOnly short.vmod";
 
-			var job = CreateJob(resultFileName);
+			var job = CreateJob(actual);
 			job.Run();
 
-			var results = ModalResults.ReadFromFile(resultFileName);
-			var expectedResults = ModalResults.ReadFromFile(expectedResultsName);
-
-			Assert.AreEqual(expectedResults.Rows.Count, results.Rows.Count, "Moddata: Row count differs.");
+			ResultFileHelper.TestModFile(expected, actual);
 		}
 
 		[TestMethod]
 		public void TestEngineOnly_SimulatorRun()
 		{
-			var resultFileName = "TestEngineOnly_SimulatorRun-result.vmod";
-			var expectedResultsName = @"TestData\Results\EngineOnlyCycles\24tCoach_EngineOnly short.vmod";
+			var actual = @"TestEngineOnly_SimulatorRun-result.vmod";
+			var expected = @"TestData\Results\EngineOnlyCycles\24tCoach_EngineOnly short.vmod";
 
-			var job = CreateJob(resultFileName);
+			var job = CreateJob(actual);
 
 			var sim = new JobContainer(new TestSumWriter());
 			sim.AddJob(job);
 			sim.RunJobs();
 
-			var results = ModalResults.ReadFromFile(resultFileName);
-			var expectedResults = ModalResults.ReadFromFile(expectedResultsName);
-
-			Assert.AreEqual(expectedResults.Rows.Count, results.Rows.Count, "Moddata: Row count differs.");
+			ResultFileHelper.TestModFile(expected, actual);
 		}
 
 		public IVectoSimulator CreateJob(string resultFileName)
 		{
 			var sumFileName = resultFileName.Substring(0, resultFileName.Length - 4) + "vsum";
 
-			if (File.Exists(resultFileName)) {
-				File.Delete(resultFileName);
-			}
-
-			if (File.Exists(sumFileName)) {
-				File.Delete(sumFileName);
-			}
-
-			var dataWriter = new ModalDataWriter(resultFileName);
+			var dataWriter = new ModalDataWriter(resultFileName, engineOnly: true);
 			var sumWriter = new SummaryFileWriter(sumFileName);
 			var job = SimulatorFactory.CreateTimeBasedEngineOnlyJob(EngineFile, CycleFile, jobFileName: "", jobName: "",
 				dataWriter: dataWriter, sumWriter: sumWriter);
@@ -90,62 +77,30 @@ namespace TUGraz.VectoCore.Tests.Models.Simulation
 				@"TestEngineOnly-MultipleJobs-result2",
 				@"TestEngineOnly-MultipleJobs-result3"
 			};
-			var expectedResultsName = @"TestData\Results\EngineOnlyCycles\24tCoach_EngineOnly short.vmod";
-			var expectedResults = ModalResults.ReadFromFile(expectedResultsName);
 
 			var simulation = new JobContainer(new TestSumWriter());
 			foreach (var resultFile in resultFiles) {
 				simulation.AddJob(CreateJob(resultFile));
 			}
-
-			resultFiles = resultFiles.Select(x => x + "_Coach Engine Only short.vmod").ToArray();
-
 			simulation.RunJobs();
 
-			foreach (var resultFile in resultFiles) {
-				var results = ModalResults.ReadFromFile(resultFile);
-				Assert.AreEqual(expectedResults.Rows.Count, results.Rows.Count, "Moddata: Row count differs.");
-			}
+			ResultFileHelper.TestModFiles(resultFiles.Select(x => x + "_Coach Engine Only short.vmod"),
+				Enumerable.Repeat(@"TestData\Results\EngineOnlyCycles\24tCoach_EngineOnly short.vmod", resultFiles.Length));
 		}
 
 		[TestMethod]
 		public void Test_VectoJob()
 		{
-			//run jobs
 			var jobData = VectoJobData.ReadFromFile(@"TestData\Jobs\24t Coach.vecto");
 			var jobContainer = new JobContainer(jobData);
 			jobContainer.RunJobs();
 
-
-			// check sum file
-			var expectedSumFile = @"TestData\Results\EngineOnlyCycles\24t Coach.vsum";
-			var sumFile = @"TestData\Jobs\24t Coach.vsum";
-			Assert.IsTrue(File.Exists(sumFile), "sum file is missing: " + sumFile);
-			Assert.AreEqual(File.ReadAllLines(sumFile).Length, File.ReadAllLines(expectedSumFile).Length,
-				string.Format("sum file row count differs. Expected File: {0}, Actual File: {1}", expectedSumFile, sumFile));
-
-			// check vmod files
-			var expectedResultFiles = new[] {
+			ResultFileHelper.TestSumFile(@"TestData\Results\EngineOnlyCycles\24t Coach.vsum", @"TestData\Jobs\24t Coach.vsum");
+			ResultFileHelper.TestModFiles(new[] {
 				@"TestData\Results\EngineOnlyCycles\24t Coach_Engine Only1.vmod",
 				@"TestData\Results\EngineOnlyCycles\24t Coach_Engine Only2.vmod",
 				@"TestData\Results\EngineOnlyCycles\24t Coach_Engine Only3.vmod"
-			};
-			var resultFiles = expectedResultFiles.Select(x => Path.GetFileName(x));
-			foreach (var result in resultFiles) {
-				Assert.IsTrue(File.Exists(result), "vmod file is missing: " + result);
-			}
-
-			var resultFileIt = resultFiles.GetEnumerator();
-
-			foreach (var expectedResultFile in expectedResultFiles) {
-				resultFileIt.MoveNext();
-				var results = ModalResults.ReadFromFile(resultFileIt.Current);
-				var expectedResults = ModalResults.ReadFromFile(expectedResultFile);
-
-				Assert.AreEqual(expectedResults.Rows.Count, results.Rows.Count,
-					string.Format("Moddata: Row count differs. Expected File: {0}, Actual File: {1}", expectedResultFile,
-						resultFileIt.Current));
-			}
+			}, new[] { "24t Coach_Engine Only1.vmod", "24t Coach_Engine Only2.vmod", "24t Coach_Engine Only3.vmod" });
 		}
 	}
 }
\ No newline at end of file
diff --git a/VectoCoreTest/Models/SimulationComponent/MockPorts.cs b/VectoCoreTest/Utils/MockPorts.cs
similarity index 100%
rename from VectoCoreTest/Models/SimulationComponent/MockPorts.cs
rename to VectoCoreTest/Utils/MockPorts.cs
diff --git a/VectoCoreTest/Utils/ResultFileHelper.cs b/VectoCoreTest/Utils/ResultFileHelper.cs
new file mode 100644
index 0000000000..57d4aba48e
--- /dev/null
+++ b/VectoCoreTest/Utils/ResultFileHelper.cs
@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Data;
+using System.Linq;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using TUGraz.VectoCore.Models.Simulation.Data;
+
+namespace TUGraz.VectoCore.Tests.Utils
+{
+	public static class ResultFileHelper
+	{
+		public static void TestModFile(string expectedFile, string actualFile)
+		{
+			TestModFiles(new[] { expectedFile }, new[] { actualFile });
+		}
+
+		public static IEnumerable<TResult> ZipAll<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first,
+			IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
+		{
+			var firstEnum = first.GetEnumerator();
+			var secondEnum = second.GetEnumerator();
+			while (true) {
+				var firstHadNext = firstEnum.MoveNext();
+				var secondHadNext = secondEnum.MoveNext();
+				if (firstHadNext && secondHadNext) {
+					yield return resultSelector(firstEnum.Current, secondEnum.Current);
+				} else if (firstHadNext != secondHadNext) {
+					throw new IndexOutOfRangeException("The argument enumerables must have the same length.");
+				} else {
+					yield break;
+				}
+			}
+		}
+
+		public static void TestModFiles(IEnumerable<string> expectedFiles, IEnumerable<string> actualFiles)
+		{
+			var resultFiles = expectedFiles.ZipAll(actualFiles, (expectedFile, actualFile) => new { expectedFile, actualFile });
+			foreach (var result in resultFiles) {
+				Assert.IsTrue(File.Exists(result.actualFile), "MOD File is missing: " + result);
+
+				var expected = ModalResults.ReadFromFile(result.expectedFile);
+				var actual = ModalResults.ReadFromFile(result.actualFile);
+
+				Assert.AreEqual(expected.Rows.Count, actual.Rows.Count,
+					string.Format("Moddata: Row count differs.\nExpected {0} Rows in {1}\nGot {2} Rows in {3}", expected.Rows.Count,
+						result.expectedFile, actual.Rows.Count, result.actualFile));
+
+				Assert.AreEqual(expected.Columns.Count, actual.Columns.Count,
+					string.Format("Moddata: Columns count differs.\nExpected {0} Columns in {1}\nGot {2} Columns in {3}",
+						expected.Columns.Count,
+						result.expectedFile, actual.Columns.Count, result.actualFile));
+
+				var actualCols = actual.Columns.Cast<DataColumn>().Select(x => x.ColumnName).ToList();
+				var expectedCols = expected.Columns.Cast<DataColumn>().Select(x => x.ColumnName).ToList();
+
+				Assert.IsTrue(expectedCols.SequenceEqual(actualCols),
+					string.Format("Moddata: Columns differ:\nExpected: {0}\nActual: {1}", string.Join(", ", expectedCols),
+						string.Join(", ", actualCols)));
+
+				// todo: Test Contents of MOD File.
+			}
+		}
+
+		public static void TestSumFile(string expectedFile, string actualFile)
+		{
+			Assert.IsTrue(File.Exists(actualFile), "SUM File is missing: " + actualFile);
+
+			var expected = File.ReadAllLines(expectedFile);
+			var actual = File.ReadAllLines(actualFile);
+
+			Assert.AreEqual(expected.Length, actual.Length,
+				string.Format("SUM File row count differs.\nExpected {0} Rows in {1}\nGot {2} Rows in {3}", expected.Length,
+					expectedFile, actual.Length, actualFile));
+
+			Assert.AreEqual(expected.First(), actual.First(),
+				string.Format("SUM File Header differs:\nExpected: '{0}'\nActual  : '{1}'", expected.First(), actual.First()));
+
+			// todo: test contents of sum file
+		}
+	}
+}
\ No newline at end of file
diff --git a/VectoCoreTest/Models/Simulation/TestSumWriter.cs b/VectoCoreTest/Utils/TestSumWriter.cs
similarity index 100%
rename from VectoCoreTest/Models/Simulation/TestSumWriter.cs
rename to VectoCoreTest/Utils/TestSumWriter.cs
diff --git a/VectoCoreTest/VectoCoreTest.csproj b/VectoCoreTest/VectoCoreTest.csproj
index 8b687e5032..06dc4b36c2 100644
--- a/VectoCoreTest/VectoCoreTest.csproj
+++ b/VectoCoreTest/VectoCoreTest.csproj
@@ -78,9 +78,10 @@
     <Compile Include="Models\SimulationComponent\GearboxTest.cs" />
     <Compile Include="Models\SimulationComponent\RetarderTest.cs" />
     <Compile Include="Models\Simulation\DrivingCycleTests.cs" />
-    <Compile Include="Models\SimulationComponent\MockPorts.cs" />
+    <Compile Include="Utils\ResultFileHelper.cs" />
+    <Compile Include="Utils\MockPorts.cs" />
     <Compile Include="Models\Simulation\SimulationTests.cs" />
-    <Compile Include="Models\Simulation\TestSumWriter.cs" />
+    <Compile Include="Utils\TestSumWriter.cs" />
     <Compile Include="Models\Simulation\VechicleContainerTests.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\Settings.Designer.cs">
@@ -190,7 +191,7 @@
     <None Include="TestData\Cycles\Coach Engine Only.vdri">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-        <None Include="TestData\EngineFullLoadJumps.csv">
+    <None Include="TestData\EngineFullLoadJumps.csv">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     <None Include="TestData\Results\EngineFullLoadJumps\EngineFLJ_1000rpm_10Hz.csv">
-- 
GitLab