Loading...
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2025 Simon Glass
# Written by Simon Glass <sjg@chromium.org>

"""Test U-Boot Makefile dependency tracking

This test verifies that Makefile rules properly track dependencies and rebuild
targets when dependency files change. It tests rules that had FORCE removed
to rely on proper dependency tracking instead.

Tests cover:
- hwids_to_dtsi: Hardware ID mapping to device tree source
- $(obj)/%.dtbo: Device tree source overlay to binary overlay compilation
- ESL dependency chains: Certificate -> ESL file -> DTSI file transformation
"""

import os
import time
from types import SimpleNamespace

import pytest
import utils

# Time to wait between file modifications to ensure different timestamps
TIMESTAMP_DELAY = 2


def _info(env, message):
    """Log an informational message

    Args:
        env (SimpleNamespace): Test environment with ubman logger
        message (str): Message to log
    """
    env.ubman.log.info(message)

def _run_make(env, message, mtime_file=None):
    """Run a real U-Boot build command with logging

    Args:
        env (SimpleNamespace): Test environment with ubman logger and paths
        message (str): Log message describing the build
        mtime_file (str, optional): File path to get mtime from. If None,
            returns None.

    Returns:
        float or None: File modification time of mtime_file, or None if
            mtime_file not provided
    """
    _info(env, message)

    # Build all targets using real U-Boot makefile
    utils.run_and_log(env.ubman, ['make', '-j30', 'all'], cwd=env.build_dir)

    # Return modification time if mtime_file is specified
    if mtime_file:
        return os.path.getmtime(mtime_file)
    return None


def _setup_hwids_env(ubman):
    """Set up test environment for hwids dependency tracking

    Uses real U-Boot build

    Args:
        ubman (ConsoleBase): ubman fixture

    Returns:
        SimpleNamespace: Test environment with paths and config
    """
    # CHID is enabled in sandbox config

    # Use the real sandbox hwids directory
    hwids_dir = os.path.join(ubman.config.source_dir, 'board/sandbox/hwids')
    hwidmap_file = os.path.join(hwids_dir, 'compatible.hwidmap')

    # The hwids DTSI file gets created in test/fdt_overlay/ directory
    # when building test DTBs that include hwids
    test_target = os.path.join(ubman.config.build_dir,
                               'test/fdt_overlay/hwids.dtsi')

    # Use the known hwid txt files for testing
    hwid_txt_file = os.path.join(hwids_dir, 'test-device-1.txt')
    second_test_target = os.path.join(hwids_dir, 'test-device-2.txt')

    return SimpleNamespace(
        ubman=ubman,
        build_dir=ubman.config.build_dir,
        source_dir=ubman.config.source_dir,
        hwids_dir=hwids_dir,
        hwidmap_file=hwidmap_file,
        hwid_txt_file=hwid_txt_file,
        second_test_target=second_test_target,
        test_target=test_target
    )


def _check_initial_build(env):
    """Test initial build and no-change rebuild behavior

    Args:
        env (SimpleNamespace): Test environment with paths and ubman logger

    Returns:
        tuple: (initial_mtime, second_mtime) modification times
    """
    # Clean the test target
    if os.path.exists(env.test_target):
        os.remove(env.test_target)
    initial_mtime = _run_make(env, 'build hwids target', env.test_target)

    _info(env, f'Initial target created at {initial_mtime}')

    # Wait a bit to ensure timestamp differences
    time.sleep(TIMESTAMP_DELAY)

    # Run build again - target should NOT be rebuilt (no dependencies changed)
    mtime = _run_make(env, 'build again without changes', env.test_target)
    assert mtime == initial_mtime, \
        f'Target should not rebuild when dependencies unchanged ' \
        f'(initial: {initial_mtime}, second: {mtime})'

    return initial_mtime, mtime


def _check_hwidmap_change(env, previous_mtime):
    """Test that changing hwidmap file triggers rebuild

    Args:
        env (SimpleNamespace): Test environment with paths and ubman logger
        previous_mtime (float): Previous modification time to compare against

    Returns:
        float: New modification time after rebuild
    """
    time.sleep(TIMESTAMP_DELAY)

    # Touch the hwidmap file to update its timestamp
    os.utime(env.hwidmap_file)

    # Build again - target SHOULD be rebuilt due to hwidmap change
    mtime = _run_make(env, 'build after hwidmap change', env.test_target)
    assert mtime > previous_mtime, \
        f'Target should rebuild when hwidmap changes ' \
        f'(previous: {previous_mtime}, new: {mtime})'

    return mtime


def _check_txt_changes(env, previous_mtime):
    """Test that changing txt files triggers rebuild

    Args:
        env (SimpleNamespace): Test environment with paths and ubman logger
        previous_mtime (float): Previous modification time to compare against

    Returns:
        float: Final modification time after all changes
    """
    # Test existing txt file modification
    time.sleep(TIMESTAMP_DELAY)

    # Touch the txt file to update its timestamp
    os.utime(env.hwid_txt_file)

    modified_mtime = _run_make(env, 'build after txt change', env.test_target)
    assert modified_mtime > previous_mtime, \
        f'Target should rebuild when txt file changes ' \
        f'(previous: {previous_mtime}, modified: {modified_mtime})'

    # Test modification of a second txt file to verify dependency tracking
    # across multiple files
    time.sleep(TIMESTAMP_DELAY)

    # Touch second txt file to update its timestamp
    os.utime(env.second_test_target)

    mtime = _run_make(env, 'build after second txt change', env.test_target)
    assert mtime > modified_mtime, \
        f'Target should rebuild when second txt file changes ' \
        f'(modified: {modified_mtime}, final: {mtime})'

    return mtime


def _setup_dtbo_env(ubman):
    """Set up test environment for dtbo dependency tracking

    Uses real U-Boot build

    Args:
        ubman (ConsoleBase): ubman fixture

    Returns:
        SimpleNamespace: DTBO test environment with paths and config
    """
    # Use the existing test overlay files from U-Boot source
    dtso_file = os.path.join(ubman.config.source_dir,
                             'test/fdt_overlay/test-fdt-overlay.dtso')

    # The DTBO gets built and compiled into .o file during test build
    # We track the .o file since the .dtbo is an intermediate file
    dtbo_file = os.path.join(ubman.config.build_dir,
                             'test/fdt_overlay/test-fdt-overlay.dtbo.o')

    return SimpleNamespace(
        ubman=ubman,
        build_dir=ubman.config.build_dir,
        source_dir=ubman.config.source_dir,
        dtso_file=dtso_file,
        dtbo_target=dtbo_file
    )



def _check_dtbo_build(env):
    """Test initial dtbo build and no-change rebuild behavior

    Args:
        env (SimpleNamespace): Test environment with paths and ubman logger

    Returns:
        float: Initial modification time of dtbo file
    """
    # Clean the DTBO target and related files
    dtbo_base = env.dtbo_target.replace('.dtbo.o', '.dtbo')
    for f in [env.dtbo_target, dtbo_base, dtbo_base + '.S']:
        if os.path.exists(f):
            os.remove(f)
    initial_mtime = _run_make(env, 'build dtbo target', env.dtbo_target)

    _info(env, f'Initial dtbo created at {initial_mtime}')

    # Wait a bit to ensure timestamp differences
    time.sleep(TIMESTAMP_DELAY)

    # Run build again - target should NOT be rebuilt (no dependencies changed)
    mtime = _run_make(env, 'build dtbo again', env.dtbo_target)
    assert mtime == initial_mtime, \
        f'DTBO should not rebuild when dtso unchanged ' \
        f'(initial: {initial_mtime}, second: {mtime})'

    return initial_mtime


def _check_dtso_change(env, previous_mtime):
    """Test that changing dtso file triggers dtbo rebuild

    Args:
        env (SimpleNamespace): Test environment with paths and ubman logger
        previous_mtime (float): Previous modification time to compare against

    Returns:
        float: New modification time after dtso change and rebuild
    """
    time.sleep(TIMESTAMP_DELAY)

    # Touch the dtso file to update its timestamp
    os.utime(env.dtso_file)

    # Build again - target SHOULD be rebuilt due to dtso change
    mtime = _run_make(env, 'build dtbo after dtso change', env.dtbo_target)
    assert mtime > previous_mtime, \
        f'DTBO should rebuild when dtso changes ' \
        f'(previous: {previous_mtime}, new: {mtime})'

    return mtime


def _setup_esl_env(ubman):
    """Set up test environment for ESL dependency tracking

    Uses real U-Boot Makefile

    Args:
        ubman (ConsoleBase): ubman fixture

    Returns:
        SimpleNamespace: ESL test environment with paths and config
    """
    # Capsule authentication is enabled in sandbox config

    # Use the real U-Boot template and certificate files
    template_file = os.path.join(ubman.config.source_dir,
                                 'lib/efi_loader/capsule_esl.dtsi.in')
    cert_file = os.path.join(ubman.config.source_dir,
                             'board/sandbox/capsule_pub_key_good.crt')

    # Build directory paths for the real ESL files
    # ESL files are created in each directory where DTBs are built
    esl_file = os.path.join(ubman.config.build_dir,
                            'test/fdt_overlay/capsule_esl_file')
    esl_dtsi = os.path.join(ubman.config.build_dir,
                            'test/fdt_overlay/.capsule_esl.dtsi')

    return SimpleNamespace(
        ubman=ubman,
        build_dir=ubman.config.build_dir,
        source_dir=ubman.config.source_dir,
        cert_file=cert_file,
        esl_template=template_file,
        esl_file=esl_file,
        esl_dtsi=esl_dtsi
    )



def _check_esl_build(env):
    """Test initial ESL build and no-change rebuild behavior

    Args:
        env (SimpleNamespace): Test environment with paths and ubman logger

    Returns:
        tuple: (esl_mtime, dtsi_mtime) modification times
    """
    # Clean the ESL files and test DTB files that depend on them
    for f in [env.esl_file, env.esl_dtsi,
              os.path.join(env.build_dir,
                           'test/fdt_overlay/test-fdt-base.dtb')]:
        if os.path.exists(f):
            os.remove(f)

    # Build test target that should trigger ESL creation
    esl_mtime = _run_make(env, 'should create ESL', env.esl_file)
    dtsi_mtime = _run_make(env, 'should create ESL DTSI', env.esl_dtsi)

    _info(env, f'Initial ESL created at {esl_mtime}')
    _info(env, f'Initial DTSI created at {dtsi_mtime}')

    # Wait a bit to ensure timestamp differences
    time.sleep(TIMESTAMP_DELAY)

    # Run build again - targets should NOT be rebuilt (no dependencies changed)
    new_esl_mtime = _run_make(env, 'build ESL again', env.esl_file)
    assert new_esl_mtime == esl_mtime, \
        f'ESL should not rebuild when cert unchanged ' \
        f'(initial: {esl_mtime}, second: {new_esl_mtime})'

    new_dtsi_mtime = _run_make(env, 'build DTSI again', env.esl_dtsi)
    assert new_dtsi_mtime == dtsi_mtime, \
        f'DTSI should not rebuild when ESL/template unchanged ' \
        f'(initial: {dtsi_mtime}, second: {new_dtsi_mtime})'

    return esl_mtime, dtsi_mtime


def _check_cert_change(env, prev_esl_mtime, prev_dtsi_mtime):
    """Test certificate change triggers full rebuild chain

    Args:
        env (SimpleNamespace): Test environment with paths and ubman logger
        prev_esl_mtime (float): Previous ESL modification time
        prev_dtsi_mtime (float): Previous DTSI modification time

    Returns:
        tuple: (new_esl_mtime, new_dtsi_mtime) after certificate change
    """
    time.sleep(TIMESTAMP_DELAY)

    # Touch the certificate file to update its timestamp
    os.utime(env.cert_file)

    # Build ESL - should rebuild due to cert change
    esl_mtime = _run_make(env, 'build ESL after cert change', env.esl_file)
    assert esl_mtime > prev_esl_mtime, \
        f'ESL should rebuild when certificate changes ' \
        f'(previous: {prev_esl_mtime}, new: {esl_mtime})'

    # Build DTSI - should rebuild due to ESL change
    dtsi_mtime = _run_make(env, 'build DTSI after ESL change', env.esl_dtsi)
    assert dtsi_mtime > prev_dtsi_mtime, \
        f'DTSI should rebuild when ESL changes ' \
        f'(previous: {prev_dtsi_mtime}, new: {dtsi_mtime})'

    return esl_mtime, dtsi_mtime


def _check_template_change(env, prev_esl_mtime, prev_dtsi_mtime):
    """Test template change rebuilds DTSI but not ESL

    Args:
        env (SimpleNamespace): Test environment with paths and ubman logger
        prev_esl_mtime (float): Previous ESL modification time
        prev_dtsi_mtime (float): Previous DTSI modification time

    Returns:
        tuple: (esl_mtime, new_dtsi_mtime) after template change
    """
    time.sleep(TIMESTAMP_DELAY)

    # Touch the template file to update its timestamp
    os.utime(env.esl_template)

    # Build ESL - should NOT rebuild (cert unchanged)
    esl_mtime = _run_make(env, 'build ESL after template change',
                         env.esl_file)
    assert esl_mtime == prev_esl_mtime, \
        f'ESL should not rebuild when only template changes ' \
        f'(previous: {prev_esl_mtime}, current: {esl_mtime})'

    # Build DTSI - should rebuild due to template change
    dtsi_mtime = _run_make(env, 'build DTSI after template change',
                          env.esl_dtsi)
    assert dtsi_mtime > prev_dtsi_mtime, \
        f'DTSI should rebuild when template changes ' \
        f'(previous: {prev_dtsi_mtime}, new: {dtsi_mtime})'

    return esl_mtime, dtsi_mtime


@pytest.mark.boardspec('sandbox')
@pytest.mark.slow
def test_dep_hwids(ubman):
    """Test that Makefile dependency tracking works without FORCE

    This test verifies that the hwids_to_dtsi rule (which had FORCE removed)
    still properly rebuilds when dependency files are modified.
    """
    env = _setup_hwids_env(ubman)

    _info(env, 'initial build and no-change rebuild')
    _, second_mtime = _check_initial_build(env)

    # Initial build should have created hwids files

    _info(env, 'hwidmap file change triggers rebuild')
    third_mtime = _check_hwidmap_change(env, second_mtime)

    _info(env, 'txt file changes trigger rebuild')
    _check_txt_changes(env, third_mtime)


@pytest.mark.boardspec('sandbox')
@pytest.mark.slow
def test_dep_dtbo(ubman):
    """Test that dtbo dependency tracking works without FORCE

    This test verifies that the $(obj)/%.dtbo rule properly rebuilds
    when the corresponding .dtso source file is modified.
    """
    env = _setup_dtbo_env(ubman)

    _info(env, 'initial dtbo build and no-change rebuild')
    first_mtime = _check_dtbo_build(env)

    # Initial build should have created DTBO files

    _info(env, 'dtso file change triggers dtbo rebuild')
    _check_dtso_change(env, first_mtime)


@pytest.mark.boardspec('sandbox')
@pytest.mark.slow
def test_dep_esl(ubman):
    """Test that ESL dependency tracking works without FORCE

    This test verifies that the capsule ESL rules properly track dependencies
    and rebuild when source files are modified. Tests the chain:
    certificate -> ESL file -> DTSI file

    If ESL files are not created due to missing dependencies in Makefile,
    the test focuses on demonstrating the template dependency requirement.
    """
    env = _setup_esl_env(ubman)

    _info(env, 'initial ESL build and no-change rebuild')
    esl_mtime, dtsi_mtime = _check_esl_build(env)
    _info(env, 'certificate change triggers full rebuild chain')
    esl_mtime, dtsi_mtime = _check_cert_change(env, esl_mtime, dtsi_mtime)

    _info(env, 'template change rebuilds DTSI only')
    _check_template_change(env, esl_mtime, dtsi_mtime)