diff --git a/tests/bdd/README.md b/tests/bdd/README.md new file mode 100644 index 0000000..a7a52db --- /dev/null +++ b/tests/bdd/README.md @@ -0,0 +1,8 @@ +# bdd-tests + +## dfu.feature + +The following environment variables must be exported to run this test: + +* SDK_ROOT: path to nRF5_SDK_15.2.0_9412b96 directory +* PCA10056_0 and PCA10056_1: jlink serial numbers diff --git a/tests/bdd/dfu.feature b/tests/bdd/dfu.feature new file mode 100644 index 0000000..68162bf --- /dev/null +++ b/tests/bdd/dfu.feature @@ -0,0 +1,21 @@ +Feature: Perform DFU + Scenario: USB serial DFU completes without error + Given the user wants to perform dfu usb-serial + And using package examples\dfu\secure_dfu_test_images\uart\nrf52840\blinky_mbr.zip + And nrfjprog examples\dfu\secure_bootloader\pca10056_usb_debug\hex\secure_bootloader_usb_mbr_pca10056_debug.hex for usb-serial PCA10056_0 + Then perform dfu + + Scenario: Serial DFU completes without error + Given the user wants to perform dfu serial + And using package examples\dfu\secure_dfu_test_images\uart\nrf52840\blinky_mbr.zip + And nrfjprog examples\dfu\secure_bootloader\pca10056_uart_debug\hex\secure_bootloader_uart_mbr_pca10056_debug.hex for serial PCA10056_0 + Then perform dfu + + Scenario: BLE DFU completes without error + Given the user wants to perform dfu ble + And using package examples\dfu\secure_dfu_test_images\ble\nrf52840\hrs_application_s140.zip + And option --conn-ic-id NRF52 + And option --name DfuTarg + And nrfjprog examples\dfu\secure_bootloader\pca10056_ble_debug\hex\secure_bootloader_ble_s140_pca10056_debug.hex for ble PCA10056_0 + And nrfjprog connectivity for serial PCA10056_1 + Then perform dfu diff --git a/tests/bdd/genpkg_help_information.feature b/tests/bdd/genpkg_help_information.feature deleted file mode 100644 index 01d62d5..0000000 --- a/tests/bdd/genpkg_help_information.feature +++ /dev/null @@ -1,10 +0,0 @@ -Feature: Help information - Scenario: User types --help - Given user types 'nrfutil dfu genpkg --help' - When user press enter - Then output contains 'Generate a zipfile package for distribution to Apps supporting Nordic DFU' and exit code is 0 - - Scenario: User does not type mandatory arguments - Given user types 'nrfutil dfu genpkg' - When user press enter - Then output contains 'Error: Missing argument "zipfile".' and exit code is 2 diff --git a/tests/bdd/help_information.feature b/tests/bdd/help_information.feature new file mode 100644 index 0000000..eb1a6f1 --- /dev/null +++ b/tests/bdd/help_information.feature @@ -0,0 +1,20 @@ +Feature: Help information + Scenario: User types pkg generate --help + Given user types 'nrfutil pkg generate --help' + When user press enter + Then output contains 'Generate a zip package for distribution to apps that support Nordic DFU' and exit code is 0 + + Scenario: User does not type mandatory arguments + Given user types 'nrfutil pkg generate' + When user press enter + Then output contains 'Error: Missing argument "ZIPFILE".' and exit code is 2 + + Scenario: User types --help + Given user types 'nrfutil --help' + When user press enter + Then output contains 'Show this message and exit.' and exit code is 0 + + Scenario: User types version + Given user types 'nrfutil version' + When user press enter + Then output version is correct diff --git a/tests/bdd/steps/dfu_steps.py b/tests/bdd/steps/dfu_steps.py new file mode 100644 index 0000000..6d5d67d --- /dev/null +++ b/tests/bdd/steps/dfu_steps.py @@ -0,0 +1,229 @@ +# +# Copyright (c) 2019 Nordic Semiconductor ASA +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# +# 3. Neither the name of Nordic Semiconductor ASA nor the names of other +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# 4. This software must only be used in or with a processor manufactured by Nordic +# Semiconductor ASA, or in or with a processor manufactured by a third party that +# is used in combination with a processor manufactured by Nordic Semiconductor. +# +# 5. Any software provided in binary or object form under this license must not be +# reverse engineered, decompiled, modified and/or disassembled. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from Queue import Empty +import logging +import os +import time +import sys +import subprocess + +from click.testing import CliRunner +from behave import then, given, when + +from nordicsemi.__main__ import cli, int_as_text_to_int +from nordicsemi.lister.device_lister import DeviceLister +from pc_ble_driver_py import config +connectivity_root = os.path.join(os.path.dirname(config.__file__), 'hex', 'sd_api_v3') + + +ENUMERATE_WAIT_TIME = 2.0 # Seconds to wait for enumeration to finish + +def resolve_hex_path(filename): + if filename == "connectivity": + hex_version = config.get_connectivity_hex_version() + filename = 'connectivity_{}_115k2_with_s132_3.1.hex'.format(hex_version) + return os.path.join(connectivity_root, filename) + elif filename == "connectivity_usb": + hex_version = config.get_connectivity_hex_version() + filename = 'connectivity_{}_usb_with_s132_3.1.hex'.format(hex_version) + return os.path.join(connectivity_root, filename) + else: + filename = os.path.join(*filename.split("\\")) + assert "SDK_ROOT" in os.environ, \ + "Environment variable 'SDK_ROOT' must be exported" + + SDK_ROOT = os.environ["SDK_ROOT"] + return os.path.join(SDK_ROOT, filename) + + +def find_nrfjprog(program): + """ + From pc-ble-driver-py/ble-driver.py + """ + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + + return None + +def program_image_usb_serial(context, nrfjprog, full_image_path, snr): + lister = DeviceLister() + + return_code = subprocess.call("\"{nrfjprog}\" --eraseall --snr {snr}" .format(nrfjprog=nrfjprog,snr=snr), shell=True) + assert return_code == 0, "Nrfjprog could not erase board with serial number {}".format(snr) + time.sleep(ENUMERATE_WAIT_TIME) # Waiting for device to enumerate + + devices_before_programming = lister.get_device(get_all=True, vendor_id="1915", product_id="521F") + + return_code = subprocess.call("\"{nrfjprog}\" --program {image} --chiperase -r --snr {snr}" + .format(nrfjprog=nrfjprog, image=full_image_path, snr=snr), shell=True) + + assert return_code == 0, \ + "Nrfjprog could program image {} to board with serial number {}".format(full_image_path, snr) + + time.sleep(ENUMERATE_WAIT_TIME) # Waiting for device to enumerate + + devices_after_programming = lister.get_device(get_all=True, vendor_id="1915", product_id="521F") + + dfu_device = None + + for device in devices_after_programming: + match = False + for device_old in devices_before_programming: + if device.serial_number == device_old.serial_number: + match = True + break + if not match: + dfu_device = device + break + + assert dfu_device, "Device was programmed, but did not enumerate in {} seconds.".format(ENUMERATE_WAIT_TIME) + + port = dfu_device.get_first_available_com_port() + return port + + +def program_image_serial(context, nrfjprog, full_image_path, snr): + lister = DeviceLister() + + return_code = subprocess.call("\"{nrfjprog}\" --eraseall --snr {snr}" + .format(nrfjprog=nrfjprog,snr=snr), shell=True) + + assert return_code == 0, "Nrfjprog could not erase board with serial number {}".format(snr) + + return_code = subprocess.call("\"{nrfjprog}\" --program {image} --chiperase -r --snr {snr}" + .format(nrfjprog=nrfjprog, image=full_image_path, snr=snr), shell=True) + + assert return_code == 0, \ + "Nrfjprog could program image {} to board with serial number {}".format(full_image_path, snr) + + time.sleep(ENUMERATE_WAIT_TIME) # Waiting for device to enumerate + + snr_left_pad = snr + if (len(snr_left_pad)) < 12: + snr_left_pad = '0'*(12-len(snr_left_pad)) + snr_left_pad + + device = lister.get_device(get_all=False, serial_number=snr_left_pad) + devices = lister.enumerate() + + assert device, "Device was programmed, but did not enumerate in {} seconds.".format(ENUMERATE_WAIT_TIME) + + port = device.get_first_available_com_port() + return port + + +def program_image_ble(nrfjprog, full_image_path, snr): + return_code = subprocess.call("\"{nrfjprog}\" --eraseall --snr {snr}" + .format(nrfjprog=nrfjprog,snr=snr), shell=True) + assert return_code == 0, "Nrfjprog could not erase board with serial number {}".format(snr) + + return_code = subprocess.call("\"{nrfjprog}\" --program {image} --chiperase -r --snr {snr}" + .format(nrfjprog=nrfjprog, image=full_image_path, snr=snr), shell=True) + + assert return_code == 0, \ + "Nrfjprog could program image {} to board with serial number {}".format(full_image_path, snr) + +logger = logging.getLogger(__file__) + +STDOUT_TEXT_WAIT_TIME = 50 # Number of seconds to wait for expected output from stdout + +@given(u'the user wants to perform dfu {dfu_type}') +def step_impl(context, dfu_type): + runner = CliRunner() + context.runner = runner + args = ['dfu', dfu_type] + + context.args = args + +@given(u'using package {package}') +def step_impl(context, package): + full_package_path = resolve_hex_path(package) + context.args.extend(['-pkg', full_package_path]) + context.pkg = full_package_path + +@given(u'option {args}') +def step_impl(context, args): + context.args.extend(args.split(" ")) + +@given(u'nrfjprog {image} for {image_type} {board}') +def step_impl(context, image, image_type, board): + + full_image_path = resolve_hex_path(image) + + nrfjprog = find_nrfjprog("nrfjprog") + if nrfjprog == None: + nrfjprog = find_nrfjprog("nrfjprog.exe") + + assert nrfjprog, "nrfjprog is not installed" + + assert board in os.environ, \ + "Environment variable '{}' must be exported with JLink serial number".format(board) + + snr = str(int(os.environ[board])) # Remove zeros to the left. + + if image_type == "usb-serial": + port = program_image_usb_serial(context, nrfjprog, full_image_path, snr) + context.args.extend(['-p', port]) + context.p = port + elif image_type == "serial": + port = program_image_serial(context, nrfjprog, full_image_path, snr) + context.args.extend(['-p', port]) + context.p = port + elif image_type == 'ble': + program_image_ble(nrfjprog, full_image_path, snr) + else: + assert False, "Invalid dfu transport." + + + + +@then(u'perform dfu') +def step_impl(context): + result = context.runner.invoke(cli, context.args) + logger.debug("exit_code: %s, output: \'%s\'", result.exit_code, result.output) + assert result.exit_code == 0 diff --git a/tests/bdd/steps/genpkg_help_information_steps.py b/tests/bdd/steps/help_information_steps.py similarity index 87% rename from tests/bdd/steps/genpkg_help_information_steps.py rename to tests/bdd/steps/help_information_steps.py index 0ff8c44..a34da48 100644 --- a/tests/bdd/steps/genpkg_help_information_steps.py +++ b/tests/bdd/steps/help_information_steps.py @@ -71,3 +71,14 @@ def step_impl(context, stdout_text, exit_code): assert result.exit_code == int_as_text_to_int(exit_code) assert result.output != None assert result.output.find(stdout_text) >= 0 + +@then(u'output version is correct') +def step_impl(context): + assert "nrfutil_version" in os.environ, \ + "Environment variable 'nrfutil_version' must be exported" + version = os.environ["nrfutil_version"] + + result = context.runner.invoke(cli, context.args) + logger.debug("exit_code: %s, output: \'%s\'", result.exit_code, result.output) + assert result.output != None + assert result.output.find(version) >= 0