X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=development%2Fautotests%2Fkeytest.py;h=7bf240c1371d834a27c012d1f66cb697354dc57b;hb=c200e49d675f9830e801f12098f05dcf274fa492;hp=bbb57b4fe385bf2f194bacd11c4ac9a3e4709f1c;hpb=1b929a0c4543d56ecc80fb748df0b365ff65c985;p=lyx.git diff --git a/development/autotests/keytest.py b/development/autotests/keytest.py index bbb57b4fe3..7bf240c137 100755 --- a/development/autotests/keytest.py +++ b/development/autotests/keytest.py @@ -8,6 +8,7 @@ # Adapted by Tommaso Cucinotta from the original MonKey Test by # John McCabe-Dansted. +from __future__ import print_function import random import os import re @@ -16,7 +17,7 @@ import time #from subprocess import call import subprocess -print 'Beginning keytest.py' +print('Beginning keytest.py') FNULL = open('/dev/null', 'w') @@ -78,7 +79,7 @@ class CommandSourceFromFile(CommandSource): self.infile.close() linesbak = self.lines self.p = p - print p, self.p, 'self.p' + print(p, self.p, 'self.p') self.i = 0 self.count = 0 self.loops = 0 @@ -88,10 +89,10 @@ class CommandSourceFromFile(CommandSource): if p > 0.001: if random.uniform(0, 1) < 0.5: - print 'randomdrop_independant\n' + print('randomdrop_independant\n') self.randomdrop_independant() else: - print 'randomdrop_slice\n' + print('randomdrop_slice\n') self.randomdrop_slice() if screenshot_out is None: count_atleast = 100 @@ -113,11 +114,11 @@ class CommandSourceFromFile(CommandSource): self.lines = [] for l in origlines: if random.uniform(0, 1) < self.p: - print 'Randomly dropping line ' + l + '\n' + print('Randomly dropping line ' + l + '\n') else: self.lines.append(l) - print 'LINES\n' - print self.lines + print('LINES\n') + print(self.lines) sys.stdout.flush() def randomdrop_slice(self): @@ -131,10 +132,10 @@ class CommandSourceFromFile(CommandSource): drop_mid = random.randint(0, num_lines) drop_start = max(drop_mid - num_drop / 2, 0) drop_end = min(drop_start + num_drop, num_lines) - print drop_start, drop_mid, drop_end - print lines + print(drop_start, drop_mid, drop_end) + print(lines) del lines[drop_start:drop_end] - print lines + print(lines) self.lines = lines def getCommand(self): @@ -151,98 +152,228 @@ class CommandSourceFromFile(CommandSource): self.i = self.i + 1 return line +def get_proc_pid(proc_name): + pid=os.popen("pidof " + proc_name).read().rstrip() + return pid + +wlistreg = re.compile(r'^(0x[0-9a-f]{5,9})\s+[^\s]+\s+([0-9]+)\s.*$') +def get_proc_win_id(pid, ignoreid): + nlist = os.popen("wmctrl -l -p").read() + wlist = nlist.split("\n") + for item in wlist: + m = wlistreg.match(item) + if m: + win_id = m.group(1) + win_pid = m.group(2) + if win_pid == pid: + if win_id != ignoreid: + return win_id + return None + def lyx_exists(): if lyx_pid is None: return False fname = '/proc/' + lyx_pid + '/status' return os.path.exists(fname) -def lyx_sleeping(): - fname = '/proc/' + lyx_pid + '/status' - f = open(fname, 'r') - lines = f.readlines() - sleeping = lines[1].find('(sleeping)') > 0 - - # print 'LYX_STATE', lines[1] , 'SLEEPING=', sleeping - - return sleeping - # Interruptible os.system() def intr_system(cmd, ignore_err = False): - print "Executing " + cmd + "\n" + print("Executing " + cmd) ret = os.system(cmd) if os.WIFSIGNALED(ret): raise KeyboardInterrupt if ret != 0 and not ignore_err: - raise BaseException("command failed.") + raise BaseException("command failed:" + cmd) return ret +statreg = re.compile(r'^State:.*\(([a-z]+)\)') -def sendKeystring(keystr, LYX_PID): +resstatus = [] +def printresstatus(): + for line in resstatus: + line = line.rstrip() + print(" " + line.rstrip()) + print('End of /proc-lines') - # print "sending keystring "+keystr+"\n" +def lyx_status(pid): + resstatus = [] + if lyx_pid is None: + return "dead" + fname = '/proc/' + pid + '/status' + status = "dead" + try: + f = open(fname) + found = False + for line in f: + resstatus.extend([line]) + m = statreg.match(line) + if m: + status = m.group(1) + found = True + f.close() + return status + except IOError as e: + print("I/O error({0}): {1}".format(e.errno, e.strerror)) + return "dead" + except: + print("Unexpected error:", sys.exc_info()[0]) + return "dead" + return status + +# Return true if LyX (identified via lyx_pid) is sleeping +def lyx_sleeping(): + return lyx_status(lyx_pid) == "sleeping" + +# Return true if LyX (identified via lyx_pid) is zombie +def lyx_zombie(): + return lyx_status(lyx_pid) == "zombie" + +def lyx_dead(): + status = lyx_status(lyx_pid) + return (status == "dead") or (status == "zombie") - if not re.match(".*\w.*", keystr): - print 'print .' + keystr + '.\n' - keystr = 'a' +def wait_until_lyx_sleeping(): before_secs = time.time() - while lyx_exists() and not lyx_sleeping(): - time.sleep(0.02) - sys.stdout.flush() + while True: + status = lyx_status(lyx_pid) + if status == "sleeping": + return + if (status == "dead") or (status == "zombie"): + print('Lyx is dead, exiting') + printresstatus() + sys.stdout.flush() + os._exit(1) if time.time() - before_secs > 180: - print 'Killing due to freeze (KILL_FREEZE)' + print('Killing due to freeze (KILL_FREEZE)') # Do profiling, but sysprof has no command line interface? # intr_system("killall -KILL lyx") - + printresstatus() + sys.stdout.flush() os._exit(1) + time.sleep(0.02) + +def sendKeystringLocal(keystr, LYX_PID): + wait_until_lyx_sleeping() if not screenshot_out is None: - while lyx_exists() and not lyx_sleeping(): - time.sleep(0.02) - sys.stdout.flush() - print 'Making Screenshot: ' + screenshot_out + ' OF ' + infilename + print('Making Screenshot: ' + screenshot_out + ' OF ' + infilename) time.sleep(0.2) intr_system('import -window root '+screenshot_out+str(x.count)+".png") time.sleep(0.1) - sys.stdout.flush() actual_delay = key_delay if actual_delay == '': actual_delay = def_delay - if not xvkbd_hacked: - subprocess.call([xvkbd_exe, "-xsendevent", "-window", lyx_window_name, - "-delay", actual_delay, "-text", keystr], stdout = FNULL, stderr = FNULL) + xvpar = [xvkbd_exe] + if qt_frontend == 'QT5': + xvpar.extend(["-jump-pointer", "-no-back-pointer"]) else: - subprocess.call([xvkbd_exe, "-no_root", "-wait_idle", lyx_pid, - "-xsendevent", "-window", lyx_window_name, - "-delay", actual_delay, "-text", keystr], stdout = FNULL, stderr = FNULL) + xvpar.extend(["-xsendevent"]) + if lyx_other_window_name is None: + xvpar.extend(["-window", lyx_window_name]) + else: + xvpar.extend(["-window", lyx_other_window_name]) + xvpar.extend(["-delay", actual_delay, "-text", keystr]) + print("Sending \"" + keystr + "\"") + subprocess.call(xvpar, stdout = FNULL, stderr = FNULL) + sys.stdout.flush() + +Axreg = re.compile(r'^(.*)\\Ax([^\\]*)(.*)$') +returnreg = re.compile(r'(\\\[[A-Z][a-z]+\])(.*)$') + +# recursive wrapper around sendKeystringLocal() +# handling \Ax-entries +def sendKeystringAx(line, LYX_PID): + global key_delay + saved_delay = key_delay + m = Axreg.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) + else: + if content != "": + sendKeystringLocal(content, LYX_PID) + if rest != "": + sendKeystringLocal(rest, LYX_PID) + else: + if line != "": + sendKeystringLocal(line, LYX_PID) + +specialkeyreg = re.compile(r'(.+)(\\[AC]([a-zA-Z]|\\\[[A-Z][a-z]+\]).*)$') +# Split line at start of each meta or controll char + +def sendKeystringAC(line, LYX_PID): + m = specialkeyreg.match(line) + if m: + first = m.group(1) + second = m.group(2) + sendKeystringAC(first, LYX_PID) + sendKeystringAC(second, LYX_PID) + else: + sendKeystringAx(line, LYX_PID) + +controlkeyreg = re.compile(r'^(.*\\\[[A-Z][a-z]+\])(.*\\\[[A-Z][a-z]+\])(.*)$') +# 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) + time.sleep(controlkey_delay) + if third != "": + sendKeystringRT(third, LYX_PID) + else: + sendKeystringAC(line, LYX_PID) def system_retry(num_retry, cmd): i = 0 rtn = intr_system(cmd) while ( ( i < num_retry ) and ( rtn != 0) ): i = i + 1 - rtn = intr_system(cmd) + rtn = intr_system(cmd) time.sleep(1) if ( rtn != 0 ): - print "Command Failed: "+cmd - print " EXITING!\n" + print("Command Failed: "+cmd) + print(" EXITING!\n") os._exit(1) def RaiseWindow(): #intr_system("echo x-session-manager PID: $X_PID.") #intr_system("echo x-session-manager open files: `lsof -p $X_PID | grep ICE-unix | wc -l`") ####intr_system("wmctrl -l | ( grep '"+lyx_window_name+"' || ( killall lyx ; sleep 1 ; killall -9 lyx ))") - #intr_system("wmctrl -R '"+lyx_window_name+"' ;sleep 0.1") - system_retry(30, "wmctrl -a '"+lyx_window_name+"'") + print("lyx_window_name = " + lyx_window_name + "\n") + intr_system("wmctrl -R '"+lyx_window_name+"' ;sleep 0.1") + system_retry(30, "wmctrl -i -a '"+lyx_window_name+"'") lyx_pid = os.environ.get('LYX_PID') -print 'lyx_pid: ' + str(lyx_pid) + '\n' +print('lyx_pid: ' + str(lyx_pid) + '\n') infilename = os.environ.get('KEYTEST_INFILE') outfilename = os.environ.get('KEYTEST_OUTFILE') max_drop = os.environ.get('MAX_DROP') 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') @@ -250,15 +381,33 @@ max_loops = os.environ.get('MAX_LOOPS') if max_loops is None: max_loops = 3 +PACKAGE = os.environ.get('PACKAGE') +if not PACKAGE is None: + print("PACKAGE = " + PACKAGE + "\n") + +PO_BUILD_DIR = os.environ.get('PO_BUILD_DIR') +if not PO_BUILD_DIR is None: + print("PO_BUILD_DIR = " + PO_BUILD_DIR + "\n") + +lyx = os.environ.get('LYX') +if lyx is None: + lyx = "lyx" + lyx_exe = os.environ.get('LYX_EXE') if lyx_exe is None: - lyx_exe = "lyx" + lyx_exe = lyx xvkbd_exe = os.environ.get('XVKBD_EXE') if xvkbd_exe is None: xvkbd_exe = "xvkbd" -xvkbd_hacked = os.environ.get('XVKBD_HACKED') != None +qt_frontend = os.environ.get('QT_FRONTEND') +if qt_frontend is None: + qt_frontend = 'QT4' +if qt_frontend == 'QT5': + controlkey_delay = 0.01 +else: + controlkey_delay = 0.4 locale_dir = os.environ.get('LOCALE_DIR') if locale_dir is None: @@ -266,7 +415,10 @@ if locale_dir is None: def_delay = os.environ.get('XVKBD_DELAY') if def_delay is None: - def_delay = '100' + if qt_frontend == 'QT5': + def_delay = '5' + else: + def_delay = '1' file_new_command = os.environ.get('FILE_NEW_COMMAND') if file_new_command is None: @@ -280,69 +432,90 @@ if ResetCommand is None: if lyx_window_name is None: lyx_window_name = 'LyX' -print 'outfilename: ' + outfilename + '\n' -print 'max_drop: ' + max_drop + '\n' +print('outfilename: ' + outfilename + '\n') +print('max_drop: ' + max_drop + '\n') if infilename is None: - print 'infilename is None\n' + print('infilename is None\n') x = CommandSource() - print 'Using x=CommandSource\n' + print('Using x=CommandSource\n') else: - print 'infilename: ' + infilename + '\n' + print('infilename: ' + infilename + '\n') probability_we_drop_a_command = random.uniform(0, float(max_drop)) - print 'probability_we_drop_a_command: ' - print '%s' % probability_we_drop_a_command - print '\n' + print('probability_we_drop_a_command: ') + print('%s' % probability_we_drop_a_command) + print('\n') x = CommandSourceFromFile(infilename, probability_we_drop_a_command) - print 'Using x=CommandSourceFromFile\n' + print('Using x=CommandSourceFromFile\n') outfile = open(outfilename, 'w') if not lyx_pid is None: RaiseWindow() - sendKeystring("\Afn", lyx_pid) + # Next command is language dependent + #sendKeystringRT("\Afn", lyx_pid) write_commands = True failed = False +lineempty = re.compile(r'^\s*$') while not failed: #intr_system('echo -n LOADAVG:; cat /proc/loadavg') c = x.getCommand() if c is None: break - if c.strip() == "": + + # Do not strip trailing spaces, only check for 'empty' lines + if lineempty.match(c): continue outfile.writelines(c + '\n') outfile.flush() if c[0] == '#': - print "Ignoring comment line: " + c + print("Ignoring comment line: " + c) elif c[0:9] == 'TestBegin': - print "\n" - lyx_pid=os.popen("pidof lyx").read() + print("\n") + lyx_pid=get_proc_pid(lyx) if lyx_pid != "": - print "Found running instance(s) of LyX: " + lyx_pid + ": killing them all\n" - intr_system("killall lyx", True) + print("Found running instance(s) of LyX: " + lyx_pid + ": killing them all\n") + intr_system("killall " + lyx, True) time.sleep(0.5) - intr_system("killall -KILL lyx", True) + intr_system("killall -KILL " + lyx, True) time.sleep(0.2) - print "Starting LyX . . ." + print("Starting LyX . . .") if lyx_userdir is None: intr_system(lyx_exe + c[9:] + "&") else: intr_system(lyx_exe + " -userdir " + lyx_userdir + " " + c[9:] + "&") - while True: - lyx_pid=os.popen("pidof lyx").read().rstrip() + count = 5 + old_lyx_pid = "-7" + old_lyx_window_name = None + print("Waiting for LyX to show up . . .") + while count > 0: + lyx_pid=get_proc_pid(lyx) + if lyx_pid != old_lyx_pid: + print('lyx_pid=' + lyx_pid) + old_lyx_pid = lyx_pid if lyx_pid != "": - lyx_window_name=os.popen("wmctrl -l -p | grep ' " + str(lyx_pid) + " ' | cut -d ' ' -f 1").read().rstrip() - if lyx_window_name != "": + lyx_window_name=get_proc_win_id(lyx_pid, "") + if not lyx_window_name is None: + if old_lyx_window_name != lyx_window_name: + print('lyx_win=' + lyx_window_name, '\n') + old_lyx_window_name = lyx_window_name break - print 'lyx_win: ' + lyx_window_name + '\n' - print "Waiting for LyX to show up . . ." + else: + count = count - 1 time.sleep(1) - print 'lyx_pid: ' + lyx_pid + '\n' - print 'lyx_win: ' + lyx_window_name + '\n' + if count <= 0: + print('Timeout: could not start ' + lyx_exe, '\n') + sys.stdout.flush() + failed = True + else: + print('lyx_pid: ' + lyx_pid) + print('lyx_win: ' + lyx_window_name) + sendKeystringLocal("\C\[Home]", lyx_pid) + time.sleep(controlkey_delay) elif c[0:5] == 'Sleep': - print "Sleeping for " + c[6:] + "\n" + print("Sleeping for " + c[6:] + " seconds") time.sleep(float(c[6:])) elif c[0:4] == 'Exec': cmd = c[5:].rstrip() @@ -350,45 +523,90 @@ while not failed: elif c == 'Loop': outfile.close() outfile = open(outfilename + '+', 'w') - print 'Now Looping' + print('Now Looping') elif c == 'RaiseLyx': - print 'Raising Lyx' + print('Raising Lyx') RaiseWindow() elif c[0:4] == 'KK: ': if lyx_exists(): - sendKeystring(c[4:], lyx_pid) + sendKeystringRT(c[4:], lyx_pid) else: ##intr_system('killall lyx; sleep 2 ; killall -9 lyx') - print 'No path /proc/' + lyx_pid + '/status, exiting' + if lyx_pid is None: + print('No path /proc/xxxx/status, exiting') + else: + print('No path /proc/' + lyx_pid + '/status, exiting') os._exit(1) elif c[0:4] == 'KD: ': key_delay = c[4:].rstrip('\n') - print 'Setting DELAY to ' + key_delay + '.\n' + print('Setting DELAY to ' + key_delay) elif c == 'Loop': RaiseWindow() - sendKeystring(ResetCommand, lyx_pid) + sendKeystringRT(ResetCommand, lyx_pid) elif c[0:6] == 'Assert': cmd = c[7:].rstrip() - print "Executing " + cmd result = intr_system(cmd) failed = failed or (result != 0) - print "result=" + str(result) + ", failed=" + str(failed) + print("result=" + str(result) + ", failed=" + str(failed)) + elif c[0:15] == 'TestEndWithKill': + cmd = c[16:].rstrip() + if lyx_dead(): + print("LyX instance not found because of crash or assert !\n") + failed = True + 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) + while not lyx_dead(): + intr_system("kill -9 " + str(lyx_pid), True); + time.sleep(0.5) + if cmd != "": + print("Executing " + cmd) + result = intr_system(cmd) + failed = failed or (result != 0) + print("result=" + str(result) + ", failed=" + str(failed)) + else: + print("failed=" + str(failed)) elif c[0:7] == 'TestEnd': - time.sleep(0.5) - print "Terminating lyx instance: " + str(lyx_pid) + "\n" - intr_system("kill -9 " + str(lyx_pid), True); - while lyx_exists(): - print "Waiting for lyx to die...\n" + #lyx_other_window_name = None + if lyx_dead(): + print("LyX instance not found because of crash or assert !\n") + failed = True + else: + print(" ------------ Forcing quit of lyx instance: " + str(lyx_pid) + " ------------") + # \Ax Enter command line is sometimes blocked + # \[Escape] works after this + sendKeystringAx("\Ax\[Escape]", lyx_pid) + time.sleep(controlkey_delay) + # now we should be outside any dialog + # and so the function lyx-quit should work + sendKeystringLocal("\Cq", lyx_pid) time.sleep(0.5) + if lyx_sleeping(): + # probably waiting for Save/Discard/Abort, we select 'Discard' + sendKeystringRT("\[Tab]\[Return]", lyx_pid) + lcount = 0 + else: + lcount = 1 + while not lyx_dead(): + lcount = lcount + 1 + if lcount > 20: + print("LyX still up, killing process and waiting for it to die...\n") + intr_system("kill -9 " + str(lyx_pid), True); + time.sleep(0.5) cmd = c[8:].rstrip() - print "Executing " + cmd - result = intr_system(cmd) - failed = failed or (result != 0) - print "result=" + str(result) + ", failed=" + str(failed) + if cmd != "": + print("Executing " + cmd) + result = intr_system(cmd) + failed = failed or (result != 0) + print("result=" + str(result) + ", failed=" + str(failed)) + else: + print("failed=" + str(failed)) elif c[0:4] == 'Lang': lang = c[5:].rstrip() - print "Setting LANG=" + lang + "\n" + print("Setting LANG=" + lang) os.environ['LANG'] = lang + os.environ['LC_ALL'] = lang # If it doesn't exist, create a link //LC_MESSAGES/lyx.mo # pointing to the corresponding .gmo file. Needed to let lyx find the right translation files. # See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg165613.html @@ -397,29 +615,45 @@ while not failed: ccode = lang[0:idx] else: ccode = lang + + print("Setting LANGUAGE=" + ccode) + os.environ['LANGUAGE'] = ccode + idx = lang.find("_") if idx != -1: short_code = lang[0:idx] else: short_code = ccode lyx_dir = os.popen("dirname \"" + lyx_exe + "\"").read().rstrip() - print "Executing: grep 'PACKAGE =' " + lyx_dir + "/Makefile | sed -e 's/PACKAGE = \(.*\)/\\1/'" - lyx_name = os.popen("grep 'PACKAGE =' " + lyx_dir + "/Makefile | sed -e 's/PACKAGE = \(.*\)/\\1/'").read().rstrip() - intr_system("mkdir -p " + locale_dir + "/" + ccode + "/LC_MESSAGES") - if lyx_dir[0:3] == "../": - rel_dir = "../../" + lyx_dir + if PACKAGE is None: + # on cmake-build there is no Makefile in this directory + # so PACKAGE has to be provided + if os.path.exists(lyx_dir + "/Makefile"): + print("Executing: grep 'PACKAGE =' " + lyx_dir + "/Makefile | sed -e 's/PACKAGE = \(.*\)/\\1/'") + lyx_name = os.popen("grep 'PACKAGE =' " + lyx_dir + "/Makefile | sed -e 's/PACKAGE = \(.*\)/\\1/'").read().rstrip() + else: + print('Could not determine PACKAGE name needed for translations\n') + failed = True else: - rel_dir = lyx_dir + lyx_name = PACKAGE + intr_system("mkdir -p " + locale_dir + "/" + ccode + "/LC_MESSAGES") intr_system("rm -f " + locale_dir + "/" + ccode + "/LC_MESSAGES/" + lyx_name + ".mo") - intr_system("ln -s " + rel_dir + "/../po/" + short_code + ".gmo " + locale_dir + "/" + ccode + "/LC_MESSAGES/" + lyx_name + ".mo") + if PO_BUILD_DIR is None: + if lyx_dir[0:3] == "../": + rel_dir = "../../" + lyx_dir + else: + rel_dir = lyx_dir + intr_system("ln -s " + rel_dir + "/../po/" + short_code + ".gmo " + locale_dir + "/" + ccode + "/LC_MESSAGES/" + lyx_name + ".mo") + else: + intr_system("ln -s " + PO_BUILD_DIR + "/" + short_code + ".gmo " + locale_dir + "/" + ccode + "/LC_MESSAGES/" + lyx_name + ".mo") else: - print "Unrecognised Command '" + c + "'\n" + print("Unrecognised Command '" + c + "'\n") failed = True -print "Test case terminated: " +print("Test case terminated: ") if failed: - print "FAIL\n" + print("FAIL\n") os._exit(1) else: - print "Ok\n" + print("Ok\n") os._exit(0)