diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 464a17126ddc7..a210ac7c6058a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -48,6 +48,7 @@ tests/ci/cancel_and_rerun_workflow_lambda/app.py - [ ] ClickHouse Keeper (1h) - [x] Iceberg (2h) - [ ] LDAP (1h) +- [ ] OAuth (5m) - [x] Parquet (1.5h) - [ ] RBAC (1.5h) - [ ] SSL Server (1h) diff --git a/.github/actions/create_workflow_report/action.yml b/.github/actions/create_workflow_report/action.yml index 0bf2933550f97..d22566dcb90f0 100644 --- a/.github/actions/create_workflow_report/action.yml +++ b/.github/actions/create_workflow_report/action.yml @@ -1,6 +1,10 @@ name: Create and Upload Combined Report description: Create and upload a combined CI report inputs: + workflow_config: + description: "Workflow config (25.8 generated YAML still passes this)" + required: false + default: "" final: description: "Control whether the report is final or a preview" required: false @@ -8,6 +12,15 @@ inputs: runs: using: "composite" steps: + - name: Create workflow config + if: ${{ inputs.workflow_config != '' }} + shell: bash + run: | + mkdir -p ./ci/tmp + cat > ./ci/tmp/workflow_config.json << 'EOF' + ${{ inputs.workflow_config }} + EOF + - name: Create and upload workflow report env: PR_NUMBER: ${{ github.event.pull_request.number || 0 }} diff --git a/.github/actions/create_workflow_report/create_workflow_report.py b/.github/actions/create_workflow_report/create_workflow_report.py index a4896cba87a9c..ce4b342012a45 100755 --- a/.github/actions/create_workflow_report/create_workflow_report.py +++ b/.github/actions/create_workflow_report/create_workflow_report.py @@ -3,6 +3,7 @@ import html import os import sys +from glob import glob import time from pathlib import Path from itertools import combinations @@ -29,7 +30,10 @@ DATABASE_PASSWORD_VAR = "CLICKHOUSE_TEST_STAT_PASSWORD" S3_BUCKET = "altinity-build-artifacts" GITHUB_REPO = "Altinity/ClickHouse" +GITHUB_GIT_URL = f"https://github.com/{GITHUB_REPO}.git" GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") or os.getenv("GH_TOKEN") +# Release tags are fetched here rather than refs/tags/ so local tags are never touched. +RELEASE_TAG_REF_PREFIX = "refs/workflow-report/tags/" # Resolve from this file so discovery works from repo root or the action folder. KNOWN_FAILS_FILE = ( Path(__file__).resolve().parents[3] / "tests" / "broken_tests.yaml" @@ -61,13 +65,28 @@ def query_dataframe_with_retry( wait = backoff_seconds * attempt time.sleep(wait) -def get_commit_statuses(sha: str) -> pd.DataFrame: +def _results_link_has_ref(target_url: str | None, ref: str) -> bool: + """ + Whether a CI status results link has `ref` in it:`json.html?REF=&...` for + praktika jobs, `/REFs//...` for regression jobs. + """ + target_url = target_url or "" + return f"REF={ref}&" in target_url or f"/REFs/{ref}/" in target_url + + +def get_commit_statuses(sha: str, pr_number: int, ref: str) -> pd.DataFrame: """ Fetch commit statuses for a given SHA and return as a pandas DataFrame. Handles pagination to get all statuses. + Statuses belong to the commit, not the ref, so a release tag and its branch + can write to the same job names. For release runs (`pr_number` 0), only + statuses of `ref` are kept. + Args: sha (str): Commit SHA to fetch statuses for. + pr_number (int): Pull request number, 0 for release runs. + ref (str): Ref of the run being reported. Returns: pd.DataFrame: DataFrame containing all statuses. @@ -116,6 +135,7 @@ def get_commit_statuses(sha: str) -> pd.DataFrame: "results_link": item["target_url"], } for item in all_data + if pr_number != 0 or _results_link_has_ref(item["target_url"], ref) ] # Create DataFrame @@ -216,8 +236,10 @@ def _enrich_prs_in_release_merge_prs(df: pd.DataFrame, repo: str) -> pd.DataFram def _git_rev_parse(ref: str, cwd: str | None) -> str | None: + # Peel to a commit so a raw SHA that is not in the object store fails. + # git rev-parse --verify <40-hex> succeeds even when the object is missing. p = subprocess.run( - ["git", "rev-parse", "--verify", ref], + ["git", "rev-parse", "--verify", f"{ref}^{{commit}}"], cwd=cwd, capture_output=True, text=True, @@ -237,7 +259,7 @@ def _git_is_ancestor(ancestor: str, descendant: str, cwd: str | None) -> bool: def _git_log_merge_prs( - baseline: str, branch_ref: str, cwd: str | None, repo: str + baseline: str, commit_sha: str, cwd: str | None, repo: str ) -> pd.DataFrame: p = subprocess.run( [ @@ -245,7 +267,7 @@ def _git_log_merge_prs( "-c", "core.quotepath=false", "log", - f"{baseline}..{branch_ref}", + f"{baseline}..{commit_sha}", "--merges", "--format=%H%x09%s", ], @@ -265,7 +287,7 @@ def _git_log_merge_prs( if not m: continue pr_number, head_owner = int(m.group(1)), m.group(2) - if head_owner == "ClickHouse": + if head_owner == 'ClickHouse': continue rows.append( { @@ -281,15 +303,11 @@ def _git_log_merge_prs( return df -def _find_release_baseline( - branch_ref: str, repo: str, cwd: str | None -) -> tuple[str | None, str | None, str | None]: - """Return (tag_name, tag_sha, published_date YYYY-MM-DD) for the latest - non-draft release tag that is an ancestor of branch_ref. - Version-tag refs skip the matching release so the range is previous→this tag. - """ +def _get_github_releases(repo: str) -> list[dict]: + """Non-draft GitHub releases of repo, newest first.""" if not GITHUB_TOKEN: - return None, None, None + print("GITHUB_TOKEN is not set; skipping GitHub releases lookup") + return [] headers = { "Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json", @@ -302,20 +320,31 @@ def _find_release_baseline( raise Exception( f"GitHub API request failed: {response.status_code} {response.text}" ) + return [rel for rel in response.json() if not rel.get("draft")] + + +def _find_release_baseline( + branch_ref: str, + releases: list[dict], + cwd: str | None, + commit_sha: str, +) -> tuple[str | None, str | None, str | None]: + """Return (tag_name, tag_sha, published_date YYYY-MM-DD) for the latest + release tag in releases that is an ancestor of commit_sha. + Version-tag refs skip the matching release so the range is previous→this tag. + """ parsed = _parse_release_ref(branch_ref) skip_current = parsed is not None and parsed[2] is not None - for rel in response.json(): - if rel.get("draft"): - continue + for rel in releases: tag_name = rel.get("tag_name") if not tag_name: continue - tag_sha = _git_rev_parse(tag_name, cwd) + tag_sha = _release_tag_sha(tag_name, cwd) if not tag_sha: continue - if not _git_is_ancestor(tag_sha, branch_ref, cwd): + if not _git_is_ancestor(tag_sha, commit_sha, cwd): continue - if skip_current and _git_is_ancestor(branch_ref, tag_sha, cwd): + if skip_current and _git_is_ancestor(commit_sha, tag_sha, cwd): continue published_at = rel.get("published_at") or rel.get("created_at") or "" published_date = published_at[:10] if published_at else None @@ -323,12 +352,12 @@ def _find_release_baseline( return None, None, None -def _find_rebase_baseline(branch_ref: str, cwd: str | None) -> str | None: +def _find_rebase_baseline(commit_sha: str, cwd: str | None) -> str | None: p = subprocess.run( [ "git", "log", - branch_ref, + commit_sha, "--reverse", "-i", "-E", @@ -350,9 +379,9 @@ def _find_rebase_baseline(branch_ref: str, cwd: str | None) -> str | None: return lines[0] -def _git_commit_date(sha: str, cwd: str | None) -> str | None: +def _git_commit_time(sha: str, cwd: str | None) -> str | None: p = subprocess.run( - ["git", "show", "-s", "--format=%cs", sha], + ["git", "show", "-s", "--format=%ct", sha], cwd=cwd, capture_output=True, text=True, @@ -360,8 +389,113 @@ def _git_commit_date(sha: str, cwd: str | None) -> str | None: ) if p.returncode != 0: return None - date = p.stdout.strip() - return date or None + timestamp = p.stdout.strip() + if not timestamp: + return None + return datetime.fromtimestamp(int(timestamp), timezone.utc).strftime( + "%Y-%m-%dT%H:%M:%SZ" + ) + + +def _git_is_shallow(cwd: str | None) -> bool: + p = subprocess.run( + ["git", "rev-parse", "--is-shallow-repository"], + cwd=cwd, + capture_output=True, + text=True, + check=True, + ) + return p.stdout.strip() == "true" + + +def _git_fetch(options: list[str], refspecs: list[str], cwd: str | None) -> None: + p = subprocess.run( + ["git", "fetch", *options, GITHUB_GIT_URL, *refspecs], + cwd=cwd, + capture_output=True, + text=True, + check=False, + ) + if p.returncode != 0: + raise Exception(f"git fetch failed: {p.stderr.strip()}") + + +def _release_tag_sha(tag_name: str, cwd: str | None) -> str | None: + return _git_rev_parse(f"{RELEASE_TAG_REF_PREFIX}{tag_name}", cwd) + + +def _read_autogenerated_versions(sha: str, cwd: str | None) -> tuple[int, int]: + p = subprocess.run( + ["git", "show", f"{sha}:cmake/autogenerated_versions.txt"], + cwd=cwd, + capture_output=True, + text=True, + check=True, + ) + major = re.search(r"SET\(VERSION_MAJOR (\d+)\)", p.stdout) + minor = re.search(r"SET\(VERSION_MINOR (\d+)\)", p.stdout) + if not major or not minor: + raise Exception(f"Cannot read version from {sha}") + return int(major.group(1)), int(minor.group(1)) + + +def _filter_releases_to_version_line( + releases: list[dict], + branch_ref: str, + commit_sha: str, + cwd: str | None, +) -> list[dict]: + """Keep GitHub releases that belong on commit_sha's version line.""" + parsed = _parse_release_ref(branch_ref) + if parsed is not None: + flavour, minor_version, _ = parsed + suffix = f"altinity{flavour}" + prefix = f"v{minor_version}." + return [ + rel + for rel in releases + if (tag := rel.get("tag_name")) + and tag.startswith(prefix) + and tag.endswith(f".{suffix}") + ] + major, minor = _read_autogenerated_versions(commit_sha, cwd) + prefix = f"v{major}.{minor}." + return [ + rel + for rel in releases + if (tag := rel.get("tag_name")) and tag.startswith(prefix) + ] + + +def _fetch_release_tags(releases: list[dict], cwd: str) -> None: + """Fetch release tags into RELEASE_TAG_REF_PREFIX; skip tags missing remotely.""" + for rel in releases: + tag_name = rel.get("tag_name") + if not tag_name: + continue + try: + _git_fetch( + ["--no-tags"], + [f"+refs/tags/{tag_name}:{RELEASE_TAG_REF_PREFIX}{tag_name}"], + cwd, + ) + except Exception as e: + # NOTE (strtgbb): one deleted remote tag must not abort the section, but + # if it is the newest release the baseline moves back to an older one + print(f"WARNING: skipping release tag {tag_name}: {e}") + + +def _fetch_commit(commit_sha: str, cwd: str) -> None: + """Fetch the workflow commit from GITHUB_GIT_URL. + + Local checkouts might be a different branch and might not have the run SHA yet. + --filter/--unshallow would convert a full clone into a partial one, so those + options are used only when the checkout is already shallow. + """ + if _git_is_shallow(cwd): + _git_fetch(["--filter=tree:0", "--unshallow"], [commit_sha], cwd) + else: + _git_fetch(["--no-tags"], [commit_sha], cwd) def get_prs_in_release_dataframe( @@ -369,6 +503,7 @@ def get_prs_in_release_dataframe( *, repo: str = GITHUB_REPO, cwd: str, + commit_sha: str, ) -> tuple[pd.DataFrame, str | None]: f""" PRs merged into branch_ref that belong in the next release notes: after the latest GitHub @@ -376,88 +511,62 @@ def get_prs_in_release_dataframe( Merge commits whose subject is from the upstream repository (ClickHouse/) are omitted. Columns: pr_number, pr_name, labels. - Returns (dataframe, baseline_date YYYY-MM-DD or None). + commit_sha is the workflow commit from the GitHub Actions API. + + Returns (dataframe, baseline commit time as UTC YYYY-MM-DDTHH:MM:SSZ or None). """ - branch_sha = _git_rev_parse(branch_ref, cwd) - if not branch_sha: - raise Exception(f"Cannot resolve branch ref: {branch_ref!r}") - - # CI often checks out with --depth=1 and --no-tags; fetch tags once, then deepen - # in steps until a baseline is reachable. - # Observed baseline distances are ~1k commits and grow slowly, so DEEPEN_CAP sits - # well above that; the cap exists so a failed lookup fails fast instead of fetching - # the branch's entire (100k+ commit) history. - DEEPEN_STEP = 250 - DEEPEN_CAP = 10000 - - subprocess.run( - ["git", "fetch", "--tags", "origin"], - cwd=cwd, - capture_output=True, - text=True, - check=False, + _fetch_commit(commit_sha, cwd) + resolved = _git_rev_parse(commit_sha, cwd) + if not resolved: + raise Exception(f"Cannot resolve commit: {commit_sha!r}") + commit_sha = resolved + + releases = _filter_releases_to_version_line( + _get_github_releases(repo), branch_ref, commit_sha, cwd ) + _fetch_release_tags(releases, cwd) - parsed = _parse_release_ref(branch_ref) - fetch_ref = ( - f"refs/tags/{branch_ref}" - if parsed is not None and parsed[2] is not None - else branch_ref + _, baseline_sha, _ = _find_release_baseline( + branch_ref, releases, cwd, commit_sha=commit_sha ) - - baseline_sha = None - baseline_date = None - for _ in range(DEEPEN_CAP // DEEPEN_STEP): - subprocess.run( - ["git", "fetch", f"--deepen={DEEPEN_STEP}", "origin", fetch_ref], - cwd=cwd, - capture_output=True, - text=True, - check=False, - ) - _, baseline_sha, baseline_date = _find_release_baseline( - branch_ref, repo, cwd - ) - if not baseline_sha: - baseline_sha = _find_rebase_baseline(branch_ref, cwd) - if baseline_sha: - baseline_date = _git_commit_date(baseline_sha, cwd) - if baseline_sha: - break + if not baseline_sha: + baseline_sha = _find_rebase_baseline(commit_sha, cwd) if not baseline_sha: raise Exception( - "No release tag on this branch and no rebase bootstrap merge found " - f"within {DEEPEN_CAP} commits" + "No release tag on this branch and no rebase bootstrap merge found" ) - df = _git_log_merge_prs(baseline_sha, branch_ref, cwd, repo) - return _enrich_prs_in_release_merge_prs(df, repo), baseline_date + df = _git_log_merge_prs(baseline_sha, commit_sha, cwd, repo) + return ( + _enrich_prs_in_release_merge_prs(df, repo), + _git_commit_time(baseline_sha, cwd), + ) def _parse_release_ref(ref_name: str) -> tuple[str, str, str | None] | None: """Parse a release ref into (flavour, minor_version, full_version). Handles version tags (``v25.8.28.10001.altinitystable``) and release - branches (``stable-25.8``). ``full_version`` is None for branches, which + branches (``stable-25.8``, ``antalya-25.8``, ``fips-25.3``). ``full_version`` is None for branches, which carry no patch or tweak. Returns None for refs without a version. """ # Match tag match = re.match( - r"^v((\d+\.\d+)\.\d+\.\d+)\.altinity(stable|antalya|test)$", ref_name + r"^v((\d+\.\d+)\.\d+\.\d+)\.altinity(stable|antalya|fips|test)$", ref_name ) if match: - flavour = "antalya" if match.group(3) == "antalya" else "stable" + flavour = match.group(3) if match.group(3) in ("antalya", "fips") else "stable" return flavour, match.group(2), match.group(1) # Match branch - match = re.match(r"^(stable|antalya)-(\d+\.\d+)$", ref_name) + match = re.match(r"^(stable|antalya|fips)-(\d+\.\d+)$", ref_name) if match: return match.group(1), match.group(2), None return None -def _prs_in_release_search_url(branch_name: str, baseline_date: str | None) -> str | None: - if not baseline_date: +def _prs_in_release_search_url(branch_name: str, baseline_time: str | None) -> str | None: + if not baseline_time: return None # Tag-triggered runs expose the tag as head_branch; search needs the PR base # branch (e.g. v25.8.28.10001.altinitystable → stable-25.8). @@ -465,7 +574,7 @@ def _prs_in_release_search_url(branch_name: str, baseline_date: str | None) -> s if parsed: flavour, minor_version, _ = parsed branch_name = f"{flavour}-{minor_version}" - query = f"is:pr base:{branch_name} merged:>{baseline_date}" + query = f"is:pr base:{branch_name} merged:>{baseline_time}" return ( f"https://github.com/{GITHUB_REPO}/pulls?" + urllib.parse.urlencode({"q": query}) @@ -474,18 +583,9 @@ def _prs_in_release_search_url(branch_name: str, baseline_date: str | None) -> s def _checks_latest_test_status_cte(commit_sha: str, branch_name: str) -> str: """ - Shared filtering for gh-data.checks: anchor time selects the latest result batch - per check_name. - - Rows that must not set the anchor (but are still kept if their time is >= anchor): - - Stateless teardown: check_name LIKE 'Stateless%' AND test_name not matching ^[0-9]{5} - - Empty test_name: CIDB job-level parent rows - - Pre/Post Hooks: praktika phases with their own stopwatches; Post Hooks always runs - after tests and would otherwise become the sole surviving batch, hiding all FAILs - - Keep rows with check_start_time >= anchor so the latest main batch and any later - teardown/hook rows are included. Earlier batches (failed attempts before a rerun - uploaded a newer uniform timestamp) are dropped. + Shared filtering for gh-data.checks: anchor time excludes stateless teardown checks + (Stateless% + test_name not matching ^[0-9]{5}); keep rows with check_start_time + >= anchor so main + teardown phases are included. """ return f"""WITH checks_with_anchor AS ( SELECT @@ -718,6 +818,10 @@ def get_new_fails_this_pr( # Combine both types of fails and select only desired columns desired_columns = ["job_name", "test_name", "test_status", "results_link"] + if len(checks_fails) > 0 and "labels" in checks_fails.columns: + desired_columns.insert(desired_columns.index("results_link"), "labels") + if len(regression_fails) > 0: + regression_fails["labels"] = "" all_pr_fails = pd.concat([checks_fails, regression_fails], ignore_index=True)[ desired_columns ] @@ -788,36 +892,59 @@ def get_new_fails_this_pr( def _workflow_config_s3_url(pr_number: int, commit_sha: str, ref_name: str) -> str: - # Uploads use a double slash before config_workflow in the S3 key. if pr_number == 0: return ( f"https://{S3_BUCKET}.s3.amazonaws.com/REFs/{ref_name}/{commit_sha}/" - "/config_workflow/workflow_config_masterci.json" + "config_workflow/workflow_config_masterci.json" ) return ( f"https://{S3_BUCKET}.s3.amazonaws.com/PRs/{pr_number}/{commit_sha}/" - "/config_workflow/workflow_config_pr.json" + "config_workflow/workflow_config_pr.json" ) +def _workflow_config_from_local() -> dict | None: + # NOTE (strtgbb): 25.8 jobs still write ci/tmp/workflow_config*.json + # (including workflow_config_release_builds.json). 26.8 S3 lookup only knows + # masterci/pr filenames, so local wins when present. + if os.path.exists("./ci/tmp/workflow_status.json"): + with open("./ci/tmp/workflow_status.json", "r") as f: + data = json.load(f)["config_workflow"]["outputs"]["data"] + if isinstance(data, str): + data = json.loads(data) + if isinstance(data, dict) and "WORKFLOW_CONFIG" in data: + return data["WORKFLOW_CONFIG"] + if isinstance(data, dict): + return data + workflow_config_files = glob("./ci/tmp/workflow_config*.json") + if len(workflow_config_files) == 1: + with open(workflow_config_files[0], "r") as f: + return json.load(f) + return None + + @lru_cache def get_workflow_config(pr_number: int, commit_sha: str, ref_name: str) -> dict: - """Fetch workflow config published by Config Workflow from S3. + """Load workflow config from the local run, then S3. On failure, warn and return a minimal empty config (same shape recreation uses). """ + local = _workflow_config_from_local() + if local is not None: + return local url = _workflow_config_s3_url(pr_number, commit_sha, ref_name) try: response = requests.get(url, timeout=30) if response.status_code != 200: print( f"WARNING:Failed to fetch workflow config from {url}: " - f"{response.status_code}" + f"{response.status_code}", + file=sys.stderr, ) return {"cache_jobs": {}} return response.json() except Exception as e: - print(f"WARNING:Failed to fetch workflow config from {url}: {e}") + print(f"WARNING:Failed to fetch workflow config from {url}: {e}", file=sys.stderr) return {"cache_jobs": {}} @@ -1004,6 +1131,82 @@ def format_test_status(text: str) -> str: return f'{text}' +def _label_names_from_ext(ext: dict) -> list[str]: + names = [] + for item in ext.get("labels") or []: + if isinstance(item, str): + name = item + elif isinstance(item, dict) and item.get("name"): + name = item["name"] + else: + continue + if name != "cidb": + names.append(name) + return names + + +def fetch_workflow_result_json( + pr_number: int, branch: str, commit_sha: str +) -> dict | None: + if pr_number == 0: + ref_param = f"REF={branch}" + workflow_name = "MasterCI" + else: + ref_param = f"PR={pr_number}" + workflow_name = "PR" + + status_file = f"result_{workflow_name.lower()}.json" + s3_path = ( + f"https://{S3_BUCKET}.s3.amazonaws.com/" + f"{ref_param.replace('=', 's/')}/{commit_sha}/{status_file}" + ) + try: + response = requests.get(s3_path, timeout=30) + if response.status_code != 200: + return None + return response.json() + except Exception as e: + print(f"WARNING:Failed to fetch workflow result from {s3_path}: {e}") + return None + + +def get_failure_labels_from_workflow(workflow_data: dict | None) -> dict: + if not workflow_data: + return {} + labels_map = {} + for job in workflow_data.get("results") or []: + job_name = job.get("name") + if not job_name: + continue + for leaf in job.get("results") or []: + test_name = leaf.get("name") + if not test_name: + continue + names = _label_names_from_ext(leaf.get("ext") or {}) + if names: + labels_map[(job_name, test_name)] = ", ".join(names) + return labels_map + + +def add_labels_to_checks_fails( + checks_fails: pd.DataFrame, workflow_data: dict | None +) -> pd.DataFrame: + if checks_fails is None or len(checks_fails) == 0: + return checks_fails + labels_map = get_failure_labels_from_workflow(workflow_data) + df = checks_fails.copy() + df["labels"] = df.apply( + lambda row: labels_map.get((row["job_name"], row["test_name"]), ""), + axis=1, + ) + cols = [c for c in df.columns if c != "labels"] + if "results_link" in cols: + cols.insert(cols.index("results_link"), "labels") + else: + cols.append("labels") + return df[cols] + + def format_results_as_html_table(results, *, branch_name: str = "") -> str: if not isinstance(results, pd.DataFrame): return results @@ -1040,42 +1243,30 @@ def format_col_name(col_name: str) -> str: "PR Labels": lambda labels: format_pr_labels_with_verification( labels, branch_name=branch_name ), + "Labels": lambda labels: html.escape(str(labels), quote=True) if labels else "", } - html = results.to_html( + return results.to_html( index=False, formatters=formatters, escape=False, border=0, classes=["test-results-table"], ) - return html def backfill_skipped_statuses( - job_statuses: pd.DataFrame, pr_number: int, branch: str, commit_sha: str + job_statuses: pd.DataFrame, + workflow_result: dict | None, ): """ Fill in the job statuses for skipped jobs. """ - - if pr_number == 0: - ref_param = f"REF={branch}" - workflow_name = "MasterCI" - else: - ref_param = f"PR={pr_number}" - workflow_name = "PR" - - status_file = f"result_{workflow_name.lower()}.json" - s3_path = f"https://{S3_BUCKET}.s3.amazonaws.com/{ref_param.replace('=', 's/')}/{commit_sha}/{status_file}" - response = requests.get(s3_path) - - if response.status_code != 200: + if workflow_result is None: return job_statuses - status_data = response.json() skipped_jobs = [] - for job in status_data["results"]: + for job in workflow_result["results"]: status = job["status"].lower() if status == "skipped" and len(job["links"]) > 0: skipped_jobs.append( @@ -1221,10 +1412,15 @@ def create_workflow_report( settings={"use_numpy": True}, ) + workflow_result = fetch_workflow_result_json(pr_number, branch_name, commit_sha) + results_dfs = { "prs_in_release": [], - "job_statuses": get_commit_statuses(commit_sha), - "checks_fails": get_checks_fails(db_client, commit_sha, branch_name), + "job_statuses": get_commit_statuses(commit_sha, pr_number, branch_name), + "checks_fails": add_labels_to_checks_fails( + get_checks_fails(db_client, commit_sha, branch_name), + workflow_result, + ), "checks_known_fails": [], "pr_new_fails": [], "checks_errors": get_checks_errors(db_client, commit_sha, branch_name), @@ -1236,12 +1432,12 @@ def create_workflow_report( if pr_number == 0 and not mark_preview: try: - prs_df, baseline_date = get_prs_in_release_dataframe( - branch_name, cwd=os.getcwd() + prs_df, baseline_time = get_prs_in_release_dataframe( + branch_name, cwd=os.getcwd(), commit_sha=commit_sha ) results_dfs["prs_in_release"] = prs_df prs_in_release_search_url = _prs_in_release_search_url( - branch_name, baseline_date + branch_name, baseline_time ) except Exception as e: print(f"Error in get_prs_in_release_dataframe: {e}") @@ -1286,7 +1482,8 @@ def create_workflow_report( pr_info = {} results_dfs["job_statuses"] = backfill_skipped_statuses( - results_dfs["job_statuses"], pr_number, branch_name, commit_sha + results_dfs["job_statuses"], + workflow_result, ) high_cve_count = 0 diff --git a/.github/actions/create_workflow_report/test_prs_in_release.py b/.github/actions/create_workflow_report/test_prs_in_release.py new file mode 100644 index 0000000000000..695e92fa5330a --- /dev/null +++ b/.github/actions/create_workflow_report/test_prs_in_release.py @@ -0,0 +1,440 @@ +#!/usr/bin/env python3 +"""Unit tests for PRs-in-release baseline selection and merge filtering.""" + +import io +import subprocess +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from unittest.mock import MagicMock, patch + +import pandas as pd + +from create_workflow_report import ( + RELEASE_TAG_REF_PREFIX, + _checks_latest_test_status_cte, + _enrich_prs_in_release_merge_prs, + _fetch_release_tags, + _fetch_commit, + _find_release_baseline, + _get_github_releases, + _git_log_merge_prs, + _git_rev_parse, + _parse_release_ref, + _release_tag_sha, + _filter_releases_to_version_line, + get_prs_in_release_dataframe, + get_workflow_config, +) + +CURRENT_TAG = "v26.6.4.20001.altinityantalya" +PREV_TAG = "v26.6.2.20001.altinityantalya" +CURRENT_SHA = "aa" * 20 +PREV_SHA = "bb" * 20 +# Linear history: PREV_SHA is an ancestor of CURRENT_SHA (and of itself). +COMMIT_ORDER = {PREV_SHA: 0, CURRENT_SHA: 1} +REF_TO_SHA = { + CURRENT_TAG: CURRENT_SHA, + PREV_TAG: PREV_SHA, + CURRENT_SHA: CURRENT_SHA, + PREV_SHA: PREV_SHA, + "antalya-26.6": CURRENT_SHA, +} + +RELEASES = [ + { + "draft": False, + "tag_name": CURRENT_TAG, + "published_at": "2026-09-11T14:02:51Z", + }, + { + "draft": False, + "tag_name": PREV_TAG, + "published_at": "2026-08-26T23:29:39Z", + }, +] + + +def _rev_parse(ref, cwd): + return REF_TO_SHA.get(ref.removeprefix(RELEASE_TAG_REF_PREFIX)) + + +def _is_ancestor(ancestor, descendant, cwd): + a = REF_TO_SHA.get(ancestor, ancestor) + d = REF_TO_SHA.get(descendant, descendant) + if a not in COMMIT_ORDER or d not in COMMIT_ORDER: + return False + return COMMIT_ORDER[a] <= COMMIT_ORDER[d] + + +def _releases_response(): + response = MagicMock() + response.status_code = 200 + response.json.return_value = RELEASES + [ + {"draft": True, "tag_name": "v26.9.1.1.altinityantalya"} + ] + return response + + +class TestGetGithubReleases(unittest.TestCase): + def test_skips_drafts(self): + with ( + patch("create_workflow_report.GITHUB_TOKEN", "token"), + patch( + "create_workflow_report.requests.get", + return_value=_releases_response(), + ), + ): + releases = _get_github_releases("Altinity/ClickHouse") + self.assertEqual(releases, RELEASES) + + def test_no_token_returns_empty(self): + with ( + patch("create_workflow_report.GITHUB_TOKEN", None), + patch("builtins.print") as printed, + ): + self.assertEqual(_get_github_releases("Altinity/ClickHouse"), []) + printed.assert_called_once() + self.assertIn("GITHUB_TOKEN", printed.call_args.args[0]) + + +class TestFindReleaseBaseline(unittest.TestCase): + def test_tag_ref_skips_current_github_release(self): + with ( + patch( + "create_workflow_report._git_rev_parse", + side_effect=_rev_parse, + ), + patch( + "create_workflow_report._git_is_ancestor", + side_effect=_is_ancestor, + ), + ): + tag_name, tag_sha, published_date = _find_release_baseline( + CURRENT_TAG, RELEASES, cwd=None, commit_sha=CURRENT_SHA + ) + self.assertEqual(tag_name, PREV_TAG) + self.assertEqual(tag_sha, PREV_SHA) + self.assertEqual(published_date, "2026-08-26") + + def test_branch_ref_keeps_latest_github_release(self): + with ( + patch( + "create_workflow_report._git_rev_parse", + side_effect=_rev_parse, + ), + patch( + "create_workflow_report._git_is_ancestor", + side_effect=_is_ancestor, + ), + ): + tag_name, tag_sha, published_date = _find_release_baseline( + "antalya-26.6", RELEASES, cwd=None, commit_sha=CURRENT_SHA + ) + self.assertEqual(tag_name, CURRENT_TAG) + self.assertEqual(tag_sha, CURRENT_SHA) + self.assertEqual(published_date, "2026-09-11") + + def test_ancestry_uses_commit_not_branch_name(self): + # Local antalya-26.6 may sit on the latest release; the run commit is older. + with ( + patch( + "create_workflow_report._git_rev_parse", + side_effect=_rev_parse, + ), + patch( + "create_workflow_report._git_is_ancestor", + side_effect=_is_ancestor, + ), + ): + tag_name, tag_sha, published_date = _find_release_baseline( + "antalya-26.6", RELEASES, cwd=None, commit_sha=PREV_SHA + ) + self.assertEqual(tag_name, PREV_TAG) + self.assertEqual(tag_sha, PREV_SHA) + self.assertEqual(published_date, "2026-08-26") + + +def _version_file(major, minor): + return subprocess.CompletedProcess( + [], 0, stdout=f"SET(VERSION_MAJOR {major})\nSET(VERSION_MINOR {minor})\n" + ) + + +class TestFetchCommit(unittest.TestCase): + def test_full_clone_fetches_commit_without_clone_shape_flags(self): + with ( + patch("create_workflow_report._git_is_shallow", return_value=False), + patch("create_workflow_report._git_fetch") as fetch, + ): + _fetch_commit(CURRENT_SHA, ".") + fetch.assert_called_once_with(["--no-tags"], [CURRENT_SHA], ".") + + def test_shallow_clone_unshallows_commit(self): + with ( + patch("create_workflow_report._git_is_shallow", return_value=True), + patch("create_workflow_report._git_fetch") as fetch, + ): + _fetch_commit(PREV_SHA, ".") + fetch.assert_called_once_with( + ["--filter=tree:0", "--unshallow"], [PREV_SHA], "." + ) + + +class TestReleaseTagSha(unittest.TestCase): + def test_namespaced_only_ignores_local_tag(self): + with patch( + "create_workflow_report._git_rev_parse", + side_effect=lambda ref, cwd: ( + "private" if ref.startswith(RELEASE_TAG_REF_PREFIX) else "local" + ), + ): + self.assertEqual(_release_tag_sha(CURRENT_TAG, None), "private") + with patch( + "create_workflow_report._git_rev_parse", + side_effect=lambda ref, cwd: ( + None if ref.startswith(RELEASE_TAG_REF_PREFIX) else "local" + ), + ): + self.assertIsNone(_release_tag_sha(CURRENT_TAG, None)) + + +class TestReleasesForVersionLine(unittest.TestCase): + def test_keeps_flavour_and_minor_drops_others(self): + mixed = RELEASES + [ + { + "draft": False, + "tag_name": "v25.8.33.10001.altinitystable", + "published_at": "2026-09-01T00:00:00Z", + }, + { + "draft": False, + "tag_name": "v26.6.8.10001.altinitystable", + "published_at": "2026-10-01T00:00:00Z", + }, + ] + out = _filter_releases_to_version_line(mixed, "antalya-26.6", CURRENT_SHA, None) + self.assertEqual([r["tag_name"] for r in out], [CURRENT_TAG, PREV_TAG]) + + def test_odd_branch_reads_version_file(self): + mixed = RELEASES + [ + { + "draft": False, + "tag_name": "v25.8.33.10001.altinitystable", + "published_at": "2026-09-01T00:00:00Z", + }, + ] + with patch( + "create_workflow_report._read_autogenerated_versions", + return_value=(26, 6), + ): + out = _filter_releases_to_version_line(mixed, "odd-branch", CURRENT_SHA, None) + self.assertEqual([r["tag_name"] for r in out], [CURRENT_TAG, PREV_TAG]) + + +class TestFipsRefs(unittest.TestCase): + def test_parses_fips_branch_and_tag(self): + self.assertEqual(_parse_release_ref("fips-25.3"), ("fips", "25.3", None)) + self.assertEqual( + _parse_release_ref("v25.3.8.30001.altinityfips"), + ("fips", "25.3", "25.3.8.30001"), + ) + + def test_fips_branch_keeps_only_fips_tags(self): + fips_tag = "v25.3.8.30001.altinityfips" + releases = [ + {"draft": False, "tag_name": fips_tag}, + {"draft": False, "tag_name": "v25.3.8.20001.altinityantalya"}, + {"draft": False, "tag_name": "v25.3.8.10001.altinitystable"}, + ] + out = _filter_releases_to_version_line(releases, "fips-25.3", CURRENT_SHA, None) + self.assertEqual([r["tag_name"] for r in out], [fips_tag]) + + +class TestFetchReleaseTagsWarning(unittest.TestCase): + def test_missing_tag_is_reported(self): + def _fetch(options, refspecs, cwd): + if CURRENT_TAG in refspecs[0]: + raise Exception("git fetch failed: not found") + + with ( + patch("create_workflow_report._git_fetch", side_effect=_fetch), + patch("builtins.print") as printed, + ): + _fetch_release_tags(RELEASES, ".") + message = printed.call_args.args[0] + self.assertIn(CURRENT_TAG, message) + self.assertIn("not found", message) + + +class TestGitRevParse(unittest.TestCase): + def test_missing_full_sha_is_unresolved(self): + with tempfile.TemporaryDirectory() as cwd: + subprocess.run(["git", "init", "-q"], cwd=cwd, check=True) + subprocess.run( + [ + "git", + "-c", + "user.name=t", + "-c", + "user.email=t@t", + "commit", + "-q", + "--allow-empty", + "-m", + "init", + ], + cwd=cwd, + check=True, + ) + head = _git_rev_parse("HEAD", cwd) + self.assertRegex(head, r"^[0-9a-f]{40}$") + self.assertEqual(_git_rev_parse(head, cwd), head) + self.assertIsNone(_git_rev_parse("deadbeef" * 5, cwd)) + + +class TestGetPrsInReleaseCommitResolve(unittest.TestCase): + def test_raises_when_commit_cannot_resolve(self): + with ( + patch("create_workflow_report._fetch_commit"), + patch("create_workflow_report._git_rev_parse", return_value=None), + ): + with self.assertRaises(Exception) as ctx: + get_prs_in_release_dataframe( + "antalya-26.6", cwd=".", commit_sha="deadbeef" * 5 + ) + self.assertIn("Cannot resolve commit", str(ctx.exception)) + self.assertIn("deadbeef", str(ctx.exception)) + + +class TestGitLogMergePrs(unittest.TestCase): + def test_includes_fork_and_altinity_omits_upstream(self): + stdout = "\n".join( + [ + f"{'e' * 40}\tMerge pull request #2226 from strtgbb/community-pr-failure-artifacts", + f"{'a' * 40}\tMerge pull request #113355 from ClickHouse/backport/26.6/112784", + f"{'b' * 40}\tMerge pull request #2252 from Altinity/ci/antalya-26.6/container-dns", + ] + ) + with patch( + "create_workflow_report.subprocess.run", + return_value=subprocess.CompletedProcess([], 0, stdout=stdout), + ): + df = _git_log_merge_prs("baseline", "branch", None, "Altinity/ClickHouse") + self.assertEqual(set(df["pr_number"]), {2226, 2252}) + + +def _pr_response(status_code, title=""): + response = MagicMock() + response.status_code = status_code + response.text = "Not Found" if status_code == 404 else "error" + response.json.return_value = {"title": title, "labels": []} + return response + + +class TestEnrichPrsInReleaseMergePrs(unittest.TestCase): + def test_skips_404_keeps_ours(self): + # Upstream PR merged from a personal fork (head_owner != ClickHouse) + # is not in Altinity/ClickHouse; 404 means skip, not fail. + df = pd.DataFrame( + { + "pr_number": [114814, 2287], + "merge_commit_sha": ["a" * 40, "b" * 40], + "merge_subject": [ + "Merge pull request #114814 from groeneai/groeneai/backport-112327-25.8", + "Merge pull request #2287 from Altinity/bump/antalya-25.8/25.8.33", + ], + } + ) + + def _get(url, headers=None): + if url.endswith("/114814"): + return _pr_response(404) + return _pr_response(200, title="bump 25.8.33") + + with ( + patch("create_workflow_report.GITHUB_TOKEN", "token"), + patch("create_workflow_report.requests.get", side_effect=_get), + ): + out = _enrich_prs_in_release_merge_prs(df, "Altinity/ClickHouse") + self.assertEqual(list(out["pr_number"]), [2287]) + self.assertEqual(list(out["pr_name"]), ["bump 25.8.33"]) + + def test_all_404s_returns_empty(self): + df = pd.DataFrame( + { + "pr_number": [114814], + "merge_commit_sha": ["a" * 40], + "merge_subject": [ + "Merge pull request #114814 from groeneai/groeneai/backport-112327-25.8", + ], + } + ) + with ( + patch("create_workflow_report.GITHUB_TOKEN", "token"), + patch( + "create_workflow_report.requests.get", + return_value=_pr_response(404), + ), + ): + out = _enrich_prs_in_release_merge_prs(df, "Altinity/ClickHouse") + self.assertEqual(len(out), 0) + self.assertEqual(list(out.columns), ["pr_number", "pr_name", "pr_labels"]) + + def test_non_404_error_still_raises(self): + df = pd.DataFrame( + { + "pr_number": [2287], + "merge_commit_sha": ["b" * 40], + "merge_subject": [ + "Merge pull request #2287 from Altinity/bump/antalya-25.8/25.8.33", + ], + } + ) + with ( + patch("create_workflow_report.GITHUB_TOKEN", "token"), + patch( + "create_workflow_report.requests.get", + return_value=_pr_response(500), + ), + ): + with self.assertRaises(Exception) as ctx: + _enrich_prs_in_release_merge_prs(df, "Altinity/ClickHouse") + self.assertIn("500", str(ctx.exception)) + + +class TestChecksLatestTestStatusCte(unittest.TestCase): + def test_anchor_excludes_hooks_and_empty_test_name(self): + sql = _checks_latest_test_status_cte("deadbeef", "antalya-25.8") + self.assertIn("test_name != ''", sql) + self.assertIn("NOT IN ('Pre Hooks', 'Post Hooks')", sql) + + +class TestGetWorkflowConfigStdout(unittest.TestCase): + def tearDown(self): + get_workflow_config.cache_clear() + + def test_s3_404_warning_goes_to_stderr(self): + # action.yml takes the script's stdout as the report URL. A WARNING on + # stdout makes grep '^https?://' fail even after a successful upload. + mock_resp = MagicMock() + mock_resp.status_code = 404 + stdout = io.StringIO() + stderr = io.StringIO() + with ( + patch( + "create_workflow_report._workflow_config_from_local", + return_value=None, + ), + patch("create_workflow_report.requests.get", return_value=mock_resp), + redirect_stdout(stdout), + redirect_stderr(stderr), + ): + cfg = get_workflow_config(2719, "abc", "antalya-25.8") + self.assertEqual(cfg, {"cache_jobs": {}}) + self.assertEqual(stdout.getvalue(), "") + self.assertIn("WARNING", stderr.getvalue()) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/actions/create_workflow_report/test_report_queries.py b/.github/actions/create_workflow_report/test_report_queries.py new file mode 100755 index 0000000000000..dd824c1b7bda4 --- /dev/null +++ b/.github/actions/create_workflow_report/test_report_queries.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +""" +Test cases for report queries: ``commit_sha``, ``head_ref``, and ``expect``. + +``expect`` is the exact set of ``test_name`` values the query must return (order +ignored). Use an empty ``expect`` tuple when it must return no failures. + +Requires ``testflows`` and ``clickhouse_driver``. Run: + + pip install testflows + python .github/actions/create_workflow_report/test_report_queries.py + +Set ``CHECKS_DATABASE_HOST``, ``CLICKHOUSE_TEST_STAT_LOGIN``, +``CLICKHOUSE_TEST_STAT_PASSWORD``. + +To capture ``expect`` for a commit: + + python3 -c " + import os + from clickhouse_driver import Client + from create_workflow_report import get_checks_fails + c = Client(host=os.environ['CHECKS_DATABASE_HOST'], + user=os.environ['CLICKHOUSE_TEST_STAT_LOGIN'], + password=os.environ['CLICKHOUSE_TEST_STAT_PASSWORD'], + port=9440, secure='y', verify=False, settings={'use_numpy': True}) + df = get_checks_fails(c, 'COMMIT_SHA', 'HEAD_REF') + print(repr(tuple(sorted(df['test_name'].tolist())))) + " +""" + +import os +import subprocess +from unittest.mock import patch + +import pandas as pd +from clickhouse_driver import Client +from testflows.core import * + +from create_workflow_report import ( + _git_log_merge_prs, + get_checks_fails, + get_commit_statuses, +) + + +def check_result_matches_expect(df: pd.DataFrame, expect: list[str]) -> None: + """Result ``test_name`` values must match ``expect`` exactly (as a set).""" + if "test_name" not in df.columns and not df.empty: + fail(f"DataFrame has no test_name column: {df.columns.tolist()}") + actual = set(df["test_name"].tolist()) if not df.empty else set() + required = set(expect) + if actual != required: + fail(f"test results mismatch: got {sorted(actual)}; expected {sorted(required)}") + + +@TestOutline(Scenario) +@Examples( + "case_id commit_sha head_ref expect", + [ + ( + "tests_passing_no_reruns", + "088fb0351680a67f6f34b4ad56ca12030012c919", + "qa/update-broken-tests", + (), + ), + ( + "stress_startup_failed_no_reruns", + "33fd024890a401d30d85b3fad656f9deba916cbc", + "antalya-25.8", + ( + "Cannot start clickhouse-server", + "Server failed to start (see application_errors.txt and clickhouse-server.clean.log)", + ), + ), + ( + "stateless_and_integration_failed_no_reruns", + "4edabbad8665e1c727bbd7c891e0cbcd81aefd56", + "antalya-25.8", + ( + "test_dns_cache/test.py::test_user_access_ip_change[node5]", + "01042_check_query_and_last_granule_size", + ), + ), + ( + "stateless_fail_in_teardown_no_reruns", + "0cd90a87ab7b2ad83d24df87d96af5d3de7858c2", + "feature/antalya-26.1/json_part2", + ( + "00411_long_accurate_number_comparison_int4", + "03572_export_merge_tree_part_limits_and_table_functions", + "Exception in test runner", + "Some queries hung", + ), + ), + ( + "stateless_and_integration_passed_after_reruns", + "49bb3f7beb5e6e424a1e94c749478fd23a8e6196", + "antalya-25.8", + (), + ), + ( + "stress_passed_after_reruns", + "51762a72207f3d4bcee51a0a78912a8b2cbb1bb5", + "antalya-25.8", + (), + ), + # Post Hooks (later stopwatch) used to set the check_start_time anchor and hide + # Integration FAILs, e.g. AzureQueue on https://github.com/Altinity/ClickHouse/pull/2107. + ( + "integration_fail_hidden_by_post_hooks_anchor", + "6185add695010b15869b3dc1bcb9c417621b36bc", + "bump/antalya-26.6/26.6.2.81", + ( + "00071_merge_tree_optimize_aio", + "01167_isolation_hermitage", + "01509_parallel_quorum_insert_no_replicas_long", + "03752_attach_as_replicated_transaction_metadata", + "04033_tpc_ds_q20", + "04033_tpc_ds_q39", + "04033_tpc_ds_q58", + "04033_tpc_ds_q71", + "Unknown error", + "test_storage_s3_queue/test_0.py::test_move_after_processing[another_bucket-AzureQueue]", + ), + ), + ], +) +def test_checks_fails_query(self, case_id, commit_sha, head_ref, expect): + """Test checks fails query for one commit.""" + with Given("DB Client is configured"): + host = os.getenv("CHECKS_DATABASE_HOST") + user = os.getenv("CLICKHOUSE_TEST_STAT_LOGIN") + password = os.getenv("CLICKHOUSE_TEST_STAT_PASSWORD") + if not all([host, user, password]): + skip( + "Set CHECKS_DATABASE_HOST, CLICKHOUSE_TEST_STAT_LOGIN, " + "CLICKHOUSE_TEST_STAT_PASSWORD" + ) + client = Client( + host=host, + user=user, + password=password, + port=9440, + secure="y", + verify=False, + settings={"use_numpy": True}, + ) + + with When( + f"I call get_checks_fails for commit_sha={commit_sha!r} head_ref={head_ref!r}" + ): + df = get_checks_fails(client, commit_sha, head_ref) + + with Then("result test_name set equals expect"): + check_result_matches_expect(df, list(expect)) + + +@TestScenario +def test_git_log_merge_prs_includes_fork_prs(self): + """Test PR merge history includes contributor forks.""" + stdout = "\n".join( + [ + f"{'e' * 40}\tMerge pull request #2226 from strtgbb/community-pr-failure-artifacts", + f"{'a' * 40}\tMerge pull request #113355 from ClickHouse/backport/26.6/112784", + f"{'b' * 40}\tMerge pull request #2252 from Altinity/ci/antalya-26.6/container-dns", + ] + ) + with When("I collect fork, upstream, and Altinity pull request merges"): + with patch( + "create_workflow_report.subprocess.run", + return_value=subprocess.CompletedProcess([], 0, stdout=stdout), + ): + df = _git_log_merge_prs("baseline", "branch", None, "Altinity/ClickHouse") + + with Then("fork and Altinity pull requests are included but upstream is omitted"): + assert set(df["pr_number"]) == {2226, 2252} + + +@TestScenario +def test_commit_statuses_only_keep_requested_ref(self): + """Test statuses written by another ref on the same commit are ignored.""" + base = "https://altinity-build-artifacts.s3.amazonaws.com" + tag = "v26.6.8.20002.altinityantalya" + # GitHub returns statuses newest first. + statuses = [ + ("Stress test", "failure", f"{base}/json.html?REF=antalya-26.6&sha=abc"), + ("Regression release x", "failure", f"{base}/REFs/antalya-26.6/abc/r.html"), + ("Regression release y", "success", f"{base}/REFs/{tag}/abc/r.html"), + ("Stress test", "success", f"{base}/json.html?REF={tag}&sha=abc"), + ("PR job", "success", f"{base}/json.html?PR=1&sha=abc"), + ] + response = type( + "Response", + (), + { + "status_code": 200, + "headers": {}, + "json": lambda _: [ + {"context": c, "state": st, "description": "", "target_url": u} + for c, st, u in statuses + ], + }, + )() + with When("I fetch statuses for the release tag"): + with patch("create_workflow_report.requests.get", return_value=response): + df = get_commit_statuses("abc", 0, tag) + + with Then("only statuses of the tag remain"): + assert dict(zip(df["job_name"], df["job_status"])) == { + "Stress test": "success", + "Regression release y": "success", + } + + with When("I fetch statuses for a pull request run"): + with patch("create_workflow_report.requests.get", return_value=response): + df = get_commit_statuses("abc", 1, "some-branch") + + with Then("statuses are not filtered by ref"): + assert set(df["job_name"]) == {"Stress test", "Regression release x", "Regression release y", "PR job"} + + +@Name("test report queries") +@TestModule +def test_report_queries(self): + Scenario(run=test_checks_fails_query, flags=TE) + Scenario(run=test_git_log_merge_prs_includes_fork_prs, flags=TE) + Scenario(run=test_commit_statuses_only_keep_requested_ref, flags=TE) + + +if main(): + test_report_queries() diff --git a/.github/workflows/release_builds.yml b/.github/workflows/fast_builds.yml similarity index 90% rename from .github/workflows/release_builds.yml rename to .github/workflows/fast_builds.yml index 2014e4f9c3969..890ebed1271fe 100644 --- a/.github/workflows/release_builds.yml +++ b/.github/workflows/fast_builds.yml @@ -1,6 +1,6 @@ # generated by praktika -name: Release Builds +name: Fast Builds on: workflow_dispatch: inputs: @@ -69,7 +69,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -83,9 +83,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Config Workflow' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Config Workflow' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Config Workflow' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Config Workflow' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi dockers_build_amd: @@ -117,7 +117,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -131,9 +131,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Dockers Build (amd)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Dockers Build (amd)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Dockers Build (amd)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Dockers Build (amd)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi dockers_build_arm: @@ -165,7 +165,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -179,9 +179,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Dockers Build (arm)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Dockers Build (arm)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Dockers Build (arm)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Dockers Build (arm)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi build_amd_debug: @@ -213,7 +213,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -227,9 +227,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Build (amd_debug)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_debug)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Build (amd_debug)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_debug)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi build_amd_release: @@ -261,7 +261,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -275,9 +275,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Build (amd_release)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_release)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Build (amd_release)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_release)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi build_amd_asan: @@ -309,7 +309,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -323,9 +323,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Build (amd_asan)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_asan)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Build (amd_asan)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_asan)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi build_amd_tsan: @@ -357,7 +357,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -371,9 +371,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Build (amd_tsan)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_tsan)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Build (amd_tsan)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_tsan)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi build_amd_msan: @@ -405,7 +405,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -419,9 +419,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Build (amd_msan)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_msan)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Build (amd_msan)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_msan)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi build_amd_ubsan: @@ -453,7 +453,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -467,9 +467,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Build (amd_ubsan)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_ubsan)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Build (amd_ubsan)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_ubsan)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi build_amd_binary: @@ -501,7 +501,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -515,9 +515,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Build (amd_binary)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_binary)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Build (amd_binary)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Build (amd_binary)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi build_arm_release: @@ -549,7 +549,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -563,9 +563,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Build (arm_release)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Build (arm_release)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Build (arm_release)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Build (arm_release)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi build_arm_binary: @@ -597,7 +597,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -611,9 +611,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Build (arm_binary)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Build (arm_binary)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Build (arm_binary)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Build (arm_binary)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi docker_server_image: @@ -645,7 +645,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -659,9 +659,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Docker server image' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Docker server image' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Docker server image' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Docker server image' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi docker_keeper_image: @@ -693,7 +693,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -707,9 +707,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Docker keeper image' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Docker keeper image' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Docker keeper image' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Docker keeper image' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi install_packages_amd_release: @@ -741,7 +741,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -755,9 +755,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Install packages (amd_release)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Install packages (amd_release)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Install packages (amd_release)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Install packages (amd_release)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi install_packages_arm_release: @@ -789,7 +789,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -803,9 +803,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Install packages (arm_release)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Install packages (arm_release)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Install packages (arm_release)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Install packages (arm_release)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi stateless_tests_amd_binary_old_analyzer_s3_storage_databasereplicated_parallel: @@ -837,7 +837,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -851,9 +851,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Stateless tests (amd_binary, old analyzer, s3 storage, DatabaseReplicated, parallel)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (amd_binary, old analyzer, s3 storage, DatabaseReplicated, parallel)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Stateless tests (amd_binary, old analyzer, s3 storage, DatabaseReplicated, parallel)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (amd_binary, old analyzer, s3 storage, DatabaseReplicated, parallel)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi stateless_tests_amd_binary_old_analyzer_s3_storage_databasereplicated_sequential: @@ -885,7 +885,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -899,9 +899,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Stateless tests (amd_binary, old analyzer, s3 storage, DatabaseReplicated, sequential)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (amd_binary, old analyzer, s3 storage, DatabaseReplicated, sequential)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Stateless tests (amd_binary, old analyzer, s3 storage, DatabaseReplicated, sequential)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (amd_binary, old analyzer, s3 storage, DatabaseReplicated, sequential)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi stateless_tests_amd_binary_parallelreplicas_s3_storage_parallel: @@ -933,7 +933,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -947,9 +947,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Stateless tests (amd_binary, ParallelReplicas, s3 storage, parallel)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (amd_binary, ParallelReplicas, s3 storage, parallel)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Stateless tests (amd_binary, ParallelReplicas, s3 storage, parallel)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (amd_binary, ParallelReplicas, s3 storage, parallel)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi stateless_tests_amd_binary_parallelreplicas_s3_storage_sequential: @@ -981,7 +981,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -995,9 +995,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Stateless tests (amd_binary, ParallelReplicas, s3 storage, sequential)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (amd_binary, ParallelReplicas, s3 storage, sequential)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Stateless tests (amd_binary, ParallelReplicas, s3 storage, sequential)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (amd_binary, ParallelReplicas, s3 storage, sequential)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi stateless_tests_arm_binary_parallel: @@ -1029,7 +1029,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -1043,9 +1043,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Stateless tests (arm_binary, parallel)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (arm_binary, parallel)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Stateless tests (arm_binary, parallel)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (arm_binary, parallel)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi stateless_tests_arm_binary_sequential: @@ -1077,7 +1077,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -1091,9 +1091,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Stateless tests (arm_binary, sequential)' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (arm_binary, sequential)' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Stateless tests (arm_binary, sequential)' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Stateless tests (arm_binary, sequential)' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi finish_workflow: @@ -1125,7 +1125,7 @@ jobs: cat > ./ci/tmp/workflow_inputs.json << 'EOF' ${{ toJson(github.event.inputs) }} EOF - cat > ./ci/tmp/workflow_config_release_builds.json << 'EOF' + cat > ./ci/tmp/workflow_config_fast_builds.json << 'EOF' ${{ needs.config_workflow.outputs.data }} EOF cat > ./ci/tmp/workflow_status.json << 'EOF' @@ -1139,9 +1139,9 @@ jobs: . ./ci/tmp/praktika_setup_env.sh set -o pipefail if command -v ts &> /dev/null; then - python3 -m praktika run 'Finish Workflow' --workflow "Release Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log + python3 -m praktika run 'Finish Workflow' --workflow "Fast Builds" --ci |& ts '[%Y-%m-%d %H:%M:%S]' | tee ./ci/tmp/job.log else - python3 -m praktika run 'Finish Workflow' --workflow "Release Builds" --ci |& tee ./ci/tmp/job.log + python3 -m praktika run 'Finish Workflow' --workflow "Fast Builds" --ci |& tee ./ci/tmp/job.log fi ########################################################################################## @@ -1229,6 +1229,7 @@ jobs: if: ${{ !cancelled() }} uses: ./.github/actions/create_workflow_report with: + workflow_config: ${{ needs.config_workflow.outputs.data }} final: true SourceUpload: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index e8462da920252..c8b68c6f70862 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -4372,6 +4372,7 @@ jobs: if: ${{ !cancelled() }} uses: ./.github/actions/create_workflow_report with: + workflow_config: ${{ needs.config_workflow.outputs.data }} final: true SourceUpload: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 50793485c1682..d196c684db7fe 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -4215,6 +4215,7 @@ jobs: if: ${{ !cancelled() }} uses: ./.github/actions/create_workflow_report with: + workflow_config: ${{ needs.config_workflow.outputs.data }} final: true SourceUpload: diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index b3f3ae84ebcd4..11d5c80f3e6da 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -126,7 +126,7 @@ jobs: strategy: fail-fast: false matrix: - SUITE: [aes_encryption, atomic_insert, attach, base_58, clickhouse_keeper_failover,data_types, datetime64_extended_range, disk_level_encryption, dns, engines, example, extended_precision_data_types, functions, jwt_authentication, kafka, kerberos, key_value, lightweight_delete, memory, part_moves_between_shards, selects, session_timezone, settings, version, window_functions] + SUITE: [aes_encryption, atomic_insert, attach, base_58, clickhouse_keeper_failover,data_types, datetime64_extended_range, disk_level_encryption, dns, engines, example, extended_precision_data_types, functions, jwt_authentication, kafka, kerberos, key_value, memory, part_moves_between_shards, selects, session_timezone, settings, version, window_functions] uses: ./.github/workflows/regression-reusable-suite.yml with: ref: ${{ inputs.commit }} @@ -143,6 +143,40 @@ jobs: job_name: ${{ matrix.SUITE }} secrets: inherit + LightweightDelete: + if: | + fromJson(inputs.workflow_config).custom_data.ci_regression_jobs[0] == null || + contains(fromJson(inputs.workflow_config).custom_data.ci_regression_jobs, 'common') + strategy: + fail-fast: false + matrix: + include: + - PART: 1 + FILTER: --only "/lightweight delete/disk space/*" + - PART: 2 + FILTER: --only "/lightweight delete/hard restart/*" + - PART: 3 + FILTER: --skip "/lightweight delete/disk space/*" "/lightweight delete/hard restart/*" "/lightweight delete/alter after delete/*" "/lightweight delete/concurrent alter and delete/*" "/lightweight delete/concurrent delete/*" "/lightweight delete/basic checks/*" "/lightweight delete/performance/*" "/lightweight delete/ontime tests/*" + - PART: 4 + FILTER: --only "/lightweight delete/alter after delete/*" "/lightweight delete/concurrent alter and delete/*" "/lightweight delete/concurrent delete/*" "/lightweight delete/basic checks/*" "/lightweight delete/performance/*" "/lightweight delete/ontime tests/*" + uses: ./.github/workflows/regression-reusable-suite.yml + with: + ref: ${{ inputs.commit }} + workflow_config: ${{ inputs.workflow_config }} + suite_name: lightweight_delete + suite_executable: regression.py + output_format: new-fails + flags: --with-analyzer + timeout_minutes: 300 + runner_arch: ${{ inputs.arch }} + runner_type: ${{ inputs.runner_type }} + part: ${{ matrix.PART }} + build_sha: ${{ inputs.build_sha }} + set_commit_status: true + job_name: lightweight_delete + extra_args: ${{ matrix.FILTER }} + secrets: inherit + AggregateFunctions: if: | fromJson(inputs.workflow_config).custom_data.ci_regression_jobs[0] == null || @@ -303,6 +337,29 @@ jobs: job_name: ldap_${{ matrix.SUITE }} secrets: inherit + OAuth: + if: | + contains(fromJson(inputs.workflow_config).custom_data.version.string, 'antalya') && + ( + fromJson(inputs.workflow_config).custom_data.ci_regression_jobs[0] == null || + contains(fromJson(inputs.workflow_config).custom_data.ci_regression_jobs, 'oauth') + ) + uses: ./.github/workflows/regression-reusable-suite.yml + with: + ref: ${{ inputs.commit }} + workflow_config: ${{ inputs.workflow_config }} + suite_name: oauth + suite_executable: regression.py + output_format: new-fails + flags: --with-analyzer --identity-provider keycloak + timeout_minutes: ${{ inputs.timeout_minutes }} + runner_arch: ${{ inputs.arch }} + runner_type: ${{ inputs.runner_type }} + build_sha: ${{ inputs.build_sha }} + set_commit_status: true + job_name: oauth + secrets: inherit + Parquet: if: | fromJson(inputs.workflow_config).custom_data.ci_regression_jobs[0] == null || diff --git a/ci/praktika/yaml_additional_templates.py b/ci/praktika/yaml_additional_templates.py index 8da2aa5938f70..aab5ff4903664 100644 --- a/ci/praktika/yaml_additional_templates.py +++ b/ci/praktika/yaml_additional_templates.py @@ -122,6 +122,7 @@ class AltinityWorkflowTemplates: if: ${{ !cancelled() }} uses: ./.github/actions/create_workflow_report with: + workflow_config: ${{ needs.config_workflow.outputs.data }} final: true """, "SourceUpload": r""" diff --git a/ci/workflows/release_builds.py b/ci/workflows/fast_builds.py similarity index 98% rename from ci/workflows/release_builds.py rename to ci/workflows/fast_builds.py index 9ecbc39a5b9a2..f1041905e4703 100644 --- a/ci/workflows/release_builds.py +++ b/ci/workflows/fast_builds.py @@ -19,7 +19,7 @@ ] workflow = Workflow.Config( - name="Release Builds", + name="Fast Builds", event=Workflow.Event.DISPATCH, jobs=[ *[ diff --git a/cmake/autogenerated_versions.txt b/cmake/autogenerated_versions.txt index 7748605693972..b00fcffc6cc52 100644 --- a/cmake/autogenerated_versions.txt +++ b/cmake/autogenerated_versions.txt @@ -7,9 +7,9 @@ SET(VERSION_MAJOR 25) SET(VERSION_MINOR 8) SET(VERSION_PATCH 33) SET(VERSION_GITHASH 46297556f3ac83e1a3ee4a05cc31f4287f5e17ac) -SET(VERSION_DESCRIBE v25.8.33.20002.altinityantalya) -SET(VERSION_STRING 25.8.33.20002.altinityantalya) +SET(VERSION_DESCRIBE v25.8.33.20003.altinityantalya) +SET(VERSION_STRING 25.8.33.20003.altinityantalya) # end of autochange -SET(VERSION_TWEAK 20002) +SET(VERSION_TWEAK 20003) SET(VERSION_FLAVOUR altinityantalya)