1# Copyright (c) 2018 Foundries.io
2# Copyright (c) 2020 Nordic Semiconductor ASA
3#
4# SPDX-License-Identifier: Apache-2.0
5
6import argparse
7import functools
8import io
9import os
10from pathlib import Path
11import shlex
12import shutil
13import typing
14from unittest.mock import patch, call
15
16import pytest
17
18from runners.nrfjprog import NrfJprogBinaryRunner
19from runners.nrfutil import NrfUtilBinaryRunner
20from conftest import RC_KERNEL_HEX
21
22
23#
24# Test values
25#
26
27TEST_DEF_SNR = 'test-default-serial-number'  # for mocking user input
28TEST_OVR_SNR = 'test-override-serial-number'
29
30TEST_TOOL_OPT = '--ip 192.168.1.10'
31TEST_TOOL_OPT_L = shlex.split(TEST_TOOL_OPT)
32
33# nRF53 flashing is special in that we have different results
34# depending on the input hex file. For that reason, we test it with
35# real hex files.
36TEST_DIR = Path(__file__).parent / 'nrf'
37NRF5340_APP_ONLY_HEX = os.fspath(TEST_DIR / 'nrf5340_app_only.hex')
38NRF5340_NET_ONLY_HEX = os.fspath(TEST_DIR / 'nrf5340_net_only.hex')
39NRF5340_APP_AND_NET_HEX = os.fspath(TEST_DIR / 'nrf5340_app_and_net.hex')
40
41CLASS_MAP = {'nrfjprog': NrfJprogBinaryRunner, 'nrfutil': NrfUtilBinaryRunner}
42
43#
44# A dictionary mapping test cases to expected results.
45#
46# The keys are TC objects.
47#
48# The values are usually tool commands we expect to be executed for
49# each test case. Verification is done by mocking the check_call()
50# ZephyrBinaryRunner method which is used to run the commands.
51#
52# Values can also be callables which take a tmpdir and return the
53# expected commands. This is needed for nRF53 testing.
54#
55
56class TC(typing.NamedTuple):    # 'TestCase'
57    # NRF51, NRF52, etc.
58    family: str
59
60    # 'APP', 'NET', 'APP+NET', or None.
61    coprocessor: typing.Optional[str]
62
63    # Run a recover command first if True
64    recover: bool
65
66    # Use --reset instead of --pinreset if True
67    softreset: bool
68
69    # --snr TEST_OVR_SNR if True, --snr TEST_DEF_SNR if False
70    snr: bool
71
72    # --chiperase if True,
73    # --sectorerase if False (or --sectoranduicrerase on nRF52)
74    erase: bool
75
76    # --tool-opt TEST_TOOL_OPT if True
77    tool_opt: bool
78
79EXPECTED_MAP = {'nrfjprog': 0, 'nrfutil': 1}
80EXPECTED_RESULTS = {
81
82    # -------------------------------------------------------------------------
83    # NRF51
84    #
85    #  family          CP    recov  soft   snr    erase  tool_opt
86    TC('NRF51_FAMILY', None, False, False, False, False, False):
87    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--sectorerase', '--verify', '-f', 'NRF51',
88      '--snr', TEST_DEF_SNR],
89     ['nrfjprog', '--pinreset', '-f', 'NRF51', '--snr', TEST_DEF_SNR]),
90     (TEST_DEF_SNR, None)),
91
92    TC('NRF51_FAMILY', None, False, False, False, True, False):
93    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--chiperase', '--verify', '-f', 'NRF51',
94      '--snr', TEST_DEF_SNR],
95     ['nrfjprog', '--pinreset', '-f', 'NRF51', '--snr', TEST_DEF_SNR]),
96     (TEST_DEF_SNR, None)),
97
98    TC('NRF51_FAMILY', None, False, False, True, False, False):
99    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--sectorerase', '--verify', '-f', 'NRF51',
100      '--snr', TEST_OVR_SNR],
101     ['nrfjprog', '--pinreset', '-f', 'NRF51', '--snr', TEST_OVR_SNR]),
102     (TEST_OVR_SNR, None)),
103
104    TC('NRF51_FAMILY', None, False, True, False, False, False):
105    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--sectorerase', '--verify', '-f', 'NRF51',
106      '--snr', TEST_DEF_SNR],
107     ['nrfjprog', '--reset', '-f', 'NRF51', '--snr', TEST_DEF_SNR]),
108     (TEST_DEF_SNR, None)),
109
110    TC('NRF51_FAMILY', None, True, False, False, False, False):
111    ((['nrfjprog', '--recover', '-f', 'NRF51', '--snr', TEST_DEF_SNR],
112     ['nrfjprog', '--program', RC_KERNEL_HEX, '--sectorerase', '--verify', '-f', 'NRF51',
113      '--snr', TEST_DEF_SNR],
114     ['nrfjprog', '--pinreset', '-f', 'NRF51', '--snr', TEST_DEF_SNR]),
115     (TEST_DEF_SNR, None)),
116
117    TC('NRF51_FAMILY', None, True, True, True, True, False):
118    ((['nrfjprog', '--recover', '-f', 'NRF51', '--snr', TEST_OVR_SNR],
119     ['nrfjprog', '--program', RC_KERNEL_HEX, '--chiperase', '--verify', '-f', 'NRF51',
120      '--snr', TEST_OVR_SNR],
121     ['nrfjprog', '--reset', '-f', 'NRF51', '--snr', TEST_OVR_SNR]),
122     (TEST_OVR_SNR, None)),
123
124    TC('NRF51_FAMILY', None, True, True, True, True, True):
125    ((['nrfjprog', '--recover', '-f', 'NRF51', '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L,
126     ['nrfjprog', '--program', RC_KERNEL_HEX, '--chiperase', '--verify', '-f', 'NRF51',
127      '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L,
128     ['nrfjprog', '--reset', '-f', 'NRF51', '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L),
129     (TEST_OVR_SNR, None)),
130
131    # -------------------------------------------------------------------------
132    # NRF52
133    #
134    #  family          CP    recov  soft   snr    erase  tool_opt
135    TC('NRF52_FAMILY', None, False, False, False, False, False):
136    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--sectoranduicrerase',
137      '--verify', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
138     ['nrfjprog', '--pinresetenable', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
139     ['nrfjprog', '--pinreset', '-f', 'NRF52', '--snr', TEST_DEF_SNR]),
140     (TEST_DEF_SNR, None)),
141
142    TC('NRF52_FAMILY', None, False, False, False, True, False):
143    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--chiperase', '--verify', '-f', 'NRF52',
144      '--snr', TEST_DEF_SNR],
145     ['nrfjprog', '--pinresetenable', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
146     ['nrfjprog', '--pinreset', '-f', 'NRF52', '--snr', TEST_DEF_SNR]),
147     (TEST_DEF_SNR, None)),
148
149    TC('NRF52_FAMILY', None, False, False, True, False, False):
150    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--sectoranduicrerase',
151      '--verify', '-f', 'NRF52', '--snr', TEST_OVR_SNR],
152     ['nrfjprog', '--pinresetenable', '-f', 'NRF52', '--snr', TEST_OVR_SNR],
153     ['nrfjprog', '--pinreset', '-f', 'NRF52', '--snr', TEST_OVR_SNR]),
154     (TEST_OVR_SNR, None)),
155
156    TC('NRF52_FAMILY', None, False, True, False, False, False):
157    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--sectoranduicrerase',
158      '--verify', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
159     ['nrfjprog', '--reset', '-f', 'NRF52', '--snr', TEST_DEF_SNR]),
160     (TEST_DEF_SNR, None)),
161
162    TC('NRF52_FAMILY', None, True, False, False, False, False):
163    ((['nrfjprog', '--recover', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
164     ['nrfjprog', '--program', RC_KERNEL_HEX, '--sectoranduicrerase',
165      '--verify', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
166     ['nrfjprog', '--pinresetenable', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
167     ['nrfjprog', '--pinreset', '-f', 'NRF52', '--snr', TEST_DEF_SNR]),
168     (TEST_DEF_SNR, None)),
169
170    TC('NRF52_FAMILY', None, True, True, True, True, False):
171    ((['nrfjprog', '--recover', '-f', 'NRF52', '--snr', TEST_OVR_SNR],
172     ['nrfjprog', '--program', RC_KERNEL_HEX, '--chiperase', '--verify', '-f', 'NRF52',
173      '--snr', TEST_OVR_SNR],
174     ['nrfjprog', '--reset', '-f', 'NRF52', '--snr', TEST_OVR_SNR]),
175     (TEST_OVR_SNR, None)),
176
177    TC('NRF52_FAMILY', None, True, True, True, True, True):
178    ((['nrfjprog', '--recover', '-f', 'NRF52', '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L,
179     ['nrfjprog', '--program', RC_KERNEL_HEX, '--chiperase', '--verify', '-f', 'NRF52',
180      '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L,
181     ['nrfjprog', '--reset', '-f', 'NRF52', '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L),
182     (TEST_OVR_SNR, None)),
183
184    # -------------------------------------------------------------------------
185    # NRF53 APP only
186    #
187    #  family          CP     recov  soft   snr    erase  tool_opt
188    TC('NRF53_FAMILY', 'APP', False, False, False, False, False):
189    ((['nrfjprog', '--program', NRF5340_APP_ONLY_HEX, '--sectorerase',
190      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_APPLICATION', '--snr', TEST_DEF_SNR],
191     ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_DEF_SNR]),
192     (TEST_DEF_SNR, None)),
193
194    TC('NRF53_FAMILY', 'APP', False, False, False, True, False):
195    ((['nrfjprog', '--program', NRF5340_APP_ONLY_HEX, '--chiperase',
196      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_APPLICATION', '--snr', TEST_DEF_SNR],
197     ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_DEF_SNR]),
198     (TEST_DEF_SNR, None)),
199
200    TC('NRF53_FAMILY', 'APP', False, False, True, False, False):
201    ((['nrfjprog', '--program', NRF5340_APP_ONLY_HEX, '--sectorerase',
202      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_APPLICATION', '--snr', TEST_OVR_SNR],
203     ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_OVR_SNR]),
204     (TEST_OVR_SNR, None)),
205
206    TC('NRF53_FAMILY', 'APP', False, True, False, False, False):
207    ((['nrfjprog', '--program', NRF5340_APP_ONLY_HEX, '--sectorerase',
208      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_APPLICATION', '--snr', TEST_DEF_SNR],
209     ['nrfjprog', '--reset', '-f', 'NRF53', '--snr', TEST_DEF_SNR]),
210     (TEST_DEF_SNR, None)),
211
212    TC('NRF53_FAMILY', 'APP', True, False, False, False, False):
213    ((['nrfjprog', '--recover', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK',
214      '--snr', TEST_DEF_SNR],
215     ['nrfjprog', '--recover', '-f', 'NRF53', '--snr', TEST_DEF_SNR],
216     ['nrfjprog', '--program', NRF5340_APP_ONLY_HEX, '--sectorerase',
217      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_APPLICATION', '--snr', TEST_DEF_SNR],
218     ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_DEF_SNR]),
219     (TEST_DEF_SNR, None)),
220
221    TC('NRF53_FAMILY', 'APP', True, True, True, True, False):
222    ((['nrfjprog', '--recover', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK',
223      '--snr', TEST_OVR_SNR],
224     ['nrfjprog', '--recover', '-f', 'NRF53', '--snr', TEST_OVR_SNR],
225     ['nrfjprog', '--program', NRF5340_APP_ONLY_HEX, '--chiperase',
226      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_APPLICATION', '--snr', TEST_OVR_SNR],
227     ['nrfjprog', '--reset', '-f', 'NRF53', '--snr', TEST_OVR_SNR]),
228     (TEST_OVR_SNR, None)),
229
230    # -------------------------------------------------------------------------
231    # NRF53 NET only
232    #
233    #  family          CP     recov  soft   snr    erase  tool_opt
234    TC('NRF53_FAMILY', 'NET', False, False, False, False, False):
235    ((['nrfjprog', '--program', NRF5340_NET_ONLY_HEX, '--sectorerase',
236      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK', '--snr', TEST_DEF_SNR],
237     ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_DEF_SNR]),
238     (TEST_DEF_SNR, None)),
239
240    TC('NRF53_FAMILY', 'NET', False, False, False, True, False):
241    ((['nrfjprog', '--program', NRF5340_NET_ONLY_HEX, '--chiperase',
242      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK', '--snr', TEST_DEF_SNR],
243     ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_DEF_SNR]),
244     (TEST_DEF_SNR, None)),
245
246    TC('NRF53_FAMILY', 'NET', False, False, True, False, False):
247    ((['nrfjprog', '--program', NRF5340_NET_ONLY_HEX, '--sectorerase',
248      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK', '--snr', TEST_OVR_SNR],
249     ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_OVR_SNR]),
250     (TEST_OVR_SNR, None)),
251
252    TC('NRF53_FAMILY', 'NET', False, True, False, False, False):
253    ((['nrfjprog', '--program', NRF5340_NET_ONLY_HEX, '--sectorerase',
254      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK', '--snr', TEST_DEF_SNR],
255     ['nrfjprog', '--reset', '-f', 'NRF53', '--snr', TEST_DEF_SNR]),
256     (TEST_DEF_SNR, None)),
257
258    TC('NRF53_FAMILY', 'NET', True, False, False, False, False):
259    ((['nrfjprog', '--recover', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK',
260      '--snr', TEST_DEF_SNR],
261     ['nrfjprog', '--recover', '-f', 'NRF53', '--snr', TEST_DEF_SNR],
262     ['nrfjprog', '--program', NRF5340_NET_ONLY_HEX, '--sectorerase',
263      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK', '--snr', TEST_DEF_SNR],
264     ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_DEF_SNR]),
265     (TEST_DEF_SNR, None)),
266
267    TC('NRF53_FAMILY', 'NET', True, True, True, True, False):
268    ((['nrfjprog', '--recover', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK',
269      '--snr', TEST_OVR_SNR],
270     ['nrfjprog', '--recover', '-f', 'NRF53', '--snr', TEST_OVR_SNR],
271     ['nrfjprog', '--program', NRF5340_NET_ONLY_HEX, '--chiperase',
272      '--verify', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK', '--snr', TEST_OVR_SNR],
273     ['nrfjprog', '--reset', '-f', 'NRF53', '--snr', TEST_OVR_SNR]),
274     (TEST_OVR_SNR, None)),
275
276    # -------------------------------------------------------------------------
277    # NRF53 APP+NET
278    #
279    #  family          CP         recov  soft   snr    erase  tool_opt
280    TC('NRF53_FAMILY', 'APP+NET', False, False, False, False, False):
281    ((lambda tmpdir, infile: \
282        (['nrfjprog',
283          '--program',
284          os.fspath(tmpdir / 'GENERATED_CP_NETWORK_' + Path(infile).name),
285          '--sectorerase', '--verify', '-f', 'NRF53',
286          '--coprocessor', 'CP_NETWORK', '--snr', TEST_DEF_SNR],
287         ['nrfjprog',
288          '--program',
289          os.fspath(tmpdir / 'GENERATED_CP_APPLICATION_' + Path(infile).name),
290          '--sectorerase', '--verify', '-f', 'NRF53',
291          '--coprocessor', 'CP_APPLICATION', '--snr', TEST_DEF_SNR],
292         ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_DEF_SNR])),
293     (TEST_DEF_SNR, None)),
294
295    TC('NRF53_FAMILY', 'APP+NET', False, False, False, True, False):
296    ((lambda tmpdir, infile: \
297        (['nrfjprog',
298          '--program',
299          os.fspath(tmpdir / 'GENERATED_CP_NETWORK_' + Path(infile).name),
300          '--chiperase', '--verify', '-f', 'NRF53',
301          '--coprocessor', 'CP_NETWORK', '--snr', TEST_DEF_SNR],
302         ['nrfjprog',
303          '--program',
304          os.fspath(tmpdir / 'GENERATED_CP_APPLICATION_' + Path(infile).name),
305          '--chiperase', '--verify', '-f', 'NRF53',
306          '--coprocessor', 'CP_APPLICATION', '--snr', TEST_DEF_SNR],
307         ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_DEF_SNR])),
308     (TEST_DEF_SNR, None)),
309
310    TC('NRF53_FAMILY', 'APP+NET', False, False, True, False, False):
311    ((lambda tmpdir, infile: \
312        (['nrfjprog',
313          '--program',
314          os.fspath(tmpdir / 'GENERATED_CP_NETWORK_' + Path(infile).name),
315          '--sectorerase', '--verify', '-f', 'NRF53',
316          '--coprocessor', 'CP_NETWORK', '--snr', TEST_OVR_SNR],
317         ['nrfjprog',
318          '--program',
319          os.fspath(tmpdir / 'GENERATED_CP_APPLICATION_' + Path(infile).name),
320          '--sectorerase', '--verify', '-f', 'NRF53',
321          '--coprocessor', 'CP_APPLICATION', '--snr', TEST_OVR_SNR],
322         ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_OVR_SNR])),
323     (TEST_OVR_SNR, None)),
324
325    TC('NRF53_FAMILY', 'APP+NET', False, True, False, False, False):
326    ((lambda tmpdir, infile: \
327        (['nrfjprog',
328          '--program',
329          os.fspath(tmpdir / 'GENERATED_CP_NETWORK_' + Path(infile).name),
330          '--sectorerase', '--verify', '-f', 'NRF53',
331          '--coprocessor', 'CP_NETWORK', '--snr', TEST_DEF_SNR],
332         ['nrfjprog',
333          '--program',
334          os.fspath(tmpdir / 'GENERATED_CP_APPLICATION_' + Path(infile).name),
335          '--sectorerase', '--verify', '-f', 'NRF53',
336          '--coprocessor', 'CP_APPLICATION', '--snr', TEST_DEF_SNR],
337         ['nrfjprog', '--reset', '-f', 'NRF53', '--snr', TEST_DEF_SNR])),
338     (TEST_DEF_SNR, None)),
339
340    TC('NRF53_FAMILY', 'APP+NET', True, False, False, False, False):
341    ((lambda tmpdir, infile: \
342        (['nrfjprog', '--recover', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK',
343          '--snr', TEST_DEF_SNR],
344         ['nrfjprog', '--recover', '-f', 'NRF53', '--snr', TEST_DEF_SNR],
345         ['nrfjprog',
346          '--program',
347          os.fspath(tmpdir / 'GENERATED_CP_NETWORK_' + Path(infile).name),
348          '--sectorerase', '--verify', '-f', 'NRF53',
349          '--coprocessor', 'CP_NETWORK', '--snr', TEST_DEF_SNR],
350         ['nrfjprog',
351          '--program',
352          os.fspath(tmpdir / 'GENERATED_CP_APPLICATION_' + Path(infile).name),
353          '--sectorerase', '--verify', '-f', 'NRF53',
354          '--coprocessor', 'CP_APPLICATION', '--snr', TEST_DEF_SNR],
355         ['nrfjprog', '--pinreset', '-f', 'NRF53', '--snr', TEST_DEF_SNR])),
356     (TEST_DEF_SNR, None)),
357
358    TC('NRF53_FAMILY', 'APP+NET', True, True, True, True, False):
359    ((lambda tmpdir, infile: \
360        (['nrfjprog', '--recover', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK',
361          '--snr', TEST_OVR_SNR],
362         ['nrfjprog', '--recover', '-f', 'NRF53', '--snr', TEST_OVR_SNR],
363         ['nrfjprog',
364          '--program',
365          os.fspath(tmpdir / 'GENERATED_CP_NETWORK_' + Path(infile).name),
366          '--chiperase', '--verify', '-f', 'NRF53',
367          '--coprocessor', 'CP_NETWORK', '--snr', TEST_OVR_SNR],
368         ['nrfjprog',
369          '--program',
370          os.fspath(tmpdir / 'GENERATED_CP_APPLICATION_' + Path(infile).name),
371          '--chiperase', '--verify', '-f', 'NRF53',
372          '--coprocessor', 'CP_APPLICATION', '--snr', TEST_OVR_SNR],
373         ['nrfjprog', '--reset', '-f', 'NRF53', '--snr', TEST_OVR_SNR])),
374     (TEST_OVR_SNR, None)),
375
376    TC('NRF53_FAMILY', 'APP+NET', True, True, True, True, True):
377    ((lambda tmpdir, infile: \
378        (['nrfjprog', '--recover', '-f', 'NRF53', '--coprocessor', 'CP_NETWORK',
379          '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L,
380         ['nrfjprog', '--recover', '-f', 'NRF53', '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L,
381         ['nrfjprog',
382          '--program',
383          os.fspath(tmpdir / 'GENERATED_CP_NETWORK_' + Path(infile).name),
384          '--chiperase', '--verify', '-f', 'NRF53',
385          '--coprocessor', 'CP_NETWORK', '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L,
386         ['nrfjprog',
387          '--program',
388          os.fspath(tmpdir / 'GENERATED_CP_APPLICATION_' + Path(infile).name),
389          '--chiperase', '--verify', '-f', 'NRF53',
390          '--coprocessor', 'CP_APPLICATION', '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L,
391         ['nrfjprog', '--reset', '-f', 'NRF53', '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L)),
392     (TEST_OVR_SNR, None)),
393
394
395    # -------------------------------------------------------------------------
396    # NRF91
397    #
398    #  family          CP    recov  soft   snr    erase  tool_opt
399    TC('NRF91_FAMILY', None, False, False, False, False, False):
400    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--sectorerase', '--verify', '-f', 'NRF91',
401      '--snr', TEST_DEF_SNR],
402     ['nrfjprog', '--pinreset', '-f', 'NRF91', '--snr', TEST_DEF_SNR]),
403     (TEST_DEF_SNR, None)),
404
405    TC('NRF91_FAMILY', None, False, False, False, True, False):
406    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--chiperase', '--verify', '-f', 'NRF91',
407      '--snr', TEST_DEF_SNR],
408     ['nrfjprog', '--pinreset', '-f', 'NRF91', '--snr', TEST_DEF_SNR]),
409     (TEST_DEF_SNR, None)),
410
411    TC('NRF91_FAMILY', None, False, False, True, False, False):
412    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--sectorerase', '--verify', '-f', 'NRF91',
413      '--snr', TEST_OVR_SNR],
414     ['nrfjprog', '--pinreset', '-f', 'NRF91', '--snr', TEST_OVR_SNR]),
415     (TEST_OVR_SNR, None)),
416
417    TC('NRF91_FAMILY', None, False, True, False, False, False):
418    ((['nrfjprog', '--program', RC_KERNEL_HEX, '--sectorerase', '--verify', '-f', 'NRF91',
419      '--snr', TEST_DEF_SNR],
420     ['nrfjprog', '--reset', '-f', 'NRF91', '--snr', TEST_DEF_SNR]),
421     (TEST_DEF_SNR, None)),
422
423    TC('NRF91_FAMILY', None, True, False, False, False, False):
424    ((['nrfjprog', '--recover', '-f', 'NRF91', '--snr', TEST_DEF_SNR],
425     ['nrfjprog', '--program', RC_KERNEL_HEX, '--sectorerase', '--verify', '-f', 'NRF91',
426      '--snr', TEST_DEF_SNR],
427     ['nrfjprog', '--pinreset', '-f', 'NRF91', '--snr', TEST_DEF_SNR]),
428     (TEST_DEF_SNR, None)),
429
430    TC('NRF91_FAMILY', None, True, True, True, True, False):
431    ((['nrfjprog', '--recover', '-f', 'NRF91', '--snr', TEST_OVR_SNR],
432     ['nrfjprog', '--program', RC_KERNEL_HEX, '--chiperase', '--verify', '-f', 'NRF91',
433      '--snr', TEST_OVR_SNR],
434     ['nrfjprog', '--reset', '-f', 'NRF91', '--snr', TEST_OVR_SNR]),
435     (TEST_OVR_SNR, None)),
436
437    TC('NRF91_FAMILY', None, True, True, True, True, True):
438    ((['nrfjprog', '--recover', '-f', 'NRF91', '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L,
439     ['nrfjprog', '--program', RC_KERNEL_HEX, '--chiperase', '--verify', '-f', 'NRF91',
440      '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L,
441     ['nrfjprog', '--reset', '-f', 'NRF91', '--snr', TEST_OVR_SNR] + TEST_TOOL_OPT_L),
442     (TEST_OVR_SNR, None)),
443}
444
445#
446# Monkey-patches
447#
448
449def get_board_snr_patch(glob):
450    return TEST_DEF_SNR
451
452def require_patch(cur_tool, program):
453    assert cur_tool == program
454
455os_path_isfile = os.path.isfile
456
457def os_path_isfile_patch(filename):
458    if filename == RC_KERNEL_HEX:
459        return True
460    return os_path_isfile(filename)
461
462#
463# Test functions.
464#
465# These are white box tests that rely on the above monkey-patches.
466#
467
468def id_fn(test_case):
469    if test_case.coprocessor is None:
470        cp = ''
471    else:
472        cp = f'-{test_case.coprocessor}'
473    s = 'soft_reset' if test_case.softreset else 'pin_reset'
474    sn = 'default_snr' if test_case.snr else 'override_snr'
475    e = 'chip_erase' if test_case.erase else 'sector[anduicr]_erase'
476    r = 'recover' if test_case.recover else 'no_recover'
477    t = 'tool_opt' if test_case.tool_opt else 'no_tool_opt'
478
479    return f'{test_case.family[:5]}{cp}-{s}-{sn}-{e}-{r}-{t}'
480
481def fix_up_runner_config(test_case, runner_config, tmpdir):
482    # Helper that adjusts the common runner_config fixture for our
483    # nRF-specific tests.
484
485    to_replace = {}
486
487    # Provide a skeletal zephyr/.config file to use as the runner's
488    # BuildConfiguration.
489    zephyr = tmpdir / 'zephyr'
490    zephyr.mkdir()
491    dotconfig = os.fspath(zephyr / '.config')
492    with open(dotconfig, 'w') as f:
493        f.write(f'''
494CONFIG_SOC_SERIES_{test_case.family[:5]}X=y
495''')
496    to_replace['build_dir'] = tmpdir
497
498    if test_case.family != 'NRF53_FAMILY':
499        return runner_config._replace(**to_replace)
500
501    if test_case.coprocessor == 'APP':
502        to_replace['hex_file'] = NRF5340_APP_ONLY_HEX
503    elif test_case.coprocessor == 'NET':
504        to_replace['hex_file'] = NRF5340_NET_ONLY_HEX
505    elif test_case.coprocessor == 'APP+NET':
506        # Since the runner is going to generate files next to its input
507        # file, we need to stash a copy in a tmpdir it can use.
508        outfile = tmpdir / Path(NRF5340_APP_AND_NET_HEX).name
509        shutil.copyfile(NRF5340_APP_AND_NET_HEX, outfile)
510        to_replace['hex_file'] = os.fspath(outfile)
511    else:
512        assert False, f'bad test case {test_case}'
513
514    return runner_config._replace(**to_replace)
515
516def check_expected(tool, test_case, check_fn, get_snr, tmpdir, runner_config):
517
518    expected = EXPECTED_RESULTS[test_case][EXPECTED_MAP[tool]]
519    if tool == 'nrfutil':
520        assert len(check_fn.call_args_list) == 1
521        assert len(check_fn.call_args_list[0].args) == 1
522        # Extract filename
523        nrfutil_args = check_fn.call_args_list[0].args[0]
524        tmpfile = nrfutil_args[nrfutil_args.index('--batch-path') + 1]
525        cmds = (['nrfutil', '--json', 'device', 'x-execute-batch', '--batch-path',
526                 tmpfile, '--serial-number', expected[0]],)
527        call_args = [call(nrfutil_args)]
528    else:
529        cmds = expected
530        call_args = check_fn.call_args_list
531
532    if callable(cmds):
533        assert (call_args ==
534                [call(x) for x in cmds(tmpdir, runner_config.hex_file)])
535    else:
536        assert call_args == [call(x) for x in cmds]
537
538    if not test_case.snr:
539        get_snr.assert_called_once_with('*')
540    else:
541        get_snr.assert_not_called()
542
543@pytest.mark.parametrize('tool', ["nrfjprog","nrfutil"])
544@pytest.mark.parametrize('test_case', EXPECTED_RESULTS.keys(), ids=id_fn)
545@patch('runners.core.ZephyrBinaryRunner.require')
546@patch('runners.nrfjprog.NrfBinaryRunner.get_board_snr',
547       side_effect=get_board_snr_patch)
548@patch('runners.nrfutil.subprocess.Popen')
549@patch('runners.nrfjprog.NrfBinaryRunner.check_call')
550def test_init(check_call, popen, get_snr, require, tool, test_case,
551              runner_config, tmpdir):
552    popen.return_value.__enter__.return_value.stdout = io.BytesIO(b'')
553
554    require.side_effect = functools.partial(require_patch, tool)
555    runner_config = fix_up_runner_config(test_case, runner_config, tmpdir)
556    snr = TEST_OVR_SNR if test_case.snr else None
557    tool_opt = TEST_TOOL_OPT_L if test_case.tool_opt else []
558    cls = CLASS_MAP[tool]
559    runner = cls(runner_config,
560                 test_case.family,
561                 test_case.softreset,
562                 snr,
563                 erase=test_case.erase,
564                 tool_opt=tool_opt,
565                 recover=test_case.recover)
566
567    with patch('os.path.isfile', side_effect=os_path_isfile_patch):
568        runner.run('flash')
569    assert require.called
570
571    CHECK_FN_MAP = {'nrfjprog': check_call, 'nrfutil': popen}
572    check_expected(tool, test_case, CHECK_FN_MAP[tool], get_snr, tmpdir,
573                   runner_config)
574
575@pytest.mark.parametrize('tool', ["nrfjprog","nrfutil"])
576@pytest.mark.parametrize('test_case', EXPECTED_RESULTS.keys(), ids=id_fn)
577@patch('runners.core.ZephyrBinaryRunner.require')
578@patch('runners.nrfjprog.NrfBinaryRunner.get_board_snr',
579       side_effect=get_board_snr_patch)
580@patch('runners.nrfutil.subprocess.Popen')
581@patch('runners.nrfjprog.NrfBinaryRunner.check_call')
582def test_create(check_call, popen, get_snr, require, tool, test_case,
583                runner_config, tmpdir):
584    popen.return_value.__enter__.return_value.stdout = io.BytesIO(b'')
585
586    require.side_effect = functools.partial(require_patch, tool)
587    runner_config = fix_up_runner_config(test_case, runner_config, tmpdir)
588
589    args = []
590    if test_case.softreset:
591        args.append('--softreset')
592    if test_case.snr:
593        args.extend(['--dev-id', TEST_OVR_SNR])
594    if test_case.erase:
595        args.append('--erase')
596    if test_case.recover:
597        args.append('--recover')
598    if test_case.tool_opt:
599        args.extend(['--tool-opt', TEST_TOOL_OPT])
600
601    parser = argparse.ArgumentParser(allow_abbrev=False)
602    cls = CLASS_MAP[tool]
603    cls.add_parser(parser)
604    arg_namespace = parser.parse_args(args)
605    runner = cls.create(runner_config, arg_namespace)
606    with patch('os.path.isfile', side_effect=os_path_isfile_patch):
607        runner.run('flash')
608
609    assert require.called
610
611    CHECK_FN_MAP = {'nrfjprog': check_call, 'nrfutil': popen}
612    check_expected(tool, test_case, CHECK_FN_MAP[tool], get_snr, tmpdir,
613                   runner_config)
614