Skip to content

Add detected_board fixture #427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions test/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,42 @@
# otherwise use the software for commercial activities involving the Arduino
# software without disclosing the source code of your own applications. To purchase
# a commercial license, send an email to [email protected].
import collections
import os

from invoke.context import Context

Board = collections.namedtuple("Board", "address fqbn package architecture id core")


def running_on_ci():
"""
Returns whether the program is running on a CI environment
"""
val = os.getenv("APPVEYOR") or os.getenv("DRONE") or os.getenv("GITHUB_WORKFLOW")
return val is not None


def build_runner(cli_path, env, working_dir):
"""
Provide a wrapper around invoke's `run` API so that every test
will work in its own temporary folder.

Useful reference:
http://docs.pyinvoke.org/en/1.2/api/runners.html#invoke.runners.Result

:param cli_path: the path to the ``arduino-cli`` executable file.
:param env: a ``dict`` with the environment variables to use.
:param working_dir: the CWD where the command will be executed.

:returns a runner function with the mechanic to run an ``arduino-cli`` instance
with a given environment ``env`` in the directory ```working_dir`.
"""

def _run(cmd_string):
cli_full_line = "{} {}".format(cli_path, cmd_string)
run_context = Context()
with run_context.cd(working_dir):
return run_context.run(cli_full_line, echo=False, hide=True, warn=True, env=env)

return _run
77 changes: 66 additions & 11 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
# otherwise use the software for commercial activities involving the Arduino
# software without disclosing the source code of your own applications. To purchase
# a commercial license, send an email to [email protected].
import json
import os

import pytest
from invoke.context import Context

from .common import build_runner, Board


@pytest.fixture(scope="function")
Expand Down Expand Up @@ -50,23 +52,76 @@ def working_dir(tmpdir_factory):
@pytest.fixture(scope="function")
def run_command(pytestconfig, data_dir, downloads_dir, working_dir):
"""
Provide a wrapper around invoke's `run` API so that every test
will work in the same temporary folder.
Run the ``arduino-cli`` command to perform a the real test on the CLI.
"""
cli_path = os.path.join(pytestconfig.rootdir, "..", "arduino-cli")
env = {
"ARDUINO_DATA_DIR": data_dir,
"ARDUINO_DOWNLOADS_DIR": downloads_dir,
"ARDUINO_SKETCHBOOK_DIR": data_dir,
}

return build_runner(cli_path, env, working_dir)

Useful reference:
http://docs.pyinvoke.org/en/1.2/api/runners.html#invoke.runners.Result

@pytest.fixture(scope="session")
def _run_session_command(pytestconfig, tmpdir_factory, downloads_dir):
"""
Run the ``arduino-cli`` command to collect general metadata and store it in
a `session` scope for the tests.
"""
cli_path = os.path.join(pytestconfig.rootdir, "..", "arduino-cli")
data_dir = tmpdir_factory.mktemp("SessionDataDir")
env = {
"ARDUINO_DATA_DIR": data_dir,
"ARDUINO_DOWNLOADS_DIR": downloads_dir,
"ARDUINO_SKETCHBOOK_DIR": data_dir,
}
# it looks like the pyinvoke library has a few problems in dealing with the path
# object, so to avoid this issue we can convert them to str.
# Take a look at https://github.com/pyinvoke/invoke/issues/454 for more details.
working_dir = str(tmpdir_factory.mktemp("SessionTestWork"))

return build_runner(cli_path, env, working_dir)


@pytest.fixture(scope="session")
def detected_boards(_run_session_command):
"""This fixture provides a list of all the boards attached to the host.

This fixture will parse the JSON output of the ``arduino-cli board list --format json``
command to extract all the connected boards data.

:returns a list ``Board`` objects.
"""

result = _run_session_command("core update-index")
assert result.ok

result = _run_session_command("board list --format json")
assert result.ok

detected_boards = []

ports = json.loads(result.stdout)
assert isinstance(ports, list)
for port in ports:
boards = port.get('boards', [])
assert isinstance(boards, list)
for board in boards:
fqbn = board.get('FQBN')
package, architecture, _id = fqbn.split(":")
detected_boards.append(
Board(
address=port.get('address'),
fqbn=fqbn,
package=package,
architecture=architecture,
id=_id,
core="{}:{}".format(package, architecture)
)
)

def _run(cmd_string):
cli_full_line = "{} {}".format(cli_path, cmd_string)
run_context = Context()
with run_context.cd(working_dir):
return run_context.run(cli_full_line, echo=False, hide=True, warn=True, env=env)
assert len(detected_boards) >= 1, "There are no boards available for testing"

return _run
return detected_boards
68 changes: 17 additions & 51 deletions test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,7 @@ def test_compile_with_simple_sketch(run_command, data_dir):


@pytest.mark.skipif(running_on_ci(), reason="VMs have no serial ports")
def test_compile_and_compile_combo(run_command, data_dir):
# Init the environment explicitly
result = run_command("core update-index")
assert result.ok

# Install required core(s)
result = run_command("core install arduino:avr")
result = run_command("core install arduino:samd")
assert result.ok
def test_compile_and_compile_combo(run_command, detected_boards, data_dir):

# Create a test sketch
sketch_name = "CompileAndUploadIntegrationTest"
Expand All @@ -88,49 +80,23 @@ def test_compile_and_compile_combo(run_command, data_dir):
assert result.ok
assert "Sketch created in: {}".format(sketch_path) in result.stdout

#
# Build a list of detected boards to test, if any.
#
result = run_command("board list --format json")
assert result.ok

#
# The `board list --format json` returns a JSON that looks like to the following:
#
# [
# {
# "address": "/dev/cu.usbmodem14201",
# "protocol": "serial",
# "protocol_label": "Serial Port (USB)",
# "boards": [
# {
# "name": "Arduino NANO 33 IoT",
# "FQBN": "arduino:samd:nano_33_iot"
# }
# ]
# }
# ]

detected_boards = []

ports = json.loads(result.stdout)
assert isinstance(ports, list)
for port in ports:
boards = port.get('boards')
assert isinstance(boards, list)
for board in boards:
detected_boards.append(dict(address=port.get('address'), fqbn=board.get('FQBN')))

assert len(detected_boards) >= 1, "There are no boards available for testing"

# Build sketch for each detected board
for board in detected_boards:
log_file_name = "{fqbn}-compile.log".format(fqbn=board.get('fqbn').replace(":", "-"))

# Init the environment explicitly
result = run_command("core update-index")
assert result.ok

# Install required core(s)
result = run_command("core install {}".format(board.core))
assert result.ok

log_file_name = "{fqbn}-compile.log".format(fqbn=board.fqbn.replace(":", "-"))
log_file_path = os.path.join(data_dir, log_file_name)
command_log_flags = "--log-format json --log-file {} --log-level trace".format(log_file_path)
result = run_command("compile -b {fqbn} --upload -p {address} {sketch_path} {log_flags}".format(
fqbn=board.get('fqbn'),
address=board.get('address'),
fqbn=board.fqbn,
address=board.address,
sketch_path=sketch_path,
log_flags=command_log_flags
))
Expand All @@ -139,10 +105,10 @@ def test_compile_and_compile_combo(run_command, data_dir):
log_json = open(log_file_path, 'r')
json_log_lines = log_json.readlines()
expected_trace_sequence = [
"Compile {sketch} for {fqbn} started".format(sketch=sketch_path, fqbn=board.get('fqbn')),
"Compile {sketch} for {fqbn} successful".format(sketch=sketch_name, fqbn=board.get('fqbn')),
"Upload {sketch} on {fqbn} started".format(sketch=sketch_path, fqbn=board.get('fqbn')),
"Upload {sketch} on {fqbn} successful".format(sketch=sketch_name, fqbn=board.get('fqbn'))
"Compile {sketch} for {fqbn} started".format(sketch=sketch_path, fqbn=board.fqbn),
"Compile {sketch} for {fqbn} successful".format(sketch=sketch_name, fqbn=board.fqbn),
"Upload {sketch} on {fqbn} started".format(sketch=sketch_path, fqbn=board.fqbn),
"Upload {sketch} on {fqbn} successful".format(sketch=sketch_name, fqbn=board.fqbn)
]
assert is_message_sequence_in_json_log_traces(expected_trace_sequence, json_log_lines)

Expand Down