From 33c6e894cb08628c70aaa96a21c2d4bef5631b6b Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Thu, 12 Sep 2024 17:54:30 +0800 Subject: [PATCH] Report test result to comment on PR --- .github/workflows/build.yml | 48 +++++ .github/workflows/check-style.yml | 20 ++ .github/workflows/generate_test_report.py | 76 ++++++++ .github/workflows/get_artifact_url.py | 6 + .github/workflows/github_utils.py | 107 +++++++++++ .github/workflows/integration-test.yml | 34 ++++ .github/workflows/macos.yml | 26 +++ .github/workflows/pull-request.yml | 176 +++++++++++------- .github/workflows/report.py | 24 +++ .github/workflows/report.yml | 32 ++++ .github/workflows/run-integration-test.sh | 36 +--- .github/workflows/run_integration_test.py | 50 +++++ .../translate_sanitize_to_ck_build_option.py | 22 +++ .github/workflows/unit-test.yml | 32 ++++ src/Common/tests/gtest_pod_array.cpp | 2 +- 15 files changed, 591 insertions(+), 100 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/check-style.yml create mode 100644 .github/workflows/generate_test_report.py create mode 100644 .github/workflows/get_artifact_url.py create mode 100644 .github/workflows/github_utils.py create mode 100644 .github/workflows/integration-test.yml create mode 100644 .github/workflows/macos.yml create mode 100644 .github/workflows/report.py create mode 100644 .github/workflows/report.yml create mode 100644 .github/workflows/run_integration_test.py create mode 100644 .github/workflows/translate_sanitize_to_ck_build_option.py create mode 100644 .github/workflows/unit-test.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..9c0e47d9e4f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,48 @@ +name: Build + +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + build_type: + required: true + type: string + sanitize: + required: true + type: string + +jobs: + run: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Prepare environment + run: bash .github/workflows/prepare-env.sh 14 + + - name: Generate Makefile + run: | + export CC=`which clang` CXX=`which clang++` + SANITIZE_OPTION=$(python3 .github/workflows/translate_sanitize_to_ck_build_option.py ${{ inputs.sanitize }}) + if [ "$SANITIZE_OPTION" != "none" ]; then + cmake -G Ninja -B ./build -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DSANITIZE=$SANITIZE_OPTION + else + cmake -G Ninja -B ./build -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} + fi + + - name: Build + working-directory: ${{ github.workspace }}/build + run: ninja -j 10 + + - name: Upload RaftKeeper binary + uses: actions/upload-artifact@v4 + with: + name: raftkeeper-binary-${{ inputs.sanitize}} + path: build/programs/raftkeeper + + - name: Upload unit test binary + uses: actions/upload-artifact@v4 + with: + name: unit-test-binary-${{ inputs.sanitize}} + path: build/src/rk_unit_tests diff --git a/.github/workflows/check-style.yml b/.github/workflows/check-style.yml new file mode 100644 index 00000000000..49c0cd87030 --- /dev/null +++ b/.github/workflows/check-style.yml @@ -0,0 +1,20 @@ +name: Check Style + +on: # yamllint disable-line rule:truthy + workflow_call: + +env: + REPORT_DIR: /tmp/reports + +jobs: + run: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + - name: Install tools + run: sudo apt-get update && sudo apt install -y shellcheck libxml2-utils git python3-pip pylint yamllint && pip3 install codespell + + - name: Check style + working-directory: ${{ github.workspace }}/tests/ci + run: python3 code_style_check.py | tee style-report.log diff --git a/.github/workflows/generate_test_report.py b/.github/workflows/generate_test_report.py new file mode 100644 index 00000000000..ee4186aa571 --- /dev/null +++ b/.github/workflows/generate_test_report.py @@ -0,0 +1,76 @@ +import xml.etree.ElementTree as ET + +import os + + +def generate_report(report_dir, report_title): + print("report_dir:", report_dir) + test_cases = {} + + # Iterate over all files in the directory + for report_file in os.listdir(report_dir): + print("report_file:", report_file) + if report_file.endswith(('-none.xml', '-tsan.xml', '-msan.xml', '-asan.xml', '-ubsan.xml')): + sanitize_type = report_file.split('-')[-1].split('.')[0] + with open(os.path.join(report_dir, report_file), 'r') as file: + xml_data = file.read() + print("xml_data:", xml_data) + root = ET.fromstring(xml_data) + + for testsuite in root.findall('testsuite'): + for testcase in testsuite.findall('testcase'): + # integration test classname is like test_auto.test, we only need the first part + classname = testcase.get('classname').split('.')[0] + name = testcase.get('name') + failure = testcase.find('failure') + status = '❌' if failure is not None else '✅' + error_message = failure.get('message').replace('\n', '
') if failure is not None else '' + + print(f"test case: {classname} {name} {failure} {error_message} {status}") + if (classname, name) not in test_cases: + test_cases[(classname, name)] = [] + + test_cases[(classname, name)].append((sanitize_type, status, error_message)) + + header = "| Classname | Name | Sanitize Type | Status | Error Message |\n" + header += "|---------------|----------------------------------------|---------------|--------|---------------|\n" + + successful_tests = [] + failed_tests = [] + + for (classname, name), results in test_cases.items(): + # If all of test case are passed or not passed, squash them into one row and set sanitize_type to '-' + statuses = {status for _, status, _ in results} + if len(statuses) == 1: + status = statuses.pop() + # If there's an error message, use it; otherwise, set it to an empty string + error_message = next((error for _, _, error in results if error), '') + row = f"| {classname} | {name} | | {status} | {error_message} |" + if error_message != '': + failed_tests.append(row) + else: + successful_tests.append(row) + else: + # Process normally if not squashed + for sanitize_type, status, error_message in results: + row = f"| {classname} | {name} | {sanitize_type} | {status} | {error_message} |" + if error_message != '': + failed_tests.append(row) + else: + successful_tests.append(row) + + failed_table = header + "\n".join(failed_tests) if failed_tests else "All test cases passed!" + successful_table = header + "\n".join(successful_tests) if successful_tests else "" + + collapsible_successful_table = ( + "
\n" + "Successful Test Cases\n\n" + + successful_table + + "\n
" + ) + + commit_id = os.environ['GITHUB_SHA'] + print("commit_id:", commit_id) + + report = f"{report_title} for commit {commit_id}.\n\n{failed_table}\n\n{collapsible_successful_table}" + return report diff --git a/.github/workflows/get_artifact_url.py b/.github/workflows/get_artifact_url.py new file mode 100644 index 00000000000..fd748560aaa --- /dev/null +++ b/.github/workflows/get_artifact_url.py @@ -0,0 +1,6 @@ +from github_utils import get_artifact_url +import sys + + +if __name__ == "__main__": + get_artifact_url(sys.argv[1]) diff --git a/.github/workflows/github_utils.py b/.github/workflows/github_utils.py new file mode 100644 index 00000000000..3da71b18fda --- /dev/null +++ b/.github/workflows/github_utils.py @@ -0,0 +1,107 @@ +import os +import sys +import json + +from github.Auth import AppAuth +from github import Github + + +app_id = "995720" +installation_id = 54780428 + +private_key_part_1 = """-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAyN+CJniZ5AXyAdcHmfwq0ltaaSLBahjsndlJxJY4zxS3ywxC +""" + +private_key_part_2 = """ornAzDEKp4VtBf9EWIraMsCk1Ak9Wp0yICNngJ+Ch13173xk2nkRNBRYAnq+Ln+x +3VetS7hNMkHrSRHZezapzMCki9C0E7a9ks2F3sMwj8rKKhCvspMWGDBTMan5pKkc +YLlOhA27MF66SOIdDAqbi9m0pKeoNvaFaD5hRykYE+bDjnfTXSRWzPragEk5pbKl +sROzSDHqNYDH1d3QK4D1Uk7uT5ZQnkQuWFfliLbSaqPk4NhRwQx4jCqbkQZ4HvPm +8q8I5fql1gDwOxPr1eTLOU00C5x3wcwnFT+MXwIDAQABAoIBAC38FNRvyXMM9WD8 +c+4Jb1gmt6TX4wVB3XEpXBzX8vtdF9Iw5VRRR9S26WR+Q/4aeO/4IYl61oD/+H60 ++9Olpz0nxv7sQK/pf0EQdCLDAX7X0I/ehb5RIwfxkiKOOqnIn0v4sJiCBWlIhuD4 +dZ+U0y+y6XwRhYRpu38a8vToozL7+WX/Wu656FK0H0huH53EazrxxD6JLpoIFErK +ojU5vzPJUgTdWnMZAdlEOjcO0Qg/XwDlzmQFga5duIzkt9CcmlOwRoWsVBph6YP8 +Qzz2sjHslzoLo0Nmc+HVZLE+uyKjeI5UFfPRu5c+rZgbwR27fDJY/O/JsFbV8wZm +5u504TkCgYEA849asWpIiWWg4U00txtM1etNe450x9bo6LgQ2QtcJTk5OPGuxUt3 +sX51upYHat8VbEuEiEe70QEUySwqOiK6ofLn8zr8tPmg0smy0zt1DJy/tPHQ4ubX +A9DKMqYxHJPS4iRi5R7sOIjNFBfA2iUPEesQRPXAIMkIyevv6LBfg9UCgYEA0yIB +bftBTJc1WDbaOU339GYm8rLjiDHyK9DAOC548m85Bm1b45EtMoB5e+0VY/C10eLy +A2A7t7CpA9kLZRVlx4voQ21bTjEnO470GN6SfmhcLZ65WzvbZvLHj+zGjkV8+Lot +Av02h6hnwfwQhvN9XsZPyNk4IdcXK7045DnGzWMCgYAcF5HHWtHo/w7STbxhzkVL +eythr+mqTxBoHyraTeQf6vy9o6qb2PuCPmrHzZwnaHmpFwC/Uz7HeY9zMKPiNrU+ +Dq1QMaKKISy6g0cb9ASpIr892JJWSXfNWdyogOCzQh2VtcquUKXAU48L3T2CK7oU +P/+NZKb3YRihaZQvS4CIzQKBgQC6UsxILu/Vk6u0CdRTtgcYW/4LOOurichZ+oNo +ETsTWCxPC7uH/NqSMucDAptZ81fBvjIt4INS/Ehr6OMxdcy4aTO0LZHiU2Z4HRQ1 +zlYh0B9o8yZI6W4aUC7lSOOBMrmzFzoZ5TR2S5wliTlcnw0I0qIecfQjiRods4O9 +hW94WQKBgHMxir/eMLR8z9hj1jpJh+eN3Uo6UM+5dzgqNUn4CJC2Tt6tIZPoxdhX +MimMH9yrRcCe068VlvqN+AsTTpkpceJT52dV5iXuEm1DoYgvhvD19MMlW/QmOY8w +5GHXzNf9wt2HkMZZcpCasMTfWW3LvtEomcA3USQbZt5NgX7QjAVo +-----END RSA PRIVATE KEY-----""" + +private_key = private_key_part_1 + private_key_part_2 + +def comment_on_pr(content, title): + print("content:", content) + if content is None: + raise Exception("Content is required") + + app_auth = AppAuth(app_id, private_key) + g = Github(auth=app_auth.get_installation_auth(installation_id)) + + # Get the pull request number and repository details + repo_name = os.environ['GITHUB_REPOSITORY'] + print("repo_name:", repo_name) + + # Read the event payload + with open(os.environ['GITHUB_EVENT_PATH'], 'r') as f: + event_payload = json.load(f) + + # Extract the pull request number + pull_request_number = event_payload['pull_request']['number'] + print("pull_request_number:", pull_request_number) + + repo = g.get_repo(repo_name) + pull_request = repo.get_pull(int(pull_request_number)) + + # Get all comments on the pull request + comments = pull_request.get_issue_comments() + + # Check if a comment by raftkeepeer-robot[bot] exists + comment_found = False + for comment in comments: + print("find comment:", comment) + if comment.user.login == "raftkeepeer-robot[bot]" and title in comment.body: + # Update the existing comment + comment.edit(content) + comment_found = True + break + + # If not, create a new comment + if not comment_found: + pull_request.create_issue_comment(content) + + +def get_artifact_url(artifact_name): + # Authenticate with GitHub + app_auth = AppAuth(app_id, private_key) + g = Github(auth=app_auth.get_installation_auth(installation_id)) + + # Get the repository + repo_name = os.environ['GITHUB_REPOSITORY'] + repo = g.get_repo(repo_name) + + # Get the specific workflow run + run_id = os.environ['GITHUB_RUN_ID'] + print("run_id:", run_id) + workflow_run = repo.get_workflow_run(int(run_id)) + + # List artifacts for the workflow run + artifacts = workflow_run.get_artifacts() + + # Assuming you want the first artifact, get its download URL + for artifact in artifacts: + if artifact.name == artifact_name: + return artifact.archive_download_url + + return None diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 00000000000..66e7a5660d1 --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,34 @@ +name: Test + +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + sanitize: + required: true + type: string + +jobs: + run: + runs-on: ubuntu-22.04 + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Download binary + uses: actions/download-artifact@v4 + with: + name: raftkeeper-binary-${{ inputs.sanitize }} + path: build/programs/ + + - name: Add executable privileges + run: sudo chmod 755 build/programs/raftkeeper + + - name: Run integration Tests + run: bash .github/workflows/run-integration-test.sh tests/integration --junitxml=integration-test-report-${{ inputs.sanitize }}.xml + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: integration-test-report-${{ inputs.sanitize }}.xml + path: tests/integration/integration-test-report-${{ inputs.sanitize }}.xml diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 00000000000..d5aa640dfb7 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,26 @@ +name: Build and Test macOS + +on: # yamllint disable-line rule:truthy + workflow_call: + +jobs: + run: + runs-on: macos-13 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install tools + run: brew install ninja ccache cmake llvm@17 + + - name: Generate Makefile + run: export CC=$(brew --prefix llvm@17)/bin/clang CXX=$(brew --prefix llvm@17)/bin/clang++ && cmake -G Ninja -B ./build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + working-directory: ${{github.workspace}}/build + run: ninja -j 10 + + - name: Run unit tests + working-directory: ${{github.workspace}}/build + run: ./src/rk_unit_tests --gtest_color=yes diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f8b1372c4cf..0c132d7e322 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -36,86 +36,124 @@ env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: RelWithDebInfo -# Cancel the previous wf run in PRs. +# Cancel the previous workflow run in this PR. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: check-style: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - - name: Install tools - run: sudo apt-get update && sudo apt install -y shellcheck libxml2-utils git python3-pip pylint yamllint && pip3 install codespell - - - name: Check style - working-directory: ${{github.workspace}}/tests/ci - run: python3 code_style_check.py - - - name: print info - run: echo ${{github.workspace}} && echo ${{runner.temp}} && echo ${{runner.workspace}} + uses: ./.github/workflows/check-style.yml build: - runs-on: ubuntu-22.04 + uses: ./.github/workflows/build.yml + with: + build_type: RelWithDebInfo + sanitize: none needs: check-style - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Install tools - run: sudo apt install -y ninja-build ccache - - - name: Generate Makefile - run: export CC=`which clang` CXX=`which clang++` && cmake -G Ninja -B ./build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - - name: Build - working-directory: ${{github.workspace}}/build - run: ninja -j 10 - - - name: Upload raftkeeper binary - uses: actions/upload-artifact@v3 - with: - name: raftkeeper-binary - path: build/programs/raftkeeper - - - name: Upload unit test binary - uses: actions/upload-artifact@v3 - with: - name: unit-test-binary - path: build/src/rk_unit_tests - - run-unit-tests: - runs-on: ubuntu-22.04 + + unit-test: + uses: ./.github/workflows/unit-test.yml + with: + sanitize: none needs: build - steps: - - name: Download unit test binary - uses: actions/download-artifact@v3 - with: - name: unit-test-binary - path: build/src/ - - - name: Run unit Tests - working-directory: ${{github.workspace}}/build - run: sudo chmod 755 ./src/rk_unit_tests && ./src/rk_unit_tests --gtest_color=yes - - run-integration-tests: - runs-on: ubuntu-22.04 + + integration-test: + uses: ./.github/workflows/integration-test.yml + with: + sanitize: none needs: build - steps: - - uses: actions/checkout@v3 - - name: Download raftkeeper binary - uses: actions/download-artifact@v3 - with: - name: raftkeeper-binary - path: build/programs/ + build-tsan: + uses: ./.github/workflows/build.yml + with: + build_type: RelWithDebInfo + sanitize: tsan + needs: check-style + + tsan-unit-test: + uses: ./.github/workflows/unit-test.yml + with: + sanitize: tsan + needs: build-tsan + + tsan-integration-test: + uses: ./.github/workflows/integration-test.yml + with: + sanitize: tsan + needs: build-tsan + + build-asan: + uses: ./.github/workflows/build.yml + with: + build_type: RelWithDebInfo + sanitize: asan + needs: check-style + + asan-unit-test: + uses: ./.github/workflows/unit-test.yml + with: + sanitize: asan + needs: build-asan + + asan-integration-test: + uses: ./.github/workflows/integration-test.yml + with: + sanitize: asan + needs: build-asan + + build-msan: + uses: ./.github/workflows/build.yml + with: + build_type: RelWithDebInfo + sanitize: msan + needs: check-style - - name: Add executable privileges - run: sudo chmod 755 ${{github.workspace}}/build/programs/raftkeeper + msan-unit-test: + uses: ./.github/workflows/unit-test.yml + with: + sanitize: msan + needs: build-msan + + msan-integration-test: + uses: ./.github/workflows/integration-test.yml + with: + sanitize: msan + needs: build-msan + + build-ubsan: + uses: ./.github/workflows/build.yml + with: + build_type: RelWithDebInfo + sanitize: ubsan + needs: check-style - - name: Run integration Tests - working-directory: ${{github.workspace}}/tests/integration - run: bash ${{github.workspace}}/.github/workflows/run-integration-test.sh ${{github.workspace}}/tests/integration + ubsan-unit-test: + uses: ./.github/workflows/unit-test.yml + with: + sanitize: ubsan + needs: build-ubsan + + ubsan-integration-test: + uses: ./.github/workflows/integration-test.yml + with: + sanitize: ubsan + needs: build-ubsan + + unit-test-report: + if: always() + uses: ./.github/workflows/report.yml + with: + test_type: unit + needs: [unit-test, tsan-unit-test, asan-unit-test, msan-unit-test, ubsan-unit-test] + + integration-test-report: + if: always() + uses: ./.github/workflows/report.yml + with: + test_type: integration + needs: [integration-test, tsan-integration-test, asan-integration-test, msan-integration-test, ubsan-integration-test] + + macos-build-and-unit-test: + uses: ./.github/workflows/macos.yml + needs: check-style diff --git a/.github/workflows/report.py b/.github/workflows/report.py new file mode 100644 index 00000000000..2f27b944e36 --- /dev/null +++ b/.github/workflows/report.py @@ -0,0 +1,24 @@ +import os +import sys + +from generate_test_report import generate_report +from github_utils import comment_on_pr + +# Report to PR comment +def report(report_dir, report_type): + content = None + title = None + + if report_type == 'unit': + title = f"### Unit test report" + elif report_type == 'integration': + title = f"Integration test report" + else: + raise ValueError('Integration test report not available') + + content = generate_report(report_dir, title) + if content is not None: + comment_on_pr(content, title) + +if __name__ == "__main__": + report(sys.argv[1], sys.argv[2]) diff --git a/.github/workflows/report.yml b/.github/workflows/report.yml new file mode 100644 index 00000000000..233f30945d6 --- /dev/null +++ b/.github/workflows/report.yml @@ -0,0 +1,32 @@ +name: Unit Test + +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + test_type: # only integration or unit + required: true + type: string + +jobs: + run: + runs-on: ubuntu-22.04 + steps: + - name: Checkout the repository + if: always() + uses: actions/checkout@v3 + + - name: pip install PyGithub + if: always() + run: pip install PyGithub + + - name: Download test report files + if: always() + uses: actions/download-artifact@v4 + with: + pattern: ${{ inputs.test_type }}-test-report-* + merge-multiple: true + path: build/test/${{ inputs.test_type }}/ + + - name: Comment test report on PR + if: always() + run: if [ -n "$(ls -A build/test/${{ inputs.test_type }}/ 2>/dev/null)" ]; then python3 .github/workflows/report.py build/test/${{ inputs.test_type }}/ ${{ inputs.test_type }}; fi diff --git a/.github/workflows/run-integration-test.sh b/.github/workflows/run-integration-test.sh index 6d9939d663d..329930ff2cd 100644 --- a/.github/workflows/run-integration-test.sh +++ b/.github/workflows/run-integration-test.sh @@ -10,9 +10,10 @@ test_result="succeed" # shellcheck disable=SC2120 function run_tests() { - ./runner --binary "${tests_root_dir}"/../../build/programs/raftkeeper \ - --base-configs-dir "${tests_root_dir}"/../../programs/server \ - $@ \ + echo "tests_root_dir: ${tests_root_dir}" + ./runner --binary ../../build/programs/raftkeeper \ + --base-configs-dir ../../programs/server \ + " $@" \ | tee /tmp/tests_output.log if [ ${PIPESTATUS[0]} -ne 0 ]; then @@ -20,6 +21,7 @@ function run_tests() failed_test_cases=($(grep -E '^test.*(FAILED|ERROR)' /tmp/tests_output.log | grep '/' | awk -F'/' '{print $1}' | sort | uniq)) for failed_test_case in ${failed_test_cases[*]} do + echo $failed_test_case >> /tmp/$failed_test_cases.log raftkeeper_instances=$(ls "$failed_test_case"/_instances | grep node) for raftkeeper_instance in ${raftkeeper_instances[*]} do @@ -32,34 +34,8 @@ function run_tests() fi } -function run_tests_individually() -{ - # shellcheck disable=SC2207 - test_cases=($(ls "$tests_root_dir" | grep test_)) -# test_cases=(test_multinode_simple) - echo "Total ${#test_cases[*]} test cases to run." - # shellcheck disable=SC1073 - for test_case in ${test_cases[*]} - do - echo -e "\n----------------- Run test $test_case -----------------" - ./runner --binary "${tests_root_dir}"/../../build/programs/raftkeeper \ - --base-configs-dir "${tests_root_dir}"/../../programs/server \ - "$test_case" - # shellcheck disable=SC2181 - if [ $? -ne 0 ]; then - test_result="failed" - raftkeeper_instances=$(ls "$test_case"/_instances | grep node) - for raftkeeper_instance in ${raftkeeper_instances[*]} - do - echo -e "\n----------------- Captured $test_case $raftkeeper_instance raftkeeper-server.log -----------------" - sudo cat "$test_case"/_instances/"$raftkeeper_instance"/logs/raftkeeper-server.log - done - fi - done -} - -run_tests $@ +run_tests "$@" if [ $test_result == "failed" ]; then exit 1; diff --git a/.github/workflows/run_integration_test.py b/.github/workflows/run_integration_test.py new file mode 100644 index 00000000000..55dc203d7b1 --- /dev/null +++ b/.github/workflows/run_integration_test.py @@ -0,0 +1,50 @@ +import os +import subprocess +import sys + +def run_tests(tests_root_dir, *args): + test_result = "succeed" + os.chdir(tests_root_dir) + + result = subprocess.run( + ["./runner", "--binary", f"{tests_root_dir}/../../build/programs/raftkeeper", + "--base-configs-dir", f"{tests_root_dir}/../../programs/server", ' '.join(args)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + + with open("/tmp/tests_output.log", "w") as log_file: + log_file.write(result.stdout) + + if result.returncode != 0: + test_result = "failed" + failed_test_cases = subprocess.run( + ["grep", "-E", "^test.*(FAILED|ERROR)", "/tmp/tests_output.log"], + stdout=subprocess.PIPE, text=True + ).stdout.splitlines() + + failed_test_cases = sorted(set(line.split('/')[0] for line in failed_test_cases if '/' in line)) + + with open("/tmp/tests_report.log", "w") as report_file: + for failed_test_case in failed_test_cases: + report_file.write(f"{failed_test_case}\n") + raftkeeper_instances = os.listdir(f"{failed_test_case}/_instances") + raftkeeper_instances = [instance for instance in raftkeeper_instances if "node" in instance] + + for raftkeeper_instance in raftkeeper_instances: + print(f"\n----------------- Captured {failed_test_case} {raftkeeper_instance} raftkeeper-server.log -----------------") + with open(f"{failed_test_case}/_instances/{raftkeeper_instance}/logs/raftkeeper-server.log") as log_file: + print(log_file.read()) + print(f"\n----------------- Captured {failed_test_case} {raftkeeper_instance} stderr.log -----------------") + with open(f"{failed_test_case}/_instances/{raftkeeper_instance}/logs/stderr.log") as log_file: + print(log_file.read()) + + return test_result + + +if __name__ == "__main__": + tests_root_dir = sys.argv[1] + args = sys.argv[2:] + test_result = run_tests(tests_root_dir, *args) + + if test_result == "failed": + sys.exit(1) \ No newline at end of file diff --git a/.github/workflows/translate_sanitize_to_ck_build_option.py b/.github/workflows/translate_sanitize_to_ck_build_option.py new file mode 100644 index 00000000000..4fc9f2ebcd9 --- /dev/null +++ b/.github/workflows/translate_sanitize_to_ck_build_option.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +import sys + +# translate sanitize to ck build option, for example: tsan to thread +def translate_sanitize_to_ck_option(sanitize): + sanitize_map = { + 'none': 'none', + 'tsan': 'thread', + 'asan': 'address', + 'msan': 'memory', + 'ubsan': 'undefined' + } + return sanitize_map.get(sanitize, '') + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: translate_sanitize_to_ck_build_option.py ") + sys.exit(1) + sanitize = sys.argv[1] + ck_option = translate_sanitize_to_ck_option(sanitize) + print(ck_option) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml new file mode 100644 index 00000000000..60ff5a02ecb --- /dev/null +++ b/.github/workflows/unit-test.yml @@ -0,0 +1,32 @@ +name: Unit Test + +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + sanitize: + required: true + type: string + +jobs: + run: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Download binary + uses: actions/download-artifact@v4 + with: + name: unit-test-binary-${{ inputs.sanitize }} + path: build/src/ + + - name: Run unit tests + working-directory: ${{ github.workspace }}/build + run: sudo chmod 755 ./src/rk_unit_tests && ./src/rk_unit_tests --gtest_color=yes --gtest_output=xml:unit-test-report-${{ inputs.sanitize }}.xml + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: unit-test-report-${{ inputs.sanitize }}.xml + path: build/unit-test-report-${{ inputs.sanitize }}.xml diff --git a/src/Common/tests/gtest_pod_array.cpp b/src/Common/tests/gtest_pod_array.cpp index dfd4d2edcb9..42bc3859902 100644 --- a/src/Common/tests/gtest_pod_array.cpp +++ b/src/Common/tests/gtest_pod_array.cpp @@ -73,7 +73,7 @@ struct ItemWithSize char v[size] {}; }; -TEST(Common, PODInsertElementSizeNotMultipleOfLeftPadding) +TEST(Common, PODArrayInsertWithIllegalPadding) { using ItemWith24Size = ItemWithSize<24>; PaddedPODArray arr1_initially_empty;