]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
[GHA] Refactor workflows 2657/head
authors3rj1k <evasive.gyron@gmail.com>
Mon, 2 Dec 2024 01:16:21 +0000 (02:16 +0100)
committers3rj1k <evasive.gyron@gmail.com>
Tue, 3 Dec 2024 20:30:46 +0000 (21:30 +0100)
13 files changed:
.github/docker/debian/bookworm/amd64/CI/Dockerfile [new file with mode: 0644]
.github/workflows/ci.yml
.github/workflows/scan-build.yml [new file with mode: 0644]
.github/workflows/unit-test-dind.yml [new file with mode: 0644]
.github/workflows/unit-test.yml
.github/workflows/windows.yml [new file with mode: 0644]
build/modmake.rulesam
ci.sh [new file with mode: 0755]
scan_build.sh [deleted file]
tests/unit/collect-test-logs.sh
tests/unit/run-tests-docker.sh [new file with mode: 0755]
tests/unit/run-tests.sh
tests/unit/test.sh

diff --git a/.github/docker/debian/bookworm/amd64/CI/Dockerfile b/.github/docker/debian/bookworm/amd64/CI/Dockerfile
new file mode 100644 (file)
index 0000000..0fd3cec
--- /dev/null
@@ -0,0 +1,24 @@
+ARG BUILDER_IMAGE=signalwire/freeswitch-public-ci-base:bookworm-amd64
+
+FROM ${BUILDER_IMAGE}
+
+ARG MAINTAINER_EMAIL="andrey@signalwire.com"
+
+LABEL org.opencontainers.image.authors="${MAINTAINER_EMAIL}"
+
+SHELL ["/bin/bash", "-c"]
+
+COPY --from=sofia-sip / /usr/src/sofia-sip
+COPY --from=freeswitch / /usr/src/freeswitch
+
+RUN cd /usr/src/freeswitch && \
+    ./ci.sh -t unit-test -a configure -c sofia-sip -p "/usr/src/sofia-sip" && \
+    ./ci.sh -t unit-test -a build -c sofia-sip -p "/usr/src/sofia-sip" && \
+    ./ci.sh -t unit-test -a install -c sofia-sip -p "/usr/src/sofia-sip" && \
+    ./ci.sh -t unit-test -a configure -c freeswitch -p "/usr/src/freeswitch" && \
+    ./ci.sh -t unit-test -a build -c freeswitch -p "/usr/src/freeswitch" && \
+    ./ci.sh -t unit-test -a install -c freeswitch -p "/usr/src/freeswitch"
+
+WORKDIR /usr/src/freeswitch/tests/unit
+
+ENTRYPOINT ["/usr/src/freeswitch/tests/unit/run-tests.sh"]
index 51586fabfe7a9be9bb94e8df8444c21c8a32d18f..0a774108e0a05893135a6da63fc4c4f74ffd0762 100644 (file)
@@ -4,18 +4,45 @@ on:
   push:
     branches:
       - master
+      - v1.10
   pull_request:
     types:
       - opened
       - synchronize
+  workflow_dispatch:
+    inputs:
+      freeswitch_ref:
+        description: 'FreeSWITCH repository ref'
+        required: false
+        type: string
+      sofia-sip_ref:
+        description: 'Sofia-Sip repository ref'
+        required: false
+        type: string
+      dind:
+        description: 'Run tests using Docker-in-Docker'
+        required: false
+        type: boolean
+        default: false
+
+env:
+  CI_BASE_STATIC_IMAGE: signalwire/freeswitch-public-ci-base:bookworm-amd64
+  DOCKER_BUILD_SUMMARY: false
+  DOCKER_BUILD_CHECKS_ANNOTATIONS: false
+  DOCKER_BUILD_RECORD_UPLOAD: false
 
 jobs:
   unit-tests-pre-config:
+    if: ${{ !inputs.dind }}
+    name: "Unit-tests pre-config"
     runs-on: ubuntu-latest
     env:
       TOTAL_GROUPS: 2
     outputs:
       matrix: ${{ steps.set-matrix.outputs.matrix }}
+      container-image: ${{ steps.set-vars.outputs.container-image }}
+      working-directory: ${{ steps.set-vars.outputs.working-directory }}
+      tests-only: ${{ steps.set-vars.outputs.tests-only }}
     steps:
       - id: set-matrix
         shell: bash
@@ -25,81 +52,52 @@ jobs:
               include: [range(1; $groups + 1) | {group: ., total: $groups}]
             }')
           echo "matrix=$MATRIX" | tee -a $GITHUB_OUTPUT
+      - id: set-vars
+        shell: bash
+        run: |
+          echo "tests-only=false" >> $GITHUB_OUTPUT
+          echo "working-directory=freeswitch/tests/unit" >> $GITHUB_OUTPUT
+          echo "container-image=$CI_BASE_STATIC_IMAGE" >> $GITHUB_OUTPUT
 
   unit-tests:
-    needs: unit-tests-pre-config
+    if: ${{ !inputs.dind }}
+    name: "Unit-tests (group ${{ matrix.group }})"
+    needs:
+      - unit-tests-pre-config
     strategy:
+      fail-fast: false
       matrix: ${{ fromJson(needs.unit-tests-pre-config.outputs.matrix) }}
-    name: "unit-tests (group ${{ matrix.group }})"
     uses: ./.github/workflows/unit-test.yml
     with:
       total-groups: ${{ matrix.total }}
       current-group: ${{ matrix.group }}
+      container-image: ${{ needs.unit-tests-pre-config.outputs.container-image }}
+      working-directory: ${{ needs.unit-tests-pre-config.outputs.working-directory }}
+      tests-only: ${{ fromJson(needs.unit-tests-pre-config.outputs.tests-only) }}
     secrets: inherit
 
-  scan-build:
+  validate-unit-tests:
+    if: ${{ always() && !inputs.dind }}
+    name: "Validate Unit-tests"
+    needs: unit-tests
     runs-on: ubuntu-latest
-    container:
-      image: signalwire/freeswitch-public-base:bookworm
-      credentials:
-        username: ${{ secrets.DOCKERHUB_USERNAME }}
-        password: ${{ secrets.DOCKERHUB_TOKEN }}
-      options: --privileged
-    env:
-      REPOTOKEN: ${{ secrets.REPOTOKEN }}
-      DEBIAN_FRONTEND: noninteractive
-
     steps:
-      - name: Install dependencies
-        shell: bash
+      - name: Check unit tests status
         run: |
-          echo "machine freeswitch.signalwire.com password $REPOTOKEN" > /etc/apt/auth.conf && \
-          apt-get update && \
-          apt-get -y remove \
-            libsofia-sip-ua0 \
-            libspandsp-dev && \
-          apt-get -y install \
-            autoconf \
-            libsofia-sip-ua-dev \
-            libspandsp3-dev && \
-          rm -rf /etc/apt/auth.conf
-
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          path: /__w/freeswitch/freeswitch
+          if [ "${{ needs.unit-tests.result }}" != "success" ]; then
+            exit 1
+          fi
 
-      - name: Bootstrap
-        shell: bash
-        working-directory: /__w/freeswitch/freeswitch
-        run: |
-          ./bootstrap.sh -j || exit 1
+  unit-tests-dind:
+    if: ${{ inputs.dind }}
+    name: "Unit-tests D-in-D"
+    uses: ./.github/workflows/unit-test-dind.yml
+    with:
+      freeswitch_ref: ${{ inputs.freeswitch_ref }}
+      sofia-sip_ref: ${{ inputs.sofia-sip_ref }}
+    secrets: inherit
 
-      - name: Scan-build FreeSwitch
-        shell: bash
-        working-directory: /__w/freeswitch/freeswitch
-        run: |
-          cp build/modules.conf.most modules.conf && \
-          echo 'codecs/mod_openh264' >> modules.conf && \
-          sed -i \
-            -e '/mod_mariadb/s/^#//g' \
-            -e '/mod_v8/s/^#//g' \
-            -e '/mod_ilbc/s/^/#/g' \
-            -e '/mod_isac/s/^/#/g' \
-            -e '/mod_mp4/s/^/#/g' \
-            -e '/mod_mongo/s/^/#/g' \
-            -e '/mod_pocketsphinx/s/^/#/g' \
-            -e '/mod_sangoma_codec/s/^/#/g' \
-            -e '/mod_siren/s/^/#/g' \
-            -e '/mod_avmd/s/^/#/g' \
-            -e '/mod_basic/s/^/#/g' \
-            -e '/mod_cdr_mongodb/s/^/#/g' \
-            -e '/mod_cv/s/^/#/g' \
-            -e '/mod_erlang_event/s/^/#/g' \
-            -e '/mod_perl/s/^/#/g' \
-            -e '/mod_rtmp/s/^/#/g' \
-            -e '/mod_unimrcp/s/^/#/g' \
-            -e '/mod_xml_rpc/s/^/#/g' \
-          modules.conf && \
-          ./configure && \
-          ./scan_build.sh
+  scan-build:
+    name: "Scan Build"
+    uses: ./.github/workflows/scan-build.yml
+    secrets: inherit
diff --git a/.github/workflows/scan-build.yml b/.github/workflows/scan-build.yml
new file mode 100644 (file)
index 0000000..5d90f2b
--- /dev/null
@@ -0,0 +1,100 @@
+name: Scan build (Static Analysis)
+
+on:
+  workflow_call:
+    inputs:
+      freeswitch_ref:
+        description: 'FreeSWITCH repository ref'
+        required: false
+        type: string
+      sofia-sip_ref:
+        description: 'Sofia-Sip repository ref'
+        required: false
+        type: string
+
+jobs:
+  scan-build:
+    runs-on: ubuntu-latest
+    container:
+      image: signalwire/freeswitch-public-ci-base:bookworm-amd64
+      options: --privileged
+    env:
+      DEBIAN_FRONTEND: noninteractive
+
+    steps:
+      - name: Checkout Sofia-Sip
+        if: inputs.sofia-sip_ref == ''
+        uses: actions/checkout@v4
+        with:
+          repository: freeswitch/sofia-sip
+          path: sofia-sip
+
+      - name: Checkout Sofia-Sip (via ref)
+        if: inputs.sofia-sip_ref != ''
+        uses: actions/checkout@v4
+        with:
+          repository: freeswitch/sofia-sip
+          ref: ${{ inputs.sofia-sip_ref }}
+          path: sofia-sip
+
+      - name: Checkout FreeSWITCH (via ref)
+        if: inputs.freeswitch_ref != ''
+        uses: actions/checkout@v4
+        with:
+          ref: ${{ inputs.freeswitch_ref }}
+          path: freeswitch
+
+      - name: Checkout FreeSWITCH
+        if: inputs.freeswitch_ref == ''
+        uses: actions/checkout@v4
+        with:
+          path: freeswitch
+
+      - name: Configure, Build and Install Sofia-Sip
+        shell: bash
+        working-directory: freeswitch
+        run: |
+          ./ci.sh -t scan-build -a configure -c sofia-sip -p "$GITHUB_WORKSPACE/sofia-sip"
+          ./ci.sh -t scan-build -a build -c sofia-sip -p "$GITHUB_WORKSPACE/sofia-sip"
+          ./ci.sh -t scan-build -a install -c sofia-sip -p "$GITHUB_WORKSPACE/sofia-sip"
+
+      - name: Configure FreeSWITCH
+        shell: bash
+        working-directory: freeswitch
+        run: |
+          ./ci.sh -t scan-build -a configure -c freeswitch -p "$GITHUB_WORKSPACE/freeswitch"
+
+      - name: Run scan-build analysis
+        shell: bash
+        working-directory: freeswitch
+        run: |
+          ./ci.sh -t scan-build -a build -c freeswitch -p "$GITHUB_WORKSPACE/freeswitch"
+
+      - name: Check analysis results
+        if: always()
+        shell: bash
+        working-directory: freeswitch
+        run: |
+          ./ci.sh -t scan-build -a validate -c freeswitch -p "$GITHUB_WORKSPACE/freeswitch"
+
+      - name: Upload Scan-Build logs
+        if: failure()
+        uses: actions/upload-artifact@v4
+        with:
+          name: scan-build-logs
+          path: freeswitch/scan-build
+          retention-days: 3
+          if-no-files-found: ignore
+          compression-level: 9
+
+      - name: Notify run tests result to slack
+        if: |
+          failure() &&
+          github.event_name == 'push' &&
+          (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/v1.10')
+        uses: signalwire/actions-template/.github/actions/slack@main
+        with:
+          CHANNEL: ${{ secrets.SLACK_DEVOPS_CI_CHANNEL }}
+          MESSAGE: Scan-Build ${{ github.repository }} > <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.run_id }}>. Static analysis failed.
+        env:
+          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
diff --git a/.github/workflows/unit-test-dind.yml b/.github/workflows/unit-test-dind.yml
new file mode 100644 (file)
index 0000000..40a51a1
--- /dev/null
@@ -0,0 +1,101 @@
+name: Unit-tests D-in-D
+
+on:
+  workflow_call:
+    inputs:
+      freeswitch_ref:
+        description: 'FreeSWITCH repository ref'
+        required: false
+        type: string
+      sofia-sip_ref:
+        description: 'Sofia-Sip repository ref'
+        required: false
+        type: string
+
+env:
+  MAX_CONTAINERS: 8
+  NUM_CPU_PER_CONTAINER: 1
+  DOCKER_BUILD_SUMMARY: false
+  DOCKER_BUILD_CHECKS_ANNOTATIONS: false
+  DOCKER_BUILD_RECORD_UPLOAD: false
+
+jobs:
+  unit-tests:
+    runs-on: ubuntu-latest
+    container:
+      image: signalwire/freeswitch-public-ci-base:bookworm-amd64
+      options: --privileged
+    env:
+      DEBIAN_FRONTEND: noninteractive
+      ASAN_OPTIONS: log_path=stdout:disable_coredump=0:unmap_shadow_on_exit=1:fast_unwind_on_malloc=0
+
+    steps:
+      - name: Checkout Sofia-Sip (via ref)
+        if: inputs.sofia-sip_ref != ''
+        uses: actions/checkout@v4
+        with:
+          repository: freeswitch/sofia-sip
+          ref: ${{ inputs.sofia-sip_ref }}
+          path: sofia-sip
+
+      - name: Checkout Sofia-Sip
+        if: inputs.sofia-sip_ref == ''
+        uses: actions/checkout@v4
+        with:
+          repository: freeswitch/sofia-sip
+          path: sofia-sip
+
+      - name: Checkout FreeSWITCH (via ref)
+        if: inputs.freeswitch_ref != ''
+        uses: actions/checkout@v4
+        with:
+          ref: ${{ inputs.freeswitch_ref }}
+          path: freeswitch
+
+      - name: Checkout FreeSWITCH
+        if: inputs.freeswitch_ref == ''
+        uses: actions/checkout@v4
+        with:
+          path: freeswitch
+
+      - name: Run Unit-Test containers and collect artifacts
+        id: unit_tests
+        shell: bash
+        run: |
+          echo "logs_path=${GITHUB_WORKSPACE}/freeswitch/tests/unit/logs" >> $GITHUB_OUTPUT
+
+          "${GITHUB_WORKSPACE}/freeswitch/tests/unit/run-tests-docker.sh" \
+            --base-image signalwire/freeswitch-public-ci-base:bookworm-amd64 \
+            --cpus ${{ env.NUM_CPU_PER_CONTAINER }} \
+            --image-tag ci.local \
+            --max-containers ${{ env.MAX_CONTAINERS }} \
+            --output-dir "${GITHUB_WORKSPACE}/freeswitch/tests/unit/logs" \
+            --sofia-sip-path "${GITHUB_WORKSPACE}/sofia-sip" \
+            --freeswitch-path "${GITHUB_WORKSPACE}/freeswitch"
+
+          test -d "/cores" && ls -lah /cores
+
+          cd "${GITHUB_WORKSPACE}/freeswitch/tests/unit/" && \
+          ./collect-test-logs.sh --dir logs --print
+
+      - name: Upload Unit-Test logs
+        if: failure()
+        uses: actions/upload-artifact@v4
+        with:
+          name: test-results-${{ github.sha }}-${{ github.run_number }}
+          path: ${{ steps.unit_tests.outputs.logs_path }}
+          retention-days: 3
+          if-no-files-found: ignore
+          compression-level: 9
+
+      - name: Notify run tests result to slack
+        if: |
+          failure() &&
+          github.event_name == 'push' &&
+          (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/v1.10')
+        uses: signalwire/actions-template/.github/actions/slack@main
+        with:
+          CHANNEL: ${{ secrets.SLACK_DEVOPS_CI_CHANNEL }}
+          MESSAGE: Unit-Tests ${{ github.repository }} > <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.run_id }}>. Some tests are failing.
+        env:
+          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
index 5db97620a4f36bec963a8939db9e4e21a4b6ce12..e89a1f6799b39f356e5cde6cb56622c60da664b6 100644 (file)
@@ -7,19 +7,41 @@ on:
         description: 'Total number of test groups'
         required: true
         type: number
+        default: 1
       current-group:
         description: 'Current test group number'
         required: true
         type: number
+        default: 1
+      freeswitch_ref:
+        description: 'FreeSWITCH repository ref'
+        required: false
+        type: string
+      sofia-sip_ref:
+        description: 'Sofia-Sip repository ref'
+        required: false
+        type: string
+      container-image:
+        description: 'Container image to use for running tests'
+        required: false
+        type: string
+        default: 'signalwire/freeswitch-public-ci-base:bookworm-amd64'
+      working-directory:
+        description: 'Working directory for running tests'
+        required: false
+        type: string
+        default: 'freeswitch/tests/unit'
+      tests-only:
+        description: 'Run only tests, skip other tasks'
+        required: false
+        type: boolean
+        default: false
 
 jobs:
   unit-test:
     runs-on: ubuntu-latest
     container:
-      image: signalwire/freeswitch-public-base:bookworm
-      credentials:
-        username: ${{ secrets.DOCKERHUB_USERNAME }}
-        password: ${{ secrets.DOCKERHUB_TOKEN }}
+      image: ${{ inputs.container-image }}
       options: --privileged
     env:
       DEBIAN_FRONTEND: noninteractive
@@ -29,83 +51,87 @@ jobs:
       - name: Override core_pattern
         shell: bash
         run: |
-          cat /proc/sys/kernel/core_pattern
           echo '/cores/core.%s.%E.%e.%p.%t' > /proc/sys/kernel/core_pattern
           cat /proc/sys/kernel/core_pattern
 
-      - name: Install dependencies
-        shell: bash
-        env:
-          REPOTOKEN: ${{ secrets.REPOTOKEN }}
-        run: |
-          echo "machine freeswitch.signalwire.com password $REPOTOKEN" > /etc/apt/auth.conf && \
-          apt-get update && \
-          apt-get -y remove \
-            libsofia-sip-ua0 \
-            libspandsp-dev && \
-          apt-get -y install \
-            libspandsp3-dev && \
-          rm -rf /etc/apt/auth.conf
-
-      - name: Checkout code
+      - name: Checkout Sofia-Sip (via ref)
+        if: ${{ !inputs.tests-only && inputs.sofia-sip_ref != '' }}
         uses: actions/checkout@v4
         with:
-          path: /__w/freeswitch/freeswitch
-
-      - name: Bootstrap
-        shell: bash
-        working-directory: /__w/freeswitch/freeswitch
-        run: |
-          ./bootstrap.sh -j || exit 1
+          repository: freeswitch/sofia-sip
+          ref: ${{ inputs.sofia-sip_ref }}
+          path: sofia-sip
 
       - name: Checkout Sofia-Sip
+        if: ${{ !inputs.tests-only && inputs.sofia-sip_ref == '' }}
         uses: actions/checkout@v4
         with:
           repository: freeswitch/sofia-sip
-          path: /__w/freeswitch/freeswitch/sofia-sip
+          path: sofia-sip
+
+      - name: Checkout FreeSWITCH (via ref)
+        if: ${{ !inputs.tests-only && inputs.freeswitch_ref != '' }}
+        uses: actions/checkout@v4
+        with:
+          ref: ${{ inputs.freeswitch_ref }}
+          path: freeswitch
+
+      - name: Checkout FreeSWITCH
+        if: ${{ !inputs.tests-only && inputs.freeswitch_ref == '' }}
+        uses: actions/checkout@v4
+        with:
+          path: freeswitch
 
-      - name: Build sofia-sip
+      - name: Configure, Build and Install Sofia-Sip
+        if: ${{ !inputs.tests-only }}
         shell: bash
-        working-directory: /__w/freeswitch/freeswitch/sofia-sip
+        working-directory: freeswitch
         run: |
-          ./autogen.sh && \
-          ./configure.gnu && \
-          make -j$(nproc --all) install
+          ./ci.sh -t unit-test -a configure -c sofia-sip -p "$GITHUB_WORKSPACE/sofia-sip"
+          ./ci.sh -t unit-test -a build -c sofia-sip -p "$GITHUB_WORKSPACE/sofia-sip"
+          ./ci.sh -t unit-test -a install -c sofia-sip -p "$GITHUB_WORKSPACE/sofia-sip"
 
-      - name: Build FreeSwitch
+      - name: Configure, Build and Install FreeSWITCH
+        if: ${{ !inputs.tests-only }}
         shell: bash
-        working-directory: /__w/freeswitch/freeswitch
+        working-directory: freeswitch
         run: |
-          echo 'codecs/mod_openh264' >> modules.conf && \
-          sed -i \
-            -e '/applications\/mod_http_cache/s/^#//g' \
-            -e '/event_handlers\/mod_rayo/s/^#//g' \
-            -e '/formats\/mod_opusfile/s/^#//g' \
-            -e '/languages\/mod_lua/s/^#//g' \
-          modules.conf && \
-          ./configure \
-            --enable-address-sanitizer \
-            --enable-fake-dlclose && \
-          make -j$(nproc --all) |& tee ./unit-tests-build-result.txt
+          ./ci.sh -t unit-test -a configure -c freeswitch -p "$GITHUB_WORKSPACE/freeswitch"
+          ./ci.sh -t unit-test -a build -c freeswitch -p "$GITHUB_WORKSPACE/freeswitch"
+          ./ci.sh -t unit-test -a install -c freeswitch -p "$GITHUB_WORKSPACE/freeswitch"
 
-          echo ${PIPESTATUS[0]} > ./build-status.txt
-          if ! test "$(cat ./build-status.txt | tr -d '[:space:]')" -eq 0; then
-            exit "$(cat ./build-status.txt | tr -d '[:space:]')"
-          fi
-          make install
+      - name: Run unit tests
+        shell: bash
+        working-directory: ${{ inputs.working-directory }}
+        run: |
+          ./run-tests.sh ${{ inputs.total-groups }} ${{ inputs.current-group }} --output-dir logs || exit 1
 
-      - name: Run tests
+      - name: Collect unit test logs
+        if: always()
         shell: bash
-        working-directory: /__w/freeswitch/freeswitch/tests/unit
+        working-directory: ${{ inputs.working-directory }}
         run: |
-          ./run-tests.sh ${{ inputs.total-groups }} ${{ inputs.current-group }}
-          mkdir logs && (mv log_run-tests_*.html logs || true) && (mv backtrace_*.txt logs || true)
-          ./collect-test-logs.sh
+          test -d "/cores" && ls -lah /cores
+          ./collect-test-logs.sh --dir logs --print
 
-      - name: Notify result
+      - name: Upload Unit-Test logs
         if: failure()
-        uses: signalwire/actions-template/.github/actions/notify-ci-result@main
+        uses: actions/upload-artifact@v4
         with:
-          for: "run_tests"
-          test_logs_path: /__w/freeswitch/freeswitch/tests/unit
-          test_artifacts_suffix: "-${{ inputs.current-group }}"
+          name: test-results-${{ github.sha }}-${{ github.run_number }}-${{ inputs.current-group }}-of-${{ inputs.total-groups }}
+          path: ${{ inputs.working-directory }}/logs
+          retention-days: 3
+          if-no-files-found: ignore
+          compression-level: 9
+
+      - name: Notify run tests result to slack
+        if: |
+          failure() &&
+          github.event_name == 'push' &&
+          (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/v1.10')
+        uses: signalwire/actions-template/.github/actions/slack@main
+        with:
+          CHANNEL: ${{ secrets.SLACK_DEVOPS_CI_CHANNEL }}
+          MESSAGE: Unit-Tests ${{ github.repository }} > <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.run_id }}>. Some tests are failing.
+        env:
+          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
new file mode 100644 (file)
index 0000000..5d59fba
--- /dev/null
@@ -0,0 +1,28 @@
+name: Windows
+
+on:
+  pull_request:
+    types: [opened, synchronize]
+  push:
+    branches: [master, release]
+jobs:
+  x64:
+    runs-on: windows-2019
+
+    steps:
+    - uses: actions/checkout@v4
+      with:
+        fetch-depth: 0
+
+    - name: Add msbuild to PATH
+      uses: microsoft/setup-msbuild@v2
+
+    - name: Build
+      run: msbuild Freeswitch.2017.sln -t:build -verbosity:minimal -property:Configuration=Release -property:Platform=x64
+
+    - name: Upload Artifact
+      uses: actions/upload-artifact@v4
+      with:
+        name: MSI Package
+        path: D:\a\freeswitch\freeswitch\x64\*.msi
+      if: contains(github.event.pull_request.title, ':upload-artifacts') || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/v1.10'
index 5ccdf8b5c20fd1c92c6d73e3dc1bf86a17c4b9bc..9e4eb56f975c899b5a141b361243ba67293397d5 100644 (file)
@@ -17,5 +17,4 @@ extraclean-modules: extraclean
 
 print_tests:
        @set +e; \
-       test -z "$(TESTS)" || for i in $(TESTS); do echo $(subdir)/$$i; done;
-       
\ No newline at end of file
+       test -z "$(TESTS)" || for i in $(TESTS); do echo $(subdir)/$$i; done; echo;
diff --git a/ci.sh b/ci.sh
new file mode 100755 (executable)
index 0000000..c8d9e84
--- /dev/null
+++ b/ci.sh
@@ -0,0 +1,259 @@
+#!/usr/bin/env bash
+
+### shfmt -w -s -ci -sr -kp -fn ci.sh
+
+#------------------------------------------------------------------------------
+# CI Script
+# Helper script for running CI jobs
+#------------------------------------------------------------------------------
+
+# Function to display usage information
+display_usage()
+{
+       echo "Usage: $0 -t <type> -a <action> -c <code> -p <path>"
+       echo "Options:"
+       echo "  -t  Type (unit-test, scan-build)"
+       echo "  -a  Action (configure, build, install, validate)"
+       echo "  -c  Code (sofia-sip, freeswitch)"
+       echo "  -p  Path to code"
+       exit 1
+}
+
+# Parse command line arguments
+while getopts "t:p:a:c:h" opt; do
+       case $opt in
+               t) TYPE="$OPTARG" ;;
+               a) ACTION="$OPTARG" ;;
+               c) CODE="$OPTARG" ;;
+               p) PATH_TO_CODE="$OPTARG" ;;
+               h) display_usage ;;
+               ?) display_usage ;;
+       esac
+done
+
+# Function to handle sofia-sip configuration
+configure_sofia_sip()
+{
+       ./autogen.sh && ./configure.gnu || exit 1
+}
+
+# Function to handle sofia-sip build
+build_sofia_sip()
+{
+       make -j$(nproc) || exit 1
+}
+
+# Function to handle sofia-sip installation
+install_sofia_sip()
+{
+       make install || exit 1
+}
+
+# Function to handle sofia-sip validation
+validate_sofia_sip()
+{
+       exit 0
+}
+
+# Function to handle freeswitch configuration
+configure_freeswitch()
+{
+       local type="$1"
+
+       ./bootstrap.sh -j || exit 1
+
+       case "$type" in
+               "unit-test")
+                       echo 'codecs/mod_openh264' >> modules.conf
+                       sed -i \
+                               -e '/applications\/mod_http_cache/s/^#//g' \
+                               -e '/event_handlers\/mod_rayo/s/^#//g' \
+                               -e '/formats\/mod_opusfile/s/^#//g' \
+                               -e '/languages\/mod_lua/s/^#//g' \
+                               modules.conf
+
+                       export ASAN_OPTIONS=log_path=stdout:disable_coredump=0:unmap_shadow_on_exit=1:fast_unwind_on_malloc=0
+
+                       ./configure \
+                               --enable-address-sanitizer \
+                               --enable-fake-dlclose ||
+                               exit 1
+
+                       ;;
+               "scan-build")
+                       cp build/modules.conf.most modules.conf
+
+                       # "Enable"/"Uncomment" mods
+                       echo 'codecs/mod_openh264' >> modules.conf
+                       sed -i \
+                               -e '/mod_mariadb/s/^#//g' \
+                               -e '/mod_v8/s/^#//g' \
+                               modules.conf
+
+                       # "Disable"/"Comment out" mods
+                       sed -i \
+                               -e '/mod_ilbc/s/^/#/g' \
+                               -e '/mod_isac/s/^/#/g' \
+                               -e '/mod_mp4/s/^/#/g' \
+                               -e '/mod_mongo/s/^/#/g' \
+                               -e '/mod_pocketsphinx/s/^/#/g' \
+                               -e '/mod_sangoma_codec/s/^/#/g' \
+                               -e '/mod_siren/s/^/#/g' \
+                               -e '/mod_avmd/s/^/#/g' \
+                               -e '/mod_basic/s/^/#/g' \
+                               -e '/mod_cdr_mongodb/s/^/#/g' \
+                               -e '/mod_cv/s/^/#/g' \
+                               -e '/mod_erlang_event/s/^/#/g' \
+                               -e '/mod_perl/s/^/#/g' \
+                               -e '/mod_rtmp/s/^/#/g' \
+                               -e '/mod_unimrcp/s/^/#/g' \
+                               -e '/mod_xml_rpc/s/^/#/g' \
+                               modules.conf
+
+                       ./configure || exit 1
+
+                       ;;
+               *)
+                       exit 1
+                       ;;
+       esac
+}
+
+# Function to handle freeswitch build
+build_freeswitch()
+{
+       local type="$1"
+
+       set -o pipefail
+
+       case "$type" in
+               "unit-test")
+                       make --no-keep-going -j$(nproc --all) |& tee ./unit-tests-build-result.txt
+                       build_status=${PIPESTATUS[0]}
+                       if [[ $build_status != "0" ]]; then
+                               exit $build_status
+                       fi
+
+                       ;;
+               "scan-build")
+                       if ! command -v scan-build-14 > /dev/null 2>&1; then
+                               echo "Error: scan-build-14 command not found. Please ensure clang static analyzer is installed." >&2
+                               exit 1
+                       fi
+
+                       mkdir -p scan-build
+
+                       scan-build-14 \
+                               --force-analyze-debug-code \
+                               --status-bugs \
+                               -o ./scan-build/ \
+                               make --no-keep-going -j$(nproc --all) |& tee ./scan-build-result.txt
+                       build_status=${PIPESTATUS[0]}
+
+                       if ! grep -siq "scan-build: No bugs found" ./scan-build-result.txt; then
+                               echo "scan-build: bugs found!"
+                               exit 1
+                       fi
+
+                       if [[ $build_status != "0" ]]; then
+                               echo "scan-build: compilation failed!"
+                               exit $build_status
+                       fi
+
+                       ;;
+               *)
+                       exit 1
+                       ;;
+       esac
+}
+
+# Function to handle freeswitch installation
+install_freeswitch()
+{
+       make install || exit 1
+}
+
+# Function to handle freeswitch validation
+validate_freeswitch()
+{
+       local type="$1"
+
+       case "$type" in
+               "unit-test")
+                       exit 0
+                       ;;
+               "scan-build")
+                       REPORT_PATH=$(find scan-build* -mindepth 1 -type d)
+                       if [ -n "$REPORT_PATH" ]; then
+                               echo "Found analysis report at: $REPORT_PATH"
+
+                               if command -v html2text > /dev/null 2>&1; then
+                                       echo "Report contents:"
+                                       html2text "$REPORT_PATH"/*.html || true
+                               fi
+
+                               echo "Number of issues found:"
+                               grep -c "<!--BUGDESC" "$REPORT_PATH"/*.html || true
+
+                               exit $([ -d "$REPORT_PATH" ])
+                       else
+                               echo "No analysis report found"
+                               exit 0
+                       fi
+
+                       ;;
+               *)
+                       exit 1
+                       ;;
+       esac
+}
+
+# Change to the code directory
+if [ -n "$PATH_TO_CODE" ]; then
+       cd "$PATH_TO_CODE" || exit 1
+fi
+
+# Execute appropriate flow based on code, type, and action
+case "$CODE" in
+       "sofia-sip")
+               case "$ACTION" in
+                       "configure")
+                               configure_sofia_sip "$TYPE"
+                               ;;
+                       "build")
+                               build_sofia_sip "$TYPE"
+                               ;;
+                       "install")
+                               install_sofia_sip "$TYPE"
+                               ;;
+                       "validate")
+                               validate_sofia_sip "$TYPE"
+                               ;;
+                       *)
+                               exit 1
+                               ;;
+               esac
+               ;;
+       "freeswitch")
+               case "$ACTION" in
+                       "configure")
+                               configure_freeswitch "$TYPE"
+                               ;;
+                       "build")
+                               build_freeswitch "$TYPE"
+                               ;;
+                       "install")
+                               install_freeswitch "$TYPE"
+                               ;;
+                       "validate")
+                               validate_freeswitch "$TYPE"
+                               ;;
+                       *)
+                               exit 1
+                               ;;
+               esac
+               ;;
+       *)
+               exit 1
+               ;;
+esac
diff --git a/scan_build.sh b/scan_build.sh
deleted file mode 100755 (executable)
index 200cc2d..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-mkdir -p scan-build
-scan-build-14 --force-analyze-debug-code -o ./scan-build/ make -j`nproc --all` |& tee ./scan-build-result.txt
-exitstatus=${PIPESTATUS[0]}
-echo "*** Exit status is $exitstatus"
-export SubString="scan-build: No bugs found"
-export COMPILATION_FAILED="false"
-export BUGS_FOUND="false"
-if [[ "0" != "$exitstatus" ]] ; then
-  export COMPILATION_FAILED="true"
-  echo MESSAGE="compilation failed" >> $GITHUB_OUTPUT
-fi
-export RESULTFILE="$PWD/scan-build-result.txt"
-# cat $RESULTFILE
-if ! grep -sq "$SubString" $RESULTFILE; then
-  export BUGS_FOUND="true"
-  echo MESSAGE="found bugs" >> $GITHUB_OUTPUT
-fi
-export REPORT=$PWD/`find scan-build* -mindepth 1 -type d`
-echo "COMPILATION_FAILED: $COMPILATION_FAILED"
-echo "BUGS_FOUND: $BUGS_FOUND"
-echo "COMPILATION_FAILED=$COMPILATION_FAILED" >> $GITHUB_OUTPUT
-echo "BUGS_FOUND=$BUGS_FOUND" >> $GITHUB_OUTPUT
-echo "REPORT=$REPORT" >> $GITHUB_OUTPUT
-if [[ "0" != "$exitstatus" ]] || ! grep -sq "$SubString" $RESULTFILE; then
-  exit 1
-fi
-exit 0
index fd2a5f9d9aee44c3cbf51e29a1827c2522538f2a..40efd654c1d8a5d36a1f2bf1b15b1321c7b0060d 100755 (executable)
@@ -1,34 +1,99 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
+### shfmt -w -s -ci -sr -kp -fn tests/unit/collect-test-logs.sh
+
+#------------------------------------------------------------------------------
+# Collects test logs and generates an HTML report
+# of failed unit tests with links to backtraces
+#------------------------------------------------------------------------------
+
+# Configuration with default value
+LOG_DIR="./logs"
+PRINT_TO_CONSOLE=0
+
+# Parse command line arguments
+while [[ $# -gt 0 ]]; do
+       case $1 in
+               -p | --print)
+                       if ! command -v html2text > /dev/null 2>&1; then
+                               echo "Error: html2text is required for printing HTML contents"
+                               echo "Please install html2text and try again"
+                               exit 1
+                       fi
+                       PRINT_TO_CONSOLE=1
+                       shift
+                       ;;
+               -d | --dir)
+                       if [ -z "$2" ]; then
+                               echo "Error: Log directory path is required for -d|--dir option"
+                               exit 1
+                       fi
+                       LOG_DIR="$2"
+                       shift 2
+                       ;;
+               *)
+                       echo "Unknown option: $1"
+                       echo "Usage: $0 [-p|--print] [-d|--dir DIR]"
+                       exit 1
+                       ;;
+       esac
+done
+
+# Initialize HTML
 echo "Collecting test logs"
-LOG_DIR=./logs
-html="<html><h3>There are failed unit-tests:</h3><table>"
-logs=$(find $LOG_DIR -type f -iname "*.html" -print | sort)
+html="<html><head><title>Failed Unit Tests Report</title></head><body><h3>There are failed unit-tests:</h3><table>"
+
+# Find all HTML log files and sort them
+logs=$(find "$LOG_DIR" -type f -iname "*.html" -print | sort)
 logs_found=0
 olddirname=""
-for name in $logs
-do
-       logname=$(basename $name)
-       testname=$(echo $logname | awk -F 'log_run-tests_' '{print $2}' | awk -F '.html' '{print $1}')
+
+# Process each log file
+for name in $logs; do
+       # Extract test information
+       logname=$(basename "$name")
+       testname=$(echo "$logname" | awk -F 'log_run-tests_' '{print $2}' | awk -F '.html' '{print $1}')
        testpath="${testname//!/\/}"
-       dirname=$(dirname $testpath)
-       test=$(basename $testpath)
+       dirname=$(dirname "$testpath")
+       test=$(basename "$testpath")
+
+       # Add directory header if it's new
        if [ "$olddirname" != "$dirname" ]; then
-               html+="<tr align=\"left\"><th><br>$dirname</th></tr>" ;
-               olddirname=$dirname ;
+               html+="<tr align=\"left\"><th><br>$dirname</th></tr>"
+               olddirname=$dirname
        fi
-       html+="<tr align=\"left\"><td><a href="$logname">$test</a>"
+
+       # Add test entry
+       html+="<tr align=\"left\"><td><a href=\"$logname\">$test</a>"
+
+       # Check for backtrace
        backtrace="backtrace_$testname.txt"
        if test -f "${LOG_DIR}/$backtrace"; then
+               if [ $PRINT_TO_CONSOLE -eq 1 ]; then
+                       echo "Core dumped, backtrace:"
+                       cat $backtrace
+                       echo
+               fi
+
                html+=". Core dumped, backtrace is available <a href=\"$backtrace\">here</a>"
        fi
+
        html+="</td></tr>"
        logs_found=1
+
+       # Print current log file if requested
+       if [ $PRINT_TO_CONSOLE -eq 1 ]; then
+               echo "=== Contents of $name ==="
+               html2text "$name"
+               echo "=== End of $name ==="
+               echo
+       fi
 done
 
+# Generate report if logs were found
 if [ $logs_found -ne 0 ]; then
-       html+="</table></html>"
-       echo $html > $LOG_DIR/artifacts.html
+       html+="</table></body></html>"
+       echo "$html" > "$LOG_DIR/artifacts.html"
        exit 1
 fi
 
diff --git a/tests/unit/run-tests-docker.sh b/tests/unit/run-tests-docker.sh
new file mode 100755 (executable)
index 0000000..91e52d3
--- /dev/null
@@ -0,0 +1,165 @@
+#!/usr/bin/env bash
+
+### shfmt -w -s -ci -sr -kp -fn tests/unit/run-tests-docker.sh
+
+#------------------------------------------------------------------------------
+# Docker Test Runner
+# Parallel test execution in Docker with configurable CPU and container counts
+#------------------------------------------------------------------------------
+
+# Exit on error
+set -e
+
+# Global exit status
+GLOBAL_EXIT_STATUS=0
+
+# Default values
+SOFIA_SIP_PATH=""
+FREESWITCH_PATH=""
+IMAGE_TAG="ci.local"
+BASE_IMAGE=""
+MAX_CONTAINERS="1"
+CPUS_PER_CONTAINER="1"
+CONTAINER_IDS_FILE=$(mktemp)
+OUTPUT_DIR=""
+
+# Parse command line arguments
+while [[ $# -gt 0 ]]; do
+       case $1 in
+               --sofia-sip-path)
+                       SOFIA_SIP_PATH="$2"
+                       shift
+                       ;;
+               --freeswitch-path)
+                       FREESWITCH_PATH="$2"
+                       shift
+                       ;;
+               --image-tag)
+                       IMAGE_TAG="$2"
+                       shift
+                       ;;
+               --base-image)
+                       BASE_IMAGE="$2"
+                       shift
+                       ;;
+               --max-containers)
+                       MAX_CONTAINERS="$2"
+                       shift
+                       ;;
+               --cpus)
+                       CPUS_PER_CONTAINER="$2"
+                       shift
+                       ;;
+               --output-dir)
+                       OUTPUT_DIR="$2"
+                       shift
+                       ;;
+               *)
+                       echo "Unknown parameter: $1"
+                       exit 1
+                       ;;
+       esac
+       shift
+done
+
+# Validate paths exist
+if [ ! -d "$SOFIA_SIP_PATH" ]; then
+       echo "Error: Sofia-SIP path does not exist: $SOFIA_SIP_PATH"
+       exit 1
+fi
+
+if [ ! -d "$FREESWITCH_PATH" ]; then
+       echo "Error: FreeSWITCH path does not exist: $FREESWITCH_PATH"
+       exit 1
+fi
+
+# Validate output directory is provided (required for test results)
+if [ -z "$OUTPUT_DIR" ]; then
+       echo "Error: Output directory must be specified with --output-dir"
+       exit 1
+fi
+
+# Create output directory
+mkdir -p "$OUTPUT_DIR"
+
+# Define build log file
+BUILD_LOG="$OUTPUT_DIR/docker_build.log"
+
+echo "Starting Docker build..."
+
+# Build Docker image and redirect output to log file
+if ! docker build \
+       --build-arg BUILDER_IMAGE="$BASE_IMAGE" \
+       --build-context sofia-sip="$SOFIA_SIP_PATH" \
+       --build-context freeswitch="$FREESWITCH_PATH" \
+       --file "$FREESWITCH_PATH/.github/docker/debian/bookworm/amd64/CI/Dockerfile" \
+       --tag "$IMAGE_TAG" \
+       "$FREESWITCH_PATH/.github/docker/debian" > "$BUILD_LOG" 2>&1; then
+       echo "Docker build failed! Build log:"
+       cat "$BUILD_LOG"
+       exit 1
+fi
+
+echo "Build completed successfully! Build log saved to: $BUILD_LOG"
+
+# Get working directory from image
+CONTAINER_WORKDIR=$(docker inspect "$IMAGE_TAG" --format='{{.Config.WorkingDir}}')
+if [ -z "$CONTAINER_WORKDIR" ]; then
+       echo "Error: Could not determine container working directory"
+       exit 1
+fi
+
+# Start test containers with output directory mounted
+echo "Starting $MAX_CONTAINERS containers..."
+
+for i in $(seq 1 "$MAX_CONTAINERS"); do
+       CTID=$(docker run --privileged \
+               --cpus "$CPUS_PER_CONTAINER" \
+               --volume "$OUTPUT_DIR:$CONTAINER_WORKDIR/$OUTPUT_DIR" \
+               --detach \
+               "$IMAGE_TAG" \
+               --output-dir "$OUTPUT_DIR" \
+               $MAX_CONTAINERS $i)
+       echo "$CTID $i" >> "$CONTAINER_IDS_FILE"
+       echo "Started container: $CTID (index: $i)"
+done
+
+echo "All containers started successfully!"
+
+# Wait for containers to finish and collect results
+echo "Waiting for containers to finish..."
+while read -r line; do
+       CTID=$(echo "$line" | cut -d' ' -f1)
+       INDEX=$(echo "$line" | cut -d' ' -f2)
+
+       docker wait "$CTID" > /dev/null 2>&1
+       EXIT_CODE=$(docker inspect "$CTID" --format='{{.State.ExitCode}}')
+
+       # Save container logs to output directory
+       docker logs "$CTID" > "$OUTPUT_DIR/$CTID.log" 2>&1
+       echo "Container logs saved to: $OUTPUT_DIR/$CTID.log"
+
+       if [ "$EXIT_CODE" -ne 0 ]; then
+               echo "Container $CTID (index: $INDEX) failed with exit code: $EXIT_CODE"
+               GLOBAL_EXIT_STATUS=1
+
+               tail -n 50 "$OUTPUT_DIR/$CTID.log"
+       else
+               echo "Container $CTID (index: $INDEX) completed successfully"
+       fi
+
+done < "$CONTAINER_IDS_FILE"
+
+# Clean up temporary files
+rm -f "$CONTAINER_IDS_FILE"
+
+echo "Test outputs have been saved to: $OUTPUT_DIR"
+
+# Exit with global status
+if [ "$GLOBAL_EXIT_STATUS" -eq 0 ]; then
+       echo "All containers completed successfully!"
+else
+       echo "One or more containers failed!"
+fi
+
+exit $GLOBAL_EXIT_STATUS
index 559dec4a8f0ddfe71aefc6a0cb3f821645520c01..c0e97ae0b0cd0b2bb1cbaf111e5db12a7aca1a1b 100755 (executable)
-#!/bin/bash
+#!/usr/bin/env bash
 
-# "print_tests" returns relative paths to all the tests
-TESTS=$(make -s -C ../.. print_tests)
+### shfmt -w -s -ci -sr -kp -fn tests/unit/run-tests.sh
 
-chunks=${1:-1}
-chunk_number=${2:-1}
+#------------------------------------------------------------------------------
+# Test Runner Script
+# Runs tests in chunks, distributing them across processors
+# Supports moving test artifacts to specified output directory
+#------------------------------------------------------------------------------
 
-IFS=$'\n' read -d '' -r -a lines <<< "$TESTS"
+# Exit on error, undefined vars, and propagate pipe failures
+set -euo pipefail
 
-result=""
-for ((i=chunk_number-1; i<${#lines[@]}; i+=chunks))
-do
-  result+="${lines[$i]}"$'\n'
+# Global variable for test failures
+declare -i TESTS_FAILED=0
+
+# Function to show usage
+show_usage()
+{
+       echo "Usage: $0 [chunks] [chunk_number] [options]"
+       echo "  chunks        : Number of chunks to split tests into (default: 1)"
+       echo "  chunk_number  : Which chunk to run (default: 1)"
+       echo "Options:"
+       echo "  --dry-run     : Show test distribution without running tests"
+       echo "  --output-dir  : Directory to store test artifacts (will be created if needed)"
+       echo "  -h|--help     : Show this help message"
+       exit 1
+}
+
+# Function to validate numeric input
+validate_number()
+{
+       local val=$1
+       local name=$2
+
+       if ! [[ $val =~ ^[0-9]+$ ]]; then
+               echo "Error: $name must be a positive number, got: $val"
+               exit 1
+       fi
+
+       if [ "$val" -lt 1 ]; then
+               echo "Error: $name must be greater than 0, got: $val"
+               exit 1
+       fi
+}
+
+# Function to format duration in human-readable form
+format_duration()
+{
+       local duration=$1
+       local minutes=$((duration / 60))
+       local seconds=$((duration % 60))
+       printf "%02d:%02d" $minutes $seconds
+}
+
+# Function to move test artifacts to output directory
+move_artifacts()
+{
+       local output_dir=$1
+
+       # Create output directory if it doesn't exist
+       mkdir -p "$output_dir"
+
+       # Move HTML logs and backtrace files if they exist
+       # Using || true to prevent script failure if no files match
+       (mv log_run-tests_*.html "$output_dir" 2> /dev/null || true)
+       (mv backtrace_*.txt "$output_dir" 2> /dev/null || true)
+
+       # Check if any files were moved
+       if [ -n "$(ls -A "$output_dir" 2> /dev/null)" ]; then
+               echo "Test artifacts moved to: $output_dir"
+       else
+               echo "No test artifacts found to move"
+       fi
+}
+
+# Parse command line arguments
+chunks=1
+chunk_number=1
+dry_run=false
+output_dir=""
+
+while [[ $# -gt 0 ]]; do
+       case $1 in
+               --dry-run)
+                       dry_run=true
+                       shift
+                       ;;
+               --output-dir)
+                       if [ -n "${2:-}" ]; then
+                               output_dir="$2"
+                               shift 2
+                       else
+                               echo "Error: --output-dir requires a directory path"
+                               exit 1
+                       fi
+                       ;;
+               -h | --help)
+                       show_usage
+                       ;;
+               *)
+                       if [[ $chunks -eq 1 ]]; then
+                               chunks=$1
+                       elif [[ $chunk_number -eq 1 ]]; then
+                               chunk_number=$1
+                       else
+                               echo "Error: Unknown argument $1"
+                               show_usage
+                       fi
+                       shift
+                       ;;
+       esac
+done
+
+# Validate numeric inputs
+validate_number "$chunks" "chunks"
+validate_number "$chunk_number" "chunk_number"
+
+# Validate chunk parameters
+if [ "$chunk_number" -gt "$chunks" ]; then
+       echo "Error: chunk_number ($chunk_number) cannot be greater than total chunks ($chunks)"
+       exit 1
+fi
+
+# Get list of tests from make
+echo "Fetching test list..."
+TESTS=$(make -s -C ../.. print_tests 2>/dev/null) || {
+       echo "Error: Failed to fetch test list"
+       exit 1
+}
+
+# Split tests into array
+IFS=$'\n' read -d '' -r -a all_tests <<< "$TESTS" || true # || true to handle last line without newline
+
+# Check if any tests were found
+if [ ${#all_tests[@]} -eq 0 ]; then
+       echo "Error: No tests found!"
+       exit 1
+fi
+
+# Get total number of tests from array length
+total_tests=${#all_tests[@]}
+
+# Select tests for this chunk
+chunk_tests=()
+for ((i = chunk_number - 1; i < total_tests; i += chunks)); do
+       chunk_tests+=("${all_tests[$i]}")
+done
+
+# Size of this chunk
+chunk_size=${#chunk_tests[@]}
+
+# Print execution information
+echo ""
+echo "Test Distribution Information:"
+echo "Total tests found: $total_tests"
+echo "Chunk size: $chunk_size"
+echo "Running chunk $chunk_number/$chunks"
+if [ -n "$output_dir" ]; then
+       echo "Artifacts will be stored in: $output_dir"
+fi
+echo ""
+echo "Tests to be executed:"
+for i in "${!chunk_tests[@]}"; do
+       printf "%3d) %s\n" "$((i + 1))" "${chunk_tests[$i]}"
 done
+echo ""
+
+# Exit here if dry run
+if [ "$dry_run" = true ]; then
+       echo "Dry run complete. Use without --dry-run to execute tests."
+       exit 0
+fi
+
+# Record start time
+start_time=$(date +%s)
 
-TESTS=$result
+# Run tests sequentially within the chunk
+if ! make -f run-tests.mk TEST_LIST="${chunk_tests[*]}"; then
+       TESTS_FAILED=1
+fi
 
-echo "-----------------------------------------------------------------";
-echo "Starting tests on $(nproc --all) processors";
-echo "Tests found: ${TESTS}";
-echo "-----------------------------------------------------------------";
+# Move artifacts if output directory was specified
+if [ -n "$output_dir" ]; then
+       move_artifacts "$output_dir"
+fi
 
-make -f run-tests.mk TEST_LIST=$TESTS
+# Record end time and calculate duration
+end_time=$(date +%s)
+duration=$((end_time - start_time))
 
-echo "Timing results:"
-cat test_times.log
+# Print timing results and statistics
+echo ""
+echo "Execution Summary for chunk $chunk_number/$chunks:"
+echo "Started at : $(date -d @$start_time '+%Y-%m-%d %H:%M:%S')"
+echo "Finished at: $(date -d @$end_time '+%Y-%m-%d %H:%M:%S')"
+echo "Duration   : $(format_duration $duration)"
+echo "Status     : $([ $TESTS_FAILED -eq 0 ] && echo "SUCCESS" || echo "FAILED")"
+echo ""
 
-echo "Done running tests!"
+# Exit with appropriate status code
+exit $TESTS_FAILED
index 08de6ccd991652abe2b0182f3cee4678160f0e7e..c77fe5592dfde988948128e6fdecc8f81f8b8dbd 100755 (executable)
@@ -1,5 +1,13 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
+### shfmt -w -s -ci -sr -kp -fn tests/unit/run-tests.sh
+
+#------------------------------------------------------------------------------
+# Test Execution Script
+# Executes unit tests and handles test logs and core dumps
+#------------------------------------------------------------------------------
+
+# Initialize timing
 start_time=$(date +%s)
 
 # All output will be collected here
@@ -8,49 +16,74 @@ TESTSUNITPATH=$PWD
 # All relative paths are based on the tree's root
 FSBASEDIR=$(realpath "$PWD/../../")
 
+# Set system limits
 ulimit -c unlimited
 ulimit -a
 
+# Get test identifier from argument
 i=$1
 
-echo "----------------------------------" ;
-echo "Starting test: $i" ;
-echo "----------------------------------" ;
+echo ""
+echo "Starting test: $i"
+echo ""
 
 # Change folder to where the test is
 currenttestpath="$FSBASEDIR/$i"
-cd $(dirname "$currenttestpath")
+cd "$(dirname "$currenttestpath")"
 
 # Tests are unique per module, so need to distinguish them by their directory
 relativedir=$(dirname "$i")
 echo "Relative dir is $relativedir"
 
+# Set up log file path
 file=$(basename -- "$currenttestpath")
-log="$TESTSUNITPATH/log_run-tests_${relativedir//\//!}!$file.html";
+log="$TESTSUNITPATH/log_run-tests_${relativedir//\//!}!$file.html"
 
 # Execute the test
 echo "Start executing $currenttestpath"
-$currenttestpath 2>&1 | tee >(ansi2html > $log) ;
-exitstatus=${PIPESTATUS[0]} ;
+$currenttestpath 2>&1 | tee >(ansi2html > "$log")
+exitstatus=${PIPESTATUS[0]}
 echo "End executing $currenttestpath"
+
+# Record execution time
 end_time=$(date +%s)
 duration=$((end_time - start_time))
 echo "Test $1 took $duration seconds" >> test_times.log
 echo "Exit status is $exitstatus"
 
-if [ "0" -eq $exitstatus ] ; then
-       rm $log ;
+# Handle test results
+if [ "0" -eq "$exitstatus" ]; then
+       rm "$log"
 else
-       echo "*** ./$i exit status is $exitstatus" ;
-       corefilesearch=/cores/core.*.!__w!freeswitch!freeswitch!${relativedir//\//!}!.libs!$file.* ;
-       echo $corefilesearch ;
+       echo "*** ./$i exit status is $exitstatus"
+
+       # Search for core dumps
+       corefilesearch="/cores/core.*.!__w!freeswitch!freeswitch!${relativedir//\//!}!.libs!$file.*"
+       echo "$corefilesearch"
+
        if ls $corefilesearch 1> /dev/null 2>&1; then
-           echo "coredump found";
-           coredump=$(ls $corefilesearch) ;
-           echo $coredump;
-           echo "set logging file $TESTSUNITPATH/backtrace_${i//\//!}.txt" ;
-           gdb -ex "set logging file $TESTSUNITPATH/backtrace_${i//\//!}.txt" -ex "set logging on" -ex "set pagination off" -ex "bt full" -ex "bt" -ex "info threads" -ex "thread apply all bt" -ex "thread apply all bt full" -ex "quit" /__w/freeswitch/freeswitch/$relativedir/.libs/$file $coredump ;
-       fi ;
-       echo "*** $log was saved" ;
-fi ;
-echo "----------------" ;
+               echo "coredump found"
+               coredump=$(ls $corefilesearch)
+               echo "$coredump"
+
+               # Generate backtrace using GDB
+               gdb \
+                       -ex "set logging file $TESTSUNITPATH/backtrace_${i//\//!}.txt" \
+                       -ex "set logging on" \
+                       -ex "set pagination off" \
+                       -ex "bt full" \
+                       -ex "bt" \
+                       -ex "info threads" \
+                       -ex "thread apply all bt" \
+                       -ex "thread apply all bt full" \
+                       -ex "quit" \
+                       "$FSBASEDIR/$relativedir/.libs/$file" \
+                       "$coredump"
+       fi
+
+       echo "*** $log was saved"
+
+       exit $exitstatus
+fi
+
+echo ""