From 697d8236ee5836b1a2f7fac6031acf8e7867dafa Mon Sep 17 00:00:00 2001 From: Robert Hague Date: Sat, 25 Nov 2023 12:58:04 +0100 Subject: [PATCH 01/12] Convert acceptance tests to a test project --- AcceptanceTest/.editorconfig | 13 ++ AcceptanceTest/.gitignore | 1 - AcceptanceTest/ATRunner.cs | 64 ------ AcceptanceTest/AcceptanceTest.csproj | 21 +- AcceptanceTest/Comparator.rb | 72 ------- AcceptanceTest/ComparatorTestCase.rb | 37 ---- AcceptanceTest/Fix40.cs | 25 +++ AcceptanceTest/Fix41.cs | 25 +++ AcceptanceTest/Fix42.cs | 25 +++ AcceptanceTest/Fix43.cs | 25 +++ AcceptanceTest/Fix44.cs | 25 +++ AcceptanceTest/Fix44NoReset.cs | 25 +++ AcceptanceTest/Fix50.cs | 25 +++ AcceptanceTest/Fix50sp1.cs | 25 +++ AcceptanceTest/Fix50sp2.cs | 25 +++ AcceptanceTest/FixParser.rb | 53 ----- AcceptanceTest/FixParserTestCase.rb | 46 ---- AcceptanceTest/Misc.cs | 25 +++ AcceptanceTest/Reflector.cs | 232 +++++++++++++++++++++ AcceptanceTest/Reflector.rb | 161 -------------- AcceptanceTest/ReflectorClient.rb | 132 ------------ AcceptanceTest/ReflectorClientTestCase.rb | 55 ----- AcceptanceTest/ReflectorServer.rb | 107 ---------- AcceptanceTest/ReflectorServerTestCase.rb | 33 --- AcceptanceTest/ReflectorTestCase.rb | 208 ------------------ AcceptanceTest/Runner.cs | 114 ++++++++++ AcceptanceTest/Runner.rb | 146 ------------- AcceptanceTest/RunnerTestCase.rb | 21 -- AcceptanceTest/SocketServer.rb | 57 ----- AcceptanceTest/SocketServerTestCase.rb | 68 ------ AcceptanceTest/TestBase.cs | 48 +++++ AcceptanceTest/at.xsl | 62 ------ AcceptanceTest/at_xml_to_nunit_xml.rb | 123 ----------- AcceptanceTest/at_xml_to_nunit_xml_test.rb | 20 -- AcceptanceTest/cfg/at_40.cfg | 2 +- AcceptanceTest/cfg/at_41.cfg | 2 +- AcceptanceTest/cfg/at_42.cfg | 2 +- AcceptanceTest/cfg/at_42.misc.cfg | 2 +- AcceptanceTest/cfg/at_43.cfg | 2 +- AcceptanceTest/cfg/at_44.cfg | 2 +- AcceptanceTest/cfg/at_44_noreset.cfg | 2 +- AcceptanceTest/cfg/at_50.cfg | 4 +- AcceptanceTest/cfg/at_50_sp1.cfg | 4 +- AcceptanceTest/cfg/at_50_sp2.cfg | 4 +- AcceptanceTest/definitions/fields.fmt | 5 - AcceptanceTest/runat.ps1 | 164 --------------- AcceptanceTest/test.rb | 60 ------ QuickFIXn.sln | 60 +++--- README.md | 44 +--- scripts/Acceptance-Test.ps1 | 130 ------------ 50 files changed, 719 insertions(+), 1914 deletions(-) create mode 100644 AcceptanceTest/.editorconfig delete mode 100644 AcceptanceTest/.gitignore delete mode 100644 AcceptanceTest/ATRunner.cs delete mode 100644 AcceptanceTest/Comparator.rb delete mode 100644 AcceptanceTest/ComparatorTestCase.rb create mode 100644 AcceptanceTest/Fix40.cs create mode 100644 AcceptanceTest/Fix41.cs create mode 100644 AcceptanceTest/Fix42.cs create mode 100644 AcceptanceTest/Fix43.cs create mode 100644 AcceptanceTest/Fix44.cs create mode 100644 AcceptanceTest/Fix44NoReset.cs create mode 100644 AcceptanceTest/Fix50.cs create mode 100644 AcceptanceTest/Fix50sp1.cs create mode 100644 AcceptanceTest/Fix50sp2.cs delete mode 100644 AcceptanceTest/FixParser.rb delete mode 100644 AcceptanceTest/FixParserTestCase.rb create mode 100644 AcceptanceTest/Misc.cs create mode 100644 AcceptanceTest/Reflector.cs delete mode 100644 AcceptanceTest/Reflector.rb delete mode 100644 AcceptanceTest/ReflectorClient.rb delete mode 100644 AcceptanceTest/ReflectorClientTestCase.rb delete mode 100644 AcceptanceTest/ReflectorServer.rb delete mode 100644 AcceptanceTest/ReflectorServerTestCase.rb delete mode 100644 AcceptanceTest/ReflectorTestCase.rb create mode 100644 AcceptanceTest/Runner.cs delete mode 100644 AcceptanceTest/Runner.rb delete mode 100644 AcceptanceTest/RunnerTestCase.rb delete mode 100644 AcceptanceTest/SocketServer.rb delete mode 100644 AcceptanceTest/SocketServerTestCase.rb create mode 100644 AcceptanceTest/TestBase.cs delete mode 100755 AcceptanceTest/at.xsl delete mode 100644 AcceptanceTest/at_xml_to_nunit_xml.rb delete mode 100644 AcceptanceTest/at_xml_to_nunit_xml_test.rb delete mode 100644 AcceptanceTest/definitions/fields.fmt delete mode 100644 AcceptanceTest/runat.ps1 delete mode 100644 AcceptanceTest/test.rb delete mode 100644 scripts/Acceptance-Test.ps1 diff --git a/AcceptanceTest/.editorconfig b/AcceptanceTest/.editorconfig new file mode 100644 index 000000000..7882a5f48 --- /dev/null +++ b/AcceptanceTest/.editorconfig @@ -0,0 +1,13 @@ +[*.cs] + +# NUnit2003: Consider using Assert.That(expr, Is.True) instead of Assert.IsTrue(expr) +dotnet_diagnostic.NUnit2003.severity = silent + +# NUnit2005: Consider using Assert.That(actual, Is.EqualTo(expected)) instead of Assert.AreEqual(expected, actual) +dotnet_diagnostic.NUnit2005.severity = silent + +# NUnit2017: Consider using Assert.That(expr, Is.Null) instead of Assert.IsNull(expr) +dotnet_diagnostic.NUnit2017.severity = silent + +# NUnit2019: Consider using Assert.That(expr, Is.Not.Null) instead of Assert.IsNotNull(expr) +dotnet_diagnostic.NUnit2019.severity = silent diff --git a/AcceptanceTest/.gitignore b/AcceptanceTest/.gitignore deleted file mode 100644 index 80a91d558..000000000 --- a/AcceptanceTest/.gitignore +++ /dev/null @@ -1 +0,0 @@ -TestResults.xml diff --git a/AcceptanceTest/ATRunner.cs b/AcceptanceTest/ATRunner.cs deleted file mode 100644 index 57fdec20b..000000000 --- a/AcceptanceTest/ATRunner.cs +++ /dev/null @@ -1,64 +0,0 @@ -using QuickFix; -using System.Threading; - -namespace AcceptanceTest -{ - public class ATRunner - { - static bool _stopMe = false; - - static void Main(string[] args) - { - if (args.Length != 1) - { - System.Console.WriteLine("usage: AcceptanceTest CONFIG_FILENAME"); - System.Environment.Exit(2); - } - - FileLog debugLog = new FileLog("log", new SessionID("AT", "Application", "Debug")); - ThreadedSocketAcceptor acceptor = null; - try - { - ATApplication testApp = new ATApplication(debugLog); - testApp.StopMeEvent += new System.Action(delegate() { _stopMe = true; }); - - SessionSettings settings = new SessionSettings(args[0]); - IMessageStoreFactory storeFactory = new FileStoreFactory(settings); - ILogFactory logFactory = null; - if (settings.Get().Has("Verbose") && settings.Get().GetBool("Verbose")) - logFactory = new FileLogFactory(settings); - acceptor = new ThreadedSocketAcceptor(testApp, storeFactory, settings, logFactory); - - acceptor.Start(); - while (true) - { - System.Threading.Thread.Sleep(1000); - - // for tests of logout - if (_stopMe) - { - // this doesn't seem to work - // after stop, it doesn't seem to start up again - /* - acceptor.Stop(); - Thread.Sleep(5 * 1000); - _stopMe = false; - acceptor.Start(); - */ - } - } - } - catch (System.Exception e) - { - debugLog.OnEvent(e.ToString()); - } - - finally - { - if(acceptor != null) - acceptor.Stop(); - } - - } - } -} diff --git a/AcceptanceTest/AcceptanceTest.csproj b/AcceptanceTest/AcceptanceTest.csproj index d3578cb8a..12430facc 100644 --- a/AcceptanceTest/AcceptanceTest.csproj +++ b/AcceptanceTest/AcceptanceTest.csproj @@ -1,12 +1,19 @@ - + - Exe - net6.0 - AnyCPU;x64 + net6.0 false + true + + + + + + + + @@ -19,4 +26,10 @@ + + + + + + diff --git a/AcceptanceTest/Comparator.rb b/AcceptanceTest/Comparator.rb deleted file mode 100644 index c9710900f..000000000 --- a/AcceptanceTest/Comparator.rb +++ /dev/null @@ -1,72 +0,0 @@ -#**************************************************************************** -# Copyright (c) quickfixengine.org All rights reserved. -# -# This file is part of the QuickFIX FIX Engine -# -# This file may be distributed under the terms of the quickfixengine.org -# license as defined by quickfixengine.org and appearing in the file -# LICENSE included in the packaging of this file. -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -# -# See http://www.quickfixengine.org/LICENSE for licensing information. -# -# Contact ask@quickfixengine.org if any conditions of this licensing are -# not clear to you. -#**************************************************************************** - -class Comparator < Hash - - def initialize(patterns) - patterns.each_line do - | line | - line.chomp! - array = line.split("=") - num = array[0].to_i - regex = Regexp.new(array[1]) - self[num] = regex; - end - end - - def compare(left, right) - @reason = nil - left_array = left.split("\001") - right_array = right.split("\001") - # check for number of fields - if left_array.size != right_array.size - @reason = "Number of fields do not match" - return false - end - left_array.each_index do - | index | - left_field = left_array[index].split("=") - right_field = right_array[index].split("=") - # check if field is in same order - if left_field[0] != right_field[0] - @reason = "Expected field (" + left_field[0] + ") but found field (" + right_field[0] + ")" - return false - end - - regexp = self[left_field[0].to_i] - # do a straight comparison or regex comparison - if regexp == nil - if left_field[1] != right_field[1] - @reason = "Value in field (" + left_field[0] + ") should be (" + left_field[1] + ") but was (" + right_field[1] + ")" - return false - end - else - if !(regexp === right_field[1]) - @reason = "Field (" + left_field[0] + ") does not match pattern" - return false - end - end - end - return true - end - - def reason() - return @reason - end - -end diff --git a/AcceptanceTest/ComparatorTestCase.rb b/AcceptanceTest/ComparatorTestCase.rb deleted file mode 100644 index d60ba24b7..000000000 --- a/AcceptanceTest/ComparatorTestCase.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'Comparator' -require 'runit/testcase' - -class ComparatorTestCase < RUNIT::TestCase - - def test_parsePatterns - patterns = "10=\\d{1,3}\n52=\\d{8}-\\d2:\\d2:\\d2\n" - comp = Comparator.new(patterns) - assert_equals(/\d{1,3}/, comp[10]) - assert_equals(/\d{8}-\d2:\d2:\d2/, comp[52]) - end - - def test_compare - patterns = "10=\\d{1,3}\n52=\\d{8}-\\d{2}:\\d{2}:\\d{2}\n" - comp = Comparator.new(patterns) - # matching fields - assert(comp.compare("1=hello\0012=goodbye\001", "1=hello\0012=goodbye\001")) - assert(comp.reason == nil) - # non-matching field - assert(!comp.compare("1=helloo\0012=goodbye\001", "1=hello\0012=goodbye\001")) - assert(comp.reason == "Value in field (1) should be (helloo) but was (hello)") - # out of order fields - assert(!comp.compare("2=hello\0011=goodbye\001", "1=hello\0012=goodbye\001")) - assert(comp.reason == "Expected field (2) but found field (1)") - # different number of fields - assert(!comp.compare("1=hello\001", "1=hello\0012=goodbye\001")) - assert(comp.reason == "Number of fields do not match") - # mathing non-deterministic field - assert(comp.compare( - "1=hello\0012=goodbye\00152=12345678-12:23:34\001", "1=hello\0012=goodbye\00152=87654321-98:87:76\001")) - # non-matching non-deterministic field - assert(!comp.compare( - "1=hello\0012=goodbye\00152=12345678-12:23:34\001", "1=hello\0012=goodbye\00152=7654321-98:87:76\001")) - assert(comp.reason == "Field (52) does not match pattern") - end - -end diff --git a/AcceptanceTest/Fix40.cs b/AcceptanceTest/Fix40.cs new file mode 100644 index 000000000..47ffe67b1 --- /dev/null +++ b/AcceptanceTest/Fix40.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using QuickFix; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AcceptanceTest; + +public class Fix40 : TestBase +{ + private const string DefinitionsBaseDirPath = "definitions/server/fix40"; + + protected override SessionSettings Settings => new(@"cfg/at_40.cfg"); + + [TestCaseSource(nameof(Definitions))] + public void TestCase(string definitionFileName) + { + RunTest(Path.Combine(DefinitionsBaseDirPath, definitionFileName)); + } + + private static IEnumerable Definitions() + { + return Directory.EnumerateFiles(DefinitionsBaseDirPath, "*.def").Select(Path.GetFileName); + } +} \ No newline at end of file diff --git a/AcceptanceTest/Fix41.cs b/AcceptanceTest/Fix41.cs new file mode 100644 index 000000000..a1bf57aad --- /dev/null +++ b/AcceptanceTest/Fix41.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using QuickFix; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AcceptanceTest; + +public class Fix41 : TestBase +{ + private const string DefinitionsBaseDirPath = "definitions/server/fix41"; + + protected override SessionSettings Settings => new(@"cfg/at_41.cfg"); + + [TestCaseSource(nameof(Definitions))] + public void TestCase(string definitionFileName) + { + RunTest(Path.Combine(DefinitionsBaseDirPath, definitionFileName)); + } + + private static IEnumerable Definitions() + { + return Directory.EnumerateFiles(DefinitionsBaseDirPath, "*.def").Select(Path.GetFileName); + } +} \ No newline at end of file diff --git a/AcceptanceTest/Fix42.cs b/AcceptanceTest/Fix42.cs new file mode 100644 index 000000000..affffbda9 --- /dev/null +++ b/AcceptanceTest/Fix42.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using QuickFix; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AcceptanceTest; + +public class Fix42 : TestBase +{ + private const string DefinitionsBaseDirPath = "definitions/server/fix42"; + + protected override SessionSettings Settings => new(@"cfg/at_42.cfg"); + + [TestCaseSource(nameof(Definitions))] + public void TestCase(string definitionFileName) + { + RunTest(Path.Combine(DefinitionsBaseDirPath, definitionFileName)); + } + + private static IEnumerable Definitions() + { + return Directory.EnumerateFiles(DefinitionsBaseDirPath, "*.def").Select(Path.GetFileName); + } +} \ No newline at end of file diff --git a/AcceptanceTest/Fix43.cs b/AcceptanceTest/Fix43.cs new file mode 100644 index 000000000..0108696f5 --- /dev/null +++ b/AcceptanceTest/Fix43.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using QuickFix; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AcceptanceTest; + +public class Fix43 : TestBase +{ + private const string DefinitionsBaseDirPath = "definitions/server/fix43"; + + protected override SessionSettings Settings => new(@"cfg/at_43.cfg"); + + [TestCaseSource(nameof(Definitions))] + public void TestCase(string definitionFileName) + { + RunTest(Path.Combine(DefinitionsBaseDirPath, definitionFileName)); + } + + private static IEnumerable Definitions() + { + return Directory.EnumerateFiles(DefinitionsBaseDirPath, "*.def").Select(Path.GetFileName); + } +} \ No newline at end of file diff --git a/AcceptanceTest/Fix44.cs b/AcceptanceTest/Fix44.cs new file mode 100644 index 000000000..f2e2f9a91 --- /dev/null +++ b/AcceptanceTest/Fix44.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using QuickFix; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AcceptanceTest; + +public class Fix44 : TestBase +{ + private const string DefinitionsBaseDirPath = "definitions/server/fix44"; + + protected override SessionSettings Settings => new(@"cfg/at_44.cfg"); + + [TestCaseSource(nameof(Definitions))] + public void TestCase(string definitionFileName) + { + RunTest(Path.Combine(DefinitionsBaseDirPath, definitionFileName)); + } + + private static IEnumerable Definitions() + { + return Directory.EnumerateFiles(DefinitionsBaseDirPath, "*.def").Select(Path.GetFileName); + } +} \ No newline at end of file diff --git a/AcceptanceTest/Fix44NoReset.cs b/AcceptanceTest/Fix44NoReset.cs new file mode 100644 index 000000000..08928e072 --- /dev/null +++ b/AcceptanceTest/Fix44NoReset.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using QuickFix; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AcceptanceTest; + +public class Fix44NoReset : TestBase +{ + private const string DefinitionsBaseDirPath = "definitions/server/fix44noreset"; + + protected override SessionSettings Settings => new(@"cfg/at_44_noreset.cfg"); + + [TestCaseSource(nameof(Definitions))] + public void TestCase(string definitionFileName) + { + RunTest(Path.Combine(DefinitionsBaseDirPath, definitionFileName)); + } + + private static IEnumerable Definitions() + { + return Directory.EnumerateFiles(DefinitionsBaseDirPath, "*.def").Select(Path.GetFileName); + } +} \ No newline at end of file diff --git a/AcceptanceTest/Fix50.cs b/AcceptanceTest/Fix50.cs new file mode 100644 index 000000000..f8daf01cc --- /dev/null +++ b/AcceptanceTest/Fix50.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using QuickFix; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AcceptanceTest; + +public class Fix50 : TestBase +{ + private const string DefinitionsBaseDirPath = "definitions/server/fix50"; + + protected override SessionSettings Settings => new(@"cfg/at_50.cfg"); + + [TestCaseSource(nameof(Definitions))] + public void TestCase(string definitionFileName) + { + RunTest(Path.Combine(DefinitionsBaseDirPath, definitionFileName)); + } + + private static IEnumerable Definitions() + { + return Directory.EnumerateFiles(DefinitionsBaseDirPath, "*.def").Select(Path.GetFileName); + } +} \ No newline at end of file diff --git a/AcceptanceTest/Fix50sp1.cs b/AcceptanceTest/Fix50sp1.cs new file mode 100644 index 000000000..a2584bfc9 --- /dev/null +++ b/AcceptanceTest/Fix50sp1.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using QuickFix; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AcceptanceTest; + +public class Fix50sp1 : TestBase +{ + private const string DefinitionsBaseDirPath = "definitions/server/fix50sp1"; + + protected override SessionSettings Settings => new(@"cfg/at_50_sp1.cfg"); + + [TestCaseSource(nameof(Definitions))] + public void TestCase(string definitionFileName) + { + RunTest(Path.Combine(DefinitionsBaseDirPath, definitionFileName)); + } + + private static IEnumerable Definitions() + { + return Directory.EnumerateFiles(DefinitionsBaseDirPath, "*.def").Select(Path.GetFileName); + } +} \ No newline at end of file diff --git a/AcceptanceTest/Fix50sp2.cs b/AcceptanceTest/Fix50sp2.cs new file mode 100644 index 000000000..c9fa49dbf --- /dev/null +++ b/AcceptanceTest/Fix50sp2.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using QuickFix; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AcceptanceTest; + +public class Fix50sp2 : TestBase +{ + private const string DefinitionsBaseDirPath = "definitions/server/fix50sp2"; + + protected override SessionSettings Settings => new(@"cfg/at_50_sp2.cfg"); + + [TestCaseSource(nameof(Definitions))] + public void TestCase(string definitionFileName) + { + RunTest(Path.Combine(DefinitionsBaseDirPath, definitionFileName)); + } + + private static IEnumerable Definitions() + { + return Directory.EnumerateFiles(DefinitionsBaseDirPath, "*.def").Select(Path.GetFileName); + } +} \ No newline at end of file diff --git a/AcceptanceTest/FixParser.rb b/AcceptanceTest/FixParser.rb deleted file mode 100644 index c9e828b7c..000000000 --- a/AcceptanceTest/FixParser.rb +++ /dev/null @@ -1,53 +0,0 @@ -#**************************************************************************** -# Copyright (c) quickfixengine.org All rights reserved. -# -# This file is part of the QuickFIX FIX Engine -# -# This file may be distributed under the terms of the quickfixengine.org -# license as defined by quickfixengine.org and appearing in the file -# LICENSE included in the packaging of this file. -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -# -# See http://www.quickfixengine.org/LICENSE for licensing information. -# -# Contact ask@quickfixengine.org if any conditions of this licensing are -# not clear to you. -#**************************************************************************** - -require "socket" - -class FixParser - - def initialize(io) - @io = io - end - - def readFixMessage() - if(@io.eof?) - raise "Was disconnected, expected data" - end - - m = "" - # read to begining of MsgLen field - m = @io.gets("\0019=") - # read contents of MsgLen field - length = @io.gets("\001") - m += length - length.chop! - - # regex checks to make sure length is an integer - # if it isn't there is nothing we can do so - # close the connection - if( (/^\d*$/ === length) == nil ) - @io.close - end - # read body - m += @io.read(Integer(length)) - # read CheckSum - m += @io.gets("\001") - return m - end - -end diff --git a/AcceptanceTest/FixParserTestCase.rb b/AcceptanceTest/FixParserTestCase.rb deleted file mode 100644 index 84b15a8be..000000000 --- a/AcceptanceTest/FixParserTestCase.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'FixParser' -require 'runit/testcase' -require "thread" -require 'SocketServer' - -class FixParserTestCase < RUNIT::TestCase - - def test_readFixMessage - fixMsg1 = "8=FIX.4.2\0019=12\00135=A\001108=30\00110=31\001" - fixMsg2 = "8=FIX.4.2\0019=17\00135=4\00136=88\001123=Y\00110=34\001" - - server = SocketServer.new - def server.message=(m) - @message = m - end - - def server.connectAction(s) - end - - def server.receiveAction(s) - s.write(@message) - end - - def server.disconnectAction(s) - end - - server.message = fixMsg1 + fixMsg2 - Thread.start do - server.listen(RUNIT::TestCase.port) - end - server.wait - - s = TCPSocket.open("localhost", RUNIT::TestCase.port) - parser = FixParser.new(s) - begin - assert_equals(fixMsg1, parser.readFixMessage) - assert_equals(fixMsg2, parser.readFixMessage) - rescue IOError - # I have no idea why this is being thrown - end - - s.close - server.stop() - end - -end diff --git a/AcceptanceTest/Misc.cs b/AcceptanceTest/Misc.cs new file mode 100644 index 000000000..6ab77b96b --- /dev/null +++ b/AcceptanceTest/Misc.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using QuickFix; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AcceptanceTest; + +public class Misc : TestBase +{ + private const string DefinitionsBaseDirPath = "definitions/server/misc"; + + protected override SessionSettings Settings => new(@"cfg/at_42.misc.cfg"); + + [TestCaseSource(nameof(Definitions))] + public void TestCase(string definitionFileName) + { + RunTest(Path.Combine(DefinitionsBaseDirPath, definitionFileName)); + } + + private static IEnumerable Definitions() + { + return Directory.EnumerateFiles(DefinitionsBaseDirPath, "*.def").Select(Path.GetFileName); + } +} \ No newline at end of file diff --git a/AcceptanceTest/Reflector.cs b/AcceptanceTest/Reflector.cs new file mode 100644 index 000000000..5c2d0d22b --- /dev/null +++ b/AcceptanceTest/Reflector.cs @@ -0,0 +1,232 @@ +#nullable enable +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; + +namespace AcceptanceTest; + +internal interface IReflector : IDisposable +{ + void InitiateConnect(); + + void Initiate(string initiateMessage); + + void Expect(string expectedMessage); + + void InitiateDisconnect(); + + void ExpectDisconnect(); +} + +internal class ReflectorClient : IReflector +{ + private static readonly Regex s_timeRegex = new(@"\", RegexOptions.Compiled); + private static readonly Regex s_beginStringRegex = new(@"^8=.*?[\001]", RegexOptions.Compiled); + private static readonly Regex s_bodyLengthRegex = new(@"[\001]9=.*?[\001]", RegexOptions.Compiled); + private static readonly Regex s_checksumRegex = new(@"[\001]10=.*[\001]$", RegexOptions.Compiled); + private static readonly Dictionary s_expectPatterns = new() + { + { "10", new Regex(@"\d{3}", RegexOptions.Compiled) }, + { "42", new Regex(@"\d{8}-\d{2}:\d{2}:\d{2}", RegexOptions.Compiled) }, + { "52", new Regex(@"\d{8}-\d{2}:\d{2}:\d{2}|\d{8}-\d{2}:\d{2}:\d{2}[.]\d{3}", RegexOptions.Compiled) }, + { "60", new Regex(@"\d{8}-\d{2}:\d{2}:\d{2}", RegexOptions.Compiled) }, + { "122", new Regex(@"\d{8}-\d{2}:\d{2}:\d{2}", RegexOptions.Compiled) }, + }; + + private readonly IPEndPoint _endPoint; + private readonly QuickFix.Parser _parser; + private readonly byte[] _readBuffer; + + private Socket? _socket; + + + public ReflectorClient(IPEndPoint endPoint) + { + _endPoint = endPoint; + _parser = new QuickFix.Parser(); + _readBuffer = new byte[1024]; + } + + public void Expect(string expectedMessage) + { + Assert.IsNotNull(_socket); + + string actualMessage; + while (!_parser.ReadFixMessage(out actualMessage)) + { + int bytesReceived = _socket.Receive(_readBuffer); + + if (bytesReceived == 0) + { + Assert.Fail("Socket was shut down by remote host."); + } + + _parser.AddToStream(_readBuffer, bytesReceived); + } + + expectedMessage = Decorate(expectedMessage); + + string[] expectedFields = expectedMessage[..^1].Split('\u0001'); + string[] actualFields = actualMessage[..^1].Split('\u0001'); + + int mismatchIndex = 0; + + for (int fieldIndex = 0; fieldIndex < Math.Min(expectedFields.Length, actualFields.Length); fieldIndex++) + { + string[] expectedTagValue = expectedFields[fieldIndex].Split('=', 2); + string[] actualTagValue = actualFields[fieldIndex].Split('=', 2); + + if (expectedTagValue[0] != actualTagValue[0]) + { + AssertAtIndex(mismatchIndex); + } + + mismatchIndex += expectedTagValue[0].Length + 1; + + if (s_expectPatterns.TryGetValue(expectedTagValue[0], out Regex? valueRegex)) + { + if (!valueRegex.IsMatch(actualTagValue[1])) + { + AssertAtIndex(mismatchIndex); + } + } + else if (expectedTagValue[1] != actualTagValue[1]) + { + AssertAtIndex(mismatchIndex); + } + + mismatchIndex += actualTagValue[1].Length + 1; + } + + if (expectedFields.Length != actualFields.Length) + { + AssertAtIndex(mismatchIndex); + } + + void AssertAtIndex(int mismatchIndex) + { + Assert.Fail($@" +Expected: {expectedMessage} +But was: {actualMessage} + {new string(' ', mismatchIndex)}^"); + } + } + + public void Initiate(string initiateMessage) + { + string decoratedMessage = Decorate(initiateMessage); + Assert.IsNotNull(_socket); + _socket.Send(Encoding.Latin1.GetBytes(decoratedMessage)); + } + + /// + /// Fills in timestamps and adds missing BodyLength/Checksum fields. + /// + /// The decorated message. + private static string Decorate(string initiateMessage) + { + DateTime now = DateTime.UtcNow; + + initiateMessage = initiateMessage.Replace("