diff options
author | PMR <pmr@pmr-lander> | 2019-09-21 10:39:34 +0200 |
---|---|---|
committer | PMR <pmr@pmr-lander> | 2019-09-21 10:39:34 +0200 |
commit | eb7c89ab81a6841ac9118a2569fb1071cead9f32 (patch) | |
tree | 968e0688b5a184d8ed89f82f4459fa0bb66fbcee | |
parent | ca5a917f7304e173247025bfd8c43b1236473b29 (diff) | |
parent | 98ccaa543c57fd4d8c46130f84db395a8422ca9e (diff) |
Merge branch 'master' into release
112 files changed, 9357 insertions, 58 deletions
diff --git a/bin/alsa_pcm_info b/bin/alsa_pcm_info new file mode 100755 index 0000000..abb870f --- /dev/null +++ b/bin/alsa_pcm_info @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Authors: Jonathan Cave <jonathan.cave@canonical.com> + +""" +Script to print some simple information from the /proc/asound/pcm file. Used +in lieu of working alsa-utils. +""" + +import os + +PCM_FILE = '/proc/asound/pcm' + +if os.path.exists(PCM_FILE): + with open(PCM_FILE, 'r') as f: + for line in f: + t = [l.strip() for l in line.split(':')] + # 0 = Card and device id + ids = t[0].split('-') + print("Card: {}".format(ids[0])) + print("Device: {}".format(ids[1])) + # 1 = Name of device + print("Name: {}".format(t[1])) + # 2 = Name of device again ?! + # 3+ = Some number of capabilties + for cap in t[3:]: + if cap.startswith('playback'): + print("Playback: 1") + if cap.startswith('capture'): + print("Capture: 1") + print() diff --git a/bin/alsa_tests.py b/bin/alsa_tests.py new file mode 100755 index 0000000..16fb044 --- /dev/null +++ b/bin/alsa_tests.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# Copyright 2016 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Authors: Maciej Kisielewski <maciej.kisielewski@canonical.com> + + +import alsaaudio +import argparse +import cmath +import contextlib +import math +import os +import struct +import sys +import threading + +RATE = 44100 +PERIOD = 441 # chunk size (in samples) that will be used when talking to alsa + + +def fft(x): + N = len(x) + if N <= 1: + return x + even = fft(x[0::2]) + odd = fft(x[1::2]) + T = [cmath.exp(-2j*cmath.pi*k/N) * odd[k] for k in range(N//2)] + return ([even[k] + T[k] for k in range(N//2)] + + [even[k] - T[k] for k in range(N//2)]) + + +def sine(freq, length, period_len, amplitude=0.5): + """ + Generate `period_len` samples of sine of `freq` frequency and `amplitude`. + It generates at least `length` samples, totalling at next multiple of + period_len. E.g. sine(440, 15, 10) will generate two 10-item lists of + samples. Returns list of floats in the range of [-1.0 .. 1.0]. + """ + t = 0 + while t < length: + sample = [] + for i in range(period_len): + sample.append( + amplitude * math.sin(2 * math.pi * ((t + i) / (RATE / freq)))) + yield sample + t += period_len + + +class Player: + def __init__(self, device=None): + if not device: + available_pcms = alsaaudio.pcms(alsaaudio.PCM_PLAYBACK) + if not available_pcms: + raise SystemExit('No PCMs detected') + self.pcm = alsaaudio.PCM(device=available_pcms[0]) + else: + self.pcm = alsaaudio.PCM(device=device) + self.pcm.setchannels(1) + self.pcm.setformat(alsaaudio.PCM_FORMAT_FLOAT_LE) + self.pcm.setperiodsize(PERIOD) + + def play(self, chunk): + assert(len(chunk) == PERIOD) + # alsa expects bytes, so we need to repack the list of floats into a + # bytes sequence + buff = b''.join([struct.pack("<f", x) for x in chunk]) + self.pcm.write(buff) + + @contextlib.contextmanager + def changed_volume(self): + """Change volume to 50% and unmute the output while in the context""" + available_mixers = alsaaudio.mixers() + if not available_mixers: + # no mixers available - silently ignore change_volume request + yield + return + try: + # get default mixer - this may fail on some systems + mixer = alsaaudio.Mixer() + except alsaaudio.ALSAAudioError: + # pick the first mixer available + mixer = alsaaudio.Mixer(available_mixers[0]) + stored_mute = mixer.getmute() + stored_volume = mixer.getvolume() + mixer.setmute(0) + mixer.setvolume(50) + yield + # the getters returned lists of volumes/mutes per channel, setters + # require scalars so we need to set it one by one + for ch, mute in enumerate(stored_mute): + mixer.setmute(mute, ch) + for ch, vol in enumerate(stored_volume): + mixer.setvolume(vol, ch) + + +class Recorder: + def __init__(self): + self.pcm = alsaaudio.PCM(alsaaudio.PCM_CAPTURE) + self.pcm.setchannels(1) + self.pcm.setformat(alsaaudio.PCM_FORMAT_FLOAT_LE) + self.pcm.setperiodsize(PERIOD) + + def record(self, length): + samples = [] + while len(samples) < length: + raw = self.pcm.read() + samples += [x[0] for x in struct.iter_unpack("<f", raw[1])] + + return samples + + +def playback_test(seconds, device): + player = Player(device) + with player.changed_volume(): + for chunk in sine(440, seconds * RATE, PERIOD): + player.play(chunk) + + +def loopback_test(seconds, device, freq=455.5): + def generator(): + player = Player(device) + with player.changed_volume(): + for chunk in sine(freq, seconds * RATE, PERIOD): + player.play(chunk) + + plr_thread = threading.Thread(target=generator) + plr_thread.daemon = True + plr_thread.start() + + rec = Recorder() + samples = rec.record(seconds * RATE) + + # fft requires len(samples) to be a power of 2. + # let's trim samples to match that + real_len = 2 ** math.floor(math.floor(math.log2(len(samples)))) + real_seconds = seconds * (real_len / len(samples)) + samples = samples[0:real_len] + + Y = fft(samples) + freqs = [abs(y) for y in Y[:int(RATE/2)]] + dominant = freqs.index(max(freqs)) / real_seconds + print("Dominant frequency is {}, expected {}".format(dominant, freq)) + + epsilon = 1.0 + if abs(dominant - freq) < epsilon: + return 0 + else: + return 1 + + +def main(): + actions = { + 'playback': playback_test, + 'loopback': loopback_test, + } + parser = argparse.ArgumentParser(description='Sound testing using ALSA') + parser.add_argument('action', metavar='ACTION', choices=actions.keys()) + parser.add_argument('--duration', type=int, default=5) + parser.add_argument('--device', type=str) + args = parser.parse_args() + if args.device: + device = args.device + elif 'ALSADEVICE' in os.environ: + device = os.environ['ALSADEVICE'] + else: + device = None + return(actions[args.action](args.duration, device)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/bin/boot_mode_test b/bin/boot_mode_test index 53e4fac..7c9358c 100755 --- a/bin/boot_mode_test +++ b/bin/boot_mode_test @@ -24,7 +24,6 @@ import os import sys import logging from argparse import ArgumentParser -from platform import linux_distribution def version_check(check): @@ -38,21 +37,10 @@ def version_check(check): return code from requested check if the installed version is 16.04 or newer. """ - installed_version = int(linux_distribution()[1].split('.')[0]) - if installed_version < 16: - logging.info("This system appears to be older than 16.04 LTS so this " - "will not block a certification in progress.") - if check == 'efi': - efi_boot_check() - else: - secure_boot_check() - - return 0 + if check == 'efi': + return efi_boot_check() else: - if check == 'efi': - return efi_boot_check() - else: - return secure_boot_check() + return secure_boot_check() def efi_boot_check(): diff --git a/bin/booted_kernel_tests.py b/bin/booted_kernel_tests.py new file mode 100755 index 0000000..ed54b25 --- /dev/null +++ b/bin/booted_kernel_tests.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# Copyright 2019 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Jonathan Cave <jonathan.cave@canonical.com> + +import hashlib +import sys + +from checkbox_support.snap_utils.system import get_kernel_snap +from checkbox_support.snap_utils.system import get_bootloader + +# 64kb buffer, hopefully suitable for all devices that might run this test +BUF_SIZE = 65536 + + +def get_running_kernel_path(): + bootloader = get_bootloader() + if bootloader is None: + raise SystemExit('ERROR: failed to get bootloader') + path = '/boot/{}/kernel.img'.format(bootloader) + return path + + +def get_snap_kernel_path(): + kernel = get_kernel_snap() + if kernel is None: + raise SystemExit('ERROR: failed to get kernel snap') + path = '/snap/{}/current/kernel.img'.format(kernel) + return path + + +def get_hash(path): + sha1 = hashlib.sha1() + with open(path, 'rb') as f: + while True: + data = f.read(BUF_SIZE) + if not data: + break + sha1.update(data) + return sha1.hexdigest() + + +def kernel_matches_current(): + rh = get_hash(get_running_kernel_path()) + print('Running kernel hash:\n', rh, '\n') + sh = get_hash(get_snap_kernel_path()) + print('Current kernel snap hash:\n', sh, '\n') + if rh != sh: + return 1 + return 0 + + +if __name__ == '__main__': + sys.exit(kernel_matches_current()) diff --git a/bin/dmi-sysfs-resource b/bin/dmi-sysfs-resource new file mode 100755 index 0000000..6741030 --- /dev/null +++ b/bin/dmi-sysfs-resource @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> + +"""Collect information about all sysfs attributes related to DMI.""" + +import os + +import guacamole + + +class dmi_sysfs_resource(guacamole.Command): + + """ + Collect information about all sysfs attributes related to DMI. + + This program reads all the readable files in /sys/class/dmi/id/ and + presents them a single RFC822 record. + + @EPILOG@ + + Unreadable files (typically due to permissions) are silently skipped. + Please run this program as root if you wish to access various serial + numbers. + """ + + def invoked(self, ctx): + sysfs_root = '/sys/class/dmi/id/' + if not os.path.isdir(sysfs_root): + return + for dmi_attr in sorted(os.listdir(sysfs_root)): + dmi_filename = os.path.join(sysfs_root, dmi_attr) + if not os.path.isfile(dmi_filename): + continue + if not os.access(dmi_filename, os.R_OK): + continue + with open(dmi_filename, 'rt', encoding='utf-8') as stream: + dmi_data = stream.read().strip() + print("{}: {}".format(dmi_attr, dmi_data)) + + +if __name__ == "__main__": + dmi_sysfs_resource().main() diff --git a/bin/gateway_ping_test b/bin/gateway_ping_test index 914792c..7bac0f8 100755 --- a/bin/gateway_ping_test +++ b/bin/gateway_ping_test @@ -133,6 +133,18 @@ def get_host_to_ping(interface=None, verbose=False, default=None): stderr=subprocess.STDOUT) except subprocess.CalledProcessError: pass + # Try to get the gateway address for the interface from networkctl + cmd = 'networkctl status --no-pager --no-legend {}'.format(interface) + try: + output = subprocess.check_output(cmd, shell=True) + for line in output.decode(sys.stdout.encoding).splitlines(): + key, val = line.strip().split(':', maxsplit=1) + if key == "Gateway": + subprocess.check_output(["ping", "-q", "-c", "1", val], + stderr=subprocess.STDOUT) + break + except subprocess.CalledProcessError: + pass ARP_POPULATE_TRIES = 10 num_tries = 0 while num_tries < ARP_POPULATE_TRIES: diff --git a/bin/gpio_gpiomem_loopback.py b/bin/gpio_gpiomem_loopback.py new file mode 100755 index 0000000..c52cb23 --- /dev/null +++ b/bin/gpio_gpiomem_loopback.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# Copyright 2019 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Jonathan Cave <jonathan.cave@canonical.com> + +import RPi.GPIO as GPIO + +import os +import sys +import time + + +def loopback_test(out_lane, in_lane): + print("{} -> {}".format(out_lane, in_lane), flush=True) + out_lane = int(out_lane) + in_lane = int(in_lane) + GPIO.setup(out_lane, GPIO.OUT, initial=GPIO.LOW) + GPIO.setup(in_lane, GPIO.IN) + for i in range(6): + GPIO.output(out_lane, i % 2) + time.sleep(0.5) + if GPIO.input(in_lane) != (i % 2): + raise SystemExit("Failed loopback test out: {} in: {}".format( + out_lane, in_lane)) + time.sleep(0.5) + + +def gpio_pairs(model_name): + gpio_data = os.path.expandvars( + '$PLAINBOX_PROVIDER_DATA/gpio-loopback.{}.in'.format(model_name)) + if not os.path.exists(gpio_data): + raise SystemExit( + "ERROR: no gpio information found at: {}".format(gpio_data)) + with open(gpio_data, 'r') as f: + for line in f: + if line.startswith('#'): + continue + yield line.strip().split(',') + + +def main(): + if len(sys.argv) < 2: + raise SystemExit('Usage: gpio_loopback.py MODEL_NAME') + model_name = sys.argv[1] + + print("Using RPi.GPIO module {}".format(GPIO.VERSION)) + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + + for pair in gpio_pairs(model_name): + loopback_test(*pair) + + GPIO.cleanup() + + +if __name__ == "__main__": + main() diff --git a/bin/gpio_sysfs_loopback.py b/bin/gpio_sysfs_loopback.py new file mode 100755 index 0000000..21d8fb2 --- /dev/null +++ b/bin/gpio_sysfs_loopback.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# Copyright 2019 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Jonathan Cave <jonathan.cave@canonical.com> + +import errno +import os +import sys +import time + + +def export_gpio(lane): + try: + with open('/sys/class/gpio/export', 'w') as f_export: + f_export.write('{}\n'.format(lane)) + except OSError as e: + if e.errno == errno.EBUSY: + # EBUSY indicates GPIO already exported + print('GPIO {} already exported'.format(lane)) + pass + else: + sys.stderr.write('Failed request to export GPIO {}\n'.format(lane)) + raise + # test directory exists + if not os.path.exists('/sys/class/gpio/gpio{}'.format(lane)): + raise SystemExit('GPIO {} failed to export'.format(lane)) + + +def unexport_gpio(lane): + try: + with open('/sys/class/gpio/unexport', 'w') as f_unexport: + f_unexport.write('{}\n'.format(lane)) + except OSError: + sys.stderr.write('Failed request to unexport GPIO {}\n'.format(lane)) + raise + # test directory removed + if os.path.exists('/sys/class/gpio/gpio{}'.format(lane)): + raise SystemExit('GPIO {} failed to export'.format(lane)) + + +def configure_gpio(lane, direction): + with open('/sys/class/gpio/gpio{}/direction'.format(lane), 'wt') as f: + f.write('{}\n'.format(direction)) + + +def write_gpio(lane, val): + with open('/sys/class/gpio/gpio{}/value'.format(lane), 'wt') as f: + f.write('{}\n'.format(val)) + + +def read_gpio(lane): + with open('/sys/class/gpio/gpio{}/value'.format(lane), 'r') as f: + return f.read().strip() + + +def loopback_test(out_lane, in_lane): + print("{} -> {}".format(out_lane, in_lane), flush=True) + export_gpio(out_lane) + configure_gpio(out_lane, 'out') + export_gpio(in_lane) + configure_gpio(in_lane, 'in') + for i in range(6): + write_gpio(out_lane, i % 2) + time.sleep(0.5) + if read_gpio(in_lane) != str(i % 2): + raise SystemExit("Failed loopback test out: {} in: {}".format( + out_lane, in_lane)) + time.sleep(0.5) + unexport_gpio(out_lane) + unexport_gpio(in_lane) + + +def gpio_pairs(model_name): + gpio_data = os.path.expandvars( + '$PLAINBOX_PROVIDER_DATA/gpio-loopback.{}.in'.format(model_name)) + if not os.path.exists(gpio_data): + raise SystemExit( + "ERROR: no gpio information found at: {}".format(gpio_data)) + with open(gpio_data, 'r') as f: + for line in f: + if line.startswith('#'): + continue + yield line.strip().split(',') + + +def main(): + if len(sys.argv) < 2: + raise SystemExit('Usage: gpio_syfs_loopback.py MODEL_NAME') + model_name = sys.argv[1] + for pair in gpio_pairs(model_name): + loopback_test(*pair) + + +if __name__ == '__main__': + main() diff --git a/bin/i2c_driver_test b/bin/i2c_driver_test new file mode 100755 index 0000000..4af3bf8 --- /dev/null +++ b/bin/i2c_driver_test @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# Copyright 2016 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Authors: Gavin Lin <gavin.lin@canonical.com> +# Sylvain Pineau <sylvain.pineau@canonical.com> + +""" +This script will check number of detected I2C buses or devices + +To see how to use, please run "./i2c_driver_test" +""" + +import os +import subprocess + +from guacamole import Command + + +class Bus(Command): + + """Detect I2C bus.""" + + def invoked(self, ctx): + """Method called when the command is invoked.""" + # Detect I2C buses and calculate number of them + result = subprocess.check_output(['i2cdetect', '-l'], + universal_newlines=True) + print(result) + bus_number = len(result.splitlines()) + print('Detected bus number: {}'.format(bus_number)) + + # Test failed if no I2C bus detected + if bus_number == 0: + raise SystemExit('Test failed, no bus detected.') + + # Verify if detected number of buses is as expected + else: + if ctx.args.bus != 0: + if bus_number == ctx.args.bus: + print('Test passed') + else: + raise SystemExit('Test failed, expecting {} I2C ' + 'buses.'.format(ctx.args.bus)) + + def register_arguments(self, parser): + """Register command line arguments for the bus sub-command.""" + parser.add_argument( + '-b', '--bus', type=int, help='Expected number of I2C bus.', + default=0) + + +class Device(Command): + + """Detect I2C device.""" + + def invoked(self, ctx): + # Make sure that we have root privileges + if os.geteuid() != 0: + raise SystemExit('Error: please run this command as root') + # Calculate number of buses + result = subprocess.check_output(['i2cdetect', '-l'], + universal_newlines=True) + detected_i2c_bus = [] + for line in result.splitlines(): + detected_i2c_bus.append(line.split('\t')[0].split('-')[1]) + print('Detected buses: {}'.format(detected_i2c_bus)) + + # Detect device on each bus + exit_code = 1 + for i in detected_i2c_bus: + print('Checking I2C bus {}'.format(i)) + result = subprocess.check_output(['i2cdetect', '-y', '-r', str(i)], + universal_newlines=True) + print(result) + result_line = result.splitlines()[1:] + for l in result_line: + address_value = l.strip('\n').split(':')[1].split() + for v in address_value: + if v != '--': exit_code = 0 + if exit_code == 1: + raise SystemExit('No I2C device detected on any I2C bus') + else: + print('I2C device detected') + return exit_code + + +class I2cDriverTest(Command): + + """I2C driver test.""" + + sub_commands = ( + ('bus', Bus), + ('device', Device) + ) + + def invoked(self, ctx): + """Method called when the command is invoked.""" + if not ctx.early_args.rest: + ctx.parser.print_help() + return 1 + + +if __name__ == '__main__': + I2cDriverTest().main() + diff --git a/bin/module_loaded_test b/bin/module_loaded_test new file mode 100755 index 0000000..028d70b --- /dev/null +++ b/bin/module_loaded_test @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Authors: Jonathan Cave <jonathan.cave@canonical.com> + +""" +Script to test if a module is loaded in the running kernel. If not the +module will be loaded. This can be disabled using a flag. +""" + +import subprocess + +from guacamole import Command + + +class ModuleLoadedTest(Command): + """Check if a kernel module is loaded. If not load it.""" + + def register_arguments(self, parser): + parser.add_argument("module", help="Specify the module to test for.") + + parser.add_argument("-n", "--no-load", action="store_true", help=( + "Don't try and load the module just test.")) + + def invoked(self, context): + if self.is_module_loaded(context.args.module): + return + + # module not loaded + if context.args.no_load: + return 1 + + # attempt a load of the module + if not self.load_module(context.args.module): + return 1 + + def is_module_loaded(self, module): + with open('/proc/modules', 'r') as modules: + for line in modules: + if line.split(' ')[0] == module: + print("Found kernel module {}".format(module)) + return True + print("Couldn't find kernel module {}".format(module)) + return False + + def load_module(self, module): + if subprocess.call(['modprobe', module]) == 0: + print("Module {} was loaded".format(module)) + return True + print("Module {} failed to load".format(module)) + return False + + +if __name__ == "__main__": + ModuleLoadedTest().main() diff --git a/bin/network b/bin/network index 8c4f4ff..5bdf1f0 100755 --- a/bin/network +++ b/bin/network @@ -63,6 +63,7 @@ class IPerfPerformanceTest(object): cpu_load_fail_threshold, iperf3, num_threads, + reverse, protocol="tcp", data_size="1", run_time=None, @@ -80,6 +81,7 @@ class IPerfPerformanceTest(object): self.run_time = run_time self.scan_timeout = scan_timeout self.iface_timeout = iface_timeout + self.reverse = reverse def run_one_thread(self, cmd, port_num): """Run a single test thread, storing the output in the global results[] @@ -192,6 +194,8 @@ class IPerfPerformanceTest(object): if self.run_time is not None: cmd = "{} -c {} -t {} -i 1 -f m -P {}".format( self.executable, self.target, self.run_time, iperf_threads) + if self.reverse: + cmd += " -R" else: # Because we can vary the data size, we need to vary the timeout as # well. It takes an estimated 15 minutes to send 1GB over 10Mb/s. @@ -471,7 +475,8 @@ def run_test(args, test_target): iperf_benchmark = IPerfPerformanceTest(args.interface, test_target, args.fail_threshold, args.cpu_load_fail_threshold, - args.iperf3, args.num_threads) + args.iperf3, args.num_threads, + args.reverse) if args.datasize: iperf_benchmark.data_size = args.datasize if args.runtime: @@ -822,6 +827,9 @@ TEST_TARGET_IPERF = iperf-server.example.com '--num-threads', type=int, default=-1, help=("Number of threads to use in the test. " "(Default is computed based on network speed.)")) + test_parser.add_argument( + '--reverse', default=False, action="store_true", + help="Run in reverse mode (server sends, client receives)") # Sub info options info_parser.add_argument( diff --git a/bin/plug_connected_test.py b/bin/plug_connected_test.py new file mode 100755 index 0000000..4b2a855 --- /dev/null +++ b/bin/plug_connected_test.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright 2019 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Authors: Jonathan Cave <jonathan.cave@canonical.com> + +import sys + +from checkbox_support.snap_utils.snapd import Snapd + + +if __name__ == "__main__": + if len(sys.argv) < 3: + raise SystemExit('Usage: plug_connected_test.py SNAP PLUG') + snap_name = sys.argv[1] + plug_name = sys.argv[2] + + data = Snapd().interfaces() + for plug in data.get('plugs', []): + if plug['snap'] == snap_name and plug['plug'] == plug_name: + if 'connections' in plug: + print('{}:{} is connected to:'.format( + plug['snap'], plug['plug'])) + for slot in plug['connections']: + print('{}:{}'.format(slot['snap'], slot['slot'])) + else: + raise SystemExit('ERROR: {}:{} is not connected'.format( + plug['snap'], plug['plug'])) diff --git a/bin/serial_loopback.py b/bin/serial_loopback.py new file mode 100755 index 0000000..39fe40d --- /dev/null +++ b/bin/serial_loopback.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# Copyright 2018 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Vinay Simha BN <vinaysimha@inforcecomputing.com> +# Jonathan Cave <jonathan.cave@canonical.com> + +import argparse + +import serial + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('device', help='Serial port device e.g. /dev/ttyS1') + parser.add_argument('--baudrate', default=115200, type=int) + parser.add_argument('--bytesize', choices=[5, 6, 7, 8], type=int, + help='set bytesize, one of {5, 6, 7, 8}, default: 8', + default=8) + parser.add_argument('--parity', choices=['N', 'E', 'O', 'S', 'M'], + type=lambda c: c.upper(), + help='set parity, one of {N E O S M}, default: N', + default='N') + parser.add_argument('--stopbits', choices=[1, 2], type=int, + help='set stopbits, one of {1, 2}, default: 1', + default=1) + args = parser.parse_args() + print("Test parameters:", vars(args)) + with serial.Serial(args.device, + baudrate=args.baudrate, + bytesize=args.bytesize, + parity=args.parity, + stopbits=args.stopbits, + timeout=1) as ser: + test_str = 'loopback\n' + ser.write(test_str.encode('UTF-8')) + rcv = ser.readline().decode('UTF-8') + print("Sent:", repr(test_str)) + print("Received:", repr(rcv)) + if rcv != test_str: + raise SystemExit('SERIAL LOOPBACK TEST FAILED') + print("SERIAL LOOPBACK TEST PASSED") + + +if __name__ == '__main__': + main() diff --git a/bin/snap_tests.py b/bin/snap_tests.py new file mode 100755 index 0000000..ad1e22a --- /dev/null +++ b/bin/snap_tests.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +# Copyright 2015-2019 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Authors: Jonathan Cave <jonathan.cave@canonical.com> + +import os + +from guacamole import Command +from checkbox_support.snap_utils.snapd import Snapd + +# Requirements for the test snap: +# - the snap must not be installed at the start of the nested test plan +# - the snap must be strictly confined (no classic or devmode flags) +# - there must be different revisions on the stable & edge channels +TEST_SNAP = os.getenv('TEST_SNAP', 'test-snapd-tools') +SNAPD_TASK_TIMEOUT = int(os.getenv('SNAPD_TASK_TIMEOUT', 30)) +SNAPD_POLL_INTERVAL = int(os.getenv('SNAPD_POLL_INTERVAL', 1)) + + +class SnapList(Command): + + """snap list sub-command.""" + + def invoked(self, ctx): + """snap list should show the core package is installed.""" + data = Snapd().list() + for snap in data: + if snap['name'] in ('core', 'core16', 'core18'): + print("Found a core snap") + print(snap['name'], snap['version'], snap['revision']) + return 0 + return 1 + + +class SnapSearch(Command): + + """snap search sub-command.""" + + def invoked(self, ctx): + """snap search for TEST_SNAP.""" + data = Snapd().find(TEST_SNAP,) + for snap in data: + print('ID:', snap['id']) + print('Name:', snap['name']) + print('Developer:', snap['developer']) + return 0 + return 1 + + +class SnapInstall(Command): + + """snap install sub-command.""" + + def register_arguments(self, parser): + """Setup command arguments.""" + parser.add_argument('channel', help='channel to install from') + + def invoked(self, ctx): + """Test install of test-snapd-tools snap.""" + print('Install {}...'.format(TEST_SNAP)) + s = Snapd(SNAPD_TASK_TIMEOUT, SNAPD_POLL_INTERVAL) + s.install(TEST_SNAP, ctx.args.channel) + print('Confirm in snap list...') + data = s.list() + for snap in data: + if snap['name'] == TEST_SNAP: + return 0 + print(' not in snap list') + return 1 + + +class SnapRefresh(Command): + + """snap refresh sub-command.""" + + def invoked(self, ctx): + """Test refresh of test-snapd-tools snap.""" + def get_rev(): + data = Snapd().list() + for snap in data: + if snap['name'] == TEST_SNAP: + return snap['revision'] + print('Get starting revision...') + start_rev = get_rev() + print(' revision:', start_rev) + print('Refresh to edge...') + s = Snapd(SNAPD_TASK_TIMEOUT, SNAPD_POLL_INTERVAL) + s.refresh(TEST_SNAP, 'edge') + print('Get new revision...') + new_rev = get_rev() + print(' revision:', new_rev) + if new_rev == start_rev: + return 1 + return 0 + + +class SnapRevert(Command): + + """snap revert sub-command.""" + + def invoked(self, ctx): + """Test revert of test-snapd-tools snap.""" + s = Snapd(SNAPD_TASK_TIMEOUT, SNAPD_POLL_INTERVAL) + print('Get stable channel revision from store...') + r = s.info(TEST_SNAP) + stable_rev = r['channels']['latest/stable']['revision'] + print('Get current installed revision...') + r = s.list(TEST_SNAP) + installed_rev = r['revision'] # should be edge revision + print('Reverting snap {}...'.format(TEST_SNAP)) + s.revert(TEST_SNAP) + print('Get new installed revision...') + r = s.list(TEST_SNAP) + rev = r['revision'] + if rev != stable_rev: + print("Not stable revision number") + return 1 + if rev == installed_rev: + print("Identical revision number") + return 1 + return 0 + + +class SnapReupdate(Command): + + """snap reupdate sub-command.""" + + def invoked(self, ctx): + """Test re-update of test-snapd-tools snap.""" + s = Snapd(SNAPD_TASK_TIMEOUT, SNAPD_POLL_INTERVAL) + print('Get edge channel revision from store...') + r = s.info(TEST_SNAP) + edge_rev = r['channels']['latest/edge']['revision'] + print('Remove edge revision...') + s.remove(TEST_SNAP, edge_rev) + print('Refresh to edge channel...') + s.refresh(TEST_SNAP, 'edge') + print('Get new installed revision...') + r = s.list(TEST_SNAP) + rev = r['revision'] + if rev != edge_rev: + print("Not edge revision number") + return 1 + + +class SnapRemove(Command): + + """snap remove sub-command.""" + + def invoked(self, ctx): + """Test remove of test-snapd-tools snap.""" + print('Install {}...'.format(TEST_SNAP)) + s = Snapd(SNAPD_TASK_TIMEOUT, SNAPD_POLL_INTERVAL) + s.remove(TEST_SNAP) + print('Check not in snap list') + data = s.list() + for snap in data: + if snap['name'] == TEST_SNAP: + print(' found in snap list') + return 1 + return 0 + + +class Snap(Command): + + """Fake snap like command.""" + + sub_commands = ( + ('list', SnapList), + ('search', SnapSearch), + ('install', SnapInstall), + ('refresh', SnapRefresh), + ('revert', SnapRevert), + ('reupdate', SnapReupdate), + ('remove', SnapRemove) + ) + + +if __name__ == '__main__': + Snap().main() diff --git a/bin/test_bt_keyboard b/bin/test_bt_keyboard new file mode 100755 index 0000000..1457a1d --- /dev/null +++ b/bin/test_bt_keyboard @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# Copyright 2016 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Maciej Kisielewski <maciej.kisielewski@canonical.com> + +import checkbox_support.bt_helper + + +def main(): + mgr = bt_helper.BtManager() + mgr.ensure_adapters_powered() + print('Make sure that the keyboard is turned on and is in pairable state.') + print('Then press ENTER') + input() + print('Scanning for devices...') + mgr.scan() + + keyboards = sorted(mgr.get_bt_devices( + category=bt_helper.BT_KEYBOARD, filters={'Paired': False}), + key=lambda x: int(x.rssi or -255), reverse=True) + if not keyboards: + print("No keyboards detected") + return + print('Detected keyboards (sorted by RSSI; highest first).') + # let's assing numbers to keyboards + keyboards = dict(enumerate(keyboards, 1)) + for num, kb in keyboards.items(): + print('{}. {} (RSSI: {})'.format(num, kb, kb.rssi)) + chosen = False + while not chosen: + print('Which one would you like to connect to? (0 to exit) ', + flush=True) + num = input() + if num == '0': + return + chosen = num.isnumeric() and int(num) in keyboards.keys() + kb = keyboards[int(num)] + print('{} chosen. Pairing...'.format(kb)) + kb.pair() + print(('Try typing on a keyboard. ' + 'Type "quit" and press ENTER to end the test.')) + while input().lower() != 'quit': + pass + print('Unpairing the keyboard...') + kb.unpair() + + +if __name__ == '__main__': + main() diff --git a/bin/testlib.py b/bin/testlib.py new file mode 100755 index 0000000..5f783f8 --- /dev/null +++ b/bin/testlib.py @@ -0,0 +1,1450 @@ +from __future__ import print_function +# +# testlib.py quality assurance test script +# Copyright (C) 2008-2016 Canonical Ltd. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# + +'''Common classes and functions for package tests.''' + +import crypt +import glob +import grp +import gzip +import os +import os.path +import platform +import pwd +import random +import re +import shutil +import socket +import string +import subprocess +import sys +import tempfile +import time +import unittest + +from stat import ST_SIZE + +# Don't make python-pexpect mandatory +try: + import pexpect +except ImportError: + pass + +import warnings +warnings.filterwarnings('ignore', message=r'.*apt_pkg\.TagFile.*', category=DeprecationWarning) +try: + import apt_pkg + # cope with apt_pkg api changes. + if 'init_system' in dir(apt_pkg): + apt_pkg.init_system() + else: + apt_pkg.InitSystem() +except: + # On non-Debian system, fall back to simple comparison without debianisms + class apt_pkg(object): + @staticmethod + def version_compare(one, two): + list_one = one.split('.') + list_two = two.split('.') + while len(list_one) > 0 and len(list_two) > 0: + try: + if int(list_one[0]) > int(list_two[0]): + return 1 + if int(list_one[0]) < int(list_two[0]): + return -1 + except: + # ugh, non-numerics in the version, fall back to + # string comparison, which will be wrong for e.g. + # 3.2 vs 3.16rc1 + if list_one[0] > list_two[0]: + return 1 + if list_one[0] < list_two[0]: + return -1 + list_one.pop(0) + list_two.pop(0) + return 0 + + @staticmethod + def VersionCompare(one, two): + return apt_pkg.version_compare(one, two) + +bogus_nxdomain = "208.69.32.132" + +# http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html +# This is needed so that the subprocesses that produce endless output +# actually quit when the reader goes away. +import signal +def subprocess_setup(): + # Python installs a SIGPIPE handler by default. This is usually not what + # non-Python subprocesses expect. + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + + +class TimedOutException(Exception): + def __init__(self, value="Timed Out"): + self.value = value + + def __str__(self): + return repr(self.value) + + +def _restore_backup(path): + pathbackup = path + '.autotest' + if os.path.exists(pathbackup): + shutil.move(pathbackup, path) + + +def _save_backup(path): + pathbackup = path + '.autotest' + if os.path.exists(path) and not os.path.exists(pathbackup): + shutil.copy2(path, pathbackup) + # copy2 does not copy ownership, so do it here. + # Reference: http://docs.python.org/library/shutil.html + a = os.stat(path) + os.chown(pathbackup, a[4], a[5]) + + +def config_copydir(path): + if os.path.exists(path) and not os.path.isdir(path): + raise OSError("'%s' is not a directory" % (path)) + _restore_backup(path) + + pathbackup = path + '.autotest' + if os.path.exists(path): + shutil.copytree(path, pathbackup, symlinks=True) + + +def config_replace(path, contents, append=False): + '''Replace (or append) to a config file''' + _restore_backup(path) + if os.path.exists(path): + _save_backup(path) + if append: + with open(path) as fh: + contents = fh.read() + contents + with open(path, 'w') as fh: + fh.write(contents) + + +def config_comment(path, field): + _save_backup(path) + contents = "" + with open(path) as fh: + for line in fh: + if re.search("^\s*%s\s*=" % (field), line): + line = "#" + line + contents += line + + with open(path + '.new', 'w') as new_fh: + new_fh.write(contents) + os.rename(path + '.new', path) + + +def config_set(path, field, value, spaces=True): + _save_backup(path) + contents = "" + if spaces: + setting = '%s = %s\n' % (field, value) + else: + setting = '%s=%s\n' % (field, value) + found = False + with open(path) as fh: + for line in fh: + if re.search("^\s*%s\s*=" % (field), line): + found = True + line = setting + contents += line + if not found: + contents += setting + + with open(path + '.new', 'w') as new_config: + new_config.write(contents) + os.rename(path + '.new', path) + + +def config_patch(path, patch, depth=1): + '''Patch a config file''' + _restore_backup(path) + _save_backup(path) + + handle, name = mkstemp_fill(patch) + rc = subprocess.call(['/usr/bin/patch', '-p%s' % depth, path], stdin=handle, stdout=subprocess.PIPE) + os.unlink(name) + if rc != 0: + raise Exception("Patch failed") + + +def config_restore(path): + '''Rename a replaced config file back to its initial state''' + _restore_backup(path) + + +def timeout(secs, f, *args): + def handler(signum, frame): + raise TimedOutException() + + old = signal.signal(signal.SIGALRM, handler) + result = None + signal.alarm(secs) + try: + result = f(*args) + finally: + signal.alarm(0) + signal.signal(signal.SIGALRM, old) + + return result + + +def require_nonroot(): + if os.geteuid() == 0: + print("This series of tests should be run as a regular user with sudo access, not as root.", file=sys.stderr) + sys.exit(1) + + +def require_root(): + if os.geteuid() != 0: + print("This series of tests should be run with root privileges (e.g. via sudo).", file=sys.stderr) + sys.exit(1) + + +def require_sudo(): + if os.geteuid() != 0 or os.environ.get('SUDO_USER', None) is None: + print("This series of tests must be run under sudo.", file=sys.stderr) + sys.exit(1) + if os.environ['SUDO_USER'] == 'root': + print('Please run this test using sudo from a regular user. (You ran sudo from root.)', file=sys.stderr) + sys.exit(1) + + +def random_string(length, lower=False): + '''Return a random string, consisting of ASCII letters, with given + length.''' + + s = '' + selection = string.ascii_letters + if lower: + selection = string.ascii_lowercase + maxind = len(selection) - 1 + for l in range(length): + s += selection[random.randint(0, maxind)] + return s + + +def mkstemp_fill(contents, suffix='', prefix='testlib-', dir=None): + '''As tempfile.mkstemp does, return a (file, name) pair, but with + prefilled contents.''' + + handle, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir) + os.close(handle) + handle = open(name, "w+") + handle.write(contents) + handle.flush() + handle.seek(0) + + return handle, name + + +def create_fill(path, contents, mode=0o644): + '''Safely create a page''' + # make the temp file in the same dir as the destination file so we + # don't get invalid cross-device link errors when we rename + handle, name = mkstemp_fill(contents, dir=os.path.dirname(path)) + handle.close() + os.rename(name, path) + os.chmod(path, mode) + + +def login_exists(login): + '''Checks whether the given login exists on the system.''' + + try: + pwd.getpwnam(login) + return True + except KeyError: + return False + + +def group_exists(group): + '''Checks whether the given login exists on the system.''' + + try: + grp.getgrnam(group) + return True + except KeyError: + return False + + +def is_empty_file(path): + '''test if file is empty, returns True if so''' + with open(path) as fh: + return (len(fh.read()) == 0) + + +def recursive_rm(dirPath, contents_only=False): + '''recursively remove directory''' + names = os.listdir(dirPath) + for name in names: + path = os.path.join(dirPath, name) + if os.path.islink(path) or not os.path.isdir(path): + os.unlink(path) + else: + recursive_rm(path) + if not contents_only: + os.rmdir(dirPath) + + +def check_pidfile(exe, pidfile): + '''Checks if pid in pidfile is running''' + if not os.path.exists(pidfile): + return False + + # get the pid + try: + with open(pidfile, 'r') as fd: + pid = fd.readline().rstrip('\n') + except: + return False + + return check_pid(exe, pid) + + +def check_pid(exe, pid): + '''Checks if pid is running''' + cmdline = "/proc/%s/cmdline" % (str(pid)) + if not os.path.exists(cmdline): + return False + + # get the command line + try: + with open(cmdline, 'r') as fd: + tmp = fd.readline().split('\0') + except: + return False + + # this allows us to match absolute paths or just the executable name + if re.match('^' + exe + '$', tmp[0]) or \ + re.match('.*/' + exe + '$', tmp[0]) or \ + re.match('^' + exe + ': ', tmp[0]) or \ + re.match('^\(' + exe + '\)', tmp[0]): + return True + + return False + + +def check_port(port, proto, ver=4): + '''Check if something is listening on the specified port. + WARNING: for some reason this does not work with a bind mounted /proc + ''' + assert (port >= 1) + assert (port <= 65535) + assert (proto.lower() == "tcp" or proto.lower() == "udp") + assert (ver == 4 or ver == 6) + + fn = "/proc/net/%s" % (proto) + if ver == 6: + fn += str(ver) + + rc, report = cmd(['cat', fn]) + assert (rc == 0) + + hport = "%0.4x" % port + + if re.search(': [0-9a-f]{8}:%s [0-9a-f]' % str(hport).lower(), report.lower()): + return True + return False + + +def get_arch(): + '''Get the current architecture''' + rc, report = cmd(['uname', '-m']) + assert (rc == 0) + return report.strip() + + +def get_multiarch_tuple(): + '''Get the current debian multiarch tuple''' + + # XXX dpkg-architecture on Ubuntu 12.04 requires -q be adjacent to + # the variable name + rc, report = cmd(['dpkg-architecture', '-qDEB_HOST_MULTIARCH']) + assert (rc == 0) + return report.strip() + + +def get_bits(): + '''return the default architecture number of bits (32 or 64, usually)''' + return platform.architecture()[0][0:2] + + +def get_memory(): + '''Gets total ram and swap''' + meminfo = "/proc/meminfo" + memtotal = 0 + swaptotal = 0 + if not os.path.exists(meminfo): + return (False, False) + + try: + fd = open(meminfo, 'r') + for line in fd.readlines(): + splitline = line.split() + if splitline[0] == 'MemTotal:': + memtotal = int(splitline[1]) + elif splitline[0] == 'SwapTotal:': + swaptotal = int(splitline[1]) + fd.close() + except: + return (False, False) + + return (memtotal, swaptotal) + + +def is_running_in_vm(): + '''Check if running under a VM''' + # add other virtualization environments here + for search in ['QEMU Virtual CPU']: + rc, report = cmd_pipe(['dmesg'], ['grep', search]) + if rc == 0: + return True + return False + + +def ubuntu_release(): + '''Get the Ubuntu release''' + f = "/etc/lsb-release" + try: + size = os.stat(f)[ST_SIZE] + except: + return "UNKNOWN" + + if size > 1024 * 1024: + raise IOError('Could not open "%s" (too big)' % f) + + with open("/etc/lsb-release", 'r') as fh: + lines = fh.readlines() + + pat = re.compile(r'DISTRIB_CODENAME') + for line in lines: + if pat.search(line): + return line.split('=')[1].rstrip('\n').rstrip('\r') + + return "UNKNOWN" + + +def cmd(command, input=None, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, stdin=None, timeout=None, env=None): + '''Try to execute given command (array) and return its stdout, or return + a textual error if it failed.''' + + try: + sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup, env=env, universal_newlines=True) + except OSError as e: + return [127, str(e)] + + out, outerr = sp.communicate(input) + # Handle redirection of stdout + if out is None: + out = '' + # Handle redirection of stderr + if outerr is None: + outerr = '' + return [sp.returncode, out + outerr] + + +def cmd_pipe(command1, command2, input=None, stderr=subprocess.STDOUT, stdin=None): + '''Try to pipe command1 into command2.''' + try: + sp1 = subprocess.Popen(command1, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr, close_fds=True) + sp2 = subprocess.Popen(command2, stdin=sp1.stdout, stdout=subprocess.PIPE, stderr=stderr, close_fds=True) + except OSError as e: + return [127, str(e)] + + out = sp2.communicate(input)[0] + return [sp2.returncode, out] + + +def cwd_has_enough_space(cdir, total_bytes): + '''Determine if the partition of the current working directory has 'bytes' + free.''' + rc, df_output = cmd(['df']) + if rc != 0: + result = 'df failed, got exit code %d, expected %d\n' % (rc, 0) + raise OSError(result) + if rc != 0: + return False + + kb = total_bytes / 1024 + + mounts = dict() + for line in df_output.splitlines(): + if '/' not in line: + continue + tmp = line.split() + mounts[tmp[5]] = int(tmp[3]) + + cdir = os.getcwd() + while cdir != '/': + if cdir not in mounts: + cdir = os.path.dirname(cdir) + continue + return kb < mounts[cdir] + + return kb < mounts['/'] + + +def get_md5(filename): + '''Gets the md5sum of the file specified''' + + (rc, report) = cmd(["/usr/bin/md5sum", "-b", filename]) + expected = 0 + assert (expected == rc) + + return report.split(' ')[0] + + +def dpkg_compare_installed_version(pkg, check, version): + '''Gets the version for the installed package, and compares it to the + specified version. + ''' + (rc, report) = cmd(["/usr/bin/dpkg", "-s", pkg]) + assert (rc == 0) + assert ("Status: install ok installed" in report) + installed_version = "" + for line in report.splitlines(): + if line.startswith("Version: "): + installed_version = line.split()[1] + + assert (installed_version != "") + + (rc, report) = cmd(["/usr/bin/dpkg", "--compare-versions", installed_version, check, version]) + assert (rc == 0 or rc == 1) + if rc == 0: + return True + return False + + +def _run_apt_command(pkg_list, apt_cmd='install'): + env = os.environ.copy() + env['DEBIAN_FRONTEND'] = 'noninteractive' + # debugging version, but on precise doesn't actually run dpkg + # command = ['apt-get', '-y', '--force-yes', '-o', 'Dpkg::Options::=--force-confold', '-o', 'Debug::pkgDPkgPM=true', apt_cmd] + command = ['apt-get', '-y', '--force-yes', '-o', 'Dpkg::Options::=--force-confold', apt_cmd] + command.extend(pkg_list) + rc, report = cmd(command) + return rc, report + + +# note: for the following install_* functions, you probably want the +# versions in the TestlibCase class (see below) +def install_builddeps(src_pkg): + rc, report = _run_apt_command([src_pkg], 'build-dep') + assert(rc == 0) + + +def install_package(package): + rc, report = _run_apt_command([package], 'install') + assert(rc == 0) + + +def install_packages(pkg_list): + rc, report = _run_apt_command(pkg_list, 'install') + assert(rc == 0) + + +def prepare_source(source, builder, cached_src, build_src, patch_system): + '''Download and unpack source package, installing necessary build depends, + adjusting the permissions for the 'builder' user, and returning the + directory of the unpacked source. Patch system can be one of: + - cdbs + - dpatch + - quilt + - quiltv3 + - None (not the string) + + This is normally used like this: + + def setUp(self): + ... + self.topdir = os.getcwd() + self.cached_src = os.path.join(os.getcwd(), "source") + self.tmpdir = tempfile.mkdtemp(prefix='testlib', dir='/tmp') + self.builder = testlib.TestUser() + testlib.cmd(['chgrp', self.builder.login, self.tmpdir]) + os.chmod(self.tmpdir, 0o775) + + def tearDown(self): + ... + self.builder = None + self.topdir = os.getcwd() + if os.path.exists(self.tmpdir): + testlib.recursive_rm(self.tmpdir) + + def test_suite_build(self): + ... + build_dir = testlib.prepare_source('foo', \ + self.builder, \ + self.cached_src, \ + os.path.join(self.tmpdir, \ + os.path.basename(self.cached_src)), + "quilt") + os.chdir(build_dir) + + # Example for typical build, adjust as necessary + print("") + print(" make clean") + rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'clean']) + + print(" configure") + rc, report = testlib.cmd(['sudo', '-u', self.builder.login, './configure', '--prefix=%s' % self.tmpdir, '--enable-debug']) + + print(" make (will take a while)") + rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make']) + + print(" make check (will take a while)",) + rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'check']) + expected = 0 + result = 'Got exit code %d, expected %d\n' % (rc, expected) + self.assertEqual(expected, rc, result + report) + + def test_suite_cleanup(self): + ... + if os.path.exists(self.cached_src): + testlib.recursive_rm(self.cached_src) + + It is up to the caller to clean up cached_src and build_src (as in the + above example, often the build_src is in a tmpdir that is cleaned in + tearDown() and the cached_src is cleaned in a one time clean-up + operation (eg 'test_suite_cleanup()) which must be run after the build + suite test (obviously). + ''' + + # Make sure we have a clean slate + assert (os.path.exists(os.path.dirname(build_src))) + assert (not os.path.exists(build_src)) + + cdir = os.getcwd() + if os.path.exists(cached_src): + shutil.copytree(cached_src, build_src) + os.chdir(build_src) + else: + # Only install the build dependencies on the initial setup + install_builddeps(source) + os.makedirs(build_src) + os.chdir(build_src) + + # These are always needed + pkgs = ['build-essential', 'dpkg-dev', 'fakeroot'] + install_packages(pkgs) + + rc, report = cmd(['apt-get', 'source', source]) + assert (rc == 0) + shutil.copytree(build_src, cached_src) + print("(unpacked %s)" % os.path.basename(glob.glob('%s_*.dsc' % source)[0]), end=' ') + + unpacked_dir = os.path.join(build_src, glob.glob('%s-*' % source)[0]) + + # Now apply the patches. Do it here so that we don't mess up our cached + # sources. + os.chdir(unpacked_dir) + assert (patch_system in ['cdbs', 'dpatch', 'quilt', 'quiltv3', None]) + if patch_system is not None and patch_system != "quiltv3": + if patch_system == "quilt": + os.environ.setdefault('QUILT_PATCHES', 'debian/patches') + rc, report = cmd(['quilt', 'push', '-a']) + assert (rc == 0) + elif patch_system == "cdbs": + rc, report = cmd(['./debian/rules', 'apply-patches']) + assert (rc == 0) + elif patch_system == "dpatch": + rc, report = cmd(['dpatch', 'apply-all']) + assert (rc == 0) + + cmd(['chown', '-R', '%s:%s' % (builder.uid, builder.gid), build_src]) + os.chdir(cdir) + + return unpacked_dir + + +def get_changelog_version(source_dir): + '''Extract a package version from a changelog''' + package_version = "" + + changelog_file = os.path.join(source_dir, "debian/changelog") + + if os.path.exists(changelog_file): + changelog = open(changelog_file, 'r') + header = changelog.readline().split() + package_version = header[1].strip('()') + + return package_version + + +def _aa_status(): + '''Get aa-status output''' + exe = "/usr/sbin/aa-status" + assert (os.path.exists(exe)) + if os.geteuid() == 0: + return cmd([exe]) + return cmd(['sudo', exe]) + + +def is_apparmor_loaded(path): + '''Check if profile is loaded''' + rc, report = _aa_status() + if rc != 0: + return False + + for line in report.splitlines(): + if line.endswith(path): + return True + return False + + +def is_apparmor_confined(path): + '''Check if application is confined''' + rc, report = _aa_status() + if rc != 0: + return False + + for line in report.splitlines(): + if re.search('%s \(' % path, line): + return True + return False + + +def check_apparmor(path, first_ubuntu_release, is_running=True): + '''Check if path is loaded and confined for everything higher than the + first Ubuntu release specified. + + Usage: + rc, report = testlib.check_apparmor('/usr/sbin/foo', 8.04, is_running=True) + if rc < 0: + return self._skipped(report) + + expected = 0 + result = 'Got exit code %d, expected %d\n' % (rc, expected) + self.assertEqual(expected, rc, result + report) + ''' + global manager + rc = -1 + + if manager.lsb_release["Release"] < first_ubuntu_release: + return (rc, "Skipped apparmor check") + + if not os.path.exists('/sbin/apparmor_parser'): + return (rc, "Skipped (couldn't find apparmor_parser)") + + rc = 0 + msg = "" + if not is_apparmor_loaded(path): + rc = 1 + msg = "Profile not loaded for '%s'" % path + + # this check only makes sense it the 'path' is currently executing + if is_running and rc == 0 and not is_apparmor_confined(path): + rc = 1 + msg = "'%s' is not running in enforce mode" % path + + return (rc, msg) + + +def get_gcc_version(gcc, full=True): + gcc_version = 'none' + if not gcc.startswith('/'): + gcc = '/usr/bin/%s' % (gcc) + if os.path.exists(gcc): + gcc_version = 'unknown' + lines = cmd([gcc, '-v'])[1].strip().splitlines() + version_lines = [x for x in lines if x.startswith('gcc version')] + if len(version_lines) == 1: + gcc_version = " ".join(version_lines[0].split()[2:]) + if not full: + return gcc_version.split()[0] + return gcc_version + + +def is_kdeinit_running(): + '''Test if kdeinit is running''' + # applications that use kdeinit will spawn it if it isn't running in the + # test. This is a problem because it does not exit. This is a helper to + # check for it. + rc, report = cmd(['ps', 'x']) + if 'kdeinit4 Running' not in report: + print("kdeinit not running (you may start/stop any KDE application then run this script again)", file=sys.stderr) + return False + return True + + +def get_pkgconfig_flags(libs=[]): + '''Find pkg-config flags for libraries''' + assert (len(libs) > 0) + rc, pkg_config = cmd(['pkg-config', '--cflags', '--libs'] + libs) + expected = 0 + if rc != expected: + print('Got exit code %d, expected %d\n' % (rc, expected), file=sys.stderr) + assert(rc == expected) + return pkg_config.split() + + +def cap_to_name(cap_num): + '''given an integer, return the capability name''' + rc, output = cmd(['capsh', '--decode=%x' % cap_num]) + expected = 0 + if rc != expected: + print('capsh: got exit code %d, expected %d\n' % (rc, expected), file=sys.stderr) + cap_name = output.strip().split('=')[1] + return cap_name + + +def enumerate_capabilities(): + i = 0 + cap_list = [] + done = False + while not done: + cap_name = cap_to_name(pow(2, i)) + if cap_name == str(i): + done = True + else: + cap_list.append(cap_name) + i += 1 + if i > 64: + done = True + return cap_list + + +class TestDaemon: + '''Helper class to manage daemons consistently''' + def __init__(self, init): + '''Setup daemon attributes''' + self.initscript = init + + def start(self): + '''Start daemon''' + rc, report = cmd([self.initscript, 'start']) + expected = 0 + result = 'Got exit code %d, expected %d\n' % (rc, expected) + time.sleep(2) + if expected != rc: + return (False, result + report) + + if "fail" in report: + return (False, "Found 'fail' in report\n" + report) + + return (True, "") + + def stop(self): + '''Stop daemon''' + rc, report = cmd([self.initscript, 'stop']) + expected = 0 + result = 'Got exit code %d, expected %d\n' % (rc, expected) + if expected != rc: + return (False, result + report) + + if "fail" in report: + return (False, "Found 'fail' in report\n" + report) + + return (True, "") + + def reload(self): + '''Reload daemon''' + rc, report = cmd([self.initscript, 'force-reload']) + expected = 0 + result = 'Got exit code %d, expected %d\n' % (rc, expected) + if expected != rc: + return (False, result + report) + + if "fail" in report: + return (False, "Found 'fail' in report\n" + report) + + return (True, "") + + def restart(self): + '''Restart daemon''' + (res, str) = self.stop() + if not res: + return (res, str) + + (res, str) = self.start() + if not res: + return (res, str) + + return (True, "") + + def force_restart(self): + '''Restart daemon even if already stopped''' + (res, str) = self.stop() + + (res, str) = self.start() + if not res: + return (res, str) + + return (True, "") + + def status(self): + '''Check daemon status''' + rc, report = cmd([self.initscript, 'status']) + expected = 0 + result = 'Got exit code %d, expected %d\n' % (rc, expected) + if expected != rc: + return (False, result + report) + + if "fail" in report: + return (False, "Found 'fail' in report\n" + report) + + return (True, "") + + +class TestlibManager(object): + '''Singleton class used to set up per-test-run information''' + def __init__(self): + # Set glibc aborts to dump to stderr instead of the tty so test output + # is more sane. + os.environ.setdefault('LIBC_FATAL_STDERR_', '1') + + # check verbosity + self.verbosity = False + if (len(sys.argv) > 1 and '-v' in sys.argv[1:]): + self.verbosity = True + + # Load LSB release file + self.lsb_release = dict() + if os.path.exists('/usr/bin/lsb_release') and not os.path.exists('/bin/lsb_release'): + for line in subprocess.Popen(['lsb_release', '-a'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True).communicate()[0].splitlines(): + field, value = line.split(':', 1) + value = value.strip() + field = field.strip() + # Convert numerics + try: + value = float(value) + except: + pass + self.lsb_release.setdefault(field, value) + if not self.lsb_release: + self.lsb_release = { + 'Distributor ID': 'Ubuntu Core', + 'Description': 'Ubuntu Core 16', + 'Release': 16, + 'Codename': 'xenial', + } + + # FIXME: hack OEM releases into known-Ubuntu versions + if self.lsb_release['Distributor ID'] == "HP MIE (Mobile Internet Experience)": + if self.lsb_release['Release'] == 1.0: + self.lsb_release['Distributor ID'] = "Ubuntu" + self.lsb_release['Release'] = 8.04 + else: + raise OSError("Unknown version of HP MIE") + + # FIXME: hack to assume a most-recent release if we're not + # running under Ubuntu. + if self.lsb_release['Distributor ID'] not in ["Ubuntu", "Linaro"]: + self.lsb_release['Release'] = 10000 + # Adjust Linaro release to pretend to be Ubuntu + if self.lsb_release['Distributor ID'] in ["Linaro"]: + self.lsb_release['Distributor ID'] = "Ubuntu" + self.lsb_release['Release'] -= 0.01 + + # Load arch + if not os.path.exists('/usr/bin/dpkg'): + machine = cmd(['uname', '-m'])[1].strip() + if machine.endswith('86'): + self.dpkg_arch = 'i386' + elif machine.endswith('_64'): + self.dpkg_arch = 'amd64' + elif machine.startswith('arm'): + self.dpkg_arch = 'armel' + else: + raise ValueError("Unknown machine type '%s'" % (machine)) + else: + self.dpkg_arch = cmd(['dpkg', '--print-architecture'])[1].strip() + + # Find kernel version + self.kernel_is_ubuntu = False + self.kernel_version_signature = None + self.kernel_version = cmd(["uname", "-r"])[1].strip() + versig = '/proc/version_signature' + if os.path.exists(versig): + self.kernel_is_ubuntu = True + self.kernel_version_signature = open(versig).read().strip() + self.kernel_version_ubuntu = self.kernel_version + elif os.path.exists('/usr/bin/dpkg'): + # this can easily be inaccurate but is only an issue for Dapper + rc, out = cmd(['dpkg', '-l', 'linux-image-%s' % (self.kernel_version)]) + if rc == 0: + self.kernel_version_signature = out.strip().split('\n').pop().split()[2] + self.kernel_version_ubuntu = self.kernel_version_signature + if self.kernel_version_signature is None: + # Attempt to fall back to something for non-Debian-based + self.kernel_version_signature = self.kernel_version + self.kernel_version_ubuntu = self.kernel_version + # Build ubuntu version without hardware suffix + try: + self.kernel_version_ubuntu = "-".join([x for x in self.kernel_version_signature.split(' ')[1].split('-') if re.search('^[0-9]', x)]) + except: + pass + + # Find gcc version + self.gcc_version = get_gcc_version('gcc') + + # Find libc + self.path_libc = [x.split()[2] for x in cmd(['ldd', '/bin/ls'])[1].splitlines() if x.startswith('\tlibc.so.')][0] + + # Report self + if self.verbosity: + kernel = self.kernel_version_ubuntu + if kernel != self.kernel_version_signature: + kernel += " (%s)" % (self.kernel_version_signature) + print("Running test: '%s' distro: '%s %.2f' kernel: '%s' arch: '%s' uid: %d/%d SUDO_USER: '%s')" % ( + sys.argv[0], + self.lsb_release['Distributor ID'], + self.lsb_release['Release'], + kernel, + self.dpkg_arch, + os.geteuid(), os.getuid(), + os.environ.get('SUDO_USER', '')), file=sys.stdout) + sys.stdout.flush() + + # Additional heuristics + # if os.environ.get('SUDO_USER', os.environ.get('USER', '')) in ['mdeslaur']: + # sys.stdout.write("Replying to Marc Deslauriers in http://launchpad.net/bugs/%d: " % random.randint(600000, 980000)) + # sys.stdout.flush() + # time.sleep(0.5) + # sys.stdout.write("destroyed\n") + # time.sleep(0.5) + + def hello(self, msg): + print("Hello from %s" % (msg), file=sys.stderr) +# The central instance +manager = TestlibManager() + + +class TestlibCase(unittest.TestCase): + def __init__(self, *args): + '''This is called for each TestCase test instance, which isn't much better + than SetUp.''' + + unittest.TestCase.__init__(self, *args) + + # Attach to and duplicate dicts from manager singleton + self.manager = manager + # self.manager.hello(repr(self) + repr(*args)) + self.my_verbosity = self.manager.verbosity + self.lsb_release = self.manager.lsb_release + self.dpkg_arch = self.manager.dpkg_arch + self.kernel_version = self.manager.kernel_version + self.kernel_version_signature = self.manager.kernel_version_signature + self.kernel_version_ubuntu = self.manager.kernel_version_ubuntu + self.kernel_is_ubuntu = self.manager.kernel_is_ubuntu + self.gcc_version = self.manager.gcc_version + self.path_libc = self.manager.path_libc + + def version_compare(self, one, two): + if 'version_compare' in dir(apt_pkg): + return apt_pkg.version_compare(one, two) + else: + return apt_pkg.VersionCompare(one, two) + + def assertFileType(self, filename, filetype, strict=True): + '''Checks the file type of the file specified''' + + (rc, report, out) = self._testlib_shell_cmd(["/usr/bin/file", "-b", filename]) + out = out.strip() + expected = 0 + # Absolutely no idea why this happens on Hardy + if self.lsb_release['Release'] == 8.04 and rc == 255 and len(out) > 0: + rc = 0 + result = 'Got exit code %d, expected %d:\n%s\n' % (rc, expected, report) + self.assertEqual(expected, rc, result) + + if strict: + filetype = '^%s$' % (filetype) + else: + # accept if the beginning of the line matches + filetype = '^%s' % (filetype) + result = 'File type reported by file: [%s], expected regex: [%s]\n' % (out, filetype) + self.assertNotEqual(None, re.search(filetype, out), result) + + def yank_commonname_from_cert(self, certfile): + '''Extract the commonName from a given PEM''' + rc, out = cmd(['openssl', 'asn1parse', '-in', certfile]) + if rc == 0: + ready = False + for line in out.splitlines(): + if ready: + return line.split(':')[-1] + if ':commonName' in line: + ready = True + return socket.getfqdn() + + def announce(self, text): + if self.my_verbosity: + print("(%s) " % (text), file=sys.stderr, end='') + sys.stdout.flush() + + def make_clean(self): + rc, output = self.shell_cmd(['make', 'clean']) + self.assertEqual(rc, 0, output) + + def get_makefile_compiler(self): + # Find potential compiler name + compiler = 'gcc' + if os.path.exists('Makefile'): + for line in open('Makefile'): + if line.startswith('CC') and '=' in line: + items = [x.strip() for x in line.split('=')] + if items[0] == 'CC': + compiler = items[1] + break + return compiler + + def make_target(self, target, expected=0): + '''Compile a target and report output''' + + compiler = self.get_makefile_compiler() + rc, output = self.shell_cmd(['make', target]) + self.assertEqual(rc, expected, 'rc(%d)!=%d:\n' % (rc, expected) + output) + self.assertTrue('%s ' % (compiler) in output, 'Expected "%s":' % (compiler) + output) + return output + + # call as return testlib.skipped() + def _skipped(self, reason=""): + '''Provide a visible way to indicate that a test was skipped''' + if reason != "": + reason = ': %s' % (reason) + self.announce("skipped%s" % (reason)) + return False + + def _testlib_shell_cmd(self, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=None): + argstr = "'" + "', '".join(args).strip() + "'" + rc, out = cmd(args, stdin=stdin, stdout=stdout, stderr=stderr, env=env) + report = 'Command: ' + argstr + '\nOutput:\n' + out + return rc, report, out + + def shell_cmd(self, args, stdin=None): + return cmd(args, stdin=stdin) + + def assertShellExitEquals(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=None, msg="", bare_report=False): + '''Test a shell command matches a specific exit code''' + rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr, env=env) + result = 'Got exit code %d, expected %d\n' % (rc, expected) + self.assertEqual(expected, rc, msg + result + report) + if bare_report: + return out + else: + return report + + # make sure exit value is in a list of expected values + def assertShellExitIn(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""): + '''Test a shell command matches a specific exit code''' + rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) + result = 'Got exit code %d, expected one of %s\n' % (rc, ', '.join(map(str, expected))) + self.assertIn(rc, expected, msg + result + report) + + def assertShellExitNotEquals(self, unwanted, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""): + '''Test a shell command doesn't match a specific exit code''' + rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) + result = 'Got (unwanted) exit code %d\n' % rc + self.assertNotEqual(unwanted, rc, msg + result + report) + + def assertShellOutputContains(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False, expected=None): + '''Test a shell command contains a specific output''' + rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) + result = 'Got exit code %d. Looking for text "%s"\n' % (rc, text) + if not invert: + self.assertTrue(text in out, msg + result + report) + else: + self.assertFalse(text in out, msg + result + report) + if expected is not None: + result = 'Got exit code %d. Expected %d (%s)\n' % (rc, expected, " ".join(args)) + self.assertEqual(rc, expected, msg + result + report) + + def assertShellOutputEquals(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False, expected=None): + '''Test a shell command matches a specific output''' + rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) + result = 'Got exit code %d. Looking for exact text "%s" (%s)\n' % (rc, text, " ".join(args)) + if not invert: + self.assertEqual(text, out, msg + result + report) + else: + self.assertNotEqual(text, out, msg + result + report) + if expected is not None: + result = 'Got exit code %d. Expected %d (%s)\n' % (rc, expected, " ".join(args)) + self.assertEqual(rc, expected, msg + result + report) + + def _word_find(self, report, content, invert=False): + '''Check for a specific string''' + if invert: + warning = 'Found "%s"\n' % content + self.assertTrue(content not in report, warning + report) + else: + warning = 'Could not find "%s"\n' % content + self.assertTrue(content in report, warning + report) + + def _test_sysctl_value(self, path, expected, msg=None, exists=True): + sysctl = '/proc/sys/%s' % (path) + self.assertEqual(exists, os.path.exists(sysctl), sysctl) + value = None + if exists: + with open(sysctl) as sysctl_fd: + value = int(sysctl_fd.read()) + report = "%s is not %d: %d" % (sysctl, expected, value) + if msg: + report += " (%s)" % (msg) + self.assertEqual(value, expected, report) + return value + + def set_sysctl_value(self, path, desired): + sysctl = '/proc/sys/%s' % (path) + self.assertTrue(os.path.exists(sysctl), "%s does not exist" % (sysctl)) + with open(sysctl, 'w') as sysctl_fh: + sysctl_fh.write(str(desired)) + self._test_sysctl_value(path, desired) + + def kernel_at_least(self, introduced): + return self.version_compare(self.kernel_version_ubuntu, + introduced) >= 0 + + def kernel_claims_cve_fixed(self, cve): + changelog = "/usr/share/doc/linux-image-%s/changelog.Debian.gz" % (self.kernel_version) + if os.path.exists(changelog): + for line in gzip.open(changelog): + if cve in line and "revert" not in line and "Revert" not in line: + return True + return False + + def install_builddeps(self, src_pkg): + rc, report = _run_apt_command([src_pkg], 'build-dep') + self.assertEqual(0, rc, 'Failed to install build-deps for %s\nOutput:\n%s' % (src_pkg, report)) + + def install_package(self, package): + rc, report = _run_apt_command([package], 'install') + self.assertEqual(0, rc, 'Failed to install package %s\nOutput:\n%s' % (package, report)) + + def install_packages(self, pkg_list): + rc, report = _run_apt_command(pkg_list, 'install') + self.assertEqual(0, rc, 'Failed to install packages %s\nOutput:\n%s' % (','.join(pkg_list), report)) + + +class TestGroup: + '''Create a temporary test group and remove it again in the dtor.''' + + def __init__(self, group=None, lower=False): + '''Create a new group''' + + self.group = None + if group: + if group_exists(group): + raise ValueError('group name already exists') + else: + while(True): + group = random_string(7, lower=lower) + if not group_exists(group): + break + + assert subprocess.call(['groupadd', group]) == 0 + self.group = group + g = grp.getgrnam(self.group) + self.gid = g[2] + + def __del__(self): + '''Remove the created group.''' + + if self.group: + rc, report = cmd(['groupdel', self.group]) + assert rc == 0 + + +class TestUser: + '''Create a temporary test user and remove it again in the dtor.''' + + def __init__(self, login=None, home=True, group=None, uidmin=None, lower=False, shell=None): + '''Create a new user account with a random password. + + By default, the login name is random, too, but can be explicitly + specified with 'login'. By default, a home directory is created, this + can be suppressed with 'home=False'.''' + + self.login = None + + if os.geteuid() != 0: + raise ValueError("You must be root to run this test") + + if login: + if login_exists(login): + raise ValueError('login name already exists') + else: + while(True): + login = 't' + random_string(7, lower=lower) + if not login_exists(login): + break + + self.salt = random_string(2) + self.password = random_string(8, lower=lower) + self.crypted = crypt.crypt(self.password, self.salt) + + creation = ['useradd', '-p', self.crypted] + if home: + creation += ['-m'] + if group: + creation += ['-G', group] + if uidmin: + creation += ['-K', 'UID_MIN=%d' % uidmin] + if shell: + creation += ['-s', shell] + creation += [login] + assert subprocess.call(creation) == 0 + # Set GECOS + assert subprocess.call(['usermod', '-c', 'Buddy %s' % (login), login]) == 0 + + self.login = login + p = pwd.getpwnam(self.login) + self.uid = p[2] + self.gid = p[3] + self.gecos = p[4] + self.home = p[5] + self.shell = p[6] + + def __del__(self): + '''Remove the created user account.''' + + if self.login: + # sanity check the login name so we don't accidentally wipe too much + if len(self.login) > 3 and '/' not in self.login: + subprocess.call(['rm', '-rf', '/home/' + self.login, '/var/mail/' + self.login]) + rc, report = cmd(['userdel', '-f', self.login]) + assert rc == 0 + + def add_to_group(self, group): + '''Add user to the specified group name''' + rc, report = cmd(['usermod', '-G', group, self.login]) + if rc != 0: + print(report) + assert rc == 0 + + +class AddUser: + '''Create a temporary test user and remove it again in the dtor.''' + + def __init__(self, login=None, home=True, encrypt_home=False, group=None, lower=False, shell=None): + '''Create a new user account with a random password. + + By default, the login name is random, too, but can be explicitly + specified with 'login'. By default, a home directory is created, this + can be suppressed with 'home=False'. + + This class differs from the TestUser class in that the adduser/deluser + tools are used rather than the useradd/user/del tools. The adduser + program is the only commandline program that can be used to add a new + user with an encrypted home directory. It is possible that the AddUser + class may replace the TestUser class in the future.''' + + self.login = None + + if os.geteuid() != 0: + raise ValueError("You must be root to run this test") + + if login: + if login_exists(login): + raise ValueError('login name already exists') + else: + while(True): + login = 't' + random_string(7, lower=True) + if not login_exists(login): + break + + self.password = random_string(8, lower=lower) + + creation = ['adduser', '--quiet'] + + if not home: + creation += ['--no-create-home'] + elif encrypt_home: + creation += ['--encrypt-home'] + + if shell: + creation += ['--shell', shell] + + creation += ['--gecos', 'Buddy %s' % (login), login] + + child = pexpect.spawn(creation.pop(0), creation, timeout=5) + assert child.expect('Enter new UNIX password:') == 0 + child.sendline(self.password) + assert child.expect('Retype new UNIX password:') == 0 + child.sendline(self.password) + + child.wait() + child.close() + assert child.exitstatus == 0 + assert child.signalstatus is None + + self.login = login + + if group: + assert self.add_to_group(group) == 0 + + p = pwd.getpwnam(self.login) + self.uid = p[2] + self.gid = p[3] + self.gecos = p[4] + self.home = p[5] + self.shell = p[6] + + def __del__(self): + '''Remove the created user account.''' + + if self.login: + # sanity check the login name so we don't accidentally wipe too much + rc, report = cmd(['deluser', '--remove-home', self.login]) + assert rc == 0 + + def add_to_group(self, group): + '''Add user to the specified group name''' + rc, report = cmd(['adduser', self.login, group]) + if rc != 0: + print(report) + assert rc == 0 + + +# Timeout handler using alarm() from John P. Speno's Pythonic Avocado +class TimeoutFunctionException(Exception): + """Exception to raise on a timeout""" + pass + + +class TimeoutFunction: + def __init__(self, function, timeout): + self.timeout = timeout + self.function = function + + def handle_timeout(self, signum, frame): + raise TimeoutFunctionException() + + def __call__(self, *args, **kwargs): + old = signal.signal(signal.SIGALRM, self.handle_timeout) + signal.alarm(self.timeout) + try: + result = self.function(*args, **kwargs) + finally: + signal.signal(signal.SIGALRM, old) + signal.alarm(0) + return result + + +def main(): + print("hi") + unittest.main() diff --git a/bin/tpm-sysfs-resource b/bin/tpm-sysfs-resource new file mode 100755 index 0000000..34f81b5 --- /dev/null +++ b/bin/tpm-sysfs-resource @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> + + +"""Collect information about all sysfs attributes related to TPM.""" + +import os + +import guacamole + + +class tpm_sysfs_resource(guacamole.Command): + + """ + Collect information about all sysfs attributes related to TPM. + + This program traverses all the TPM device nodes found in /sys/class/tpm/. + Each present device is subsequently inspected by reading all readable files + in /sys/class/tpm/*/device/* and presenting the data present there as + subsequent RFC822 records. There is one record per TPM chip. In order to + differentiate each chip, each record contains the field x-sysfs-device-name + that stores the full sysfs directory name of the chip. + + @EPILOG@ + + Unreadable files (typically due to permissions) are silently skipped. + """ + + def invoked(self, ctx): + # This is found on 4.2 kernels + sysfs_root_tpm = '/sys/class/tpm/' + # This is found on 3.19 kernels + sysfs_root_misc = '/sys/class/misc/' + if os.path.isdir(sysfs_root_tpm): + sysfs_root = sysfs_root_tpm + elif os.path.isdir(sysfs_root_misc): + sysfs_root = sysfs_root_misc + else: + return + for tpm_id in sorted(os.listdir(sysfs_root)): + if sysfs_root == sysfs_root_misc and not tpm_id.startswith('tpm'): + continue + print("x-sysfs-device-name: {}".format(tpm_id)) + tpm_dirname = os.path.join(sysfs_root, tpm_id, 'device') + for tpm_attr in sorted(os.listdir(tpm_dirname)): + tpm_filename = os.path.join(tpm_dirname, tpm_attr) + if not os.path.isfile(tpm_filename): + continue + if not os.access(tpm_filename, os.R_OK): + continue + with open(tpm_filename, 'rt', encoding='utf-8') as stream: + tpm_data = stream.read() + tpm_data = tpm_data.rstrip() + if '\n' in tpm_data: + print("{}:".format(tpm_attr)) + for tpm_data_chunk in tpm_data.splitlines(): + print(" {}".format(tpm_data_chunk)) + else: + print("{}: {}".format(tpm_attr, tpm_data)) + print() + + +if __name__ == "__main__": + tpm_sysfs_resource().main() diff --git a/bin/wifi_ap_wizard.py b/bin/wifi_ap_wizard.py new file mode 100755 index 0000000..5506d01 --- /dev/null +++ b/bin/wifi_ap_wizard.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Copyright 2017 Canonical Ltd. +# Written by: +# Maciej Kisielewski <maciej.kisielewski@canonical.com> + +"""This program talks to wifi-ap.setup-wizard and sets up predefined AP""" + +from checkbox_support.interactive_cmd import InteractiveCommand +import select +import subprocess +import sys + + +class InteractiveCommandWithShell(InteractiveCommand): + # this exists only to everride how subprocess.Popen is called (the addition + # of shell=True. If InteractiveCommand gets this as default, or an option + # this class won't be needed + def start(self): + self._proc = subprocess.Popen( + self._args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, shell=True) + self._is_running = True + self._poller = select.poll() + self._poller.register(self._proc.stdout, select.POLLIN) + + +def main(): + if len(sys.argv) < 3: + raise SystemExit( + 'Usage: wifi_ap_wizard.py WLAN_INTERFACE SHARED_INTERFACE') + wlan_iface = sys.argv[1] + shared_iface = sys.argv[2] + + with InteractiveCommandWithShell('wifi-ap.setup-wizard') as wizard: + steps = [ + ('Which SSID you want to use for the access point', + 'Ubuntu_Wizard'), + ('Do you want to protect your network with a WPA2 password', 'y'), + ('Please enter the WPA2 passphrase', 'Test1234'), + ('Insert the Access Point IP address', '192.168.42.1'), + ('How many host do you want your DHCP pool to hold to', '100'), + ('Do you want to enable connection sharing?', 'y'), + ('Which network interface you want to use for connection sharing?', + shared_iface), + ('Do you want to enable the AP now?', 'y'), + ] + iface_choice_prompt = "Which wireless interface" + wizard.wait_for_output() + first_prompt = wizard.read_all() + + if iface_choice_prompt in first_prompt: + wizard.writeline(wlan_iface) + else: + # the prompt is already consumed, so let's write a response + wizard.writeline(steps[0][1]) + # and proceed with next steps + steps = steps[1:] + + for prompt, response in steps: + if wizard.wait_until_matched(prompt, 1) is None: + raise SystemExit('Did not get prompted ("{}")'.format(prompt)) + wizard.writeline(response) + +if __name__ == '__main__': + main() diff --git a/bin/wifi_client_test b/bin/wifi_client_test new file mode 100755 index 0000000..6868847 --- /dev/null +++ b/bin/wifi_client_test @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Authors: Gavin Lin <gavin.lin@canonical.com> + +""" +This script will run wireless network test automatically. + +To see how to use, please run "./wifi_client_test --help" +""" +import argparse +import os +import subprocess +import sys +import time + + +# Configuration file path +CFG_PATH = '/etc/network/interfaces.d/plainbox_wifi_client_test' + +# Use ping to check connection? (yes/no) +PING_TEST = 'no' + +# Use DHCP to get ip address? (yes/no) (This may be required for ping test) +DHCP_IP = 'no' + +# Read arguments +parser = argparse.ArgumentParser( + description=('This script will test wireless network in client mode.')) +parser.add_argument( + '-i', '--interface', type=str, help=( + 'The interface which will be tested, default is wlan0'), + default='wlan0') +parser.add_argument( + '-s', '--ssid', type=str, help=( + 'SSID of target network, this is required argument'), + required=True) +parser.add_argument( + '-k', '--psk', type=str, help=( + 'Pre-shared key of target network, this is optional argument,' + ' only for PSK protected network')) +if PING_TEST == 'yes': + parser.add_argument( + '-p', '--ping', type=str, help=( + 'Server in target network which respond to ping,' + ' default is ubuntu.com'), + default='ubuntu.com') +args = parser.parse_args() + +# Create wireless network configuration file +wifi_cfg_file = open(CFG_PATH, 'w', encoding='utf-8') +# DHCP or static ip +if DHCP_IP == 'yes': + wifi_cfg_file.write('iface ' + args.interface + ' inet dhcp\n') +else: + wifi_cfg_file.write('iface ' + args.interface + ' inet static\n') + wifi_cfg_file.write(' address 192.168.1.1\n') +# SSID +wifi_cfg_file.write(' wpa-ssid ' + '\"' + args.ssid + '\"\n') +# Opened network or PSK protected network +if not args.psk: + wifi_cfg_file.write(' wpa-key-mgmt NONE\n') +else: + wifi_cfg_file.write(' wpa-psk ' + '\"' + args.psk + '\"\n') +wifi_cfg_file.close() + +# Bring up the interface +subprocess.call(['ip', 'link', 'set', args.interface, 'up']) +time.sleep(15) +subprocess.call(['ifdown', args.interface]) +time.sleep(15) +subprocess.call(['ifup', args.interface]) +time.sleep(15) + +# Check connection +if PING_TEST == 'yes': + test_result = subprocess.call([ + 'ping', '-c', '5', '-I', args.interface, args.ping]) + if not test_result: + print('Connection test passed') + exit_code = 0 + else: + print('Connection test failed') + exit_code = 1 +else: + test_result = subprocess.check_output([ + 'iw', args.interface, 'link']).decode() + if test_result.find('SSID') != -1: + print('Connection test passed') + exit_code = 0 + else: + print('Connection test failed') + exit_code = 1 + +# Bring down the interface and remove configuration file after test +subprocess.call(['ifdown', args.interface]) +time.sleep(15) +os.remove(CFG_PATH) +sys.exit(exit_code) diff --git a/bin/wifi_master_mode b/bin/wifi_master_mode new file mode 100755 index 0000000..5953ea3 --- /dev/null +++ b/bin/wifi_master_mode @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Jonathan Cave <jonathan.cave@canonical.com> +# Po-Hsu Lin <po-hsu.lin@canonical.com> + +import logging +import os +import sys +import subprocess +import tempfile + +from guacamole import Command + + +class WifiMasterMode(Command): + + """Make system to act as an 802.11 Wi-Fi Access Point.""" + + def register_arguments(self, parser): + parser.add_argument('--protocol', default='g', + choices=['a', 'b', 'g', 'ad'], + help="802.11 protocol, possible value: a/b/g/ad") + parser.add_argument('--auto', action='store_true', + help="Run in the automated mode") + + def invoked(self, ctx): + data_dir = "" + try: + data_dir = os.environ['PLAINBOX_PROVIDER_DATA'] + except KeyError: + logging.error("PLAINBOX_PROVIDER_DATA variable not set") + return 1 + logging.info("Provider data dir: {}".format(data_dir)) + + wifi_dev = "wlan0" + try: + wifi_dev = os.environ['WIFI_AP_DEV'] + except KeyError: + logging.info("WIFI_AP_DEV variable not set, defaulting to wlan0") + logging.info("Wi-Fi adapter: {}".format(wifi_dev)) + + conf_in = os.path.join(data_dir, 'hostapd.conf.in') + if not os.path.isfile(conf_in): + logging.error("Couldn't find {}".format(conf_in)) + return 1 + + with tempfile.NamedTemporaryFile(mode='w+t') as conf_file_out: + with open(conf_in, "r") as conf_file_in: + data_in = conf_file_in.read() + data_out = data_in.replace("$PROTOCOL", ctx.args.protocol) + data_out = data_out.replace("$WIFI-DEV-NAME", wifi_dev) + conf_file_out.write(data_out) + conf_file_out.flush() + + if ctx.args.auto: + child = subprocess.Popen(['hostapd', '-d', conf_file_out.name], + stdout=subprocess.PIPE, + universal_newlines=True) + log = '' + while child.poll() is None: + output = child.stdout.readline() + log += output + if 'AP-ENABLED' in output: + logging.info(output) + logging.info("AP successfully established.") + child.terminate() + if child.poll() is not 0: + output = child.stdout.read() + logging.error(log + output) + logging.error('AP failed to start') + return child.poll() + else: + # Print and flush this or it will get buffered during test + print("Hit any key to start the Access Point, a second key " + + "press will stop the Access Point:") + sys.stdout.flush() + input() + + try: + child = subprocess.Popen(['hostapd', conf_file_out.name]) + + # kill the process on input + input() + finally: + child.terminate() + +if __name__ == "__main__": + WifiMasterMode().main() diff --git a/bin/wwan_tests b/bin/wwan_tests new file mode 100755 index 0000000..17389e5 --- /dev/null +++ b/bin/wwan_tests @@ -0,0 +1,387 @@ +#!/usr/bin/env python3 +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Jonathan Cave <jonathan.cave@canonical.com> +# Po-Hsu Lin <po-hsu.lin@canonical.com> + +import logging +import os +import subprocess +import sys +import time + +import dbus +from guacamole import Command + + +TEST_IP = "8.8.8.8" +GSM_CON_ID = "GSMCONN" + + +DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties' +DBUS_OBJECTMANAGER = 'org.freedesktop.DBus.ObjectManager' + +DBUS_MM1_SERVICE = 'org.freedesktop.ModemManager1' +DBUS_MM1_PATH = '/org/freedesktop/ModemManager1' +DBUS_MM1_IF = 'org.freedesktop.ModemManager1' +DBUS_MM1_IF_MODEM = 'org.freedesktop.ModemManager1.Modem' +DBUS_MM1_IF_MODEM_SIMPLE = 'org.freedesktop.ModemManager1.Modem.Simple' +DBUS_MM1_IF_MODEM_3GPP = 'org.freedesktop.ModemManager1.Modem.Modem3gpp' +DBUS_MM1_IF_MODEM_CDMA = 'org.freedesktop.ModemManager1.Modem.ModemCdma' +DBUS_MM1_IF_SIM = 'org.freedesktop.ModemManager1.Sim' + +MMModemCapability = { + 'MM_MODEM_CAPABILITY_NONE': 0, + 'MM_MODEM_CAPABILITY_POTS': 1 << 0, + 'MM_MODEM_CAPABILITY_CDMA_EVDO': 1 << 1, + 'MM_MODEM_CAPABILITY_GSM_UMTS': 1 << 2, + 'MM_MODEM_CAPABILITY_LTE': 1 << 3, + 'MM_MODEM_CAPABILITY_LTE_ADVANCED': 1 << 4, + 'MM_MODEM_CAPABILITY_IRIDIUM': 1 << 5, + 'MM_MODEM_CAPABILITY_ANY': 0xFFFFFFFF} + + +class MMDbus(): + def __init__(self): + self._bus = dbus.SystemBus() + self._modems = [] + try: + manager_proxy = self._bus.get_object(DBUS_MM1_SERVICE, + DBUS_MM1_PATH) + om = dbus.Interface(manager_proxy, DBUS_OBJECTMANAGER) + self._modems = om.GetManagedObjects() + except dbus.exceptions.DBusException as excp: + if (excp.get_dbus_name() == + "org.freedesktop.DBus.Error.ServiceUnknown"): + logging.error(excp.get_dbus_message()) + logging.error("Note: wwan_tests requires ModemManager >=1.0") + else: + logging.error(excp.get_dbus_message()) + return + + def _modem_by_id(self, mm_id): + for m in self._modems: + if mm_id == (int(os.path.basename(m))): + return m + + def _modem_props_iface(self, mm_id): + m = self._modem_by_id(mm_id) + if m is not None: + proxy = self._bus.get_object(DBUS_MM1_SERVICE, m) + return dbus.Interface(proxy, dbus_interface=DBUS_PROPERTIES) + + def get_modem_ids(self): + modem_ids = [] + for m in self._modems: + modem_ids.append((int(os.path.basename(m)))) + return modem_ids + + def equipment_id_to_mm_id(self, equipment_id): + for mm_id in self.get_modem_ids(): + if equipment_id == self.get_equipment_id(mm_id): + return mm_id + + def get_rat_support(self, mm_id): + pi = self._modem_props_iface(mm_id) + return pi.Get(DBUS_MM1_IF_MODEM, 'CurrentCapabilities') + + def get_equipment_id(self, mm_id): + pi = self._modem_props_iface(mm_id) + return pi.Get(DBUS_MM1_IF_MODEM, "EquipmentIdentifier") + + def get_manufacturer(self, mm_id): + pi = self._modem_props_iface(mm_id) + return pi.Get(DBUS_MM1_IF_MODEM, 'Manufacturer') + + def get_model_name(self, mm_id): + pi = self._modem_props_iface(mm_id) + return pi.Get(DBUS_MM1_IF_MODEM, 'Model') + + def sim_present(self, mm_id): + pi = self._modem_props_iface(mm_id) + if pi.Get(DBUS_MM1_IF_MODEM, 'Sim') != '/': + return True + return False + + def _get_sim_pi(self, mm_id): + pi = self._modem_props_iface(mm_id) + sim_path = pi.Get(DBUS_MM1_IF_MODEM, 'Sim') + if sim_path != '/': + sim_proxy = self._bus.get_object(DBUS_MM1_SERVICE, sim_path) + return dbus.Interface(sim_proxy, dbus_interface=DBUS_PROPERTIES) + + def get_sim_operatorname(self, mm_id): + sim_pi = self._get_sim_pi(mm_id) + if sim_pi is None: + return 'No card' + return sim_pi.Get(DBUS_MM1_IF_SIM, 'OperatorName') + + def get_sim_operatoridentifier(self, mm_id): + sim_pi = self._get_sim_pi(mm_id) + if sim_pi is None: + return 'No card' + return sim_pi.Get(DBUS_MM1_IF_SIM, 'OperatorIdentifier') + + def get_sim_imsi(self, mm_id): + sim_pi = self._get_sim_pi(mm_id) + if sim_pi is None: + return 'No card' + return sim_pi.Get(DBUS_MM1_IF_SIM, 'Imsi') + + def get_sim_simidentifier(self, mm_id): + sim_pi = self._get_sim_pi(mm_id) + if sim_pi is None: + return 'No card' + return sim_pi.Get(DBUS_MM1_IF_SIM, 'SimIdentifier') + + +def _value_from_table(item, item_id, key): + if item == 'modem': + flag = '-m' + if item == 'sim': + flag = '-i' + proc = subprocess.Popen(['mmcli', flag, str(item_id)], + stdout=subprocess.PIPE) + while True: + line = proc.stdout.readline().decode(sys.stdout.encoding) + if line == '': + break + if key in line: + chars = ' |\'\n\t' + for c in chars: + if c in line: + line = line.replace(c, '') + value = line.split(':', 1)[1] + return value + + +class MMCLI(): + def __init__(self): + self._modem_ids = [] + try: + proc = subprocess.Popen(['mmcli', '-L'], + stdout=subprocess.PIPE) + while True: + line = proc.stdout.readline().decode(sys.stdout.encoding) + if line == '': + break + if '/org/freedesktop/ModemManager1/Modem' in line: + path = line.strip().split()[0] + self._modem_ids.append(int(os.path.basename(path))) + except OSError: + logging.error("mmcli not found") + + def _get_sim_id(self, mm_id): + sim_value = _value_from_table('modem', mm_id, 'SIM') + if sim_value == 'none': + return None + else: + return int(os.path.basename(sim_value)) + + def get_modem_ids(self): + return self._modem_ids + + def equipment_id_to_mm_id(self, equipment_id): + for mm_id in self.get_modem_ids(): + if equipment_id == self.get_equipment_id(mm_id): + return mm_id + + def get_equipment_id(self, mm_id): + return _value_from_table('modem', mm_id, 'equipment id') + + def get_manufacturer(self, mm_id): + return _value_from_table('modem', mm_id, 'manufacturer') + + def get_model_name(self, mm_id): + return _value_from_table('modem', mm_id, 'model') + + def sim_present(self, mm_id): + if self._get_sim_id(mm_id) is None: + return False + return True + + def get_sim_operatorname(self, mm_id): + sim_id = self._get_sim_id(mm_id) + if sim_id is None: + return 'No card' + return _value_from_table('sim', sim_id, 'operator name') + + def get_sim_imsi(self, mm_id): + sim_id = self._get_sim_id(mm_id) + if sim_id is None: + return 'No card' + return _value_from_table('sim', sim_id, 'imsi') + + def get_sim_operatoridentifier(self, mm_id): + sim_id = self._get_sim_id(mm_id) + if sim_id is None: + return 'No card' + return _value_from_table('sim', sim_id, 'operator id') + + def get_sim_simidentifier(self, mm_id): + sim_id = self._get_sim_id(mm_id) + if sim_id is None: + return 'No card' + return _value_from_table('sim', sim_id, ' id') + + +def _create_3gpp_connection(wwan_if, apn): + subprocess.check_call(["nmcli", "c", "add", + "con-name", GSM_CON_ID, + "type", "gsm", + "ifname", wwan_if, + "apn", apn]) + + +def _wwan_radio_on(): + subprocess.check_call(["nmcli", "r", "wwan", "on"]) + + +def _wwan_radio_off(): + subprocess.check_call(["nmcli", "r", "wwan", "off"]) + + +def _destroy_3gpp_connection(): + subprocess.check_call(["nmcli", "c", + "delete", GSM_CON_ID]) + + +def _ping_test(if_name): + ret_code = 1 + route = subprocess.call(["ip", "route", "add", TEST_IP, "dev", if_name]) + if route == 0: + ret_code = subprocess.check_call(["ping", "-c", "4", + "-I", if_name, TEST_IP]) + subprocess.call(["ip", "route", "del", TEST_IP, "dev", if_name]) + return ret_code + + +class ThreeGppConnection(Command): + + def register_arguments(self, parser): + parser.add_argument('wwan_control_if', type=str, + help='The control interface for the device') + parser.add_argument('wwan_net_if', type=str, + help='The network interface used when connected') + parser.add_argument('apn', type=str, + help='The APN for data connection') + parser.add_argument('wwan_setup_time', type=int, default=30, + help='delay before ping test') + + def invoked(self, ctx): + ret_code = 1 + try: + _create_3gpp_connection(ctx.args.wwan_control_if, ctx.args.apn) + _wwan_radio_on() + time.sleep(ctx.args.wwan_setup_time) + ret_code = _ping_test(ctx.args.wwan_net_if) + except: + pass + _destroy_3gpp_connection() + _wwan_radio_off() + return ret_code + + +class CountModems(Command): + + def invoked(self, ctx): + if ctx.args.use_cli: + mm = MMCLI() + else: + mm = MMDbus() + print(len(mm.get_modem_ids())) + + +class Resources(Command): + + def invoked(self, ctx): + if ctx.args.use_cli: + mm = MMCLI() + else: + mm = MMDbus() + for m in mm.get_modem_ids(): + print("mm_id: {}".format(m)) + # Removed ability to fetch supported RATs when adding CLI support + # to this script. It is somewhat messy to scrape this from mmcli + # output and it wasn't actually used in test definitions. + # + # cap_bits = mm.get_rat_support(m) + # if cap_bits != 0: + # if (cap_bits & MMModemCapability['MM_MODEM_CAPABILITY_POTS']): + # print("pots: supported") + # if (cap_bits & + # MMModemCapability['MM_MODEM_CAPABILITY_CDMA_EVDO']): + # print("cdma_evdo: supported") + # if (cap_bits & + # MMModemCapability['MM_MODEM_CAPABILITY_GSM_UMTS']): + # print("gsm_umts: supported") + # if (cap_bits & + # MMModemCapability['MM_MODEM_CAPABILITY_LTE']): + # print("lte: supported") + # if (cap_bits & + # MMModemCapability['MM_MODEM_CAPABILITY_LTE_ADVANCED']): + # print("lte_advanced: supported") + # if (cap_bits & + # MMModemCapability['MM_MODEM_CAPABILITY_IRIDIUM']): + # print("iridium: supported") + print("hw_id: {}".format(mm.get_equipment_id(m))) + print("manufacturer: {}".format(mm.get_manufacturer(m))) + print("model: {}".format(mm.get_model_name(m))) + print() + + +class SimPresent(Command): + + def register_arguments(self, parser): + parser.add_argument('hw_id', type=str, + help='The hardware ID of the modem whose attached' + 'SIM we want to query') + + def invoked(self, ctx): + if ctx.args.use_cli: + mm = MMCLI() + else: + mm = MMDbus() + mm_id = mm.equipment_id_to_mm_id(ctx.args.hw_id) + if not mm.sim_present(mm_id): + return 1 + + +class SimInfo(Command): + + def register_arguments(self, parser): + parser.add_argument('hw_id', type=str, + help='The hardware ID of the modem whose attached' + 'SIM we want to query') + + def invoked(self, ctx): + if ctx.args.use_cli: + mm = MMCLI() + else: + mm = MMDbus() + mm_id = mm.equipment_id_to_mm_id(ctx.args.hw_id) + print("Operator: {}".format(mm.get_sim_operatorname(mm_id))) + print("IMSI: {}".format(mm.get_sim_imsi(mm_id))) + print("MCC/MNC: {}".format(mm.get_sim_operatoridentifier(mm_id))) + print("ICCID: {}".format(mm.get_sim_simidentifier(mm_id))) + + +class WWANTests(Command): + + sub_commands = ( + ('count', CountModems), + ('resources', Resources), + ('3gpp-connection', ThreeGppConnection), + ('sim-present', SimPresent), + ('sim-info', SimInfo) + ) + + def register_arguments(self, parser): + parser.add_argument('--use-cli', action='store_true', + help="Use mmcli for all calls rather than dbus") + + +if __name__ == "__main__": + WWANTests().main() diff --git a/data/gpio-loopback.pi2.in b/data/gpio-loopback.pi2.in new file mode 120000 index 0000000..1421770 --- /dev/null +++ b/data/gpio-loopback.pi2.in @@ -0,0 +1 @@ +gpio-loopback.ubuntu-core-18-pi3.in \ No newline at end of file diff --git a/data/gpio-loopback.pi3.in b/data/gpio-loopback.pi3.in new file mode 120000 index 0000000..1421770 --- /dev/null +++ b/data/gpio-loopback.pi3.in @@ -0,0 +1 @@ +gpio-loopback.ubuntu-core-18-pi3.in \ No newline at end of file diff --git a/data/gpio-loopback.ubuntu-core-18-pi2.in b/data/gpio-loopback.ubuntu-core-18-pi2.in new file mode 120000 index 0000000..1421770 --- /dev/null +++ b/data/gpio-loopback.ubuntu-core-18-pi2.in @@ -0,0 +1 @@ +gpio-loopback.ubuntu-core-18-pi3.in \ No newline at end of file diff --git a/data/gpio-loopback.ubuntu-core-18-pi3.in b/data/gpio-loopback.ubuntu-core-18-pi3.in new file mode 100644 index 0000000..e21666f --- /dev/null +++ b/data/gpio-loopback.ubuntu-core-18-pi3.in @@ -0,0 +1,23 @@ +# +# The GPIO IDs as presented by sysfs correspond to the BCM IDs presented on +# https://pinout.xyz/ +# !!! The numbers below are therefore NOT the physical pin location on the header +# +# See the gadget snap slot definitions here: +# https://github.com/snapcore/pi3-gadget/blob/master/snapcraft.yaml +# +# These pairs have been chosen to be physically close and so easier to connect: +# +2,3 +4,17 +27,22 +10,9 +11,0 +5,6 +13,19 +18,23 +24,25 +8,7 +1,12 +16,20 +26,21 diff --git a/data/hostapd.conf.in b/data/hostapd.conf.in new file mode 100644 index 0000000..9a5bb78 --- /dev/null +++ b/data/hostapd.conf.in @@ -0,0 +1,21 @@ +interface=$WIFI-DEV-NAME +ctrl_interface=/var/run/hostapd +ctrl_interface_group=0 + +ssid=UbuntuCoreTest + +# a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g, ad = IEEE 802.11ad +hw_mode=$PROTOCOL +channel=1 + +# IEEE 802.11 specifies two authentication algorithms. hostapd can be +# configured to allow both of these or only one. Open system authentication +# should be used with IEEE 802.1X. +# Bit fields of allowed authentication algorithms: +# bit 0 = Open System Authentication +# bit 1 = Shared Key Authentication (requires WEP) +auth_algs=3 + +wpa=1 +wpa_passphrase=snappyubuntucore +wpa_key_mgmt=WPA-PSK WPA-EAP diff --git a/data/palm_rejection.qml b/data/palm_rejection.qml index 3f93f2a..429f8d8 100644 --- a/data/palm_rejection.qml +++ b/data/palm_rejection.qml @@ -1,7 +1,7 @@ /* * This file is part of Checkbox. * - * Copyright 2018 Canonical Ltd. + * Copyright 2018-2019 Canonical Ltd. * Written by: * Maciej Kisielewski <maciej.kisielewski@canonical.com> * @@ -20,9 +20,8 @@ import QtQuick 2.5 import QtQuick.Layouts 1.2 import QtQuick.Controls 1.4 -import Plainbox 1.0 -QmlJob { +Item { property var steps: [ ['images/palm-rejection-1.jpg', 2000], ['images/palm-rejection-2.jpg', 500], @@ -61,21 +60,17 @@ QmlJob { text: 'Pass' Layout.fillHeight: true Layout.fillWidth: true - onClicked: testDone({'outcome': 'pass'}) - } - Button { - text: 'Skip' - Layout.fillHeight: true - Layout.fillWidth: true - onClicked: testDone({'outcome': 'skip'}) + onClicked: { + console.log('PASS') + Qt.quit() + } } Button { text: 'Fail' Layout.fillHeight: true Layout.fillWidth: true - onClicked: testDone({'outcome': 'fail'}) + onClicked: Qt.quit() } - } } Timer { diff --git a/src/EXECUTABLES b/src/EXECUTABLES index 1f505d9..943bfdf 100644 --- a/src/EXECUTABLES +++ b/src/EXECUTABLES @@ -1,2 +1,3 @@ +alsa_test clocktest threaded_memtest diff --git a/src/Makefile b/src/Makefile index 917e6f1..1978732 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,13 +1,15 @@ .PHONY: -all: clocktest threaded_memtest +all: alsa_test clocktest threaded_memtest .PHONY: clean clean: - rm -f clocktest threaded_memtest + rm -f alsa_test clocktest threaded_memtest threaded_memtest: CFLAGS += -pthread threaded_memtest: CFLAGS += -Wno-unused-but-set-variable clocktest: CFLAGS += -D_POSIX_C_SOURCE=199309L -D_BSD_SOURCE clocktest: LDLIBS += -lrt +alsa_test: CXXFLAGS += -std=c++11 +alsa_test: LDLIBS += -lasound -pthread CFLAGS += -Wall diff --git a/src/alsa_test.cpp b/src/alsa_test.cpp new file mode 100644 index 0000000..fc9f1f4 --- /dev/null +++ b/src/alsa_test.cpp @@ -0,0 +1,594 @@ +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <cstring> +#include <iostream> +#include <istream> +#include <iterator> +#include <limits> +#include <map> +#include <string> +#include <sstream> +#include <thread> +#include <vector> +#include <alsa/asoundlib.h> +#include <complex> +#include <valarray> + +typedef std::valarray<std::complex<float>> CArray; +void fft(CArray& x) +{ + // almost the same implementation as one on rosetta code + const size_t N = x.size(); + if (N <= 1) return; + CArray even = x[std::slice(0, N/2, 2)]; + CArray odd = x[std::slice(1, N/2, 2)]; + fft(even); + fft(odd); + for (size_t k = 0; k < N/2; ++k) { + auto t = std::polar(1.0f, -2 * float(M_PI) * k / N) * odd[k]; + x[k] = even[k] + t; + x[k+N/2] = even[k] - t; + } +} + +struct Logger { + enum class Level {normal, info, debug}; + void set_level(Level new_lvl) { + level = new_lvl; + } + std::ostream& info() { + if (this->level >= Level::info) { + return std::cout; + } else { + return this->nullStream; + } + } + std::ostream& normal() { + return std::cout; + } + Logger() : level(Level::normal) {} + Logger(const Logger& l) {} + +private: + Level level; + struct NullStream : std::ostream { + template<typename T> + NullStream& operator<<(T const&) { + return *this; + } + }; + NullStream nullStream; +}; + +Logger logger = Logger(); + +std::vector<std::pair<std::string, std::string>> all_formats = { + {"float_44100", "Float32 encoded, 44100Hz sampling"}, + {"float_48000", "Float32 encoded, 48000Hz sampling"}, + {"int16_44100", "Signed Int16 encoded, 44100Hz sampling"}, + {"int16_48000", "Signed Int16 encoded, 48000Hz sampling"}, + {"uint16_44100", "Unsigned Int16 encoded, 44100 sampling"}, + {"uint16_48000", "Unsigned Int16 encoded, 48000 sampling"} +}; + +namespace Alsa{ + +using std::string; + +struct AlsaError: std::runtime_error { + explicit AlsaError(const string& what_arg) : runtime_error(what_arg) {} +}; + +template<class storage_type> +struct Pcm { + enum class Mode {playback, capture}; + + Pcm() : Pcm{"default", Mode::playback} {} + Pcm(string device_name, Mode mode = Mode::playback) { + snd_pcm_stream_t stream_mode; + switch(mode) { + case Mode::playback: + stream_mode = SND_PCM_STREAM_PLAYBACK; + break; + case Mode::capture: + stream_mode = SND_PCM_STREAM_CAPTURE; + break; + } + + int res = snd_pcm_open(&this->pcm_handle, device_name.c_str(), + stream_mode, 0 /* blocking */); + if (res < 0) { + auto ec = std::error_code(-res, std::system_category()); + auto msg = string("Failed to open device: ") + string(device_name) + + string(". ") + string(snd_strerror(res)); + throw AlsaError(msg); + } + logger.info() << "PCM opened. Name: " << device_name << " PCM handle: " + << pcm_handle << " PCM mode: " + << (int(mode) ? "capture" : "playback") << std::endl; + } + ~Pcm() { + switch (mode) { + case Mode::playback: + logger.info() << "Draining PCM " << pcm_handle << std::endl; + snd_pcm_drain(this->pcm_handle); + break; + case Mode::capture: + logger.info() << "Dropping PCM " << pcm_handle << std::endl; + snd_pcm_drop(this->pcm_handle); + break; + } + logger.info() << "Closing PCM " << pcm_handle << std::endl; + snd_pcm_close(this->pcm_handle); + } + void drain() { + snd_pcm_drain(this->pcm_handle); + } + void set_params(const unsigned desired_rate) { + snd_pcm_hw_params_t *params = nullptr; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(this->pcm_handle, params); + if (snd_pcm_hw_params_set_access(this->pcm_handle, params, + SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { + throw AlsaError("Failed to set access mode"); + } + if (snd_pcm_hw_params_set_channels(this->pcm_handle, params, 2) < 0) { + throw AlsaError("Failed to set the number of channels"); + } + if (auto res = snd_pcm_hw_params_set_format(this->pcm_handle, params, + get_alsa_format()) < 0) { + throw AlsaError(string("Failed to set format") + string( + snd_strerror(res))); + } + this->rate = desired_rate; + // pick will determine how alsa picks value, + // 0: exact value, -1: closest smaller value, +1: closest bigger value + int pick = 0; + if (snd_pcm_hw_params_set_rate_near(this->pcm_handle, params, + &this->rate, &pick) < 0) { + throw AlsaError("Failed to set rate"); + } + if (snd_pcm_hw_params(this->pcm_handle, params) < 0) { + throw AlsaError("Failed to write params to ALSA"); + } + logger.info() << "got rate: " << rate << std::endl; + snd_pcm_uframes_t frames; + int dir; + auto res = snd_pcm_hw_params_get_period_size(params, &frames, &dir); + this->period = frames; + unsigned period_time; + snd_pcm_hw_params_get_period_time(params, &period_time, NULL); + logger.info() << "period_time: " << period_time << std::endl; + logger.info() << "state: " << + snd_pcm_state_name(snd_pcm_state(this->pcm_handle)) << std::endl; + unsigned channs; + snd_pcm_hw_params_get_channels_max(params, &channs); + logger.info() << "no. of channels: " << channs << std::endl; + } + void sine(const float freq, const float duration, const float amplitude) const { + auto *buff = new storage_type[this->period * 2]; + void *ugly_ptr = static_cast<void*>(buff); + unsigned t = 0; + while (t < float(this->rate) * duration) { + for (int i=0; i < this->period * 2; i+=2) { + auto sample = sin(2 * M_PI *((t + i/2) / (this->rate / freq))); + // we need to convert the sample to the target range, -1.0f should + // match the min_val and +1.0f should match the max_val + auto target_range = float(this->max_val()) - float(this->min_val()); + sample = target_range * ((sample + 1.0f)/2.0f) + float(min_val()); + // saturate/trim + if (sample > float(max_val())) + sample = max_val(); + else if (sample < float(min_val())) + sample = min_val(); + // set volume + sample *= amplitude; + buff[i] = sample; + buff[i+1] = buff[i]; // the other channel + } + auto res = snd_pcm_writei(this->pcm_handle, ugly_ptr, this->period); + if (res == -EPIPE) { + logger.info() << "Buffer underrun" << std::endl; + snd_pcm_prepare(this->pcm_handle); + } + t += this->period; + } + logger.info() << "state: " << + snd_pcm_state_name(snd_pcm_state(this->pcm_handle)) << std::endl; + snd_pcm_start(this->pcm_handle); + delete[] buff; + } + void record(storage_type *buff, int buff_size /*in samples*/) { + auto *local_buff = new storage_type[this->period * 2]; + int res; + snd_pcm_start(this->pcm_handle); + logger.info() << "state: " << + snd_pcm_state_name(snd_pcm_state(this->pcm_handle)) << std::endl; + + while(buff_size > 0) { + if (buff_size >= this->period * 2) { + void *ugly_ptr = static_cast<void*>(buff); + res = snd_pcm_readi(this->pcm_handle, ugly_ptr, this->period); + buff_size -= this->period * 2; + buff += this->period *2; + } else { + void *ugly_ptr = static_cast<void*>(local_buff); + res = snd_pcm_readi(this->pcm_handle, ugly_ptr, this->period); + std::memcpy(buff, local_buff, buff_size * sizeof(storage_type)); + buff_size = 0; + } + } + delete[] local_buff; + } + void play(storage_type *buff, int buff_size) { + snd_pcm_prepare(this->pcm_handle); + while (buff_size > 0) { + void *ugly_ptr = static_cast<void*>(buff); + auto res = snd_pcm_writei(this->pcm_handle, ugly_ptr, this->period); + buff_size -= this->period *2; + buff += this->period * 2; + } + logger.info() << "state: " << + snd_pcm_state_name(snd_pcm_state(this->pcm_handle)) << std::endl; + } + + +private: + snd_pcm_format_t get_alsa_format(); + bool is_little_endian() { + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return true; + #else + return false; + #endif + } + storage_type min_val() const { return std::numeric_limits<storage_type>::min();} + storage_type max_val() const { return std::numeric_limits<storage_type>::max();} + + snd_pcm_t *pcm_handle; + unsigned rate; + snd_pcm_uframes_t period; + Mode mode; +}; + +template<> +float Alsa::Pcm<float>::min_val() const { return -1.0f; } + +template<> +float Alsa::Pcm<float>::max_val() const { return 1.0f; } + +template<> +snd_pcm_format_t Alsa::Pcm<float>::get_alsa_format() { + return is_little_endian() ? SND_PCM_FORMAT_FLOAT_LE : SND_PCM_FORMAT_FLOAT_BE; +} +template<> +snd_pcm_format_t Alsa::Pcm<double>::get_alsa_format() { + return is_little_endian() ? SND_PCM_FORMAT_FLOAT64_LE : SND_PCM_FORMAT_FLOAT64_BE; +} +template<> +snd_pcm_format_t Alsa::Pcm<int16_t>::get_alsa_format() { + return is_little_endian() ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_S16_BE; +} +template<> +snd_pcm_format_t Alsa::Pcm<uint16_t>::get_alsa_format() { + return is_little_endian() ? SND_PCM_FORMAT_U16_LE : SND_PCM_FORMAT_U16_BE; +} +template<> +snd_pcm_format_t Alsa::Pcm<int8_t>::get_alsa_format() { + return SND_PCM_FORMAT_S8; +} +template<> +snd_pcm_format_t Alsa::Pcm<uint8_t>::get_alsa_format() { + return SND_PCM_FORMAT_U8; +} + +struct Mixer { + Mixer(string card_name, string mixer_name) { + int res; + res = snd_mixer_open(&mixer_handle, 0); + if (res < 0) throw AlsaError("Failed to open an empty Mixer"); + res = snd_mixer_attach(mixer_handle, card_name.c_str()); + if (res < 0) throw AlsaError("Failed to attach HCTL to a Mixer"); + res = snd_mixer_selem_register(mixer_handle, NULL, NULL); + if (res < 0) throw AlsaError("Failed to register a Mixer"); + res = snd_mixer_load(mixer_handle); + if (res < 0) throw AlsaError("Failed to load a Mixer"); + snd_mixer_selem_id_alloca(&sid); + snd_mixer_selem_id_set_index(sid, 0); + snd_mixer_selem_id_set_name(sid, mixer_name.c_str()); + elem = snd_mixer_find_selem(mixer_handle, sid); + if (!elem) throw AlsaError(mixer_name + " mixer not found."); + } + ~Mixer() { + snd_mixer_close(mixer_handle); + } + void set_all_playback_volume(float volume) { + long min, max; + snd_mixer_selem_get_playback_volume_range(elem, &min, &max); + int new_vol = int(float(max) * volume); + snd_mixer_selem_set_playback_switch_all(elem, 1); + snd_mixer_selem_set_playback_volume_all(elem, new_vol); + } + void set_all_capture_volume(float volume) { + long min, max; + snd_mixer_selem_get_capture_volume_range(elem, &min, &max); + int new_vol = int(float(max) * volume); + snd_mixer_selem_set_capture_switch_all(elem, 1); + snd_mixer_selem_set_capture_volume_all(elem, new_vol); + } +private: + snd_mixer_t *mixer_handle; + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t* elem; +}; + +std::vector<std::string> get_devices(std::string io) { + std::vector<std::string> result; + void **out; + int err = snd_device_name_hint(-1 /* all cards */, "pcm", &out); + if (err) { + logger.normal() << "Couldn't get the device hints" << std::endl; + return result; + } + while (*out) { + const char *name = snd_device_name_get_hint(*out, "NAME"); + const char *desc = snd_device_name_get_hint(*out, "DESC"); + const char *ioid = snd_device_name_get_hint(*out, "IOID"); + if (ioid == nullptr) ioid = "Both"; + logger.info() << "Got a device hint. Name: " << name + << " Description: " << desc + << " IOID: " << ioid << std::endl; + std::string direction{ioid}; + if (direction == io) { + result.push_back(std::string{name}); + } + out++; + } + return result; +} +}; //namespace Alsa + +template<class storage_type> +int playback_test(float duration, int sampling_rate, const char* capture_pcm, const char* playback_pcm) { + auto player = Alsa::Pcm<storage_type>(); + player.set_params(sampling_rate); + player.sine(440, duration, 0.5f); + return 0; +} + +template<class storage_type> +float dominant_freq(storage_type *buff, int buffsize, int rate) { + CArray data(buffsize); + for (int i=0; i < buffsize; i++) { + data[i] = std::complex<float>(buff[i], 0); + } + fft(data); + auto freqs = std::vector<float>(buffsize/2); // drop mirrored freqs + for (int i=0; i< buffsize / 2; i++){ + freqs[i] = std::abs(data[i]); + } + auto it = std::max_element(freqs.begin(), freqs.end()); + if (it != freqs.end()) { + return float(std::distance(freqs.begin(), it)) / (float(buffsize) / rate); + } else { + return 0.0f; + } +} +template<class storage_type> +int loopback_test(float duration, int sampling_rate, const char* capture_pcm, const char* playback_pcm) { + const float test_freq = 440.0f; + int buffsize = static_cast<int>(ceil(float(sampling_rate * 2) * duration)); + std::vector<storage_type> buff(buffsize); + for (int attempt = 0; attempt < 3; ++attempt) { + for (int i=0; i<buffsize; i++) buff[i] = storage_type(0); + auto recorder = Alsa::Pcm<storage_type> (capture_pcm, Alsa::Pcm<storage_type>::Mode::capture); + recorder.set_params(sampling_rate); + std::thread rec_thread([&recorder, &buff, &buffsize]() mutable{ + recorder.record(&buff[0], buffsize); + }); + try { + auto player = Alsa::Pcm<storage_type>(playback_pcm); + player.set_params(sampling_rate); + player.sine(test_freq, duration, 0.5f); + player.drain(); + rec_thread.join(); + } + catch (Alsa::AlsaError& exc) { + rec_thread.join(); + return 1; + } + float dominant = dominant_freq<storage_type>(&buff[0], buffsize, sampling_rate * 2); + if (dominant > 0.0f) { + //buff contains stereo samples, so the sampling rate can be considered 88200 + logger.normal() << "Dominant frequency: " << dominant << std::endl; + // inverse-proportional to duration - the longer it runs, + // the more accurate the fft gets + float epsilon = 5 / duration + 1; + float deviation = abs(test_freq - dominant); + logger.normal() << "Deviation: " << deviation << std::endl; + if (deviation <= epsilon) + return 0; + } + } + return 1; +} +template<class storage_type> +int fallback_loopback(float duration, int sampling_rate, const char* _1, const char* _2) { + auto playback = Alsa::get_devices("Output"); + auto record = Alsa::get_devices("Input"); + auto both = Alsa::get_devices("Both"); + std::copy(both.begin(), both.end(), std::back_inserter(playback)); + std::copy(both.begin(), both.end(), std::back_inserter(record)); + for (auto player = playback.cbegin(); player != playback.cend(); ++player) { + if (*player == std::string{"surround40:CARD=PCH,DEV=0"}) { + continue; + } + for (auto recorder = record.cbegin(); recorder != record.cend(); ++recorder) { + logger.normal() << "Trying combination " << *player << " -> " << *recorder << std::endl; + try { + int error = loopback_test<storage_type>( + duration, sampling_rate, recorder->c_str(), player->c_str()); + if (!error) { + return 0; + } + } + catch(Alsa::AlsaError& exc) { + logger.normal() << "Alsa problem: " << exc.what() << std::endl; + } + } + } + return 1; +} +int list_formats(){ + const char* env_var = std::getenv("ALSA_TEST_FORMATS"); + std::vector<std::string> picked_formats; + + if (env_var) { + std::stringstream ss(env_var); + std::istream_iterator<std::string> ss_iter(ss); + std::istream_iterator<std::string> end; + picked_formats = std::vector<std::string>(ss_iter, end); + } else { + // nothing specified in the envvar so let's just copy keys from all_formats + for (auto it: all_formats) { + picked_formats.push_back(it.first); + } + } + + for (auto format: all_formats) { + if (find(picked_formats.begin(), picked_formats.end(), format.first) == picked_formats.end()) { + // format not picked + continue; + } + std::cout << "format: " << format.first << std::endl; + std::cout << "description: " << format.second << std::endl; + std::cout << std::endl; + } + return 0; +} + +int list_devices() { + auto playback = Alsa::get_devices("Output"); + auto record = Alsa::get_devices("Input"); + auto both = Alsa::get_devices("Both"); + std::copy(both.begin(), both.end(), std::back_inserter(playback)); + std::copy(both.begin(), both.end(), std::back_inserter(record)); + std::cout << "Playback devices: " << std::endl; + for (auto i = playback.cbegin(); i != playback.cend(); ++i) { + std::cout << *i << std::endl; + } + std::cout << "\n\nRecording devices: " << std::endl; + for (auto i = record.cbegin(); i != record.cend(); ++i) { + std::cout << *i << std::endl; + } + return 0; +} + +void set_volumes(const std::string playback_pcm, const std::string capture_pcm) { + try { + auto playback_mixer = Alsa::Mixer(playback_pcm, "Master"); + auto capture_mixer = Alsa::Mixer(capture_pcm, "Capture"); + playback_mixer.set_all_playback_volume(0.75f); + capture_mixer.set_all_capture_volume(0.75f); + } catch(Alsa::AlsaError err) { + logger.normal() << "Failed to change volume: " << err.what() << std::endl; + // not being able to change the volume is not critical to the test + // and for some devices "Master" and "Caputre" mixers may not exist + // so let's just print warning if that's the case + } +} + +int main(int argc, char *argv[]) { + std::vector<std::string> args{}; + for (int i=0; i < argc; ++i) { + args.push_back(std::string(argv[i])); + } + if (std::find(args.begin(), args.end(), std::string("-v")) != args.end()) { + logger.set_level(Logger::Level::info); + } + auto format = std::string("int16_48000"); + auto format_it = std::find(args.begin(), args.end(), std::string("--format")); + if (format_it != args.end()) { // not doing && because of sequence points + if (++format_it != args.end()) { + format = std::string(*format_it); + auto it = find_if( + all_formats.begin(), all_formats.end(), + [format](std::pair<std::string, std::string> p) {return p.first == format;}); + if (it == all_formats.end()) { + std::cerr << "Unknown format: " << format << std::endl; + return 1; + } + } + } + auto sample_format = std::string(format.begin(), find(format.begin(), format.end(), '_')); + auto sampling_rate = atoi( + std::string(find(format.begin(), format.end(), '_') + 1, format.end()).c_str()); + logger.info() << "Using format: " << sample_format << + " and sampling rate: " << sampling_rate << std::endl; + std::map<std::string, int(*)(float, int, const char*, const char*)> scenarios; + if (sample_format == "float") { + scenarios["playback"] = playback_test<float>; + scenarios["loopback"] = loopback_test<float>; + scenarios["fallback"] = fallback_loopback<float>; + } + else if (sample_format == "int16") { + scenarios["playback"] = playback_test<int16_t>; + scenarios["loopback"] = loopback_test<int16_t>; + scenarios["fallback"] = fallback_loopback<int16_t>; + } + else if (sample_format == "uint16") { + scenarios["playback"] = playback_test<uint16_t>; + scenarios["loopback"] = loopback_test<uint16_t>; + scenarios["fallback"] = fallback_loopback<uint16_t>; + } + else { + assert(!"MISSING IF-ELSES FOR FORMATS"); + } + + if (args.size() < 2) { + std::cerr << "Required 'scenario' argument missing" << std::endl; + return 1; + } + float duration = 1.0f; + auto it = std::find(args.begin(), args.end(), std::string("-d")); + if (it != args.end()) { // not doing && because of sequence points + if (++it != args.end()) { + duration = std::stof(*it); + } + } + std::string capture_pcm{"default"}; + it = std::find(args.begin(), args.end(), std::string("--capture-pcm")); + if (it != args.end()) { + if (++it != args.end()) { + capture_pcm = *it; + } + } + std::string playback_pcm{"default"}; + it = std::find(args.begin(), args.end(), std::string("--playback-pcm")); + if (it != args.end()) { // not doing && because of sequence points + if (++it != args.end()) { + playback_pcm = *it; + } + } + set_volumes(playback_pcm, capture_pcm); + std::string scenario{args[1]}; + if (scenario == "playback") { + return scenarios["playback"](duration, sampling_rate, capture_pcm.c_str(), playback_pcm.c_str()); + } + else if (scenario == "loopback") { + int error = scenarios["loopback"](duration, sampling_rate, capture_pcm.c_str(), playback_pcm.c_str()); + if (!error) return 0; + return scenarios["fallback"](duration, sampling_rate, nullptr, nullptr); + } + else if (scenario == "list-formats") { + return list_formats(); + } + else if (scenario == "list-devices") { + return list_devices(); + } + if (scenarios.find(args[1]) == scenarios.end()) { + std::cerr << args[1] << " scenario not found!" << std::endl; + return 1; + } +} diff --git a/units/6lowpan/category.pxu b/units/6lowpan/category.pxu new file mode 100644 index 0000000..7502b2a --- /dev/null +++ b/units/6lowpan/category.pxu @@ -0,0 +1,3 @@ +unit: category +id: 6lowpan +_name: 6LoWPAN diff --git a/units/6lowpan/jobs.pxu b/units/6lowpan/jobs.pxu new file mode 100644 index 0000000..10fe86a --- /dev/null +++ b/units/6lowpan/jobs.pxu @@ -0,0 +1,11 @@ +plugin: shell +category_id: 6lowpan +id: 6lowpan/kconfig +estimated_duration: 1.2 +command: + for config in CONFIG_6LOWPAN CONFIG_IEEE802154 CONFIG_IEEE802154_6LOWPAN CONFIG_MAC802154; do + zcat /proc/config.gz | egrep "$config=(y|m)" || exit 1 + done +_summary: kernel config options for 6LoWPAN +_description: + Checks the kernel config options for 6LoWPAN / IEEE802.15.4 support diff --git a/units/6lowpan/test-plan.pxu b/units/6lowpan/test-plan.pxu new file mode 100644 index 0000000..a9b48cd --- /dev/null +++ b/units/6lowpan/test-plan.pxu @@ -0,0 +1,7 @@ +id: 6lowpan-automated +unit: test plan +_name: Automated 6LoWPAN tests +_description: Automated 6LoWPAN tests for Snappy Ubuntu Core devices +include: + 6lowpan/kconfig + diff --git a/units/audio/jobs.pxu b/units/audio/jobs.pxu index a661833..0745e5c 100644 --- a/units/audio/jobs.pxu +++ b/units/audio/jobs.pxu @@ -617,3 +617,232 @@ command: audio_settings store --file=$PLAINBOX_SESSION_SHARE/audio_settings_after_suspend_30_cycles diff $PLAINBOX_SESSION_SHARE/audio_settings_before_suspend $PLAINBOX_SESSION_SHARE/audio_settings_after_suspend_30_cycles +id: audio/detect-playback-devices +_summary: Check that at least one audio playback device exits +plugin: shell +category_id: com.canonical.plainbox::audio +flags: also-after-suspend +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_audio_playback == 'True' +command: + COUNT=$(alsa_pcm_info | grep Playback | wc -l) + echo "Count: $COUNT" + if [ $COUNT -eq 0 ]; then + exit 1 + fi +estimated_duration: 1s + +id: audio/detect-capture-devices +_summary: Check that at least one audio capture device exists +plugin: shell +category_id: com.canonical.plainbox::audio +flags: also-after-suspend +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_audio_capture == 'True' +command: + COUNT=$(alsa_pcm_info | grep Capture | wc -l) + echo "Count: $COUNT" + if [ $COUNT -eq 0 ]; then + exit 1 + fi +esimated_duration: 1s + +id: audio/alsa-playback +_summary: Playback works +_purpose: + Check if sound is played through ALSA on the default output +_steps: + 1. Make sure speakers or headphones are connect to the device + 2. Commence the test +_verification: + Did you hear the sound? +plugin: user-interact-verify +depends: audio/detect-playback-devices +user: root +environ: ALSA_CONFIG_PATH ALSADEVICE +flags: also-after-suspend +command: alsa_test playback -d 5 +category_id: com.canonical.plainbox::audio +estimated_duration: 10 + +id: audio/alsa-loopback-automated +_summary: Captured sound matches played one (automated) +_purpose: + Check if sound that is 'hearable' by capture device +plugin: shell +depends: audio/detect-playback-devices audio/detect-capture-devices +user: root +environ: ALSA_CONFIG_PATH LD_LIBRARY_PATH ALSADEVICE +flags: also-after-suspend +command: alsa_test loopback -d 5 +category_id: com.canonical.plainbox::audio +estimated_duration: 5 + +id: audio/alsa-loopback +_summary: Captured sound matches played one +_purpose: + Check if sound that is 'hearable' by capture device +_steps: + 1. Connect line-out to line-in (plug the loop-back cable) + 2. Commence the test + 3. Observe command's output +_verification: + Is the reported frequency in the 438 - 442 range +plugin: user-interact-verify +depends: audio/detect-playback-devices audio/detect-capture-devices +user: root +environ: ALSA_CONFIG_PATH LD_LIBRARY_PATH ALSADEVICE +flags: also-after-suspend +command: alsa_test loopback -d 5 +category_id: com.canonical.plainbox::audio +estimated_duration: 30 + +id: audio/pa-record-internal-mic +_summary: Record a wav file and check it using pulseaudio - internal mic +_purpose: + Check if audio input work fine through pulseaudio on internal mic +_steps: + 1. Make sure no external mic is connected to the device + 2. Make sure there's at least one output device connected to the device + 3. Workaround to run pulseaudio correctly: + sudo mkdir -p /run/user/0/snap.pulseaudio/pulse + 4. Find out corrent source, sink id: + sudo pulseaudio.pactl list + 5. Set input/output profile: + sudo pulseaudio.pactl set-card-profile 0 output:analog-stereo+input:analog-stereo + 6. Unmute and set volume of input/output: + sudo pulseaudio.pactl set-sink-mute <Sink id #, e.g. 0> 0 + sudo pulseaudio.pactl set-sink-volume <Sink id #, e.g. 0> 80% + sudo pulseaudio.pactl set-source-mute <Source id #, e.g. 0> 0 + sudo pulseaudio.pactl set-source-volume <Source id #, e.g. 0> 80% + 7. Record for 5 seconds to a wav file: + sudo timeout 5 pulseaudio.parec -v /var/snap/pulseaudio/common/test.wav + 8. Play the recorded file + sudo pulseaudio.paplay -v /var/snap/pulseaudio/common/test.wav + 9. Remove the recorded file + sudo rm /var/snap/pulseaudio/common/test.wav +_verification: + Did you hear the recorded sound correctly? +plugin: manual +flags: also-after-suspend +requires: snap.name == 'pulseaudio' +category_id: com.canonical.plainbox::audio +estimated_duration: 1m + +id: audio/pa-record-external-mic +_summary: Record a wav file and check it using pulseaudio - external mic +_purpose: + Check if audio input work fine through pulseaudio on external mic +_steps: + 1. Make sure an external mic is connected to the device + 2. Make sure there's at least one output device connected to the device + 3. Workaround to run pulseaudio correctly: + sudo mkdir -p /run/user/0/snap.pulseaudio/pulse + 4. Find out corrent source, sink id: + sudo pulseaudio.pactl list + 5. Set input/output profile: + sudo pulseaudio.pactl set-card-profile 0 output:analog-stereo+input:analog-stereo + 6. Unmute and set volume of input/output: + sudo pulseaudio.pactl set-sink-mute <Sink id #, e.g. 0> 0 + sudo pulseaudio.pactl set-sink-volume <Sink id #, e.g. 0> 80% + sudo pulseaudio.pactl set-source-mute <Source id #, e.g. 0> 0 + sudo pulseaudio.pactl set-source-volume <Source id #, e.g. 0> 80% + 7. Record for 5 seconds to a wav file: + sudo timeout 5 pulseaudio.parec -v /var/snap/pulseaudio/common/test.wav + 8. Play the recorded file + sudo pulseaudio.paplay -v /var/snap/pulseaudio/common/test.wav + 9. Remove the recorded file + sudo rm /var/snap/pulseaudio/common/test.wav +_verification: + Did you hear the recorded sound correctly? +plugin: manual +flags: also-after-suspend +requires: snap.name == 'pulseaudio' +category_id: com.canonical.plainbox::audio +estimated_duration: 1m + +id: audio/pa-playback-headphone +_summary: Play sample wav file using pulseaudio - headphone +_purpose: + Check if sound is played through pulseaudio to headphone +_steps: + 1. Make sure a headphone is connected to the device + 2. Workaround to run pulseaudio correctly: + sudo mkdir -p /run/user/0/snap.pulseaudio/pulse + 3. Find out corrent source, sink id: + sudo pulseaudio.pactl list + 4. Set input/output profile: + sudo pulseaudio.pactl set-card-profile 0 output:analog-stereo+input:analog-stereo + 5. Unmute and set volume of output: + sudo pulseaudio.pactl set-sink-mute <Sink id #, e.g. 0> 0 + sudo pulseaudio.pactl set-sink-volume <Sink id #, e.g. 0> 80% + 6. Put a test wav file in system as /var/snap/pulseaudio/common/test.wav + 7. Play the test wav file + sudo pulseaudio.paplay -v /var/snap/pulseaudio/common/test.wav + 8. Remove the test file + sudo rm /var/snap/pulseaudio/common/test.wav +_verification: + Did you hear the sound correctly? +plugin: manual +flags: also-after-suspend +requires: snap.name == 'pulseaudio' +category_id: com.canonical.plainbox::audio +estimated_duration: 1m + +id: audio/pa-playback-lineout +_summary: Play sample wav file using pulseaudio - lineout +_purpose: + Check if sound is played through pulseaudio to lineout +_steps: + 1. Make sure a output device is connected to the lineout port on device + 2. Workaround to run pulseaudio correctly: + sudo mkdir -p /run/user/0/snap.pulseaudio/pulse + 3. Find out corrent source, sink id: + sudo pulseaudio.pactl list + 4. Set input/output profile: + sudo pulseaudio.pactl set-card-profile 0 output:analog-stereo+input:analog-stereo + 5. Unmute and set volume of output: + sudo pulseaudio.pactl set-sink-mute <Sink id #, e.g. 0> 0 + sudo pulseaudio.pactl set-sink-volume <Sink id #, e.g. 0> 80% + 6. Put a test wav file in system as /var/snap/pulseaudio/common/test.wav + 7. Play the test wav file + sudo pulseaudio.paplay -v /var/snap/pulseaudio/common/test.wav + 8. Remove the test file + sudo rm /var/snap/pulseaudio/common/test.wav +_verification: + Did you hear the sound correctly? +plugin: manual +flags: also-after-suspend +requires: snap.name == 'pulseaudio' +category_id: com.canonical.plainbox::audio +estimated_duration: 1m + +id: audio/pa-playback-hdmi +_summary: Play sample wav file using pulseaudio - hdmi +_purpose: + Check if sound is played through pulseaudio to HDMI output device +_steps: + 1. Make sure a HDMI output device is connected to the device + 2. Workaround to run pulseaudio correctly: + sudo mkdir -p /run/user/0/snap.pulseaudio/pulse + 3. Find out corrent source, sink id: + sudo pulseaudio.pactl list + 4. Set input/output profile: + sudo pulseaudio.pactl set-card-profile 0 output:hdmi-stereo+input:analog-stereo + 5. Unmute and set volume of output: + sudo pulseaudio.pactl set-sink-mute <Sink id #, e.g. 0> 0 + sudo pulseaudio.pactl set-sink-volume <Sink id #, e.g. 0> 80% + 6. Put a test wav file in system as /var/snap/pulseaudio/common/test.wav + 7. Play the test wav file + sudo pulseaudio.paplay -v /var/snap/pulseaudio/common/test.wav + 8. Remove the test file + sudo rm /var/snap/pulseaudio/common/test.wav +_verification: + Did you hear the sound correctly? +plugin: manual +flags: also-after-suspend +requires: snap.name == 'pulseaudio' +category_id: com.canonical.plainbox::audio +estimated_duration: 1m diff --git a/units/audio/manifest.pxu b/units/audio/manifest.pxu new file mode 100644 index 0000000..95417f6 --- /dev/null +++ b/units/audio/manifest.pxu @@ -0,0 +1,15 @@ +# Copyright 2017 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Sylvain Pineau <sylvain.pineau@canonical.com> + +unit: manifest entry +id: has_audio_playback +_name: Audio playback +value-type: bool + +unit: manifest entry +id: has_audio_capture +_name: Audio capture +value-type: bool diff --git a/units/audio/resource.pxu b/units/audio/resource.pxu new file mode 100644 index 0000000..c7fd3b7 --- /dev/null +++ b/units/audio/resource.pxu @@ -0,0 +1,9 @@ +unit: job +id: alsa_resource +category_id: com.canonical.plainbox::audio +plugin: resource +_summary: Gather device info about alsa sound devices +_description: Gather device info about alsa sound devices +command: alsa_pcm_info +estimated_duration: 1s +flags: preserve-locale diff --git a/units/audio/test-plan.pxu b/units/audio/test-plan.pxu index a147682..f583b91 100644 --- a/units/audio/test-plan.pxu +++ b/units/audio/test-plan.pxu @@ -73,3 +73,77 @@ include: suspend/microphone-plug-detection-after-suspend certification-status=blocker suspend/playback_headphones-after-suspend certification-status=blocker suspend/alsa_record_playback_external-after-suspend certification-status=blocker + +id: audio-full +unit: test plan +_name: Audio tests +_description: QA audio tests for Snappy Ubuntu Core devices +include: +nested_part: + audio-manual + audio-automated + +id: audio-manual +unit: test plan +_name: Manual audio tests +_description: Manual audio tests for Snappy Ubuntu Core devices +include: + audio/alsa-playback + audio/alsa-loopback + +id: audio-pa-manual +unit: test plan +_name: Manual audio tests using pulseaudio +_description: Manual audio tests using pulseaudio for Snappy Ubuntu Core devices +include: + audio/pa-record-internal-mic + audio/pa-record-external-mic + audio/pa-playback-headphone + audio/pa-playback-lineout + audio/pa-playback-hdmi + +id: audio-automated +unit: test plan +_name: Automated audio tests +_description: Automated audio tests for Snappy Ubuntu Core devices +include: + audio/detect-playback-devices + audio/detect-capture-devices + audio/alsa-loopback-automated + +id: after-suspend-audio-full +unit: test plan +_name: Audio tests (after suspend) +_description: QA audio tests for Snappy Ubuntu Core devices +include: +nested_part: + after-suspend-audio-manual + after-suspend-audio-automated + +id: after-suspend-audio-manual +unit: test plan +_name: Manual audio tests (after suspend) +_description: Manual audio tests for Snappy Ubuntu Core devices +include: + after-suspend-audio/alsa-playback + after-suspend-audio/alsa-loopback + +id: after-suspend-audio-pa-manual +unit: test plan +_name: Manual audio tests using pulseaudio (after suspend) +_description: Manual audio tests using pulseaudio for Snappy Ubuntu Core devices +include: + after-suspend-audio/pa-record-internal-mic + after-suspend-audio/pa-record-external-mic + after-suspend-audio/pa-playback-headphone + after-suspend-audio/pa-playback-lineout + after-suspend-audio/pa-playback-hdmi + +id: after-suspend-audio-automated +unit: test plan +_name: Automated audio tests +_description: Automated audio tests for Snappy Ubuntu Core devices +include: + after-suspend-audio/detect-playback-devices + after-suspend-audio/detect-capture-devices + after-suspend-audio/alsa-loopback-automated diff --git a/units/bluetooth/category.pxu b/units/bluetooth/category.pxu new file mode 100644 index 0000000..7f3c06c --- /dev/null +++ b/units/bluetooth/category.pxu @@ -0,0 +1,7 @@ +unit: category +id: bluetooth_bluez5_selftests +_name: Bluetooth - BlueZ Self Tests + +unit: category +id: after-suspend-bluetooth_bluez5_selftests +_name: Suspend - Bluetooth - BlueZ Self Tests diff --git a/units/bluetooth/jobs.pxu b/units/bluetooth/jobs.pxu index 6160e76..cee3467 100644 --- a/units/bluetooth/jobs.pxu +++ b/units/bluetooth/jobs.pxu @@ -261,3 +261,134 @@ user: root flags: also-after-suspend also-after-suspend-manual category_id: com.canonical.plainbox::bluetooth estimated_duration: 10 + +unit: template +template-resource: bluez-internal-rfcomm-tests +template-unit: job +id: bluetooth/bluez-internal-rfcomm-tests_{bluez-internal-rfcomm-test} +category_id: bluetooth_bluez5_selftests +_summary: BlueZ-{bluez-internal-rfcomm-test} +_description: + Runs a specific test from the rfcomm test suite +plugin: shell +user: root +flags: also-after-suspend +command: + rfcomm-tester -p "{bluez-internal-rfcomm-test}" +requires: device.category == 'BLUETOOTH' +estimated_duration: 2.0 + +unit: template +template-resource: bluez-internal-hci-tests +template-unit: job +id: bluetooth/bluez-internal-hci-tests_{bluez-internal-hci-test} +category_id: bluetooth_bluez5_selftests +_summary: BlueZ-{bluez-internal-hci-test} +_description: + Runs a specific test from the hci test suite +plugin: shell +user: root +flags: also-after-suspend +command: + hci-tester -p "{bluez-internal-hci-test}" +requires: device.category == 'BLUETOOTH' +estimated_duration: 2.0 + +unit: template +template-resource: bluez-internal-mgmt-tests +template-unit: job +id: bluetooth/bluez-internal-mgmt-tests_{bluez-internal-mgmt-test} +category_id: bluetooth_bluez5_selftests +_summary: BlueZ-{bluez-internal-mgmt-test} +_description: + Runs a specific test from the mgmt test suite +plugin: shell +user: root +flags: also-after-suspend +command: + mgmt-tester -p "{bluez-internal-mgmt-test}" +requires: device.category == 'BLUETOOTH' +estimated_duration: 2.0 + +unit: template +template-resource: bluez-internal-uc-tests +template-unit: job +id: bluetooth/bluez-internal-uc-tests_{bluez-internal-uc-test} +category_id: bluetooth_bluez5_selftests +_summary: BlueZ-{bluez-internal-uc-test} +_description: + Runs a specific test from the user channel test suite +plugin: shell +user: root +flags: also-after-suspend +command: + userchan-tester -p "{bluez-internal-uc-test}" +requires: device.category == 'BLUETOOTH' +estimated_duration: 2.0 + +unit: template +template-resource: bluez-internal-bnep-tests +template-unit: job +id: bluetooth/bluez-internal-bnep-tests_{bluez-internal-bnep-test} +category_id: bluetooth_bluez5_selftests +_summary: BlueZ-{bluez-internal-bnep-test} +_description: + Runs a specific test from the bnep test suite +plugin: shell +user: root +flags: also-after-suspend +command: + bnep-tester -p "{bluez-internal-bnep-test}" +requires: device.category == 'BLUETOOTH' +estimated_duration: 2.0 + +id: bluetooth/keyboard +_summary: Bluetooth keyboard works +_purpose: + Check if bluetooth keyboard works +_verification: + Did the keyboard work? +plugin: user-verify +user: root +flags: also-after-suspend +command: test_bt_keyboard +category_id: com.canonical.plainbox::bluetooth +estimated_duration: 1m + +id: bluetooth/keyboard-manual +_summary: Bluetooth keyboard manual test +_purpose: + Check bluetooth input device works +_steps: + 1. Run the following command to start bluetoothctl console: + sudo bluetoothctl -a + ***Following steps are run in bluetoothctl console*** + 2. Power on the device: + power on + 3. Register agent for keyboard: + agent KeyboardOnly + default-agent + 4. Put controller in pairable mode: + pairable on + 5. Scan nearby bluetooth device: + scan on + 6. Stop Scanning after bluetooth keyboard is found: + scan off + 7. Pair bluetooth + pair [MAC address of bluetooth keyboard] + 8. Enter PIN Code on bluetooth keyboard if needed + 9. Trust the bluetooth keyboard + trust [MAC address of bluetooth keyboard] + 10. Connect to bluetooth keyboard: + connect [MAC address of bluetooth keyboard] + 11. Quit bluetoothctl console + quit + 12. Use bluetooth keyboard to input + **for headless please check the input outside the bluetooth console by using: + $ sudo cat /dev/tty1 +_verification: + Confirm characters from Bluetooth input device are displayed correctly +plugin: manual +flags: also-after-suspend-manual +category_id: com.canonical.plainbox::bluetooth +estimated_duration: 5m diff --git a/units/bluetooth/resource.pxu b/units/bluetooth/resource.pxu new file mode 100644 index 0000000..24783e2 --- /dev/null +++ b/units/bluetooth/resource.pxu @@ -0,0 +1,84 @@ +unit: job +id: bluez-internal-rfcomm-tests +plugin: resource +_summary: Gather list of tests provided by bluez rfcomm test binary +_description: + Bluez includes some internal test that exercise the stack. This resource + collects a list of the provided rfcomm tests. +requires: device.category == 'BLUETOOTH' +command: + rfcomm-tester -l | while read i + do + echo "bluez-internal-rfcomm-test: $i" + echo + done +estimated_duration: 2s +flags: preserve-locale + +unit: job +id: bluez-internal-hci-tests +plugin: resource +_summary: Gather list of tests provided by bluez hci test binary +_description: + Bluez includes some internal test that exercise the stack. This resource + collects a list of the provided hci tests. +requires: device.category == 'BLUETOOTH' +command: + hci-tester -l | while read i + do + echo "bluez-internal-hci-test: $i" + echo + done +estimated_duration: 2s +flags: preserve-locale + +unit: job +id: bluez-internal-mgmt-tests +plugin: resource +_summary: Gather list of tests provided by bluez mgmt test binary +_description: + Bluez includes some internal test that exercise the stack. This resource + collects a list of the provided mgmt tests. +requires: device.category == 'BLUETOOTH' +command: + mgmt-tester -l | while read i + do + echo "bluez-internal-mgmt-test: $i" + echo + done +estimated_duration: 2s +flags: preserve-locale + +unit: job +id: bluez-internal-uc-tests +plugin: resource +_summary: Gather list of tests provided by bluez user channel test binary +_description: + Bluez includes some internal test that exercise the stack. This resource + collects a list of the provided user channel tests. +requires: device.category == 'BLUETOOTH' +command: + userchan-tester -l | while read i + do + echo "bluez-internal-uc-test: $i" + echo + done +estimated_duration: 2s +flags: preserve-locale + +unit: job +id: bluez-internal-bnep-tests +plugin: resource +_summary: Gather list of tests provided by bluez bnep test binary +_description: + Bluez includes some internal test that exercise the stack. This resource + collects a list of the provided bnep tests. +requires: device.category == 'BLUETOOTH' +command: + bnep-tester -l | while read i + do + echo "bluez-internal-bnep-test: $i" + echo + done +estimated_duration: 2s +flags: preserve-locale diff --git a/units/bluetooth/test-plan.pxu b/units/bluetooth/test-plan.pxu index 2b1819b..69497ae 100644 --- a/units/bluetooth/test-plan.pxu +++ b/units/bluetooth/test-plan.pxu @@ -34,3 +34,119 @@ include: bluetooth4/HOGP-mouse certification-status=blocker bluetooth4/HOGP-keyboard certification-status=blocker bluetooth/audio-a2dp certification-status=blocker + +id: bluetooth-full +unit: test plan +_name: Bluetooth tests +_description: QA tests for Bluetooth +estimated_duration: 6m +include: +nested_part: + bluetooth-manual + bluez-automated + +id: bluetooth-manual +unit: test plan +_name: Manual Bluetooth tests +_description: Manual QA tests for Bluetooth +estimated_duration: 5m +include: + bluetooth/keyboard-manual + +id: bluez-automated +unit: test plan +_name: Automated tests for bluez +_description: + Automated tests for bluez +estimated_duration: 1m +include: + bluetooth/detect + bluetooth/bluez-controller-detect + bluetooth/bluez-internal-rfcomm-tests_.* + bluetooth/bluez-internal-hci-tests_.* + # Blacklist all mngt tests since they randomly fail + # (even with upstream 5.45) + # Note: The mgmt tests are not that much critical as these just test + # the management interface. + # Next line is commented out until upstream fixes the issue + # bluetooth/bluez-internal-mgmt-tests_.* + bluetooth/bluez-internal-uc-tests_.* + bluetooth/bluez-internal-bnep-tests_.* + bluetooth4/beacon_eddystone_url_.* +bootstrap_include: + device + bluez-internal-rfcomm-tests + bluez-internal-hci-tests + # Blacklist all mngt tests since they randomly fail + # (even with upstream 5.45) + # Note: The mgmt tests are not that much critical as these just test + # the management interface. + # Next line is commented out until upstream fixes the issue + # bluez-internal-mgmt-tests + bluez-internal-uc-tests + bluez-internal-bnep-tests +exclude: + # Read Country Code is deprecated + "bluetooth/bluez-internal-hci-tests_Read Country Code" + # Blacklist the following three hci tests, they randomly fail + # (even with upstream 5.45) + "bluetooth/bluez-internal-hci-tests_LE Read Local PK" + "bluetooth/bluez-internal-hci-tests_Read Local Supported Codecs" + "bluetooth/bluez-internal-hci-tests_LE Generate DHKey" + +id: after-suspend-bluetooth-full +unit: test plan +_name: Bluetooth tests (after suspend) +_description: QA tests for Bluetooth +estimated_duration: 6m +include: +nested_part: + after-suspend-bluetooth-manual + after-suspend-bluez-automated + +id: after-suspend-bluetooth-manual +unit: test plan +_name: Manual Bluetooth tests +_description: Manual QA tests for Bluetooth +estimated_duration: 5m +include: + after-suspend-manual-bluetooth/keyboard-manual + +id: after-suspend-bluez-automated +unit: test plan +_name: Automated tests for bluez +_description: + Automated tests for bluez +estimated_duration: 1m +include: + after-suspend-bluetooth/bluez-internal-rfcomm-tests_.* + after-suspend-bluetooth/bluez-internal-hci-tests_.* + # Blacklist all mngt tests since they randomly fail + # (even with upstream 5.45) + # Note: The mgmt tests are not that much critical as these just test + # the management interface. + # Next line is commented out until upstream fixes the issue + # after-suspend-bluetooth/bluez-internal-mgmt-tests_.* + after-suspend-bluetooth/bluez-internal-uc-tests_.* + after-suspend-bluetooth/bluez-internal-bnep-tests_.* + after-suspend-bluetooth4/beacon_eddystone_url_.* +bootstrap_include: + device + bluez-internal-rfcomm-tests + bluez-internal-hci-tests + # Blacklist all mngt tests since they randomly fail + # (even with upstream 5.45) + # Note: The mgmt tests are not that much critical as these just test + # the management interface. + # Next line is commented out until upstream fixes the issue + # bluez-internal-mgmt-tests + bluez-internal-uc-tests + bluez-internal-bnep-tests +exclude: + # Read Country Code is deprecated + "after-suspend-bluetooth/bluez-internal-hci-tests_Read Country Code" + # Blacklist the following three hci tests, they randomly fail + # (even with upstream 5.45) + "after-suspend-bluetooth/bluez-internal-hci-tests_LE Read Local PK" + "after-suspend-bluetooth/bluez-internal-hci-tests_Read Local Supported Codecs" + "after-suspend-bluetooth/bluez-internal-hci-tests_LE Generate DHKey" diff --git a/units/cpu/test-plan.pxu b/units/cpu/test-plan.pxu index c20efe8..365531c 100644 --- a/units/cpu/test-plan.pxu +++ b/units/cpu/test-plan.pxu @@ -46,3 +46,33 @@ include: cpu/maxfreq_test-log-attach cpu/offlining_test certification-status=blocker cpu/topology certification-status=blocker + +id: cpu-full +unit: test plan +_name: CPU tests +_description: QA CPU tests for Snappy Ubuntu Core devices +include: +nested_part: + cpu-automated + +id: cpu-manual +unit: test plan +_name: Manual CPU tests +_description: Manual QA CPU tests for Snappy Ubuntu Core devices +include: + +id: cpu-automated +unit: test plan +_name: Automated CPU tests +_description: Automated CPU tests for Snappy Ubuntu Core devices +include: + cpu/scaling_test + cpu/scaling_test-log-attach + cpu/maxfreq_test + cpu/maxfreq_test-log-attach + cpu/clocktest + cpu/offlining_test + cpu/topology + cpu/arm_vfp_support_.* + cpu/cstates + cpu/cstates_results.log diff --git a/units/disk/encryption.pxu b/units/disk/encryption.pxu index b7d41c8..dea0637 100644 --- a/units/disk/encryption.pxu +++ b/units/disk/encryption.pxu @@ -18,4 +18,31 @@ command: {%- else %} fde_tests.py desktop {% endif -%} -estimated_duration: 2.0 \ No newline at end of file +estimated_duration: 2.0 + +id: disk/encryption/check-fde-tpm +_summary: Disk decryption after TPM change +_description: + Check that the data partition cannot be decrypted (and therefore the device + cannot boot) if PCR7 value is modified. +category_id: com.canonical.plainbox::disk +estimated_duration: 45m +plugin: manual +_purpose: + The device partition is encrypted using TPM master key. To unseal the master + key from TPM, PCR7 (Platform Configuration Register 7) needs to be identical + to the value it had when the master key was sealed into TPM. Every time the + device boots, it checks PCR7 to unseal TPM and retrieves master key from TPM + to decrypt its data partition. If TPM PCR7 is modified (e.g. by flashing the + BIOS), the device won't be able to get the master key and decrypt its data + partition. +_steps: + 1. Install the image and make sure it boots and you can log in. + 2. Turn the device off and upgrade/downgrade the BIOS + 3. Make sure the BIOS is set up properly (e.g. TPM enabled, UEFI boot mode) + 4. Start the device +_verification: + Mark this test as "Passed" if the device cannot boot anymore. + Note: You must flash the BIOS back to the latest version and re-install the + image afterwards. + diff --git a/units/disk/jobs.pxu b/units/disk/jobs.pxu index b2e4301..3e0f568 100644 --- a/units/disk/jobs.pxu +++ b/units/disk/jobs.pxu @@ -90,7 +90,15 @@ requires: package.name == 'uuid-runtime' or executable.name == 'uuidgen' _summary: Disk stress_ng test for {product_slug} _description: Disk stress_ng test for {product_slug} -command: disk_stress_ng {name} --base-time 240 --really-run +command: + if [ -n "$STRESS_NG_DISK_TIME" ] + then + echo "Found STRESS_NG_DISK_TIME env var, stress_ng disk running time is now: $STRESS_NG_DISK_TIME seconds" + disk_stress_ng {name} --base-time $STRESS_NG_DISK_TIME --really-run + else + echo "STRESS_NG_DISK_TIME env var is not found, stress_ng disk running time is default value" + disk_stress_ng {name} --base-time 240 --really-run + fi unit: template template-resource: device diff --git a/units/ethernet/jobs.pxu b/units/ethernet/jobs.pxu index 103a7b3..bd7f273 100644 --- a/units/ethernet/jobs.pxu +++ b/units/ethernet/jobs.pxu @@ -407,6 +407,24 @@ _description: unit: template template-resource: device template-filter: device.category == 'NETWORK' and device.interface != 'UNKNOWN' +template-unit: job +id: ethernet/iperf3_reverse_{interface} +plugin: shell +_summary: Iperf3 stress testing for {interface} (reverse) +category_id: com.canonical.plainbox::ethernet +estimated_duration: 240.0 +user: root +environ: + TEST_TARGET_IPERF + LD_LIBRARY_PATH +command: network test -i {interface} -t iperf --iperf3 --scan-timeout 3600 --fail-threshold 50 --cpu-load-fail-threshold 90 --runtime 90 --num_runs 4 --reverse +_description: + This test uses iperf3 to ensure network devices pass data at an acceptable + minimum percentage of advertized speed (Reverse). + +unit: template +template-resource: device +template-filter: device.category == 'NETWORK' and device.interface != 'UNKNOWN' template-engine: jinja2 template-unit: job id: ethernet/check-{{ interface }}-static-configuration diff --git a/units/gadget/category.pxu b/units/gadget/category.pxu new file mode 100644 index 0000000..29d3fef --- /dev/null +++ b/units/gadget/category.pxu @@ -0,0 +1,3 @@ +unit: category +id: gadget +_name: Gadget snap tests diff --git a/units/gpio/category.pxu b/units/gpio/category.pxu new file mode 100644 index 0000000..9433850 --- /dev/null +++ b/units/gpio/category.pxu @@ -0,0 +1,9 @@ +# Copyright 2015-2018 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Maciej Kisielewski <maciej.kisielewski@canonical.com> + +unit: category +id: gpio +_name: General Purpose I/O diff --git a/units/gpio/jobs.pxu b/units/gpio/jobs.pxu new file mode 100644 index 0000000..ec1e948 --- /dev/null +++ b/units/gpio/jobs.pxu @@ -0,0 +1,38 @@ +# This job currently has a template filter so that devices which do not yet +# have a pin definition file do not generate fails, when all devices have a +# definition this could be removed +unit: template +template-resource: com.canonical.certification::model_assertion +template-filter: model_assertion.model in ("pi2", "pi3", "ubuntu-core-18-pi2", "ubuntu-core-18-pi3") +template-unit: job +id: gpio/sysfs_loopback_pairs_{model} +_summary: Test GPIO lines exposed on headers can be controlled via sysfs +plugin: shell +user: root +category_id: gpio +command: + gpio_sysfs_loopback.py {model} +estimated_duration: 20.0 +flags: preserve-locale also-after-suspend + + +# Filtering this job by the same devices as above as uses the same pin +# definition file and uses the RPi python module +unit: template +template-resource: com.canonical.certification::model_assertion +template-filter: model_assertion.model in ("pi2", "pi3", "ubuntu-core-18-pi2", "ubuntu-core-18-pi3") +template-unit: job +id: gpio/gpiomem_loopback_pairs_{model} +_summary: Test GPIO lines exposed on headers can be controlled via /dev/gpiomem +plugin: shell +user: root +category_id: gpio +command: + gpio_gpiomem_loopback.py {model} +estimated_duration: 20.0 +flags: preserve-locale also-after-suspend +# If starting to test confinement then this connection will be requried: +#requires: +# {%- if __on_ubuntucore__ %} +# connections.slot == 'snapd:gpio-memory-control' and connections.plug == 'checkbox-plano:gpio-memory-control' +# {% endif -%} diff --git a/units/gpio/test-plan.pxu b/units/gpio/test-plan.pxu new file mode 100644 index 0000000..a32be08 --- /dev/null +++ b/units/gpio/test-plan.pxu @@ -0,0 +1,40 @@ +id: gpio-full +unit: test plan +_name: GPIO tests +_description: QA GPIO tests for Ubuntu Core devices +include: +nested_part: + gpio-manual + gpio-automated + +id: gpio-manual +unit: test plan +_name: Manual GPIO tests +_description: Manual GPIO tests for Ubuntu Core devices +include: + +id: after-suspend-gpio-manual +unit: test plan +_name: Manual GPIO tests (after suspend) +_description: Manual GPIO tests for Ubuntu Core devices (after suspend) +include: + +id: gpio-automated +unit: test plan +_name: Automated GPIO tests +_description: Automated GPIO tests for Ubuntu Core devices +bootstrap_include: + model_assertion +include: + gpio/sysfs_loopback_pairs_.* + gpio/gpiomem_loopback_pairs_.* + +id: after-suspend-gpio-automated +unit: test plan +_name: Automated GPIO tests (after suspend) +_description: Automated GPIO tests for Ubuntu Core devices (after suspend) +bootstrap_include: + model_assertion +include: + after-suspend-gpio/sysfs_loopback_pairs_.* + after-suspend-gpio/gpiomem_loopback_pairs_.* diff --git a/units/graphics/test-plan.pxu b/units/graphics/test-plan.pxu index e0f2bb8..fd52dc7 100644 --- a/units/graphics/test-plan.pxu +++ b/units/graphics/test-plan.pxu @@ -31,14 +31,15 @@ _name: Graphics tests (integrated GPU) (Automated) _description: Graphics tests (integrated GPU) (Automated) include: - graphics/1_auto_switch_card_.* certification-status=blocker - graphics/xorg-version certification-status=blocker - graphics/xorg-failsafe certification-status=blocker - graphics/xorg-process certification-status=blocker - graphics/VESA_drivers_not_in_use certification-status=blocker - graphics/1_driver_version_.* certification-status=blocker - graphics/1_compiz_check_.* certification-status=blocker + graphics/1_auto_switch_card_.* certification-status=blocker + graphics/xorg-version certification-status=blocker + graphics/xorg-failsafe certification-status=blocker + graphics/xorg-process certification-status=blocker + graphics/VESA_drivers_not_in_use certification-status=blocker + graphics/1_driver_version_.* certification-status=blocker + graphics/1_compiz_check_.* certification-status=blocker graphics/1_minimum_resolution_.* + suspend/1_resolution_before_suspend_.*_auto certification-status=blocker bootstrap_include: graphics_card @@ -99,7 +100,6 @@ _name: After suspend tests (integrated GPU automated) _description: After suspend tests (integrated GPU automated) include: graphics/1_auto_switch_card_.* certification-status=blocker - suspend/1_resolution_before_suspend_.*_auto certification-status=blocker suspend/1_suspend_after_switch_to_card_.*_auto certification-status=blocker # The following after suspend jobs will automatically select the right suspend job # depending on the amount of graphic cards available on the SUT: diff --git a/units/i2c/category.pxu b/units/i2c/category.pxu new file mode 100644 index 0000000..b0020b1 --- /dev/null +++ b/units/i2c/category.pxu @@ -0,0 +1,9 @@ +# Copyright 2016 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Gavin Lin <gavin.lin@canonical.com> + +unit: category +id: i2c +_name: I2C (Inter-Integrated Circuit) diff --git a/units/i2c/jobs.pxu b/units/i2c/jobs.pxu new file mode 100644 index 0000000..db90509 --- /dev/null +++ b/units/i2c/jobs.pxu @@ -0,0 +1,45 @@ +# Copyright 2016 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Gavin Lin <gavin.lin@canonical.com> + +unit: job +id: i2c/i2c-bus-detect +_summary: Check number of detected I2C bus +_purpose: + Verify if number of detected I2C bus is as expected +_steps: + 1. This task is fully automatic, when expected i2c bus number($I2C_BUS_NUMBER) + is set, this job will verify if detected number of i2c bus is as expected. + 2. If expected i2c bus number is not set, this job will pass if system + detected there's at least one i2c bus. +command: + if [ -z ${I2C_BUS_NUMBER+x} ]; then + i2c_driver_test bus + else + i2c_driver_test bus -b $I2C_BUS_NUMBER + fi +user: root +plugin: shell +category_id: i2c +environ: I2C_BUS_NUMBER +estimated_duration: 20s +requires: manifest.has_i2c == 'True' +imports: from com.canonical.plainbox import manifest + +unit: job +id: i2c/i2c-device-detect +_summary: Check if any I2C device detected +_purpose: + Verify if there's any I2C device +_steps: + 1. This task is fully automatic, test will pass if there's at least one + i2c device detected on any I2C bus. +command: + i2c_driver_test device +user: root +plugin: shell +category_id: i2c +estimated_duration: 3m +depends: i2c/i2c-bus-detect diff --git a/units/i2c/manifest.pxu b/units/i2c/manifest.pxu new file mode 100644 index 0000000..fafbd15 --- /dev/null +++ b/units/i2c/manifest.pxu @@ -0,0 +1,10 @@ +# Copyright 2016 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Gavin Lin <gavin.lin@canonical.com> + +unit: manifest entry +id: has_i2c +_name: I2C +value-type: bool diff --git a/units/i2c/test-plan.pxu b/units/i2c/test-plan.pxu new file mode 100644 index 0000000..99e9aaf --- /dev/null +++ b/units/i2c/test-plan.pxu @@ -0,0 +1,15 @@ +id: i2c-full +unit: test plan +_name: I2c tests +_description: QA i2c tests for Snappy Ubuntu Core devices +include: +nested_part: + i2c-automated + +id: i2c-automated +unit: test plan +_name: Automated i2c tests +_description: Automated i2c tests for Snappy Ubuntu Core devices +include: + i2c/i2c-bus-detect + i2c/i2c-device-detect diff --git a/units/info/jobs.pxu b/units/info/jobs.pxu index 9ed5d33..e7ac3ea 100644 --- a/units/info/jobs.pxu +++ b/units/info/jobs.pxu @@ -459,3 +459,11 @@ category_id: com.canonical.plainbox::info estimated_duration: 0.2 _summary: attach network configuration command: network_configs + +id: parts_meta_info_attachment +plugin: attachment +category_id: com.canonical.plainbox::info +command: cat $SNAP/parts_meta_info $SNAP/checkbox-runtime/parts_meta_info || echo "no parts meta info available" +environ: SNAP +estimated_duration: 0.02 +_summary: Attaches an information about all parts that constituted this snap diff --git a/units/kernel-snap/category.pxu b/units/kernel-snap/category.pxu new file mode 100644 index 0000000..361df99 --- /dev/null +++ b/units/kernel-snap/category.pxu @@ -0,0 +1,3 @@ +unit: category +id: kernel-snap +_name: Kernel snap tests diff --git a/units/kernel-snap/jobs.pxu b/units/kernel-snap/jobs.pxu new file mode 100644 index 0000000..5b573fd --- /dev/null +++ b/units/kernel-snap/jobs.pxu @@ -0,0 +1,17 @@ + +id: kernel-snap/booted-kernel-matches-current +category_id: kernel-snap +_summary: The booted kernel image matches image in current kernel snap +_description: + On some Ubuntu Core deviecs it is necessary for the kernel image to be + extracted from the kernel snap and placed in the boot partition (notably + device using full disk encryption). This checks the images are in sync. +plugin: shell +user: root +estimated_duration: 2.0 +command: + booted_kernel_tests.py +imports: + from com.canonical.certification import ubuntu_core_features +requires: + ubuntu_core_features.force_kernel_extraction == 'True' diff --git a/units/kernel-snap/test-plan.pxu b/units/kernel-snap/test-plan.pxu new file mode 100644 index 0000000..7bd4dc8 --- /dev/null +++ b/units/kernel-snap/test-plan.pxu @@ -0,0 +1,22 @@ + +id: kernel-snap-full +unit: test plan +_name: Kernel Snap tests +_description: Kernel Snap test for Ubuntu Core devices +include: +nested_part: + kernel-snap-manual + kernel-snap-automated + +id: kernel-snap-automated +unit: test plan +_name: Automated Kernel Snap tests +_description: Automated Kernel Snap tests for Ubuntu Core devices +include: + kernel-snap/booted-kernel-matches-current + +id: kernel-snap-manual +unit: test plan +_name: Manual Kernel Snap tests +_description: Manual Kernel Snap tests for Ubuntu Core devices +include: diff --git a/units/location/category.pxu b/units/location/category.pxu new file mode 100644 index 0000000..7fe4424 --- /dev/null +++ b/units/location/category.pxu @@ -0,0 +1,3 @@ +unit: category +id: location +_name: Location Service diff --git a/units/location/jobs.pxu b/units/location/jobs.pxu new file mode 100644 index 0000000..2e854ab --- /dev/null +++ b/units/location/jobs.pxu @@ -0,0 +1,46 @@ +id: location/status +_summary: Queries the status of a service instance +command: locationd.status +category_id: location +requires: snap.name == 'locationd' +estimated_duration: 1 +flags: simple preserve-cwd also-after-suspend + +id: location/monitor +_summary: Connects to a location service instance, monitoring its activity. +command: timeout 15m bash -c 'grep -m 1 Update <( exec locationd.monitor ); kill $! 2> /dev/null' +category_id: location +requires: snap.name == 'locationd' +estimated_duration: 900 +flags: simple preserve-cwd also-after-suspend + +id: location/status-manual +_summary: Queries the status of a service instance +_purpose: + Queries the status of a service instance +_steps: + 1. Type the following command in a second terminal to run the location service status: + $ locationd.status +_verification: + Did the command succeed? +category_id: location +flags: also-after-suspend +requires: snap.name == 'locationd' +estimated_duration: 1 +plugin: manual + +id: location/monitor-manual +_summary: Connects to a location service instance, monitoring its activity. +_purpose: + Connects to a location service instance, monitoring its activity. +_steps: + 1. Type the following command in a second terminal to run the location service monitor: + $ timeout 15m bash -c 'grep -m 1 Update <( exec locationd.monitor ); kill $! 2> /dev/null' +_verification: + Did the command succeed (it can take up to 15m from a cold start)? +category_id: location +flags: also-after-suspend +requires: snap.name == 'locationd' +estimated_duration: 900 +plugin: manual + diff --git a/units/location/test-plan.pxu b/units/location/test-plan.pxu new file mode 100644 index 0000000..2b38435 --- /dev/null +++ b/units/location/test-plan.pxu @@ -0,0 +1,47 @@ +id: location-full +unit: test plan +_name: Location Service tests +_description: QA location service tests for Snappy Ubuntu Core devices +include: +nested_part: + location-automated + location-manual + +id: location-automated +unit: test plan +_name: Automated location service tests +_description: Automated location service tests for Snappy Ubuntu Core devices +include: + location/status + +id: location-manual +unit: test plan +_name: Manual location service tests +_description: Automated location service tests for Snappy Ubuntu Core devices +include: + location/status-manual + location/monitor-manual + +id: after-suspend-location-full +unit: test plan +_name: Location Service tests (after suspend) +_description: QA location service tests for Snappy Ubuntu Core devices +include: +nested_part: + after-suspend-location-automated + after-suspend-location-manual + +id: after-suspend-location-automated +unit: test plan +_name: Automated location service tests (after suspend) +_description: Automated location service tests for Snappy Ubuntu Core devices +include: + after-suspend-location/status + +id: after-suspend-location-manual +unit: test plan +_name: Manual location service tests (after suspend) +_description: Automated location service tests for Snappy Ubuntu Core devices +include: + after-suspend-location/status-manual + after-suspend-location/monitor-manual diff --git a/units/mediacard/jobs.pxu b/units/mediacard/jobs.pxu index 8ce16d6..f2b3a64 100644 --- a/units/mediacard/jobs.pxu +++ b/units/mediacard/jobs.pxu @@ -783,3 +783,21 @@ _description: VERIFICATION: Did the results of the test match the expected performance of the inserted device? +unit: template +template-resource: removable_partition +template-filter: removable_partition.bus == 'mediacard' +template-unit: job +plugin: shell +category_id: com.canonical.plainbox::mediacard +id: mediacard/storage-preinserted-{symlink_uuid} +user: root +estimated_duration: 45.0 +flags: also-after-suspend reset-locale +command: USB_RWTEST_PARTITIONS={symlink_uuid} checkbox-support-usb_read_write +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_card_reader == 'True' +_summary: Automated test of SD Card reading & writing ({symlink_uuid}) +_description: + This is a fully automated version of mediacard/sd-automated and assumes that the + system under test has a memory card device plugged in prior to checkbox execution. diff --git a/units/mediacard/test-plan.pxu b/units/mediacard/test-plan.pxu index 2ef8a01..8b267a6 100644 --- a/units/mediacard/test-plan.pxu +++ b/units/mediacard/test-plan.pxu @@ -52,3 +52,34 @@ include: suspend/sdhc-insert-after-suspend certification-status=blocker suspend/sdhc-storage-after-suspend certification-status=blocker suspend/sdhc-remove-after-suspend certification-status=blocker + +id: mediacard-full +unit: test plan +_name: Mediacard tests +_description: QA mediacard tests for Snappy Ubuntu Core devices +include: +nested_part: + mediacard-manual +# nested_part doesn't include automated test plan as its tests overlap with the +# ones from the manual one + +id: mediacard-manual +unit: test plan +_name: Manual mediacard tests +_description: Manual mediacard tests for Snappy Ubuntu Core devices +include: + mediacard/.*-insert + mediacard/.*-storage + mediacard/.*-remove + mediacard/.*-performance-manual + +id: mediacard-automated +unit: test plan +_name: Automated mediacard tests +_description: + Automated mediacard tests for Snappy Ubuntu Core devices + (not requiring udisks2) +include: + mediacard/storage-preinserted-.* +bootstrap_include: + removable_partition diff --git a/units/memory/test-plan.pxu b/units/memory/test-plan.pxu new file mode 100644 index 0000000..123d437 --- /dev/null +++ b/units/memory/test-plan.pxu @@ -0,0 +1,20 @@ +id: memory-full +unit: test plan +_name: Memory tests +_description: QA memory tests for Ubuntu Core devices +include: +nested_part: + memory-automated + +id: memory-manual +unit: test plan +_name: Manual memory tests +_description: Manual memory tests for Ubuntu Core devices +include: + +id: memory-automated +unit: test plan +_name: Automated memory tests +_description: Automated memory tests for Ubuntu Core devices +include: + memory/info diff --git a/units/miscellanea/jobs.pxu b/units/miscellanea/jobs.pxu index ca726a6..1641790 100644 --- a/units/miscellanea/jobs.pxu +++ b/units/miscellanea/jobs.pxu @@ -421,7 +421,7 @@ estimated_duration: 20.0 id: miscellanea/sosreport user: root requires: executable.name == 'sosreport' -command: sosreport --batch --tmp-dir $PLAINBOX_SESSION_SHARE +command: sosreport --batch -n lxd --tmp-dir $PLAINBOX_SESSION_SHARE _summary: Generate baseline sosreport _description: Generates a baseline sosreport of logs and system data diff --git a/units/monitor/jobs.pxu b/units/monitor/jobs.pxu index 54a86c9..df1bb95 100644 --- a/units/monitor/jobs.pxu +++ b/units/monitor/jobs.pxu @@ -311,3 +311,109 @@ _steps: _verification: Was the desktop displayed correctly with on the screen connected using a "USB Type-C to VGA" adapter in every mode? + +id: monitor/dvi +_summary: Monitor works (DVI) +_purpose: + Check output to display through DVI port +_steps: + 1. Connect display to DVI port + 2. Check the display +_verification: + Output to display works +plugin: manual +category_id: com.canonical.plainbox::monitor +estimated_duration: 300 +flags: also-after-suspend + +id: monitor/hdmi +_summary: Monitor works (HDMI) +_purpose: + Check output to display through HDMI port +_steps: + 1. Connect display to HDMI port + 2. Check the display +_verification: + Output to display works +plugin: manual +category_id: com.canonical.plainbox::monitor +estimated_duration: 300 +flags: also-after-suspend + +id: monitor/displayport +_summary: Monitor works (DisplayPort) +_purpose: + Check output to display through DisplayPort +_steps: + 1. Connect display to DisplayPort + 2. Check the display +_verification: + Output to display works +plugin: manual +category_id: com.canonical.plainbox::monitor +estimated_duration: 300 +flags: also-after-suspend + +id: monitor/dvi-to-vga +_summary: Monitor works (DVI-to-VGA) +_purpose: + Check output to display through VGA adaptor on DVI port +_steps: + 1. Connect display to VGA adaptor on DVI port + 2. Check the display +_verification: + Output to display works +plugin: manual +category_id: com.canonical.plainbox::monitor +estimated_duration: 300 +flags: also-after-suspend + +id: monitor/hdmi-to-vga +_summary: Monitor works (HDMI-to-VGA) +_purpose: + Check output to display through VGA adaptor on HDMI port +_steps: + 1. Connect display to VGA adaptor on HDMI port + 2. Check the display +_verification: + Output to display works +plugin: manual +category_id: com.canonical.plainbox::monitor +estimated_duration: 300 +flags: also-after-suspend + +id: monitor/displayport_hotplug +_summary: Can hotplug monitor (DisplayPort) +plugin: manual +category_id: com.canonical.plainbox::monitor +_purpose: + This test will check the DisplayPort port and the ability to do hotplugging. +_steps: + Skip this test if your system does not have a DisplayPort port. + 1. If a display is already connected, unplug it. + 2. (Re-)Connect a display to the DisplayPort port on your system +_verification: + Was the interface displayed correctly on the screen? +flags: also-after-suspend + +id: monitor/hdmi-hotplug-automated +flags: also-after-suspend +estimated_duration: 15.0 +plugin: shell +category_id: com.canonical.plainbox::monitor +_summary: Automated HDMI hotplug test +_description: + This test will use edid injector on muxpi to check if system detect HDMI hotplug +environ: HDMI_PORT MUXPI_IP +requires: manifest.has_muxpi_hdmi == 'True' +imports: from com.canonical.plainbox import manifest +command: + export SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" + timeout 60 ssh $SSH_OPTS ubuntu@$MUXPI_IP "stm -hdmi off" || exit 1 + sleep 3 + timeout 60 ssh $SSH_OPTS ubuntu@$MUXPI_IP "stm -hdmi on" || exit 1 + sleep 3 + if [ "$(cat /sys/class/drm/$HDMI_PORT/status)" != "connected" ] ;then exit 1; fi + timeout 60 ssh $SSH_OPTS ubuntu@$MUXPI_IP "stm -hdmi off" || exit 1 + sleep 3 + if [ "$(cat /sys/class/drm/$HDMI_PORT/status)" != "disconnected" ] ;then exit 1; fi diff --git a/units/monitor/manifest.pxu b/units/monitor/manifest.pxu new file mode 100644 index 0000000..650e86c --- /dev/null +++ b/units/monitor/manifest.pxu @@ -0,0 +1,11 @@ +# Copyright 2019 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Gavin Lin <gavin.lin@canonical.com> + +unit: manifest entry +id: has_muxpi_hdmi +_prompt: Is the device connected to the following?: +_name: MuxPi HDMI Port +value-type: bool diff --git a/units/monitor/test-plan.pxu b/units/monitor/test-plan.pxu index 2e635cf..5eb1f66 100644 --- a/units/monitor/test-plan.pxu +++ b/units/monitor/test-plan.pxu @@ -273,4 +273,56 @@ include: bootstrap_include: graphics_card +id: monitor-full +unit: test plan +_name: Monitor tests +_description: QA monitor tests for Snappy Ubuntu Core devices +include: +nested_part: + monitor-manual + monitor-automated + +id: monitor-manual +unit: test plan +_name: Manual monitor tests +_description: Manual monitor tests for Snappy Ubuntu Core devices +include: + monitor/dvi + monitor/hdmi + monitor/dvi-to-vga + monitor/hdmi-to-vga + monitor/displayport_hotplug + +id: monitor-automated +unit: test plan +_name: Automated monitor tests +_description: Automated monitor tests for Snappy Ubuntu Core devices +include: + monitor/hdmi-hotplug-automated +id: after-suspend-monitor-full +unit: test plan +_name: Monitor tests (after suspend) +_description: QA monitor tests for Snappy Ubuntu Core devices +include: +nested_part: + after-suspend-monitor-manual + after-suspend-monitor-automated + +id: after-suspend-monitor-manual +unit: test plan +_name: Manual monitor tests (after suspend) +_description: Manual monitor tests for Snappy Ubuntu Core devices +include: + after-suspend-monitor/dvi + after-suspend-monitor/hdmi + after-suspend-monitor/dvi-to-vga + after-suspend-monitor/hdmi-to-vga + after-suspend-monitor/displayport_hotplug + +id: after-suspend-monitor-automated +unit: test plan +_name: Automated monitor tests (after suspend) +_description: Automated monitor tests for Snappy Ubuntu Core devices +include: + after-suspend-monitor/hdmi-hotplug-automated diff --git a/units/power-management/jobs.pxu b/units/power-management/jobs.pxu index 9f8f62e..904c2f0 100644 --- a/units/power-management/jobs.pxu +++ b/units/power-management/jobs.pxu @@ -49,7 +49,7 @@ id: power-management/poweroff-log-attach estimated_duration: 1.0 command: set -o pipefail - more $PLAINBOX_SESSION_SHARE/*poweroff.1.log | cat + cat $PLAINBOX_SESSION_SHARE/*poweroff.1.log _description: This will attach any logs from the power-management/poweroff test to the results. @@ -71,7 +71,7 @@ id: power-management/reboot-log-attach estimated_duration: 1.0 command: set -o pipefail - more $PLAINBOX_SESSION_SHARE/*reboot.1.log | cat + cat $PLAINBOX_SESSION_SHARE/*reboot.1.log _description: This will attach any logs from the power-management/reboot test to the results. @@ -251,3 +251,70 @@ _description: successfully otherwise, Select 'Fail' to indicate there was a problem. VERIFICATION: Did the system shutdown and boot correctly? + +id: power-management/poweroff-manual +_summary: System can be powered off +_purpose: + Check system can poweroff successfully +_steps: + 1. Execute following command: + sudo poweroff +_verification: + System poweroff successfully +plugin: manual +category_id: com.canonical.plainbox::power-management +estimated_duration: 300 + +id: power-management/reboot-manual +_summary: System can be rebooted +_purpose: + Check system can reboot +_steps: + 1. Execute following command: + sudo reboot +_verification: + System reboot into system successfully +plugin: manual +category_id: com.canonical.plainbox::power-management +estimated_duration: 300 + +id: power-management/warm-reboot +category_id: com.canonical.plainbox::power-management +_summary: Warm reboot +_description: This tests reboots the system using the `reboot` command +unit: job +plugin: shell +command: + dbus-send --system --print-reply --dest=org.freedesktop.login1 /org/freedesktop/login1 "org.freedesktop.login1.Manager.Reboot" boolean:true +user: root +flags: preserve-locale noreturn autorestart +estimated_duration: 180.0 + +id: power-management/cold-reboot +category_id: com.canonical.plainbox::power-management +_summary: Cold reboot +_description: This tests powers off the system and then powers it on using RTC +unit: job +plugin: shell +requires: rtc.state == 'supported' +command: + rtcwake --mode no -s 120 + sleep 5 + rtcwake -m show + sleep 5 + dbus-send --system --print-reply --dest=org.freedesktop.login1 /org/freedesktop/login1 "org.freedesktop.login1.Manager.PowerOff" boolean:true +user: root +flags: preserve-locale noreturn autorestart +estimated_duration: 300 + +unit: template +template-resource: model_assertion +template-unit: job +plugin: shell +category_id: com.canonical.plainbox::power-management +id: power-management/tickless_idle_{kernel} +estimated_duration: 1.0 +requires: cpuinfo.platform in ('i386', 'x86_64', 'ppc64el', 'pSeries') +_description: Check to see if CONFIG_NO_HZ is set in the kernel (this is just a simple regression check) +command: + zgrep 'CONFIG_NO_HZ=y' /snap/{kernel}/current/config-`uname -r` >/dev/null 2>&1 || ( echo "WARNING: Tickless Idle is NOT set" >&2 && exit 1 ) diff --git a/units/power-management/test-plan.pxu b/units/power-management/test-plan.pxu index 2cd1a76..bb782c1 100644 --- a/units/power-management/test-plan.pxu +++ b/units/power-management/test-plan.pxu @@ -57,3 +57,30 @@ include: power-management/poweroff-log-attach power-management/reboot certification-status=blocker power-management/reboot-log-attach + +id: power-full +unit: test plan +_name: Power tests +_description: QA power tests for Snappy Ubuntu Core devices +include: +nested_part: + power-manual + # power-automated is not part of the -full test plan as it silently + # autorestarts checkbox upon reboots thanks to a systemd unit on core. + # This could lead to multiple jobs being skipped w/o any notice + +id: power-automated +unit: test plan +_name: Automated power tests +_description: Automated power tests for Snappy Ubuntu Core devices +include: + power-management/warm-reboot + power-management/cold-reboot + +id: power-manual +unit: test plan +_name: Manual power tests +_description: Manual power tests for Snappy Ubuntu Core devices +include: + power-management/poweroff-manual + power-management/reboot-manual diff --git a/units/rtc/category.pxu b/units/rtc/category.pxu new file mode 100644 index 0000000..683e64e --- /dev/null +++ b/units/rtc/category.pxu @@ -0,0 +1,3 @@ +unit: category +id: rtc_category +_name: Real Time Clock (RTC) diff --git a/units/rtc/jobs.pxu b/units/rtc/jobs.pxu new file mode 100644 index 0000000..0740d19 --- /dev/null +++ b/units/rtc/jobs.pxu @@ -0,0 +1,16 @@ +id: rtc/battery +_summary: RTC battery tracks the time +_purpose: + RTC battery backup power can send system wakeup event +_steps: + 1. Start the test to poweroff the system (wakeup scheduled in 30s) +_verification: + RTC can wake up the system successfully +command: + rtcwake -v -m disable + rtcwake -v -m off -s 30 +plugin: user-interact +user: root +category_id: rtc_category +estimated_duration: 40 +flags: noreturn diff --git a/units/rtc/test-plan.pxu b/units/rtc/test-plan.pxu new file mode 100644 index 0000000..3b37bf0 --- /dev/null +++ b/units/rtc/test-plan.pxu @@ -0,0 +1,20 @@ +id: rtc-full +unit: test plan +_name: RTC tests +_description: QA RTC tests for Snappy Ubuntu Core devices +include: +nested_part: + rtc-manual + +id: rtc-manual +unit: test plan +_name: Manual RTC tests +_description: Manual RTC tests for Snappy Ubuntu Core devices +include: + rtc/battery + +id: rtc-automated +unit: test plan +_name: Automated RTC tests +_description: Automated RTC tests for Snappy Ubuntu Core devices +include: diff --git a/units/security/category.pxu b/units/security/category.pxu new file mode 100644 index 0000000..a29d6a7 --- /dev/null +++ b/units/security/category.pxu @@ -0,0 +1,3 @@ +unit: category +id: security +_name: Security diff --git a/units/security/test-plan.pxu b/units/security/test-plan.pxu new file mode 100644 index 0000000..594bf31 --- /dev/null +++ b/units/security/test-plan.pxu @@ -0,0 +1,31 @@ +id: kernel-security-full +unit: test plan +_name: Kernel-security tests +_description: Collection of tests from QA Regression Testing repository +include: +nested_part: + kernel-security-automated +estimated_duration: 900s + +id: kernel-security-automated +unit: test plan +_name: Automated kernel-security tests +_description: Automated kernel-security tests for Snappy Ubuntu Core devices +estimated_duration: 900s +include: + +id: tainted-kernel-security-full +unit: test plan +_name: Kernel-security tests (tainted) +_description: Kernel-security from QA Regression Testing (ignoring tainting) +include: +nested_part: + tainted-kernel-security-automated +estimated_duration: 870s + +id: tainted-kernel-security-automated +unit: test plan +_name: Automated kernel-security tests (tainted) +_description: Automated kernel-security tests (ignoring tainting) +include: +estimated_duration: 870s diff --git a/units/self/jobs.pxu b/units/self/jobs.pxu new file mode 100644 index 0000000..f085a59 --- /dev/null +++ b/units/self/jobs.pxu @@ -0,0 +1,12 @@ + +unit: template +template-engine: jinja2 +template-resource: interface +template-filter: interface.interface == 'content' and interface.type == 'plug' and interface.snap == '{{ __system_env__["SNAP_NAME"] }}' +id: self/content-plug-connected-{{ name }} +_summary: Ensure the content interface plug {{ name }} is connected +plugin: shell +command: + plug_connected_test.py {{ snap }} {{ name }} +estimated_duration: 1.0 + diff --git a/units/self/test-plan.pxu b/units/self/test-plan.pxu new file mode 100644 index 0000000..bfb3220 --- /dev/null +++ b/units/self/test-plan.pxu @@ -0,0 +1,26 @@ + +id: self-full +unit: test plan +_name: Self tests +_description: Tests to make sure the checkbox snap is setup correctly +include: +nested_part: + self-manual + self-automated + + +id: self-manual +unit: test plan +_name: Manual self tests +_description: Manual tests to make sure the checkbox snap is setup correctly +include: + + +id: self-automated +unit: test plan +_name: Automated self tests +_description: Automated tests to make sure the checkbox snap is setup correctly +include: + self/content-plug-connected-.* +bootstrap_include: + interface diff --git a/units/serial/category.pxu b/units/serial/category.pxu new file mode 100644 index 0000000..148bfe1 --- /dev/null +++ b/units/serial/category.pxu @@ -0,0 +1,3 @@ +unit: category +id: serial +_name: Serial Port diff --git a/units/serial/jobs.pxu b/units/serial/jobs.pxu new file mode 100644 index 0000000..1a82448 --- /dev/null +++ b/units/serial/jobs.pxu @@ -0,0 +1,33 @@ +id: serial/rs232-console +_summary: Serial debugging console is enabled and operational +_purpose: + Check user can log into system through serial port from another machine +_steps: + 1. Connect USB to db9 null modem adapter cable to serial port of test machine + 2. Connect the cable to USB port of another ubuntu machine (client) + 3. Install screen on client (if not done in Prerequisite) + 4. Execute following command on client: + sudo screen /dev/ttyUSB0 + 5. Start getty service on test machine: + sudo systemctl start getty@[rs232 device, ex. /dev/ttyS0].service + 6. Log into the test machine from terminal on client +_verification: + 1. Output to client is fine after getty service started + 2. Log into test machine from terminal on client successfully +plugin: manual +flags: also-after-suspend +category_id: serial +estimated_duration: 600 + +unit: template +template-resource: serial_ports_static +template-unit: job +id: serial/loopback-{dev} +_summary: Serial loopback test of {dev} +_purpose: Check if serial port is working hardwired +plugin: shell +user: root +command: serial_loopback.py {dev} +flags: preserve-locale preserve-cwd also-after-suspend +category_id: serial +estimated_duration: 3.0 diff --git a/units/serial/test-plan.pxu b/units/serial/test-plan.pxu new file mode 100644 index 0000000..2369427 --- /dev/null +++ b/units/serial/test-plan.pxu @@ -0,0 +1,45 @@ +id: serial-full +unit: test plan +_name: Serial tests +_description: QA serial tests for Snappy Ubuntu Core devices +include: +nested_part: + serial-manual + serial-automated + +id: serial-manual +unit: test plan +_name: Manual serial tests +_description: Manual serial tests for Snappy Ubuntu Core devices +include: + serial/rs232-console + +id: serial-automated +unit: test plan +_name: Automated serial tests +_description: Automated serial tests for Snappy Ubuntu Core devices +include: + serial/loopback-.* + +id: after-suspend-serial-full +unit: test plan +_name: Serial tests (after suspend) +_description: QA serial tests for Snappy Ubuntu Core devices +include: +nested_part: + after-suspend-serial-manual + after-suspend-serial-automated + +id: after-suspend-serial-manual +unit: test plan +_name: Manual serial tests (after suspend) +_description: Manual serial tests for Snappy Ubuntu Core devices +include: + after-suspend-serial/rs232-console + +id: after-suspend-serial-automated +unit: test plan +_name: Automated serial tests (after suspend) +_description: Automated serial tests for Snappy Ubuntu Core devices +include: + after-suspend-serial/loopback-.* diff --git a/units/snappy/category.pxu b/units/snappy/category.pxu new file mode 100644 index 0000000..91dd927 --- /dev/null +++ b/units/snappy/category.pxu @@ -0,0 +1,3 @@ +unit: category +id: snappy +_name: Snappy Ubuntu Core diff --git a/units/snappy/snappy.pxu b/units/snappy/snappy.pxu new file mode 100644 index 0000000..fed3f76 --- /dev/null +++ b/units/snappy/snappy.pxu @@ -0,0 +1,335 @@ +id: snappy/snap-list +_summary: Test that the snap list command is working. +_purpose: If snap list command is working then should at least find the + ubuntu-core package. +plugin: shell +command: snap_tests.py list +category_id: snappy +estimated_duration: 10s +flags: preserve-locale + +id: snappy/snap-search +template-engine: jinja2 +_summary: Test that the snap find command is working. +_purpose: + If snap find command is working then should find + {{ __checkbox_env__.get("TEST_SNAP", "test-snapd-tools") }} in the store. +plugin: shell +command: snap_tests.py search +category_id: snappy +estimated_duration: 10s +flags: preserve-locale + +id: snappy/snap-install +template-engine: jinja2 +_summary: Test the snap install command is working +_purpose: + The store should contain the + {{ __checkbox_env__.get("TEST_SNAP", "test-snapd-tools") }} snap. The test + makes sure this can be downloaded and installed on the system. +plugin: shell +command: snap_tests.py install stable +category_id: snappy +estimated_duration: 10s +flags: preserve-locale +user: root +environ: TEST_SNAP SNAPD_TASK_TIMEOUT SNAPD_POLL_INTERVAL + +id: snappy/snap-remove +template-engine: jinja2 +_summary: Test the snap remove command is working. +_purpose: + After having installed the + {{ __checkbox_env__.get("TEST_SNAP", "test-snapd-tools") }} snap, check it + can removed. +plugin: shell +command: snap_tests.py remove +category_id: snappy +estimated_duration: 10s +depends: snappy/snap-install +flags: preserve-locale +user: root +environ: TEST_SNAP SNAPD_TASK_TIMEOUT SNAPD_POLL_INTERVAL + +id: snappy/snap-refresh +_summary: Test the snap refresh command is able to update the hello snap. +_purpose: + Check hello snap can be refreshed by snap refresh +_steps: + 1. Install + snap install hello + 2. Check version number + snap list hello + 3. Update + snap refresh hello --beta + 4. Check version number + snap list hello +_verification: + Check hello version is newer using the beta channel +plugin: manual +after: snappy/snap-remove +category_id: snappy +estimated_duration: 60 + +id: snappy/snap-revert +_summary: Test the snap revert command is able to revert the hello snap. +_purpose: + Check hello snap can be reverted by snap revert +_steps: + 1. Revert + snap revert hello + 2. Check version number + snap list hello +_verification: + Check hello version is back to its stable version +plugin: manual +depends: snappy/snap-refresh +category_id: snappy +estimated_duration: 60 + +id: snappy/snap-reupdate +_summary: Test the snap refresh command is able to refresh again the hello snap. +_purpose: + Check hello snap can be refreshed after removal of the blacklisted revision +_steps: + 1. Remove reverted version (and associated data) + snap remove hello --revision=<beta_revision> + 2. Reupdate + snap refresh hello --beta + 3. Check version number + snap list hello +_verification: + Check hello version is again the one from the beta channel +plugin: manual +depends: snappy/snap-revert +category_id: snappy +estimated_duration: 60 + +id: snappy/snap-refresh-automated +template-engine: jinja2 +_summary: Test the snap refresh command is working. +_description: + The snap {{ __checkbox_env__.get("TEST_SNAP", "test-snapd-tools") }} should + be installed from the stable channel prior to starting the test. The job + refreshes to edge and compares the revision before and after. +plugin: shell +command: snap_tests.py refresh +depends: snappy/snap-install +category_id: snappy +estimated_duration: 10s +user: root +environ: TEST_SNAP SNAPD_TASK_TIMEOUT SNAPD_POLL_INTERVAL + +id: snappy/snap-revert-automated +template-engine: jinja2 +_summary: Test the snap revert command is working. +_description: + Runs after snap-refresh-automated and should revert the installed edge channel + snap {{ __checkbox_env__.get("TEST_SNAP", "test-snapd-tools") }} leftover + from that test to the one from stable. +plugin: shell +command: snap_tests.py revert +depends: snappy/snap-refresh-automated +category_id: snappy +estimated_duration: 10s +user: root +environ: TEST_SNAP SNAPD_TASK_TIMEOUT SNAPD_POLL_INTERVAL + +id: snappy/snap-reupdate-automated +template-engine: jinja2 +_summary: Test the snap refresh command works after blacklisting. +_description: + Checks that the {{ __checkbox_env__.get("TEST_SNAP", "test-snapd-tools") }} + snap can be refreshed after removal of the blacklisted revision. +plugin: shell +command: snap_tests.py reupdate +depends: snappy/snap-revert-automated +category_id: snappy +estimated_duration: 10s +user: root +environ: TEST_SNAP SNAPD_TASK_TIMEOUT SNAPD_POLL_INTERVAL + +id: snappy/os-refresh +_summary: Refresh the system using the snap tool +_purpose: + Check "core" can be refreshed by snap refresh +_steps: + 1. Check version number + snap list core + 2. Update + snap refresh core --edge + 3. Reboot the system and log in + sudo reboot + 4. Check version number + snap list core +_verification: + Check core version is newer using the edge channel +plugin: manual +category_id: snappy +estimated_duration: 400 + +id: snappy/os-refresh-with-refresh-control +_summary: Refresh the system using the snap tool +_purpose: + Check "core" can be refreshed by snap refresh +_steps: + 1. Check version number + snap list core + 2. Update + snap refresh core --edge --ignore-validation + 3. Reboot the system and log in + sudo reboot + 4. Check version number + snap list core +_verification: + Check core version is newer using the edge channel +plugin: manual +category_id: snappy +estimated_duration: 400 + +id: snappy/os-revert +_summary: Rollback system update using the snap tool +_purpose: + Check core can be reverted by snap revert +_steps: + 1. Check version number + snap list core + 2. Revert + snap revert core + 3. Reboot the system and log in + sudo reboot + 4. Check version number + snap list core +_verification: + Check core version is back to its stable version +plugin: manual +depends: snappy/os-refresh +category_id: snappy +estimated_duration: 400 + +id: snappy/os-revert-with-refresh-control +_summary: Rollback system update using the snap tool +_purpose: + Check core can be reverted by snap revert +_steps: + 1. Check version number + snap list core + 2. Revert + snap revert core + 3. Reboot the system and log in + sudo reboot + 4. Check version number + snap list core +_verification: + Check core version is back to its stable version +plugin: manual +depends: snappy/os-refresh-with-refresh-control +category_id: snappy +estimated_duration: 400 + +id: snappy/os-fail-boot +_summary: Automatically rollback after failed boot after upgrade +_purpose: + Check system will rollback to original core snap if failed to boot the updated one +_steps: + 1. Remove reverted version (and associated data) + snap remove core --revision=<edge_revision> + 2. Check that the edge revision is back in the refresh list + snap refresh --list core + 3. Update + snap refresh core --edge + 4. Modify the GRUB Environment Block to simulate a failed boot + sudo /usr/bin/grub-editenv /boot/grub/grubenv set snap_mode=trying + 5. Reboot the system and log in + sudo reboot + 6. Check version number + snap list core +_verification: + Check system is currently booting the stable core version +plugin: manual +category_id: snappy +depends: snappy/os-revert +estimated_duration: 500 + +id: snappy/os-fail-boot-with-refresh-control +_summary: Automatically rollback after failed boot after upgrade +_purpose: + Check system will rollback to original core snap if failed to boot the updated one +_steps: + 1. Remove reverted version (and associated data) + snap remove core --revision=<edge_revision> + 2. Check that the edge revision is back in the refresh list + snap refresh --list core + 3. Update + snap refresh core --edge --ignore-validation + 4. Modify the GRUB Environment Block to simulate a failed boot + sudo /usr/bin/grub-editenv /boot/grub/grubenv set snap_mode=trying + 5. Reboot the system and log in + sudo reboot + 6. Check version number + snap list core +_verification: + Check system is currently booting the stable core version +plugin: manual +category_id: snappy +depends: snappy/os-revert-with-refresh-control +estimated_duration: 500 + +id: snappy/sshd +_summary: SSH is enabled and operational +_purpose: + Check if user can access the system through ssh from other machine +_steps: + 1. Execute following command on other machine in same network + ssh [user id]@[ip address of the testing system] + 2. Enter password to login +_verification: + Can log into system through ssh from other machine +plugin: manual +category_id: snappy +estimated_duration: 120 + +id: snappy/test-store-install-beta +_summary: Snappy install command - beta channel store +_purpose: + Test the snappy install command is able to install and remove snap in beta + channel store. +plugin: shell +command: snap_tests.py install beta && snap_tests.py remove +user: root +category_id: com.canonical.certification::snappy +estimated_duration: 30s +flags: preserve-locale + +id: snappy/test-store-install-edge +_summary: Snappy install command - edge channel store +_purpose: + Test the snappy install command is able to install snap in edge + channel store. +plugin: shell +command: snap_tests.py install edge && snap_tests.py remove +user: root +category_id: com.canonical.certification::snappy +estimated_duration: 30s +flags: preserve-locale + +unit: template +template-resource: com.canonical.certification::model_assertion +template-unit: job +id: snappy/test-store-config-{store} +_summary: Test that image is using the correct snappy store configuration. +_purpose: + The image can be tied to using a particular store for the OEM. This + tests the store for the image is as expected. +plugin: shell +environ: STORE_ID +command: + echo "Expected store ID:" + echo "$STORE_ID" + echo + echo "Store ID in model assertion:" + echo "{store}" + [ "$STORE_ID" == "{store}" ] +category_id: com.canonical.certification::snappy +estimated_duration: 1.0 +flags: preserve-locale diff --git a/units/snappy/test-plan.pxu b/units/snappy/test-plan.pxu new file mode 100644 index 0000000..9efba23 --- /dev/null +++ b/units/snappy/test-plan.pxu @@ -0,0 +1,84 @@ +id: snappy-snap-full +unit: test plan +_name: Tests for snap command +_description: + QA test plan that includes generic tests for the snap command for Snappy + Ubuntu Core devices. +estimated_duration: 5m +include: +nested_part: + snappy-snap-manual + snappy-snap-automated + +id: snappy-snap-full-with-refresh-control +unit: test plan +_name: Tests for snap command +_description: + QA test plan that includes generic tests for the snap command for Snappy + Ubuntu Core devices. +estimated_duration: 5m +include: +nested_part: + snappy-snap-manual-with-refresh-control + snappy-snap-automated + +id: snappy-snap-manual +unit: test plan +_name: QA tests for snap command +_description: + QA test that includes manual tests for the snap command for Snappy Ubuntu + Core devices. +include: + snappy/snap-refresh + snappy/snap-revert + snappy/snap-reupdate + snappy/os-refresh + snappy/os-revert + snappy/os-fail-boot + snappy/sshd +mandatory_include: + snap +bootstrap_include: + model_assertion + +id: snappy-snap-manual-with-refresh-control +unit: test plan +_name: QA tests for snap command on refresh controlled stores +_description: + QA test that includes manual tests for the snap command for Snappy Ubuntu + Core devices. +include: + snappy/snap-refresh + snappy/snap-revert + snappy/snap-reupdate + snappy/os-refresh-with-refresh-control + snappy/os-revert-with-refresh-control + snappy/os-fail-boot-with-refresh-control + snappy/sshd +mandatory_include: + snap +bootstrap_include: + model_assertion + +id: snappy-snap-automated +unit: test plan +_name: Automated tests for snap command +_description: + QA test plan that includes automated tests for the snap command for Snappy + Ubuntu Core devices. +estimated_duration: 1m +include: + snappy/snap-list + snappy/snap-search + snappy/snap-install + snappy/snap-refresh-automated + snappy/snap-revert-automated + snappy/snap-reupdate-automated + snappy/snap-remove + snappy/test-store-install-beta + snappy/test-store-install-edge + snappy/test-store-config-.* +mandatory_include: + snap +bootstrap_include: + model_assertion diff --git a/units/stress/boot.pxu b/units/stress/boot.pxu new file mode 100644 index 0000000..dcc0bf5 --- /dev/null +++ b/units/stress/boot.pxu @@ -0,0 +1,262 @@ +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Jonathan Cave <jonathan.cave@canonical.com> + + +unit: category +id: stress-tests +_name: Stress Tests + + +unit: category +id: stress-tests/cold-boot +_name: Cold-boot Stress Test + + +unit: category +id: stress-tests/warm-boot +_name: Warm-boot Stress Test + + +id: reboot-run-generator +category_id: stress-tests +_description: + Generate a set of IDs corresponding to number of iterations required in the + reboots tests. +plugin: resource +environ: STRESS_BOOT_ITERATIONS +command: + ITERATIONS=${STRESS_BOOT_ITERATIONS:-2} + for i in $(seq 2 $ITERATIONS); do + echo "reboot_id: $i" + echo "reboot_id_previous: $((i-1))" + echo + done +estimated_duration: 1s +flags: preserve-locale + + +id: init-boot-loop-data +category_id: stress-tests +_summary: Generate the baseline data set to test against +_description: This creates baseline data sets which be considered the master + copies and all further tests will be compared against these. +unit: job +plugin: shell +command: + lspci -i $SNAP/usr/share/misc/pci.ids > $PLAINBOX_SESSION_SHARE/lspci_original || true + nmcli -t -f active,BSSID d w l | grep -oP "(?<=^yes:).*" > $PLAINBOX_SESSION_SHARE/wifi_original || true + checkbox-support-lsusb -f $SNAP/checkbox-runtime/var/lib/usbutils/usb.ids -s | sort > $PLAINBOX_SESSION_SHARE/lsusb_original || true +environ: LD_LIBRARY_PATH +user: root +estimated_duration: 1s +flags: preserve-locale + +id: cold-boot-loop-reboot1 +category_id: stress-tests/cold-boot +_summary: Perform cold reboot 1 +_description: This is a template that will be used to generate a stress test + of the system boot. Specifically this is how the device will be request a + reboot. +unit: job +plugin: shell +environ: STRESS_BOOT_WAKEUP_DELAY +command: rtcwake --mode off -s ${STRESS_BOOT_WAKEUP_DELAY:-120} +user: root +flags: preserve-locale noreturn autorestart +estimated_duration: 180.0 +depends: init-boot-loop-data + +id: cold-boot-loop-reboot{reboot_id} +category_id: stress-tests/cold-boot +_summary: Perform cold reboot {reboot_id} +_description: This is a template that will be used to generate a stress test + of the system boot. Specifically this is how the device will be request a + reboot. +unit: template +template-resource: reboot-run-generator +template-unit: job +plugin: shell +environ: STRESS_BOOT_WAKEUP_DELAY STRESS_BOOT_WAIT_DELAY +command: + sleep ${{STRESS_BOOT_WAIT_DELAY:-120}} + rtcwake --mode off -s ${{STRESS_BOOT_WAKEUP_DELAY:-120}} +user: root +flags: preserve-locale noreturn autorestart +estimated_duration: 180.0 +after: cold-boot-loop-test{reboot_id_previous} +depends: init-boot-loop-data + +id: cold-boot-loop-test1 +category_id: stress-tests/cold-boot +_summary: Cold boot system configuration test 1 +_description: This is a template that will be used to generate a stress test + of the system boot. Specifically this is the test that will be performed + on each cycle to verify that all hardware is detected. +unit: job +plugin: shell +environ: LD_LIBRARY_PATH +command: + lspci -i $SNAP/usr/share/misc/pci.ids > $PLAINBOX_SESSION_SHARE/lspci_test + nmcli -t -f active,BSSID d w l | grep -oP "(?<=^yes:).*" > $PLAINBOX_SESSION_SHARE/wifi_test + checkbox-support-lsusb -f $SNAP/checkbox-runtime/var/lib/usbutils/usb.ids -s | sort > $PLAINBOX_SESSION_SHARE/lsusb_test + diff -u $PLAINBOX_SESSION_SHARE/lspci_original $PLAINBOX_SESSION_SHARE/lspci_test + if [ $? -ne 0 ] ; then + echo "lspci mismatch during cycle 1" + exit 1 + fi + diff -u $PLAINBOX_SESSION_SHARE/wifi_original $PLAINBOX_SESSION_SHARE/wifi_test + if [ $? -ne 0 ] ; then + echo "wifi mismatch during cycle 1" + exit 1 + fi + diff -u $PLAINBOX_SESSION_SHARE/lsusb_original $PLAINBOX_SESSION_SHARE/lsusb_test + if [ $? -ne 0 ] ; then + echo "lsusb mismatch during cycle 1" + exit 1 + fi +user: root +flags: preserve-locale +estimated_duration: 1.0 +depends: cold-boot-loop-reboot1 + +id: cold-boot-loop-test{reboot_id} +category_id: stress-tests/cold-boot +_summary: Cold boot system configuration test {reboot_id} +_description: This is a template that will be used to generate a stress test + of the system boot. Specifically this is the test that will be performed + on each cycle to verify that all hardware is detected. +unit: template +template-resource: reboot-run-generator +template-unit: job +plugin: shell +environ: LD_LIBRARY_PATH +command: + lspci -i $SNAP/usr/share/misc/pci.ids > $PLAINBOX_SESSION_SHARE/lspci_test + nmcli -t -f active,BSSID d w l | grep -oP "(?<=^yes:).*" > $PLAINBOX_SESSION_SHARE/wifi_test + checkbox-support-lsusb -f $SNAP/checkbox-runtime/var/lib/usbutils/usb.ids -s | sort > $PLAINBOX_SESSION_SHARE/lsusb_test + diff -u $PLAINBOX_SESSION_SHARE/lspci_original $PLAINBOX_SESSION_SHARE/lspci_test + if [ $? -ne 0 ] ; then + echo "lspci mismatch during cycle {reboot_id}" + exit 1 + fi + diff -u $PLAINBOX_SESSION_SHARE/wifi_original $PLAINBOX_SESSION_SHARE/wifi_test + if [ $? -ne 0 ] ; then + echo "wifi mismatch during cycle {reboot_id}" + exit 1 + fi + diff -u $PLAINBOX_SESSION_SHARE/lsusb_original $PLAINBOX_SESSION_SHARE/lsusb_test + if [ $? -ne 0 ] ; then + echo "lsusb mismatch during cycle {reboot_id}" + exit 1 + fi +user: root +flags: preserve-locale +estimated_duration: 1.0 +depends: cold-boot-loop-reboot{reboot_id} + + +id: warm-boot-loop-reboot1 +category_id: stress-tests/warm-boot +_summary: Perform warm reboot 1 +_description: This is a template that will be used to generate a stress test + of the system boot. Specifically this is how the device will be request a + reboot. +unit: job +plugin: shell +command: + reboot +user: root +flags: preserve-locale noreturn autorestart +estimated_duration: 60s +depends: init-boot-loop-data + +id: warm-boot-loop-reboot{reboot_id} +category_id: stress-tests/warm-boot +_summary: Perform warm reboot {reboot_id} +_description: This is a template that will be used to generate a stress test + of the system boot. Specifically this is how the device will be request a + reboot. +unit: template +template-resource: reboot-run-generator +template-unit: job +plugin: shell +environ: STRESS_BOOT_WAIT_DELAY +command: + sleep ${{STRESS_BOOT_WAIT_DELAY:-120}} + reboot +user: root +flags: preserve-locale noreturn autorestart +estimated_duration: 60.0 +after: warm-boot-loop-test{reboot_id_previous} +depends: init-boot-loop-data + +id: warm-boot-loop-test1 +category_id: stress-tests/warm-boot +_summary: Warm boot system configuration test 1 +_description: This is a template that will be used to generate a stress test + of the system boot. Specifically this is the test that will be performed + on each cycle to verify that all hardware is detected. +unit: job +plugin: shell +environ: LD_LIBRARY_PATH +command: + lspci -i $SNAP/usr/share/misc/pci.ids > $PLAINBOX_SESSION_SHARE/lspci_test + nmcli -t -f active,BSSID d w l | grep -oP "(?<=^yes:).*" > $PLAINBOX_SESSION_SHARE/wifi_test + checkbox-support-lsusb -f $SNAP/checkbox-runtime/var/lib/usbutils/usb.ids -s | sort > $PLAINBOX_SESSION_SHARE/lsusb_test + diff -u $PLAINBOX_SESSION_SHARE/lspci_original $PLAINBOX_SESSION_SHARE/lspci_test + if [ $? -ne 0 ] ; then + echo "lspci mismatch during cycle 1" + exit 1 + fi + diff -u $PLAINBOX_SESSION_SHARE/wifi_original $PLAINBOX_SESSION_SHARE/wifi_test + if [ $? -ne 0 ] ; then + echo "wifi mismatch during cycle 1" + exit 1 + fi + diff -u $PLAINBOX_SESSION_SHARE/lsusb_original $PLAINBOX_SESSION_SHARE/lsusb_test + if [ $? -ne 0 ] ; then + echo "lsusb mismatch during cycle 1" + exit 1 + fi +user: root +flags: preserve-locale +estimated_duration: 1.0 +depends: warm-boot-loop-reboot1 + +id: warm-boot-loop-test{reboot_id} +category_id: stress-tests/warm-boot +_summary: Warm boot system configuration test {reboot_id} +_description: This is a template that will be used to generate a stress test + of the system boot. Specifically this is the test that will be performed + on each cycle to verify that all hardware is detected. +unit: template +template-resource: reboot-run-generator +template-unit: job +plugin: shell +environ: LD_LIBRARY_PATH +command: + lspci -i $SNAP/usr/share/misc/pci.ids > $PLAINBOX_SESSION_SHARE/lspci_test + nmcli -t -f active,BSSID d w l | grep -oP "(?<=^yes:).*" > $PLAINBOX_SESSION_SHARE/wifi_test + checkbox-support-lsusb -f $SNAP/checkbox-runtime/var/lib/usbutils/usb.ids -s | sort > $PLAINBOX_SESSION_SHARE/lsusb_test + diff -u $PLAINBOX_SESSION_SHARE/lspci_original $PLAINBOX_SESSION_SHARE/lspci_test + if [ $? -ne 0 ] ; then + echo "lspci mismatch during cycle {reboot_id}" + exit 1 + fi + diff -u $PLAINBOX_SESSION_SHARE/wifi_original $PLAINBOX_SESSION_SHARE/wifi_test + if [ $? -ne 0 ] ; then + echo "wifi mismatch during cycle {reboot_id}" + exit 1 + fi + diff -u $PLAINBOX_SESSION_SHARE/lsusb_original $PLAINBOX_SESSION_SHARE/lsusb_test + if [ $? -ne 0 ] ; then + echo "lsusb mismatch during cycle {reboot_id}" + exit 1 + fi +user: root +flags: preserve-locale +estimated_duration: 1.0 +depends: warm-boot-loop-reboot{reboot_id} diff --git a/units/stress/jobs.pxu b/units/stress/jobs.pxu index 756d537..719e9e1 100644 --- a/units/stress/jobs.pxu +++ b/units/stress/jobs.pxu @@ -18,7 +18,15 @@ estimated_duration: 7200.0 requires: package.name == 'stress-ng' or executable.name == 'stress-ng' user: root -command: cpu_stress --runtime 7200 +command: + if [ -n "$STRESS_NG_CPU_TIME" ] + then + echo "Found STRESS_NG_CPU_TIME env var, stress_ng cpu running time is now: $STRESS_NG_CPU_TIME seconds" + cpu_stress --runtime $STRESS_NG_CPU_TIME + else + echo STRESS_NG_CPU_TIME env var is not found, stress_ng cpu running time is default value + cpu_stress --runtime 7200 + fi _summary: Stress of CPUs (very long runtime) _description: @@ -273,7 +281,7 @@ estimated_duration: 1.0 depends: stress/reboot command: set -o pipefail - more $PLAINBOX_SESSION_SHARE/*reboot.100.log | cat + cat $PLAINBOX_SESSION_SHARE/*reboot.100.log plugin: shell category_id: com.canonical.plainbox::stress @@ -294,7 +302,7 @@ id: stress/reboot_30_log depends: stress/reboot_30 command: set -o pipefail - more $PLAINBOX_SESSION_SHARE/*reboot.30.log | cat + cat $PLAINBOX_SESSION_SHARE/*reboot.30.log plugin: shell category_id: com.canonical.plainbox::stress @@ -317,7 +325,7 @@ estimated_duration: 1.0 depends: stress/poweroff command: set -o pipefail - more $PLAINBOX_SESSION_SHARE/*poweroff.100.log | cat + cat $PLAINBOX_SESSION_SHARE/*poweroff.100.log plugin: shell category_id: com.canonical.plainbox::stress @@ -339,7 +347,7 @@ id: stress/poweroff_30_log depends: stress/poweroff_30 command: set -o pipefail - more $PLAINBOX_SESSION_SHARE/*poweroff.30.log | cat + cat $PLAINBOX_SESSION_SHARE/*poweroff.30.log plugin: shell category_id: com.canonical.plainbox::stress @@ -356,7 +364,7 @@ estimated_duration: 1.0 depends: stress/reboot_30_check command: set -o pipefail - more $PLAINBOX_SESSION_SHARE/pm_log_check_reboot.30.log | cat + cat $PLAINBOX_SESSION_SHARE/pm_log_check_reboot.30.log plugin: shell category_id: com.canonical.plainbox::stress @@ -373,7 +381,7 @@ estimated_duration: 1.0 depends: stress/poweroff_30_check command: set -o pipefail - more $PLAINBOX_SESSION_SHARE/pm_log_check_poweroff.30.log | cat + cat $PLAINBOX_SESSION_SHARE/pm_log_check_poweroff.30.log plugin: shell category_id: com.canonical.plainbox::stress @@ -390,7 +398,7 @@ estimated_duration: 1.0 depends: stress/reboot_check command: set -o pipefail - more $PLAINBOX_SESSION_SHARE/pm_log_check_reboot.100.log | cat + cat $PLAINBOX_SESSION_SHARE/pm_log_check_reboot.100.log plugin: shell category_id: com.canonical.plainbox::stress @@ -407,7 +415,7 @@ estimated_duration: 1.0 depends: stress/poweroff_check command: set -o pipefail - more $PLAINBOX_SESSION_SHARE/pm_log_check_poweroff.100.log | cat + cat $PLAINBOX_SESSION_SHARE/pm_log_check_poweroff.100.log plugin: shell category_id: com.canonical.plainbox::stress diff --git a/units/stress/s3s4.pxu b/units/stress/s3s4.pxu new file mode 100644 index 0000000..9a62f8e --- /dev/null +++ b/units/stress/s3s4.pxu @@ -0,0 +1,136 @@ +# Copyright 2016 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Sylvain Pineau <sylvain.pineau@canonical.com> + +unit: category +id: stress-tests/suspend +_name: Suspend (S3) Stress Test + +unit: category +id: stress-tests/hibernate +_name: Hibernate (S4) Stress Test + + +id: stress_s3_iterations +category_id: stress-tests/suspend +_description: + Resource job meant to be used in the S3 stress templates +plugin: resource +environ: STRESS_S3_ITERATIONS +command: + echo "s3_iterations: ${STRESS_S3_ITERATIONS:-30}" +estimated_duration: 1s +flags: preserve-locale + +id: stress_s4_iterations +category_id: stress-tests/suspend +_description: + Resource job meant to be used in the S4 stress templates +plugin: resource +environ: STRESS_S4_ITERATIONS +command: + echo "s4_iterations: ${STRESS_S4_ITERATIONS:-30}" +estimated_duration: 1s +flags: preserve-locale + + +unit: template +template-resource: stress_s3_iterations +template-unit: job +plugin: shell +flags: preserve-locale +category_id: stress-tests/suspend +id: stress-tests/suspend_{s3_iterations}_cycles +imports: + from com.canonical.certification import sleep + from com.canonical.certification import rtc +requires: + sleep.mem == 'supported' + rtc.state == 'supported' +estimated_duration: 2400.0 +environ: PLAINBOX_SESSION_SHARE STRESS_S3_SLEEP_DELAY STRESS_S3_WAIT_DELAY LD_LIBRARY_PATH +user: root +command: + export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$SNAP/usr/lib/fwts" + set -o pipefail; checkbox-support-fwts_test -l $PLAINBOX_SESSION_SHARE/suspend_{s3_iterations}_cycles -f none -s s3 --s3-device-check --s3-device-check-delay=${{STRESS_S3_WAIT_DELAY:-45}} --s3-sleep-delay=${{STRESS_S3_SLEEP_DELAY:-30}} --s3-multiple={s3_iterations} -j $SNAP/share/fwts | tee $PLAINBOX_SESSION_SHARE/suspend_{s3_iterations}_cycles_times.log +_description: + PURPOSE: + This is an automated stress test that will force the system to suspend/resume for {s3_iterations} cycles. + +unit: template +template-resource: stress_s3_iterations +template-unit: job +plugin: shell +flags: preserve-locale +category_id: stress-tests/suspend +id: stress-tests/suspend-{s3_iterations}-cycles-log-check +after: stress-tests/suspend_{s3_iterations}_cycles +estimated_duration: 1.0 +command: [ -e $PLAINBOX_SESSION_SHARE/suspend_{s3_iterations}_cycles.log ] && sleep_test_log_check -v s3 $PLAINBOX_SESSION_SHARE/suspend_{s3_iterations}_cycles.log +_description: + Automated check of the {s3_iterations} cycles suspend log for errors detected by fwts. + +unit: template +template-resource: stress_s3_iterations +template-unit: job +plugin: attachment +flags: preserve-locale +category_id: stress-tests/suspend +id: stress-tests/suspend-{s3_iterations}-cycles-log-attach +estimated_duration: 1.0 +after: stress-tests/suspend_{s3_iterations}_cycles +command: [ -e $PLAINBOX_SESSION_SHARE/suspend_{s3_iterations}_cycles.log ] && cat $PLAINBOX_SESSION_SHARE/suspend_{s3_iterations}_cycles.log +_description: + Attaches the log from the {s3_iterations} cycles Suspend/Resume test if it exists + + +unit: template +template-resource: stress_s4_iterations +template-unit: job +plugin: shell +flags: preserve-locale +category_id: stress-tests/hibernate +id: stress-tests/hibernate_{s4_iterations}_cycles +imports: + from com.canonical.certification import sleep + from com.canonical.certification import rtc +requires: + sleep.disk == 'supported' + rtc.state == 'supported' +estimated_duration: 5400.00 +environ: PLAINBOX_SESSION_SHARE STRESS_S4_SLEEP_DELAY STRESS_S4_WAIT_DELAY LD_LIBRARY_PATH +user: root +command: + export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$SNAP/usr/lib/fwts" + checkbox-support-fwts_test -l $PLAINBOX_SESSION_SHARE/hibernate_{s4_iterations}_cycles -f none -s s4 --s4-device-check --s4-device-check-delay=${{STRESS_S4_WAIT_DELAY:-45}} --s4-sleep-delay=${{STRESS_S4_SLEEP_DELAY:-120}} --s4-multiple={s4_iterations} -j $SNAP/share/fwts +_description: + PURPOSE: + This is an automated stress test that will force the system to hibernate/resume for {s4_iterations} cycles + +unit: template +template-resource: stress_s4_iterations +template-unit: job +plugin: shell +flags: preserve-locale +category_id: stress-tests/hibernate +id: stress-tests/hibernate-{s4_iterations}-cycles-log-check +after: stress-tests/hibernate_{s4_iterations}_cycles +estimated_duration: 1.0 +command: [ -e $PLAINBOX_SESSION_SHARE/hibernate_{s4_iterations}_cycles.log ] && sleep_test_log_check -v s4 $PLAINBOX_SESSION_SHARE/hibernate_{s4_iterations}_cycles.log +_description: + Automated check of the {s4_iterations} cycles hibernate log for errors detected by fwts. + +unit: template +template-resource: stress_s4_iterations +template-unit: job +plugin: attachment +flags: preserve-locale +category_id: stress-tests/hibernate +id: stress-tests/hibernate-{s4_iterations}-cycles-log-attach +estimated_duration: 1.0 +after: stress-tests/hibernate_{s4_iterations}_cycles +command: [ -e $PLAINBOX_SESSION_SHARE/hibernate_{s4_iterations}_cycles.log ] && cat $PLAINBOX_SESSION_SHARE/hibernate_{s4_iterations}_cycles.log +_description: + Attaches the log from the {s4_iterations} cycles Hibernate/Resume test if it exists diff --git a/units/stress/stress-ng.pxu b/units/stress/stress-ng.pxu new file mode 100644 index 0000000..9cbe615 --- /dev/null +++ b/units/stress/stress-ng.pxu @@ -0,0 +1,31 @@ +unit: job +id: stress-ng-classes +plugin: resource +_summary: Gather list of test classes from stress-ng +_description: + stress-ng divides tests in to classes. Get a list of classes so can divide + up tests. Complete class list: cpu, cpu-cache, device, io, interrupt, + filesystem, memory, network, os, pipe, scheduler, vm. +command: + command -v stress-ng >/dev/null 2>&1 || { >&2 echo "stress-ng command not found"; exit 1; } + CLASSES="cpu cpu-cache memory os pipe scheduler vm" + for c in $CLASSES; do + echo "stress-ng-class: $c" + echo + done +estimated_duration: 1s +flags: preserve-locale + +unit: template +template-resource: stress-ng-classes +template-unit: job +id: stress/stress-ng-test-for-class-{stress-ng-class} +category_id: stress-tests +_summary: Run the stress-ng stressors for class {stress-ng-class} +_description: + Runs the stressors for the class defined. Tests run sequentially using the + same number of proccesses as online processors. +plugin: shell +estimated_duration: 1200.0 +command: + stress-ng --sequential 0 --class {stress-ng-class} diff --git a/units/stress/test-plan.pxu b/units/stress/test-plan.pxu index 8e7929a..7967dd5 100644 --- a/units/stress/test-plan.pxu +++ b/units/stress/test-plan.pxu @@ -88,3 +88,160 @@ include: stress/cpu_stress_ng_test certification-status=blocker memory/memory_stress_ng certification-status=blocker +id: stress-full +unit: test plan +_name: Stress tests +_description: QA stress tests for Snappy Ubuntu Core devices +include: +nested_part: + stress-automated + +id: stress-automated +unit: test plan +_name: Automated stress tests +_description: Automated stress tests for Snappy Ubuntu Core devices +include: +nested_part: + warm-boot-stress-test + cold-boot-stress-test + suspend-stress-test + hibernate-stress-test + stress-ng-automated + +unit: test plan +id: warm-boot-stress-test +_name: Warm boot stress test +_description: + Reboots the machine a pre-defined number of times and on + resumption of OS performs a hardware check to ensure all + items are still present. Reboot is immediate. +estimated_duration: 25h +bootstrap_include: + reboot-run-generator +include: + warm-boot-loop-.* +mandatory_include: + com.canonical.plainbox::manifest + package + snap + uname + lsb + cpuinfo + dpkg + dmi_attachment + sysfs_attachment + udev_attachment + lspci_attachment + lsusb_attachment + dmi + meminfo + interface + +unit: test plan +id: cold-boot-stress-test +_name: Cold boot stress test +_description: + Reboots the machine a pre-defined number of times and on + resumption of OS performs a hardware check to ensure all + items are still present. The reboot is delayed by 2 minutes + by the RTC to allow hardware to cool. +estimated_duration: 42h +bootstrap_include: + reboot-run-generator +include: + cold-boot-loop-.* +mandatory_include: + com.canonical.plainbox::manifest + package + snap + uname + lsb + cpuinfo + dpkg + dmi_attachment + sysfs_attachment + udev_attachment + lspci_attachment + lsusb_attachment + dmi + meminfo + interface + + +unit: test plan +id: suspend-stress-test +_name: Suspend (S3) stress test +_description: + Suspends the machine a pre-defined number of times and on + resume of OS performs a hardware check to ensure all + items are still present. +estimated_duration: 42h +bootstrap_include: + stress_s3_iterations +include: + stress-tests/suspend.* +mandatory_include: + com.canonical.plainbox::manifest + package + snap + uname + lsb + cpuinfo + dpkg + dmi_attachment + sysfs_attachment + udev_attachment + lspci_attachment + lsusb_attachment + dmi + meminfo + interface + +unit: test plan +id: hibernate-stress-test +_name: Hibernate (S4) stress test +_description: + Hibernates the machine a pre-defined number of times and on + resume of OS performs a hardware check to ensure all + items are still present. +estimated_duration: 42h +bootstrap_include: + stress_s4_iterations +include: + stress-tests/hibernate.* +mandatory_include: + com.canonical.plainbox::manifest + package + snap + uname + lsb + cpuinfo + dpkg + dmi_attachment + sysfs_attachment + udev_attachment + lspci_attachment + lsusb_attachment + dmi + meminfo + interface + +id: stress-ng-automated +unit: test plan +_name: Automated stress-ng tests +_description: Automated stress-ng tests for Snappy Ubuntu Core devices +include: + stress/stress-ng-test-for-class-.* + disk/disk_stress_ng_.* +bootstrap_include: + device + stress-ng-classes + +id: stress-iperf3-automated +unit: test plan +_name: Automated iperf3 tests +_description: Automated iperf3 performance test +include: + ethernet/iperf3_.* +bootstrap_include: + device diff --git a/units/suspend/suspend.pxu b/units/suspend/suspend.pxu index 0c3a942..35ba417 100644 --- a/units/suspend/suspend.pxu +++ b/units/suspend/suspend.pxu @@ -268,7 +268,14 @@ command: if [[ -v SNAP ]]; then export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$SNAP/usr/lib/fwts" fi - set -o pipefail; checkbox-support-fwts_test -f none -l $PLAINBOX_SESSION_SHARE/suspend_single -s s3 --s3-sleep-delay=30 --s3-device-check --s3-device-check-delay=45 | tee $PLAINBOX_SESSION_SHARE/suspend_single_times.log + # fwts s3 is not available on all architectures (i.e ARM) + if fwts --show-tests-categories | grep -q 's3 '; then + echo "Calling fwts" + set -o pipefail; checkbox-support-fwts_test -f none -l $PLAINBOX_SESSION_SHARE/suspend_single -s s3 --s3-sleep-delay=30 --s3-device-check --s3-device-check-delay=45 | tee $PLAINBOX_SESSION_SHARE/suspend_single_times.log + else + echo "Calling rtcwake" + rtcwake -m mem -s 30 + fi estimated_duration: 90.000 unit: template diff --git a/units/suspend/test-plan.pxu b/units/suspend/test-plan.pxu index edc6aaa..1d6e12f 100644 --- a/units/suspend/test-plan.pxu +++ b/units/suspend/test-plan.pxu @@ -102,3 +102,47 @@ include: keys/sleep certification-status=blocker suspend/oops_after_suspend certification-status=blocker suspend/oops_results_after_suspend.log + +id: suspend-tp +unit: test plan +_name: Suspend the system +_description: + This test plan should be nested in other test plans that require tests to be + rerun after suspending the SUT. It's a full-blown TP to help manage the + execution order, by placing it between before-suspend-tp and after-suspend-tp + in the nested_part section of the surrounding test plan. +include: + suspend/suspend_advanced_auto + +id: hibernate-tp +unit: test plan +_name: Hibernate the system +_description: + This test plan should be nested in other test plans that require tests to be + rerun after hibernating the SUT. It's a full-blown TP to help manage the + execution order, by placing it between before-hibernate-tp and + after-hibernate-tp in the nested_part section of the surrounding test plan. +include: + power-management/hibernate_advanced_auto + +id: suspend-tp-manual +unit: test plan +_name: Suspend the system (manual) +_description: + This test plan should be nested in other test plans that require tests to be + rerun after suspending the SUT. It's a full-blown TP to help manage the + execution order, by placing it between before-suspend-tp and after-suspend-tp + in the nested_part section of the surrounding test plan. +include: + suspend/suspend_advanced + +id: hibernate-tp-manual +unit: test plan +_name: Hibernate the system (manual) +_description: + This test plan should be nested in other test plans that require tests to be + rerun after hibernating the SUT. It's a full-blown TP to help manage the + execution order, by placing it between before-hibernate-tp and + after-hibernate-tp in the nested_part section of the surrounding test plan. +include: + power-management/hibernate_advanced diff --git a/units/touchpad/jobs.pxu b/units/touchpad/jobs.pxu index 2fba8ad..a7b7e35 100644 --- a/units/touchpad/jobs.pxu +++ b/units/touchpad/jobs.pxu @@ -219,14 +219,17 @@ _siblings: [{ "id": "touchpad/continuous-move-after-suspend", "depends": "suspend/suspend_advanced touchpad/continuous-move" }] -plugin: qml -category_id: com.canonical.plainbox::touchpad id: touchpad/palm-rejection -requires: dmi.product in ['Notebook','Laptop','Portable','Convertible'] -estimated_duration: 20 +plugin: user-interact +category_id: com.canonical.plainbox::touchpad +command: qmlscene -qt5 --fullscreen $PLAINBOX_PROVIDER_DATA/palm_rejection.qml 2>&1 | grep -o PASS _purpose: This test checks if touchpad ignores palm touches -qml_file: palm_rejection.qml +_steps: + Select "Test" and follow the instruction on the screen +_verification: + Cursor should not have moved. _siblings: [{ "id": "touchpad/palm-rejection-after-suspend", "depends": "suspend/suspend_advanced touchpad/palm-rejection" }] +estimated_duration: 40 diff --git a/units/tpm/category.pxu b/units/tpm/category.pxu new file mode 100644 index 0000000..612346b --- /dev/null +++ b/units/tpm/category.pxu @@ -0,0 +1,9 @@ +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> + +unit: category +id: tpm +_name: TPM (Trusted Platform Module) diff --git a/units/tpm/manifest.pxu b/units/tpm/manifest.pxu new file mode 100644 index 0000000..cef5c86 --- /dev/null +++ b/units/tpm/manifest.pxu @@ -0,0 +1,10 @@ +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> + +unit: manifest entry +id: has_tpm_chip +_name: TPM1.2 chip +value-type: bool diff --git a/units/tpm/sysfs.pxu b/units/tpm/sysfs.pxu new file mode 100644 index 0000000..fdbf390 --- /dev/null +++ b/units/tpm/sysfs.pxu @@ -0,0 +1,81 @@ +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> + +unit: job +id: sysfs_tpm_count +category_id: tpm +plugin: resource +_summary: Count the number of visible TPM chips in sysfs +_description: + This job just counts the number of visible TPM chips in as reported by + tpm-sysfs-resource tool. The only resource attribute is 'count' +command: echo "count: $(tpm-sysfs-resource | grep -F 'x-sysfs-device-name' | wc -l)" +estimated_duration: 2s +flags: preserve-locale + +unit: job +id: sysfs_tpm +category_id: tpm +plugin: resource +_summary: Collect TPM information from sysfs +_description: + This job collects all the available TPM information from + /sys/class/tpm/*/device/*. +command: tpm-sysfs-resource +estimated_duration: 2s +flags: preserve-locale +# Tie this resource with the has_tpm_chip manifest entry. This way it will +# automatically get skipped (and everything along with it) when according to +# the manifest, there is no TPM chip on the DUT. +requires: manifest.has_tpm_chip == 'True' +imports: from com.canonical.plainbox import manifest + +unit: job +id: sysfs_tpm_after_taking_ownership +category_id: tpm +plugin: resource +after: action/take-ownership +_summary: Collect TPM information from sysfs (after taking ownership) +_description: + This job collects all the available TPM information from + /sys/class/tpm/*/device/*. Distinct files present there are converted to + attributes of resource records. +command: tpm-sysfs-resource +estimated_duration: 2s +flags: preserve-locale +# See note about manifest on the sysfs_tpm job above. +requires: manifest.has_tpm_chip == 'True' +imports: from com.canonical.plainbox import manifest + +unit: job +id: sysfs_tpm_after_clearing_ownership +category_id: tpm +plugin: resource +after: action/clear-ownership +_summary: Collect TPM information from sysfs (after clearing ownership) +_description: + This job collects all the available TPM information from + /sys/class/tpm/*/device/*. Distinct files present there are converted to + attributes of resource records. +command: tpm-sysfs-resource +estimated_duration: 2s +flags: preserve-locale +# See note about manifest on the sysfs_tpm job above. +requires: manifest.has_tpm_chip == 'True' +imports: from com.canonical.plainbox import manifest + +unit: job +id: sysfs_dmi +category_id: tpm +plugin: resource +_summary: Collect DMI information from sysfs +_description: + This job collects all the available DMI information from /sys/class/dmi/id/*. + The main purpose of including this job is to allow the provider to include + vendor-specific quirks by looking at the sysfs_dmi.bios_vendor attribute. +command: dmi-sysfs-resource +estimated_duration: 1s +flags: preserve-locale diff --git a/units/tpm/test-plan.pxu b/units/tpm/test-plan.pxu new file mode 100644 index 0000000..0aafb84 --- /dev/null +++ b/units/tpm/test-plan.pxu @@ -0,0 +1,56 @@ +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> + +unit: test plan +id: tpm-manual +_name: TPM (Trusted Platform Module) Smoke Tests +_description: + This test plan contains simple tests for the TPM chip. The tests will ensure + that the TPM chip is present, is visible by the OS and that ownership over the + chip can be taken. + . + After testing, a number of logs are collected for additional analysis. +estimated_duration: 20m +include: + # First just the manifest stuff (interactive collection + resource) + com.canonical.plainbox::collect-manifest + com.canonical.plainbox::manifest + # Vendor-specific, interactive recovery process. We want to educate the tester + # how they can reset the TPM chip from unknown state using vendor-specific + # instructions. + recovery/.*/clear-tpm-chip + # Generic setup path, we want to go from any TPM state (Active, Inactive, + # Disabled) to Active where we can perform useful tests. + setup/enable-inactive-tpm + setup/enable-disabled-tpm + # The first thing we want to try is to clear the ownership of the TPM chip + # This will let us ensure that all the other tests are independent of any + # ownership or data already stored there. + action/clear-ownership + # Which might require us to reboot / poweroff the machine to complete. + action/re-enable-tpm + # Next we want to take the ownership of the chip. + action/take-ownership + # Finally, with the TPM chip owned, we can perform some simple query jobs and + # collect everything. + query/.* + +unit: test plan +id: tpm-automated +_name: Collect TPM (Trusted Platform Module) Information +_description: + This test plan contains a selection of jobs that collect various bits of + information about a TPM chip. They are designed to run with an owned TPM chip + that uses well-known (20 zero bytes) owner key. + . + This test plan is not interactive. +estimated_duration: 1m +include: + com.canonical.plainbox::manifest + # Next we want to take the ownership of the chip if it's not owned yet. + action/take-ownership + # Then collect all of the information + query/.* diff --git a/units/tpm/tpm.pxu b/units/tpm/tpm.pxu new file mode 100644 index 0000000..584dc53 --- /dev/null +++ b/units/tpm/tpm.pxu @@ -0,0 +1,285 @@ +# Copyright 2015-2016 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> + +unit: job +id: setup/enable-disabled-tpm +category_id: tpm +_summary: Use BIOS to activate a disabled TPM chip +_purpose: + This job will reconfigure the TPM chip to be in the Active state. +_steps: + To enable the TPM chip in the BIOS, write down the following instructions and + exit the testing application. + . + INSTRUCTIONS FOR ENABLING THE TPM CHIP: + . + - Commence the test to restart the machine + - Enter BIOS using machine-specific hot-key (typically F1, F2, or delete) + - Navigate to TPM menu, the precise location may depend on your BIOS version + and the make and model of your DUT. On some models it is under the + following menu: Security / Security Chip. + - Change the current setting to Active + - Save BIOS settings + - Power the machine off (NOTE: a reboot may not be sufficient) + - Power the machine back on again + - Let the machine boot + - Restart the testing application +plugin: user-interact +user: root +command: reboot +estimated_duration: 3m +flags: preserve-locale noreturn +# NOTE: This job will only run if we know we have a TPM chip (according to the +# manifest) but we don't see one in sysfs (because it's disabled). +requires: sysfs_tpm_count.count == "0" and manifest.has_tpm_chip == 'True' +imports: from com.canonical.plainbox import manifest + +unit: job +id: setup/enable-inactive-tpm +category_id: tpm +_summary: Use BIOS to activate an inactive TPM chip +_purpose: + This job will reconfigure the TPM chip to be in the Active state. +_steps: + To enable the TPM chip in the BIOS, write down the following instructions and + exit the testing application. + . + INSTRUCTIONS FOR ENABLING THE TPM CHIP: + . + - Commence the test to restart the machine + - Enter BIOS using machine-specific hot-key (typically F1, F2, or delete) + - Navigate to TPM menu, the precise location may depend on your BIOS version + and the make and model of your DUT. On some models it is under the + following menu: Security / Security Chip. + - Change the current setting to Active + - Save BIOS settings + - Power the machine off (NOTE: a reboot may not be sufficient) + - Power the machine back on again + - Let the machine boot + - Restart the testing application +plugin: user-interact +user: root +command: reboot +# NOTE: This job will only run if we know we have a TPM chip (according to the +# manifest, again) but sysfs claims it's temporarily deactivated (which is the +# confusing way to say it's inactive) +requires: sysfs_tpm_count.count != "0" and sysfs_tpm.temp_deactivated == "1" +estimated_duration: 3m +flags: preserve-locale noreturn + +unit: job +id: action/clear-ownership +category_id: tpm +_summary: Clear ownership of the TPM chip +_purpose: + This job tries to automatically clear the ownership of an owned TPM chip. It + uses well-known owner secret (20 bytes of zeros). + . + NOTE: The actual TPM chip will be cleared after the machine reboots. After + reboot the TPM will be in the default state: unowned, disabled and inactive. + Subsequent jobs will instruct test operator to enter BIOS and re-enable the + chip. +_steps: + INSTRUCTIONS FOR CLEARING THE TPM CHIP: + . + - Commence the test to reboot the machine + - Let the machine boot + - Restart the testing application +plugin: user-interact +command: + tpm.clear --log debug --well-known && reboot +requires: sysfs_tpm.owned == "1" and sysfs_tpm.enabled == "1" and sysfs_tpm.active == "1" and sysfs_tpm.temp_deactivated == "0" +estimated_duration: 5s +flags: preserve-locale preserve-cwd + +unit: job +id: action/re-enable-tpm +category_id: tpm +_summary: Re-enable TPM chip in BIOS (after clearing ownership) +_purpose: + This job will re-enable the TPM chip in the BIOS after having cleared the ownership. +_steps: + To enable the TPM chip in the BIOS, write down the following instructions and + exit the testing application. + . + INSTRUCTIONS FOR ENABLING THE TPM CHIP: + . + - Commence the test to restart the machine + - Enter BIOS using machine-specific hot-key (typically F1, F2, or delete) + - Navigate to TPM menu, the precise location may depend on your BIOS version + and the make and model of your DUT. On some models it is under the + following menu: Security / Security Chip. + - Change the current setting to Active. If it is already in the active state + then set it to Disabled and then back to Active. This might be a bug in the BIOS. + - Save BIOS settings + - Power the machine off (NOTE: a reboot may not be sufficient) + - Power the machine back on again + - Let the machine boot + - Restart the testing application +plugin: user-interact +user: root +command: reboot +requires: sysfs_tpm_after_clearing_ownership.owned == "0" and sysfs_tpm_after_clearing_ownership.enabled == "0" and sysfs_tpm_after_clearing_ownership.active == "0" and sysfs_tpm_after_clearing_ownership.temp_deactivated == "1" +estimated_duration: 3m +flags: preserve-locale noreturn + +unit: job +id: action/take-ownership +category_id: tpm +_summary: Take ownership of the TPM chip +_description: + This job tries to automatically take the ownership of an unowned TPM chip. It + uses well-known owner and SRK secretes (20 bytes of zeros). +plugin: shell +command: tpm.takeownership --log debug --owner-well-known --srk-well-known +requires: sysfs_tpm.owned == "0" and sysfs_tpm.enabled == "1" and sysfs_tpm.active == "1" and sysfs_tpm.temp_deactivated == "0" +estimated_duration: 5s +flags: preserve-locale preserve-cwd + +# A bunch of shell jobs that run various TPM commands + +unit: job +id: query/tpm_version +category_id: tpm +_summary: Collect the output of tpm_version +_description: + This job collects the output of "tpm_version" for inspection by a + Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.version 2>&1 +estimated_duration: 1s +flags: preserve-locale preserve-cwd + +unit: job +id: query/tpm_selftest +category_id: tpm +_summary: Collect the output of tpm_selftest +_description: + This job collects the output of "tpm_selftest" for inspection by the + Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.selftest 2>&1 +estimated_duration: 1s +flags: preserve-locale preserve-cwd + +unit: job +id: query/tpm_setactive-status +category_id: tpm +_summary: Collect the output of tpm_setactive --status --well-known +_description: + This simply collects the output of "tpm_setactive --status --well-known" for + inspection by a Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.setactive --status --well-known 2>&1 +estimated_duration: 1s +flags: preserve-locale preserve-cwd + +unit: job +id: query/tpm_nvinfo +category_id: tpm +_summary: Collect the output of tpm_nvinfo +_description: + This simply collects the output of "tpm_nvinfo" for inspection by a + Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.nvinfo +estimated_duration: 1s +flags: preserve-locale preserve-cwd + +unit: job +id: query/tpm_restrictpubek-status +category_id: tpm +_summary: Collect the output of tpm_restrictpubek --status --well-known +_description: + This simply collects the output of "tpm_restrictpubek --status --well-known" + for inspection by a Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.restrictpubek --status --well-known +estimated_duration: 1s +flags: preserve-locale preserve-cwd + +unit: job +id: query/tpm_restrictsrk-status +category_id: tpm +_summary: Collect the output of tpm_restrictsrk --status --well-known +_description: + This simply collects the output of "tpm_restrictsrk --status --well-known" + for inspection by a Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.restrictsrk --status --well-known +estimated_duration: 1s +flags: preserve-locale preserve-cwd + +unit: job +id: query/tpm_setclearable-status +category_id: tpm +_summary: Collect the output of tpm_setclearable--status --well-known +_description: + This simply collects the output of "tpm_setclearable --status --well-known" + for inspection by a Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.setclearable --status --well-known +estimated_duration: 1s +flags: preserve-locale preserve-cwd + +unit: job +id: query/tpm_setenable-status +category_id: tpm +_summary: Collect the output of tpm_setenable --status --well-known +_description: + This simply collects the output of "tpm_setenable --status --well-known" + for inspection by a Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.setenable --status --well-known +estimated_duration: 1s +flags: preserve-locale preserve-cwd + +unit: job +id: query/tpm_setownable-status +category_id: tpm +_summary: Collect the output of tpm_setownable --status --well-known +_description: + This simply collects the output of "tpm_setownable --status --well-known" + for inspection by a Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.setownable --status --well-known +estimated_duration: 1s +flags: preserve-locale preserve-cwd + +unit: job +id: query/tpm_setpresence -status +category_id: tpm +_summary: Collect the output of tpm_setpresence --status --well-known +_description: + This simply collects the output of "tpm_setpresence --status --well-known" + for inspection by a Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.setpresence --status --well-known +estimated_duration: 1s +flags: preserve-locale preserve-cwd + +unit: job +id: query/tpm_getpubek +category_id: tpm +_summary: Collect the output of tpm_getpubek --well-known +_description: + This simply collects the output of "tpm_getpubek --well-known" + for inspection by a Certification engineer. +plugin: shell +requires: snap.name == 'tpm' +command: tpm.getpubek --well-known +estimated_duration: 1s +flags: preserve-locale preserve-cwd diff --git a/units/usb/test-plan.pxu b/units/usb/test-plan.pxu index e6a9165..f177798 100644 --- a/units/usb/test-plan.pxu +++ b/units/usb/test-plan.pxu @@ -197,3 +197,136 @@ _name: After suspend automated USB 3 write/read/compare tests on storage devices _description: After suspend automated USB 3 write/read/compare tests on storage devices include: after-suspend-usb3/storage-preinserted + +id: usb-full +unit: test plan +_name: USB tests +_description: QA USB tests for Snappy Ubuntu Core devices +include: +nested_part: + usb-manual + +id: usb-manual +unit: test plan +_name: Manual USB tests +_description: Manual USB tests for Snappy Ubuntu Core devices +include: + usb/hid + usb/insert + usb/storage-automated # depends on manual one, so not automated + usb/remove + +id: usb-automated +unit: test plan +_name: Automated USB tests +_description: Automated USB tests for Snappy Ubuntu Core devices +include: + usb/storage-preinserted-.* +bootstrap_include: + removable_partition + +id: usb3-full +unit: test plan +_name: USB3 tests +_description: QA USB3 tests for Snappy Ubuntu Core devices +include: +nested_part: + usb3-manual + +id: usb3-manual +unit: test plan +_name: Manual USB3 tests +_description: Manual USB3 tests for Snappy Ubuntu Core devices +include: + usb3/insert + usb3/storage-automated # depends on manual one, so not automated + usb3/remove + +id: usb-c-full +unit: test plan +_name: USB-C tests +_description: QA USB-C tests for Snappy Ubuntu Core devices +include: +nested_part: + usb-c-manual + +id: usb-c-manual +unit: test plan +_name: Manual USB-C tests +_description: Manual USB-C tests for Snappy Ubuntu Core devices +include: + usb-c/c-to-a-adapter/hid + usb-c/c-to-a-adapter/insert + usb-c/c-to-a-adapter/storage-automated + usb-c/c-to-a-adapter/remove + usb-c/hid + usb-c/insert + usb-c/storage-automated + usb-c/remove + +id: after-suspend-usb-full +unit: test plan +_name: USB tests (after suspend) +_description: QA USB tests for Snappy Ubuntu Core devices +include: +nested_part: + after-suspend-usb-manual + +id: after-suspend-usb-manual +unit: test plan +_name: Manual USB tests (after suspend) +_description: Manual USB tests for Snappy Ubuntu Core devices +include: + after-suspend-usb/hid + after-suspend-usb/insert + after-suspend-usb/storage-automated # depends on manual one, so not automated + after-suspend-usb/remove + +id: after-suspend-usb3-full +unit: test plan +_name: USB3 tests (after suspend) +_description: QA USB3 tests for Snappy Ubuntu Core devices +include: +nested_part: + after-suspend-usb3-manual + +id: after-suspend-usb3-manual +unit: test plan +_name: Manual USB3 tests (after suspend) +_description: Manual USB3 tests for Snappy Ubuntu Core devices +include: + after-suspend-usb3/insert + after-suspend-usb3/storage-automated # depends on manual one, so not automated + after-suspend-usb3/remove + +id: after-suspend-usb-c-full +unit: test plan +_name: USB-C tests (after suspend) +_description: QA USB-C tests for Snappy Ubuntu Core devices +include: +nested_part: + after-suspend-usb-c-manual + +id: after-suspend-usb-c-manual +unit: test plan +_name: Manual USB-C tests (after suspend) +_description: Manual USB-C tests for Snappy Ubuntu Core devices +include: + after-suspend-usb-c/c-to-a-adapter/hid + after-suspend-usb-c/c-to-a-adapter/insert + after-suspend-usb-c/c-to-a-adapter/storage-automated + after-suspend-usb-c/c-to-a-adapter/remove + after-suspend-usb-c/hid + after-suspend-usb-c/insert + after-suspend-usb-c/storage-automated + after-suspend-usb-c/remove + +id: after-suspend-usb-automated +unit: test plan +_name: Automated USB tests +_description: Automated USB tests for Snappy Ubuntu Core devices +include: + after-suspend-usb/storage-preinserted-.* +bootstrap_include: + removable_partition + diff --git a/units/usb/usb.pxu b/units/usb/usb.pxu index 75cf3c9..670f649 100644 --- a/units/usb/usb.pxu +++ b/units/usb/usb.pxu @@ -290,3 +290,35 @@ command: removable_storage_test -s 268400000 -p 15 usb _description: This test will check that your USB 2.0 port transfers data at a minimum expected speed. + +unit: template +template-resource: removable_partition +template-filter: "usb" in removable_partition.bus +template-unit: job +plugin: shell +category_id: com.canonical.plainbox::usb +id: usb/storage-preinserted-{symlink_uuid} +user: root +estimated_duration: 45.0 +flags: also-after-suspend reset-locale +command: USB_RWTEST_PARTITIONS={symlink_uuid} checkbox-support-usb_read_write +_summary: + Test USB storage on 2.0 or 1.1 ports detected by udev ({symlink_uuid}) +_description: + Tests USB 2.0 or 1.1 ports on a system by doing write/read/compare tests on + randomly created data. It requires that a USB stick is plugged into an + available USB port before running the certification suite. + +id: usb/hid +_summary: USB keyboard works +_purpose: + Check USB input device works +_steps: + 1. Connect USB keyboard + 2. Input somethings with USB keyboard +_verification: + What just input is displayed correctly +plugin: manual +flags: also-after-suspend +category_id: com.canonical.plainbox::usb +estimated_duration: 60 diff --git a/units/watchdog/jobs.pxu b/units/watchdog/jobs.pxu new file mode 100644 index 0000000..2b5b236 --- /dev/null +++ b/units/watchdog/jobs.pxu @@ -0,0 +1,67 @@ +id: watchdog/systemd-config +_summary: Check if the hardware watchdog is properly configured +template-engine: jinja2 +command: + inbuilt=$(systemctl show -p RuntimeWatchdogUSec | awk -F= '{print $2}') + external=$(systemctl is-active watchdog.service) + {%- if __on_ubuntucore__ %} + if [ "$inbuilt" == "0" ]; then + echo "systemd watchdog should be enabled but reset timeout: $inbuilt" + exit 1 + fi + if [ "$external" == "active" ]; then + echo "found unexpected active watchdog.service unit" + exit 1 + fi + echo "systemd watchdog enabled, reset timeout: $inbuilt" + echo "watchdog.service is not active" + {%- else %} + if [ "$inbuilt" != "0" ]; then + echo "systemd watchdog should not be enabled but reset timeout: $inbuilt" + exit 1 + fi + if [ "$external" != "active" ]; then + echo "watchdog.service unit does not report as active" + exit 1 + fi + echo "systemd watchdog disabled" + echo "watchdog.service active" + {% endif -%} +category_id: com.canonical.plainbox::power-management +flags: simple + +id: watchdog/trigger-system-reset +depends: watchdog/systemd-config +_summary: Test that the watchdog module can trigger a system reset +_purpose: + The watchdog module should be capable of issuing a hard reset of the SUT. +_steps: + 1. Commence the test to trigger a SysRq. + 2. Once the watchdog timeout has expired (10s) the SUT should reset itself. + 3. The board will reboot and the user should resume the test session. +_verification: + Did the board reset itself? +command: + echo 1 > /proc/sys/kernel/sysrq + echo 0 > /proc/sys/kernel/panic + echo c > /proc/sysrq-trigger +flags: noreturn preserve-locale +user: root +plugin: user-interact-verify +category_id: com.canonical.plainbox::power-management +estimated_duration: 60 + +id: watchdog/trigger-system-reset-auto +depends: watchdog/systemd-config +_summary: Test that the watchdog module can trigger a system reset +command: + sync + sleep 5 + echo 1 > /proc/sys/kernel/sysrq + echo 0 > /proc/sys/kernel/panic + echo c > /proc/sysrq-trigger +flags: preserve-locale noreturn autorestart +user: root +plugin: shell +category_id: com.canonical.plainbox::power-management +estimated_duration: 60 diff --git a/units/watchdog/test-plan.pxu b/units/watchdog/test-plan.pxu new file mode 100644 index 0000000..407a273 --- /dev/null +++ b/units/watchdog/test-plan.pxu @@ -0,0 +1,27 @@ +id: watchdog-full +unit: test plan +_name: Watchdog tests +_description: + QA test plan that includes watchdog tests +estimated_duration: 1m +include: +nested_part: + watchdog-manual + +id: watchdog-manual +unit: test plan +_name: Manual watchdog tests +_description: Manual watchdog tests for Snappy Ubuntu Core devices +include: + watchdog/systemd-config + watchdog/trigger-system-reset + +id: watchdog-automated +unit: test plan +_name: Automated watchdog tests +_description: + QA test plan that includes automated watchdog tests +estimated_duration: 1s +include: + watchdog/systemd-config + watchdog/trigger-system-reset-auto diff --git a/units/wireless/category.pxu b/units/wireless/category.pxu new file mode 100644 index 0000000..c967414 --- /dev/null +++ b/units/wireless/category.pxu @@ -0,0 +1,3 @@ +unit: category +id: wifi_ap +_name: Wi-Fi access point diff --git a/units/wireless/jobs.pxu b/units/wireless/jobs.pxu index a997285..7165827 100644 --- a/units/wireless/jobs.pxu +++ b/units/wireless/jobs.pxu @@ -166,6 +166,52 @@ requires: {% endif -%} # net_if_management.device == '{{ interface }}' and net_if_management.managed_by == 'NetworkManager' +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' and device.interface != 'UNKNOWN' +template-engine: jinja2 +template-unit: job +id: wireless/wireless_connection_wpa_ax_nm_{{ interface }} +_summary: Connect to WPA-encrypted 802.11ax Wi-Fi network on {{ interface }} +_purpose: + Check system can connect to 802.11ax AP with wpa security +plugin: shell +command: + net_driver_info $NET_DRIVER_INFO + wifi_nmcli_test.py secured {{ interface }} "$WPA_AX_SSID" "$WPA_AX_PSK" +category_id: com.canonical.plainbox::wireless +estimated_duration: 30.0 +flags: preserve-locale also-after-suspend also-after-suspend-manual +requires: + wireless_sta_protocol.{{ interface }}_ax == 'supported' + {%- if __on_ubuntucore__ %} + connections.slot == 'network-manager:service' and connections.plug == '{{ __system_env__["SNAP_NAME"] }}:network-manager' + {% endif -%} +# net_if_management.device == '{{ interface }}' and net_if_management.managed_by == 'NetworkManager' + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' and device.interface != 'UNKNOWN' +template-engine: jinja2 +template-unit: job +id: wireless/wireless_connection_open_ax_nm_{{ interface }} +_summary: Connect to unencrypted 802.11ax Wi-Fi network on {{ interface }} +_purpose: + Check system can connect to insecure 802.11ax AP +plugin: shell +command: + net_driver_info $NET_DRIVER_INFO + wifi_nmcli_test.py open {{ interface }} "$OPEN_AX_SSID" +category_id: com.canonical.plainbox::wireless +estimated_duration: 30.0 +flags: preserve-locale also-after-suspend also-after-suspend-manual +requires: + wireless_sta_protocol.{{ interface }}_ax == 'supported' + {%- if __on_ubuntucore__ %} + connections.slot == 'network-manager:service' and connections.plug == '{{ __system_env__["SNAP_NAME"] }}:network-manager' + {% endif -%} +# net_if_management.device == '{{ interface }}' and net_if_management.managed_by == 'NetworkManager' + plugin: user-interact-verify category_id: com.canonical.plainbox::wireless id: wireless/wireless_connection_wpa_bg_manual diff --git a/units/wireless/test-plan.pxu b/units/wireless/test-plan.pxu index 8b4aafe..f6d6fb3 100644 --- a/units/wireless/test-plan.pxu +++ b/units/wireless/test-plan.pxu @@ -41,6 +41,8 @@ include: wireless/wireless_connection_open_n_nm_.* certification-status=blocker wireless/wireless_connection_wpa_ac_nm_.* certification-status=blocker wireless/wireless_connection_open_ac_nm_.* certification-status=blocker + wireless/wireless_connection_wpa_ax_nm_.* certification-status=blocker + wireless/wireless_connection_open_ax_nm_.* certification-status=blocker wireless/nm_connection_restore_.* id: after-suspend-wireless-cert-automated @@ -57,6 +59,8 @@ include: after-suspend-wireless/wireless_connection_open_n_nm_.* certification-status=blocker after-suspend-wireless/wireless_connection_wpa_ac_nm_.* certification-status=blocker after-suspend-wireless/wireless_connection_open_ac_nm_.* certification-status=blocker + after-suspend-wireless/wireless_connection_wpa_ax_nm_.* certification-status=blocker + after-suspend-wireless/wireless_connection_open_ax_nm_.* certification-status=blocker after-suspend-wireless/nm_connection_restore_.* id: after-suspend-manual-wireless-cert-automated @@ -73,6 +77,8 @@ include: after-suspend-manual-wireless/wireless_connection_open_n_nm_.* certification-status=blocker after-suspend-manual-wireless/wireless_connection_wpa_ac_nm_.* certification-status=blocker after-suspend-manual-wireless/wireless_connection_open_ac_nm_.* certification-status=blocker + after-suspend-manual-wireless/wireless_connection_wpa_ax_nm_.* certification-status=blocker + after-suspend-manual-wireless/wireless_connection_open_ax_nm_.* certification-status=blocker after-suspend-manual-wireless/nm_connection_restore_.* id: wireless-cert-blockers @@ -90,6 +96,8 @@ include: wireless/wireless_connection_open_n_nm_.* certification-status=blocker wireless/wireless_connection_wpa_ac_nm_.* certification-status=blocker wireless/wireless_connection_open_ac_nm_.* certification-status=blocker + wireless/wireless_connection_wpa_ax_nm_.* certification-status=blocker + wireless/wireless_connection_open_ax_nm_.* certification-status=blocker wireless/nm_connection_restore_.* id: after-suspend-wireless-cert-blockers @@ -107,4 +115,291 @@ include: after-suspend-manual-wireless/wireless_connection_open_n_nm_.* certification-status=blocker after-suspend-manual-wireless/wireless_connection_wpa_ac_nm_.* certification-status=blocker after-suspend-manual-wireless/wireless_connection_open_ac_nm_.* certification-status=blocker + after-suspend-manual-wireless/wireless_connection_wpa_ax_nm_.* certification-status=blocker + after-suspend-manual-wireless/wireless_connection_open_ax_nm_.* certification-status=blocker after-suspend-manual-wireless/nm_connection_restore_.* + +id: wireless-full +unit: test plan +_name: Wireless tests +_description: QA tests for wireless connections +estimated_duration: 30m +include: +nested_part: + wireless-manual + wireless-automated + +id: wireless-manual +unit: test plan +_name: Manual tests for wireless +_description: Manual tests wireless +include: + # following matchers may also include some automated jobs, this could be + # fixed with some regex magic, but the lesser evil seems to be just to + # include them as well; XXX: the test plan is not really manual-only + wireless/wireless_connection_open_ax_.* + wireless/wireless_connection_open_ac_.* + wireless/wireless_connection_open_bg_.* + wireless/wireless_connection_open_n_.* + wireless/wireless_connection_wpa_ax_.* + wireless/wireless_connection_wpa_ac_.* + wireless/wireless_connection_wpa_bg_.* + wireless/wireless_connection_wpa_n_.* + +id: wireless-automated +unit: test plan +_name: Automated tests for wireless +_description: + Automated connection tests for unencrypted or WPA-encrypted 802.11 bg, n, ac, ax + networks. +include: + wireless/detect + wireless/wireless_scanning_.* + wireless/wireless_connection_open_ax_nm_.* + wireless/wireless_connection_open_ac_nm_.* + wireless/wireless_connection_open_bg_nm_.* + wireless/wireless_connection_open_n_nm_.* + wireless/wireless_connection_wpa_ax_nm_.* + wireless/wireless_connection_wpa_ac_nm_.* + wireless/wireless_connection_wpa_bg_nm_.* + wireless/wireless_connection_wpa_n_nm_.* + # wireless/wireless_connection_open_ax_np_.* + # wireless/wireless_connection_open_ac_np_.* + # wireless/wireless_connection_open_bg_np_.* + # wireless/wireless_connection_open_n_np_.* + # wireless/wireless_connection_wpa_ax_np_.* + # wireless/wireless_connection_wpa_ac_np_.* + # wireless/wireless_connection_wpa_bg_np_.* + # wireless/wireless_connection_wpa_n_np_.* +bootstrap_include: + device + +id: wireless-netplan-automated +unit: test plan +_name: Automated tests for wireless using netplan +_description: + Automated connection tests for unencrypted or WPA-encrypted 802.11 bg, n, ac, ax + networks using netplan. +include: + wireless/detect + # wireless/wireless_scanning_.* + # wireless/wireless_connection_open_ax_nm_.* + # wireless/wireless_connection_open_ac_nm_.* + # wireless/wireless_connection_open_bg_nm_.* + # wireless/wireless_connection_open_n_nm_.* + # wireless/wireless_connection_wpa_ax_nm_.* + # wireless/wireless_connection_wpa_ac_nm_.* + # wireless/wireless_connection_wpa_bg_nm_.* + # wireless/wireless_connection_wpa_n_nm_.* + wireless/wireless_connection_open_ax_np_.* + wireless/wireless_connection_open_ac_np_.* + wireless/wireless_connection_open_bg_np_.* + wireless/wireless_connection_open_n_np_.* + wireless/wireless_connection_wpa_ax_np_.* + wireless/wireless_connection_wpa_ac_np_.* + wireless/wireless_connection_wpa_bg_np_.* + wireless/wireless_connection_wpa_n_np_.* +bootstrap_include: + device + + +# not suffixing with "-full" for backwards compatibility +id: wireless-wifi-master-mode +unit: test plan +_name: QA tests for wifi master mode +_description: + System as Access Point tests +include: + wireless/wifi_ap_.* +exclude: + wireless/wifi_ap_across_reboot_.*_setup + wireless/wifi_ap_across_reboot_.*_check +bootstrap_include: + device + wifi_interface_mode + +id: wireless-wifi-master-mode-manual +unit: test plan +_name: QA tests for wifi master mode +_description: + System as Access Point tests +include: + wireless/wifi_ap_open_b_no_sta_.*_manual + wireless/wifi_ap_open_g_no_sta_.*_manual + wireless/wifi_ap_wpa_b_no_sta_.*_manual + wireless/wifi_ap_wpa_g_no_sta_.*_manual + wireless/wifi_ap_wpa_b_with_sta_.*_manual + wireless/wifi_ap_wpa_g_with_sta_.*_manual +bootstrap_include: + device + wifi_interface_mode + +id: wireless-wifi-master-mode-auto +unit: test plan +_name: Automated tests for wifi master mode +_description: + Automated tests for using System as Access Point +include: + wireless/wifi_ap_open_b_no_sta_.*_auto + wireless/wifi_ap_open_g_no_sta_.*_auto + wireless/wifi_ap_wpa_b_no_sta_.*_auto + wireless/wifi_ap_wpa_g_no_sta_.*_auto + wireless/wifi_ap_wpa_b_with_sta_.*_auto + wireless/wifi_ap_wpa_g_with_sta_.*_auto + wireless/wifi_ap_setup_wizard_.*_auto +bootstrap_include: + device + wifi_interface_mode + +# not suffixing with "-full" for backwards compatibility +id: after-suspend-wireless-wifi-master-mode +unit: test plan +_name: QA tests for wifi master mode (after suspend) +_description: + System as Access Point tests +include: + after-suspend-wireless/wifi_ap_.* +bootstrap_include: + device + +id: after-suspend-wireless-wifi-master-mode-manual +unit: test plan +_name: QA tests for wifi master mode (after suspend) +_description: + System as Access Point tests +include: + after-suspend-wireless/wifi_ap_open_b_no_sta_.*_manual + after-suspend-wireless/wifi_ap_open_g_no_sta_.*_manual + after-suspend-wireless/wifi_ap_wpa_b_no_sta_.*_manual + after-suspend-wireless/wifi_ap_wpa_g_no_sta_.*_manual + after-suspend-wireless/wifi_ap_wpa_b_with_sta_.*_manual + after-suspend-wireless/wifi_ap_wpa_g_with_sta_.*_manual +bootstrap_include: + device + wifi_interface_mode + +id: after-suspend-wireless-wifi-master-mode-auto +unit: test plan +_name: QA tests for wifi master mode (after suspend) +_description: + System as Access Point tests +include: + after-suspend-wireless/wifi_ap_open_b_no_sta_.*_auto + after-suspend-wireless/wifi_ap_open_g_no_sta_.*_auto + after-suspend-wireless/wifi_ap_wpa_b_no_sta_.*_auto + after-suspend-wireless/wifi_ap_wpa_g_no_sta_.*_auto + after-suspend-wireless/wifi_ap_wpa_b_with_sta_.*_auto + after-suspend-wireless/wifi_ap_wpa_g_with_sta_.*_auto + after-suspend-wireless/wifi_ap_setup_wizard_.*_auto +bootstrap_include: + device + wifi_interface_mode + +id: wireless-wowlan-full +unit: test plan +_name: QA tests for WoWLAN +_description: + Wake on Wireless LAN (WoWLAN) tests +include: +nested_part: + wireless-wowlan-manual + wireless-wowlan-automated + +id: wireless-wowlan-manual +unit: test plan +_name: Manual QA tests for WoWLAN +_description: + Manual Wake on Wireless LAN (WoWLAN) tests +include: + wireless/wowlan_.* +bootstrap_include: + device + +id: wireless-wowlan-automated +unit: test plan +_name: Automated QA tests for WoWLAN +_description: + Automated Wake on Wireless LAN (WoWLAN) tests +include: + +id: after-suspend-wireless-full +unit: test plan +_name: Wireless tests (after suspend) +_description: QA tests for wireless connections +estimated_duration: 30m +include: +nested_part: + after-suspend-wireless-manual + after-suspend-wireless-automated + +id: after-suspend-wireless-manual +unit: test plan +_name: Manual tests for wireless (after suspend) +_description: Manual tests wireless +include: + # following matchers may also include some automated jobs, this could be + # fixed with some regex magic, but the lesser evil seems to be just to + # include them as well; XXX: the test plan is not really manual-only + after-suspend-wireless/wireless_connection_open_ax_.* + after-suspend-wireless/wireless_connection_open_ac_.* + after-suspend-wireless/wireless_connection_open_bg_.* + after-suspend-wireless/wireless_connection_open_n_.* + after-suspend-wireless/wireless_connection_wpa_ax_.* + after-suspend-wireless/wireless_connection_wpa_ac_.* + after-suspend-wireless/wireless_connection_wpa_bg_.* + after-suspend-wireless/wireless_connection_wpa_n_.* + after-suspend-wireless/wifi_ap_.* + +id: after-suspend-wireless-automated +unit: test plan +_name: Automated tests for wireless (after suspend) +_description: + Automated connection tests for unencrypted or WPA-encrypted 802.11 bg, n, ac, ax + networks. +include: + after-suspend-wireless/wireless_scanning_.* + after-suspend-wireless/wireless_connection_open_ax_nm_.* + after-suspend-wireless/wireless_connection_open_ac_nm_.* + after-suspend-wireless/wireless_connection_open_bg_nm_.* + after-suspend-wireless/wireless_connection_open_n_nm_.* + after-suspend-wireless/wireless_connection_wpa_ax_nm_.* + after-suspend-wireless/wireless_connection_wpa_ac_nm_.* + after-suspend-wireless/wireless_connection_wpa_bg_nm_.* + after-suspend-wireless/wireless_connection_wpa_n_nm_.* + # after-suspend-wireless/wireless_connection_open_ax_np_.* + # after-suspend-wireless/wireless_connection_open_ac_np_.* + # after-suspend-wireless/wireless_connection_open_bg_np_.* + # after-suspend-wireless/wireless_connection_open_n_np_.* + # after-suspend-wireless/wireless_connection_wpa_ax_np_.* + # after-suspend-wireless/wireless_connection_wpa_ac_np_.* + # after-suspend-wireless/wireless_connection_wpa_bg_np_.* + # after-suspend-wireless/wireless_connection_wpa_n_np_.* +bootstrap_include: + device + +id: after-suspend-wireless-netplan-automated +unit: test plan +_name: Automated tests for wireless using netplan (after suspend) +_description: + Automated connection tests for unencrypted or WPA-encrypted 802.11 bg, n, ac, ax + networks using netplan. +include: + # after-suspend-wireless/wireless_scanning_.* + # after-suspend-wireless/wireless_connection_open_ax_nm_.* + # after-suspend-wireless/wireless_connection_open_ac_nm_.* + # after-suspend-wireless/wireless_connection_open_bg_nm_.* + # after-suspend-wireless/wireless_connection_open_n_nm_.* + # after-suspend-wireless/wireless_connection_wpa_ax_nm_.* + # after-suspend-wireless/wireless_connection_wpa_ac_nm_.* + # after-suspend-wireless/wireless_connection_wpa_bg_nm_.* + # after-suspend-wireless/wireless_connection_wpa_n_nm_.* + after-suspend-wireless/wireless_connection_open_ax_np_.* + after-suspend-wireless/wireless_connection_open_ac_np_.* + after-suspend-wireless/wireless_connection_open_bg_np_.* + after-suspend-wireless/wireless_connection_open_n_np_.* + after-suspend-wireless/wireless_connection_wpa_ax_np_.* + after-suspend-wireless/wireless_connection_wpa_ac_np_.* + after-suspend-wireless/wireless_connection_wpa_bg_np_.* + after-suspend-wireless/wireless_connection_wpa_n_np_.* +bootstrap_include: + device diff --git a/units/wireless/wifi-ap.pxu b/units/wireless/wifi-ap.pxu new file mode 100644 index 0000000..df25b50 --- /dev/null +++ b/units/wireless/wifi-ap.pxu @@ -0,0 +1,1081 @@ +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_open_a_no_sta_{interface}_manual +category_id: wifi_ap +_summary: Create open 802.11a Wi-Fi AP on {interface} with no STA (Manual) +plugin: manual +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +_purpose: + Check that the system can create an open 802.11a Access Point without any STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=a + $ sudo wifi-ap.config set wifi.interface-mode=direct + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=open + 3. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 4. Attempt to connect to the AP +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_open_a_no_sta_{interface}_auto +category_id: wifi_ap +_summary: Create open 802.11a Wi-Fi AP on {interface} with no STA +plugin: shell +_description: + Check that the system can create an open 802.11a Access Point without any STA + connection on {interface} by configuring the system using wifi-ap snap and + then checking status of the interface using `iw` command. +user: root +command: + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=a + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=open + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_a_open + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_a_open)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_open_b_no_sta_{interface}_manual +category_id: wifi_ap +_summary: Create open 802.11b Wi-Fi AP on {interface} with no STA (Manual) +plugin: manual +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +_purpose: + Check that the system can create an open 802.11b Access Point without any STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=b + $ sudo wifi-ap.config set wifi.interface-mode=direct + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=open + 3. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 4. Attempt to connect to the AP +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_open_b_no_sta_{interface}_auto +category_id: wifi_ap +_summary: Create open 802.11b Wi-Fi AP on {interface} with no STA +plugin: shell +_description: + Check that the system can create an open 802.11b Access Point without any STA + connection on {interface} by configuring the system using wifi-ap snap and + then checking status of the interface using `iw` command. +user: root +command: + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=b + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=open + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_b_open + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_b_open)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_open_g_no_sta_{interface}_manual +category_id: wifi_ap +_summary: Create open 802.11g Wi-Fi AP on {interface} with no STA (Manual) +plugin: manual +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +_purpose: + Check that the system can create an open 802.11g Access Point without any STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=g + $ sudo wifi-ap.config set wifi.interface-mode=direct + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=open + 3. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 4. Attempt to connect to the AP +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_open_g_no_sta_{interface}_auto +category_id: wifi_ap +_summary: Create open 802.11g Wi-Fi AP on {interface} with no STA +plugin: shell +_description: + Check that the system can create an open 802.11g Access Point without any STA + connection on {interface} by configuring the system using wifi-ap snap and + then checking status of the interface using `iw` command. +user: root +command: + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=g + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=open + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_g_open + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_g_open)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_open_ad_no_sta_{interface}_manual +category_id: wifi_ap +_summary: Create open 802.11ad Wi-Fi AP on {interface} with no STA (Manual) +plugin: manual +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +_purpose: + Check that the system can create an open 802.11ad Access Point without any STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=ad + $ sudo wifi-ap.config set wifi.interface-mode=direct + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=open + 3. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 4. Attempt to connect to the AP +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_open_ad_no_sta_{interface}_auto +category_id: wifi_ap +_summary: Create open 802.11ad Wi-Fi AP on {interface} with no STA +plugin: shell +_description: + Check that the system can create an open 802.11ad Access Point without any STA + connection on {interface} by configuring the system using wifi-ap snap and + then checking status of the interface using `iw` command. +user: root +command: + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=ad + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=open + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_ad_open + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_ad_open)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_a_no_sta_{interface}_manual +category_id: wifi_ap +_summary: Create WPA2 802.11a Wi-Fi AP on {interface} with no STA (Manual) +plugin: manual +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +_purpose: + Check that the system can create a WPA2 802.11a Access Point without any STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=a + $ sudo wifi-ap.config set wifi.interface-mode=direct + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=wpa2 + $ sudo wifi-ap.config set wifi.security-passphrase=Test1234 + 3. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 4. Attempt to connect to the AP using password "Test1234" +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_a_no_sta_{interface}_auto +category_id: wifi_ap +_summary: Create WPA2 802.11a Wi-Fi AP on {interface} with no STA +plugin: shell +_description: + Check that the system can create an open 802.11a Access Point without any STA + connection on {interface} by configuring the system using wifi-ap snap and + then checking status of the interface using `iw` command. +user: root +command: + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=a + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=wpa2 + wifi-ap.config set wifi.security-passphrase=Test1234 + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_a_wpa2 + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_a_wpa2)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_b_no_sta_{interface}_manual +category_id: wifi_ap +_summary: Create WPA2 802.11b Wi-Fi AP on {interface} with no STA (Manual) +plugin: manual +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +_purpose: + Check that the system can create a WPA2 802.11b Access Point without any STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=b + $ sudo wifi-ap.config set wifi.interface-mode=direct + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=wpa2 + $ sudo wifi-ap.config set wifi.security-passphrase=Test1234 + 3. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 4. Attempt to connect to the AP using password "Test1234" +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_b_no_sta_{interface}_auto +category_id: wifi_ap +_summary: Create WPA2 802.11b Wi-Fi AP on {interface} with no STA +plugin: shell +_description: + Check that the system can create an open 802.11b Access Point without any STA + connection on {interface} by configuring the system using wifi-ap snap and + then checking status of the interface using `iw` command. +user: root +command: + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=b + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=wpa2 + wifi-ap.config set wifi.security-passphrase=Test1234 + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_b_wpa2 + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_b_wpa2)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_g_no_sta_{interface}_manual +category_id: wifi_ap +_summary: Create WPA2 802.11g Wi-Fi AP on {interface} with no STA (Manual) +plugin: manual +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +_purpose: + Check that the system can create a WPA2 802.11g Access Point without any STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=g + $ sudo wifi-ap.config set wifi.interface-mode=direct + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=wpa2 + $ sudo wifi-ap.config set wifi.security-passphrase=Test1234 + 3. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 4. Attempt to connect to the AP using password "Test1234" +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_g_no_sta_{interface}_auto +category_id: wifi_ap +_summary: Create WPA2 802.11g Wi-Fi AP on {interface} with no STA +plugin: shell +_description: + Check that the system can create an open 802.11g Access Point without any STA + connection on {interface} by configuring the system using wifi-ap snap and + then checking status of the interface using `iw` command. +user: root +command: + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=g + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=wpa2 + wifi-ap.config set wifi.security-passphrase=Test1234 + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_g_wpa2 + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_g_wpa2)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_ad_no_sta_{interface}_manual +category_id: wifi_ap +_summary: Create WPA2 802.11ad Wi-Fi AP on {interface} with no STA (Manual) +plugin: manual +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +_purpose: + Check that the system can create a WPA2 802.11ad Access Point without any STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=ad + $ sudo wifi-ap.config set wifi.interface-mode=direct + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=wpa2 + $ sudo wifi-ap.config set wifi.security-passphrase=Test1234 + 3. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 4. Attempt to connect to the AP using password "Test1234" +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_ad_no_sta_{interface}_auto +category_id: wifi_ap +_summary: Create WPA2 802.11ad Wi-Fi AP on {interface} with no STA +plugin: shell +_description: + Check that the system can create an open 802.11ad Access Point without any STA + connection on {interface} by configuring the system using wifi-ap snap and + then checking status of the interface using `iw` command. +user: root +command: + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=ad + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=wpa2 + wifi-ap.config set wifi.security-passphrase=Test1234 + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_ad_wpa2 + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_ad_wpa2)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_a_with_sta_{interface} +category_id: wifi_ap +_summary: Create WPA2 802.11a Wi-Fi AP on {interface} with active STA (Manual) +plugin: user-interact-verify +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +depends: wireless/wireless_connection_open_bg_nm_{interface} +estimated_duration: 120.0 +environ: LD_LIBRARY_PATH OPEN_BG_SSID +command: + nmcli dev wifi rescan + nmcli dev wifi connect $OPEN_BG_SSID ifname {interface} name WIFI_TEST_CREATED_BY_CHECKBOX +user: root +_purpose: + Check that the system can create a WPA2 802.11a Access Point with an already active STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Connect to an 802.11b/g AP with wpa security: + Press Enter to continue + 3. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=a + $ sudo wifi-ap.config set wifi.interface-mode=virtual + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=wpa2 + $ sudo wifi-ap.config set wifi.security-passphrase=Test1234 + $ sudo wifi-ap.config set wifi.channel=$(iw dev {interface} info | grep -oP 'channel\s+\K\d+') + 4. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 5. Attempt to connect to the AP using password "Test1234" +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_a_with_sta_{interface}_auto +category_id: wifi_ap +_summary: Create WPA2 802.11a Wi-Fi Access Point on {interface} with active STA +plugin: shell +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +depends: wireless/wireless_connection_open_bg_nm_{interface} +estimated_duration: 120.0 +environ: LD_LIBRARY_PATH OPEN_BG_SSID WIFI_AP_SETUPTIME +command: + echo "Scanning for existing networks" + nmcli dev wifi rescan + echo "Connecting to existing network" + nmcli dev wifi connect $OPEN_BG_SSID ifname {interface} name WIFI_TEST_CREATED_BY_CHECKBOX + trap "nmcli dev disconnect {interface}; nmcli con delete id WIFI_TEST_CREATED_BY_CHECKBOX" EXIT + if ! nmcli -m tabular -t -f GENERAL.STATE d show {interface} |grep ^100 ; then + echo "FAILED to connect to STA before setting up AP" + exit 1 + fi + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=a + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=wpa2 + wifi-ap.config set wifi.security-passphrase=Test1234 + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_a_wpa2 + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_a_wpa2)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +user: root +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_b_with_sta_{interface} +category_id: wifi_ap +_summary: Create WPA2 802.11b Wi-Fi AP on {interface} with active STA (Manual) +plugin: user-interact-verify +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +depends: wireless/wireless_connection_open_bg_nm_{interface} +estimated_duration: 120.0 +environ: LD_LIBRARY_PATH $OPEN_BG_SSID +command: + nmcli dev wifi rescan + nmcli dev wifi connect $OPEN_BG_SSID ifname {interface} name WIFI_TEST_CREATED_BY_CHECKBOX +user: root +_purpose: + Check that the system can create a WPA2 802.11b Access Point with an already active STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Connect to an 802.11b/g AP with wpa security: + Press Enter to continue + 3. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=b + $ sudo wifi-ap.config set wifi.interface-mode=virtual + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=wpa2 + $ sudo wifi-ap.config set wifi.security-passphrase=Test1234 + $ sudo wifi-ap.config set wifi.channel=$(iw dev {interface} info | grep -oP 'channel\s+\K\d+') + 4. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 5. Attempt to connect to the AP using password "Test1234" +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_b_with_sta_{interface}_auto +category_id: wifi_ap +_summary: Create WPA2 802.11b Wi-Fi Access Point on {interface} with active STA +plugin: shell +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +depends: wireless/wireless_connection_open_bg_nm_{interface} +estimated_duration: 120.0 +environ: LD_LIBRARY_PATH OPEN_BG_SSID WIFI_AP_SETUPTIME +command: + echo "Scanning for existing networks" + nmcli dev wifi rescan + echo "Connecting to existing network" + nmcli dev wifi connect $OPEN_BG_SSID ifname {interface} name WIFI_TEST_CREATED_BY_CHECKBOX + trap "nmcli dev disconnect {interface}; nmcli con delete id WIFI_TEST_CREATED_BY_CHECKBOX" EXIT + if ! nmcli -m tabular -t -f GENERAL.STATE d show {interface} |grep ^100 ; then + echo "FAILED to connect to STA before setting up AP" + exit 1 + fi + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=b + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=wpa2 + wifi-ap.config set wifi.security-passphrase=Test1234 + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_b_wpa2 + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_b_wpa2)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +user: root +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_g_with_sta_{interface} +category_id: wifi_ap +_summary: Create WPA2 802.11g Wi-Fi AP on {interface} with active STA (Manual) +plugin: user-interact-verify +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +depends: wireless/wireless_connection_open_bg_nm_{interface} +estimated_duration: 120.0 +environ: LD_LIBRARY_PATH OPEN_BG_SSID +command: + nmcli dev wifi rescan + nmcli dev wifi connect $OPEN_BG_SSID ifname {interface} name WIFI_TEST_CREATED_BY_CHECKBOX +user: root +_purpose: + Check that the system can create a WPA2 802.11g Access Point with an already active STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Connect to an 802.11b/g AP with wpa security: + Press Enter to continue + 3. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=g + $ sudo wifi-ap.config set wifi.interface-mode=virtual + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=wpa2 + $ sudo wifi-ap.config set wifi.security-passphrase=Test1234 + $ sudo wifi-ap.config set wifi.channel=$(iw dev {interface} info | grep -oP 'channel\s+\K\d+') + 4. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 5. Attempt to connect to the AP using password "Test1234" +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_g_with_sta_{interface}_auto +category_id: wifi_ap +_summary: Create WPA2 802.11g Wi-Fi Access Point on {interface} with active STA +plugin: shell +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +depends: wireless/wireless_connection_open_bg_nm_{interface} +estimated_duration: 120.0 +environ: LD_LIBRARY_PATH OPEN_BG_SSID WIFI_AP_SETUPTIME +command: + echo "Scanning for existing networks" + nmcli dev wifi rescan + echo "Connecting to existing network" + nmcli dev wifi connect $OPEN_BG_SSID ifname {interface} name WIFI_TEST_CREATED_BY_CHECKBOX + trap "nmcli dev disconnect {interface}; nmcli con delete id WIFI_TEST_CREATED_BY_CHECKBOX" EXIT + if ! nmcli -m tabular -t -f GENERAL.STATE d show {interface} |grep ^100 ; then + echo "FAILED to connect to STA before setting up AP" + exit 1 + fi + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=g + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=wpa2 + wifi-ap.config set wifi.security-passphrase=Test1234 + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_g_wpa2 + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_g_wpa2)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +user: root +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_ad_with_sta_{interface} +category_id: wifi_ap +_summary: Create WPA2 802.11ad Wi-Fi AP on {interface} with active STA (Manual) +plugin: user-interact-verify +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +depends: wireless/wireless_connection_open_bg_nm_{interface} +estimated_duration: 120.0 +environ: LD_LIBRARY_PATH OPEN_BG_SSID +command: + nmcli dev wifi rescan + nmcli dev wifi connect $OPEN_BG_SSID ifname {interface} name WIFI_TEST_CREATED_BY_CHECKBOX +user: root +_purpose: + Check that the system can create a WPA2 802.11ad Access Point with an already active STA connection +_steps: + 1. Delete existing wireless connections known to Network Manager: + $ nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + 2. Connect to an 802.11b/g AP with wpa security: + Press Enter to continue + 3. Configure the wifi-ap snap: + $ sudo wifi-ap.config set wifi.interface={interface} + $ sudo wifi-ap.config set wifi.operation-mode=ad + $ sudo wifi-ap.config set wifi.interface-mode=virtual + $ sudo wifi-ap.config set disabled=false + $ sudo wifi-ap.config set wifi.security=wpa2 + $ sudo wifi-ap.config set wifi.security-passphrase=Test1234 + $ sudo wifi-ap.config set wifi.channel=$(iw dev {interface} info | grep -oP 'channel\s+\K\d+') + 4. Using a suitable Wi-Fi client try to detect the presence of the AP called "Ubuntu" + 5. Attempt to connect to the AP using password "Test1234" +_verification: + Did the client connect? +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_wpa_ad_with_sta_{interface}_auto +category_id: wifi_ap +_summary: Create WPA2 802.11ad Wi-Fi Access Point on {interface} with active STA +plugin: shell +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +depends: wireless/wireless_connection_open_bg_nm_{interface} +estimated_duration: 120.0 +environ: LD_LIBRARY_PATH OPEN_BG_SSID WIFI_AP_SETUPTIME +command: + echo "Scanning for existing networks" + nmcli dev wifi rescan + echo "Connecting to existing network" + nmcli dev wifi connect $OPEN_BG_SSID ifname {interface} name WIFI_TEST_CREATED_BY_CHECKBOX + trap "nmcli dev disconnect {interface}; nmcli con delete id WIFI_TEST_CREATED_BY_CHECKBOX" EXIT + if ! nmcli -m tabular -t -f GENERAL.STATE d show {interface} |grep ^100 ; then + echo "FAILED to connect to STA before setting up AP" + exit 1 + fi + BEGIN_AP_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=ad + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=wpa2 + wifi-ap.config set wifi.security-passphrase=Test1234 + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_ad_wpa2 + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_ad_wpa2)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + echo "==== Service unit logs ====" + journalctl -q -u "*wifi-ap.management-service*" --no-pager --since "$BEGIN_AP_TEST_TS" -o cat + exit 1; + fi +user: root +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_setup_wizard_{interface}_auto +category_id: wifi_ap +_summary: Create Access Point on {interface} using wifi-ap.setup-wizard +plugin: shell +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 20.0 +user: root +command: + echo "Disabling AP" + wifi-ap.config set wifi.operation-mode=g + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.ssid=DISABLED + wifi-ap.config set disabled=true + wifi_ap_wizard.py {interface} eth0 + sleep ${{WIFI_AP_SETUPTIME:-10}} + echo "Running AP setup wizard" + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_Wizard)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then exit 0; else exit 1; fi +_description: + Check that the system can create a WPA2 802.11g Access Point using + wifi-ap.setup-wizard command on {interface}. +environ: WIFI_AP_SETUPTIME +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_across_reboot_{interface}_setup +category_id: wifi_ap +_summary: Create WPA2 802.11g Wi-Fi AP on {interface} and reboot (setup part) +plugin: shell +_description: + Check if the system maintains AP functionality after the reboot. + This job sets the AP. +user: root +command: + echo "Setting up AP" + wifi-ap.config set wifi.interface={interface} + wifi-ap.config set wifi.operation-mode=g + wifi-ap.config set wifi.interface-mode=direct + wifi-ap.config set wifi.security=wpa2 + wifi-ap.config set wifi.security-passphrase=Test1234 + wifi-ap.config set disabled=false + wifi-ap.config set wifi.ssid=Ubuntu_g_wpa2 + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + echo "Rebooting" + reboot +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale noreturn autorestart + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_across_reboot_{interface}_setup_manual +category_id: wifi_ap +_summary: Create WPA2 AP on {interface} and reboot (setup part, manual resume) +plugin: user-interact-verify +_purpose: + Check if the system maintains AP functionality after the reboot. + This job sets the AP. +_steps: + When you start the test, the system will reboot. + After the system goes back up, resume checkbox session and mark this job as + passing. + The next job will check if the Access Point setup survived the reboot. +user: root +command: + unsnap-wifi-ap.sh config set wifi.interface={interface} + unsnap-wifi-ap.sh config set wifi.operation-mode=g + unsnap-wifi-ap.sh config set wifi.interface-mode=direct + unsnap-wifi-ap.sh config set wifi.security=wpa2 + unsnap-wifi-ap.sh config set wifi.security-passphrase=Test1234 + unsnap-wifi-ap.sh config set disabled=0 + unsnap-wifi-ap.sh config set wifi.ssid=Ubuntu_g_wpa2 + sleep ${{WIFI_AP_SETUPTIME:-10}} + reboot +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale noreturn + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_across_reboot_{interface}_check +category_id: wifi_ap +_summary: Create WPA2 802.11g Wi-Fi AP on {interface} and reboot (after reboot part) +plugin: shell +_description: + Check if the system maintains AP functionality after the reboot. + This job checks if AP still works. +user: root +command: + echo "Waiting for AP to become available" + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_g_wpa2)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then + echo "Network detected" + exit 0 + else + echo "FAILED to detect the network" + exit 1; + fi +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +depends: wireless/wifi_ap_across_reboot_{interface}_setup +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wifi_ap_across_reboot_{interface}_check_manual +category_id: wifi_ap +_summary: Create WPA2 AP on {interface} (after reboot part, manual resume) +plugin: shell +_description: + Check if the system maintains AP functionality after the reboot. + This job checks if AP still works. +user: root +command: + sleep ${{WIFI_AP_SETUPTIME:-10}} + RES=`iw {interface} info |grep -E "(type\ AP)|(ssid\ Ubuntu_g_wpa2)" |wc -l` + wifi-ap.config set disabled=true + if [ $RES -eq 2 ]; then exit 0; else exit 1; fi +requires: + wifi_interface_mode.{interface}_AP == 'supported' + snap.name == 'wifi-ap' +depends: wireless/wifi_ap_across_reboot_{interface}_setup_manual +estimated_duration: 120.0 +environ: WIFI_AP_SETUPTIME +flags: preserve-locale diff --git a/units/wireless/wireless-connection-manual.pxu b/units/wireless/wireless-connection-manual.pxu new file mode 100644 index 0000000..9986578 --- /dev/null +++ b/units/wireless/wireless-connection-manual.pxu @@ -0,0 +1,211 @@ +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wireless_connection_open_ax_{interface} +_summary: Connect to unencrypted 802.11ax Wi-Fi network on {interface} +_purpose: + Check system can connect to insecure 802.11ax AP +_steps: + 1. Remove all wireless configuration in /etc/network/interfaces and /etc/network/interfaces.d + 2. Commence the test +_verification: + If there's "Connection test passed" message in result, mark the test as passed. +plugin: user-interact +command: wifi_client_test -i {interface} -s "$OPEN_AX_SSID" +environ: OPEN_AX_SSID +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 90 +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wireless_connection_open_ac_{interface} +_summary: Connect to unencrypted 802.11ac Wi-Fi network on {interface} +_purpose: + Check system can connect to insecure 802.11ac AP +_steps: + 1. Remove all wireless configuration in /etc/network/interfaces and /etc/network/interfaces.d + 2. Commence the test +_verification: + If there's "Connection test passed" message in result, mark the test as passed. +plugin: user-interact +command: wifi_client_test -i {interface} -s "$OPEN_AC_SSID" +environ: OPEN_AC_SSID +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 90 +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wireless_connection_open_bg_{interface} +_summary: Connect to unencrypted 802.11b/g Wi-Fi network on {interface} +_purpose: + Check system can connect to insecure 802.11b/g AP +_steps: + 1. Remove all wireless configuration in /etc/network/interfaces and /etc/network/interfaces.d + 2. Commence the test +_verification: + If there's "Connection test passed" message in result, mark the test as passed. +plugin: user-interact +command: wifi_client_test -i {interface} -s "$OPEN_BG_SSID" +environ: OPEN_BG_SSID +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 90 +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wireless_connection_open_n_{interface} +_summary: Connect to unencrypted 802.11n Wi-Fi network on {interface} +_purpose: + Check system can connect to insecure 802.11n AP +_steps: + 1. Remove all wireless configuration in /etc/network/interfaces and /etc/network/interfaces.d + 2. Commence the test +_verification: + If there's "Connection test passed" message in result, mark the test as passed. +plugin: user-interact +command: wifi_client_test -i {interface} -s "$OPEN_N_SSID" +environ: OPEN_N_SSID +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 90 +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wireless_connection_wpa_ax_{interface} +_summary: Connect to WPA-encrypted 802.11ax Wi-Fi network on {interface} +_purpose: + Check system can connect to 802.11ax AP with wpa security +_steps: + 1. Remove all wireless configuration in /etc/network/interfaces and /etc/network/interfaces.d + 2. Commence the test +_verification: + If there's "Connection test passed" message in result, mark the test as passed. +plugin: user-interact +command: wifi_client_test -i {interface} -s "$WPA_AX_SSID" -k "$WPA_AX_PSK" +environ: WPA_AX_SSID WPA_AX_PSK +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 90 +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wireless_connection_wpa_ac_{interface} +_summary: Connect to WPA-encrypted 802.11ac Wi-Fi network on {interface} +_purpose: + Check system can connect to 802.11ac AP with wpa security +_steps: + 1. Remove all wireless configuration in /etc/network/interfaces and /etc/network/interfaces.d + 2. Commence the test +_verification: + If there's "Connection test passed" message in result, mark the test as passed. +plugin: user-interact +command: wifi_client_test -i {interface} -s "$WPA_AC_SSID" -k "$WPA_AC_PSK" +environ: WPA_AC_SSID WPA_AC_PSK +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 90 +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wireless_connection_wpa_bg_{interface} +_summary: Connect to WPA-encrypted 802.11b/g Wi-Fi network {interface} +_purpose: + Check system can connect to 802.11b/g AP with wpa security +_steps: + 1. Remove all wireless configuration in /etc/network/interfaces and /etc/network/interfaces.d + 2. Commence the test +_verification: + If there's "Connection test passed" message in result, mark the test as passed. +plugin: user-interact +command: wifi_client_test -i {interface} -s "$WPA_BG_SSID" -k "$WPA_BG_PSK" +environ: WPA_BG_SSID WPA_BG_PSK +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 90 +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-unit: job +id: wireless/wireless_connection_wpa_n_{interface} +_summary: Connect to WPA-encrypted 802.11n Wi-Fi network on {interface} +_purpose: + Check system can connect to 802.11n AP with wpa security +_steps: + 1. Remove all wireless configuration in /etc/network/interfaces and /etc/network/interfaces.d + 2. Commence the test +_verification: + If there's "Connection test passed" message in result, mark the test as passed. +plugin: user-interact +command: wifi_client_test -i {interface} -s "$WPA_N_SSID" -k "$WPA_N_PSK" +environ: WPA_N_SSID WPA_N_PSK +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 90 +flags: preserve-locale also-after-suspend + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-engine: jinja2 +template-unit: job +plugin: shell +category_id: com.canonical.plainbox::wireless +id: wireless/monitor_wireless_connection_udp_{{ interface }} +requires: + connections.slot == 'network-manager:service' and connections.plug == '{{ __system_env__["SNAP_NAME"] }}:network-manager' +environ: + WPA_BG_SSID + WPA_BG_PSK + TEST_TARGET_IPERF +command: + nmcli g > /dev/null + if [ $? -ne 0 ]; then + exit 1 + fi + trap "nmcli dev disconnect {{ interface }}; nmcli con delete id WIFI_TEST_CREATED_BY_CHECKBOX" EXIT + nmcli -t -f TYPE,UUID c | grep -oP "(?<=^802-11-wireless:).*" | xargs nmcli c delete + nmcli dev wifi rescan + nmcli dev wifi connect $WPA_BG_SSID password $WPA_BG_PSK ifname {{ interface }} name WIFI_TEST_CREATED_BY_CHECKBOX + iw dev {{ interface }} link + iperf3 -c $TEST_TARGET_IPERF -t 300 -i 30 -u -b 100m -p 5050 + exit $? +estimated_duration: 330.0 +_summary: Wireless connection iperf3 test +_description: + Tests the performance of a system's wireless connection through the iperf3 tool using UDP packets. + +plugin: shell +category_id: com.canonical.plainbox::wireless +id: after-suspend-wifi/wifi_resume_time_auto +estimated_duration: 1.2 +depends: suspend/suspend_advanced_auto +requires: + device.category == 'WIRELESS' +command: network_reconnect_resume_test -t 90 -d wifi +_summary: Network reconnect resume test (wifi) +_description: + Checks the length of time it takes to reconnect an existing wifi connection + after a suspend/resume cycle. diff --git a/units/wireless/wireless-connection-netplan.pxu b/units/wireless/wireless-connection-netplan.pxu index 6811b43..956f83d 100644 --- a/units/wireless/wireless-connection-netplan.pxu +++ b/units/wireless/wireless-connection-netplan.pxu @@ -3,6 +3,30 @@ template-resource: device template-filter: device.category == 'WIRELESS' template-engine: jinja2 template-unit: job +id: wireless/wireless_connection_open_ax_np_{{ interface }} +_summary: + Connect to unencrypted 802.11ax Wi-Fi network on {{ interface }} - netplan +_purpose: + Check system can connect to insecure 802.11ax AP using netplan +plugin: shell +command: + net_driver_info $NET_DRIVER_INFO + wifi_client_test_netplan.py -i {{ interface }} -s $OPEN_AX_SSID -d +user: root +environ: LD_LIBRARY_PATH OPEN_AX_SSID NET_DRIVER_INFO +category_id: com.canonical.plainbox::wireless +estimated_duration: 15 +flags: preserve-locale also-after-suspend also-after-suspend-manual +requires: + wireless_sta_protocol.{{ interface }}_ax == 'supported' +# net_if_management.device == '{{ interface }}' and net_if_management.managed_by == 'networkd' + + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-engine: jinja2 +template-unit: job id: wireless/wireless_connection_open_ac_np_{{ interface }} _summary: Connect to unencrypted 802.11ac Wi-Fi network on {{ interface }} - netplan @@ -74,6 +98,30 @@ template-resource: device template-filter: device.category == 'WIRELESS' template-engine: jinja2 template-unit: job +id: wireless/wireless_connection_wpa_ax_np_{{ interface }} +_summary: + Connect to WPA-encrypted 802.11ax Wi-Fi network on {{ interface }} - netplan +_purpose: + Check system can connect to 802.11ax AP with wpa security using netplan +plugin: shell +command: + net_driver_info $NET_DRIVER_INFO + wifi_client_test_netplan.py -i {{ interface }} -s $WPA_AX_SSID -k $WPA_AX_PSK -d +user: root +environ: LD_LIBRARY_PATH WPA_AX_SSID WPA_AX_PSK NET_DRIVER_INFO +category_id: com.canonical.plainbox::wireless +estimated_duration: 15 +flags: preserve-locale also-after-suspend also-after-suspend-manual +requires: + wireless_sta_protocol.{{ interface }}_ax == 'supported' +# net_if_management.device == '{{ interface }}' and net_if_management.managed_by == 'networkd' + + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' +template-engine: jinja2 +template-unit: job id: wireless/wireless_connection_wpa_ac_np_{{ interface }} _summary: Connect to WPA-encrypted 802.11ac Wi-Fi network on {{ interface }} - netplan diff --git a/units/wireless/wowlan.pxu b/units/wireless/wowlan.pxu new file mode 100644 index 0000000..1c11052 --- /dev/null +++ b/units/wireless/wowlan.pxu @@ -0,0 +1,165 @@ +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' and device.mac != 'UNKNOWN' +id: wireless/wowlan_S5_{interface}_wakeonlan +_summary: Wake on Wireless LAN (WoWLAN) test from S5 - {interface} - wakeonlan +_purpose: + Check that another system can wake up from S5 the SUT using WoWLAN function. +_steps: + 1. Ensure WoWLAN is enabled in BIOS. + 2. Initiate connection to an AP (using nmcli) + 3. Configure the device for WoWLAN, run the command: + $ sudo iw phy phy0 wowlan enable magic-packet + 4. Press Enter for S5 (Soft Off). + 5. From another computer on the same network run the following command: + $ wakeonlan {mac} + If wakeonlan tool is not installed run: + $ sudo apt install wakeonlan + 6. Resume Checkbox +_verification: + Did the SUT wake up from S5? +plugin: user-interact-verify +command: poweroff +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 120 +flags: preserve-locale + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' and device.mac != 'UNKNOWN' +id: wireless/wowlan_S5_{interface}_etherwake +_summary: Wake on Wireless LAN (WoWLAN) test from S5 - {interface} - etherwake +_purpose: + Check that another system can wake up from S5 the SUT using WoWLAN function. +_steps: + 1. Ensure WoWLAN is enabled in BIOS. + 2. Initiate connection to an AP (using nmcli) + 3. Configure the device for WoWLAN, run the command: + $ sudo iw phy phy0 wowlan enable magic-packet + 4. Press Enter for S5 (Soft Off). + 5. From another computer on the same network run the following command: + $ sudo etherwake {mac} + If etherwake tool is not installed run: + $ sudo apt install etherwake + 6. Resume Checkbox +_verification: + Did the SUT wake up from S5? +plugin: user-interact-verify +command: poweroff +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 120 +flags: preserve-locale + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' and device.mac != 'UNKNOWN' +id: wireless/wowlan_S4_{interface}_wakeonlan +_summary: Wake on Wireless LAN (WoWLAN) test from S4 - {interface} - wakeonlan +_purpose: + Check that another system can wake up from S4 the SUT using WoWLAN function. +_steps: + 1. Ensure WoWLAN is enabled in BIOS. + 2. Initiate connection to an AP (using nmcli) + 3. Configure the device for WoWLAN, run the command: + $ sudo iw phy phy0 wowlan enable magic-packet + 4. Press Enter to hibernate the system. + 5. From another computer on the same network run the following command: + $ wakeonlan {mac} + If wakeonlan tool is not installed run: + $ sudo apt install wakeonlan +_verification: + Did the SUT wake up from S4? +plugin: user-interact-verify +requires: + sleep.disk == 'supported' +command: systemctl hibernate +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 120 +flags: preserve-locale + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' and device.mac != 'UNKNOWN' +id: wireless/wowlan_S4_{interface}_etherwake +_summary: Wake on Wireless LAN (WoWLAN) test from S4 - {interface} - etherwake +_purpose: + Check that another system can wake up from S4 the SUT using WoWLAN function. +_steps: + 1. Ensure WoWLAN is enabled in BIOS. + 2. Initiate connection to an AP (using nmcli) + 3. Configure the device for WoWLAN, run the command: + $ sudo iw phy phy0 wowlan enable magic-packet + 4. Press Enter to hibernate the system. + 5. From another computer on the same network run the following command: + $ sudo etherwake {mac} + If etherwake tool is not installed run: + $ sudo apt install etherwake +_verification: + Did the SUT wake up from S4? +plugin: user-interact-verify +requires: + sleep.disk == 'supported' +command: systemctl hibernate +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 120 +flags: preserve-locale + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' and device.mac != 'UNKNOWN' +id: wireless/wowlan_S3_{interface}_wakeonlan +_summary: Wake on Wireless LAN (WoWLAN) test from S3 - {interface} - wakeonlan +_purpose: + Check that another system can wake up from S3 the SUT using WoWLAN function. +_steps: + 1. Ensure WoWLAN is enabled in BIOS. + 2. Initiate connection to an AP (using nmcli) + 3. Configure the device for WoWLAN, run the command: + $ sudo iw phy phy0 wowlan enable magic-packet + 4. Press Enter to suspend the system. + 5. From another computer on the same network run the following command: + $ wakeonlan {mac} + If wakeonlan tool is not installed run: + $ sudo apt install wakeonlan +_verification: + Did the SUT wake up from S3? +plugin: user-interact-verify +requires: + sleep.mem == 'supported' +command: systemctl suspend +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 120 +flags: preserve-locale + +unit: template +template-resource: device +template-filter: device.category == 'WIRELESS' and device.mac != 'UNKNOWN' +id: wireless/wowlan_S3_{interface}_etherwake +_summary: Wake on Wireless LAN (WoWLAN) test from S3 - {interface} - etherwake +_purpose: + Check that another system can wake up from S3 the SUT using WoWLAN function. +_steps: + 1. Ensure WoWLAN is enabled in BIOS. + 2. Initiate connection to an AP (using nmcli) + 3. Configure the device for WoWLAN, run the command: + $ sudo iw phy phy0 wowlan enable magic-packet + 4. Press Enter to suspend the system. + 5. From another computer on the same network run the following command: + $ sudo etherwake {mac} + If etherwake tool is not installed run: + $ sudo apt install etherwake +_verification: + Did the SUT wake up from S3? +plugin: user-interact-verify +requires: + sleep.mem == 'supported' +command: systemctl suspend +user: root +category_id: com.canonical.plainbox::wireless +estimated_duration: 120 +flags: preserve-locale \ No newline at end of file diff --git a/units/wwan/category.pxu b/units/wwan/category.pxu new file mode 100644 index 0000000..4da3915 --- /dev/null +++ b/units/wwan/category.pxu @@ -0,0 +1,3 @@ +unit: category +id: wwan +_name: Wireless Wide Area Network diff --git a/units/wwan/jobs.pxu b/units/wwan/jobs.pxu new file mode 100644 index 0000000..d18cdc3 --- /dev/null +++ b/units/wwan/jobs.pxu @@ -0,0 +1,233 @@ +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Jonathan Cave <jonathan.cave@canonical.com> +# Po-Hsu Lin <po-hsu.lin@canonical.com> + +unit: job +id: wwan/detect +category_id: wwan +_summary: Identify if WWAN module is missing +_purpose: + Tests that there is a WWAN module present and indicates that testing of it + should follow. +plugin: shell +user: root +command: + COUNT=$(wwan_tests count) + if [ $COUNT -eq 0 ]; then + exit 1 + fi +estimated_duration: 2.0 +flags: preserve-locale also-after-suspend preserve-cwd +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_wwan_module == 'True' + snap.name == 'core' and int(snap.revision) >= 1804 or package.name == 'modemmanager' + snap.name == 'modem-manager' or package.name == 'modemmanager' + +unit: template +template-resource: wwan_resource +template-unit: job +id: wwan/gsm-connection-{manufacturer}-{model}-{hw_id}-auto +_summary: Verify a GSM broadband modem can create a data connection +_description: + Any modems discovered by the resource job that list GSM support + will be tested to ensure a data connection can be made. +plugin: shell +command: + BEGIN_CONNECTION_TEST_TS=`date '+%Y-%m-%d %H:%M:%S'` + wwan_tests 3gpp-connection $WWAN_CONTROL_IF $WWAN_NET_IF $WWAN_APN ${{WWAN_SETUPTIME:-30}} + RETVAL=$? + if [ $RETVAL -ne 0 ]; then + echo "==== Service units logs ====" + journalctl -q -u "snap.network-manager.networkmanager.service" -u "snap.modem-manager.modemmanager.service" --no-pager --since "$BEGIN_CONNECTION_TEST_TS" -o cat + exit $RETVAL + fi +environ: LD_LIBRARY_PATH WWAN_CONTROL_IF WWAN_NET_IF WWAN_APN WWAN_SETUPTIME +user: root +estimated_duration: 10.0 +category_id: wwan +flags: preserve-locale also-after-suspend preserve-cwd +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_wwan_module == 'True' + snap.name == 'core' and int(snap.revision) >= 1804 or package.name == 'modemmanager' + snap.name == 'modem-manager' or package.name == 'modemmanager' + +unit: template +template-resource: wwan_resource +template-unit: job +id: wwan/check-sim-present-{manufacturer}-{model}-{hw_id}-auto +_summary: Check if a SIM card is present in a slot connected to the modem +_description: + Check if a SIM card is present in a slot connected to the modem +plugin: shell +command: wwan_tests sim-present {hw_id} +environ: LD_LIBRARY_PATH +user: root +estimated_duration: 10.0 +category_id: wwan +flags: preserve-locale also-after-suspend preserve-cwd +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_wwan_module == 'True' + snap.name == 'core' and int(snap.revision) >= 1804 or package.name == 'modemmanager' + snap.name == 'modem-manager' or package.name == 'modemmanager' + +unit: template +template-resource: wwan_resource +template-unit: job +id: wwan/verify-sim-info-{manufacturer}-{model}-{hw_id} +depends: wwan/check-sim-present-{manufacturer}-{model}-{hw_id}-auto +_summary: Verify that the information retrieved from a SIM card is valid +_description: + Attempt to retrieve as much information as possible from a SIM present. The + user is required to verify if the output is valid. +plugin: user-interact-verify +_steps: + 1. Start the test to automatically retrieve information from the SIM card +_verification: + Check the output, if as expected then mark the test as passed. +command: wwan_tests sim-info {hw_id} +environ: LD_LIBRARY_PATH +user: root +estimated_duration: 5s +category_id: wwan +flags: preserve-locale also-after-suspend preserve-cwd +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_wwan_module == 'True' + snap.name == 'core' and int(snap.revision) >= 1804 or package.name == 'modemmanager' + snap.name == 'modem-manager' or package.name == 'modemmanager' + +id: wwan/detect-manual +plugin: manual +_summary: Check if WWAN module is available +_purpose: + Check if WWAN module is available and ready to be used. +_steps: + 1. Open another terminal on SUT (or press ctrl+z to suspend Checkbox) + 2. Run `sudo mmcli -L` command. + 3. Run `fg` to jump back to checkbox (if you're running in the same terminal) +_verification: + Check the output. Was the modem listed? +estimated_duration: 60s +flags: also-after-suspend-manual +imports: from com.canonical.plainbox import manifest +category_id: wwan +requires: + manifest.has_wwan_module == 'True' + +id: wwan/check-sim-present-manual +plugin: manual +_summary: Check if a SIM card is present in a slot connected to the modem +_purpose: + Check if a SIM card is present in a slot connected to the modem +_steps: + 1. Open another terminal on SUT (or press ctrl+z to suspend Checkbox) + 2. Run `sudo mmcli -m 0 |grep SIM + 3. Run `fg` to jump back to checkbox (if you're running in the same terminal) +_verification: + Check the output. Was the SIM information printed? +estimated_duration: 60s +flags: also-after-suspend-manual +imports: from com.canonical.plainbox import manifest +category_id: wwan +requires: + manifest.has_wwan_module == 'True' +depends: + wwan/detect-manual + +id: wwan/gsm-connection-manual +plugin: manual +template-engine: jinja2 +_summary: Verify a GSM broadband modem can create a data connection +_purpose: + Ensure that the data connection can be made +_steps: + 1. Open another terminal on SUT (or press ctrl+z to suspend Checkbox) + 2. Run `sudo nmcli c add type gsm ifname {{ __checkbox_env__.get("WWAN_CONTROL_IF", "<device name>") }} con-name GSM apn {{ __checkbox_env__.get("WWAN_APN", "<your.carrier.com>") }}` + {%- if "WWAN_CONTROL_IF" not in __checkbox_env__ %} + Replacing the <your.carrier.com> with appropriate APN name, + and <device name> with appropriate device (device can be found by + running sudo nmcli d) + {%- endif %} + {%- if "WWAN_NET_IF" not in __checkbox_env__ %} + 3. Ping Google (Run: `ping -I <ifname> 8.8.8.8 -c 5`) + {%- else %} + 3. Ping Google (Run: `ping -I {{ __checkbox_env__.get("WWAN_NET_IF") }} 8.8.8.8 -c 5`) + {%- endif %} + 4. Disconnect from the network (Run: `sudo nmcli c delete GSM`) + 5. Run `fg` to jump back to checkbox (if you're running in the same terminal) +_verification: + Did the ping came back? +estimated_duration: 120s +flags: also-after-suspend-manual +category_id: wwan +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_wwan_module == 'True' +depends: + wwan/check-sim-present-manual + +id: wwan/scan-networks-manual +plugin: manual +_summary: Verify that modem can scan for available networks +_purpose: + Ensure that the modem can scan/find available networks +_steps: + 1. Open another terminal on SUT (or press ctrl+z to suspend Checkbox) + 2. Run `sudo mmcli -m 0 --3gpp-scan --timeout=300` + 3. Run `fg` to jump back to checkbox (if you're running in the same terminal) +_verification: + Were available networks listed? +estimated_duration: 120s +flags: also-after-suspend-manual +category_id: wwan +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_wwan_module == 'True' +depends: + wwan/check-sim-present-manual + +id: wwan/gsm-connection-interrupted-manual +plugin: manual +template-engine: jinja2 +_summary: Verify a GSM broadband connection can be reconnected after the signal is lost +_purpose: + Ensure that the data connection can be revived after losing signal +_steps: + 1. Open another terminal on SUT (or press ctrl+z to suspend Checkbox) + 2. Run `sudo nmcli c add type gsm ifname {{ __checkbox_env__.get("WWAN_CONTROL_IF", "<device name>") }} con-name GSM apn {{ __checkbox_env__.get("WWAN_APN", "<your.carrier.com>") }}` + {%- if "WWAN_CONTROL_IF" not in __checkbox_env__ %} + Replacing the <your.carrier.com> with appropriate APN name, + and <device name> with appropriate device (device can be found by + running sudo nmcli d) + {%- endif %} + {%- if "WWAN_NET_IF" not in __checkbox_env__ %} + 3. Ping Google (Run: `ping -I <ifname> 8.8.8.8 -c 5`) + {%- else %} + 3. Ping Google (Run: `ping -I {{ __checkbox_env__.get("WWAN_NET_IF") }} 8.8.8.8 -c 5`) + {%- endif %} + 4. Place the system in a Faraday bag + 5. Ping Google (and verify that the connection no longer works) + 6. Remove the Faraday bag + {%- if "WWAN_NET_IF" not in __checkbox_env__ %} + 7. Ping Google (Run: `ping -I <ifname> 8.8.8.8 -c 5`) + {%- else %} + 7. Ping Google (Run: `ping -I {{ __checkbox_env__.get("WWAN_NET_IF") }} 8.8.8.8 -c 5`) + {%- endif %} + 8. Disconnect from the network (Run: `sudo nmcli r wwan off`) + 9. Run `fg` to jump back to checkbox (if you're running in the same terminal) +_verification: + Was the connection revived after plugging back in the antennae? +estimate_duration: 120s +flags: also-after-suspend-manual +category_id: wwan +imports: from com.canonical.plainbox import manifest +requires: + manifest.has_wwan_module == 'True' +depends: + wwan/check-sim-present-manual diff --git a/units/wwan/manifest.pxu b/units/wwan/manifest.pxu new file mode 100644 index 0000000..b15b01f --- /dev/null +++ b/units/wwan/manifest.pxu @@ -0,0 +1,10 @@ +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Jonathan Cave <jonathan.cave@canonical.com> + +unit: manifest entry +id: has_wwan_module +_name: WWAN module +value-type: bool diff --git a/units/wwan/resource.pxu b/units/wwan/resource.pxu new file mode 100644 index 0000000..a0e82ff --- /dev/null +++ b/units/wwan/resource.pxu @@ -0,0 +1,17 @@ +# Copyright 2015 Canonical Ltd. +# All rights reserved. +# +# Written by: +# Jonathan Cave <jonathan.cave@canonical.com> + + +unit: job +id: wwan_resource +category_id: wwan +plugin: resource +_summary: Gather device info about WWAN modems +_description: Gather device info about WWAN modems +command: wwan_tests --use-cli resources +user: root +estimated_duration: 3s +flags: preserve-locale diff --git a/units/wwan/test-plan.pxu b/units/wwan/test-plan.pxu new file mode 100644 index 0000000..421b68f --- /dev/null +++ b/units/wwan/test-plan.pxu @@ -0,0 +1,63 @@ +id: wwan-full +unit: test plan +_name: Wwan tests +_description: QA wwan tests for Snappy Ubuntu Core devices +include: + # Note this test require snap calling snap support + wwan/verify-sim-info-.* +nested_part: + wwan-manual + wwan-automated + +id: wwan-automated +unit: test plan +_name: Automated wwan tests +_description: Automated wwan tests for Snappy Ubuntu Core devices +include: + # Note these tests require snap calling snap support + wwan/detect + wwan/gsm-connection-.*-auto + wwan/check-sim-present-.*-auto +bootstrap_include: + wwan_resource + +id: after-suspend-wwan-full +unit: test plan +_name: Wwan tests (after suspend) +_description: QA wwan tests for Snappy Ubuntu Core devices +include: +nested_part: + after-suspend-wwan-automated + after-suspend-wwan-manual + +id: after-suspend-wwan-automated +unit: test plan +_name: Automated wwan tests (after suspend) +_description: Automated wwan tests for Snappy Ubuntu Core devices +include: + after-suspend-wwan/detect + after-suspend-wwan/gsm-connection-.*-auto +bootstrap_include: + wwan_resource + +id: wwan-manual +unit: test plan +_name: Manual wwan tests +_description: Manual wwan tests for Snappy Ubuntu Core devices +include: + wwan/detect-manual + wwan/gsm-connection-manual + wwan/check-sim-present-manual + wwan/scan-networks-manual + wwan/gsm-connection-interrupted-manual + +id: after-suspend-wwan-manual +unit: test plan +_name: Manual wwan tests (after suspend) +_description: Manual wwan tests for Snappy Ubuntu Core devices +include: + after-suspend-manual-wwan/detect-manual + after-suspend-manual-wwan/gsm-connection-manual + after-suspend-manual-wwan/check-sim-present-manual + after-suspend-manual-wwan/scan-networks-manual + after-suspend-wwan/gsm-connection-interrupted-manual |