summaryrefslogtreecommitdiff
diff options
authorPMR <pmr@pmr-lander>2019-09-21 10:39:34 +0200
committerPMR <pmr@pmr-lander>2019-09-21 10:39:34 +0200
commiteb7c89ab81a6841ac9118a2569fb1071cead9f32 (patch)
tree968e0688b5a184d8ed89f82f4459fa0bb66fbcee
parentca5a917f7304e173247025bfd8c43b1236473b29 (diff)
parent98ccaa543c57fd4d8c46130f84db395a8422ca9e (diff)
Merge branch 'master' into release
-rwxr-xr-xbin/alsa_pcm_info34
-rwxr-xr-xbin/alsa_tests.py172
-rwxr-xr-xbin/boot_mode_test18
-rwxr-xr-xbin/booted_kernel_tests.py56
-rwxr-xr-xbin/dmi-sysfs-resource46
-rwxr-xr-xbin/gateway_ping_test12
-rwxr-xr-xbin/gpio_gpiomem_loopback.py59
-rwxr-xr-xbin/gpio_sysfs_loopback.py97
-rwxr-xr-xbin/i2c_driver_test107
-rwxr-xr-xbin/module_loaded_test57
-rwxr-xr-xbin/network10
-rwxr-xr-xbin/plug_connected_test.py29
-rwxr-xr-xbin/serial_loopback.py47
-rwxr-xr-xbin/snap_tests.py182
-rwxr-xr-xbin/test_bt_keyboard51
-rwxr-xr-xbin/testlib.py1450
-rwxr-xr-xbin/tpm-sysfs-resource68
-rwxr-xr-xbin/wifi_ap_wizard.py65
-rwxr-xr-xbin/wifi_client_test101
-rwxr-xr-xbin/wifi_master_mode91
-rwxr-xr-xbin/wwan_tests387
l---------data/gpio-loopback.pi2.in1
l---------data/gpio-loopback.pi3.in1
l---------data/gpio-loopback.ubuntu-core-18-pi2.in1
-rw-r--r--data/gpio-loopback.ubuntu-core-18-pi3.in23
-rw-r--r--data/hostapd.conf.in21
-rw-r--r--data/palm_rejection.qml19
-rw-r--r--src/EXECUTABLES1
-rw-r--r--src/Makefile6
-rw-r--r--src/alsa_test.cpp594
-rw-r--r--units/6lowpan/category.pxu3
-rw-r--r--units/6lowpan/jobs.pxu11
-rw-r--r--units/6lowpan/test-plan.pxu7
-rw-r--r--units/audio/jobs.pxu229
-rw-r--r--units/audio/manifest.pxu15
-rw-r--r--units/audio/resource.pxu9
-rw-r--r--units/audio/test-plan.pxu74
-rw-r--r--units/bluetooth/category.pxu7
-rw-r--r--units/bluetooth/jobs.pxu131
-rw-r--r--units/bluetooth/resource.pxu84
-rw-r--r--units/bluetooth/test-plan.pxu116
-rw-r--r--units/cpu/test-plan.pxu30
-rw-r--r--units/disk/encryption.pxu29
-rw-r--r--units/disk/jobs.pxu10
-rw-r--r--units/ethernet/jobs.pxu18
-rw-r--r--units/gadget/category.pxu3
-rw-r--r--units/gpio/category.pxu9
-rw-r--r--units/gpio/jobs.pxu38
-rw-r--r--units/gpio/test-plan.pxu40
-rw-r--r--units/graphics/test-plan.pxu16
-rw-r--r--units/i2c/category.pxu9
-rw-r--r--units/i2c/jobs.pxu45
-rw-r--r--units/i2c/manifest.pxu10
-rw-r--r--units/i2c/test-plan.pxu15
-rw-r--r--units/info/jobs.pxu8
-rw-r--r--units/kernel-snap/category.pxu3
-rw-r--r--units/kernel-snap/jobs.pxu17
-rw-r--r--units/kernel-snap/test-plan.pxu22
-rw-r--r--units/location/category.pxu3
-rw-r--r--units/location/jobs.pxu46
-rw-r--r--units/location/test-plan.pxu47
-rw-r--r--units/mediacard/jobs.pxu18
-rw-r--r--units/mediacard/test-plan.pxu31
-rw-r--r--units/memory/test-plan.pxu20
-rw-r--r--units/miscellanea/jobs.pxu2
-rw-r--r--units/monitor/jobs.pxu106
-rw-r--r--units/monitor/manifest.pxu11
-rw-r--r--units/monitor/test-plan.pxu52
-rw-r--r--units/power-management/jobs.pxu71
-rw-r--r--units/power-management/test-plan.pxu27
-rw-r--r--units/rtc/category.pxu3
-rw-r--r--units/rtc/jobs.pxu16
-rw-r--r--units/rtc/test-plan.pxu20
-rw-r--r--units/security/category.pxu3
-rw-r--r--units/security/test-plan.pxu31
-rw-r--r--units/self/jobs.pxu12
-rw-r--r--units/self/test-plan.pxu26
-rw-r--r--units/serial/category.pxu3
-rw-r--r--units/serial/jobs.pxu33
-rw-r--r--units/serial/test-plan.pxu45
-rw-r--r--units/snappy/category.pxu3
-rw-r--r--units/snappy/snappy.pxu335
-rw-r--r--units/snappy/test-plan.pxu84
-rw-r--r--units/stress/boot.pxu262
-rw-r--r--units/stress/jobs.pxu26
-rw-r--r--units/stress/s3s4.pxu136
-rw-r--r--units/stress/stress-ng.pxu31
-rw-r--r--units/stress/test-plan.pxu157
-rw-r--r--units/suspend/suspend.pxu9
-rw-r--r--units/suspend/test-plan.pxu44
-rw-r--r--units/touchpad/jobs.pxu13
-rw-r--r--units/tpm/category.pxu9
-rw-r--r--units/tpm/manifest.pxu10
-rw-r--r--units/tpm/sysfs.pxu81
-rw-r--r--units/tpm/test-plan.pxu56
-rw-r--r--units/tpm/tpm.pxu285
-rw-r--r--units/usb/test-plan.pxu133
-rw-r--r--units/usb/usb.pxu32
-rw-r--r--units/watchdog/jobs.pxu67
-rw-r--r--units/watchdog/test-plan.pxu27
-rw-r--r--units/wireless/category.pxu3
-rw-r--r--units/wireless/jobs.pxu46
-rw-r--r--units/wireless/test-plan.pxu295
-rw-r--r--units/wireless/wifi-ap.pxu1081
-rw-r--r--units/wireless/wireless-connection-manual.pxu211
-rw-r--r--units/wireless/wireless-connection-netplan.pxu48
-rw-r--r--units/wireless/wowlan.pxu165
-rw-r--r--units/wwan/category.pxu3
-rw-r--r--units/wwan/jobs.pxu233
-rw-r--r--units/wwan/manifest.pxu10
-rw-r--r--units/wwan/resource.pxu17
-rw-r--r--units/wwan/test-plan.pxu63
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(&params);
+ 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