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]) |   _AM_SUBST_NOTMAKE([AUTHOR]) | ||||||
|   DISTRO=$(lsb_release -sc 2>/dev/null || uname -s 2>/dev/null) |   DISTRO=$(lsb_release -sc 2>/dev/null || uname -s 2>/dev/null) | ||||||
|   AX_SUBST(DISTRO) |   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) |   DISTRIBUTOR=$(lsb_release -si 2>/dev/null || uname -s 2>/dev/null) | ||||||
|   case "${DISTRIBUTOR// /-}" in |   case "${DISTRIBUTOR// /-}" in | ||||||
|     (Ubuntu) UBUNTU=1; AX_SUBST(UBUNTU);; |     (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;; |         (--configure|-c) configure=1;; | ||||||
|         (--docker|-d) docker=1;; |         (--docker|-d) docker=1;; | ||||||
|         (--build|-b) configure=1; build=1; buildtarget+=" distcheck";; |         (--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";; |         (--clean) shift; configure=1; build=1; buildtarget+=" maintainer-clean";; | ||||||
|  |         (--target|-t) shift; configure=1; build=1; buildtarget+=" $1";; | ||||||
|         (--overwrite|-o) overwrite=1;; |         (--overwrite|-o) overwrite=1;; | ||||||
|         (--rebuild|-r) rebuild=1;; |         (--rebuild|-r) rebuild=1;; | ||||||
|         (--rebuild-file|-f) shift; rebuildfiles+=("$1");; |         (--rebuild-file|-f) shift; rebuildfiles+=("$1");; | ||||||
| @@ -51,6 +52,8 @@ OPTIONS | |||||||
|   --configure, -c            call ./configure after initialization |   --configure, -c            call ./configure after initialization | ||||||
|   --docker, -d               build and run tests in a docker instance |   --docker, -d               build and run tests in a docker instance | ||||||
|   --build, -b                build, also call ./configure && make distcheck |   --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 |   --target, -t <target>      same as -b, but specify target instead of distcheck | ||||||
|   --overwrite, -o            overwrite all basic files (bootstrap.sh, m4-macros) |   --overwrite, -o            overwrite all basic files (bootstrap.sh, m4-macros) | ||||||
|   --rebuild, -r              force rebuild of generated files, even if modified |   --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-debbuilddeps.sh - script to install debian package dependencies | ||||||
|     * resolve-rpmbuilddeps.sh - script to install RPM 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.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 |     * 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 |     * 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 |     * 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@ | Cflags: -I\${includedir} @CPPFLAGS@ | ||||||
| Requires: @PKG_REQUIREMENTS@ | Requires: @PKG_REQUIREMENTS@ | ||||||
| EOF | 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 #### | #### Cleanup If Makefile Exists #### | ||||||
| if test -f makefile; then | if test -f makefile; then | ||||||
| @@ -1177,15 +1187,15 @@ run autoconf | |||||||
|  |  | ||||||
| #### Run Configure If User Requires #### | #### Run Configure If User Requires #### | ||||||
| if test "$configure" -eq 1; then | if test "$configure" -eq 1; then | ||||||
|     ./configure $* |     ./configure $* || exit 1 | ||||||
| fi | fi | ||||||
|  |  | ||||||
| #### Run Make If User Requires #### | #### Run Make If User Requires #### | ||||||
| if test "$build" -eq 1; then | if test "$build" -eq 1; then | ||||||
|     make $buildtarget |     make $buildtarget || exit 1 | ||||||
| fi | fi | ||||||
|  |  | ||||||
| #### Build In Docker If User Requires #### | #### Build In Docker If User Requires #### | ||||||
| if test "$docker" -eq 1; then | if test "$docker" -eq 1; then | ||||||
|     ./build-in-docker.sh |     ./build-in-docker.sh || exit 1 | ||||||
| fi | fi | ||||||
|   | |||||||
| @@ -12,10 +12,17 @@ | |||||||
|  |  | ||||||
| SCHROOTNAME="$1" | SCHROOTNAME="$1" | ||||||
| PACKAGE_NAME=$(sed -n 's/^ *m4_define(x_package_name, \(.*\)).*/\1/p' configure.ac) | 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 | 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') |     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 |     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 -- yum install -y ${FILES} || \ | ||||||
|             schroot -c ${SCHROOTNAME} -u root -- zypper install -y ${FILES} || \ |             schroot -c ${SCHROOTNAME} -u root -- zypper install -y ${FILES} || \ | ||||||
|             schroot -c ${SCHROOTNAME} -u root -- dnf install -y ${FILES} |             schroot -c ${SCHROOTNAME} -u root -- dnf install -y ${FILES} | ||||||
| @@ -23,6 +30,12 @@ if test -n "${SCHROOTNAME}"; then | |||||||
| else | else | ||||||
|     FILES=$(LANG= rpmbuild -bb --clean --nobuild --define "_topdir ." --define "_sourcedir ." ${PACKAGE_NAME}.spec 2>&1 | sed -n 's, is needed by.*,,p') |     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 |     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} || \ |         yum install -y ${FILES} || \ | ||||||
|             zypper install -y ${FILES} || \ |             zypper install -y ${FILES} || \ | ||||||
|             dnf 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 | 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 | 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 subCommandBlock(QStringList& in) { | ||||||
|       QStringList commands; |       QStringList commands; | ||||||
|       int pos(-1); |       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(' '); |         if (pos<0) pos=in[0].toStdString().find_first_not_of(' '); | ||||||
|         commands += in.takeFirst().mid(pos); |         commands += in.takeFirst().mid(pos); | ||||||
|       } |       } | ||||||
| @@ -805,6 +806,9 @@ class Script: public QObject { | |||||||
|       _rvariables.remove(_variables[name]); |       _rvariables.remove(_variables[name]); | ||||||
|       _variables.remove(name); |       _variables.remove(name); | ||||||
|     } |     } | ||||||
|  |     QStringList functions() { | ||||||
|  |       return _functions.keys(); | ||||||
|  |     } | ||||||
|     void function(QString name, std::shared_ptr<Function> f) { |     void function(QString name, std::shared_ptr<Function> f) { | ||||||
|       _functions[name] = f; |       _functions[name] = f; | ||||||
|     } |     } | ||||||
| @@ -826,7 +830,7 @@ class Script: public QObject { | |||||||
|     QString replacevars(QString txt) { |     QString replacevars(QString txt) { | ||||||
|       for(QMap<QString, QString>::iterator it(_variables.begin()); |       for(QMap<QString, QString>::iterator it(_variables.begin()); | ||||||
|           it!=_variables.end(); ++it) |           it!=_variables.end(); ++it) | ||||||
|         txt.replace(it.key(), it.value()); |         txt.replace(it.key(), it.value(), Qt::CaseSensitive); | ||||||
|       return txt; |       return txt; | ||||||
|     } |     } | ||||||
|     QString insertvars(QString txt) { |     QString insertvars(QString txt) { | ||||||
| @@ -834,7 +838,7 @@ class Script: public QObject { | |||||||
|       it.toBack(); |       it.toBack(); | ||||||
|       while (it.hasPrevious()) { |       while (it.hasPrevious()) { | ||||||
|         it.previous(); |         it.previous(); | ||||||
|         txt.replace(it.key(), it.value()); |         txt.replace(it.key(), it.value(), Qt::CaseSensitive); | ||||||
|       } |       } | ||||||
|       return txt; |       return txt; | ||||||
|     } |     } | ||||||
| @@ -1471,7 +1475,7 @@ class Execute: public Command { | |||||||
|       script.replaceInStrings(QRegularExpression("^"), "  "); |       script.replaceInStrings(QRegularExpression("^"), "  "); | ||||||
|       return tag()+" "+_command |       return tag()+" "+_command | ||||||
|         +(_args.size()?" "+_args.join(' '):QString()) |         +(_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, |     std::shared_ptr<Command> parse(Script*, QString args, | ||||||
|                                    QStringList& in, QString, int, int) { |                                    QStringList& in, QString, int, int) { | ||||||
| @@ -2096,8 +2100,8 @@ class If: public Command { | |||||||
|         "\n\n" |         "\n\n" | ||||||
|         "Execute commands conditionally. " |         "Execute commands conditionally. " | ||||||
|         "The first variant compares a variable to a value. " |         "The first variant compares a variable to a value. " | ||||||
|         "The comparision <cmp> can be = ^ . ~ < >, " |         "The comparision <cmp> can be = ! . ^ ~ < >, " | ||||||
|         "which means equal, different, contains, match, " |         "which means equal, different, contains, contains not, match, " | ||||||
|         "less (as integer), bigger (as integer). " |         "less (as integer), bigger (as integer). " | ||||||
|         "Match allows a regular expression. " |         "Match allows a regular expression. " | ||||||
|         "The second variant checks for a text in a selector, " |         "The second variant checks for a text in a selector, " | ||||||
| @@ -2106,13 +2110,13 @@ class If: public Command { | |||||||
|     } |     } | ||||||
|     QString command() const { |     QString command() const { | ||||||
|       return tag()+" "+_variable+" "+_cmp+" "+_value |       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, |     std::shared_ptr<Command> parse(Script*, QString args, | ||||||
|                                    QStringList& in, QString file, int line, |                                    QStringList& in, QString file, int line, | ||||||
|                                    int indent) { |                                    int indent) { | ||||||
|       std::shared_ptr<If> cmd(new If()); |       std::shared_ptr<If> cmd(new If()); | ||||||
|       int pos(args.indexOf(QRegularExpression("[=^.~<>]"))); |       int pos(args.indexOf(QRegularExpression("[=!.^~<>]"))); | ||||||
|       int len(1); |       int len(1); | ||||||
|       if (args.contains("->")) { |       if (args.contains("->")) { | ||||||
|         pos = args.indexOf("->"); |         pos = args.indexOf("->"); | ||||||
| @@ -2126,7 +2130,7 @@ class If: public Command { | |||||||
|       cmd->_value = args.mid(pos+len).trimmed(); |       cmd->_value = args.mid(pos+len).trimmed(); | ||||||
|       cmd->_script = std::shared_ptr<Script>(new Script); |       cmd->_script = std::shared_ptr<Script>(new Script); | ||||||
|       cmd->_script->parse(subCommandBlock(in), file, line+1, indent+1); |       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(); |         in.removeFirst(); | ||||||
|         cmd->_else = std::shared_ptr<Script>(new Script); |         cmd->_else = std::shared_ptr<Script>(new Script); | ||||||
|         cmd->_else->parse(subCommandBlock(in), file, line+1, indent+1); |         cmd->_else->parse(subCommandBlock(in), file, line+1, indent+1); | ||||||
| @@ -2153,10 +2157,12 @@ class If: public Command { | |||||||
|         switch (_cmp[0].toLatin1()) { |         switch (_cmp[0].toLatin1()) { | ||||||
|           case '=': check = script->variable(_variable)==value; |           case '=': check = script->variable(_variable)==value; | ||||||
|             break; |             break; | ||||||
|           case '^': check = script->variable(_variable)!=value; |           case '!': check = script->variable(_variable)!=value; | ||||||
|             break; |             break; | ||||||
|           case '.': check = script->variable(_variable).contains(value); |           case '.': check = script->variable(_variable).contains(value); | ||||||
|             break; |             break; | ||||||
|  |           case '^': check = !script->variable(_variable).contains(value); | ||||||
|  |             break; | ||||||
|           case '~': check = |           case '~': check = | ||||||
|               script->variable(_variable).contains(QRegularExpression(value)); |               script->variable(_variable).contains(QRegularExpression(value)); | ||||||
|             break; |             break; | ||||||
| @@ -2166,7 +2172,7 @@ class If: public Command { | |||||||
|             break; |             break; | ||||||
|           default:; |           default:; | ||||||
|         } |         } | ||||||
|         log(QString("evaluated expression to ")+(check?"true":"false")+":" |         log(QString("evaluated expression to ")+(check?"true":"false")+": " | ||||||
|             +script->variable(_variable)+" "+_cmp+" "+value); |             +script->variable(_variable)+" "+_cmp+" "+value); | ||||||
|       } |       } | ||||||
|       if (check) return runScript(this, _script, script, frame); |       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" |         " output, such as <do>, which returns the result of JavaScript or" | ||||||
|         " <execute>, which returns the output of the executed command, or" |         " <execute>, which returns the output of the executed command, or" | ||||||
|         " <call>, which returns the result of the last command. " |         " <call>, which returns the result of the last command. " | ||||||
|         "The comparision <cmp> can be = ^ . ~ < >, " |         "The comparision <cmp> can be = ! . ^ ~ < >, " | ||||||
|         "which means equal, different, contains match, " |         "which means equal, different, contains, contains not, match, " | ||||||
|         "less (as integer), bigger (as integer). " |         "less (as integer), bigger (as integer). " | ||||||
|         "Match allows a regular expression. " |         "Match allows a regular expression. " | ||||||
|         " less than < (integers), larger than > (integers)"; |         " less than < (integers), larger than > (integers)"; | ||||||
| @@ -2272,7 +2278,7 @@ class Check: public Command { | |||||||
|                                    int indent) { |                                    int indent) { | ||||||
|       std::shared_ptr<Check> cmd(new Check()); |       std::shared_ptr<Check> cmd(new Check()); | ||||||
|       cmd->_next = 0; |       cmd->_next = 0; | ||||||
|       int pos(args.indexOf(QRegularExpression("[=^.~<>]"))); |       int pos(args.indexOf(QRegularExpression("[=!.^~<>]"))); | ||||||
|       if (pos<0) throw BadArgument(tag()+" needs a comparision, not: "+args); |       if (pos<0) throw BadArgument(tag()+" needs a comparision, not: "+args); | ||||||
|       cmd->_value1 = args.left(pos).trimmed(); |       cmd->_value1 = args.left(pos).trimmed(); | ||||||
|       cmd->_cmp = args[pos].toLatin1(); |       cmd->_cmp = args[pos].toLatin1(); | ||||||
| @@ -2294,8 +2300,9 @@ class Check: public Command { | |||||||
|       bool check(false); |       bool check(false); | ||||||
|       switch (_cmp) { |       switch (_cmp) { | ||||||
|         case '=': check = value1==value2; break; |         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(value2); break; | ||||||
|         case '~': check = value1.contains(QRegularExpression(value2)); break; |         case '~': check = value1.contains(QRegularExpression(value2)); break; | ||||||
|         case '<': check = value1.toInt()<value2.toInt(); break; |         case '<': check = value1.toInt()<value2.toInt(); 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; |     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: | /* Template: | ||||||
| class : public Command { | class : public Command { | ||||||
|   public: |   public: | ||||||
| @@ -2544,8 +2754,10 @@ inline bool Command::runScript(Command* parentCommand, | |||||||
|     disconnect(&scriptCopy, SIGNAL(logging(QString)), |     disconnect(&scriptCopy, SIGNAL(logging(QString)), | ||||||
|                parent, SLOT(parentlog(QString))); |                parent, SLOT(parentlog(QString))); | ||||||
|     parentCommand->_result = scriptCopy.result(); |     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)); |       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()) |     if (parentCommand->_result.size()) | ||||||
|       parent->log("result: "+parentCommand->_result); |       parent->log("result: "+parentCommand->_result); | ||||||
|     return res; |     return res; | ||||||
| @@ -2590,6 +2802,9 @@ inline void Script::initPrototypes() { | |||||||
|   add(new Echo); |   add(new Echo); | ||||||
|   add(new OfflineStoragePath); |   add(new OfflineStoragePath); | ||||||
|   add(new ClearCookies); |   add(new ClearCookies); | ||||||
|  |   add(new Include); | ||||||
|  |   add(new Case); | ||||||
|  |   add(new Fail); | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif | #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 | #endif | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user