X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=development%2Fautotests%2Fkeytest.py;h=dab92a8ca07ed184cf14e79807a8f5914098cde8;hb=cb3922093016875404aedc6d1fcf2e088944b5c9;hp=f5dfa6faa373091b7d7cd9907a8d86706a55d301;hpb=0abdc3dd59e1e21f93e4bbce640b29886b9e29df;p=lyx.git diff --git a/development/autotests/keytest.py b/development/autotests/keytest.py index f5dfa6faa3..dab92a8ca0 100755 --- a/development/autotests/keytest.py +++ b/development/autotests/keytest.py @@ -14,6 +14,9 @@ import os import re import sys import time +import tempfile +import shutil + #from subprocess import call import subprocess @@ -164,16 +167,18 @@ class CommandSourceFromFile(CommandSource): class ControlFile: def __init__(self): - self.control = re.compile(r'^(C[ONPpRrC]):\s*(.*)$') + self.control = re.compile(r'^(C[ONPpRrC])([A-Za-z0-9]*):\s*(.*)$') self.fileformat = re.compile(r'^((\>\>?)[,\s]\s*)?([^\s]+)\s*$') - self.cntrname = None - self.cntrfile = None - - def open(self, filename): - if not self.cntrfile is None: - self.cntrfile.close() - self.cntrfile = None - self.cntrname = None + self.cntrfile = dict() + # Map keytest marker to pattern-file-marker for searchPatterns.pl + self.convertSearchMark = { 'CN': 'Comment: ', + 'CP': 'Simple: ', 'Cp': 'ErrSimple: ', + 'CR': 'Regex: ', 'Cr': 'ErrRegex: '} + + def __open(self, handle, filename): + if handle in self.cntrfile: + self.cntrfile[handle].close() + del self.cntrfile[handle] m = self.fileformat.match(filename) if m: type = m.group(2) @@ -184,50 +189,48 @@ class ControlFile: append = False else: append = False - self.cntrname = filename if append: - self.cntrfile = open(filename, 'a') + self.cntrfile[handle] = open(filename, 'a') else: - self.cntrfile = open(filename, 'w') + self.cntrfile[handle] = open(filename, 'w') - def close(self): - if not self.cntrfile is None: - self.cntrfile.close() - self.cntrfile = None - self.cntrname = None - # make the method below 'private' - def __addline(self, pat): - self.cntrfile.writelines(pat + "\n") + def closeall(self): + handles = self.cntrfile.keys() + for handle in handles: + self.__close(handle) - def getfname(self): - return self.cntrname + def __close(self, handle): + if handle in self.cntrfile: + name = self.cntrfile[handle].name + self.cntrfile[handle].close() + del self.cntrfile[handle] + print("Closed ctrl " + handle + " (" + name + ")") + + # make the method below 'private' + def __addline(self, handle, pat): + self.cntrfile[handle].writelines(pat + "\n") def dispatch(self, c): m = self.control.match(c) if not m: return False command = m.group(1) - text = m.group(2) + handle = m.group(2) + if handle is None: + handle = "" + text = m.group(3) if command == "CO": - self.open(text); + self.__open(handle, text); elif command == "CC": - self.close() + self.__close(handle) else: - if self.cntrfile is None: - print("Controlfile not initialized") - else: - if command == "CN": - self.__addline("Comment: " + text) - elif command == "CP": - self.__addline("Simple: " + text) - elif command == "Cp": - self.__addline("ErrSimple: " + text) - elif command == "CR": - self.__addline("Regex: " + text) - elif command == "Cr": - self.__addline("ErrRegex: " + text) + if handle in self.cntrfile: + if command in self.convertSearchMark: + self.__addline(handle, self.convertSearchMark[command] + text) else: die(1,"Error, Unrecognised Command '" + command + "'") + elif handle != "": + die(1, "Ctrl-file " + handle + " not in use") return True @@ -383,80 +386,83 @@ def sendKeystringLocal(keystr, LYX_PID): subprocess.call(xvpar, stdout = FNULL, stderr = FNULL) sys.stdout.flush() -Axreg = re.compile(r'^(.*)\\Ax([^\\]*)(.*)$') -returnreg = re.compile(r'(\\\[[A-Z][a-z0-9]+\])(.*)$') - -# recursive wrapper around sendKeystringLocal() -# handling \Ax-entries -def sendKeystringAx(line, LYX_PID): - global key_delay - saved_delay = key_delay - m = Axreg.match(line) +def extractmultiple(line, regex): + #print("extractmultiple " + line) + res = ["", ""] + m = regex.match(line) if m: - prefix = m.group(1) - content = m.group(2) - rest = m.group(3); - if prefix != "": - # since (.*) is greedy, check prefix for '\Ax' again - sendKeystringAx(prefix, LYX_PID) - sendKeystringLocal('\Ax', LYX_PID) - time.sleep(0.1) - m2 = returnreg.match(rest) - if m2: - line = m2.group(2) - ctrlk = m2.group(1) - key_delay = "1" - sendKeystringLocal(content + ctrlk, LYX_PID) - key_delay = saved_delay - time.sleep(controlkey_delay) - if line != "": - sendKeystringLocal(line, LYX_PID) + chr = m.group(1) + if m.group(2) == "": + res[0] = chr + res[1] = "" else: - if content != "": - sendKeystringLocal(content, LYX_PID) - if rest != "": - sendKeystringLocal(rest, LYX_PID) + norm = extractmultiple(m.group(2), regex) + res[0] = chr + norm[0] + res[1] = norm[1] else: - if line != "": - sendKeystringLocal(line, LYX_PID) - -specialkeyreg = re.compile(r'(.+)(\\[AC]([a-zA-Z]|\\\[[A-Z][a-z0-9]+\]).*)$') -# Split line at start of each meta or controll char - -def sendKeystringAC(line, LYX_PID): - m = specialkeyreg.match(line) + res[0] = "" + res[1] = line + return res + +normal_re = re.compile(r'^([^\\]|\\\\)(.*)$') +def extractnormal(line): + # collect non-special chars from start of line + return extractmultiple(line, normal_re) + +modifier_re = re.compile(r'^(\\[CAS])(.+)$') +def extractmodifiers(line): + # collect modifiers like '\\A' at start of line + return extractmultiple(line, modifier_re) + +special_re = re.compile(r'^(\\\[[A-Z][a-z0-9]+\])(.*)$') +def extractsingle(line): + # check for single key following a modifier + # either ascii like 'a' + # or special like '\[Return]' + res = [False, "", ""] + m = normal_re.match(line) if m: - first = m.group(1) - second = m.group(2) - sendKeystringAC(first, LYX_PID) - sendKeystringAC(second, LYX_PID) + res[0] = False + res[1] = m.group(1) + res[2] = m.group(2) else: - sendKeystringAx(line, LYX_PID) + m = special_re.match(line) + if m: + res[0] = True + res[1] = m.group(1) + res[2] = m.group(2) + else: + die(1, "Undecodable key for line \'" + line + "\"") + return res -controlkeyreg = re.compile(r'^(.*\\\[[A-Z][a-z0-9]+\])(.*\\\[[A-Z][a-z0-9]+\])(.*)$') -# Make sure, only one of \[Return], \[Tab], \[Down], \[Home] etc are in one sent line -# e.g. split the input line on each keysym -def sendKeystringRT(line, LYX_PID): - m = controlkeyreg.match(line) - if m: - first = m.group(1) - second = m.group(2) - third = m.group(3) - sendKeystringRT(first, LYX_PID) - time.sleep(controlkey_delay) - sendKeystringRT(second, LYX_PID) +def sendKeystring(line, LYX_PID): + if line == "": + return + normalchars = extractnormal(line) + line = normalchars[1] + if normalchars[0] != "": + sendKeystringLocal(normalchars[0], LYX_PID) + if line == "": + return + modchars = extractmodifiers(line) + line = modchars[1] + if line == "": + die(1, "Missing modified key") + modifiedchar = extractsingle(line) + line = modifiedchar[2] + special = modchars[0] != "" or modifiedchar[0] + sendKeystringLocal(modchars[0] + modifiedchar[1], LYX_PID) + if special: + # give the os time to update the status info (in /proc) time.sleep(controlkey_delay) - if third != "": - sendKeystringRT(third, LYX_PID) - else: - sendKeystringAC(line, LYX_PID) + sendKeystring(line, LYX_PID) def system_retry(num_retry, cmd): i = 0 - rtn = intr_system(cmd) + rtn = intr_system(cmd, True) while ( ( i < num_retry ) and ( rtn != 0) ): i = i + 1 - rtn = intr_system(cmd) + rtn = intr_system(cmd, True) time.sleep(1) if ( rtn != 0 ): print("Command Failed: "+cmd) @@ -470,6 +476,74 @@ def RaiseWindow(): intr_system("wmctrl -R '"+lyx_window_name+"' ;sleep 0.1") system_retry(30, "wmctrl -i -a '"+lyx_window_name+"'") +class Shortcuts: + + def __init__(self): + self.shortcut_entry = re.compile(r'^\s*"([^"]+)"\s*\"([^"]+)\"') + self.bindings = {} + self.bind = re.compile(r'^\s*\\bind\s+"([^"]+)"') + if lyx_userdir_ver is None: + self.dir = lyx_userdir + else: + self.dir = lyx_userdir_ver + + def __UseShortcut(self, c): + m = self.shortcut_entry.match(c) + if m: + sh = m.group(1) + fkt = m.group(2) + self.bindings[sh] = fkt + else: + die(1, "cad shortcut spec(" + c + ")") + + def __PrepareShortcuts(self): + if not self.dir is None: + tmp = tempfile.NamedTemporaryFile(suffix='.bind', delete=False) + try: + old = open(self.dir + '/bind/user.bind', 'r') + except IOError as e: + old = None + if not old is None: + lines = old.read().split("\n") + old.close() + bindfound = False + for line in lines: + m = self.bind.match(line) + if m: + bindfound = True + val = m.group(1) + if val in self.bindings: + if self.bindings[val] != "": + tmp.write("\\bind \"" + val + "\" \"" + self.bindings[val] + "\"\n") + self.bindings[val] = "" + else: + tmp.write(line + '\n') + elif not bindfound: + tmp.write(line + '\n') + else: + tmp.writelines( + '## This file is used for keytests only\n\n' + + 'Format 4\n\n' + ) + for val in self.bindings: + if not self.bindings[val] is None: + if self.bindings[val] != "": + tmp.write("\\bind \"" + val + "\" \"" + self.bindings[val] + "\"\n") + self.bindings[val] = "" + tmp.close() + shutil.move(tmp.name, self.dir + '/bind/user.bind') + else: + print("User dir not specified") + + def dispatch(self, c): + if c[0:12] == 'UseShortcut ': + self.__UseShortcut(c[12:]) + elif c == 'PrepareShortcuts': + print('Preparing usefull sortcuts for tests') + self.__PrepareShortcuts() + else: + return False + return True lyx_pid = os.environ.get('LYX_PID') print('lyx_pid: ' + str(lyx_pid) + '\n') @@ -480,6 +554,9 @@ lyx_window_name = os.environ.get('LYX_WINDOW_NAME') lyx_other_window_name = None screenshot_out = os.environ.get('SCREENSHOT_OUT') lyx_userdir = os.environ.get('LYX_USERDIR') +lyx_userdir_ver = os.environ.get('LYX_USERDIR_24x') +if lyx_userdir is None: + lyx_userdir = lyx_userdir_ver max_loops = os.environ.get('MAX_LOOPS') if max_loops is None: @@ -515,7 +592,8 @@ qt_frontend = os.environ.get('QT_FRONTEND') if qt_frontend is None: qt_frontend = 'QT4' if qt_frontend == 'QT5': - controlkey_delay = 0.01 + # Some tests sometimes failed with value 0.01 on Qt5.8 + controlkey_delay = 0.4 else: controlkey_delay = 0.4 @@ -563,12 +641,13 @@ outfile = open(outfilename, 'w') if not lyx_pid is None: RaiseWindow() # Next command is language dependent - #sendKeystringRT("\Afn", lyx_pid) + #sendKeystring("\Afn", lyx_pid) write_commands = True failed = False lineempty = re.compile(r'^\s*$') marked = ControlFile() +shortcuts = Shortcuts() while not failed: #intr_system('echo -n LOADAVG:; cat /proc/loadavg') c = x.getCommand() @@ -582,6 +661,8 @@ while not failed: outfile.flush() if marked.dispatch(c): continue + elif shortcuts.dispatch(c): + continue if c[0] == '#': print("Ignoring comment line: " + c) elif c[0:9] == 'TestBegin': @@ -625,8 +706,7 @@ while not failed: print('lyx_pid: ' + lyx_pid) print('lyx_win: ' + lyx_window_name) dead_expected = False - sendKeystringLocal("\C\[Home]", lyx_pid) - time.sleep(controlkey_delay) + sendKeystring("\C\[Home]", lyx_pid) elif c[0:5] == 'Sleep': print("Sleeping for " + c[6:] + " seconds") time.sleep(float(c[6:])) @@ -642,7 +722,7 @@ while not failed: RaiseWindow() elif c[0:4] == 'KK: ': if lyx_exists(): - sendKeystringRT(c[4:], lyx_pid) + sendKeystring(c[4:], lyx_pid) else: ##intr_system('killall lyx; sleep 2 ; killall -9 lyx') if lyx_pid is None: @@ -654,14 +734,14 @@ while not failed: print('Setting DELAY to ' + key_delay) elif c == 'Loop': RaiseWindow() - sendKeystringRT(ResetCommand, lyx_pid) + sendKeystring(ResetCommand, lyx_pid) elif c[0:6] == 'Assert': cmd = c[7:].rstrip() - result = intr_system(cmd) + result = intr_system(cmd, True) failed = failed or (result != 0) print("result=" + str(result) + ", failed=" + str(failed)) elif c[0:15] == 'TestEndWithKill': - marked.close() + marked.closeall() cmd = c[16:].rstrip() if lyx_dead(lyx_pid): print("LyX instance not found because of crash or assert !\n") @@ -669,32 +749,33 @@ while not failed: else: print(" ------------ Forcing kill of lyx instance: " + str(lyx_pid) + " ------------") # This line below is there only to allow lyx to update its log-file - sendKeystringLocal("\[Escape]", lyx_pid) + sendKeystring("\[Escape]", lyx_pid) dead_expected = True while not lyx_dead(lyx_pid): intr_system("kill -9 " + str(lyx_pid), True); time.sleep(0.5) if cmd != "": print("Executing " + cmd) - result = intr_system(cmd) + result = intr_system(cmd, True) failed = failed or (result != 0) print("result=" + str(result) + ", failed=" + str(failed)) else: print("failed=" + str(failed)) elif c[0:7] == 'TestEnd': - marked.close() - #lyx_other_window_name = None + #lyx_other_window_name = None if lyx_dead(lyx_pid): print("LyX instance not found because of crash or assert !\n") + marked.closeall() failed = True else: print(" ------------ Forcing quit of lyx instance: " + str(lyx_pid) + " ------------") # \[Escape]+ should work as RESET focus to main window - sendKeystringAx("\[Escape]\[Escape]\[Escape]\[Escape]", lyx_pid) - time.sleep(controlkey_delay) + sendKeystring("\[Escape]\[Escape]\[Escape]\[Escape]", lyx_pid) # now we should be outside any dialog # and so the function lyx-quit should work - sendKeystringLocal("\Cq", lyx_pid) + sendKeystring("\Cq", lyx_pid) + marked.dispatch('CP: action=lyx-quit') + marked.dispatch('CC:') time.sleep(0.5) dead_expected = True is_sleeping = wait_until_lyx_sleeping(lyx_pid) @@ -705,7 +786,7 @@ while not failed: # causing a 'beep' time.sleep(0.5) # probably waiting for Save/Discard/Abort, we select 'Discard' - sendKeystringRT("\[Tab]\[Return]", lyx_pid) + sendKeystring("\[Tab]\[Return]", lyx_pid) lcount = 0 else: lcount = 1 @@ -718,7 +799,7 @@ while not failed: cmd = c[8:].rstrip() if cmd != "": print("Executing " + cmd) - result = intr_system(cmd) + result = intr_system(cmd, True) failed = failed or (result != 0) print("result=" + str(result) + ", failed=" + str(failed)) else: @@ -756,7 +837,7 @@ while not failed: print('Could not determine PACKAGE name needed for translations\n') failed = True else: - lyx_name = PACKAGE + lyx_name = PACKAGE intr_system("mkdir -p " + locale_dir + "/" + ccode + "/LC_MESSAGES") intr_system("rm -f " + locale_dir + "/" + ccode + "/LC_MESSAGES/" + lyx_name + ".mo") if PO_BUILD_DIR is None: @@ -771,7 +852,7 @@ while not failed: print("Unrecognised Command '" + c + "'\n") failed = True -print("Test case terminated: ") +print("Test case terminated: ", end = '') if failed: die(1,"FAIL") else: