diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000000..9c0e47d9e4
--- /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 0000000000..49c0cd8703
--- /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 0000000000..ee4186aa57
--- /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 0000000000..fd748560aa
--- /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 0000000000..3da71b18fd
--- /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 0000000000..66e7a5660d
--- /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 0000000000..d5aa640dfb
--- /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 f8b1372c4c..0c132d7e32 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 0000000000..d65eeefdaa
--- /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 0000000000..233f30945d
--- /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 6d9939d663..329930ff2c 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 0000000000..55dc203d7b
--- /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 0000000000..4fc9f2ebcd
--- /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 0000000000..60ff5a02ec
--- /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 dfd4d2edcb..42bc385990 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;