From 9fdf4bad9c8df0c59cf07a9d86772f3e1e725c9d Mon Sep 17 00:00:00 2001 From: RaidMax Date: Wed, 1 Apr 2020 14:11:56 -0500 Subject: [PATCH] fix for runaway regular expression on linux explicitly set string dvars in quotes to allow setting empty dvars allow piping in input from command line (#114) update the distribution for top stats elo prevent game log file rotation from stopping event parsing --- .gitignore | 1 + Application/Application.csproj | 4 +- Application/ApplicationManager.cs | 12 +-- Application/EventParsers/BaseEventParser.cs | 84 ++++++++++--------- .../EventParsers/DynamicEventParser.cs | 8 +- .../DynamicEventParserConfiguration.cs | 24 ++++-- .../EventParsers/ParserPatternMatcher.cs | 35 ++++++++ Application/Factories/ParserRegexFactory.cs | 26 ++++++ Application/IO/GameLogEventDetection.cs | 17 ++-- Application/IO/GameLogReader.cs | 2 +- Application/IO/GameLogReaderHttp.cs | 28 +++---- Application/IW4MServer.cs | 14 +++- Application/Main.cs | 12 +-- Application/Misc/ParserMatchResult.cs | 21 +++++ Application/RconParsers/BaseRConParser.cs | 15 ++-- Application/RconParsers/DynamicRConParser.cs | 7 +- .../DynamicRConParserConfiguration.cs | 16 +++- Plugins/Tests/ManagerFixture.cs | 2 +- Plugins/Tests/TestRconParser.cs | 5 ++ .../Stats/Components/TopPlayers/_List.cshtml | 2 +- SharedLibraryCore/Helpers/ParserRegex.cs | 17 +++- .../Interfaces/IEventParserConfiguration.cs | 5 ++ .../Interfaces/IGameLogReader.cs | 8 +- SharedLibraryCore/Interfaces/IMatchResult.cs | 18 ++++ .../Interfaces/IParserPatternMatcher.cs | 21 +++++ .../Interfaces/IParserRegexFactory.cs | 14 ++++ SharedLibraryCore/SharedLibraryCore.csproj | 26 +++--- .../ApplicationTests/ApplicationTests.csproj | 3 + .../ApplicationTests/BaseEventParserTests.cs | 58 +++++++++++++ Tests/ApplicationTests/BaseRConParserTests.cs | 47 +++++++++++ .../ApplicationTests/Fixtures/EventLogTest.cs | 26 ++++++ Tests/ApplicationTests/IOTests.cs | 42 ++++++++++ Tests/ApplicationTests/ServerTests.cs | 4 +- Tests/ApplicationTests/StatsTests.cs | 2 +- WebfrontCore/WebfrontCore.csproj | 2 +- 35 files changed, 504 insertions(+), 124 deletions(-) create mode 100644 Application/EventParsers/ParserPatternMatcher.cs create mode 100644 Application/Factories/ParserRegexFactory.cs create mode 100644 Application/Misc/ParserMatchResult.cs create mode 100644 SharedLibraryCore/Interfaces/IMatchResult.cs create mode 100644 SharedLibraryCore/Interfaces/IParserPatternMatcher.cs create mode 100644 SharedLibraryCore/Interfaces/IParserRegexFactory.cs create mode 100644 Tests/ApplicationTests/BaseEventParserTests.cs create mode 100644 Tests/ApplicationTests/BaseRConParserTests.cs create mode 100644 Tests/ApplicationTests/Fixtures/EventLogTest.cs create mode 100644 Tests/ApplicationTests/IOTests.cs diff --git a/.gitignore b/.gitignore index edbc2eec0..8f7e6f1cc 100644 --- a/.gitignore +++ b/.gitignore @@ -241,3 +241,4 @@ launchSettings.json /WebfrontCore/wwwroot/fonts /WebfrontCore/wwwroot/font /Plugins/Tests/TestSourceFiles +/Tests/ApplicationTests/Files/GameEvents.json diff --git a/Application/Application.csproj b/Application/Application.csproj index e68fb48f0..4f37f0940 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -25,11 +25,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index bb0c6a07d..cfc816ecf 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -59,11 +59,12 @@ namespace IW4MAdmin.Application private readonly ITranslationLookup _translationLookup; private readonly IConfigurationHandler _commandConfiguration; private readonly IGameServerInstanceFactory _serverInstanceFactory; + private readonly IParserRegexFactory _parserRegexFactory; public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable commands, ITranslationLookup translationLookup, IConfigurationHandler commandConfiguration, IConfigurationHandler appConfigHandler, IGameServerInstanceFactory serverInstanceFactory, - IEnumerable plugins) + IEnumerable plugins, IParserRegexFactory parserRegexFactory) { MiddlewareActionHandler = actionHandler; _servers = new ConcurrentBag(); @@ -74,8 +75,8 @@ namespace IW4MAdmin.Application ConfigHandler = appConfigHandler; StartTime = DateTime.UtcNow; PageList = new PageList(); - AdditionalEventParsers = new List() { new BaseEventParser() }; - AdditionalRConParsers = new List() { new BaseRConParser() }; + AdditionalEventParsers = new List() { new BaseEventParser(parserRegexFactory) }; + AdditionalRConParsers = new List() { new BaseRConParser(parserRegexFactory) }; TokenAuthenticator = new TokenAuthentication(); _metaService = new MetaService(); _tokenSource = new CancellationTokenSource(); @@ -84,6 +85,7 @@ namespace IW4MAdmin.Application _translationLookup = translationLookup; _commandConfiguration = commandConfiguration; _serverInstanceFactory = serverInstanceFactory; + _parserRegexFactory = parserRegexFactory; Plugins = plugins; } @@ -771,7 +773,7 @@ namespace IW4MAdmin.Application public IRConParser GenerateDynamicRConParser(string name) { - return new DynamicRConParser() + return new DynamicRConParser(_parserRegexFactory) { Name = name }; @@ -779,7 +781,7 @@ namespace IW4MAdmin.Application public IEventParser GenerateDynamicEventParser(string name) { - return new DynamicEventParser() + return new DynamicEventParser(_parserRegexFactory) { Name = name }; diff --git a/Application/EventParsers/BaseEventParser.cs b/Application/EventParsers/BaseEventParser.cs index c58d71c6c..ff89e95d6 100644 --- a/Application/EventParsers/BaseEventParser.cs +++ b/Application/EventParsers/BaseEventParser.cs @@ -2,18 +2,16 @@ using SharedLibraryCore.Database.Models; using SharedLibraryCore.Interfaces; using System; -using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using static SharedLibraryCore.Server; namespace IW4MAdmin.Application.EventParsers { public class BaseEventParser : IEventParser { - public BaseEventParser() + public BaseEventParser(IParserRegexFactory parserRegexFactory) { - Configuration = new DynamicEventParserConfiguration() + Configuration = new DynamicEventParserConfiguration(parserRegexFactory) { GameDirectory = "main", }; @@ -66,6 +64,8 @@ namespace IW4MAdmin.Application.EventParsers Configuration.Kill.AddMapping(ParserRegex.GroupType.Damage, 11); Configuration.Kill.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12); Configuration.Kill.AddMapping(ParserRegex.GroupType.HitLocation, 13); + + Configuration.Time.Pattern = @"^ *(([0-9]+):([0-9]+) |^[0-9]+ )"; } public IEventParserConfiguration Configuration { get; set; } @@ -80,44 +80,48 @@ namespace IW4MAdmin.Application.EventParsers public virtual GameEvent GenerateGameEvent(string logLine) { - var timeMatch = Regex.Match(logLine, @"^ *(([0-9]+):([0-9]+) |^[0-9]+ )"); + var timeMatch = Configuration.Time.PatternMatcher.Match(logLine); int gameTime = 0; - + if (timeMatch.Success) { - gameTime = (timeMatch.Groups.Values as IEnumerable) + gameTime = timeMatch + .Values .Skip(2) - .Select(_value => int.Parse(_value.ToString())) + // this converts the timestamp into seconds passed + .Select((_value, index) => int.Parse(_value.ToString()) * (index == 0 ? 60 : 1)) .Sum(); - logLine = logLine.Substring(timeMatch.Value.Length); - } + // we want to strip the time from the log line + logLine = logLine.Substring(timeMatch.Values.First().Length); + } string[] lineSplit = logLine.Split(';'); string eventType = lineSplit[0]; if (eventType == "say" || eventType == "sayteam") { - var matchResult = Regex.Match(logLine, Configuration.Say.Pattern); + var matchResult = Configuration.Say.PatternMatcher.Match(logLine); if (matchResult.Success) { - string message = matchResult - .Groups[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]] + string message = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]] .ToString() .Replace("\x15", "") .Trim(); if (message.Length > 0) { - long originId = matchResult.Groups[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle); + long originId = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle); + int clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]); + // todo: these need to defined outside of here if (message[0] == '!' || message[0] == '@') { return new GameEvent() { Type = GameEvent.EventType.Command, Data = message, - Origin = new EFClient() { NetworkId = originId }, + Origin = new EFClient() { NetworkId = originId, ClientNumber = clientNumber }, Message = message, Extra = logLine, RequiredEntity = GameEvent.EventRequiredEntity.Origin, @@ -129,7 +133,7 @@ namespace IW4MAdmin.Application.EventParsers { Type = GameEvent.EventType.Say, Data = message, - Origin = new EFClient() { NetworkId = originId }, + Origin = new EFClient() { NetworkId = originId, ClientNumber = clientNumber }, Message = message, Extra = logLine, RequiredEntity = GameEvent.EventRequiredEntity.Origin, @@ -141,19 +145,21 @@ namespace IW4MAdmin.Application.EventParsers if (eventType == "K") { - var match = Regex.Match(logLine, Configuration.Kill.Pattern); + var match = Configuration.Kill.PatternMatcher.Match(logLine); if (match.Success) { - long originId = match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].Value.ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); - long targetId = match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].Value.ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); + long originId = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); + long targetId = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); + int originClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]); + int targetClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]); return new GameEvent() { Type = GameEvent.EventType.Kill, Data = logLine, - Origin = new EFClient() { NetworkId = originId }, - Target = new EFClient() { NetworkId = targetId }, + Origin = new EFClient() { NetworkId = originId, ClientNumber = originClientNumber }, + Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber }, RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target, GameTime = gameTime }; @@ -162,19 +168,21 @@ namespace IW4MAdmin.Application.EventParsers if (eventType == "D") { - var regexMatch = Regex.Match(logLine, Configuration.Damage.Pattern); + var match = Configuration.Damage.PatternMatcher.Match(logLine); - if (regexMatch.Success) + if (match.Success) { - long originId = regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); - long targetId = regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); + long originId = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); + long targetId = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1); + int originClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]); + int targetClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]); return new GameEvent() { Type = GameEvent.EventType.Damage, Data = logLine, - Origin = new EFClient() { NetworkId = originId }, - Target = new EFClient() { NetworkId = targetId }, + Origin = new EFClient() { NetworkId = originId, ClientNumber = originClientNumber }, + Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber }, RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target, GameTime = gameTime }; @@ -183,9 +191,9 @@ namespace IW4MAdmin.Application.EventParsers if (eventType == "J") { - var regexMatch = Regex.Match(logLine, Configuration.Join.Pattern); + var match = Configuration.Join.PatternMatcher.Match(logLine); - if (regexMatch.Success) + if (match.Success) { return new GameEvent() { @@ -195,10 +203,10 @@ namespace IW4MAdmin.Application.EventParsers { CurrentAlias = new EFAlias() { - Name = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine(), + Name = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine(), }, - NetworkId = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle), - ClientNumber = Convert.ToInt32(regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()), + NetworkId = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle), + ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()), State = EFClient.ClientState.Connecting, }, RequiredEntity = GameEvent.EventRequiredEntity.None, @@ -210,8 +218,9 @@ namespace IW4MAdmin.Application.EventParsers if (eventType == "Q") { - var regexMatch = Regex.Match(logLine, Configuration.Quit.Pattern); - if (regexMatch.Success) + var match = Configuration.Quit.PatternMatcher.Match(logLine); + + if (match.Success) { return new GameEvent() { @@ -221,10 +230,10 @@ namespace IW4MAdmin.Application.EventParsers { CurrentAlias = new EFAlias() { - Name = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine() + Name = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine() }, - NetworkId = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle), - ClientNumber = Convert.ToInt32(regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()), + NetworkId = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle), + ClientNumber = Convert.ToInt32(match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()), State = EFClient.ClientState.Disconnecting }, RequiredEntity = GameEvent.EventRequiredEntity.None, @@ -279,7 +288,6 @@ namespace IW4MAdmin.Application.EventParsers // this is a custom event printed out by _customcallbacks.gsc (used for anticheat) if (eventType == "ScriptKill") { - long originId = lineSplit[1].ConvertGuidToLong(Configuration.GuidNumberStyle, 1); long targetId = lineSplit[2].ConvertGuidToLong(Configuration.GuidNumberStyle, 1); diff --git a/Application/EventParsers/DynamicEventParser.cs b/Application/EventParsers/DynamicEventParser.cs index 97c9804cd..8ea618d90 100644 --- a/Application/EventParsers/DynamicEventParser.cs +++ b/Application/EventParsers/DynamicEventParser.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using static SharedLibraryCore.Server; +using SharedLibraryCore.Interfaces; namespace IW4MAdmin.Application.EventParsers { @@ -11,5 +8,8 @@ namespace IW4MAdmin.Application.EventParsers /// sealed internal class DynamicEventParser : BaseEventParser { + public DynamicEventParser(IParserRegexFactory parserRegexFactory) : base(parserRegexFactory) + { + } } } diff --git a/Application/EventParsers/DynamicEventParserConfiguration.cs b/Application/EventParsers/DynamicEventParserConfiguration.cs index e42239e1c..026c275ba 100644 --- a/Application/EventParsers/DynamicEventParserConfiguration.cs +++ b/Application/EventParsers/DynamicEventParserConfiguration.cs @@ -10,12 +10,24 @@ namespace IW4MAdmin.Application.EventParsers sealed internal class DynamicEventParserConfiguration : IEventParserConfiguration { public string GameDirectory { get; set; } - public ParserRegex Say { get; set; } = new ParserRegex(); - public ParserRegex Join { get; set; } = new ParserRegex(); - public ParserRegex Quit { get; set; } = new ParserRegex(); - public ParserRegex Kill { get; set; } = new ParserRegex(); - public ParserRegex Damage { get; set; } = new ParserRegex(); - public ParserRegex Action { get; set; } = new ParserRegex(); + public ParserRegex Say { get; set; } + public ParserRegex Join { get; set; } + public ParserRegex Quit { get; set; } + public ParserRegex Kill { get; set; } + public ParserRegex Damage { get; set; } + public ParserRegex Action { get; set; } + public ParserRegex Time { get; set; } public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber; + + public DynamicEventParserConfiguration(IParserRegexFactory parserRegexFactory) + { + Say = parserRegexFactory.CreateParserRegex(); + Join = parserRegexFactory.CreateParserRegex(); + Quit = parserRegexFactory.CreateParserRegex(); + Kill = parserRegexFactory.CreateParserRegex(); + Damage = parserRegexFactory.CreateParserRegex(); + Action = parserRegexFactory.CreateParserRegex(); + Time = parserRegexFactory.CreateParserRegex(); + } } } diff --git a/Application/EventParsers/ParserPatternMatcher.cs b/Application/EventParsers/ParserPatternMatcher.cs new file mode 100644 index 000000000..a9944342d --- /dev/null +++ b/Application/EventParsers/ParserPatternMatcher.cs @@ -0,0 +1,35 @@ +using IW4MAdmin.Application.Misc; +using SharedLibraryCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace IW4MAdmin.Application.EventParsers +{ + /// + /// implementation of the IParserPatternMatcher for windows (really it's the only implementation) + /// + public class ParserPatternMatcher : IParserPatternMatcher + { + private Regex regex; + + /// + public void Compile(string pattern) + { + regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); + } + + /// + public IMatchResult Match(string input) + { + var match = regex.Match(input); + + return new ParserMatchResult() + { + Success = match.Success, + Values = (match.Groups as IEnumerable)? + .Select(_item => _item.ToString()).ToArray() ?? new string[0] + }; + } + } +} diff --git a/Application/Factories/ParserRegexFactory.cs b/Application/Factories/ParserRegexFactory.cs new file mode 100644 index 000000000..578ab7ad7 --- /dev/null +++ b/Application/Factories/ParserRegexFactory.cs @@ -0,0 +1,26 @@ +using SharedLibraryCore.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace IW4MAdmin.Application.Factories +{ + /// + /// Implementation of the IParserRegexFactory + /// + public class ParserRegexFactory : IParserRegexFactory + { + private readonly IServiceProvider _serviceProvider; + + /// + public ParserRegexFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + public ParserRegex CreateParserRegex() + { + return new ParserRegex(_serviceProvider.GetService()); + } + } +} diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index bf09b87c8..5055aad7c 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -6,12 +6,11 @@ using System.Threading.Tasks; namespace IW4MAdmin.Application.IO { - class GameLogEventDetection + public class GameLogEventDetection { private long previousFileSize; private readonly Server _server; private readonly IGameLogReader _reader; - private readonly string _gameLogFile; private readonly bool _ignoreBots; class EventState @@ -20,12 +19,13 @@ namespace IW4MAdmin.Application.IO public string ServerId { get; set; } } - public GameLogEventDetection(Server server, string gameLogPath, Uri gameLogServerUri) + public GameLogEventDetection(Server server, string gameLogPath, Uri gameLogServerUri, IGameLogReader reader = null) { - _gameLogFile = gameLogPath; - _reader = gameLogServerUri != null ? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser) : _reader = new GameLogReader(gameLogPath, server.EventParser); + _reader = gameLogServerUri != null + ? reader ?? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser) + : reader ?? new GameLogReader(gameLogPath, server.EventParser); _server = server; - _ignoreBots = server.Manager.GetApplicationSettings().Configuration().IgnoreBots; + _ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false; } public async Task PollForChanges() @@ -52,7 +52,7 @@ namespace IW4MAdmin.Application.IO _server.Logger.WriteDebug("Stopped polling for changes"); } - private async Task UpdateLogEvents() + public async Task UpdateLogEvents() { long fileSize = _reader.Length; @@ -65,7 +65,10 @@ namespace IW4MAdmin.Application.IO // this makes the http log get pulled if (fileDiff < 1 && fileSize != -1) + { + previousFileSize = fileSize; return; + } var events = await _reader.ReadEventsFromLog(_server, fileDiff, previousFileSize); diff --git a/Application/IO/GameLogReader.cs b/Application/IO/GameLogReader.cs index bf50cb915..07a398632 100644 --- a/Application/IO/GameLogReader.cs +++ b/Application/IO/GameLogReader.cs @@ -24,7 +24,7 @@ namespace IW4MAdmin.Application.IO _parser = parser; } - public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) + public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) { // allocate the bytes for the new log lines List logLines = new List(); diff --git a/Application/IO/GameLogReaderHttp.cs b/Application/IO/GameLogReaderHttp.cs index 2a36e66a4..8f64f3562 100644 --- a/Application/IO/GameLogReaderHttp.cs +++ b/Application/IO/GameLogReaderHttp.cs @@ -16,27 +16,27 @@ namespace IW4MAdmin.Application.IO /// class GameLogReaderHttp : IGameLogReader { - readonly IEventParser Parser; - readonly IGameLogServer Api; + private readonly IEventParser _eventParser; + private readonly IGameLogServer _logServerApi; readonly string logPath; private string lastKey = "next"; public GameLogReaderHttp(Uri gameLogServerUri, string logPath, IEventParser parser) { - this.logPath = logPath.ToBase64UrlSafeString(); ; - Parser = parser; - Api = RestClient.For(gameLogServerUri); + this.logPath = logPath.ToBase64UrlSafeString(); + _eventParser = parser; + _logServerApi = RestClient.For(gameLogServerUri); } public long Length => -1; public int UpdateInterval => 500; - public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) + public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) { var events = new List(); string b64Path = logPath; - var response = await Api.Log(b64Path, lastKey); + var response = await _logServerApi.Log(b64Path, lastKey); lastKey = response.NextKey; if (!response.Success && string.IsNullOrEmpty(lastKey)) @@ -48,17 +48,17 @@ namespace IW4MAdmin.Application.IO else if (!string.IsNullOrWhiteSpace(response.Data)) { // parse each line - foreach (string eventLine in response.Data - .Split(Environment.NewLine) - .Where(_line => _line.Length > 0)) + var lines = response.Data + .Split(Environment.NewLine) + .Where(_line => _line.Length > 0); + + foreach (string eventLine in lines) { try { - var gameEvent = Parser.GenerateGameEvent(eventLine); + // this trim end should hopefully fix the nasty runaway regex + var gameEvent = _eventParser.GenerateGameEvent(eventLine.TrimEnd('\r')); events.Add(gameEvent); -#if DEBUG == true - server.Logger.WriteDebug($"Parsed event with id {gameEvent.Id} from http"); -#endif } catch (Exception e) diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index 97a63ac6a..53ff9f12c 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -25,7 +25,7 @@ namespace IW4MAdmin public class IW4MServer : Server { private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex; - private GameLogEventDetection LogEvent; + public GameLogEventDetection LogEvent; private readonly ITranslationLookup _translationLookup; private const int REPORT_FLAG_COUNT = 4; private int lastGameTime = 0; @@ -891,8 +891,8 @@ namespace IW4MAdmin EventParser = Manager.AdditionalEventParsers .FirstOrDefault(_parser => _parser.Version == ServerConfig.EventParserVersion); - RconParser = RconParser ?? new BaseRConParser(); - EventParser = EventParser ?? new BaseEventParser(); + RconParser = RconParser ?? Manager.AdditionalRConParsers[0]; + EventParser = EventParser ?? Manager.AdditionalEventParsers[0]; RemoteConnection.SetConfiguration(RconParser.Configuration); @@ -949,6 +949,14 @@ namespace IW4MAdmin try { var website = await this.GetDvarAsync("_website"); + + // this occurs for games that don't give us anything back when + // the dvar is not set + if (string.IsNullOrWhiteSpace(website.Value)) + { + throw new DvarException("value is empty"); + } + Website = website.Value; } diff --git a/Application/Main.cs b/Application/Main.cs index 2e1c7d541..0ffb3848e 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -1,6 +1,6 @@ -using IW4MAdmin.Application.Factories; +using IW4MAdmin.Application.EventParsers; +using IW4MAdmin.Application.Factories; using IW4MAdmin.Application.Helpers; -using IW4MAdmin.Application.IO; using IW4MAdmin.Application.Migration; using IW4MAdmin.Application.Misc; using Microsoft.Extensions.DependencyInjection; @@ -87,7 +87,7 @@ namespace IW4MAdmin.Application catch (Exception e) { string failMessage = translationLookup == null ? "Failed to initalize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"]; - string exitMessage = translationLookup == null ? "Press any key to exit..." : translationLookup["MANAGER_EXIT"]; + string exitMessage = translationLookup == null ? "Press enter to exit..." : translationLookup["MANAGER_EXIT"]; Console.WriteLine(failMessage); @@ -115,7 +115,7 @@ namespace IW4MAdmin.Application } Console.WriteLine(exitMessage); - Console.ReadKey(); + await Console.In.ReadAsync(new char[1], 0, 1); return; } @@ -237,7 +237,7 @@ namespace IW4MAdmin.Application { while (!ServerManager.CancellationToken.IsCancellationRequested) { - lastCommand = Console.ReadLine(); + lastCommand = await Console.In.ReadLineAsync(); if (lastCommand?.Length > 0) { @@ -282,6 +282,8 @@ namespace IW4MAdmin.Application .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() + .AddTransient() .AddSingleton(_serviceProvider => { var config = _serviceProvider.GetRequiredService>().Configuration(); diff --git a/Application/Misc/ParserMatchResult.cs b/Application/Misc/ParserMatchResult.cs new file mode 100644 index 000000000..5aa7953ed --- /dev/null +++ b/Application/Misc/ParserMatchResult.cs @@ -0,0 +1,21 @@ +using SharedLibraryCore.Interfaces; + +namespace IW4MAdmin.Application.Misc +{ + /// + /// implementation of the IMatchResult + /// used to hold matching results + /// + public class ParserMatchResult : IMatchResult + { + /// + /// array of matched pattern groups + /// + public string[] Values { get; set; } + + /// + /// indicates if the match succeeded + /// + public bool Success { get; set; } + } +} diff --git a/Application/RconParsers/BaseRConParser.cs b/Application/RconParsers/BaseRConParser.cs index 44ec4ce71..ceba60215 100644 --- a/Application/RconParsers/BaseRConParser.cs +++ b/Application/RconParsers/BaseRConParser.cs @@ -12,15 +12,11 @@ using static SharedLibraryCore.Server; namespace IW4MAdmin.Application.RconParsers { -#if DEBUG public class BaseRConParser : IRConParser -#else - class BaseRConParser : IRConParser -#endif { - public BaseRConParser() + public BaseRConParser(IParserRegexFactory parserRegexFactory) { - Configuration = new DynamicRConParserConfiguration() + Configuration = new DynamicRConParserConfiguration(parserRegexFactory) { CommandPrefixes = new CommandPrefix() { @@ -90,7 +86,6 @@ namespace IW4MAdmin.Application.RconParsers string removeTrailingColorCode(string input) => Regex.Replace(input, @"\^7$", ""); - value = removeTrailingColorCode(value); defaultValue = removeTrailingColorCode(defaultValue); latchedValue = removeTrailingColorCode(latchedValue); @@ -134,7 +129,11 @@ namespace IW4MAdmin.Application.RconParsers public async Task SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue) { - return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, $"{dvarName} {dvarValue}")).Length > 0; + string dvarString = (dvarValue is string str) + ? $"{dvarName} \"{str}\"" + : $"{dvarName} {dvarValue.ToString()}"; + + return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString)).Length > 0; } private List ClientsFromStatus(string[] Status) diff --git a/Application/RconParsers/DynamicRConParser.cs b/Application/RconParsers/DynamicRConParser.cs index f17d3884f..380062f13 100644 --- a/Application/RconParsers/DynamicRConParser.cs +++ b/Application/RconParsers/DynamicRConParser.cs @@ -1,4 +1,6 @@ -namespace IW4MAdmin.Application.RconParsers +using SharedLibraryCore.Interfaces; + +namespace IW4MAdmin.Application.RconParsers { /// /// empty implementation of the IW4RConParser @@ -6,5 +8,8 @@ /// sealed internal class DynamicRConParser : BaseRConParser { + public DynamicRConParser(IParserRegexFactory parserRegexFactory) : base(parserRegexFactory) + { + } } } diff --git a/Application/RconParsers/DynamicRConParserConfiguration.cs b/Application/RconParsers/DynamicRConParserConfiguration.cs index c4c680e78..16aa03b5f 100644 --- a/Application/RconParsers/DynamicRConParserConfiguration.cs +++ b/Application/RconParsers/DynamicRConParserConfiguration.cs @@ -1,4 +1,5 @@ -using SharedLibraryCore.Interfaces; +using IW4MAdmin.Application.Factories; +using SharedLibraryCore.Interfaces; using SharedLibraryCore.RCon; using System.Globalization; @@ -11,11 +12,18 @@ namespace IW4MAdmin.Application.RconParsers sealed internal class DynamicRConParserConfiguration : IRConParserConfiguration { public CommandPrefix CommandPrefixes { get; set; } - public ParserRegex Status { get; set; } = new ParserRegex(); - public ParserRegex MapStatus { get; set; } = new ParserRegex(); - public ParserRegex Dvar { get; set; } = new ParserRegex(); + public ParserRegex Status { get; set; } + public ParserRegex MapStatus { get; set; } + public ParserRegex Dvar { get; set; } public string ServerNotRunningResponse { get; set; } public bool WaitForResponse { get; set; } = true; public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber; + + public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory) + { + Status = parserRegexFactory.CreateParserRegex(); + MapStatus = parserRegexFactory.CreateParserRegex(); + Dvar = parserRegexFactory.CreateParserRegex(); + } } } diff --git a/Plugins/Tests/ManagerFixture.cs b/Plugins/Tests/ManagerFixture.cs index 90e37ea5c..29a05ecc4 100644 --- a/Plugins/Tests/ManagerFixture.cs +++ b/Plugins/Tests/ManagerFixture.cs @@ -1,4 +1,5 @@ using IW4MAdmin.Application; +using IW4MAdmin.Application.Factories; using IW4MAdmin.Application.Misc; using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; @@ -42,7 +43,6 @@ namespace Tests Manager.ConfigHandler = new BaseConfigurationHandler("test"); Manager.ConfigHandler.Set(config); - Manager.AdditionalRConParsers.Add(new TestRconParser()); Manager.Init().Wait(); diff --git a/Plugins/Tests/TestRconParser.cs b/Plugins/Tests/TestRconParser.cs index d90eb324c..23f44344a 100644 --- a/Plugins/Tests/TestRconParser.cs +++ b/Plugins/Tests/TestRconParser.cs @@ -10,6 +10,11 @@ namespace Tests { class TestRconParser : IW4MAdmin.Application.RconParsers.BaseRConParser { + public TestRconParser(IParserRegexFactory f) : base(f) + { + + } + public int FakeClientCount { get; set; } public List FakeClients { get; set; } = new List(); diff --git a/Plugins/Web/StatsWeb/Views/Stats/Components/TopPlayers/_List.cshtml b/Plugins/Web/StatsWeb/Views/Stats/Components/TopPlayers/_List.cshtml index 7084e5801..ebb5bb3f1 100644 --- a/Plugins/Web/StatsWeb/Views/Stats/Components/TopPlayers/_List.cshtml +++ b/Plugins/Web/StatsWeb/Views/Stats/Components/TopPlayers/_List.cshtml @@ -2,7 +2,7 @@ @{ Layout = null; var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex.Set; - double getDeviation(double deviations) => Math.Pow(Math.E, 5.0813 + (deviations * 0.8694)); + double getDeviation(double deviations) => Math.Pow(Math.E, 5.259 + (deviations * 0.812)); string rankIcon(double elo) { if (elo >= getDeviation(-0.75) && elo < getDeviation(1.25)) diff --git a/SharedLibraryCore/Helpers/ParserRegex.cs b/SharedLibraryCore/Helpers/ParserRegex.cs index c257d24b5..8fa409db7 100644 --- a/SharedLibraryCore/Helpers/ParserRegex.cs +++ b/SharedLibraryCore/Helpers/ParserRegex.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; namespace SharedLibraryCore.Interfaces { @@ -41,10 +40,21 @@ namespace SharedLibraryCore.Interfaces AdditionalGroup = 200 } + public IParserPatternMatcher PatternMatcher { get; private set; } + + private string pattern; /// /// stores the regular expression groups that will be mapped to group types /// - public string Pattern { get; set; } + public string Pattern + { + get => pattern; + set + { + pattern = value; + PatternMatcher.Compile(value); + } + } /// /// stores the mapping from group type to group index in the regular expression @@ -90,9 +100,10 @@ namespace SharedLibraryCore.Interfaces } } - public ParserRegex() + public ParserRegex(IParserPatternMatcher pattern) { GroupMapping = new Dictionary(); + PatternMatcher = pattern; } } } diff --git a/SharedLibraryCore/Interfaces/IEventParserConfiguration.cs b/SharedLibraryCore/Interfaces/IEventParserConfiguration.cs index 1fc8e55c9..7d5736f6a 100644 --- a/SharedLibraryCore/Interfaces/IEventParserConfiguration.cs +++ b/SharedLibraryCore/Interfaces/IEventParserConfiguration.cs @@ -39,6 +39,11 @@ namespace SharedLibraryCore.Interfaces /// ParserRegex Action { get; set; } + /// + /// stores the regex information for the time prefix in game log + /// + ParserRegex Time { get; set; } + /// /// indicates the format expected for parsed guids /// diff --git a/SharedLibraryCore/Interfaces/IGameLogReader.cs b/SharedLibraryCore/Interfaces/IGameLogReader.cs index 7dcbf76d2..25bf59707 100644 --- a/SharedLibraryCore/Interfaces/IGameLogReader.cs +++ b/SharedLibraryCore/Interfaces/IGameLogReader.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; using System.Threading.Tasks; namespace SharedLibraryCore.Interfaces @@ -17,11 +15,13 @@ namespace SharedLibraryCore.Interfaces /// /// /// - Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition); + Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition); + /// /// how long the log file is /// long Length { get; } + /// /// how often to poll the log file /// diff --git a/SharedLibraryCore/Interfaces/IMatchResult.cs b/SharedLibraryCore/Interfaces/IMatchResult.cs new file mode 100644 index 000000000..3abe1bd0d --- /dev/null +++ b/SharedLibraryCore/Interfaces/IMatchResult.cs @@ -0,0 +1,18 @@ +namespace SharedLibraryCore.Interfaces +{ + /// + /// represents a pattern match result + /// + public interface IMatchResult + { + /// + /// array of matched pattern groups + /// + string[] Values { get; set; } + + /// + /// indicates if the match succeeded + /// + bool Success { get; set; } + } +} diff --git a/SharedLibraryCore/Interfaces/IParserPatternMatcher.cs b/SharedLibraryCore/Interfaces/IParserPatternMatcher.cs new file mode 100644 index 000000000..4a1712c28 --- /dev/null +++ b/SharedLibraryCore/Interfaces/IParserPatternMatcher.cs @@ -0,0 +1,21 @@ +namespace SharedLibraryCore.Interfaces +{ + /// + /// defines the capabilities of a parser pattern + /// + public interface IParserPatternMatcher + { + /// + /// converts input string into pattern groups + /// + /// input string + /// group matches + IMatchResult Match(string input); + + /// + /// compiles the pattern to be used for matching + /// + /// + void Compile(string pattern); + } +} diff --git a/SharedLibraryCore/Interfaces/IParserRegexFactory.cs b/SharedLibraryCore/Interfaces/IParserRegexFactory.cs new file mode 100644 index 000000000..4c6f9bc67 --- /dev/null +++ b/SharedLibraryCore/Interfaces/IParserRegexFactory.cs @@ -0,0 +1,14 @@ +namespace SharedLibraryCore.Interfaces +{ + /// + /// defines the capabilities of the parser regex factory + /// + public interface IParserRegexFactory + { + /// + /// creates a new ParserRegex instance + /// + /// ParserRegex instance + ParserRegex CreateParserRegex(); + } +} diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index b4ca8e0cb..e8e8f9130 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -46,30 +46,30 @@ - + - - - + + + all runtime; build; native; contentfiles - - - - - - + + + + + + - - + + - + diff --git a/Tests/ApplicationTests/ApplicationTests.csproj b/Tests/ApplicationTests/ApplicationTests.csproj index 121244179..ba2cfe133 100644 --- a/Tests/ApplicationTests/ApplicationTests.csproj +++ b/Tests/ApplicationTests/ApplicationTests.csproj @@ -20,6 +20,9 @@ + + PreserveNewest + PreserveNewest diff --git a/Tests/ApplicationTests/BaseEventParserTests.cs b/Tests/ApplicationTests/BaseEventParserTests.cs new file mode 100644 index 000000000..efe64de43 --- /dev/null +++ b/Tests/ApplicationTests/BaseEventParserTests.cs @@ -0,0 +1,58 @@ +using ApplicationTests.Fixtures; +using IW4MAdmin.Application.EventParsers; +using IW4MAdmin.Application.Factories; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using NUnit.Framework; +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using System; + +namespace ApplicationTests +{ + [TestFixture] + public class BaseEventParserTests + { + private EventLogTest eventLogData; + private IServiceProvider serviceProvider; + + [SetUp] + public void Setup() + { + eventLogData = JsonConvert.DeserializeObject(System.IO.File.ReadAllText("Files/GameEvents.json")); + serviceProvider = new ServiceCollection() + .AddSingleton() + .AddTransient() + .AddSingleton() + .BuildServiceProvider(); + } + + [Test] + public void TestParsesAllEventData() + { + var eventParser = serviceProvider.GetService(); + + void AssertMatch(GameEvent src, LogEvent expected) + { + Assert.AreEqual(expected.ExpectedEventType, src.Type); + Assert.AreEqual(expected.ExpectedData, src.Data); + Assert.AreEqual(expected.ExpectedMessage, src.Message); + Assert.AreEqual(expected.ExpectedTime, src.GameTime); + + //Assert.AreEqual(expected.ExpectedOriginClientName, src.Origin?.Name); + Assert.AreEqual(expected.ExpectedOriginClientNumber, src.Origin?.ClientNumber); + Assert.AreEqual(expected.ExpectedOriginNetworkId, src.Origin?.NetworkId.ToString("X")); + + //Assert.AreEqual(expected.ExpectedTargetClientName, src.Target?.Name); + Assert.AreEqual(expected.ExpectedTargetClientNumber, src.Target?.ClientNumber); + Assert.AreEqual(expected.ExpectedTargetNetworkId, src.Target?.NetworkId.ToString("X")); + } + + foreach (var e in eventLogData.Events) + { + var parsedEvent = eventParser.GenerateGameEvent(e.EventLine); + AssertMatch(parsedEvent, e); + } + } + } +} diff --git a/Tests/ApplicationTests/BaseRConParserTests.cs b/Tests/ApplicationTests/BaseRConParserTests.cs new file mode 100644 index 000000000..781fe1c11 --- /dev/null +++ b/Tests/ApplicationTests/BaseRConParserTests.cs @@ -0,0 +1,47 @@ +using FakeItEasy; +using IW4MAdmin.Application.RconParsers; +using NUnit.Framework; +using SharedLibraryCore.Interfaces; + +namespace ApplicationTests +{ + [TestFixture] + public class BaseRConParserTests + { + [Test] + public void SetDvarAsync_FormatStringType() + { + var parser = new BaseRConParser(A.Fake()); + var connection = A.Fake(); + + parser.SetDvarAsync(connection, "test", "test").Wait(); + + A.CallTo(() => connection.SendQueryAsync(SharedLibraryCore.RCon.StaticHelpers.QueryType.SET_DVAR, "test \"test\"")) + .MustHaveHappened(); + } + + [Test] + public void SetDvarAsync_FormatEmptyStringTypeIncludesQuotes() + { + var parser = new BaseRConParser(A.Fake()); + var connection = A.Fake(); + + parser.SetDvarAsync(connection, "test", "").Wait(); + + A.CallTo(() => connection.SendQueryAsync(SharedLibraryCore.RCon.StaticHelpers.QueryType.SET_DVAR, "test \"\"")) + .MustHaveHappened(); + } + + [Test] + public void SetDvarAsync_FormatsNonString() + { + var parser = new BaseRConParser(A.Fake()); + var connection = A.Fake(); + + parser.SetDvarAsync(connection, "test", 123).Wait(); + + A.CallTo(() => connection.SendQueryAsync(SharedLibraryCore.RCon.StaticHelpers.QueryType.SET_DVAR, "test 123")) + .MustHaveHappened(); + } + } +} diff --git a/Tests/ApplicationTests/Fixtures/EventLogTest.cs b/Tests/ApplicationTests/Fixtures/EventLogTest.cs new file mode 100644 index 000000000..a6a59ef0b --- /dev/null +++ b/Tests/ApplicationTests/Fixtures/EventLogTest.cs @@ -0,0 +1,26 @@ +using static SharedLibraryCore.GameEvent; +using static SharedLibraryCore.Server; + +namespace ApplicationTests.Fixtures +{ + class LogEvent + { + public Game Game { get; set; } + public string EventLine { get; set; } + public EventType ExpectedEventType { get; set; } + public string ExpectedData { get; set; } + public string ExpectedMessage { get; set; } + public string ExpectedOriginNetworkId { get; set; } + public int? ExpectedOriginClientNumber { get; set; } + public string ExpectedOriginClientName { get; set; } + public string ExpectedTargetNetworkId { get; set; } + public int? ExpectedTargetClientNumber { get; set; } + public string ExpectedTargetClientName { get; set; } + public int? ExpectedTime { get; set; } + } + + class EventLogTest + { + public LogEvent[] Events { get; set; } + } +} diff --git a/Tests/ApplicationTests/IOTests.cs b/Tests/ApplicationTests/IOTests.cs new file mode 100644 index 000000000..f4cd32382 --- /dev/null +++ b/Tests/ApplicationTests/IOTests.cs @@ -0,0 +1,42 @@ +using FakeItEasy; +using IW4MAdmin.Application.IO; +using NUnit.Framework; +using SharedLibraryCore; +using SharedLibraryCore.Interfaces; +using System; +using System.Threading.Tasks; + +namespace ApplicationTests +{ + [TestFixture] + public class IOTests + { + + [Test] + public async Task GameLogEventDetection_WorksAfterFileSizeReset() + { + var reader = A.Fake(); + var detect = new GameLogEventDetection(null, "", A.Fake(), reader); + + A.CallTo(() => reader.Length) + .Returns(100) + .Once() + .Then + .Returns(200) + .Once() + .Then + .Returns(10) + .Once() + .Then + .Returns(100); + + for (int i = 0; i < 4; i++) + { + await detect.UpdateLogEvents(); + } + + A.CallTo(() => reader.ReadEventsFromLog(A.Ignored, A.Ignored, A.Ignored)) + .MustHaveHappenedTwiceExactly(); + } + } +} diff --git a/Tests/ApplicationTests/ServerTests.cs b/Tests/ApplicationTests/ServerTests.cs index 452d347a9..bd333cf07 100644 --- a/Tests/ApplicationTests/ServerTests.cs +++ b/Tests/ApplicationTests/ServerTests.cs @@ -35,7 +35,7 @@ namespace ApplicationTests new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 }, A.Fake(), A.Fake()); - var parser = new BaseEventParser(); + var parser = new BaseEventParser(A.Fake()); parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; var log = System.IO.File.ReadAllLines("Files\\T6MapRotation.log"); @@ -61,7 +61,7 @@ namespace ApplicationTests new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 }, A.Fake(), A.Fake()); - var parser = new BaseEventParser(); + var parser = new BaseEventParser(A.Fake()); parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; var log = System.IO.File.ReadAllLines("Files\\T6Game.log"); diff --git a/Tests/ApplicationTests/StatsTests.cs b/Tests/ApplicationTests/StatsTests.cs index f0e0bf46c..32fdbe4d1 100644 --- a/Tests/ApplicationTests/StatsTests.cs +++ b/Tests/ApplicationTests/StatsTests.cs @@ -56,7 +56,7 @@ namespace ApplicationTests A.Fake(), A.Fake()); - var parser = new BaseEventParser(); + var parser = new BaseEventParser(A.Fake()); parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; var log = System.IO.File.ReadAllLines("Files\\T6GameStats.log"); diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index a8e9b0fe1..2a60ad4ac 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -70,7 +70,7 @@ - +