1552 lines
62 KiB
Python
1552 lines
62 KiB
Python
import importlib.util
|
|
import io
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from datetime import date, datetime, timezone
|
|
from contextlib import redirect_stdout
|
|
from pathlib import Path
|
|
|
|
|
|
def load_growth_metrics_module():
|
|
script_path = Path(__file__).resolve().parents[1] / "scripts" / "collect_growth_metrics.py"
|
|
spec = importlib.util.spec_from_file_location("collect_growth_metrics", script_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def test_default_integration_prs_include_sglang_omni_fun_asr():
|
|
module = load_growth_metrics_module()
|
|
|
|
assert "sgl-project/sglang-omni#898" in module.DEFAULT_INTEGRATION_PRS
|
|
|
|
|
|
def test_github_headers_falls_back_to_gh_auth_token(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
|
|
monkeypatch.setenv("HOME", "/tmp/funasr-growth-metrics-no-token-home")
|
|
|
|
def fake_run(args, **kwargs):
|
|
assert args == ["gh", "auth", "token"]
|
|
return subprocess.CompletedProcess(args=args, returncode=0, stdout="cli-token\n")
|
|
|
|
monkeypatch.setattr(module.subprocess, "run", fake_run)
|
|
|
|
headers = module.github_headers()
|
|
|
|
assert headers["Authorization"] == "Bearer cli-token"
|
|
|
|
|
|
def test_github_headers_reads_funasr_ops_token_file(monkeypatch, tmp_path):
|
|
module = load_growth_metrics_module()
|
|
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
|
|
monkeypatch.setenv("HOME", str(tmp_path))
|
|
token_path = tmp_path / ".config" / "funasr-ops" / "github_token"
|
|
token_path.parent.mkdir(parents=True)
|
|
token_path.write_text("file-token\n")
|
|
|
|
def fake_run(args, **kwargs):
|
|
raise FileNotFoundError("gh")
|
|
|
|
monkeypatch.setattr(module.subprocess, "run", fake_run)
|
|
|
|
headers = module.github_headers()
|
|
|
|
assert headers["Authorization"] == "Bearer file-token"
|
|
|
|
|
|
def test_default_integration_prs_include_high_visibility_external_queue():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"infiniflow/ragflow#16473",
|
|
"pipecat-ai/pipecat#4844",
|
|
"TEN-framework/ten-framework#2191",
|
|
"activepieces/activepieces#13985",
|
|
"Uberi/speech_recognition#903",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.DEFAULT_INTEGRATION_PRS))
|
|
|
|
|
|
def test_default_integration_prs_include_new_growth_lanes():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"Significant-Gravitas/AutoGPT#13500",
|
|
"huggingface/speech-to-speech#319",
|
|
"run-llama/llama_index#21958",
|
|
"run-llama/llama_index#21996",
|
|
"mem0ai/mem0#5571",
|
|
"mudler/LocalAI#10090",
|
|
"agno-agi/agno#8501",
|
|
"GetStream/Vision-Agents#606",
|
|
"ai4s-research/awesome-ai-for-science#69",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.DEFAULT_INTEGRATION_PRS))
|
|
|
|
|
|
def test_default_integration_prs_include_video_discovery_lanes():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"tmoroney/auto-subs#629",
|
|
"mahseema/awesome-ai-tools#1689",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.DEFAULT_INTEGRATION_PRS))
|
|
|
|
|
|
def test_default_integration_prs_include_voice_agent_and_ml_discovery_lanes():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"livekit/agents#6176",
|
|
"lukasmasuch/best-of-ml-python#455",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.DEFAULT_INTEGRATION_PRS))
|
|
|
|
|
|
def test_default_integration_prs_include_high_star_awesome_discovery_lanes():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"vinta/awesome-python#3246",
|
|
"fighting41love/funNLP#478",
|
|
"josephmisiti/awesome-machine-learning#1339",
|
|
"RVC-Boss/GPT-SoVITS#2801",
|
|
"jobbole/awesome-python-cn#141",
|
|
"ChristosChristofidis/awesome-deep-learning#317",
|
|
"Hannibal046/Awesome-LLM#623",
|
|
"AiHubCN/Awesome-Chinese-LLM#103",
|
|
"pluja/awesome-privacy#836",
|
|
"BradyFU/Awesome-Multimodal-Large-Language-Models#280",
|
|
"mahmoud/awesome-python-applications#227",
|
|
"bharathgs/Awesome-pytorch-list#164",
|
|
"owainlewis/awesome-artificial-intelligence#243",
|
|
"steven2358/awesome-generative-ai#821",
|
|
"WangRongsheng/awesome-LLM-resources#162",
|
|
"crownpku/Awesome-Chinese-NLP#32",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.DEFAULT_INTEGRATION_PRS))
|
|
|
|
|
|
def test_high_star_awesome_discovery_lanes_wait_for_maintainer_review():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"vinta/awesome-python#3246",
|
|
"fighting41love/funNLP#478",
|
|
"josephmisiti/awesome-machine-learning#1339",
|
|
"RVC-Boss/GPT-SoVITS#2801",
|
|
"jobbole/awesome-python-cn#141",
|
|
"ChristosChristofidis/awesome-deep-learning#317",
|
|
"Hannibal046/Awesome-LLM#623",
|
|
"AiHubCN/Awesome-Chinese-LLM#103",
|
|
"pluja/awesome-privacy#836",
|
|
"BradyFU/Awesome-Multimodal-Large-Language-Models#280",
|
|
"mahmoud/awesome-python-applications#227",
|
|
"bharathgs/Awesome-pytorch-list#164",
|
|
"owainlewis/awesome-artificial-intelligence#243",
|
|
"steven2358/awesome-generative-ai#821",
|
|
"WangRongsheng/awesome-LLM-resources#162",
|
|
"crownpku/Awesome-Chinese-NLP#32",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.KNOWN_ASSISTED_REVIEW_REQUESTS))
|
|
|
|
|
|
def test_default_integration_prs_include_missing_validated_discovery_lanes():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"mahseema/awesome-ai-tools#1403",
|
|
"INTERMT/Awesome-PyTorch-Chinese#5",
|
|
"krzjoa/awesome-python-data-science#99",
|
|
"zzw922cn/awesome-speech-recognition-speech-synthesis-papers#27",
|
|
"Osmantic/ODS#1639",
|
|
"faroit/awesome-python-scientific-audio#85",
|
|
"joewongjc/type4me#207",
|
|
"ga642381/speech-trident#31",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.DEFAULT_INTEGRATION_PRS))
|
|
|
|
|
|
def test_missing_validated_discovery_lanes_wait_for_maintainer_review():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"mahseema/awesome-ai-tools#1403",
|
|
"INTERMT/Awesome-PyTorch-Chinese#5",
|
|
"krzjoa/awesome-python-data-science#99",
|
|
"zzw922cn/awesome-speech-recognition-speech-synthesis-papers#27",
|
|
"Osmantic/ODS#1639",
|
|
"faroit/awesome-python-scientific-audio#85",
|
|
"joewongjc/type4me#207",
|
|
"ga642381/speech-trident#31",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.KNOWN_ASSISTED_REVIEW_REQUESTS))
|
|
|
|
|
|
def test_default_integration_prs_include_mcp_discovery_lanes():
|
|
module = load_growth_metrics_module()
|
|
|
|
assert "punkpeye/awesome-mcp-servers#7153" in module.DEFAULT_INTEGRATION_PRS
|
|
|
|
|
|
def test_default_integration_prs_include_speech_server_lanes():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"speaches-ai/speaches#658",
|
|
"getpaseo/paseo#1634",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.DEFAULT_INTEGRATION_PRS))
|
|
|
|
|
|
def test_default_integration_prs_exclude_archived_integration_prs():
|
|
module = load_growth_metrics_module()
|
|
|
|
assert "xinnan-tech/xiaozhi-esp32-server#3255" not in module.DEFAULT_INTEGRATION_PRS
|
|
|
|
|
|
def test_collect_github_repo_metrics_splits_open_issues_and_pull_requests(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/modelscope/FunASR":
|
|
return {
|
|
"stargazers_count": 18716,
|
|
"forks_count": 3100,
|
|
"subscribers_count": 210,
|
|
"open_issues_count": 5,
|
|
"default_branch": "main",
|
|
"pushed_at": "2026-06-30T02:26:33Z",
|
|
"html_url": "https://github.com/modelscope/FunASR",
|
|
}
|
|
if url == "https://api.github.com/repos/modelscope/FunASR/pulls?state=open&per_page=100":
|
|
return [{"number": 3056}, {"number": 3057}]
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_github_repo_metrics("modelscope/FunASR")
|
|
|
|
assert metrics["open_items"] == 5
|
|
assert metrics["open_pull_requests"] == 2
|
|
assert metrics["open_issues"] == 3
|
|
|
|
|
|
def test_collect_ecosystem_metrics_sums_repositories_and_target_gap(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
github_payloads = {
|
|
"modelscope/FunASR": {
|
|
"stargazers_count": 18714,
|
|
"forks_count": 3100,
|
|
"subscribers_count": 210,
|
|
"open_issues_count": 3,
|
|
"default_branch": "main",
|
|
"pushed_at": "2026-06-30T01:48:35Z",
|
|
"html_url": "https://github.com/modelscope/FunASR",
|
|
},
|
|
"FunAudioLLM/Fun-ASR": {
|
|
"stargazers_count": 1318,
|
|
"forks_count": 120,
|
|
"subscribers_count": 20,
|
|
"open_issues_count": 0,
|
|
"default_branch": "main",
|
|
"pushed_at": "2026-06-29T13:24:05Z",
|
|
"html_url": "https://github.com/FunAudioLLM/Fun-ASR",
|
|
},
|
|
"FunAudioLLM/SenseVoice": {
|
|
"stargazers_count": 8718,
|
|
"forks_count": 800,
|
|
"subscribers_count": 90,
|
|
"open_issues_count": 0,
|
|
"default_branch": "main",
|
|
"pushed_at": "2026-06-29T13:24:06Z",
|
|
"html_url": "https://github.com/FunAudioLLM/SenseVoice",
|
|
},
|
|
"modelscope/FunClip": {
|
|
"stargazers_count": 5872,
|
|
"forks_count": 500,
|
|
"subscribers_count": 70,
|
|
"open_issues_count": 0,
|
|
"default_branch": "main",
|
|
"pushed_at": "2026-06-29T20:47:23Z",
|
|
"html_url": "https://github.com/modelscope/FunClip",
|
|
},
|
|
}
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url.endswith("/pulls?state=open&per_page=100"):
|
|
return []
|
|
if url.startswith("https://api.github.com/repos/"):
|
|
repo = url.removeprefix("https://api.github.com/repos/")
|
|
return github_payloads[repo]
|
|
if url == "https://pypi.org/pypi/funasr/json":
|
|
return {"info": {"version": "1.3.14", "summary": "FunASR", "project_url": "https://pypi.org/project/funasr/"}}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_ecosystem_metrics(
|
|
["modelscope/FunASR", "FunAudioLLM/Fun-ASR", "FunAudioLLM/SenseVoice", "modelscope/FunClip"],
|
|
package="funasr",
|
|
baseline_stars=31224,
|
|
target_additional_stars=20000,
|
|
)
|
|
|
|
assert metrics["ecosystem"]["total_stars"] == 34622
|
|
assert metrics["ecosystem"]["baseline_stars"] == 31224
|
|
assert metrics["ecosystem"]["added_stars"] == 3398
|
|
assert metrics["ecosystem"]["remaining_to_target"] == 16602
|
|
assert [repo["repo"] for repo in metrics["ecosystem"]["repositories"]] == [
|
|
"modelscope/FunASR",
|
|
"FunAudioLLM/Fun-ASR",
|
|
"FunAudioLLM/SenseVoice",
|
|
"modelscope/FunClip",
|
|
]
|
|
|
|
|
|
def test_collect_ecosystem_metrics_calculates_daily_target(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url.endswith("/pulls?state=open&per_page=100"):
|
|
return []
|
|
if url.startswith("https://api.github.com/repos/"):
|
|
repo = url.removeprefix("https://api.github.com/repos/")
|
|
stars = {
|
|
"modelscope/FunASR": 18715,
|
|
"FunAudioLLM/Fun-ASR": 1318,
|
|
"FunAudioLLM/SenseVoice": 8718,
|
|
"modelscope/FunClip": 5872,
|
|
}[repo]
|
|
return {
|
|
"stargazers_count": stars,
|
|
"forks_count": 0,
|
|
"subscribers_count": 0,
|
|
"open_issues_count": 0,
|
|
"default_branch": "main",
|
|
"pushed_at": "2026-06-30T00:00:00Z",
|
|
"html_url": f"https://github.com/{repo}",
|
|
}
|
|
if url == "https://pypi.org/pypi/funasr/json":
|
|
return {"info": {"version": "1.3.14", "summary": "FunASR", "project_url": "https://pypi.org/project/funasr/"}}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_ecosystem_metrics(
|
|
["modelscope/FunASR", "FunAudioLLM/Fun-ASR", "FunAudioLLM/SenseVoice", "modelscope/FunClip"],
|
|
package="funasr",
|
|
baseline_stars=31224,
|
|
target_additional_stars=20000,
|
|
target_date="2026-09-30",
|
|
today=date(2026, 6, 30),
|
|
)
|
|
|
|
assert metrics["ecosystem"]["target_date"] == "2026-09-30"
|
|
assert metrics["ecosystem"]["days_remaining"] == 92
|
|
assert metrics["ecosystem"]["required_daily_average"] == 181
|
|
|
|
|
|
def test_collect_integration_metrics_summarizes_pull_request_checks(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/huggingface/transformers/pulls/46180":
|
|
return {
|
|
"number": 46180,
|
|
"title": "Add Fun-ASR-Nano model",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "clean",
|
|
"html_url": "https://github.com/huggingface/transformers/pull/46180",
|
|
"updated_at": "2026-06-29T23:45:57Z",
|
|
"head": {"sha": "8a744ec", "ref": "add-fun-asr-nano"},
|
|
"base": {"ref": "main"},
|
|
"user": {"login": "LauraGPT"},
|
|
}
|
|
if url == "https://api.github.com/repos/huggingface/transformers/commits/8a744ec/status":
|
|
return {"state": "success", "statuses": []}
|
|
if url == "https://api.github.com/repos/huggingface/transformers/commits/8a744ec/check-runs?per_page=100":
|
|
return {
|
|
"total_count": 3,
|
|
"check_runs": [
|
|
{"name": "pr-ci / Check code quality", "status": "completed", "conclusion": "success"},
|
|
{
|
|
"name": "pr-ci / tests_processors / tests_processors [shard 3/8]",
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"html_url": "https://github.com/huggingface/transformers/actions/runs/1/job/2",
|
|
},
|
|
{"name": "pr-ci / slow tests", "status": "queued", "conclusion": None},
|
|
],
|
|
}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(
|
|
["huggingface/transformers#46180"],
|
|
now=datetime(2026, 7, 2, 0, 0, 0, tzinfo=timezone.utc),
|
|
)
|
|
|
|
integration = metrics["integrations"][0]
|
|
assert integration["pr"] == "huggingface/transformers#46180"
|
|
assert integration["title"] == "Add Fun-ASR-Nano model"
|
|
assert integration["next_action"] == "fix checks"
|
|
assert integration["updated_age_days"] == 2
|
|
assert integration["mergeable"] is True
|
|
assert integration["checks"]["state"] == "failure"
|
|
assert integration["checks"]["total_check_runs"] == 3
|
|
assert integration["checks"]["failed_check_runs"] == [
|
|
{
|
|
"name": "pr-ci / tests_processors / tests_processors [shard 3/8]",
|
|
"conclusion": "failure",
|
|
"url": "https://github.com/huggingface/transformers/actions/runs/1/job/2",
|
|
}
|
|
]
|
|
assert integration["checks"]["pending_check_runs"] == [
|
|
{"name": "pr-ci / slow tests", "status": "queued", "url": None}
|
|
]
|
|
|
|
|
|
def test_collect_integration_metrics_classifies_known_external_ci_failure(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/huggingface/transformers/pulls/46180":
|
|
return {
|
|
"number": 46180,
|
|
"title": "Add Fun-ASR-Nano model",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "blocked",
|
|
"html_url": "https://github.com/huggingface/transformers/pull/46180",
|
|
"updated_at": "2026-06-30T04:24:15Z",
|
|
"head": {"sha": "8ed336b", "ref": "add-fun-asr-nano"},
|
|
"base": {"ref": "main"},
|
|
"user": {"login": "LauraGPT"},
|
|
}
|
|
if url == "https://api.github.com/repos/huggingface/transformers/commits/8ed336b/status":
|
|
return {"state": "success", "statuses": []}
|
|
if url == "https://api.github.com/repos/huggingface/transformers/commits/8ed336b/check-runs?per_page=100":
|
|
return {
|
|
"total_count": 99,
|
|
"check_runs": [
|
|
{
|
|
"name": "pr-ci / tests_processors / tests_processors [shard 3/8]",
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"html_url": "https://github.com/huggingface/transformers/actions/runs/28419525291/job/84209871338",
|
|
},
|
|
{
|
|
"name": "pr-ci / PR CI status",
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"html_url": "https://github.com/huggingface/transformers/actions/runs/28419525291/job/84211351756",
|
|
},
|
|
],
|
|
}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(["huggingface/transformers#46180"])
|
|
|
|
integration = metrics["integrations"][0]
|
|
assert integration["checks"]["state"] == "failure"
|
|
assert integration["known_external_failure_reason"] == (
|
|
"LightOnOCR shared hub-cache read-only failure; PR CI status is the aggregate failure"
|
|
)
|
|
assert integration["known_external_failure_action"] == "wait for maintainer rerun"
|
|
assert integration["next_action"] == "wait for maintainer rerun"
|
|
|
|
|
|
def test_collect_integration_metrics_handles_known_external_ci_aggregate_race(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/huggingface/transformers/pulls/46180":
|
|
return {
|
|
"number": 46180,
|
|
"title": "Add Fun-ASR-Nano model",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "blocked",
|
|
"html_url": "https://github.com/huggingface/transformers/pull/46180",
|
|
"updated_at": "2026-06-30T04:15:07Z",
|
|
"head": {"sha": "8ed336b", "ref": "add-fun-asr-nano"},
|
|
"base": {"ref": "main"},
|
|
"user": {"login": "LauraGPT"},
|
|
}
|
|
if url == "https://api.github.com/repos/huggingface/transformers/commits/8ed336b/status":
|
|
return {"state": "success", "statuses": []}
|
|
if url == "https://api.github.com/repos/huggingface/transformers/commits/8ed336b/check-runs?per_page=100":
|
|
return {
|
|
"total_count": 98,
|
|
"check_runs": [
|
|
{
|
|
"name": "pr-ci / tests_processors / tests_processors [shard 3/8]",
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"html_url": "https://github.com/huggingface/transformers/actions/runs/28419525291/job/84209871338",
|
|
},
|
|
],
|
|
}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(["huggingface/transformers#46180"])
|
|
|
|
integration = metrics["integrations"][0]
|
|
assert integration["known_external_failure_reason"] == (
|
|
"LightOnOCR shared hub-cache read-only failure; PR CI status is the aggregate failure"
|
|
)
|
|
assert integration["known_external_failure_action"] == "wait for maintainer rerun"
|
|
assert integration["next_action"] == "wait for maintainer rerun"
|
|
|
|
|
|
def test_format_ecosystem_markdown_includes_open_pull_requests():
|
|
module = load_growth_metrics_module()
|
|
metrics = {
|
|
"collected_at_utc": "2026-06-30T02:30:00+00:00",
|
|
"ecosystem": {
|
|
"repositories": [
|
|
{
|
|
"repo": "modelscope/FunASR",
|
|
"stars": 18716,
|
|
"forks": 3100,
|
|
"open_issues": 3,
|
|
"open_pull_requests": 2,
|
|
"pushed_at": "2026-06-30T02:26:33Z",
|
|
"html_url": "https://github.com/modelscope/FunASR",
|
|
}
|
|
],
|
|
"total_stars": 18716,
|
|
"baseline_stars": 31224,
|
|
"added_stars": -12508,
|
|
"target_additional_stars": 20000,
|
|
"remaining_to_target": 32508,
|
|
"target_date": "2026-09-30",
|
|
"days_remaining": 92,
|
|
"required_daily_average": 354,
|
|
},
|
|
"pypi": {"package": "funasr", "version": "1.3.14", "project_url": "https://pypi.org/project/funasr/"},
|
|
}
|
|
|
|
output = module.format_ecosystem_markdown(metrics)
|
|
|
|
assert "| Repository | Stars | Forks | Open issues | Open PRs | Last push |" in output
|
|
assert "| [modelscope/FunASR](https://github.com/modelscope/FunASR) | 18,716 | 3,100 | 3 | 2 |" in output
|
|
|
|
|
|
def test_collect_integration_metrics_recommends_review_for_clean_success_pr(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/example/clean-pr/pulls/42":
|
|
return {
|
|
"number": 42,
|
|
"title": "Add FunASR",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "clean",
|
|
"html_url": "https://github.com/example/clean-pr/pull/42",
|
|
"updated_at": "2026-06-30T06:44:34Z",
|
|
"head": {"sha": "9a19b5e", "ref": "add-funasr"},
|
|
"base": {"ref": "main"},
|
|
"user": {"login": "LauraGPT"},
|
|
}
|
|
if url == "https://api.github.com/repos/example/clean-pr/commits/9a19b5e/status":
|
|
return {"state": "success", "statuses": []}
|
|
if url == "https://api.github.com/repos/example/clean-pr/commits/9a19b5e/check-runs?per_page=100":
|
|
return {
|
|
"total_count": 1,
|
|
"check_runs": [
|
|
{"name": "validate", "status": "completed", "conclusion": "success"},
|
|
],
|
|
}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(["example/clean-pr#42"])
|
|
|
|
assert metrics["integrations"][0]["next_action"] == "request review"
|
|
|
|
|
|
def test_known_assisted_review_requests_include_validated_discovery_prs():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"mahseema/awesome-ai-tools#1689",
|
|
"ai4s-research/awesome-ai-for-science#69",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.KNOWN_ASSISTED_REVIEW_REQUESTS))
|
|
|
|
|
|
def test_known_assisted_review_requests_include_validated_review_gate_prs():
|
|
module = load_growth_metrics_module()
|
|
|
|
expected_prs = {
|
|
"livekit/agents#6176",
|
|
"run-llama/llama_index#21958",
|
|
"run-llama/llama_index#21996",
|
|
"mudler/LocalAI#10090",
|
|
"ray-project/ray#64053",
|
|
"agno-agi/agno#8501",
|
|
"pipecat-ai/pipecat#4844",
|
|
"GetStream/Vision-Agents#606",
|
|
"Uberi/speech_recognition#903",
|
|
"huggingface/speech-to-speech#319",
|
|
"speaches-ai/speaches#658",
|
|
}
|
|
|
|
assert expected_prs.issubset(set(module.KNOWN_ASSISTED_REVIEW_REQUESTS))
|
|
|
|
|
|
def test_collect_integration_metrics_marks_known_assisted_review_request(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/lukasmasuch/best-of-ml-python/pulls/455":
|
|
return {
|
|
"number": 455,
|
|
"title": "Add FunASR",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "clean",
|
|
"html_url": "https://github.com/lukasmasuch/best-of-ml-python/pull/455",
|
|
"updated_at": "2026-06-30T09:16:59Z",
|
|
"head": {"sha": "48f3070", "ref": "add-funasr"},
|
|
"base": {"ref": "main"},
|
|
"user": {"login": "LauraGPT"},
|
|
}
|
|
if url == "https://api.github.com/repos/lukasmasuch/best-of-ml-python/commits/48f3070/status":
|
|
return {"state": "success", "statuses": []}
|
|
if url == "https://api.github.com/repos/lukasmasuch/best-of-ml-python/commits/48f3070/check-runs?per_page=100":
|
|
return {
|
|
"total_count": 1,
|
|
"check_runs": [
|
|
{"name": "tests", "status": "completed", "conclusion": "success"},
|
|
],
|
|
}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(["lukasmasuch/best-of-ml-python#455"])
|
|
|
|
integration = metrics["integrations"][0]
|
|
assert integration["known_assisted_review_reason"] == "review evidence already posted; avoid duplicate pings"
|
|
assert integration["next_action"] == "wait for maintainer review"
|
|
|
|
|
|
def test_collect_integration_metrics_applies_known_review_gate(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/punkpeye/awesome-mcp-servers/pulls/7153":
|
|
return {
|
|
"number": 7153,
|
|
"title": "Add FunASR speech recognition MCP server",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "clean",
|
|
"html_url": "https://github.com/punkpeye/awesome-mcp-servers/pull/7153",
|
|
"updated_at": "2026-06-16T04:21:55Z",
|
|
"head": {"sha": "d28680e", "ref": "add-funasr-mcp-server"},
|
|
"base": {"ref": "main"},
|
|
"user": {"login": "LauraGPT"},
|
|
}
|
|
if url == "https://api.github.com/repos/punkpeye/awesome-mcp-servers/commits/d28680e/status":
|
|
return {"state": "success", "statuses": []}
|
|
if url == "https://api.github.com/repos/punkpeye/awesome-mcp-servers/commits/d28680e/check-runs?per_page=100":
|
|
return {
|
|
"total_count": 2,
|
|
"check_runs": [
|
|
{"name": "check-submission", "status": "completed", "conclusion": "success"},
|
|
{"name": "welcome", "status": "completed", "conclusion": "skipped"},
|
|
],
|
|
}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(["punkpeye/awesome-mcp-servers#7153"])
|
|
|
|
integration = metrics["integrations"][0]
|
|
assert integration["checks"]["state"] == "success"
|
|
assert integration["known_review_gate_reason"] == "Glama listing and score badge required before review"
|
|
assert integration["next_action"] == "submit Glama"
|
|
|
|
|
|
def test_collect_integration_metrics_surfaces_pending_cla_status(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/activepieces/activepieces/pulls/13985":
|
|
return {
|
|
"number": 13985,
|
|
"title": "feat: add FunASR speech recognition piece",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "blocked",
|
|
"html_url": "https://github.com/activepieces/activepieces/pull/13985",
|
|
"updated_at": "2026-06-30T01:17:07Z",
|
|
"head": {"sha": "f9d22ee", "ref": "funasr/activepieces-13450-conflict-fix"},
|
|
"base": {"ref": "main"},
|
|
"user": {"login": "LauraGPT"},
|
|
}
|
|
if url == "https://api.github.com/repos/activepieces/activepieces/commits/f9d22ee/status":
|
|
return {
|
|
"state": "pending",
|
|
"statuses": [
|
|
{
|
|
"context": "license/cla",
|
|
"state": "pending",
|
|
"target_url": "https://cla-assistant.io/activepieces/activepieces?pullRequest=13985",
|
|
}
|
|
],
|
|
}
|
|
if url == "https://api.github.com/repos/activepieces/activepieces/commits/f9d22ee/check-runs?per_page=100":
|
|
return {
|
|
"total_count": 1,
|
|
"check_runs": [
|
|
{"name": "GitGuardian Security Checks", "status": "completed", "conclusion": "success"},
|
|
],
|
|
}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(["activepieces/activepieces#13985"])
|
|
|
|
integration = metrics["integrations"][0]
|
|
assert integration["next_action"] == "wait for author CLA"
|
|
assert integration["checks"]["pending_check_runs"] == [
|
|
{
|
|
"name": "license/cla",
|
|
"status": "pending",
|
|
"url": "https://cla-assistant.io/activepieces/activepieces?pullRequest=13985",
|
|
}
|
|
]
|
|
|
|
|
|
def test_collect_integration_metrics_classifies_autogpt_process_gate(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/Significant-Gravitas/AutoGPT/pulls/13500":
|
|
return {
|
|
"number": 13500,
|
|
"title": "Support configurable transcription endpoints",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "blocked",
|
|
"html_url": "https://github.com/Significant-Gravitas/AutoGPT/pull/13500",
|
|
"updated_at": "2026-07-07T21:13:23Z",
|
|
"head": {"sha": "ed9f137", "ref": "codex/openai-compatible-stt"},
|
|
"base": {"ref": "dev"},
|
|
"user": {"login": "LauraGPT"},
|
|
}
|
|
if url == "https://api.github.com/repos/Significant-Gravitas/AutoGPT/commits/ed9f137/status":
|
|
return {
|
|
"state": "failure",
|
|
"statuses": [
|
|
{
|
|
"context": "Vercel",
|
|
"state": "failure",
|
|
"target_url": "https://vercel.com/git/authorize?team=Significant%20Gravitas",
|
|
},
|
|
{
|
|
"context": "license/cla",
|
|
"state": "pending",
|
|
"target_url": "https://cla-assistant.io/Significant-Gravitas/AutoGPT?pullRequest=13500",
|
|
},
|
|
],
|
|
}
|
|
if url == "https://api.github.com/repos/Significant-Gravitas/AutoGPT/commits/ed9f137/check-runs?per_page=100":
|
|
return {
|
|
"total_count": 4,
|
|
"check_runs": [
|
|
{"name": "check API types", "status": "completed", "conclusion": "success"},
|
|
{"name": "lint", "status": "completed", "conclusion": "success"},
|
|
{"name": "integration_test", "status": "completed", "conclusion": "success"},
|
|
{"name": "end-to-end tests", "status": "completed", "conclusion": "success"},
|
|
],
|
|
}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(["Significant-Gravitas/AutoGPT#13500"])
|
|
|
|
integration = metrics["integrations"][0]
|
|
assert integration["known_review_gate_reason"] == (
|
|
"CLA must be completed by an authorized author; Vercel preview also requires AutoGPT team authorization"
|
|
)
|
|
assert integration["next_action"] == "wait for author CLA"
|
|
assert {check["name"] for check in integration["checks"]["failed_check_runs"]} == {"Vercel"}
|
|
assert {check["name"] for check in integration["checks"]["pending_check_runs"]} == {"license/cla"}
|
|
|
|
|
|
def test_collect_integration_metrics_classifies_review_bot_failure(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/TEN-framework/ten-framework/pulls/2191":
|
|
return {
|
|
"number": 2191,
|
|
"title": "feat: add funasr_asr_python local FunASR ASR extension",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "blocked",
|
|
"html_url": "https://github.com/TEN-framework/ten-framework/pull/2191",
|
|
"updated_at": "2026-06-29T23:02:41Z",
|
|
"head": {"sha": "43cbedc", "ref": "add-funasr-asr-extension"},
|
|
"base": {"ref": "main"},
|
|
"user": {"login": "LauraGPT"},
|
|
}
|
|
if url == "https://api.github.com/repos/TEN-framework/ten-framework/commits/43cbedc/status":
|
|
return {"state": "pending", "statuses": []}
|
|
if url == "https://api.github.com/repos/TEN-framework/ten-framework/commits/43cbedc/check-runs?per_page=100":
|
|
return {
|
|
"total_count": 1,
|
|
"check_runs": [
|
|
{
|
|
"name": "claude-review",
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"html_url": "https://github.com/TEN-framework/ten-framework/actions/runs/27985905295/job/82827178776",
|
|
},
|
|
],
|
|
}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(["TEN-framework/ten-framework#2191"])
|
|
|
|
integration = metrics["integrations"][0]
|
|
assert integration["known_review_gate_reason"] == (
|
|
"Claude review action requires maintainer permissions for fork PRs"
|
|
)
|
|
assert integration["next_action"] == "wait for maintainer review"
|
|
assert integration["checks"]["failed_check_runs"][0]["name"] == "claude-review"
|
|
|
|
|
|
def test_collect_integration_metrics_classifies_vercel_authorization_gate(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/mem0ai/mem0/pulls/5571":
|
|
return {
|
|
"number": 5571,
|
|
"title": "feat: add optional FunASR audio transcription helper",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "blocked",
|
|
"html_url": "https://github.com/mem0ai/mem0/pull/5571",
|
|
"updated_at": "2026-06-30T05:20:27Z",
|
|
"head": {"sha": "d0b1cfa", "ref": "funasr-audio-helper"},
|
|
"base": {"ref": "main"},
|
|
"user": {"login": "anneheartrecord"},
|
|
}
|
|
if url == "https://api.github.com/repos/mem0ai/mem0/commits/d0b1cfa/status":
|
|
return {
|
|
"state": "failure",
|
|
"statuses": [
|
|
{
|
|
"context": "Vercel",
|
|
"state": "failure",
|
|
"target_url": "https://vercel.com/git/authorize?team=Mem0&slug=mem0&pullRequest=5571",
|
|
}
|
|
],
|
|
}
|
|
if url == "https://api.github.com/repos/mem0ai/mem0/commits/d0b1cfa/check-runs?per_page=100":
|
|
return {"total_count": 0, "check_runs": []}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(["mem0ai/mem0#5571"])
|
|
|
|
integration = metrics["integrations"][0]
|
|
assert integration["next_action"] == "wait for preview authorization"
|
|
assert integration["checks"]["failed_check_runs"] == [
|
|
{
|
|
"name": "Vercel",
|
|
"conclusion": "failure",
|
|
"url": "https://vercel.com/git/authorize?team=Mem0&slug=mem0&pullRequest=5571",
|
|
}
|
|
]
|
|
|
|
|
|
def test_recommend_integration_action_treats_blocked_unknown_checks_as_review_gate():
|
|
module = load_growth_metrics_module()
|
|
|
|
action = module.recommend_integration_action(
|
|
{"state": "open", "draft": False, "mergeable_state": "blocked"},
|
|
{"state": "unknown", "failed_check_runs": [], "pending_check_runs": []},
|
|
)
|
|
|
|
assert action == "review gate"
|
|
|
|
|
|
def test_recommend_integration_action_waits_for_maintainer_after_assisted_blocked_review_gate():
|
|
module = load_growth_metrics_module()
|
|
|
|
action = module.recommend_integration_action(
|
|
{"state": "open", "draft": False, "mergeable_state": "blocked"},
|
|
{"state": "success", "failed_check_runs": [], "pending_check_runs": []},
|
|
known_assisted_review_reason="review evidence already posted; avoid duplicate pings",
|
|
)
|
|
|
|
assert action == "wait for maintainer review"
|
|
|
|
|
|
def test_recommend_integration_action_treats_clean_unknown_checks_as_request_review():
|
|
module = load_growth_metrics_module()
|
|
|
|
action = module.recommend_integration_action(
|
|
{"state": "open", "draft": False, "mergeable_state": "clean"},
|
|
{"state": "unknown", "failed_check_runs": [], "pending_check_runs": []},
|
|
)
|
|
|
|
assert action == "request review"
|
|
|
|
|
|
def test_recommend_integration_action_treats_dirty_merge_state_as_conflict_resolution():
|
|
module = load_growth_metrics_module()
|
|
|
|
action = module.recommend_integration_action(
|
|
{"state": "open", "draft": False, "mergeable_state": "dirty"},
|
|
{"state": "unknown", "failed_check_runs": [], "pending_check_runs": []},
|
|
)
|
|
|
|
assert action == "resolve conflicts"
|
|
|
|
|
|
def test_recommend_integration_action_prioritizes_dirty_merge_state_over_failed_checks():
|
|
module = load_growth_metrics_module()
|
|
|
|
action = module.recommend_integration_action(
|
|
{"state": "open", "draft": False, "mergeable_state": "dirty"},
|
|
{
|
|
"state": "failure",
|
|
"failed_check_runs": [{"name": "ci", "conclusion": "failure"}],
|
|
"pending_check_runs": [],
|
|
},
|
|
)
|
|
|
|
assert action == "resolve conflicts"
|
|
|
|
|
|
def test_recommend_integration_action_prioritizes_dirty_merge_state_over_pending_checks():
|
|
module = load_growth_metrics_module()
|
|
|
|
action = module.recommend_integration_action(
|
|
{"state": "open", "draft": False, "mergeable_state": "dirty"},
|
|
{
|
|
"state": "pending",
|
|
"failed_check_runs": [],
|
|
"pending_check_runs": [{"name": "ci", "status": "queued"}],
|
|
},
|
|
)
|
|
|
|
assert action == "resolve conflicts"
|
|
|
|
|
|
def test_collect_integration_metrics_treats_empty_pending_status_as_review_gate(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/sgl-project/sglang-omni/pulls/898":
|
|
return {
|
|
"number": 898,
|
|
"title": "Add Fun-ASR serving support",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "blocked",
|
|
"html_url": "https://github.com/sgl-project/sglang-omni/pull/898",
|
|
"updated_at": "2026-06-30T04:48:08Z",
|
|
"head": {"sha": "c081a79", "ref": "funasr-serving"},
|
|
"base": {"ref": "main"},
|
|
"user": {"login": "PoTaTo-Mika"},
|
|
}
|
|
if url == "https://api.github.com/repos/sgl-project/sglang-omni/commits/c081a79/status":
|
|
return {"state": "pending", "statuses": []}
|
|
if url == "https://api.github.com/repos/sgl-project/sglang-omni/commits/c081a79/check-runs?per_page=100":
|
|
return {"total_count": 0, "check_runs": []}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_integration_metrics(["sgl-project/sglang-omni#898"])
|
|
|
|
integration = metrics["integrations"][0]
|
|
assert integration["checks"]["state"] == "unknown"
|
|
assert integration["checks"]["pending_check_runs"] == []
|
|
assert integration["next_action"] == "wait for contributor conflict resolution"
|
|
|
|
|
|
def test_format_integration_markdown_includes_update_age_and_next_action():
|
|
module = load_growth_metrics_module()
|
|
metrics = {
|
|
"collected_at_utc": "2026-07-02T00:00:00+00:00",
|
|
"integrations": [
|
|
{
|
|
"pr": "huggingface/transformers#46180",
|
|
"html_url": "https://github.com/huggingface/transformers/pull/46180",
|
|
"state": "open",
|
|
"mergeable_state": "blocked",
|
|
"repo_stars": 155_000,
|
|
"repo_forks": 31_000,
|
|
"updated_at": "2026-06-29T23:45:57Z",
|
|
"updated_age_days": 2,
|
|
"next_action": "fix checks",
|
|
"checks": {"state": "failure", "failed_check_runs": [], "pending_check_runs": []},
|
|
}
|
|
],
|
|
}
|
|
|
|
output = module.format_integration_markdown(metrics)
|
|
|
|
assert "| Pull request | Stars | Forks | State | Mergeable | Checks | Failed | Pending | Age | Action | Updated |" in output
|
|
assert (
|
|
"| [huggingface/transformers#46180](https://github.com/huggingface/transformers/pull/46180) | "
|
|
"155,000 | 31,000 | open | blocked | failure | 0 | 0 | 2d | fix checks | `2026-06-29T23:45:57Z` |"
|
|
) in output
|
|
|
|
|
|
def test_format_integration_markdown_includes_known_external_failure_reason():
|
|
module = load_growth_metrics_module()
|
|
metrics = {
|
|
"collected_at_utc": "2026-07-02T00:00:00+00:00",
|
|
"integrations": [
|
|
{
|
|
"pr": "huggingface/transformers#46180",
|
|
"html_url": "https://github.com/huggingface/transformers/pull/46180",
|
|
"state": "open",
|
|
"mergeable_state": "blocked",
|
|
"repo_stars": 162_000,
|
|
"repo_forks": 33_000,
|
|
"updated_at": "2026-06-30T04:24:15Z",
|
|
"updated_age_days": 0,
|
|
"next_action": "request rerun",
|
|
"known_external_failure_reason": (
|
|
"LightOnOCR shared hub-cache read-only failure; PR CI status is the aggregate failure"
|
|
),
|
|
"checks": {
|
|
"state": "failure",
|
|
"failed_check_runs": [
|
|
{
|
|
"name": "pr-ci / tests_processors / tests_processors [shard 3/8]",
|
|
"conclusion": "failure",
|
|
"url": "https://github.com/huggingface/transformers/actions/runs/28419525291/job/84209871338",
|
|
}
|
|
],
|
|
"pending_check_runs": [],
|
|
},
|
|
}
|
|
],
|
|
}
|
|
|
|
output = module.format_integration_markdown(metrics)
|
|
|
|
assert (
|
|
"- Known external failure: LightOnOCR shared hub-cache read-only failure; "
|
|
"PR CI status is the aggregate failure"
|
|
) in output
|
|
|
|
|
|
def test_format_integration_markdown_marks_missing_exposure_metrics_as_unavailable():
|
|
module = load_growth_metrics_module()
|
|
metrics = {
|
|
"collected_at_utc": "2026-07-02T00:00:00+00:00",
|
|
"integrations": [
|
|
{
|
|
"pr": "deleted-org/deleted-repo#1",
|
|
"html_url": "https://github.com/deleted-org/deleted-repo/pull/1",
|
|
"state": "open",
|
|
"mergeable_state": "unknown",
|
|
"updated_at": "2026-06-29T23:45:57Z",
|
|
"updated_age_days": None,
|
|
"next_action": "inspect",
|
|
"checks": {"state": "unknown", "failed_check_runs": [], "pending_check_runs": []},
|
|
}
|
|
],
|
|
}
|
|
|
|
output = module.format_integration_markdown(metrics)
|
|
|
|
assert (
|
|
"| [deleted-org/deleted-repo#1](https://github.com/deleted-org/deleted-repo/pull/1) | "
|
|
"n/a | n/a | open | unknown | unknown | 0 | 0 | n/a | inspect | `2026-06-29T23:45:57Z` |"
|
|
) in output
|
|
|
|
|
|
def test_format_integration_markdown_lists_high_exposure_priorities_by_stars():
|
|
module = load_growth_metrics_module()
|
|
metrics = {
|
|
"collected_at_utc": "2026-07-02T00:00:00+00:00",
|
|
"integrations": [
|
|
{
|
|
"pr": "ray-project/ray#64053",
|
|
"html_url": "https://github.com/ray-project/ray/pull/64053",
|
|
"state": "open",
|
|
"mergeable_state": "blocked",
|
|
"repo_stars": 43_000,
|
|
"repo_forks": 7_700,
|
|
"updated_at": "2026-06-30T07:02:58Z",
|
|
"updated_age_days": 0,
|
|
"next_action": "review gate",
|
|
"checks": {"state": "success", "failed_check_runs": [], "pending_check_runs": []},
|
|
},
|
|
{
|
|
"pr": "huggingface/transformers#46180",
|
|
"html_url": "https://github.com/huggingface/transformers/pull/46180",
|
|
"state": "open",
|
|
"mergeable_state": "blocked",
|
|
"repo_stars": 162_000,
|
|
"repo_forks": 33_000,
|
|
"updated_at": "2026-06-30T04:24:15Z",
|
|
"updated_age_days": 0,
|
|
"next_action": "request rerun",
|
|
"checks": {"state": "failure", "failed_check_runs": [], "pending_check_runs": []},
|
|
},
|
|
{
|
|
"pr": "deleted-org/deleted-repo#1",
|
|
"html_url": "https://github.com/deleted-org/deleted-repo/pull/1",
|
|
"state": "open",
|
|
"mergeable_state": "unknown",
|
|
"updated_at": "2026-06-29T23:45:57Z",
|
|
"updated_age_days": None,
|
|
"next_action": "inspect",
|
|
"checks": {"state": "unknown", "failed_check_runs": [], "pending_check_runs": []},
|
|
},
|
|
],
|
|
}
|
|
|
|
output = module.format_integration_markdown(metrics)
|
|
|
|
assert "## High-exposure priorities" in output
|
|
transformers = "- [huggingface/transformers#46180](https://github.com/huggingface/transformers/pull/46180): 162,000 stars, request rerun"
|
|
ray = "- [ray-project/ray#64053](https://github.com/ray-project/ray/pull/64053): 43,000 stars, review gate"
|
|
assert transformers in output
|
|
assert ray in output
|
|
assert output.index(transformers) < output.index(ray)
|
|
assert "deleted-org/deleted-repo#1" not in output.split("## High-exposure priorities", 1)[1].split("## Failed or pending checks", 1)[0]
|
|
|
|
|
|
def test_format_integration_markdown_lists_active_operator_queue():
|
|
module = load_growth_metrics_module()
|
|
metrics = {
|
|
"collected_at_utc": "2026-07-02T00:00:00+00:00",
|
|
"integrations": [
|
|
{
|
|
"pr": "getpaseo/paseo#1634",
|
|
"html_url": "https://github.com/getpaseo/paseo/pull/1634",
|
|
"state": "open",
|
|
"mergeable_state": "dirty",
|
|
"repo_stars": 9_500,
|
|
"repo_forks": 900,
|
|
"updated_at": "2026-06-30T09:47:12Z",
|
|
"updated_age_days": 0,
|
|
"next_action": "resolve conflicts",
|
|
"checks": {"state": "unknown", "failed_check_runs": [], "pending_check_runs": []},
|
|
},
|
|
{
|
|
"pr": "huggingface/optimum-intel#1801",
|
|
"html_url": "https://github.com/huggingface/optimum-intel/pull/1801",
|
|
"state": "open",
|
|
"mergeable_state": "unstable",
|
|
"repo_stars": 600,
|
|
"repo_forks": 240,
|
|
"updated_at": "2026-06-30T04:20:02Z",
|
|
"updated_age_days": 0,
|
|
"next_action": "review gate",
|
|
"checks": {"state": "unknown", "failed_check_runs": [], "pending_check_runs": []},
|
|
},
|
|
{
|
|
"pr": "livekit/agents#6176",
|
|
"html_url": "https://github.com/livekit/agents/pull/6176",
|
|
"state": "open",
|
|
"mergeable_state": "blocked",
|
|
"repo_stars": 11_000,
|
|
"repo_forks": 3_200,
|
|
"updated_at": "2026-06-30T10:55:17Z",
|
|
"updated_age_days": 0,
|
|
"next_action": "wait for maintainer review",
|
|
"checks": {"state": "success", "failed_check_runs": [], "pending_check_runs": []},
|
|
},
|
|
{
|
|
"pr": "activepieces/activepieces#13985",
|
|
"html_url": "https://github.com/activepieces/activepieces/pull/13985",
|
|
"state": "open",
|
|
"mergeable_state": "blocked",
|
|
"repo_stars": 23_000,
|
|
"repo_forks": 3_800,
|
|
"updated_at": "2026-06-30T08:46:39Z",
|
|
"updated_age_days": 0,
|
|
"next_action": "resolve CLA",
|
|
"checks": {"state": "pending", "failed_check_runs": [], "pending_check_runs": []},
|
|
},
|
|
{
|
|
"pr": "huggingface/optimum-intel#1801",
|
|
"html_url": "https://github.com/huggingface/optimum-intel/pull/1801",
|
|
"state": "open",
|
|
"mergeable_state": "unstable",
|
|
"repo_stars": 18_000,
|
|
"repo_forks": 2_800,
|
|
"updated_at": "2026-06-30T04:20:02Z",
|
|
"updated_age_days": 0,
|
|
"next_action": "wait for maintainer preliminary PR",
|
|
"checks": {"state": "failure", "failed_check_runs": [], "pending_check_runs": []},
|
|
},
|
|
],
|
|
}
|
|
|
|
output = module.format_integration_markdown(metrics)
|
|
|
|
active_queue = output.split("## Active operator queue", 1)[1].split("## Failed or pending checks", 1)[0]
|
|
assert "- [getpaseo/paseo#1634](https://github.com/getpaseo/paseo/pull/1634): resolve conflicts" in active_queue
|
|
assert (
|
|
"- [huggingface/optimum-intel#1801](https://github.com/huggingface/optimum-intel/pull/1801): review gate"
|
|
in active_queue
|
|
)
|
|
assert "livekit/agents#6176" not in active_queue
|
|
assert "activepieces/activepieces#13985" not in active_queue
|
|
assert "wait for maintainer preliminary PR" not in active_queue
|
|
|
|
|
|
def test_format_integration_markdown_lists_manual_handoff_gates():
|
|
module = load_growth_metrics_module()
|
|
metrics = {
|
|
"collected_at_utc": "2026-07-02T00:00:00+00:00",
|
|
"integrations": [
|
|
{
|
|
"pr": "punkpeye/awesome-mcp-servers#7153",
|
|
"html_url": "https://github.com/punkpeye/awesome-mcp-servers/pull/7153",
|
|
"state": "open",
|
|
"mergeable_state": "clean",
|
|
"repo_stars": 90_000,
|
|
"repo_forks": 12_000,
|
|
"updated_at": "2026-06-16T04:21:55Z",
|
|
"updated_age_days": 16,
|
|
"next_action": "submit Glama",
|
|
"known_review_gate_reason": "Glama listing and score badge required before review",
|
|
"checks": {"state": "success", "failed_check_runs": [], "pending_check_runs": []},
|
|
},
|
|
{
|
|
"pr": "huggingface/optimum-intel#1801",
|
|
"html_url": "https://github.com/huggingface/optimum-intel/pull/1801",
|
|
"state": "open",
|
|
"mergeable_state": "unstable",
|
|
"repo_stars": 600,
|
|
"repo_forks": 240,
|
|
"updated_at": "2026-06-30T04:20:02Z",
|
|
"updated_age_days": 0,
|
|
"next_action": "review gate",
|
|
"checks": {"state": "unknown", "failed_check_runs": [], "pending_check_runs": []},
|
|
},
|
|
],
|
|
}
|
|
|
|
output = module.format_integration_markdown(metrics)
|
|
|
|
active_queue = output.split("## Active operator queue", 1)[1].split("## Manual handoff gates", 1)[0]
|
|
manual_gates = output.split("## Manual handoff gates", 1)[1].split("## Manual review gates", 1)[0]
|
|
assert "punkpeye/awesome-mcp-servers#7153" not in active_queue
|
|
assert (
|
|
"- [punkpeye/awesome-mcp-servers#7153](https://github.com/punkpeye/awesome-mcp-servers/pull/7153): "
|
|
"submit Glama; Glama listing and score badge required before review"
|
|
) in manual_gates
|
|
assert "huggingface/optimum-intel#1801" not in manual_gates
|
|
|
|
|
|
def test_format_integration_markdown_lists_known_review_gates():
|
|
module = load_growth_metrics_module()
|
|
metrics = {
|
|
"collected_at_utc": "2026-07-02T00:00:00+00:00",
|
|
"integrations": [
|
|
{
|
|
"pr": "punkpeye/awesome-mcp-servers#7153",
|
|
"html_url": "https://github.com/punkpeye/awesome-mcp-servers/pull/7153",
|
|
"state": "open",
|
|
"mergeable_state": "clean",
|
|
"repo_stars": 90_000,
|
|
"repo_forks": 12_000,
|
|
"updated_at": "2026-06-16T04:21:55Z",
|
|
"updated_age_days": 14,
|
|
"next_action": "submit Glama",
|
|
"known_review_gate_reason": "Glama listing and score badge required before review",
|
|
"checks": {"state": "success", "failed_check_runs": [], "pending_check_runs": []},
|
|
}
|
|
],
|
|
}
|
|
|
|
output = module.format_integration_markdown(metrics)
|
|
|
|
assert "## Manual review gates" in output
|
|
assert (
|
|
"- [punkpeye/awesome-mcp-servers#7153](https://github.com/punkpeye/awesome-mcp-servers/pull/7153): "
|
|
"Glama listing and score badge required before review"
|
|
) in output
|
|
|
|
|
|
def test_format_integration_markdown_lists_assisted_review_waits():
|
|
module = load_growth_metrics_module()
|
|
metrics = {
|
|
"collected_at_utc": "2026-07-02T00:00:00+00:00",
|
|
"integrations": [
|
|
{
|
|
"pr": "lukasmasuch/best-of-ml-python#455",
|
|
"html_url": "https://github.com/lukasmasuch/best-of-ml-python/pull/455",
|
|
"state": "open",
|
|
"mergeable_state": "clean",
|
|
"repo_stars": 23_652,
|
|
"repo_forks": 3_800,
|
|
"updated_at": "2026-06-30T09:16:59Z",
|
|
"updated_age_days": 0,
|
|
"next_action": "wait for maintainer review",
|
|
"known_assisted_review_reason": "review evidence already posted; avoid duplicate pings",
|
|
"checks": {"state": "success", "failed_check_runs": [], "pending_check_runs": []},
|
|
}
|
|
],
|
|
}
|
|
|
|
output = module.format_integration_markdown(metrics)
|
|
|
|
assert "## Assisted review waits" in output
|
|
assert (
|
|
"- [lukasmasuch/best-of-ml-python#455](https://github.com/lukasmasuch/best-of-ml-python/pull/455): "
|
|
"review evidence already posted; avoid duplicate pings"
|
|
) in output
|
|
|
|
|
|
def test_collect_issue_metrics_filters_pull_requests_and_assigns_waiting_on(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/modelscope/FunASR/issues?state=open&per_page=100":
|
|
return [
|
|
{
|
|
"number": 3038,
|
|
"title": "realtime websocket falls behind with two clients",
|
|
"html_url": "https://github.com/modelscope/FunASR/issues/3038",
|
|
"updated_at": "2026-06-29T19:22:38Z",
|
|
"comments": 4,
|
|
"user": {"login": "liujixingit"},
|
|
"labels": [{"name": "bug"}, {"name": "needs feedback"}],
|
|
},
|
|
{
|
|
"number": 3034,
|
|
"title": "Fun-ASR-Nano on Ascend NPU",
|
|
"html_url": "https://github.com/modelscope/FunASR/issues/3034",
|
|
"updated_at": "2026-06-29T19:49:07Z",
|
|
"comments": 3,
|
|
"user": {"login": "pubulichen"},
|
|
"labels": [{"name": "help wanted"}, {"name": "ready for PR"}],
|
|
},
|
|
{
|
|
"number": 3057,
|
|
"title": "open pull request should be ignored",
|
|
"pull_request": {"url": "https://api.github.com/repos/modelscope/FunASR/pulls/3057"},
|
|
"labels": [],
|
|
},
|
|
]
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
|
|
metrics = module.collect_issue_metrics(["modelscope/FunASR"])
|
|
|
|
repository = metrics["repositories"][0]
|
|
assert repository["repo"] == "modelscope/FunASR"
|
|
assert repository["open_issue_count"] == 2
|
|
assert [issue["number"] for issue in repository["open_issues"]] == [3038, 3034]
|
|
assert repository["open_issues"][0]["labels"] == ["bug", "needs feedback"]
|
|
assert repository["open_issues"][0]["waiting_on"] == "reporter"
|
|
assert repository["open_issues"][1]["waiting_on"] == "contributor"
|
|
|
|
|
|
def test_main_outputs_issues_json(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/modelscope/FunASR/issues?state=open&per_page=100":
|
|
return [
|
|
{
|
|
"number": 2959,
|
|
"title": "postprocess hotwords",
|
|
"html_url": "https://github.com/modelscope/FunASR/issues/2959",
|
|
"updated_at": "2026-06-29T19:46:28Z",
|
|
"comments": 2,
|
|
"user": {"login": "HaujetZhao"},
|
|
"labels": [{"name": "good first issue"}, {"name": "ready for PR"}],
|
|
}
|
|
]
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
monkeypatch.setattr(
|
|
sys,
|
|
"argv",
|
|
[
|
|
"collect_growth_metrics.py",
|
|
"--issues",
|
|
"--repos",
|
|
"modelscope/FunASR",
|
|
"--format",
|
|
"json",
|
|
],
|
|
)
|
|
|
|
with redirect_stdout(io.StringIO()) as stdout:
|
|
assert module.main() == 0
|
|
|
|
payload = json.loads(stdout.getvalue())
|
|
assert payload["repositories"][0]["open_issues"][0]["number"] == 2959
|
|
assert payload["repositories"][0]["open_issues"][0]["waiting_on"] == "contributor"
|
|
|
|
|
|
def test_main_outputs_ecosystem_json(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
repo_stars = {
|
|
"modelscope/FunASR": 18714,
|
|
"FunAudioLLM/Fun-ASR": 1318,
|
|
"FunAudioLLM/SenseVoice": 8718,
|
|
"modelscope/FunClip": 5872,
|
|
}
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url.endswith("/pulls?state=open&per_page=100"):
|
|
return []
|
|
if url.startswith("https://api.github.com/repos/"):
|
|
repo = url.removeprefix("https://api.github.com/repos/")
|
|
return {
|
|
"stargazers_count": repo_stars[repo],
|
|
"forks_count": 0,
|
|
"subscribers_count": 0,
|
|
"open_issues_count": 0,
|
|
"default_branch": "main",
|
|
"pushed_at": "2026-06-30T00:00:00Z",
|
|
"html_url": f"https://github.com/{repo}",
|
|
}
|
|
if url == "https://pypi.org/pypi/funasr/json":
|
|
return {"info": {"version": "1.3.14", "summary": "FunASR", "project_url": "https://pypi.org/project/funasr/"}}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
monkeypatch.setattr(
|
|
sys,
|
|
"argv",
|
|
[
|
|
"collect_growth_metrics.py",
|
|
"--ecosystem",
|
|
"--format",
|
|
"json",
|
|
"--baseline-stars",
|
|
"31224",
|
|
"--target-additional-stars",
|
|
"20000",
|
|
],
|
|
)
|
|
|
|
with redirect_stdout(io.StringIO()) as stdout:
|
|
assert module.main() == 0
|
|
|
|
payload = json.loads(stdout.getvalue())
|
|
assert payload["ecosystem"]["total_stars"] == 34622
|
|
assert payload["ecosystem"]["remaining_to_target"] == 16602
|
|
|
|
|
|
def test_main_outputs_integrations_json(monkeypatch):
|
|
module = load_growth_metrics_module()
|
|
|
|
def fake_fetch_json(url, headers=None):
|
|
if url == "https://api.github.com/repos/ray-project/ray/pulls/64053":
|
|
return {
|
|
"number": 64053,
|
|
"title": "docs(serve): add FunASR ASR integration example",
|
|
"state": "open",
|
|
"draft": False,
|
|
"mergeable": True,
|
|
"mergeable_state": "clean",
|
|
"html_url": "https://github.com/ray-project/ray/pull/64053",
|
|
"updated_at": "2026-06-30T01:53:52Z",
|
|
"head": {"sha": "780eb4c", "ref": "issue-64052"},
|
|
"base": {
|
|
"ref": "master",
|
|
"repo": {
|
|
"stargazers_count": 36_000,
|
|
"forks_count": 6_800,
|
|
},
|
|
},
|
|
"user": {"login": "nh-atuan"},
|
|
}
|
|
if url == "https://api.github.com/repos/ray-project/ray/commits/780eb4c/status":
|
|
return {"state": "success", "statuses": [{"context": "buildkite/microcheck", "state": "success"}]}
|
|
if url == "https://api.github.com/repos/ray-project/ray/commits/780eb4c/check-runs?per_page=100":
|
|
return {
|
|
"total_count": 1,
|
|
"check_runs": [
|
|
{"name": "disable-automerge", "status": "completed", "conclusion": "success"},
|
|
],
|
|
}
|
|
raise AssertionError(f"unexpected URL: {url}")
|
|
|
|
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
|
monkeypatch.setattr(
|
|
sys,
|
|
"argv",
|
|
[
|
|
"collect_growth_metrics.py",
|
|
"--integrations",
|
|
"--integration-prs",
|
|
"ray-project/ray#64053",
|
|
"--format",
|
|
"json",
|
|
],
|
|
)
|
|
|
|
with redirect_stdout(io.StringIO()) as stdout:
|
|
assert module.main() == 0
|
|
|
|
payload = json.loads(stdout.getvalue())
|
|
integration = payload["integrations"][0]
|
|
assert integration["pr"] == "ray-project/ray#64053"
|
|
assert integration["repo_stars"] == 36_000
|
|
assert integration["repo_forks"] == 6_800
|
|
assert integration["checks"]["state"] == "success"
|
|
assert integration["next_action"] == "wait for maintainer review"
|
|
assert integration["known_assisted_review_reason"] == "review evidence already posted; avoid duplicate pings"
|