129 lines
3.5 KiB
Python
129 lines
3.5 KiB
Python
import click
|
|
import re
|
|
import requests
|
|
import subprocess
|
|
import os
|
|
from shutil import rmtree
|
|
from bs4 import BeautifulSoup
|
|
|
|
worktree_root = "/home/hubert/src/18.0-proj"
|
|
|
|
bundles = {
|
|
"17.0": "17-0-192736",
|
|
"18.0": "18-0-320432",
|
|
}
|
|
|
|
|
|
def _get_bundle_last_page(version: str) -> int:
|
|
bundle = bundles[version]
|
|
url = f"https://runbot.odoo.com/runbot/bundle/{bundle}/page/1000"
|
|
res = requests.get(url)
|
|
assert res.status_code == 200
|
|
soup = BeautifulSoup(res.text, "html.parser")
|
|
text = soup.select_one(".page-item.active").get_text(strip=True)
|
|
return int(text)
|
|
|
|
|
|
def _commits_from_batch(id: int) -> dict:
|
|
url = f"https://runbot.odoo.com/runbot/batch/{id}"
|
|
res = requests.get(url)
|
|
assert res.status_code == 200
|
|
soup = BeautifulSoup(res.text, "html.parser")
|
|
commit_nodes = soup.select("a[title='View Commit on Github']")
|
|
commits = dict()
|
|
assert len(commit_nodes) > 0
|
|
gh_re = re.compile(r"https://github.com/odoo/([\w-]+)/commit/(\w+)")
|
|
for commit in commit_nodes:
|
|
match = gh_re.search(commit["href"])
|
|
repo = match.group(1)
|
|
commit = match.group(2)
|
|
commits[repo] = commit
|
|
|
|
assert "odoo" in commits
|
|
assert "enterprise" in commits
|
|
return commits
|
|
|
|
|
|
def _checkout_commits(commits: dict):
|
|
subprocess.check_call(
|
|
["git", "-C", f"{worktree_root}/odoo", "checkout", commits["odoo"]]
|
|
)
|
|
subprocess.check_call(
|
|
["git", "-C", f"{worktree_root}/enterprise", "checkout", commits["enterprise"]]
|
|
)
|
|
|
|
|
|
def _run_odoo(test_tags: str) -> bool:
|
|
venv_path = f"{worktree_root}/venv-bisect"
|
|
rmtree(venv_path, ignore_errors=True)
|
|
subprocess.check_call(["uv", "venv", "-p", "3.12", venv_path])
|
|
env = os.environ.copy()
|
|
env["VIRTUAL_ENV"] = venv_path
|
|
subprocess.check_call(
|
|
["uv", "pip", "install", "-r", f"{worktree_root}/odoo/requirements.txt"],
|
|
env=env,
|
|
)
|
|
res = subprocess.run(
|
|
["odoo", "--no-patch", "--drop", "--test-tags", test_tags],
|
|
cwd=worktree_root,
|
|
env=env,
|
|
)
|
|
return res.returncode == 0
|
|
|
|
|
|
def _get_batches(bundle, page=1) -> list[int]:
|
|
limit = 3000
|
|
url = f"https://runbot.odoo.com/runbot/bundle/{bundle}?limit={limit}"
|
|
res = requests.get(url)
|
|
assert res.status_code == 200
|
|
soup = BeautifulSoup(res.text, "html.parser")
|
|
batch_nodes = soup.select("div.batch_row")
|
|
batch_url_re = re.compile(r"^/runbot/batch/(\d+)$")
|
|
ids = []
|
|
for node in batch_nodes:
|
|
link = node.select_one("a[title='View Batch']")["href"]
|
|
match = batch_url_re.search(link)
|
|
ids.append(match.group(1))
|
|
return ids
|
|
|
|
|
|
def _find_first_failing_commit(batches: list[int], test_failed):
|
|
low = 0
|
|
high = len(batches)
|
|
|
|
while low < high:
|
|
mid = (low + high) // 2
|
|
if test_failed(batches[mid]):
|
|
high = mid
|
|
else:
|
|
low = mid + 1
|
|
|
|
if low < len(batches):
|
|
return batches[low]
|
|
raise ValueError("404")
|
|
|
|
|
|
def _bisect(bundle: str, test_tags: str):
|
|
batches = list(reversed(_get_batches(bundle)))
|
|
|
|
def test_failed(batch: int):
|
|
commits = _commits_from_batch(batch)
|
|
_checkout_commits(commits)
|
|
return not _run_odoo(test_tags)
|
|
|
|
suspect = _find_first_failing_commit(batches, test_failed)
|
|
if suspect:
|
|
commits = _commits_from_batch(suspect)
|
|
return commits
|
|
|
|
|
|
@click.command()
|
|
@click.option("--version", default="18.0", type=click.Choice(bundles.keys()))
|
|
@click.option("--test-tags")
|
|
def main(version, test_tags):
|
|
print(_bisect(bundles["18.0"], test_tags))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|