anteater/main.py
Hubert Van De Walle b4b0a9f71e Initial commit
2025-05-06 17:35:07 +02:00

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()