diff --git a/.github/create_combined_ci_report.py b/.github/create_combined_ci_report.py
new file mode 100755
index 000000000000..012348b3e278
--- /dev/null
+++ b/.github/create_combined_ci_report.py
@@ -0,0 +1,237 @@
+#!/usr/bin/env python3
+import argparse
+import os
+from pathlib import Path
+from itertools import combinations
+
+import requests
+from clickhouse_driver import Client
+import boto3
+from botocore.exceptions import NoCredentialsError
+
+DATABASE_HOST_VAR = "CHECKS_DATABASE_HOST"
+DATABASE_USER_VAR = "CHECKS_DATABASE_USER"
+DATABASE_PASSWORD_VAR = "CHECKS_DATABASE_PASSWORD"
+S3_BUCKET = "altinity-build-artifacts"
+
+
+def get_checks_fails(client: Client, job_url: str):
+ """
+ Get tests that did not succeed for the given job URL.
+ Exclude checks that have status 'error' as they are counted in get_checks_errors.
+ """
+ columns = (
+ "check_status, check_name, test_status, test_name, report_url as results_link"
+ )
+ query = f"""SELECT {columns} FROM `gh-data`.checks
+ WHERE task_url='{job_url}'
+ AND test_status in ('FAIL', 'ERROR')
+ AND check_status!='error'
+ ORDER BY check_name, test_name
+ """
+ return client.query_dataframe(query)
+
+
+def get_checks_errors(client: Client, job_url: str):
+ """
+ Get checks that have status 'error' for the given job URL.
+ """
+ columns = (
+ "check_status, check_name, test_status, test_name, report_url as results_link"
+ )
+ query = f"""SELECT {columns} FROM `gh-data`.checks
+ WHERE task_url='{job_url}'
+ AND check_status=='error'
+ ORDER BY check_name, test_name
+ """
+ return client.query_dataframe(query)
+
+
+def drop_prefix_rows(df, column_to_clean):
+ """
+ Drop rows from the dataframe if:
+ - the row matches another row completely except for the specified column
+ - the specified column of that row is a prefix of the same column in another row
+ """
+ to_drop = set()
+ reference_columns = [col for col in df.columns if col != column_to_clean]
+ for (i, row_1), (j, row_2) in combinations(df.iterrows(), 2):
+ if all(row_1[col] == row_2[col] for col in reference_columns):
+ if row_2[column_to_clean].startswith(row_1[column_to_clean]):
+ to_drop.add(i)
+ elif row_1[column_to_clean].startswith(row_2[column_to_clean]):
+ to_drop.add(j)
+ return df.drop(to_drop)
+
+
+def get_regression_fails(client: Client, job_url: str):
+ """
+ Get regression tests that did not succeed for the given job URL.
+ """
+ # If you rename the alias for report_url, also update the formatters in format_results_as_html_table
+ query = f"""SELECT arch, status, test_name, results_link
+ FROM (
+ SELECT
+ architecture as arch,
+ test_name,
+ argMax(result, start_time) AS status,
+ job_url,
+ report_url as results_link
+ FROM `gh-data`.clickhouse_regression_results
+ GROUP BY architecture, test_name, job_url, report_url, start_time
+ ORDER BY start_time DESC, length(test_name) DESC
+ )
+ WHERE job_url='{job_url}'
+ AND status IN ('Fail', 'Error')
+ """
+ df = client.query_dataframe(query)
+ df = drop_prefix_rows(df, "test_name")
+ return df
+
+
+def url_to_html_link(url: str) -> str:
+ if not url:
+ return ""
+ text = url.split("/")[-1]
+ if not text:
+ text = "results"
+ return f'{text}'
+
+
+def format_test_name_for_linewrap(text: str) -> str:
+ """Tweak the test name to improve line wrapping."""
+ return text.replace(".py::", "/")
+
+
+def format_results_as_html_table(results) -> str:
+ if results.empty:
+ return ""
+ results.columns = [col.replace("_", " ").title() for col in results.columns]
+ html = (
+ results.to_html(
+ index=False,
+ formatters={
+ "Results Link": url_to_html_link,
+ "Test Name": format_test_name_for_linewrap,
+ },
+ escape=False,
+ ) # tbody/thead tags interfere with the table sorting script
+ .replace("
\n", "")
+ .replace("\n", "")
+ .replace("\n", "")
+ .replace("\n", "")
+ .replace(' argparse.Namespace:
+ parser = argparse.ArgumentParser(description="Create a combined CI report.")
+ parser.add_argument(
+ "--actions-run-url", required=True, help="URL of the actions run"
+ )
+ parser.add_argument(
+ "--pr-number", required=True, help="Pull request number for the S3 path"
+ )
+ parser.add_argument(
+ "--commit-sha", required=True, help="Commit SHA for the S3 path"
+ )
+ parser.add_argument(
+ "--no-upload", action="store_true", help="Do not upload the report"
+ )
+ parser.add_argument(
+ "--mark-preview", action="store_true", help="Mark the report as a preview"
+ )
+ return parser.parse_args()
+
+
+def main():
+ args = parse_args()
+
+ db_client = Client(
+ host=os.getenv(DATABASE_HOST_VAR),
+ user=os.getenv(DATABASE_USER_VAR),
+ password=os.getenv(DATABASE_PASSWORD_VAR),
+ port=9440,
+ secure="y",
+ verify=False,
+ settings={"use_numpy": True},
+ )
+
+ s3_path = (
+ f"https://s3.amazonaws.com/{S3_BUCKET}/{args.pr_number}/{args.commit_sha}/"
+ )
+ report_destination_url = s3_path + "combined_report.html"
+ ci_running_report_url = s3_path + "ci_running.html"
+
+ response = requests.get(ci_running_report_url)
+ if response.status_code == 200:
+ ci_running_report: str = response.text
+ else:
+ print(
+ f"Failed to download CI running report. Status code: {response.status_code}, Response: {response.text}"
+ )
+ exit(1)
+
+ fail_results = {
+ "checks_fails": get_checks_fails(db_client, args.actions_run_url),
+ "checks_errors": get_checks_errors(db_client, args.actions_run_url),
+ "regression_fails": get_regression_fails(db_client, args.actions_run_url),
+ }
+
+ combined_report = (
+ ci_running_report.replace("ClickHouse CI running for", "Combined CI Report for")
+ .replace(
+ "",
+ f"""Table of Contents
+{'This is a preview. FinishCheck has not completed.
' if args.mark_preview else ""}
+
+
+CI Jobs Status
+",
+ f"""
+
+Checks Fails
+{format_results_as_html_table(fail_results['checks_fails'])}
+
+Checks Errors
+{format_results_as_html_table(fail_results['checks_errors'])}
+
+Regression Fails
+{format_results_as_html_table(fail_results['regression_fails'])}
+""",
+ )
+ )
+ report_path = Path("combined_report.html")
+ report_path.write_text(combined_report, encoding="utf-8")
+
+ if args.no_upload:
+ print(f"Report saved to {report_path}")
+ exit(0)
+
+ # Upload the report to S3
+ s3_client = boto3.client("s3")
+
+ try:
+ s3_client.put_object(
+ Bucket=S3_BUCKET,
+ Key=f"{args.pr_number}/{args.commit_sha}/combined_report.html",
+ Body=combined_report,
+ ContentType="text/html; charset=utf-8",
+ )
+ except NoCredentialsError:
+ print("Credentials not available for S3 upload.")
+
+ print(report_destination_url)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml
index 73728451316e..0fec14f81a80 100644
--- a/.github/workflows/regression.yml
+++ b/.github/workflows/regression.yml
@@ -174,6 +174,7 @@ jobs:
- name: Get deb url
run: python3 .github/get-deb-url.py --reports-path ${{ env.REPORTS_PATH }} --github-env $GITHUB_ENV
- name: Run ${{ env.SUITE }} suite
+ id: run_suite
run: EXITCODE=0;
python3
-u ${{ env.SUITE }}/regression.py
@@ -182,9 +183,20 @@ jobs:
${{ env.args }} || EXITCODE=$?;
.github/add_link_to_logs.sh;
exit $EXITCODE
+ - name: Set Commit Status
+ if: always()
+ run: python3 .github/set_builds_status.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ JOB_OUTCOME: ${{ steps.run_suite.outcome }}
+ SUITE_NAME: "Regression ${{ inputs.arch }} ${{ matrix.SUITE }}"
- name: Create and upload logs
if: always()
run: .github/create_and_upload_logs.sh 1
+ - name: Upload logs to regression results database
+ if: always()
+ timeout-minutes: 20
+ run: .github/upload_results_to_database.sh 1
- uses: actions/upload-artifact@v4
if: always()
with:
@@ -225,6 +237,7 @@ jobs:
- name: Get deb url
run: python3 .github/get-deb-url.py --reports-path ${{ env.REPORTS_PATH }} --github-env $GITHUB_ENV
- name: Run ${{ env.SUITE }} suite
+ id: run_suite
run: EXITCODE=0;
python3
-u alter/regression.py
@@ -234,9 +247,20 @@ jobs:
${{ env.args }} || EXITCODE=$?;
.github/add_link_to_logs.sh;
exit $EXITCODE
+ - name: Set Commit Status
+ if: always()
+ run: python3 .github/set_builds_status.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ JOB_OUTCOME: ${{ steps.run_suite.outcome }}
+ SUITE_NAME: "Regression ${{ inputs.arch }} Alter ${{ matrix.ONLY }} partition"
- name: Create and upload logs
if: always()
run: .github/create_and_upload_logs.sh 1
+ - name: Upload logs to regression results database
+ if: always()
+ timeout-minutes: 20
+ run: .github/upload_results_to_database.sh 1
- uses: actions/upload-artifact@v4
if: always()
with:
@@ -277,6 +301,7 @@ jobs:
- name: Get deb url
run: python3 .github/get-deb-url.py --reports-path ${{ env.REPORTS_PATH }} --github-env $GITHUB_ENV
- name: Run ${{ env.SUITE }} suite
+ id: run_suite
run: EXITCODE=0;
python3
-u ${{ env.SUITE }}/benchmark.py
@@ -293,9 +318,20 @@ jobs:
${{ env.args }} || EXITCODE=$?;
.github/add_link_to_logs.sh;
exit $EXITCODE
+ - name: Set Commit Status
+ if: always()
+ run: python3 .github/set_builds_status.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ JOB_OUTCOME: ${{ steps.run_suite.outcome }}
+ SUITE_NAME: "Regression ${{ inputs.arch }} Benchmark ${{ matrix.STORAGE }}"
- name: Create and upload logs
if: always()
run: .github/create_and_upload_logs.sh 1
+ - name: Upload logs to regression results database
+ if: always()
+ timeout-minutes: 20
+ run: .github/upload_results_to_database.sh 1
- uses: actions/upload-artifact@v4
if: always()
with:
@@ -332,6 +368,7 @@ jobs:
- name: Get deb url
run: python3 .github/get-deb-url.py --reports-path ${{ env.REPORTS_PATH }} --github-env $GITHUB_ENV
- name: Run ${{ env.SUITE }} suite
+ id: run_suite
run: EXITCODE=0;
python3
-u ${{ env.SUITE }}/regression.py
@@ -341,9 +378,20 @@ jobs:
${{ env.args }} || EXITCODE=$?;
.github/add_link_to_logs.sh;
exit $EXITCODE
+ - name: Set Commit Status
+ if: always()
+ run: python3 .github/set_builds_status.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ JOB_OUTCOME: ${{ steps.run_suite.outcome }}
+ SUITE_NAME: "Regression ${{ inputs.arch }} Clickhouse Keeper SSL"
- name: Create and upload logs
if: always()
run: .github/create_and_upload_logs.sh 1
+ - name: Upload logs to regression results database
+ if: always()
+ timeout-minutes: 20
+ run: .github/upload_results_to_database.sh 1
- uses: actions/upload-artifact@v4
if: always()
with:
@@ -383,6 +431,7 @@ jobs:
- name: Get deb url
run: python3 .github/get-deb-url.py --reports-path ${{ env.REPORTS_PATH }} --github-env $GITHUB_ENV
- name: Run ${{ env.SUITE }} suite
+ id: run_suite
run: EXITCODE=0;
python3
-u ${{ env.SUITE }}/regression.py
@@ -391,9 +440,20 @@ jobs:
${{ env.args }} || EXITCODE=$?;
.github/add_link_to_logs.sh;
exit $EXITCODE
+ - name: Set Commit Status
+ if: always()
+ run: python3 .github/set_builds_status.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ JOB_OUTCOME: ${{ steps.run_suite.outcome }}
+ SUITE_NAME: "Regression ${{ inputs.arch }} LDAP ${{ matrix.SUITE }}"
- name: Create and upload logs
if: always()
run: .github/create_and_upload_logs.sh 1
+ - name: Upload logs to regression results database
+ if: always()
+ timeout-minutes: 20
+ run: .github/upload_results_to_database.sh 1
- uses: actions/upload-artifact@v4
if: always()
with:
@@ -429,6 +489,7 @@ jobs:
- name: Get deb url
run: python3 .github/get-deb-url.py --reports-path ${{ env.REPORTS_PATH }} --github-env $GITHUB_ENV
- name: Run ${{ env.SUITE }} suite
+ id: run_suite
run: EXITCODE=0;
python3
-u ${{ env.SUITE }}/regression.py
@@ -437,9 +498,20 @@ jobs:
${{ env.args }} || EXITCODE=$?;
.github/add_link_to_logs.sh;
exit $EXITCODE
+ - name: Set Commit Status
+ if: always()
+ run: python3 .github/set_builds_status.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ JOB_OUTCOME: ${{ steps.run_suite.outcome }}
+ SUITE_NAME: "Regression ${{ inputs.arch }} Parquet"
- name: Create and upload logs
if: always()
run: .github/create_and_upload_logs.sh 1
+ - name: Upload logs to regression results database
+ if: always()
+ timeout-minutes: 20
+ run: .github/upload_results_to_database.sh 1
- uses: actions/upload-artifact@v4
if: always()
with:
@@ -480,6 +552,7 @@ jobs:
- name: Get deb url
run: python3 .github/get-deb-url.py --reports-path ${{ env.REPORTS_PATH }} --github-env $GITHUB_ENV
- name: Run ${{ env.SUITE }} suite
+ id: run_suite
run: EXITCODE=0;
python3
-u ${{ env.SUITE }}/regression.py
@@ -493,9 +566,20 @@ jobs:
${{ env.args }} || EXITCODE=$?;
.github/add_link_to_logs.sh;
exit $EXITCODE
+ - name: Set Commit Status
+ if: always()
+ run: python3 .github/set_builds_status.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ JOB_OUTCOME: ${{ steps.run_suite.outcome }}
+ SUITE_NAME: "Regression ${{ inputs.arch }} Parquet ${{ matrix.STORAGE }}"
- name: Create and upload logs
if: always()
run: .github/create_and_upload_logs.sh 1
+ - name: Upload logs to regression results database
+ if: always()
+ timeout-minutes: 20
+ run: .github/upload_results_to_database.sh 1
- uses: actions/upload-artifact@v4
if: always()
with:
@@ -536,6 +620,7 @@ jobs:
- name: Get deb url
run: python3 .github/get-deb-url.py --reports-path ${{ env.REPORTS_PATH }} --github-env $GITHUB_ENV
- name: Run ${{ env.SUITE }} suite
+ id: run_suite
run: EXITCODE=0;
python3
-u ${{ env.SUITE }}/regression.py
@@ -555,9 +640,20 @@ jobs:
${{ env.args }} || EXITCODE=$?;
.github/add_link_to_logs.sh;
exit $EXITCODE
+ - name: Set Commit Status
+ if: always()
+ run: python3 .github/set_builds_status.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ JOB_OUTCOME: ${{ steps.run_suite.outcome }}
+ SUITE_NAME: "Regression ${{ inputs.arch }} S3 ${{ matrix.STORAGE }}"
- name: Create and upload logs
if: always()
run: .github/create_and_upload_logs.sh 1
+ - name: Upload logs to regression results database
+ if: always()
+ timeout-minutes: 20
+ run: .github/upload_results_to_database.sh 1
- uses: actions/upload-artifact@v4
if: always()
with:
@@ -598,6 +694,7 @@ jobs:
- name: Get deb url
run: python3 .github/get-deb-url.py --reports-path ${{ env.REPORTS_PATH }} --github-env $GITHUB_ENV
- name: Run ${{ env.SUITE }} suite
+ id: run_suite
run: EXITCODE=0;
python3
-u ${{ env.SUITE }}/regression.py
@@ -613,9 +710,20 @@ jobs:
${{ env.args }} || EXITCODE=$?;
.github/add_link_to_logs.sh;
exit $EXITCODE
+ - name: Set Commit Status
+ if: always()
+ run: python3 .github/set_builds_status.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ JOB_OUTCOME: ${{ steps.run_suite.outcome }}
+ SUITE_NAME: "Regression ${{ inputs.arch }} Tiered Storage ${{ matrix.STORAGE }}"
- name: Create and upload logs
if: always()
run: .github/create_and_upload_logs.sh 1
+ - name: Upload logs to regression results database
+ if: always()
+ timeout-minutes: 20
+ run: .github/upload_results_to_database.sh 1
- uses: actions/upload-artifact@v4
if: always()
with:
diff --git a/.github/workflows/release_branches.yml b/.github/workflows/release_branches.yml
index 1b7f0a244d5a..36bf370954cb 100644
--- a/.github/workflows/release_branches.yml
+++ b/.github/workflows/release_branches.yml
@@ -539,7 +539,7 @@ jobs:
secrets: inherit
with:
runner_type: altinity-on-demand, altinity-type-cpx51, altinity-image-x86-app-docker-ce, altinity-setup-regression
- commit: 18c56fe46fae827748c557e9ebda0599c11b0a55
+ commit: a5b768e5aa5d634f17c5b0c960c1e79580058347
arch: release
build_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
timeout_minutes: 300
@@ -550,7 +550,7 @@ jobs:
secrets: inherit
with:
runner_type: altinity-on-demand, altinity-type-cax41, altinity-image-arm-app-docker-ce, altinity-setup-regression
- commit: 18c56fe46fae827748c557e9ebda0599c11b0a55
+ commit: a5b768e5aa5d634f17c5b0c960c1e79580058347
arch: aarch64
build_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
timeout_minutes: 300
@@ -626,3 +626,25 @@ jobs:
${{ toJson(needs) }}
EOF
python3 ./tests/ci/ci_buddy.py --check-wf-status
+ - name: Create and upload combined report
+ if: ${{ !cancelled() }}
+ env:
+ CHECKS_DATABASE_HOST: ${{ secrets.CHECKS_DATABASE_HOST }}
+ CHECKS_DATABASE_USER: ${{ secrets.CHECKS_DATABASE_USER }}
+ CHECKS_DATABASE_PASSWORD: ${{ secrets.CHECKS_DATABASE_PASSWORD }}
+ COMMIT_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
+ PR_NUMBER: ${{ github.event.number }}
+ ACTIONS_RUN_URL: ${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }}
+ shell: bash
+ run: |
+ pip install clickhouse-driver==0.2.8 numpy==1.26.4 pandas==2.2.0
+
+ REPORT_LINK=$(python3 .github/create_combined_ci_report.py --pr-number $PR_NUMBER --commit-sha $COMMIT_SHA --actions-run-url $ACTIONS_RUN_URL)
+
+ IS_VALID_URL=$(echo $REPORT_LINK | grep -E '^https?://')
+ if [[ -n $IS_VALID_URL ]]; then
+ echo "Combined CI Report: [View Report]($REPORT_LINK)" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "Error: $REPORT_LINK" >> $GITHUB_STEP_SUMMARY
+ exit 1
+ fi
diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py
index 5f49d36c8219..f9bc53324714 100644
--- a/tests/ci/ci_config.py
+++ b/tests/ci/ci_config.py
@@ -403,21 +403,24 @@ class CI:
JobNames.INTEGRATION_TEST_ASAN_OLD_ANALYZER: CommonJobConfigs.INTEGRATION_TEST.with_properties(
required_builds=[BuildNames.PACKAGE_ASAN],
num_batches=6,
+ timeout=12000, # the job timed out with default value (7200)
),
JobNames.INTEGRATION_TEST_TSAN: CommonJobConfigs.INTEGRATION_TEST.with_properties(
required_builds=[BuildNames.PACKAGE_TSAN],
num_batches=6,
- timeout=9000, # the job timed out with default value (7200)
+ timeout=12000, # the job timed out with default value (7200)
),
JobNames.INTEGRATION_TEST_ARM: CommonJobConfigs.INTEGRATION_TEST.with_properties(
required_builds=[BuildNames.PACKAGE_AARCH64],
num_batches=6,
runner_type=Runners.FUNC_TESTER_ARM,
+ timeout=12000, # the job timed out with default value (7200)
),
JobNames.INTEGRATION_TEST: CommonJobConfigs.INTEGRATION_TEST.with_properties(
required_builds=[BuildNames.PACKAGE_RELEASE],
num_batches=4,
#release_only=True,
+ timeout=12000, # the job timed out with default value (7200)
),
JobNames.INTEGRATION_TEST_FLAKY: CommonJobConfigs.INTEGRATION_TEST.with_properties(
required_builds=[BuildNames.PACKAGE_ASAN],