tools: Add changeset analyzer

This tool compares two git branches of UHD and produces a list of tests
required to verify the changeset. The intended use case is to be able to
verify branches that are being created for pull requests.

For example, say a pull requests is based on a branch that only modifies
files in host/lib/usrp/x300. Then it is only necessary to run hardware
tests on X300, running tests on other USRPs would be a waste of time.

This commit contains two files: The utility itself (a Python script),
and a rule file (a YAML file). The former uses the latter to map
a changeset to a list of tests.
This commit is contained in:
Martin Braun 2023-06-14 10:31:27 +02:00 committed by Aki Tomita
parent ac0f3014b6
commit 3b5f89ee72
2 changed files with 320 additions and 0 deletions

145
tools/changeset_testlist.py Normal file
View file

@ -0,0 +1,145 @@
#!/usr/bin/env python3
#
# Copyright 2023 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Changeset analyzer: Reads changes made on a particular branch compared to
another and computes a list of tests that need to be run to validate this branch.
"""
import os
import pathlib
import re
import sys
import subprocess
import shutil
import argparse
from ruamel import yaml
def parse_args():
"""
Parse and return args.
"""
parser = argparse.ArgumentParser(
description=__doc__,
)
parser.add_argument(
'--source-branch',
help="Source branch off diff. Defaults to the current branch.")
parser.add_argument(
'--target-branch', default='master',
help="Target branch off diff. Defaults to 'master'.")
parser.add_argument(
'--repo-path', default='.',
help="Path to the UHD git repository. Defaults to the current directory.")
parser.add_argument(
'--rule-file',
help="Path to rules file.")
parser.add_argument(
'--set-azdo-var',
help="Generate output to set an AzDO variable")
parser.add_argument(
'--list-tests', action='store_true',
help="Show the generated test-list.")
parser.add_argument(
'--include-target', action='store_true',
help="Include changes that originate from the target branch.")
parser.add_argument(
'-v', '--verbose', action='store_true',
help="Verbose output")
return parser.parse_args()
def get_changed_files(repo_path, target_branch, source_branch, include_target):
"""
Returns a list of paths in the UHD repository that have are different between
two branches.
"""
assert target_branch
# If include_target is false, then current (unstaged/uncommited) changes are
# not included. If we want to change this, then couple this with
# git diff --name-only (no further arguments)
if not include_target:
target_branch += '...'
git_cmd = shutil.which('git')
get_diff_args = [
git_cmd,
'diff',
'--name-only',
target_branch]
if source_branch:
get_diff_args.append(source_branch)
files = subprocess.check_output(get_diff_args, cwd=repo_path, encoding='utf-8')
return files.strip().split("\n")
def load_rules(rule_file):
"""
Return the rules as a Python list.
"""
with open(rule_file, 'r', encoding='utf-8') as rfd:
return yaml.safe_load(rfd)
class RuleApplier:
"""
Helper class to update an internal test list based on a set of rules.
"""
def __init__(self, rules):
self.rules = rules
self.test_list = set()
def apply(self, filename, verbose=False):
"""
Apply rules against a file.
"""
for rule in self.rules:
if self._apply_rule(rule, filename, verbose):
break
def _apply_rule(self, rule, filename, verbose=False):
"""
Helper: Apply a single rule.
Returns True if you can stop applying rules against this filename.
"""
# First: Check if this rule even applies to this file
if 're' in rule and not re.search(rule['re'], filename) \
or 'name' in rule and rule['name'] != filename:
return False
if verbose:
sys.stderr.write(f"Filename {filename} matches rule: {rule}\n")
if 'add' in rule:
self.test_list.update(rule['add'])
# If stop is specified as False, then we can still apply more rules.
if 'stop' in rule and not rule['stop']:
return False
return True
def main():
"""
Gogogo!
"""
args = parse_args()
rule_file = args.rule_file
if not rule_file:
rule_file = os.path.join(pathlib.Path(__file__).parent.resolve(), 'changeset_testlist.yaml')
file_list = get_changed_files(
args.repo_path,
args.target_branch,
args.source_branch,
args.include_target,
)
rule_applier = RuleApplier(load_rules(rule_file))
for filename in file_list:
rule_applier.apply(filename, args.verbose)
if args.set_azdo_var:
print(
f"##vso[task.setvariable variable={args.set_azdo_var};isoutput=true]" + \
';'.join(rule_applier.test_list))
if args.list_tests:
print("Required tests:")
print("---------------", end='')
print('\n* '.join(sorted([''] + list(rule_applier.test_list))))
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,175 @@
# Test targets:
# - uhd.build.$PLATFORM: Build UHD on given platforms. This includes running unit tests.
# Valid values for $PLATFORM are: all, linux, windows, mac.
# - hw.streaming.$DEVICE: Run streaming tests for $DEVICE.
# - hw.rf.$DEVICE: Run RF tests for $DEVICE.
# - devtest.$DEVICE: Run devtests for $DEVICE.
###############################################################################
# HOST CHANGES (UHD)
###############################################################################
# If only a unit test changed, then we need to re-run unit tests, but no HW tests
- re: ^host/tests/[^/]+(cpp|py)$|^host/tests/CMakeLists.txt
add:
- uhd.build.all
- uhd.utest.all
# Documentation changes
- re: ^host/docs/
add:
- uhd.docs
# Device-specific changes. These should trigger HW tests only on those devices
# they affect. We start with daughterboard rules, then motherboard rules.
- re: host/lib/usrp/dboard/zbx/
add:
- uhd.build.all
- hw.rf.x410
- hw.streaming.x410
- devtest.x410
- re: host/lib/usrp/dboard/e3xx
add:
- uhd.build.all
- hw.rf.e3xx
- hw.streaming.e3xx
- devtest.e3xx
- re: host/lib/usrp/dboard/magnesium
add:
- uhd.build.all
- hw.rf.n310
- devtest.n310
- re: host/lib/usrp/dboard/rhodium
add:
- uhd.build.all
- hw.rf.n320
- devtest.n320
- re: host/lib/usrp/dboard/twinrx/
name: host/lib/usrp/dboard/db_twinrx.cpp
add:
- uhd.build.all
- hw.rf.x310.twinrx
- hw.rf.x300.twinrx
- re: host/lib/usrp/dboard/.*CMakeLists.txt
add:
- uhd.build.all
- re: host/lib/usrp/dboard/db_.+pp$
add:
- uhd.build.all
- hw.rf.x3xx
- hw.rf.n2xx
- re: host/lib/usrp/b200/
add:
- uhd.build.all
- hw.rf.b2xx
- hw.streaming.b2xx
- devtest.b2xx
- re: host/lib/usrp/b100/
add:
- uhd.build.all
- hw.rf.b1xx
- hw.streaming.b1xx
- devtest.b1xx
- re: host/lib/usrp/x300/
add:
- uhd.build.all
- hw.rf.x3xx
- hw.streaming.x3xx
- devtest.x3xx.all
- re: host/lib/usrp/x400/
add:
- uhd.build.all
- hw.rf.x4xx
- hw.rf.n3xx
- hw.streaming.x4xx
- devtest.x4xx
- re: host/lib/usrp/mpmd/
add:
- uhd.build.all
- hw.rf.x4xx
- hw.rf.n3xx
- hw.rf.e3xx
- hw.streaming.x4xx
- hw.streaming.n3xx
- hw.streaming.e3xx
- devtest.x4xx
- devtest.e3xx
- devtest.n3xx
- re: host/lib/usrp/usrp2
add:
- uhd.build.all
- hw.rf.n2xx
# Catchall rule for UHD changes
- re: host/.+cpp$|host/.+CMakeLists.txt|host/.+hpp$|host/.+ipp$|host/.+c$|host/.+h$|host/.+py$
add:
- uhd.build.all
- uhd.utest.all
- hw.streaming.all
- hw.rf.all
- devtest.all
###############################################################################
# MPM CHANGES
###############################################################################
# When any code file changes, we want to run the unit tests, but run the
# other tests, too
- re: ^mpm/.+hpp$|^mpm/.+cpp$|^mpm/.+py$|^mpm/.+/tests/|^mpm/.+CMakeLists.txt$
add:
- mpm.utest.all
stop: False
- re: ^mpm/.+/ad9361|^mpm/.+catalina$|^mpm/.+/dboard_manager/ad936x_db.py
add:
- mpm.build.e3xx
- hw.rf.e3xx
- devtest.e3xx
- re: ^mpm/.+/periph_manager/n3xx
add:
- mpm.build.n3xx
- hw.rf.n3xx
- hw.streaming.n3xx
- devtest.n3xx
- re: ^mpm/.+/ad937x|^mpm/.+/mykonos|^mpm/.+/dboard_manager/mg_|magnesium_manager..pp$
add:
- mpm.build.n310
- hw.rf.n310
- devtest.n310
- re: ^mpm/.+/dboard_manager/rh_|^mpm/.+/dboard_manager/..._rh.py
add:
- mpm.build.n320
- hw.rf.n320
- devtest.n320
- re: ^mpm/.+periph_manager/x4xx
add:
- hw.streaming.x4xx
stop: False
- re: ^mpm/.+/rfdc|^mpm/.+_manager/x4xx
add:
- mpm.build.x4xx
- hw.rf.x4xx
- devtest.x4xx
- re: fbx.py$
add:
- mpm.build.x4xx
- hw.rf.x440
- devtest.x440
- re: zbx.py
add:
- mpm.build.x4xx
- hw.rf.x410
- devtest.x410
- re: e31x_db_manager..pp$|^mpm/.+/periph_manager/e31x|e31x_db.py$
add:
- mpm.build.e310
- hw.rf.e310
- devtest.e310
- re: neon_manager..pp$|^mpm/.+/periph_manager/e320
add:
- mpm.build.e320
- hw.rf.e320
- devtest.e320
# Catchall rule for MPM changes
- re: ^mpm/
add:
- mpm.build.all
- mpm.utest.all
- hw.streaming.all
- hw.rf.all
- devtest.all