Skip to content

Commit

Permalink
Making jlink a proper fixture (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
thirtytwobits committed Jan 25, 2020
1 parent a1f30a6 commit 792d82a
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 214 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,15 @@
"getfixturedefs",
"globbing",
"gtest",
"hexfile",
"hwinfo",
"imgmath",
"inout",
"jlink",
"jtag",
"kwargs",
"levelno",
"loadfile",
"maxdepth",
"mkdir",
"mkdtemp",
Expand Down
7 changes: 0 additions & 7 deletions docs/lib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,6 @@ Nanaimo (library)
:members:
:show-inheritance:

***************************************
:mod:`nanaimo.builtin.gtest_over_jlink`
***************************************

.. automodule:: nanaimo.builtin.gtest_over_jlink
:members:
:show-inheritance:

*************************************
:mod:`nanaimo.connections`
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ pytest11 =
pytest_nanaimo_plugin_instr_bkprecision = nanaimo.instruments.bkprecision
pytest_nanaimo_plugin_instr_saleae = nanaimo.instruments.saleae
pytest_nanaimo_plugin_instr_ykush = nanaimo.instruments.ykush
pytest_nanaimo_plugin_jlink = nanaimo.instruments.jlink
pytest_nanaimo_plugin_display = nanaimo.display
pytest_nanaimo_plugin_gtest_over_jlink = nanaimo.builtin.gtest_over_jlink
console_scripts =
nait = nanaimo.cli:main

Expand Down
82 changes: 0 additions & 82 deletions src/nanaimo/builtin/gtest_over_jlink.py

This file was deleted.

3 changes: 3 additions & 0 deletions src/nanaimo/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,12 +667,15 @@ async def on_gather(self, args: nanaimo.Namespace) -> nanaimo.Artifacts:
+--------------+---------------------------+-------------------------------------------------------------------+
| key | type | Notes |
+==============+===========================+===================================================================+
| ``cmd`` | str | The command used to execute the subprocess. |
+--------------+---------------------------+-------------------------------------------------------------------+
| ``logfile`` | Optional[pathlib.Path] | A file containing stdout, stderr, and test logs |
+--------------+---------------------------+-------------------------------------------------------------------+
"""
artifacts = nanaimo.Artifacts()

cmd = self.on_construct_command(args, artifacts)
setattr(artifacts, 'cmd', cmd)

logfile_handler = None # type: typing.Optional[logging.FileHandler]

Expand Down
151 changes: 105 additions & 46 deletions src/nanaimo/instruments/jlink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,59 +17,118 @@
# @&&&&&&&&&&%#######&@%
# nanaimo (@&&&&####@@*
#
import asyncio
import logging
import pathlib
import tempfile
import textwrap
import typing

import nanaimo
import nanaimo.fixtures


class ProgramUploaderJLink:
class ProgramUploader(nanaimo.fixtures.SubprocessFixture):
"""
Async manager of a JLinkExe subprocess.
JLinkExe fixture that loads a given hexfile to a target device.
"""

fixture_name = 'nanaimo_jlink_upload'
argument_prefix = 'jlink_up'

script_template = textwrap.dedent('''
f
device {device}
speed {speed}
si {serial_interface}
connect
hwinfo
st
h
moe
rx {reset_delay_millis}
loadfile {hexfile}
r
qc
''').lstrip()

@classmethod
def on_visit_test_arguments(cls, arguments: nanaimo.Arguments) -> None:
arguments.add_argument('--base_path',
default=str(pathlib.Path().cwd()),
help='The folder under which to search for jlink scripts.')
arguments.add_argument('--jlink-scripts',
help='A globbing pattern to collect jlink scripts for flashing tests.')
arguments.add_argument('--upload-timeout-seconds',
default='20',
type=float,
help='''The upload will be killed and an error returned
after waiting for the upload to complete for this
amount of time.''')

def __init__(self,
jlink_executable: pathlib.Path = pathlib.Path('JLinkExe'),
extra_arguments: typing.Optional[typing.List[str]] = None):
self._logger = logging.getLogger(__name__)
self._jlink_exe = jlink_executable
self._extra_arguments = extra_arguments

async def upload(self, jlink_script: pathlib.Path) -> int:
cmd = '{} -CommanderScript {}'.format(self._jlink_exe, jlink_script)
if self._extra_arguments is not None:
cmd += ' ' + ' '.join(self._extra_arguments)

self._logger.info('starting upload: %s', cmd)
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
) # type: asyncio.subprocess.Process

stdout, stderr = await proc.communicate()

self._logger.info('%s exited with %i', cmd, proc.returncode)

if stdout:
self._logger.debug(stdout.decode())
if stderr:
self._logger.error(stderr.decode())

return proc.returncode
super().on_visit_test_arguments(arguments)
arguments.add_argument('--exe',
default='JLinkExe',
help='A path to the jlink commander executable.')
arguments.add_argument('--hexfile',
help='Path to a hex file to upload.')
arguments.add_argument('--device',
help='The target device.')
arguments.add_argument('--interface-speed-khz',
help='The interface speed in kilohertz',
default='default')
arguments.add_argument('--serial-interface',
help='The serial interface to use (e.g. swd, jtag)',
default='swd')
arguments.add_argument('--reset-delay-millis',
default=400,
help='Milliseconds to wait after reset before starting to load the new image.')
arguments.add_argument('--script',
default=None,
help=textwrap.dedent('''
Path to a jlink script to use. If not provided then an script will be generated using an
internal default. The following template parameters are supported but optional:
{device} = The target device.
{speed} = The interface speed in kilohertz
{serial_interface} = The serial interface to use.
{reset_delay_millis} = An optional delay to include after reset.
{hexfile} = The hexfile to upload.
''').lstrip())

def on_construct_command(self, arguments: nanaimo.Namespace, inout_artifacts: nanaimo.Artifacts) -> str:
"""
Construct a command to upload (eke "flash") a firmware to a target device using Segger's JLink
Commander program with the assumption that a Segger debug probe like the JLink is attached to the
system.
+----------------+---------------------------------------+--------------------------------------------------+
| **Artifacts** |
| |
+----------------+---------------------------------------+--------------------------------------------------+
| key | type | Notes |
+================+=======================================+==================================================+
| ``tmpfile`` | Optional[tempfile.NamedTemporaryFile] | A temporary file containing the jlink script to |
| | | execute with expanded template parameters. |
+----------------+---------------------------------------+--------------------------------------------------+
| ``scriptfile`` | Optional[str] | A path to a script file used to invoke jlink. |
| | | This file can contain template parameters. |
+----------------+---------------------------------------+--------------------------------------------------+
"""
script_file_path = self.get_arg_covariant(arguments, 'script')
tmpfile = tempfile.NamedTemporaryFile(mode='w')
setattr(inout_artifacts, 'tmpfile', tmpfile)

if script_file_path is None:
# keep around for as long as the command exists.
setattr(inout_artifacts, 'scriptfile', None)
template = self.script_template

else:
setattr(inout_artifacts, 'scriptfile', script_file_path)
with open(script_file_path, 'r') as user_script_file:
template = user_script_file.read()

# poor man's templating
expanded_script_file = template.format(
hexfile=self.get_arg_covariant_or_fail(arguments, 'hexfile'),
device=self.get_arg_covariant_or_fail(arguments, 'device'),
speed=self.get_arg_covariant_or_fail(arguments, 'interface-speed-khz'),
serial_interface=self.get_arg_covariant_or_fail(arguments, 'serial-interface'),
reset_delay_millis=str(self.get_arg_covariant_or_fail(arguments, 'reset-delay-millis'))
)

with open(tmpfile.name, 'w') as tempfile_handle:
tempfile_handle.write(expanded_script_file)

return '{} -CommanderScript {}'.format(self.get_arg_covariant(arguments, 'exe', 'JLinkExe'),
tmpfile.name)


def pytest_nanaimo_fixture_type() -> typing.Type['nanaimo.fixtures.Fixture']:
return ProgramUploader
2 changes: 1 addition & 1 deletion src/nanaimo/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
# nanaimo (@&&&&####@@*
#

__version__ = '0.1.0'
__version__ = '0.2.0'

__license__ = 'MIT'
9 changes: 4 additions & 5 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,13 @@ def mock_JLinkExe(request): # type: ignore


@pytest.fixture
def s32K144_jlink_script(request): # type: ignore
jlink_file = pathlib.Path('test_math_saturation_loadfile_swd').with_suffix('.jlink')
return pathlib.Path(material.__file__).parent / jlink_file
def test_build_config_hex(request): # type: ignore
return pathlib.Path(material.__file__).parent / pathlib.Path('test_build_config').with_suffix('.hex')


@pytest.fixture
def s32K144_jlink_scripts(request): # type: ignore
return pathlib.Path(material.__file__).parent.glob('*.jlink')
def test_jlink_template(request): # type: ignore
return pathlib.Path(material.__file__).parent / pathlib.Path('test').with_suffix('.jlink')


@pytest.fixture
Expand Down
3 changes: 3 additions & 0 deletions test/material/test.jlink
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
device {device}
si {serial_interface}
loadfile {hexfile}
14 changes: 0 additions & 14 deletions test/material/test_build_config_loadfile_swd.jlink

This file was deleted.

14 changes: 0 additions & 14 deletions test/material/test_math_saturation_loadfile_swd.jlink

This file was deleted.

0 comments on commit 792d82a

Please sign in to comment.