From d33f624c2046d7e83724a57d14ad639539d1e6d3 Mon Sep 17 00:00:00 2001 From: Adam Williamson Date: Mon, 4 Apr 2016 14:21:06 -0700 Subject: [PATCH] kickstart-tests: handle non-US keyboard layouts wow, this was a deep dive. A few of the kickstart-tests set the OS keymap to cz. Long story short, the way openQA does keyboard input is via VNC; VNC keyboard events are for keysyms, which are layout-dependent (so when we say `send_key "a"`, openQA sends qemu the keysym for 'a', regardless of layout issues). qemu turns the keysym into a keycode and sends *that* to the guest OS, which turns it back into a keysym according to its keymap config. qemu uses a US map by default to turn the keysym it receives via VNC into a keycode. So if you send qemu a '-', it sends the guest OS the keycode that would be converted to the keysym for '-' *in the US layout*. But if the guest OS has its layout set to cz, the same keycode is converted to '=', since that's what the key with that keycode is labelled as on a Czech keyboard. That's why if you set the guest OS keymap to cz and tell openQA to type '-', you get '='. However! You can tell qemu to use a different keymap for its keysym -> keycode conversion. If you keep the qemu keymap and the guest OS keymap lined up, when you say `send_key "-"`, you should actually *get* a "-". Which is what we want, for these tests. So we tell openQA to tell qemu to do that, using the VNCKB var, which is for precisely this: it tells os-autoinst to run qemu with `-k (layout)`. --- kstest-converter | 81 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/kstest-converter b/kstest-converter index 867a32dc..a6eb2d9f 100755 --- a/kstest-converter +++ b/kstest-converter @@ -47,11 +47,13 @@ def prep_kickstarts(indir, outdir): if not tests: raise ValueError("No tests found!") for test in tests: + # read in the .ks.in file with open('{0}/{1}.ks.in'.format(indir, test), 'r') as ksinfh: kstext = ksinfh.read() - ksout = "{0}.ks".format(test) for (orig, repl) in SUBSTS: kstext = kstext.replace(orig, repl) + # write out the processed .ks + ksout = "{0}.ks".format(test) with open('{0}/{1}'.format(outdir, ksout), 'w') as ksoutfh: ksoutfh.write(kstext) @@ -73,8 +75,8 @@ def merge_templates(indir, baseurl, tempfile, outfile): outfh.write(json.dumps(templates, sort_keys=True, indent=4, separators=(',', ': '))) -def _get_disk_settings(test): - """Given text of a kickstart_tests test (.sh file) as test, return +def _get_settings_disk(sh): + """Given text of a kickstart_tests test (.sh file) as sh, return a list of appropriate openQA settings dicts for hard disks. """ # most prepare_disks just create empty disks of a given size in @@ -85,11 +87,11 @@ def _get_disk_settings(test): # create multiple clean disk images with different sizes. settings = [] simpre = re.compile(r'qemu-img create -q -f qcow2 \$\{(tmp|disk)dir\}/disk-.\.img \d+G') - numdisks = len(simpre.findall(test)) + numdisks = len(simpre.findall(sh)) # the one tricky case that exists so far is the driverdisk case. # it gets created elsewhere. here we just point to it. - if 'mkdud.py' in test: + if 'mkdud.py' in sh: numdisks += 1 settings.append({'key': 'HDD_{0}'.format(str(numdisks)), 'value': "driverdisk.img"}) @@ -98,26 +100,65 @@ def _get_disk_settings(test): return settings +def _get_settings_postinstall(sh): + """Given text of a kickstart_tests test (.sh file) as sh, return + a list of appropriate openQA settings dict for post-install test + settings (currently a single-item list, but we're future-proofing + here). + """ + # just one test checks for RESULT in /home instead of /root + if '/home/RESULT' in sh: + return [{'key': 'POSTINSTALL', 'value': 'kstest_home'}] + else: + return [{'key': 'POSTINSTALL', 'value': 'kstest_root'}] + +def _get_settings_keyboard(ksin): + """Given text of a kickstart_tests .ks.in file as ksin, return + a list of appropriate openQA settings dict for keyboard test + settings (currently a single-item list, but we're future-proofing + here). + """ + # if the kickstart sets a keyboard layout, we set VNCKB to the + # same layout. This tells openQA to run qemu with '-k (layout)', + # which makes it use that layout for converting keysyms received + # via VNC to keycodes which are passed on to the guest OS, which + # converts them back into keysyms. Basically if we want to set a + # non-US layout in the guest and have 'type_string qwerty' still + # type 'qwerty' and not 'qwertz' or 'azert' or something, this is + # how we do that. + match = re.search(r'--vckeymap (\S+)', ksin) + if match: + return [{'key': 'VNCKB', 'value': match.group(1)}] + else: + return [] + def create_testsuite(test, path, baseurl): """Create an openQA 'test suite' for a given kickstart_test. test is the test name, path is the directory the test files are in. """ - with open('{0}/{1}.sh'.format(path, test), 'r') as testfh: - sh = testfh.read() - with open('{0}/{1}.ks.in'.format(path, test), 'r') as ksfh: - ks = ksfh.read() - settings = _get_disk_settings(sh) - settings.append({'key': 'KICKSTART', 'value': '1'}) - # just one test checks for RESULT in /home instead of /root - if '/home/RESULT' in sh: - settings.append({'key': 'POSTINSTALL', 'value': 'kstest_home'}) - else: - settings.append({'key': 'POSTINSTALL', 'value': 'kstest_root'}) - # for some goddamn reason there are two different root passwords. - rootpatt = re.compile(r'rootpw (.+)') - settings.append({'key': 'ROOT_PASSWORD', 'value': rootpatt.search(ks).group(1)}) + # duh. these are all kickstart tests. + settings = [{'key': 'KICKSTART', 'value': '1'}] + + # get text of .sh and .ks.in files + with open('{0}/{1}.sh'.format(path, test), 'r') as shfh: + sh = shfh.read() + with open('{0}/{1}.ks.in'.format(path, test), 'r') as ksinfh: + ksin = ksinfh.read() + + # call all the functions for getting different settings. + settings.extend(_get_settings_disk(sh)) + settings.extend(_get_settings_postinstall(sh)) + settings.extend(_get_settings_keyboard(ksin)) + + # do some very simple ones ourselves (to avoid this function + # growing too much, the rule is that if it needs more than one + # line, split it out). + + # root password. for now we assume there is one. + settings.append({'key': 'ROOT_PASSWORD', 'value': re.search(r'rootpw (.+)', ksin).group(1)}) + # kickstart URL settings.append({'key': 'GRUB', 'value': "inst.ks={0}/{1}.ks".format(baseurl.strip('/'), test)}) - # we never want to do a user login for these. + # we never want to do a user login for these settings.append({'key': 'USER_LOGIN', 'value': "false"}) return {'name': "kstest_{0}".format(test), 'settings': settings}