summaryrefslogtreecommitdiffstats
path: root/.github/workflows/run_tests.yml
blob: 769ef71f8f0732f134a057e197d513134ec7d83c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
name: Run-tests

on:
  workflow_call:
    inputs:
      test_targets:
        description: 'Paths to tests for run ,example : ydb/ ydb/tests/func/suite'
        required: true
        type: string
        default: ydb/
      test_type:
        description: 'Test type (empty = all types: unittest,py3test,py2test,pytest,gtest)'
        required: false
        type: string
        default: ''
      test_size:
        description: 'Test size: small, medium, large (default: small,medium,large)'
        required: false
        type: string
        default: small,medium,large
      additional_ya_make_args:
        description: 'additional args for ya make'
        required: false
        type: string
        default: ''
      build_preset:
        description: 'Build preset type'
        required: true
        type: string
      branches:
        description: 'Branches to test (JSON array or single branch)'
        required: false
        type: string
        default: '["main"]'
      branches_config_path:
        description: 'Path to JSON file with branches to test'
        required: false
        type: string
        default: ''
      blacklist_paths:
        description: 'Newline-separated list of paths or patterns to blacklist; provide all required paths/patterns explicitly'
        required: false
        type: string
        default: ''
      test_threads:
        description: 'Test threads count (default: 52; build presets use a fraction of this value)'
        required: false
        type: string
        default: '52'

  workflow_dispatch:
    inputs:
      pull_number:
        description: 'Pull request number, ex: 12345'
        required: false
        default: ''
      test_targets:
        description: 'Paths to tests for run ,example : ydb/ ydb/tests/func/suite'
        required: true
        default: ydb/
      test_type:
        description: 'Test type (empty = all types: unittest,py3test,py2test,pytest,gtest)'
        required: false
        default: ''
      test_size:
        description: 'Test size: small, medium, large (default: small,medium)'
        required: false
        type: choice
        default: small,medium
        options:
          - small
          - medium
          - large
          - small,medium
          - small,medium,large
      additional_ya_make_args:
        description: 'additional args for ya make'
        required: false
        default: ''
      build_preset:
        description: 'Build preset type (relwithdebinfo, release-asan, release-msan, release-tsan)'
        required: true
        type: choice
        options:
          - relwithdebinfo
          - release-asan
          - release-msan
          - release-tsan
        default: relwithdebinfo
      collect_coredumps:
        type: boolean
        default: false
        description: "Collect coredumps via ulimit -c unlimited"
      test_threads:
        description: 'Test threads count (default: 52; build presets use a fraction of this value)'
        required: false
        default: '52'

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      branch_array: ${{ steps.set-branches.outputs.branch_array }}
      branch_name_map: ${{ steps.set-branches.outputs.branch_name_map }}
      pr_number_map: ${{ steps.set-branches.outputs.pr_number_map }}
    steps:
      - name: Checkout ${{ matrix.branch }}
        uses: actions/checkout@v5
        with:
          sparse-checkout: |
            .github/config
            .github/scripts/tests

      - name: Set branches
        id: set-branches
        env:
          CALLED_BRANCHES: '${{ inputs.branches }}'
          BRANCHES_CONFIG_PATH: '${{ inputs.branches_config_path }}'
          PULL_REQUEST_NUMBER: '${{ inputs.pull_number }}'
          GITHUB_TOKEN: '${{ github.token }}'
        run: |
          # Check if branches parameter was passed from calling workflow
          if [[ -n "$CALLED_BRANCHES" ]]; then
            echo "Branches parameter provided from calling workflow: $CALLED_BRANCHES"
            
            # Check if input is already a JSON array
            if [[ $CALLED_BRANCHES == \[* ]]; then
              # This is a JSON array - parse it
              branches=($(echo "$CALLED_BRANCHES" | jq -r '.[]'))
            else
              # If it's a single branch, create array with one element
              branches=("$CALLED_BRANCHES")
            fi

            processed_branches=()
            branch_name_map="{}"
            pr_number_map="{}"
            for branch in "${branches[@]}"; do
              # Check if branch starts with pull/ and contains numbers
              if [[ $branch =~ ^pull/([0-9]+)$ ]]; then
                PULL_REQUEST_NUMBER=${BASH_REMATCH[1]}
                echo "Processing pull request: $PULL_REQUEST_NUMBER"
                
                # Get PR data from GitHub API
                PR_DATA=$(curl --retry 5 --retry-all-errors --retry-delay 10 --fail --show-error -s -H "Authorization: token $GITHUB_TOKEN" \
                  "https://api.github.com/repos/ydb-platform/ydb/pulls/$PULL_REQUEST_NUMBER")
                MERGE_SHA=$(echo "$PR_DATA" | jq -r '.merge_commit_sha')
                BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.base.ref')
                
                if [ -n "$MERGE_SHA" ] && [ "$MERGE_SHA" != "null" ]; then
                  echo "Replaced pull/$PULL_REQUEST_NUMBER with merge commit: $MERGE_SHA (base: $BASE_BRANCH)"
                  processed_branches+=("\"$MERGE_SHA\"")
                  # Add to mapping: checkout_branch (SHA) -> test_history_branch (base branch for PR, or same for regular branches)
                  branch_name_map=$(echo "$branch_name_map" | jq -c --arg sha "$MERGE_SHA" --arg base "$BASE_BRANCH" '. + {($sha): $base}')
                  # Add to mapping: checkout_branch (SHA) -> PR number
                  pr_number_map=$(echo "$pr_number_map" | jq -c --arg sha "$MERGE_SHA" --arg pr "$PULL_REQUEST_NUMBER" '. + {($sha): $pr}')
                  # Save merge commit SHA, base branch, and PR number for comment step (use first PR)
                  if [ -z "$COMMIT_SHA" ]; then
                    echo "COMMIT_SHA=$MERGE_SHA" >> $GITHUB_ENV
                    echo "PR_BASE_BRANCH=$BASE_BRANCH" >> $GITHUB_ENV
                    echo "PR_NUMBER=$PULL_REQUEST_NUMBER" >> $GITHUB_ENV
                  fi
                else
                  echo "Failed to get merge commit for PR $PULL_REQUEST_NUMBER, discarding"
                fi
              else
                # Just add the branch as-is
                processed_branches+=("\"$branch\"")
              fi
            done

            # Reassemble into JSON array
            branch_array=$(IFS=, ; echo "[${processed_branches[*]}]")
            echo "branch_array=$branch_array" >> $GITHUB_OUTPUT
            # Output mapping of checkout_branch -> test_history_branch (for PR: SHA -> base_branch, for regular branches: empty {})
            # Use compact JSON format (no newlines) for GitHub Actions output
            branch_name_map_compact=$(echo "$branch_name_map" | jq -c .)
            echo "branch_name_map=$branch_name_map_compact" >> $GITHUB_OUTPUT
            echo "Created branch_name_map: $branch_name_map_compact"
            # Output mapping of checkout_branch -> PR number (for PR: SHA -> PR number, for regular branches: empty {})
            pr_number_map_compact=$(echo "$pr_number_map" | jq -c .)
            echo "pr_number_map=$pr_number_map_compact" >> $GITHUB_OUTPUT
            echo "Created pr_number_map: $pr_number_map_compact"
          elif [[ -n "$BRANCHES_CONFIG_PATH" ]]; then
            echo "branch_array=$(jq -c "." "$BRANCHES_CONFIG_PATH")" >> $GITHUB_OUTPUT
            # No mapping for branches from config file - use branches as-is for test history
            echo "branch_name_map={}" >> $GITHUB_OUTPUT
            echo "pr_number_map={}" >> $GITHUB_OUTPUT
          elif [[ -n "$PULL_REQUEST_NUMBER" ]] && [[ $PULL_REQUEST_NUMBER =~ ^[0-9]+$ ]]; then
            PR_DATA=$(curl --retry 5 --retry-all-errors --retry-delay 10 --fail --show-error -s -H "Authorization: token $GITHUB_TOKEN" \
                  "https://api.github.com/repos/ydb-platform/ydb/pulls/$PULL_REQUEST_NUMBER")
            MERGE_SHA=$(echo "$PR_DATA" | jq -r '.merge_commit_sha')
            BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.base.ref')
            if [ -n "$MERGE_SHA" ] && [ "$MERGE_SHA" != "null" ]; then
              # Use merge commit SHA for checkout, but base branch for test history and results
              echo "branch_array=[\"$MERGE_SHA\"]" >> $GITHUB_OUTPUT
              # Save merge commit SHA, base branch, and PR number for comment step
              echo "COMMIT_SHA=$MERGE_SHA" >> $GITHUB_ENV
              echo "PR_BASE_BRANCH=$BASE_BRANCH" >> $GITHUB_ENV
              echo "PR_NUMBER=$PULL_REQUEST_NUMBER" >> $GITHUB_ENV
              # Output mapping of checkout_branch -> test_history_branch (SHA -> base_branch for PR)
              # Use compact JSON format (no newlines) for GitHub Actions output
              branch_name_map=$(jq -c -n --arg sha "$MERGE_SHA" --arg base "$BASE_BRANCH" '{($sha): $base}')
              echo "branch_name_map=$branch_name_map" >> $GITHUB_OUTPUT
              # Output mapping of checkout_branch -> PR number (SHA -> PR number for PR)
              pr_number_map=$(jq -c -n --arg sha "$MERGE_SHA" --arg pr "$PULL_REQUEST_NUMBER" '{($sha): $pr}')
              echo "pr_number_map=$pr_number_map" >> $GITHUB_OUTPUT
            else
              echo "Failed to get merge commit for PR $PULL_REQUEST_NUMBER"
              exit 1
            fi
          else
            # If no branches specified, this is a direct workflow_dispatch run
            echo "No branches specified, using current branch: ${{ github.ref_name }}"
            echo "branch_array=[\"${{ github.ref_name }}\"]" >> $GITHUB_OUTPUT
            # No mapping for direct workflow_dispatch - use branch as-is for test history
            echo "branch_name_map={}" >> $GITHUB_OUTPUT
            echo "pr_number_map={}" >> $GITHUB_OUTPUT
          fi
          
          echo "Final branches to use: $(cat $GITHUB_OUTPUT | grep branch_array | cut -d= -f2)"

      - name: Install dependencies
        if: always()
        run: |
          # Check if COMMIT_SHA is set (means we have a PR to comment on)
          if [ -n "$COMMIT_SHA" ]; then
            pip install PyGithub
          else
            echo "No PR detected, skipping dependency installation"
          fi

      - name: Post run tests start comment
        if: always()
        shell: bash
        env:
          BUILD_PRESET: ${{ inputs.build_preset }}
          GITHUB_TOKEN: ${{ github.token }}
          TEST_SIZE: ${{ inputs.test_size }}
          BUILD_TARGET: ${{ inputs.test_targets }}
          COLLECT_COREDUMPS: ${{ inputs.collect_coredumps }}
        run: |
          # Check if COMMIT_SHA is set (means we have a PR to comment on)
          if [ -z "$COMMIT_SHA" ]; then
            echo "No PR detected (COMMIT_SHA is not set), skipping comment"
            exit 0
          fi
          commit_sha="$COMMIT_SHA"
          
          commit_short=$(echo "$commit_sha" | cut -c1-7)
          commit_url="https://github.com/${GITHUB_REPOSITORY}/commit/${commit_sha}"
          check_url="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
          
          workflow_name="${{ github.workflow }}"
          
          {
            echo "### $workflow_name started"
            echo ""
            echo "- **Build Preset:** \`$BUILD_PRESET\`"
            echo "- **Test Size:** \`$TEST_SIZE\`"
            echo "- **Test Targets:** \`$BUILD_TARGET\`"
            if [ "$COLLECT_COREDUMPS" = "true" ]; then
              echo "- **Collect Coredumps:** \`true\`"
            fi
            echo "- **Commit:** [${commit_short}](${commit_url})"
            echo "- **Workflow run:** [link](${check_url})"
            echo ""
          } | .github/scripts/tests/comment-pr.py --rewrite --no-timestamp

  run_tests:
    needs: prepare
    name: ${{ matrix.branch }}:${{ inputs.build_preset }}
    timeout-minutes: 1200
    runs-on: [ self-hosted, auto-provisioned, "${{ format('build-preset-{0}', inputs.build_preset) }}" ]
    strategy:
      fail-fast: false
      matrix:
        branch: ${{ fromJson(needs.prepare.outputs.branch_array) }}
    steps:
      - name: Set variables based on build_preset
        id: set-vars
        run: |
          TEST_THREADS="${{ inputs.test_threads }}"
          if [[ "${{ inputs.build_preset }}" == "relwithdebinfo" ]]; then
            echo "threads_count=$TEST_THREADS" >> $GITHUB_ENV
            echo "timeout=1200" >> $GITHUB_ENV
          elif [[ "${{ inputs.build_preset }}" == "release-asan" ]]; then
            echo "threads_count=$(awk "BEGIN {x=$TEST_THREADS * 0.38; printf \"%.0f\", (x < 1 ? 1 : x)}")" >> $GITHUB_ENV
            echo "timeout=1200" >> $GITHUB_ENV
          elif [[ "${{ inputs.build_preset }}" == "release-msan" ]]; then
            echo "threads_count=$(awk "BEGIN {x=$TEST_THREADS * 0.1; printf \"%.0f\", (x < 1 ? 1 : x)}")" >> $GITHUB_ENV
            echo "timeout=1200" >> $GITHUB_ENV
          elif [[ "${{ inputs.build_preset }}" == "release-tsan" ]]; then
            echo "threads_count=$(awk "BEGIN {x=$TEST_THREADS * 0.35; printf \"%.0f\", (x < 1 ? 1 : x)}")" >> $GITHUB_ENV
            echo "timeout=1200" >> $GITHUB_ENV
          else
            echo "Unknown build_preset value."
            exit 1
          fi

      - name: Checkout ${{ matrix.branch }}
        uses: actions/checkout@v5
        with:
          ref: ${{ matrix.branch }}

      - name: Setup ydb access
        uses: ./.github/actions/setup_ci_ydb_service_account_key_file_credentials
        with:
          ci_ydb_service_account_key_file_credentials: ${{ secrets.CI_YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS }}
          ydb_qa_config: ${{ vars.YDB_QA_CONFIG }}

      - name: Determine branch name and PR number for test history
        id: determine-branch
        run: |
          # Get the mapping of checkout_branch -> test_history_branch
          # For PR: checkout_branch is merge commit SHA, test_history_branch is base branch (e.g., stable-25-3)
          # For regular branches: mapping is empty {}, so use checkout_branch as-is for test history
          branch_name_map='${{ needs.prepare.outputs.branch_name_map }}'
          pr_number_map='${{ needs.prepare.outputs.pr_number_map }}'
          current_branch="${{ matrix.branch }}"
          
          # Check if current branch is in the mapping
          # If mapping is empty {} or branch not found, use branch as-is (regular branch, not a PR)
          if [ "$branch_name_map" != "{}" ] && [ -n "$branch_name_map" ]; then
            test_history_branch=$(echo "$branch_name_map" | jq -r --arg branch "$current_branch" '.[$branch] // empty')
            if [ -n "$test_history_branch" ] && [ "$test_history_branch" != "null" ]; then
              echo "branch_name=$test_history_branch" >> $GITHUB_OUTPUT
              echo "Using test history branch from mapping: $test_history_branch (checkout branch: $current_branch)"
            else
              echo "branch_name=$current_branch" >> $GITHUB_OUTPUT
              echo "Branch not in mapping, using as-is: $current_branch"
            fi
          else
            # No mapping (empty {} or not set) - this is a regular branch, use checkout branch for test history
            echo "branch_name=$current_branch" >> $GITHUB_OUTPUT
            echo "No mapping (regular branch), using checkout branch for test history: $current_branch"
          fi
          
          # Get PR number from mapping if available
          if [ "$pr_number_map" != "{}" ] && [ -n "$pr_number_map" ]; then
            pr_number=$(echo "$pr_number_map" | jq -r --arg branch "$current_branch" '.[$branch] // empty')
            if [ -n "$pr_number" ] && [ "$pr_number" != "null" ]; then
              echo "pr_number=$pr_number" >> $GITHUB_OUTPUT
              echo "Using PR number from mapping: $pr_number (checkout branch: $current_branch)"
            else
              echo "pr_number=" >> $GITHUB_OUTPUT
              echo "Branch not in PR mapping"
            fi
          else
            echo "pr_number=" >> $GITHUB_OUTPUT
            echo "No PR mapping (regular branch)"
          fi

      - name: Create blacklist file
        id: create-blacklist
        if: inputs.blacklist_paths != ''
        shell: bash
        env:
          BLACKLIST_PATHS: ${{ inputs.blacklist_paths }}
        run: |
          BLACKLIST_FILE="${{ runner.temp }}/test_blacklist.yaml"
          > "$BLACKLIST_FILE"
          while IFS= read -r path; do
            [ -z "$path" ] && continue
            echo "- path: $path" >> "$BLACKLIST_FILE"
          done <<< "$BLACKLIST_PATHS"
          echo "blacklist_file=$BLACKLIST_FILE" >> $GITHUB_OUTPUT
          echo "Blacklist file created at $BLACKLIST_FILE with content:"
          cat "$BLACKLIST_FILE"

      - name: Run YDB Tests
        id: run_tests
        timeout-minutes: ${{ fromJson(env.timeout) }}
        uses: ./.github/actions/build_and_test_ya
        with:
          build_preset: ${{ inputs.build_preset }}
          increment: false
          build_target: ${{ inputs.test_targets }}
          run_build: true
          run_tests: true
          test_retry_count: 3
          test_size: ${{ inputs.test_size }}
          test_type: ${{ inputs.test_type }}
          test_threads: ${{ env.threads_count }}
          custom_branch_name: ${{ steps.determine-branch.outputs.branch_name }}
          put_build_results_to_cache: true
          collect_coredumps: ${{ inputs.collect_coredumps }}
          additional_ya_make_args: -DDEBUGINFO_LINES_ONLY ${{ inputs.additional_ya_make_args }} ${{ steps.create-blacklist.outputs.blacklist_file && format('--test-blacklist-path {0}', steps.create-blacklist.outputs.blacklist_file) || '' }}
          pull_number: ${{ steps.determine-branch.outputs.pr_number || inputs.pull_number || '' }}
          secs: ${{ format('{{"AWS_KEY_ID":"{0}","AWS_KEY_VALUE":"{1}","REMOTE_CACHE_USERNAME":"{2}","REMOTE_CACHE_PASSWORD":"{3}","TELEGRAM_YDBOT_TOKEN":"{4}"}}',
            secrets.AWS_KEY_ID, secrets.AWS_KEY_VALUE, secrets.REMOTE_CACHE_USERNAME, secrets.REMOTE_CACHE_PASSWORD, secrets.TELEGRAM_YDBOT_TOKEN ) }}
          vars: ${{ format('{{"AWS_BUCKET":"{0}","AWS_ENDPOINT":"{1}","REMOTE_CACHE_URL":"{2}","GH_ALERTS_TG_LOGINS":"{3}","GH_ALERTS_TG_CHAT":"{4}"}}',
            vars.AWS_BUCKET, vars.AWS_ENDPOINT, vars.REMOTE_CACHE_URL_YA, vars.GH_ALERTS_TG_LOGINS, vars.GH_ALERTS_TG_CHAT ) }}