1 // 2 // Copyright (c) 2010-2023 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Collections.Generic; 9 using System.Linq; 10 using System.Reflection; 11 using System.Text; 12 using System.Text.RegularExpressions; 13 using CookComputing.XmlRpc; 14 using Antmicro.Renode.Core; 15 16 namespace Antmicro.Renode.RobotFramework 17 { 18 internal class XmlRpcServer : XmlRpcListenerService, IDisposable 19 { XmlRpcServer(KeywordManager keywordManager)20 public XmlRpcServer(KeywordManager keywordManager) 21 { 22 this.keywordManager = keywordManager; 23 } 24 25 [XmlRpcMethod("get_keyword_names")] GetKeywordNames()26 public string[] GetKeywordNames() 27 { 28 return keywordManager.GetRegisteredKeywords(); 29 } 30 31 [XmlRpcMethod("run_keyword")] RunKeyword(string keywordName, object[] arguments)32 public XmlRpcStruct RunKeyword(string keywordName, object[] arguments) 33 { 34 var result = new XmlRpcStruct(); 35 KeywordManager.KeywordLookupResult lookupResult = default(KeywordManager.KeywordLookupResult); 36 try 37 { 38 lookupResult = keywordManager.TryExecuteKeyword(keywordName, arguments, out var keywordResult); 39 if(lookupResult == KeywordManager.KeywordLookupResult.Success) 40 { 41 if(keywordResult != null) 42 { 43 if(keywordResult is string resultString) 44 { 45 keywordResult = Regex.Replace(resultString, "(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]", "", RegexOptions.Compiled); 46 } 47 else if(keywordResult is IList<object> list) 48 { 49 keywordResult = list.ToArray(); 50 } 51 result.Add(KeywordResultValue, keywordResult); 52 } 53 result.Add(KeywordResultStatus, KeywordResultPass); 54 } 55 } 56 catch(Exception e) 57 { 58 result.Clear(); 59 60 result.Add(KeywordResultStatus, KeywordResultFail); 61 result.Add(KeywordResultError, BuildRecursiveValueFromException(e, ex => ex.Message).StripNonSafeCharacters()); 62 result.Add(KeywordResultTraceback, BuildRecursiveValueFromException(e, ex => ex.StackTrace).StripNonSafeCharacters()); 63 } 64 if(lookupResult == KeywordManager.KeywordLookupResult.KeywordNotFound) 65 { 66 throw new XmlRpcFaultException(1, string.Format("Keyword \"{0}\" not found", keywordName)); 67 } 68 if(lookupResult == KeywordManager.KeywordLookupResult.ArgumentsNotMatched) 69 { 70 throw new XmlRpcFaultException(2, string.Format("Arguments types do not match any available keyword \"{0}\" : [{1}]", keywordName, string.Join(", ", arguments))); 71 } 72 return result; 73 } 74 75 [XmlRpcMethod("stop_remote_server")] Dispose()76 public void Dispose() 77 { 78 var robotFrontendEngine = (RobotFrameworkEngine)ObjectCreator.Instance.GetSurrogate(typeof(RobotFrameworkEngine)); 79 robotFrontendEngine.Shutdown(); 80 } 81 BuildRecursiveValueFromException(Exception e, Func<Exception, string> generator)82 private static string BuildRecursiveValueFromException(Exception e, Func<Exception, string> generator) 83 { 84 var result = new StringBuilder(); 85 while(e != null) 86 { 87 if(!(e is TargetInvocationException)) 88 { 89 // TargetInvocationException is only a container, it does not provide valuable information 90 result.AppendFormat("{0}: {1}\n", e.GetType().Name, generator(e)); 91 } 92 e = e.InnerException; 93 } 94 95 return result.ToString(); 96 } 97 98 private readonly KeywordManager keywordManager; 99 100 private const string KeywordResultValue = "return"; 101 private const string KeywordResultStatus = "status"; 102 private const string KeywordResultError = "error"; 103 private const string KeywordResultTraceback = "traceback"; 104 105 private const string KeywordResultPass = "PASS"; 106 private const string KeywordResultFail = "FAIL"; 107 } 108 } 109 110