Simon Glass | 2939e89 | 2025-01-20 14:25:31 -0700 | [diff] [blame] | 1 | # SPDX-License-Identifier: GPL-2.0 |
| 2 | # Copyright 2024 Google LLC |
| 3 | |
| 4 | import pytest |
| 5 | import re |
| 6 | |
| 7 | # List of test suites we expect to find with 'ut info' and 'ut all' |
| 8 | EXPECTED_SUITES = [ |
| 9 | 'addrmap', 'bdinfo', 'bloblist', 'bootm', 'bootstd', |
| 10 | 'cmd', 'common', 'dm', 'env', 'exit', |
Simon Glass | 7d4ae01 | 2025-01-20 14:25:56 -0700 | [diff] [blame] | 11 | 'fdt', 'font', 'hush', 'lib', |
Simon Glass | 2939e89 | 2025-01-20 14:25:31 -0700 | [diff] [blame] | 12 | 'loadm', 'log', 'mbr', 'measurement', 'mem', |
| 13 | 'overlay', 'pci_mps', 'setexpr', 'upl', |
| 14 | ] |
| 15 | |
| 16 | |
| 17 | # Set this to True to aid debugging of tests |
| 18 | DEBUG_ME = False |
| 19 | |
| 20 | |
| 21 | def collect_info(cons, output): |
| 22 | """Process the output from 'ut all' |
| 23 | |
| 24 | Args: |
| 25 | cons: U-Boot console object |
| 26 | output: Output from running 'ut all' |
| 27 | |
| 28 | Returns: |
| 29 | tuple: |
| 30 | set: suite names that were found in output |
| 31 | set: test names that were found in output |
| 32 | dict: test count for each suite: |
| 33 | key: suite name |
| 34 | value: number of tests for the suite found in output |
| 35 | set: missing suites (compared to EXPECTED_SUITES) |
| 36 | set: extra suites (compared to EXPECTED_SUITES) |
| 37 | """ |
| 38 | suites = set() |
| 39 | tests = set() |
| 40 | cur_suite = None |
| 41 | test_count = None |
| 42 | exp_test_count = {} |
| 43 | |
| 44 | # Collect suites{} |
| 45 | for line in output.splitlines(): |
| 46 | line = line.rstrip() |
| 47 | if DEBUG_ME: |
| 48 | cons.log.info(f'line: {line}') |
| 49 | m = re.search('----Running ([^ ]*) tests----', line) |
| 50 | if m: |
| 51 | if DEBUG_ME and cur_suite and cur_suite != 'info': |
| 52 | cons.log.info(f'suite: {cur_suite} expected {exp_test_count[cur_suite]} found {test_count}') |
| 53 | |
| 54 | cur_suite = m.group(1) |
| 55 | if DEBUG_ME: |
| 56 | cons.log.info(f'cur_suite: {cur_suite}') |
| 57 | suites.add(cur_suite) |
| 58 | |
| 59 | test_count = 0 |
| 60 | m = re.match(rf'Running (\d+) {cur_suite} tests', line) |
| 61 | if m: |
| 62 | exp_test_count[cur_suite] = int(m.group(1)) |
| 63 | m = re.search(r'Test: (\w*): ([-a-z0-9_]*\.c)?( .*)?', line) |
| 64 | if m: |
| 65 | test_name = m.group(1) |
| 66 | msg = m.group(3) |
| 67 | if DEBUG_ME: |
| 68 | cons.log.info(f"test_name {test_name} msg '{msg}'") |
Simon Glass | 598a1a5 | 2025-02-07 11:30:37 -0700 | [diff] [blame^] | 69 | full_name = f'{cur_suite}.{test_name}' |
| 70 | if msg == ' (flat tree)' and full_name not in tests: |
| 71 | tests.add(full_name) |
Simon Glass | 2939e89 | 2025-01-20 14:25:31 -0700 | [diff] [blame] | 72 | test_count += 1 |
| 73 | if not msg or 'skipped as it is manual' in msg: |
Simon Glass | 598a1a5 | 2025-02-07 11:30:37 -0700 | [diff] [blame^] | 74 | tests.add(full_name) |
Simon Glass | 2939e89 | 2025-01-20 14:25:31 -0700 | [diff] [blame] | 75 | test_count += 1 |
| 76 | if DEBUG_ME: |
| 77 | cons.log.info(f'test_count {test_count}') |
| 78 | if DEBUG_ME: |
| 79 | cons.log.info(f'suite: {cur_suite} expected {exp_test_count[cur_suite]} found {test_count}') |
| 80 | cons.log.info(f"Tests: {' '.join(sorted(list(tests)))}") |
| 81 | |
| 82 | # Figure out what is missing, or extra |
| 83 | missing = set() |
| 84 | extra = set(suites) |
| 85 | for suite in EXPECTED_SUITES: |
| 86 | if suite in extra: |
| 87 | extra.remove(suite) |
| 88 | else: |
| 89 | missing.add(suite) |
| 90 | |
| 91 | return suites, tests, exp_test_count, missing, extra |
| 92 | |
| 93 | |
| 94 | def process_ut_info(cons, output): |
| 95 | """Process the output of the 'ut info' command |
| 96 | |
| 97 | Args: |
| 98 | cons: U-Boot console object |
| 99 | output: Output from running 'ut all' |
| 100 | |
| 101 | Returns: |
| 102 | tuple: |
| 103 | int: Number of suites reported |
| 104 | int: Number of tests reported |
| 105 | dict: test count for each suite: |
| 106 | key: suite name |
| 107 | value: number of tests reported for the suite |
| 108 | |
| 109 | """ |
| 110 | suite_count = None |
| 111 | total_test_count = None |
| 112 | test_count = {} |
| 113 | for line in output.splitlines(): |
| 114 | line = line.rstrip() |
| 115 | if DEBUG_ME: |
| 116 | cons.log.info(f'line: {line}') |
| 117 | m = re.match(r'Test suites: (.*)', line) |
| 118 | if m: |
| 119 | suite_count = int(m.group(1)) |
| 120 | m = re.match(r'Total tests: (.*)', line) |
| 121 | if m: |
| 122 | total_test_count = int(m.group(1)) |
| 123 | m = re.match(r' *([0-9?]*) (\w*)', line) |
| 124 | if m: |
| 125 | test_count[m.group(2)] = m.group(1) |
| 126 | return suite_count, total_test_count, test_count |
| 127 | |
| 128 | |
| 129 | @pytest.mark.buildconfigspec('sandbox') |
| 130 | @pytest.mark.notbuildconfigspec('sandbox_spl') |
| 131 | @pytest.mark.notbuildconfigspec('sandbox64') |
Simon Glass | 9dc6e29 | 2025-01-20 14:26:04 -0700 | [diff] [blame] | 132 | # This test is disabled since it fails; remove the leading 'x' to try it |
| 133 | def xtest_suite(u_boot_console, u_boot_config): |
Simon Glass | 2939e89 | 2025-01-20 14:25:31 -0700 | [diff] [blame] | 134 | """Perform various checks on the unit tests, including: |
| 135 | |
| 136 | - The number of suites matches that reported by the 'ut info' |
| 137 | - Where available, the number of tests is each suite matches that |
| 138 | reported by 'ut info -s' |
| 139 | - The total number of tests adds up to the total that are actually run |
| 140 | with 'ut all' |
| 141 | - All suites are run with 'ut all' |
| 142 | - The expected set of suites is run (the list is hard-coded in this test) |
| 143 | |
| 144 | """ |
| 145 | cons = u_boot_console |
Simon Glass | a3ce129 | 2025-01-20 14:25:57 -0700 | [diff] [blame] | 146 | buildconfig = u_boot_config.buildconfig |
Simon Glass | 2939e89 | 2025-01-20 14:25:31 -0700 | [diff] [blame] | 147 | with cons.log.section('Run all unit tests'): |
| 148 | # ut hush hush_test_simple_dollar prints "Unknown command" on purpose. |
| 149 | with u_boot_console.disable_check('unknown_command'): |
| 150 | output = cons.run_command('ut all') |
| 151 | |
| 152 | # Process the output from the run |
| 153 | with cons.log.section('Check output'): |
| 154 | suites, all_tests, exp_test_count, missing, extra = collect_info(cons, |
| 155 | output) |
| 156 | cons.log.info(f'missing {missing}') |
| 157 | cons.log.info(f'extra {extra}') |
| 158 | |
Simon Glass | 7d4ae01 | 2025-01-20 14:25:56 -0700 | [diff] [blame] | 159 | # Make sure we got a test count for each suite |
Simon Glass | a3ce129 | 2025-01-20 14:25:57 -0700 | [diff] [blame] | 160 | assert not (suites - exp_test_count.keys()) |
| 161 | |
| 162 | # Deal with missing suites |
| 163 | with cons.log.section('Check missing suites'): |
| 164 | if 'config_cmd_seama' not in buildconfig: |
| 165 | cons.log.info("CMD_SEAMA not enabled: Ignoring suite 'seama'") |
| 166 | missing.discard('seama') |
Simon Glass | 2939e89 | 2025-01-20 14:25:31 -0700 | [diff] [blame] | 167 | |
| 168 | # Run 'ut info' and compare with the log results |
| 169 | with cons.log.section('Check suite test-counts'): |
| 170 | output = cons.run_command('ut info -s') |
| 171 | |
| 172 | suite_count, total_test_count, test_count = process_ut_info(cons, |
| 173 | output) |
| 174 | |
| 175 | if missing or extra: |
| 176 | cons.log.info(f"suites: {' '.join(sorted(list(suites)))}") |
| 177 | cons.log.error(f'missing: {sorted(list(missing))}') |
| 178 | cons.log.error(f'extra: {sorted(list(extra))}') |
| 179 | |
| 180 | assert not missing, f'Missing suites {missing}' |
| 181 | assert not extra, f'Extra suites {extra}' |
| 182 | |
| 183 | cons.log.info(str(exp_test_count)) |
| 184 | for suite in EXPECTED_SUITES: |
Simon Glass | 7d4ae01 | 2025-01-20 14:25:56 -0700 | [diff] [blame] | 185 | assert test_count[suite] in ['?', str(exp_test_count[suite])], \ |
| 186 | f'suite {suite} expected {exp_test_count[suite]}' |
Simon Glass | 2939e89 | 2025-01-20 14:25:31 -0700 | [diff] [blame] | 187 | |
| 188 | assert suite_count == len(EXPECTED_SUITES) |
| 189 | assert total_test_count == len(all_tests) |