blob: a127195feca6c40443a29f7d1c89e22ca480ec61 [file] [log] [blame] [edit]
#!/usr/bin/env python3
#
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests building sqlite and optionally running a sql script.
Generates amalgamations, builds the sqlite shell, and optionally runs a sql
file in the sqlite shell. Designed to be passed to `git bisect run` to
determine a culprit in the sqlite repro for either an issue in the build or a
sqlite script.
Example usage:
git bisect start c12b0d5b7135 9e45bccab2b8
git bisect run ../scripts/repro_tool.py repro.sql --should_build
"""
import os
import argparse
import subprocess
import sys
import textwrap
from functools import cache
from pathlib import PurePath
class SqlTester:
def __init__(self, build_dir, quiet, verbose, should_build):
self.relative_build_dir = build_dir
self.quiet = quiet
self.verbose = verbose and not quiet
self.should_build = should_build
@property
@cache
def script_dir(self):
return os.path.dirname(os.path.realpath(__file__))
@property
@cache
def chrome_root(self):
result = subprocess.run(['git', 'rev-parse', '--show-toplevel'],
encoding='UTF-8',
cwd=self.script_dir,
capture_output=True,
text=True)
return result.stdout.strip()
@property
@cache
def build_dir(self):
return os.path.join(self.chrome_root, self.relative_build_dir)
@property
@cache
def generate_amalgamation_script(self):
return os.path.join(self.script_dir, 'generate_amalgamation.py')
@property
@cache
def sqlite_shell(self):
return os.path.join(self.build_dir, 'sqlite_shell')
def run_process(self, process, stdin=None):
stdout = None if self.verbose else open(os.devnull, 'w')
process = subprocess.Popen(process,
encoding='UTF-8',
cwd=self.chrome_root,
stdin=stdin,
stdout=stdout,
stderr=subprocess.PIPE)
_, stderr = process.communicate()
return process.returncode, stderr
def generate_amalgamation(self):
return self.run_process(self.generate_amalgamation_script)
def build_sqlite_shell(self):
return self.run_process(
['autoninja', '-C', self.build_dir, 'sqlite_shell'])
def run_sql(self, sqlfile):
sqlfileobject = open(sqlfile, 'r', encoding='UTF-8')
return self.run_process(self.sqlite_shell, stdin=sqlfileobject)
def print(self, message):
if not self.quiet:
print(message)
def print_stderr(self, stderr):
self.print(textwrap.indent(stderr, ' | '))
def handle_build_error(self, stderr):
if self.should_build:
self.print(' Unexpectedly failed:')
self.print_stderr(stderr)
# -1 indicates to `git bisect run` to abort:
# https://git-scm.com/docs/git-bisect#_bisect_run
return -1
self.print(' Failed:')
self.print_stderr(stderr)
return 1
def run(self, sqlfile):
if not os.path.isfile(self.generate_amalgamation_script):
self.print('generate_amalgamation.py no longer exists in the same '
'directory as this script or has been renamed.')
# -1 indicates to `git bisect run` to abort:
# https://git-scm.com/docs/git-bisect#_bisect_run
return -1
self.print('Generating amalgamations')
returncode, stderr = self.generate_amalgamation()
if returncode != 0:
return self.handle_build_error(stderr)
self.print('Building sqlite_shell')
returncode, stderr = self.build_sqlite_shell()
if returncode != 0:
return self.handle_build_error(stderr)
if sqlfile == None:
return 0
self.print('Running sqlfile')
returncode, stderr = self.run_sql(sqlfile)
if returncode != 0:
self.print(' Failed:')
self.print_stderr(stderr)
return 1
self.print(' Success!')
return 0
def main():
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
'-C',
dest='build_dir',
default='out/Default',
nargs='?',
required=False,
help='The Chromium build directory. This is always relative the root '
'of the Chromium repo.')
parser.add_argument(
'-q',
'--quiet',
dest='quiet',
required=False,
action='store_true',
help='Don\'t print to console. Takes precedence over the verbose '
'flag.')
parser.add_argument(
'-v',
'--verbose',
dest='verbose',
required=False,
action='store_true',
help='Print subprocess output. The quiet flag takes precedence.')
parser.add_argument(
'--should_build',
dest='should_build',
required=False,
action='store_true',
help=
'If the build fails, exits with code that indicates `git bisect run` '
'to abort.')
parser.add_argument('sqlfile',
nargs='?',
help='Path to the sqlite file that repros a crash')
args = parser.parse_args()
sqlTester = SqlTester(args.build_dir, args.quiet, args.verbose,
args.should_build)
return sqlTester.run(args.sqlfile)
if __name__ == '__main__':
sys.exit(main())