using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; using Unity.PerformanceTesting.Data; using UnityEngine; namespace Unity.PerformanceTesting.Editor { /// /// Helper class to parse test runs into performance test runs. /// public class TestResultXmlParser { /// /// Parses performance test run from test run result xml. /// /// Path to test results xml file. /// public Run GetPerformanceTestRunFromXml(string resultXmlFileName) { ValidateInput(resultXmlFileName); var xmlDocument = TryLoadResultXmlFile(resultXmlFileName); var performanceTestRun = TryParseXmlToPerformanceTestRun(xmlDocument); return performanceTestRun; } private void ValidateInput(string resultXmlFileName) { if (string.IsNullOrEmpty(resultXmlFileName)) { Debug.LogWarning($"Test results path is null or empty."); } if (!File.Exists(resultXmlFileName)) { Debug.LogWarning($"Test results file does not exists at path: {resultXmlFileName}"); } } private XDocument TryLoadResultXmlFile(string resultXmlFileName) { try { return XDocument.Load(resultXmlFileName); } catch (Exception e) { var errMsg = $"Failed to load xml result file: {resultXmlFileName}"; Debug.LogWarning($"{errMsg}\r\nException: {e.Message}\r\n{e.StackTrace}"); } return null; } private Run TryParseXmlToPerformanceTestRun(XContainer xmlDocument) { var output = xmlDocument.Descendants("output").ToArray(); if (!output.Any()) { return null; } var run = DeserializeMetadata(output); DeserializeTestResults(output, run); return run; } private void DeserializeTestResults(IEnumerable output, Run run) { foreach (var element in output) { foreach (var line in element.Value.Split('\n')) { var json = GetJsonFromHashtag("performancetestresult2", line); if (json == null) { continue; } var result = TryDeserializePerformanceTestResultJsonObject(json); if (result != null) { run.Results.Add(result); } } } } private Run DeserializeMetadata(IEnumerable output) { foreach (var element in output) { var pattern = @"##performancetestruninfo2:(.+)\n"; var regex = new Regex(pattern); var matches = regex.Match(element.Value); if (matches.Groups.Count == 0) continue; if (matches.Captures.Count == 0) continue; if (matches.Groups[1].Captures.Count > 1) { Debug.LogError("Performance test run had multiple hardware and player settings, there should only be one."); return null; } var json = matches.Groups[1].Value; if (string.IsNullOrEmpty(json)) { Debug.LogError("Performance test run has incomplete hardware and player settings."); return null; } var result = TryDeserializePerformanceTestRunJsonObject(json); return result; } return null; } private PerformanceTestResult TryDeserializePerformanceTestResultJsonObject(string json) { try { return JsonUtility.FromJson(json); } catch (Exception e) { var errMsg = $"Exception thrown while deserializing json string to PerformanceTestResult: {json}"; Debug.LogWarning($"{errMsg}\r\nException: {e.Message}\r\n{e.StackTrace}"); } return null; } private Run TryDeserializePerformanceTestRunJsonObject(string json) { try { return JsonUtility.FromJson(json); } catch (Exception e) { var errMsg = $"Exception thrown while deserializing json string to PerformanceTestRun: {json}"; Debug.LogWarning($"{errMsg}\r\nException: {e.Message}\r\n{e.StackTrace}"); } return null; } private string GetJsonFromHashtag(string tag, string line) { if (!line.Contains($"##{tag}:")) return null; var jsonStart = line.IndexOf('{'); var openBrackets = 0; var stringIndex = jsonStart; while (openBrackets > 0 || stringIndex == jsonStart) { var character = line[stringIndex]; switch (character) { case '{': openBrackets++; break; case '}': openBrackets--; break; } stringIndex++; } var jsonEnd = stringIndex; return line.Substring(jsonStart, jsonEnd - jsonStart); } } }