new commands: include, case and fail; new emacs wt-mode for webtester files
This commit is contained in:
		| @@ -181,6 +181,8 @@ AC_DEFUN([AX_INIT_STANDARD_PROJECT], [ | ||||
|   _AM_SUBST_NOTMAKE([AUTHOR]) | ||||
|   DISTRO=$(lsb_release -sc 2>/dev/null || uname -s 2>/dev/null) | ||||
|   AX_SUBST(DISTRO) | ||||
|   ARCH=$((@<:@@<:@ $(uname -sm) =~ 64 @:>@@:>@ && echo amd64) || (@<:@@<:@ $(uname -sm) =~ 'i?86' @:>@@:>@ && echo i386 || uname -sm)) | ||||
|   AX_SUBST(ARCH) | ||||
|   DISTRIBUTOR=$(lsb_release -si 2>/dev/null || uname -s 2>/dev/null) | ||||
|   case "${DISTRIBUTOR// /-}" in | ||||
|     (Ubuntu) UBUNTU=1; AX_SUBST(UBUNTU);; | ||||
|   | ||||
							
								
								
									
										18
									
								
								bootstrap.sh
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								bootstrap.sh
									
									
									
									
									
								
							| @@ -31,8 +31,9 @@ while test $# -gt 0; do | ||||
|         (--configure|-c) configure=1;; | ||||
|         (--docker|-d) docker=1;; | ||||
|         (--build|-b) configure=1; build=1; buildtarget+=" distcheck";; | ||||
|         (--target|-t) shift; configure=1; build=1; buildtarget+=" $1";; | ||||
|         (--all|-a) shift; configure=1; build=1; buildtarget+=" all";; | ||||
|         (--clean) shift; configure=1; build=1; buildtarget+=" maintainer-clean";; | ||||
|         (--target|-t) shift; configure=1; build=1; buildtarget+=" $1";; | ||||
|         (--overwrite|-o) overwrite=1;; | ||||
|         (--rebuild|-r) rebuild=1;; | ||||
|         (--rebuild-file|-f) shift; rebuildfiles+=("$1");; | ||||
| @@ -51,6 +52,8 @@ OPTIONS | ||||
|   --configure, -c            call ./configure after initialization | ||||
|   --docker, -d               build and run tests in a docker instance | ||||
|   --build, -b                build, also call ./configure && make distcheck | ||||
|   --all, -a                  same as -b, but make target all | ||||
|   --clean                    same as -b, but make target maintainer-clean | ||||
|   --target, -t <target>      same as -b, but specify target instead of distcheck | ||||
|   --overwrite, -o            overwrite all basic files (bootstrap.sh, m4-macros) | ||||
|   --rebuild, -r              force rebuild of generated files, even if modified | ||||
| @@ -129,6 +132,7 @@ GENERATED FILES | ||||
|     * resolve-debbuilddeps.sh - script to install debian package dependencies | ||||
|     * resolve-rpmbuilddeps.sh - script to install RPM package dependencies | ||||
|     * build-in-docker.sh - script to build the project encapsulated in a docker container | ||||
|     * build-in-docker.conf - additional configuration for build-in-docker.sh | ||||
|     * build-resource-file.sh - build resource.qrc file from a resource directory | ||||
|     * sql-to-dot.sed - script to convert SQL schema files to graphviz dot in doxygen | ||||
|     * mac-create-app-bundle.sh - script to create apple mac os-x app-bundle | ||||
| @@ -1162,6 +1166,12 @@ Libs: -L\${libdir} -l${PACKAGE_NAME#lib} @LDFLAGS@ | ||||
| Cflags: -I\${includedir} @CPPFLAGS@ | ||||
| Requires: @PKG_REQUIREMENTS@ | ||||
| EOF | ||||
| to build-in-docker.conf <<EOF | ||||
| repos+=("Debian|Ubuntu-precise::::::universe") | ||||
| repos+=("Ubuntu-precise:::'deb http://archive.ubuntu.com/ubuntu precise universe'") | ||||
| envs+=("-e 'HOME=\${HOME}'") | ||||
| dirs+=("-v \${HOME}/.gnupg:\${HOME}/.gnupg:ro") | ||||
| EOF | ||||
|  | ||||
| #### Cleanup If Makefile Exists #### | ||||
| if test -f makefile; then | ||||
| @@ -1177,15 +1187,15 @@ run autoconf | ||||
|  | ||||
| #### Run Configure If User Requires #### | ||||
| if test "$configure" -eq 1; then | ||||
|     ./configure $* | ||||
|     ./configure $* || exit 1 | ||||
| fi | ||||
|  | ||||
| #### Run Make If User Requires #### | ||||
| if test "$build" -eq 1; then | ||||
|     make $buildtarget | ||||
|     make $buildtarget || exit 1 | ||||
| fi | ||||
|  | ||||
| #### Build In Docker If User Requires #### | ||||
| if test "$docker" -eq 1; then | ||||
|     ./build-in-docker.sh | ||||
|     ./build-in-docker.sh || exit 1 | ||||
| fi | ||||
|   | ||||
| @@ -12,10 +12,17 @@ | ||||
|  | ||||
| SCHROOTNAME="$1" | ||||
| PACKAGE_NAME=$(sed -n 's/^ *m4_define(x_package_name, \(.*\)).*/\1/p' configure.ac) | ||||
| PKGCONFIGS="${2:-epel-release}" # packages to configure yum | ||||
|  | ||||
| if test -n "${SCHROOTNAME}"; then | ||||
|     FILES=$(LANG= schroot -c ${SCHROOTNAME} -- rpmbuild -bb --clean --nobuild --define "_topdir ." --define "_sourcedir ." ${PACKAGE_NAME}.spec  2>&1 | sed -n 's, is needed by.*,,p') | ||||
|     if test -n "${FILES}"; then | ||||
|         FIRST=$(echo "${FILES}" | egrep -o "${PKGCONFIGS// /|}") | ||||
|         if test -n "${FIRST}"; then | ||||
|             schroot -c ${SCHROOTNAME} -u root -- yum install -y ${FIRST} || \ | ||||
|                 schroot -c ${SCHROOTNAME} -u root -- zypper install -y ${FIRST} || \ | ||||
|                 schroot -c ${SCHROOTNAME} -u root -- dnf install -y ${FIRST} | ||||
|         fi | ||||
|         schroot -c ${SCHROOTNAME} -u root -- yum install -y ${FILES} || \ | ||||
|             schroot -c ${SCHROOTNAME} -u root -- zypper install -y ${FILES} || \ | ||||
|             schroot -c ${SCHROOTNAME} -u root -- dnf install -y ${FILES} | ||||
| @@ -23,6 +30,12 @@ if test -n "${SCHROOTNAME}"; then | ||||
| else | ||||
|     FILES=$(LANG= rpmbuild -bb --clean --nobuild --define "_topdir ." --define "_sourcedir ." ${PACKAGE_NAME}.spec 2>&1 | sed -n 's, is needed by.*,,p') | ||||
|     if test -n "${FILES}"; then | ||||
|         FIRST=$(echo "${FILES}" | egrep -o "${PKGCONFIGS// /|}") | ||||
|         if test -n "${FIRST}"; then | ||||
|             yum install -y ${FIRST} || \ | ||||
|                 zypper install -y ${FIRST} || \ | ||||
|                 dnf install -y ${FIRST} | ||||
|         fi | ||||
|         yum install -y ${FILES} || \ | ||||
|             zypper install -y ${FILES} || \ | ||||
|             dnf install -y ${FILES} | ||||
|   | ||||
							
								
								
									
										9
									
								
								scripts/90wt-mode.wt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								scripts/90wt-mode.wt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| ;; -*-emacs-lisp-*- | ||||
| ;; | ||||
| ;; Emacs startup file for the Debian GNU/Linux wt-mode package | ||||
| ;; | ||||
|  | ||||
| ;; Set up to autoload | ||||
| (autoload 'wt-mode "wt-mode" "Major mode for editing Webtester test scripts" t) | ||||
| (setq auto-mode-alist | ||||
|      (cons '("\\.wt\\'" . wt-mode) auto-mode-alist)) | ||||
| @@ -8,5 +8,11 @@ | ||||
|  | ||||
| dist_bin_SCRIPTS = doxygen-webtester.sed | ||||
|  | ||||
| emacsdir = ${datadir}/emacs/site-lisp | ||||
| dist_emacs_SCRIPTS = wt-mode.el | ||||
|  | ||||
| emacsconfdir = ${sysconfdir}/emacs/site-start.d | ||||
| dist_emacsconf = 90wt-mode.el | ||||
|  | ||||
| MAINTAINERCLEANFILES = makefile.in | ||||
|  | ||||
|   | ||||
							
								
								
									
										48
									
								
								scripts/wt-mode.el
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								scripts/wt-mode.el
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| ;; Define an Emacs Mode for Webtester | ||||
| ;; | ||||
| ;; Features: | ||||
| ;;  - indentation support | ||||
| ;;  - syntax highlighting | ||||
| ;; | ||||
| ;; Documentations: | ||||
| ;;  - mode tutorial: | ||||
| ;;    https://www.emacswiki.org/emacs/ModeTutorial | ||||
| ;;  - faces for font lock: | ||||
| ;;    https://www.gnu.org/software/emacs/manual/html_node/elisp/Faces-for-Font-Lock.html | ||||
|  | ||||
| (defvar wt-mode-hook nil) | ||||
|  | ||||
| (defvar wt-mode-map | ||||
|   (let ((map (make-sparse-keymap))) | ||||
|     (define-key map "\C-j" 'newline-and-indent) | ||||
|     map) | ||||
|   "Newline and indent") | ||||
|  | ||||
| (add-to-list 'auto-mode-alist '("\\.wt\\'" . wt-mode)) | ||||
|  | ||||
|  | ||||
| ;; get all keywords: | ||||
| ;;   echo $(webrunner -h | sed -n 's,  COMMAND: ,,p') | sed 's, ,\\\\|,g' | ||||
| (defconst wt-font-lock-keywords | ||||
|   (list | ||||
|    '("^ *\\(ca-certificate\\|call\\|case\\|check\\|clear-cookies\\|click\\|clicktype\\|client-certificate\\|do\\|download\\|echo\\|execute\\|exists\\|exit\\|expect\\|fail\\|for\\|function\\|if\\|ignoreto\\|include\\|label\\|load\\|not\\|offline-storage-path\\|open\\|screenshot\\|set\\|setvalue\\|sleep\\|testcase\\|testsuite\\|timeout\\|unset\\|upload\\)" . font-lock-builtin-face) | ||||
|    '("^ *#.*$" . font-lock-comment-face)) | ||||
|   "Highlighting expressions for Webtester") | ||||
|  | ||||
| (defvar wt-mode-syntax-table | ||||
|   (let ((st (make-syntax-table))) | ||||
|     st) | ||||
|   "Syntax table for wt-mode") | ||||
|  | ||||
| (defun wt-mode () | ||||
|   "Major mode for editing Webtester test scripts" | ||||
|   (interactive) | ||||
|   (kill-all-local-variables) | ||||
|   (set-syntax-table wt-mode-syntax-table) | ||||
|   (use-local-map wt-mode-map) | ||||
|   (set (make-local-variable 'font-lock-defaults) '(wt-font-lock-keywords)) | ||||
|   (setq major-mode 'wt-mode) | ||||
|   (setq mode-name "Webtester") | ||||
|   (run-hooks 'wpdl-mode-hook)) | ||||
|  | ||||
| (provide 'wt-mode) | ||||
							
								
								
									
										247
									
								
								src/commands.hxx
									
									
									
									
									
								
							
							
						
						
									
										247
									
								
								src/commands.hxx
									
									
									
									
									
								
							| @@ -176,7 +176,8 @@ class Command: public QObject { | ||||
|     QStringList subCommandBlock(QStringList& in) { | ||||
|       QStringList commands; | ||||
|       int pos(-1); | ||||
|       while (in.size() && in[0].size() && in[0][0]==' ') { | ||||
|       while (in.size() && in[0].size() && in[0][0]==' ' | ||||
|              && pos<=(signed)in[0].toStdString().find_first_not_of(' ')) { | ||||
|         if (pos<0) pos=in[0].toStdString().find_first_not_of(' '); | ||||
|         commands += in.takeFirst().mid(pos); | ||||
|       } | ||||
| @@ -805,6 +806,9 @@ class Script: public QObject { | ||||
|       _rvariables.remove(_variables[name]); | ||||
|       _variables.remove(name); | ||||
|     } | ||||
|     QStringList functions() { | ||||
|       return _functions.keys(); | ||||
|     } | ||||
|     void function(QString name, std::shared_ptr<Function> f) { | ||||
|       _functions[name] = f; | ||||
|     } | ||||
| @@ -826,7 +830,7 @@ class Script: public QObject { | ||||
|     QString replacevars(QString txt) { | ||||
|       for(QMap<QString, QString>::iterator it(_variables.begin()); | ||||
|           it!=_variables.end(); ++it) | ||||
|         txt.replace(it.key(), it.value()); | ||||
|         txt.replace(it.key(), it.value(), Qt::CaseSensitive); | ||||
|       return txt; | ||||
|     } | ||||
|     QString insertvars(QString txt) { | ||||
| @@ -834,7 +838,7 @@ class Script: public QObject { | ||||
|       it.toBack(); | ||||
|       while (it.hasPrevious()) { | ||||
|         it.previous(); | ||||
|         txt.replace(it.key(), it.value()); | ||||
|         txt.replace(it.key(), it.value(), Qt::CaseSensitive); | ||||
|       } | ||||
|       return txt; | ||||
|     } | ||||
| @@ -1471,7 +1475,7 @@ class Execute: public Command { | ||||
|       script.replaceInStrings(QRegularExpression("^"), "  "); | ||||
|       return tag()+" "+_command | ||||
|         +(_args.size()?" "+_args.join(' '):QString()) | ||||
|         +(script.size()?"\n"+script.join("\n"):QString()); | ||||
|         +(script.size()?"\n  "+script.join("\n  "):QString()); | ||||
|     } | ||||
|     std::shared_ptr<Command> parse(Script*, QString args, | ||||
|                                    QStringList& in, QString, int, int) { | ||||
| @@ -2096,8 +2100,8 @@ class If: public Command { | ||||
|         "\n\n" | ||||
|         "Execute commands conditionally. " | ||||
|         "The first variant compares a variable to a value. " | ||||
|         "The comparision <cmp> can be = ^ . ~ < >, " | ||||
|         "which means equal, different, contains, match, " | ||||
|         "The comparision <cmp> can be = ! . ^ ~ < >, " | ||||
|         "which means equal, different, contains, contains not, match, " | ||||
|         "less (as integer), bigger (as integer). " | ||||
|         "Match allows a regular expression. " | ||||
|         "The second variant checks for a text in a selector, " | ||||
| @@ -2106,13 +2110,13 @@ class If: public Command { | ||||
|     } | ||||
|     QString command() const { | ||||
|       return tag()+" "+_variable+" "+_cmp+" "+_value | ||||
|         +(_script.get()?"\n"+_script->print().join("\n  "):""); | ||||
|         +(_script.get()?"\n  "+_script->print().join("\n  "):""); | ||||
|     } | ||||
|     std::shared_ptr<Command> parse(Script*, QString args, | ||||
|                                    QStringList& in, QString file, int line, | ||||
|                                    int indent) { | ||||
|       std::shared_ptr<If> cmd(new If()); | ||||
|       int pos(args.indexOf(QRegularExpression("[=^.~<>]"))); | ||||
|       int pos(args.indexOf(QRegularExpression("[=!.^~<>]"))); | ||||
|       int len(1); | ||||
|       if (args.contains("->")) { | ||||
|         pos = args.indexOf("->"); | ||||
| @@ -2126,7 +2130,7 @@ class If: public Command { | ||||
|       cmd->_value = args.mid(pos+len).trimmed(); | ||||
|       cmd->_script = std::shared_ptr<Script>(new Script); | ||||
|       cmd->_script->parse(subCommandBlock(in), file, line+1, indent+1); | ||||
|       if (in.size() && in.first().contains(QRegularExpression("^else *$"))) { | ||||
|       if (in.size() && in.first().contains(QRegularExpression("^ *else *$"))) { | ||||
|         in.removeFirst(); | ||||
|         cmd->_else = std::shared_ptr<Script>(new Script); | ||||
|         cmd->_else->parse(subCommandBlock(in), file, line+1, indent+1); | ||||
| @@ -2153,10 +2157,12 @@ class If: public Command { | ||||
|         switch (_cmp[0].toLatin1()) { | ||||
|           case '=': check = script->variable(_variable)==value; | ||||
|             break; | ||||
|           case '^': check = script->variable(_variable)!=value; | ||||
|           case '!': check = script->variable(_variable)!=value; | ||||
|             break; | ||||
|           case '.': check = script->variable(_variable).contains(value); | ||||
|             break; | ||||
|           case '^': check = !script->variable(_variable).contains(value); | ||||
|             break; | ||||
|           case '~': check = | ||||
|               script->variable(_variable).contains(QRegularExpression(value)); | ||||
|             break; | ||||
| @@ -2166,7 +2172,7 @@ class If: public Command { | ||||
|             break; | ||||
|           default:; | ||||
|         } | ||||
|         log(QString("evaluated expression to ")+(check?"true":"false")+":" | ||||
|         log(QString("evaluated expression to ")+(check?"true":"false")+": " | ||||
|             +script->variable(_variable)+" "+_cmp+" "+value); | ||||
|       } | ||||
|       if (check) return runScript(this, _script, script, frame); | ||||
| @@ -2255,8 +2261,8 @@ class Check: public Command { | ||||
|         " output, such as <do>, which returns the result of JavaScript or" | ||||
|         " <execute>, which returns the output of the executed command, or" | ||||
|         " <call>, which returns the result of the last command. " | ||||
|         "The comparision <cmp> can be = ^ . ~ < >, " | ||||
|         "which means equal, different, contains match, " | ||||
|         "The comparision <cmp> can be = ! . ^ ~ < >, " | ||||
|         "which means equal, different, contains, contains not, match, " | ||||
|         "less (as integer), bigger (as integer). " | ||||
|         "Match allows a regular expression. " | ||||
|         " less than < (integers), larger than > (integers)"; | ||||
| @@ -2272,7 +2278,7 @@ class Check: public Command { | ||||
|                                    int indent) { | ||||
|       std::shared_ptr<Check> cmd(new Check()); | ||||
|       cmd->_next = 0; | ||||
|       int pos(args.indexOf(QRegularExpression("[=^.~<>]"))); | ||||
|       int pos(args.indexOf(QRegularExpression("[=!.^~<>]"))); | ||||
|       if (pos<0) throw BadArgument(tag()+" needs a comparision, not: "+args); | ||||
|       cmd->_value1 = args.left(pos).trimmed(); | ||||
|       cmd->_cmp = args[pos].toLatin1(); | ||||
| @@ -2294,8 +2300,9 @@ class Check: public Command { | ||||
|       bool check(false); | ||||
|       switch (_cmp) { | ||||
|         case '=': check = value1==value2; break; | ||||
|         case '^': check = value1!=value2; break; | ||||
|         case '!': check = value1!=value2; break; | ||||
|         case '.': check = value1.contains(value2); break; | ||||
|         case '^': check = !value1.contains(value2); break; | ||||
|         case '~': check = value1.contains(QRegularExpression(value2)); break; | ||||
|         case '<': check = value1.toInt()<value2.toInt(); break; | ||||
|         case '>': check = value1.toInt()>value2.toInt(); break; | ||||
| @@ -2459,6 +2466,209 @@ class ClearCookies: public Command { | ||||
|     QString _url; | ||||
| }; | ||||
|  | ||||
| class Include: public Command { | ||||
|   public: | ||||
|     QString tag() const { | ||||
|       return "include"; | ||||
|     } | ||||
|     QString description() const { | ||||
|       return | ||||
|         tag()+" <filename>" | ||||
|         "\n\n" | ||||
|         "Include a test script."; | ||||
|     } | ||||
|     QString command() const { | ||||
|       return tag()+" "+_filename; | ||||
|     } | ||||
|     std::shared_ptr<Command> parse(Script* script, QString args, | ||||
|                                    QStringList&, QString, int, int indent) { | ||||
|       std::shared_ptr<Include> cmd(new Include()); | ||||
|       cmd->_filename = args; | ||||
|       QFile f(cmd->_filename); | ||||
|       if (!f.open(QIODevice::ReadOnly)) throw OpenIncludeFailed(cmd->_filename); | ||||
|       QString txt(QString::fromUtf8(f.readAll())); | ||||
|       try { | ||||
|         cmd->_script = std::shared_ptr<Script>(new Script); | ||||
|         cmd->_script->parse(txt.split('\n'), cmd->_filename, 1, indent+1); | ||||
|       } catch (std::exception& e) { | ||||
|         throw ParseIncludeFailed(cmd->_filename, e.what()); | ||||
|       } | ||||
|       return cmd; | ||||
|     } | ||||
|     bool execute(Script* script, QWebFrame* frame) try { | ||||
|       Logger log(this, script); | ||||
|       return runScript(this, _script, script, frame); | ||||
|     } catch (std::exception& e) { | ||||
|       throw ExecuteIncludeFailed(_filename, e.what()); | ||||
|     } | ||||
|   private: | ||||
|     QString _filename; | ||||
|     std::shared_ptr<Script> _script; | ||||
| }; | ||||
|  | ||||
| class Case: public Command { | ||||
|   public: | ||||
|     QString tag() const { | ||||
|       return "case"; | ||||
|     } | ||||
|     QString description() const { | ||||
|       return | ||||
|         tag()+" <variable>\n" | ||||
|         "  <cmp1> <value1>\n" | ||||
|         "    <command>\n" | ||||
|         "    <command>\n" | ||||
|         "  <cmp1> <value2>\n" | ||||
|         "    <command>\n" | ||||
|         "    <command>\n" | ||||
|         "  <...>\n" | ||||
|         "  default\n" | ||||
|         "    <command>\n" | ||||
|         "    <command>\n" | ||||
|         "    <...>\n" | ||||
|         "\n\n"+ | ||||
|         tag()+" <selector>\n" | ||||
|         "  -> <text1>\n" | ||||
|         "    <command>\n" | ||||
|         "    <command>\n" | ||||
|         "  -> <text2>\n" | ||||
|         "    <command>\n" | ||||
|         "    <command>\n" | ||||
|         "  <...>\n" | ||||
|         "  default\n" | ||||
|         "    <command>\n" | ||||
|         "    <command>\n" | ||||
|         "    <...>\n" | ||||
|         "\n\n" | ||||
|         "Execute commands conditionally depending on a variable. " | ||||
|         "It is equivalent to neested if-else-if commands." | ||||
|         "The first variant compares a variable to a value. " | ||||
|         "The comparision <cmp> can be = ! . ^ ~ < >, " | ||||
|         "which means equal, different, contains, contains not, match, " | ||||
|         "less (as integer), bigger (as integer). " | ||||
|         "Match allows a regular expression. " | ||||
|         "The second variant checks for a text in a selector, " | ||||
|         "similar to command exists. " | ||||
|         "There is an optional default part that applies if none " | ||||
|         "of the previous conditions match."; | ||||
|     } | ||||
|     QString command() const { | ||||
|       QString body; | ||||
|       Q_FOREACH(Condition condition, _conditions) { | ||||
|         body += "\n  "+condition.cmp+" "+condition.value+"\n    " | ||||
|           +condition.script->print().join("\n    "); | ||||
|       } | ||||
|       return tag()+" "+_variable+body; | ||||
|     } | ||||
|     std::shared_ptr<Command> parse(Script*, QString args, | ||||
|                                    QStringList& in, QString file, int line, | ||||
|                                    int indent) { | ||||
|       std::shared_ptr<Case> cmd(new Case()); | ||||
|       if (!args.size()) throw BadArgument(tag()+" requires a <variable> or <selector>"); | ||||
|       cmd->_variable = args; | ||||
|       QStringList body(subCommandBlock(in)); | ||||
|       while (body.size()) { | ||||
|         ++line; | ||||
|         QStringList parts(body.takeFirst().split(' ')); | ||||
|         QString cmp(parts.takeFirst()); | ||||
|         QString value(parts.join(' ')); | ||||
|         if (!cmp.contains(QRegularExpression("^[=!.^~<>]|->|default$"))) | ||||
|           throw BadArgument(tag()+" needs a comparision, not: "+cmp); | ||||
|         std::shared_ptr<Script> script(std::shared_ptr<Script>(new Script)); | ||||
|         script->parse(subCommandBlock(body), file, line+1, indent+2); | ||||
|         cmd->_conditions.append(Condition(cmp, value, script)); | ||||
|       } | ||||
|       return cmd; | ||||
|     } | ||||
|     bool execute(Script* script, QWebFrame* frame) { | ||||
|       Logger log(this, script, false); | ||||
|       QString selector(script->replacevars(_variable)); | ||||
|       Q_FOREACH(Condition condition, _conditions) { | ||||
|         QString value(script->replacevars(condition.value)); | ||||
|         bool check(false); | ||||
|         if (condition.cmp=="default") { | ||||
|           log("terminate with default branch"); | ||||
|           check = true; | ||||
|         } else if (condition.cmp=="->") { | ||||
|           Q_FOREACH(QWebElement element, frame->findAllElements(selector)) { | ||||
|             if (value.isEmpty() || // just find element | ||||
|                 element.toOuterXml().indexOf(value)!=-1 || | ||||
|                 element.toPlainText().indexOf(value)!=-1) { | ||||
|               check = true; | ||||
|               break; | ||||
|             } | ||||
|           } | ||||
|           log(QString("evaluated expression to ")+(check?"true":"false")+": " | ||||
|               +selector+" "+condition.cmp+" "+value); | ||||
|         } else { | ||||
|           switch (condition.cmp[0].toLatin1()) { | ||||
|             case '=': check = script->variable(_variable)==value; | ||||
|               break; | ||||
|             case '!': check = script->variable(_variable)!=value; | ||||
|               break; | ||||
|             case '.': check = script->variable(_variable).contains(value); | ||||
|               break; | ||||
|             case '^': check = !script->variable(_variable).contains(value); | ||||
|               break; | ||||
|             case '~': check = | ||||
|                 script->variable(_variable).contains(QRegularExpression(value)); | ||||
|               break; | ||||
|             case '<': check = script->variable(_variable).toInt()<value.toInt(); | ||||
|               break; | ||||
|             case '>': check = script->variable(_variable).toInt()>value.toInt(); | ||||
|               break; | ||||
|             default:; | ||||
|           } | ||||
|           log(QString("evaluated expression to ")+(check?"true":"false")+": " | ||||
|               +script->variable(_variable)+" "+condition.cmp+" "+value); | ||||
|         } | ||||
|         if (check) return runScript(this, condition.script, script, frame); | ||||
|       } | ||||
|       return true; | ||||
|     } | ||||
|   private: | ||||
|     struct Condition { | ||||
|         Condition(QString c, QString v, std::shared_ptr<Script> s): | ||||
|             cmp(c), value(v), script(s) { | ||||
|         } | ||||
|         Condition() {} | ||||
|         QString cmp; | ||||
|         QString value; | ||||
|         std::shared_ptr<Script> script; | ||||
|     }; | ||||
|     QString _variable; | ||||
|     QVector<Condition> _conditions; | ||||
| }; | ||||
|  | ||||
| class Fail: public Command { | ||||
|   public: | ||||
|     QString tag() const { | ||||
|       return "fail"; | ||||
|     } | ||||
|     QString description() const { | ||||
|       return | ||||
|         tag()+" <text>" | ||||
|         "\n\n" | ||||
|         "Fail with error text."; | ||||
|     } | ||||
|     QString command() const { | ||||
|       return tag()+" "+_text; | ||||
|     } | ||||
|     std::shared_ptr<Command> parse(Script*, QString args, | ||||
|                                    QStringList&, QString, int, int) { | ||||
|       std::shared_ptr<Fail> cmd(new Fail()); | ||||
|       cmd->_text = args; | ||||
|       return cmd; | ||||
|     } | ||||
|     bool execute(Script* script, QWebFrame*) { | ||||
|       Logger log(this, script); | ||||
|       throw TestFailed(script->replacevars(_text)); | ||||
|       return true; // dummy | ||||
|     } | ||||
|   private: | ||||
|     QString _text; | ||||
| }; | ||||
|  | ||||
|  | ||||
| /* Template: | ||||
| class : public Command { | ||||
|   public: | ||||
| @@ -2544,8 +2754,10 @@ inline bool Command::runScript(Command* parentCommand, | ||||
|     disconnect(&scriptCopy, SIGNAL(logging(QString)), | ||||
|                parent, SLOT(parentlog(QString))); | ||||
|     parentCommand->_result = scriptCopy.result(); | ||||
|     Q_FOREACH(QString key, scriptCopy.variables()) | ||||
|     Q_FOREACH(QString key, scriptCopy.variables()) // copy new variables to parent | ||||
|       if (!vars.contains(key)) parent->set(key, scriptCopy.variable(key)); | ||||
|     Q_FOREACH(QString key, scriptCopy.functions()) // copy new functions to parent | ||||
|       parent->function(key, scriptCopy.function(key)); | ||||
|     if (parentCommand->_result.size()) | ||||
|       parent->log("result: "+parentCommand->_result); | ||||
|     return res; | ||||
| @@ -2590,6 +2802,9 @@ inline void Script::initPrototypes() { | ||||
|   add(new Echo); | ||||
|   add(new OfflineStoragePath); | ||||
|   add(new ClearCookies); | ||||
|   add(new Include); | ||||
|   add(new Case); | ||||
|   add(new Fail); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -269,4 +269,25 @@ class CheckFailed: public TestFailed { | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class OpenIncludeFailed: public TestFailed { | ||||
|   public: | ||||
|     OpenIncludeFailed(QString file): | ||||
|         TestFailed(QString("open include file %1 failed").arg(file)) { | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class ParseIncludeFailed: public TestFailed { | ||||
|   public: | ||||
|     ParseIncludeFailed(QString file, QString msg): | ||||
|         TestFailed(QString("parse include file %1 failed with: %2").arg(file).arg(msg)) { | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class ExecuteIncludeFailed: public TestFailed { | ||||
|   public: | ||||
|     ExecuteIncludeFailed(QString file, QString msg): | ||||
|         TestFailed(QString("error in included file %1: %2").arg(file).arg(msg)) { | ||||
|     } | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
		Reference in New Issue
	
	Block a user