Add infos into target window
[ryzomcore.git] / ryzom / server / src / patchman_service / patchman_tester.cpp
blob9ccd203c334f4fb16d776b0bdbb52fa7251fe6fb
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 //-----------------------------------------------------------------------------
18 // include
19 //-----------------------------------------------------------------------------
21 // nel
22 #include "nel/misc/types_nl.h"
23 #include "nel/misc/common.h"
24 #include "nel/misc/sstring.h"
25 #include "nel/misc/smart_ptr.h"
26 #include "nel/misc/singleton.h"
27 #include "nel/misc/command.h"
28 #include "nel/misc/file.h"
30 // game share
31 #include "game_share/utils.h"
33 // local
34 #include "patchman_tester.h"
37 //-----------------------------------------------------------------------------
38 // namespaces
39 //-----------------------------------------------------------------------------
41 using namespace std;
42 using namespace NLMISC;
45 //-----------------------------------------------------------------------------
46 // namespace PATCHMAN
47 //-----------------------------------------------------------------------------
49 namespace PATCHMAN
51 //-----------------------------------------------------------------------------
52 // class CVariableSet
53 //-----------------------------------------------------------------------------
55 class CVariableSet
57 public:
58 // clear out the set of variables
59 void clear();
61 // set the value of a variable
62 void set(const CSString& varName,const CSString& value);
64 // get the value of a variable
65 const CSString& get(const CSString& varName) const;
67 // expand the variable names in a string out to their values (may do simple expression eval later too)
68 // variable names are assumed to be of the form: '$'<var_name> eg: "$MYVAR"
69 CSString expand(CSString inputString) const;
71 // dump the variable set to a given log
72 void dump(CLog& log) const;
74 private:
75 typedef map<CSString,CSString> TVariables;
76 TVariables _Variables;
80 //-----------------------------------------------------------------------------
81 // class CScriptLine
82 //-----------------------------------------------------------------------------
84 class CScriptLine
86 public:
87 // ctors
88 CScriptLine();
89 CScriptLine(const CSString& context, const CSString& keyword, const CSString& args);
91 // accessors
92 bool isValid() const;
93 CSString toString() const;
95 // execution - returns false if script should abort, otherwise true
96 // if there is an error then the error message will be not empty on return
97 bool execute(CVariableSet& vars, CSString& errorMsg) const;
99 private:
100 enum TActions
102 // the invalid operation 'no op'
103 ACT_NOP,
104 // conditional - stop execution of this routine with 'success' return value if condition fails
105 ACT_ONLYIF, ACT_ONLYIF_NOT,
106 // assert
107 ACT_ASSERT, ACT_ASSERT_NOT,
108 // set, inc or dec a variable
109 ACT_SET, ACT_INC, ACT_DEC,
110 // display a debug, info or warning
111 ACT_DEBUG, ACT_INFO, ACT_WARN,
112 // execute an NLMISC command
113 ACT_CMD,
116 CSString _RawCmdLine;
117 CSString _Context;
119 TActions _Action;
120 CVectorSString _Args;
124 //-----------------------------------------------------------------------------
125 // class CScriptRoutine
126 //-----------------------------------------------------------------------------
128 class CScriptRoutine
130 public:
131 // ctor
132 CScriptRoutine(const CSString& name=CSString());
134 // building up the script routine from input...
135 // returns true if line is valid, otherwise false
136 bool addLine(const CSString& context, const CSString& keyword,const CSString& args);
138 // execute the script
139 // return true if execution went OK, false if errors encountered / script asserts triggered
140 bool execute(CVariableSet& vars) const;
142 // accessors
143 bool isEmpty() const;
144 CSString getName() const;
147 private:
148 // the script name (the name of the event that triggers it)
149 CSString _Name;
151 // the script lines
152 typedef vector<CScriptLine> TLines;
153 TLines _Lines;
157 //-----------------------------------------------------------------------------
158 // class CPatchmanTesterImplementation
159 //-----------------------------------------------------------------------------
161 class CPatchmanTesterImplementation: public CPatchmanTester, public CRefCount
163 public:
164 // clear everything
165 void clear();
167 // clear loaded script(s) but leave state intact
168 void clearScript();
170 // clear state variables but leave scripts intact
171 void clearState();
173 // load a script file
174 void loadScript(const NLMISC::CSString& fileName);
176 // set a state variable
177 void set(const NLMISC::CSString& variableName,const NLMISC::CSString& value);
178 void set(const NLMISC::CSString& variableName,sint32 value);
180 // trigger an event
181 void trigger(const NLMISC::CSString& eventName);
183 // debug routine - display internal state info
184 void dump(NLMISC::CLog& log);
186 // debugging routine - displays the syntax help for the patchtest script files
187 void help(NLMISC::CLog& log);
189 private:
190 // private methods
191 typedef std::set<NLMISC::CSString> TFileNameSet;
192 void _readScriptFile(const NLMISC::CSString& fileName,uint32& errors,TFileNameSet& fileNameSet);
194 private:
195 // state variables (map of var name to value)
196 CVariableSet _VariableSet;
198 // scripts (vector of routines)
199 typedef vector<CScriptRoutine> TScripts;
200 TScripts _Scripts;
204 //-----------------------------------------------------------------------------
205 // methods CVariableSet
206 //-----------------------------------------------------------------------------
208 void CVariableSet::clear()
210 _Variables.clear();
213 void CVariableSet::set(const CSString& varName,const CSString& value)
215 if (value.empty())
217 _Variables.erase(varName);
218 return;
221 _Variables[varName]= value;
224 const CSString& CVariableSet::get(const CSString& varName) const
226 // setup an empty string to use if the requestsed variable doesn't exist
227 static CSString emptyString;
229 // lookup the requested variable in the variable map
230 TVariables::const_iterator it= _Variables.find(varName);
232 // if the variable was found then return its value else return the empty string
233 return (it== _Variables.end())? emptyString: it->second;
236 CSString CVariableSet::expand(CSString inputString) const
238 // setup a result buffer
239 CSString result;
241 while (!inputString.empty())
243 // add all text up to the next variable name to our result buffer
244 result+= inputString.splitTo('$',true,false);
246 // if we've just absorbed the entire remainder of the input then drop out of the while loop
247 if (inputString.empty()) break;
249 // skip the '$' sign
250 inputString= inputString.leftCrop(1);
252 // extract the variable name
253 CSString varName= inputString.splitToOneOfSeparators(" \t:=$\"\'`&|~^+-*/()<>[]{};,.?!",true,false,false,false,false);
255 // if a variable name was found...
256 if (!varName.empty())
258 // add the value of the variable to the result
259 result+= get(varName);
261 else
263 // no variable name found so keep the '$' intact in the result expression (to help debugging)
264 result+='$';
268 return result;
271 void CVariableSet::dump(CLog& log) const
274 // iterate over the variables to determine the length of the longest variable name
275 uint32 maxlen=0;
276 for (TVariables::const_iterator it= _Variables.begin(); it!=_Variables.end(); ++it)
278 maxlen=max(maxlen,(uint32)it->first.size());
281 // display the variables
282 for (TVariables::const_iterator it= _Variables.begin(); it!=_Variables.end(); ++it)
284 log.displayNL("%*s = %s",maxlen,it->first.c_str(),it->second.c_str());
289 //-----------------------------------------------------------------------------
290 // methods CScriptLine
291 //-----------------------------------------------------------------------------
293 CScriptLine::CScriptLine(): _Action(ACT_NOP)
297 CScriptLine::CScriptLine(const CSString& context, const CSString& keyword, const CSString& args): _Action(ACT_NOP), _Context(context), _RawCmdLine(keyword+" "+args)
299 if (keyword=="assert")
301 CSString argTxt= args;
302 CSString varName= argTxt.firstWord(true).strip();
303 CSString op= argTxt.firstWord(true).strip(); // should be a '!' or a '='
304 op+= argTxt.firstWord(true).strip(); // should be a '='
305 DROP_IF(op!="==" && op!="!=",_Context+": Missing '==' or '!=' in line: "+keyword+" "+args,return);
306 _Args.push_back(varName);
307 _Args.push_back(argTxt.strip());
308 _Action= (op=="==")? ACT_ASSERT: ACT_ASSERT_NOT;
310 else if (keyword=="onlyif")
312 CSString argTxt= args;
313 CSString varName= argTxt.firstWord(true).strip();
314 CSString op= argTxt.firstWord(true).strip(); // should be a '!' or a '='
315 op+= argTxt.firstWord(true).strip(); // should be a '='
316 DROP_IF(op!="==" && op!="!=",_Context+": Missing '==' or '!=' in line: "+keyword+" "+args,return);
317 _Args.push_back(varName);
318 _Args.push_back(argTxt.strip());
319 _Action= (op=="==")? ACT_ONLYIF: ACT_ONLYIF_NOT;
321 else if (keyword=="set")
323 CSString argTxt= args;
324 CSString varName= argTxt.firstWord(true).strip();
325 CSString op= argTxt.firstWord(true).strip();
326 DROP_IF(op!="=",_Context+": Missing '=' in line: "+keyword+" "+args,return);
327 _Args.push_back(varName);
328 _Args.push_back(argTxt.strip());
329 _Action= ACT_SET;
331 else if (keyword=="inc") { _Args.push_back(args); _Action= ACT_INC; }
332 else if (keyword=="dec") { _Args.push_back(args); _Action= ACT_DEC; }
333 else if (keyword=="debug") { _Args.push_back(args); _Action= ACT_DEBUG; }
334 else if (keyword=="info") { _Args.push_back(args); _Action= ACT_INFO; }
335 else if (keyword=="warn") { _Args.push_back(args); _Action= ACT_WARN; }
336 else if (keyword=="cmd") { _Args.push_back(args); _Action= ACT_CMD; }
337 else
339 DROP(context+": Unable to build script line from input: "+keyword+" "+args,return);
343 bool CScriptLine::isValid() const
345 return (_Action!=ACT_NOP);
348 CSString CScriptLine::toString() const
350 return _RawCmdLine;
353 bool CScriptLine::execute(CVariableSet& vars, CSString& errorMsg) const
355 // start by clearing out the error message, to avoid confusion
356 errorMsg.clear();
358 switch (_Action)
360 case ACT_NOP:
361 BOMB("Trying to execute bad script command: "+_RawCmdLine,return false);
363 case ACT_ONLYIF:
364 nlassert(_Args.size()==2);
365 if (vars.get(_Args[0]) != _Args[1])
367 return false;
369 break;
371 case ACT_ONLYIF_NOT:
372 nlassert(_Args.size()==2);
373 if (vars.get(_Args[0]) == _Args[1])
375 return false;
377 break;
379 case ACT_ASSERT:
380 nlassert(_Args.size()==2);
381 if (vars.get(_Args[0]) != _Args[1])
383 errorMsg= _Context+": Assert Failed: "+_RawCmdLine;
384 return false;
386 break;
388 case ACT_ASSERT_NOT:
389 nlassert(_Args.size()==2);
390 if (vars.get(_Args[0]) == _Args[1])
392 errorMsg= _Context+": Assert Failed: "+_RawCmdLine;
393 return false;
395 break;
397 case ACT_SET:
398 nlassert(_Args.size()==2);
399 vars.set(_Args[0], vars.expand(_Args[1]));
400 break;
402 case ACT_INC:
403 nlassert(_Args.size()==1);
404 vars.set(_Args[0], NLMISC::toString("%i",vars.get(_Args[0]).atosi()+1));
405 break;
407 case ACT_DEC:
408 nlassert(_Args.size()==1);
409 vars.set(_Args[0], NLMISC::toString("%i",vars.get(_Args[0]).atosi()-1));
410 break;
412 case ACT_DEBUG:
413 nlassert(_Args.size()==1);
414 nldebug("%s",vars.expand(_Args[0]).c_str());
415 break;
417 case ACT_INFO:
418 nlassert(_Args.size()==1);
419 nlinfo("%s",vars.expand(_Args[0]).c_str());
420 break;
422 case ACT_WARN:
423 nlassert(_Args.size()==1);
424 nlwarning("%s",vars.expand(_Args[0]).c_str());
425 break;
427 case ACT_CMD:
428 nlassert(_Args.size()==1);
429 NLMISC::ICommand::execute(vars.expand(_Args[0]),*NLMISC::InfoLog);
430 break;
433 // all went well so return true ... the script can continue
434 return true;
438 //-----------------------------------------------------------------------------
439 // methods CScriptRoutine
440 //-----------------------------------------------------------------------------
442 CScriptRoutine::CScriptRoutine(const CSString& name): _Name(name)
446 bool CScriptRoutine::addLine(const CSString& context, const CSString& keyword,const CSString& args)
448 // try to build a new script line from the supplied text...
449 CScriptLine theNewLine(context,keyword,args);
451 // if we failed to create a valid line from the given text then just return false
452 // there should already have been an explication of the problem in the CScriptLine ctor
453 if (!theNewLine.isValid())
455 return false;
458 // the line is valid so append it to our _Lines vector
459 _Lines.push_back(theNewLine);
460 return true;
463 bool CScriptRoutine::execute(CVariableSet& vars) const
465 for (uint32 i=0; i<_Lines.size(); ++i)
467 // sertup a variable to hold an error message in case of failed assert, etc
468 CSString errorMsg;
470 // execute the line
471 bool ok= _Lines[i].execute(vars,errorMsg);
473 // if the execution returned true then continue to the next line
474 if (ok) continue;
476 // the execute returned false so see if we have an error...
477 if (!errorMsg.empty())
479 nlwarning("Test script failed: %s",errorMsg.c_str());
482 // return success or fail depending on whether we have an error message...
483 return errorMsg.empty();
486 // the script executed successfully through to the end
487 return true;
490 bool CScriptRoutine::isEmpty() const
492 return _Lines.empty();
495 CSString CScriptRoutine::getName() const
497 return _Name;
501 //-----------------------------------------------------------------------------
502 // methods CPatchmanTesterImplementation
503 //-----------------------------------------------------------------------------
505 void CPatchmanTesterImplementation::clear()
507 clearScript();
508 clearState();
511 void CPatchmanTesterImplementation::clearScript()
513 _Scripts.clear();
516 void CPatchmanTesterImplementation::clearState()
518 _VariableSet.clear();
521 void CPatchmanTesterImplementation::loadScript(const NLMISC::CSString& fileName)
523 // setup variables used by recursive script file reader
524 uint32 errors=0;
525 TFileNameSet fileNameSet;
527 // try reading ht e script file
528 _readScriptFile(fileName,errors,fileNameSet);
530 // if there were errors then abort the read...
531 if (errors!=0)
533 nlwarning("Script parse failed: %u errors encountered",errors);
534 clear();
538 void CPatchmanTesterImplementation::_readScriptFile(const NLMISC::CSString& fileName,uint32& errors,CPatchmanTesterImplementation::TFileNameSet& fileNameSet)
540 // read in the src file
541 NLMISC::CSString fileContents;
542 fileContents.readFromFile(fileName);
543 DROP_IF(fileContents.empty(),"File not found: "+fileName, ++errors;return);
545 // split the file into lines
546 NLMISC::CVectorSString lines;
547 fileContents.splitLines(lines);
549 // process the lines one by one
550 for (uint32 i=0;i<lines.size();++i)
552 // setup a context string to pre-pend to error messages
553 NLMISC::CSString context= NLMISC::toString("%s:%u: ",fileName.c_str(),i+1);
555 // remove comments and encapsulating blanks
556 NLMISC::CSString line= lines[i].splitTo("//").strip();
557 if (line.empty()) continue;
559 // split the line into keyword and args
560 NLMISC::CSString args= line;
561 NLMISC::CSString keyword= args.strtok(" \t");
563 // try to treat the keyword
564 if (keyword=="include")
566 DROP_IF(args.empty(),context+"No file name found following 'include': "+line, ++errors;continue);
567 DROP_IF(fileNameSet.find(args)!=fileNameSet.end(),context+"Warning: Duplicate 'include' block ignored: "+line, continue);
568 fileNameSet.insert(args);
569 _readScriptFile(args.unquoteIfQuoted(),errors,fileNameSet);
571 else if (keyword=="on")
573 DROP_IF(args.empty(),context+"No event name found following 'on': "+line, ++errors;continue);
574 DROP_IF(args.countWords()!=1,context+"Invalid event name found following 'on': "+line, ++errors;continue);
575 // create a new script and append it to our vector
576 _Scripts.push_back(CScriptRoutine(args));
578 else
580 DROP_IF(_Scripts.empty(),context+"Expecting 'on <event_name>' but found: "+line, ++errors;continue);
581 bool ok= _Scripts.back().addLine(context,keyword,args);
582 if (!ok) ++errors;
587 void CPatchmanTesterImplementation::set(const NLMISC::CSString& variableName,const NLMISC::CSString& value)
589 _VariableSet.set(variableName,value);
590 trigger(variableName);
593 void CPatchmanTesterImplementation::set(const NLMISC::CSString& variableName,sint32 value)
595 set(variableName,NLMISC::toString("%i",value));
598 void CPatchmanTesterImplementation::trigger(const NLMISC::CSString& eventName)
600 for (uint32 i=0;i <_Scripts.size(); ++i)
602 if (_Scripts[i].getName()==eventName)
604 _Scripts[i].execute(_VariableSet);
609 void CPatchmanTesterImplementation::dump(NLMISC::CLog& log)
611 log.displayNL("---------------------------------------------");
612 log.displayNL("dump of patchman's patchtest state variables");
613 log.displayNL("---------------------------------------------");
614 _VariableSet.dump(log);
615 log.displayNL("---------------------------------------------");
616 log.displayNL("list of triggerable scripts");
617 log.displayNL("---------------------------------------------");
619 // build a set of script triggers to eliminate duplicates
620 typedef std::set<CSString> TTheSet;
621 TTheSet theSet;
622 for (uint32 i=0;i<_Scripts.size();++i)
624 theSet.insert(_Scripts[i].getName());
627 // run through the set we just built, displaying scrip names
628 for (TTheSet::iterator it= theSet.begin(); it!=theSet.end(); ++it)
630 log.displayNL("on %s",it->c_str());
633 log.displayNL("---------------------------------------------");
636 void CPatchmanTesterImplementation::help(NLMISC::CLog& log)
638 log.displayNL("---------------------------------------------");
639 log.displayNL("patchman's patchtest script");
640 log.displayNL("---------------------------------------------");
641 log.displayNL("include <file name>");
642 log.displayNL("on <event name>|<variable name>");
643 log.displayNL(" cmd <nlmisc command line>");
644 log.displayNL(" assert <variable> '=='|'!=' <value>|<txt>");
645 log.displayNL(" onlyif <variable> '=='|'!=' <value>|<txt>");
646 log.displayNL(" set <variable> '=' <value>|<txt>");
647 log.displayNL(" inc <variable>");
648 log.displayNL(" dec <variable>");
649 log.displayNL(" warn <variable>");
650 log.displayNL(" info <variable>");
651 log.displayNL(" debug <variable>");
652 log.displayNL("---------------------------------------------");
653 log.displayNL("Note: variable name substitution example:");
654 log.displayNL(" set toto abc d");
655 log.displayNL(" info hello $toto$toto $toto");
656 log.displayNL("=> \"hello abc dabc d abc d\"");
657 log.displayNL("---------------------------------------------");
661 //-----------------------------------------------------------------------------
662 // methods CPatchmanTester
663 //-----------------------------------------------------------------------------
665 CPatchmanTester& CPatchmanTester::getInstance()
667 static CSmartPtr<CPatchmanTesterImplementation> theInstance;
668 if (theInstance==NULL)
670 theInstance= new CPatchmanTesterImplementation;
673 return *theInstance;
676 } // end of namespace
679 NLMISC_CATEGORISED_COMMAND(patchman,patchtestClear,"clear out the patchtest singleton","")
681 if (args.size()!=0)
682 return false;
684 PATCHMAN::CPatchmanTester::getInstance().clear();
685 return true;
688 NLMISC_CATEGORISED_COMMAND(patchman,patchtestDump,"dump the state of the patchtest singleton","")
690 if (args.size()!=0)
691 return false;
693 PATCHMAN::CPatchmanTester::getInstance().dump(log);
694 return true;
697 NLMISC_CATEGORISED_COMMAND(patchman,patchtestLoad,"load a patchtest script","<file name>")
699 if (args.size()!=1)
700 return false;
702 PATCHMAN::CPatchmanTester::getInstance().loadScript(args[0]);
703 return true;
706 NLMISC_CATEGORISED_COMMAND(patchman,patchtestHelp,"display a list of keywords for the patch script","")
708 if (args.size()!=0)
709 return false;
711 PATCHMAN::CPatchmanTester::getInstance().help(log);
712 return true;
715 NLMISC_CATEGORISED_COMMAND(patchman,patchtestSet,"set a patchtest variable","<var name> <value>")
717 if (args.size()!=2)
718 return false;
720 PATCHMAN::CPatchmanTester::getInstance().set(args[0],args[1]);
721 return true;
724 NLMISC_CATEGORISED_COMMAND(patchman,patchtestTrigger,"trigger a patchtest event","<event name>")
726 if (args.size()!=1)
727 return false;
729 PATCHMAN::CPatchmanTester::getInstance().trigger(args[0]);
730 return true;