1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
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.
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 //-----------------------------------------------------------------------------
19 //-----------------------------------------------------------------------------
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"
31 #include "game_share/utils.h"
34 #include "patchman_tester.h"
37 //-----------------------------------------------------------------------------
39 //-----------------------------------------------------------------------------
42 using namespace NLMISC
;
45 //-----------------------------------------------------------------------------
47 //-----------------------------------------------------------------------------
51 //-----------------------------------------------------------------------------
53 //-----------------------------------------------------------------------------
58 // clear out the set of variables
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;
75 typedef map
<CSString
,CSString
> TVariables
;
76 TVariables _Variables
;
80 //-----------------------------------------------------------------------------
82 //-----------------------------------------------------------------------------
89 CScriptLine(const CSString
& context
, const CSString
& keyword
, const CSString
& args
);
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;
102 // the invalid operation 'no op'
104 // conditional - stop execution of this routine with 'success' return value if condition fails
105 ACT_ONLYIF
, ACT_ONLYIF_NOT
,
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
116 CSString _RawCmdLine
;
120 CVectorSString _Args
;
124 //-----------------------------------------------------------------------------
125 // class CScriptRoutine
126 //-----------------------------------------------------------------------------
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;
143 bool isEmpty() const;
144 CSString
getName() const;
148 // the script name (the name of the event that triggers it)
152 typedef vector
<CScriptLine
> TLines
;
157 //-----------------------------------------------------------------------------
158 // class CPatchmanTesterImplementation
159 //-----------------------------------------------------------------------------
161 class CPatchmanTesterImplementation
: public CPatchmanTester
, public CRefCount
167 // clear loaded script(s) but leave state intact
170 // clear state variables but leave scripts intact
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
);
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
);
191 typedef std::set
<NLMISC::CSString
> TFileNameSet
;
192 void _readScriptFile(const NLMISC::CSString
& fileName
,uint32
& errors
,TFileNameSet
& fileNameSet
);
195 // state variables (map of var name to value)
196 CVariableSet _VariableSet
;
198 // scripts (vector of routines)
199 typedef vector
<CScriptRoutine
> TScripts
;
204 //-----------------------------------------------------------------------------
205 // methods CVariableSet
206 //-----------------------------------------------------------------------------
208 void CVariableSet::clear()
213 void CVariableSet::set(const CSString
& varName
,const CSString
& value
)
217 _Variables
.erase(varName
);
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
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;
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
);
263 // no variable name found so keep the '$' intact in the result expression (to help debugging)
271 void CVariableSet::dump(CLog
& log
) const
274 // iterate over the variables to determine the length of the longest variable name
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());
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
; }
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
353 bool CScriptLine::execute(CVariableSet
& vars
, CSString
& errorMsg
) const
355 // start by clearing out the error message, to avoid confusion
361 BOMB("Trying to execute bad script command: "+_RawCmdLine
,return false);
364 nlassert(_Args
.size()==2);
365 if (vars
.get(_Args
[0]) != _Args
[1])
372 nlassert(_Args
.size()==2);
373 if (vars
.get(_Args
[0]) == _Args
[1])
380 nlassert(_Args
.size()==2);
381 if (vars
.get(_Args
[0]) != _Args
[1])
383 errorMsg
= _Context
+": Assert Failed: "+_RawCmdLine
;
389 nlassert(_Args
.size()==2);
390 if (vars
.get(_Args
[0]) == _Args
[1])
392 errorMsg
= _Context
+": Assert Failed: "+_RawCmdLine
;
398 nlassert(_Args
.size()==2);
399 vars
.set(_Args
[0], vars
.expand(_Args
[1]));
403 nlassert(_Args
.size()==1);
404 vars
.set(_Args
[0], NLMISC::toString("%i",vars
.get(_Args
[0]).atosi()+1));
408 nlassert(_Args
.size()==1);
409 vars
.set(_Args
[0], NLMISC::toString("%i",vars
.get(_Args
[0]).atosi()-1));
413 nlassert(_Args
.size()==1);
414 nldebug("%s",vars
.expand(_Args
[0]).c_str());
418 nlassert(_Args
.size()==1);
419 nlinfo("%s",vars
.expand(_Args
[0]).c_str());
423 nlassert(_Args
.size()==1);
424 nlwarning("%s",vars
.expand(_Args
[0]).c_str());
428 nlassert(_Args
.size()==1);
429 NLMISC::ICommand::execute(vars
.expand(_Args
[0]),*NLMISC::InfoLog
);
433 // all went well so return true ... the script can continue
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())
458 // the line is valid so append it to our _Lines vector
459 _Lines
.push_back(theNewLine
);
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
471 bool ok
= _Lines
[i
].execute(vars
,errorMsg
);
473 // if the execution returned true then continue to the next line
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
490 bool CScriptRoutine::isEmpty() const
492 return _Lines
.empty();
495 CSString
CScriptRoutine::getName() const
501 //-----------------------------------------------------------------------------
502 // methods CPatchmanTesterImplementation
503 //-----------------------------------------------------------------------------
505 void CPatchmanTesterImplementation::clear()
511 void CPatchmanTesterImplementation::clearScript()
516 void CPatchmanTesterImplementation::clearState()
518 _VariableSet
.clear();
521 void CPatchmanTesterImplementation::loadScript(const NLMISC::CSString
& fileName
)
523 // setup variables used by recursive script file reader
525 TFileNameSet fileNameSet
;
527 // try reading ht e script file
528 _readScriptFile(fileName
,errors
,fileNameSet
);
530 // if there were errors then abort the read...
533 nlwarning("Script parse failed: %u errors encountered",errors
);
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
));
580 DROP_IF(_Scripts
.empty(),context
+"Expecting 'on <event_name>' but found: "+line
, ++errors
;continue);
581 bool ok
= _Scripts
.back().addLine(context
,keyword
,args
);
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
;
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
;
676 } // end of namespace
679 NLMISC_CATEGORISED_COMMAND(patchman
,patchtestClear
,"clear out the patchtest singleton","")
684 PATCHMAN::CPatchmanTester::getInstance().clear();
688 NLMISC_CATEGORISED_COMMAND(patchman
,patchtestDump
,"dump the state of the patchtest singleton","")
693 PATCHMAN::CPatchmanTester::getInstance().dump(log
);
697 NLMISC_CATEGORISED_COMMAND(patchman
,patchtestLoad
,"load a patchtest script","<file name>")
702 PATCHMAN::CPatchmanTester::getInstance().loadScript(args
[0]);
706 NLMISC_CATEGORISED_COMMAND(patchman
,patchtestHelp
,"display a list of keywords for the patch script","")
711 PATCHMAN::CPatchmanTester::getInstance().help(log
);
715 NLMISC_CATEGORISED_COMMAND(patchman
,patchtestSet
,"set a patchtest variable","<var name> <value>")
720 PATCHMAN::CPatchmanTester::getInstance().set(args
[0],args
[1]);
724 NLMISC_CATEGORISED_COMMAND(patchman
,patchtestTrigger
,"trigger a patchtest event","<event name>")
729 PATCHMAN::CPatchmanTester::getInstance().trigger(args
[0]);