1#!/usr/bin/env python3 2 3## 4# Copyright (c) 2023 Apple Inc. All rights reserved. 5# 6# @APPLE_OSREFERENCE_LICENSE_HEADER_START@ 7# 8# This file contains Original Code and/or Modifications of Original Code 9# as defined in and that are subject to the Apple Public Source License 10# Version 2.0 (the 'License'). You may not use this file except in 11# compliance with the License. The rights granted to you under the License 12# may not be used to create, or enable the creation or redistribution of, 13# unlawful or unlicensed copies of an Apple operating system, or to 14# circumvent, violate, or enable the circumvention or violation of, any 15# terms of an Apple operating system software license agreement. 16# 17# Please obtain a copy of the License at 18# http://www.opensource.apple.com/apsl/ and read it before using this file. 19# 20# The Original Code and all software distributed under the License are 21# distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 22# EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 23# INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 24# FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 25# Please see the License for the specific language governing rights and 26# limitations under the License. 27# 28# @APPLE_OSREFERENCE_LICENSE_HEADER_END@ 29## 30 31""" 32LLDB unit-test runner. 33 34Discovers all unit-test that require LLDB instance and runs them from within 35LLDB testing environment. 36""" 37import atexit 38import argparse 39import unittest 40from unittest.mock import patch, MagicMock 41import sys 42import json 43from pathlib import Path 44 45from lldbtest.coverage import cov_html 46from lldbtest.unittest import LLDBTextTestRunner, LLDBJSONTestRunner 47 48# 49# Handle arguments 50# 51parser = argparse.ArgumentParser( 52 prog='runtests', 53 description='Runs lldb macro unit-tests against selected kernel' 54) 55parser.add_argument('kernel') 56parser.add_argument('-v', '--verbose', action='store_true') 57parser.add_argument('-d', '--debug', action='store_true') 58parser.add_argument('-c', '--coverage') 59parser.add_argument('-s', '--standalone', action='store_true') 60parser.add_argument('-j', '--json') 61 62args = parser.parse_args() 63 64# To avoid duplicates call this each time a script exists. 65def exit_handler(): 66 if args.coverage: 67 print('writing out coverage report ...') 68 cov_html(directory=args.coverage) 69 70 print('done.') 71 72atexit.register(exit_handler) 73 74SCRIPT_PATH = Path(__file__).parent 75 76# Select test runner class 77RunnerClass = LLDBJSONTestRunner if args.json else LLDBTextTestRunner 78 79# 80# A unit-test discovery requires to import tests as a module. This in turns 81# imports XNU which re-exports all dSYM modules. This results in failure to 82# discover tests. 83# 84# For now mock away lldb and lldb_wrap which are not available for standalone 85# unit tests. 86# 87print("Running standalone unit tests\n") 88 89with patch.dict('sys.modules', { 'lldb': MagicMock(), 'core.lldbwrap': MagicMock() }): 90 91 # Discover unit-tests 92 tests = unittest.TestLoader().discover(SCRIPT_PATH / "standalone_tests") 93 94 # Open output file if requested 95 if args.json: 96 with open(f"{args.json}-standalone.json", 'wt') as outfile: 97 runner = RunnerClass(verbosity=2 if args.verbose else 1, debug=args.debug, 98 stream=outfile) 99 runner.run(tests) 100 else: 101 runner = RunnerClass(verbosity=2 if args.verbose else 1, debug=args.debug) 102 runner.run(tests) 103 104if args.standalone: 105 sys.exit() 106 107# 108# Discover and run LLDB tests 109# 110print('Running LLDB unit tests\n') 111 112try: 113 import lldb 114except ImportError: 115 print("LLDB not available skipping tests.") 116 sys.exit() 117 118# Create Debugger instance 119debugger = lldb.SBDebugger.Create() 120 121# Created ScriptedProcess target for selected kernel binary 122error = lldb.SBError() 123target = debugger.CreateTargetWithFileAndArch(args.kernel, None) 124 125# Load scripts 126ret = lldb.SBCommandReturnObject() 127ci = debugger.GetCommandInterpreter() 128 129print('Loading scripted process plugin') 130ci.HandleCommand(f'command script import {SCRIPT_PATH / "lldb_test_process.py"}', 131 ret) 132 133# 134# Create Scripted Process for testing. 135# Python running this script and Python running inside LLDB may not match. 136# It is prefered to not shared anything across this boundary. 137# 138structured_data = lldb.SBStructuredData() 139structured_data.SetFromJSON(json.dumps({ 140 "verbose": args.verbose, 141 "debug": args.debug, 142 "json": args.json 143})) 144 145launch_info = lldb.SBLaunchInfo(None) 146launch_info.SetScriptedProcessDictionary(structured_data) 147launch_info.SetLaunchFlags(lldb.eLaunchFlagStopAtEntry) 148launch_info.SetWorkingDirectory(".") 149launch_info.SetProcessPluginName("ScriptedProcess") 150launch_info.SetScriptedProcessClassName('lldb_test_process.TestProcess') 151 152process = target.Launch(launch_info, error) 153if error.fail: 154 print(error.description) 155if not error.Success(): 156 sys.exit() 157 158ci.HandleCommand('run-unit-tests', ret) 159