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 ) }}
|