Skip to content

Commit d6c2f9d

Browse files
authored
Merge pull request #446 from RailsEventStore/parallel-mutation-tests-take2
Parallel mutation tests take2
2 parents ca6e770 + 9244c2d commit d6c2f9d

File tree

4 files changed

+168
-8
lines changed

4 files changed

+168
-8
lines changed

.github/workflows/pricing.yml

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,78 @@ jobs:
2727
{
2828
attachments: [{
2929
color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning',
30-
text: `${process.env.AS_WORKFLOW}/${process.env.AS_JOB} ${{ job.status }} in ${process.env.AS_TOOK}\n${process.env.AS_COMMIT} in ${process.env.AS_REF}`,
30+
text: `${process.env.AS_WORKFLOW}/${{ github.job }} ${{ job.status }} in ${process.env.AS_TOOK}\n${process.env.AS_COMMIT} in ${process.env.AS_REF}`,
3131
}]
3232
}
3333
env:
3434
SLACK_WEBHOOK_URL: ${{ secrets.CI_WEBHOOK }}
3535
if: always()
3636
continue-on-error: true
3737

38+
prepare_mutation_subjects_pricing:
39+
runs-on: ubuntu-24.04
40+
outputs:
41+
subject_groups: ${{ steps.split_subjects.outputs.subject_groups }}
42+
env:
43+
WORKING_DIRECTORY: ecommerce/pricing
44+
steps:
45+
- uses: actions/checkout@v3
46+
- uses: ruby/setup-ruby@v1
47+
with:
48+
ruby-version: ruby-3.3.7
49+
bundler-cache: true
50+
working-directory: ${{ env.WORKING_DIRECTORY }}
51+
- name: List and split subjects for pricing
52+
id: split_subjects
53+
working-directory: ${{ env.WORKING_DIRECTORY }}
54+
run: |
55+
SUBJECT_LIST_OUTPUT=$(RAILS_ENV=test bundle exec mutant environment subject list)
56+
57+
mapfile -t subjects_array < <( \
58+
echo "$SUBJECT_LIST_OUTPUT" | \
59+
awk 'NR == 1 {next} /Run options:/ {exit} {print}' | \
60+
sed 's/\x1b\[[0-9;]*m//g' | \
61+
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | \
62+
awk 'NF' \
63+
)
64+
65+
if [ ${#subjects_array[@]} -eq 0 ]; then
66+
echo "No subjects found for pricing after cleaning. Setting empty subject_groups."
67+
echo "subject_groups=[]" >> $GITHUB_OUTPUT
68+
exit 0
69+
fi
70+
71+
total_subjects=${#subjects_array[@]}
72+
NUM_GROUPS=32 # Define the number of parallel jobs
73+
groups_json_array_content=""
74+
75+
for (( i=0; i<NUM_GROUPS; i++ )); do
76+
current_group_subjects_array=()
77+
for (( j=i; j<total_subjects; j+=NUM_GROUPS )); do
78+
current_group_subjects_array+=("${subjects_array[j]}")
79+
done
80+
81+
if [ ${#current_group_subjects_array[@]} -gt 0 ]; then
82+
group_subjects_string=$(IFS=' '; echo "${current_group_subjects_array[*]}")
83+
84+
if [ -n "$groups_json_array_content" ]; then
85+
groups_json_array_content="$groups_json_array_content,"
86+
fi
87+
escaped_group_subjects_string=$(printf '%s' "$group_subjects_string" | sed 's/"/\\"/g')
88+
groups_json_array_content="$groups_json_array_content\"$escaped_group_subjects_string\""
89+
fi
90+
done
91+
echo "Generated subject_groups for pricing: [$groups_json_array_content]"
92+
echo "subject_groups=[$groups_json_array_content]" >> $GITHUB_OUTPUT
93+
3894
mutate:
95+
needs: prepare_mutation_subjects_pricing
96+
if: ${{ needs.prepare_mutation_subjects_pricing.outputs.subject_groups != '[]' && needs.prepare_mutation_subjects_pricing.outputs.subject_groups != '' }}
3997
runs-on: ubuntu-24.04
4098
strategy:
4199
fail-fast: false
100+
matrix:
101+
subject_group: ${{ fromJson(needs.prepare_mutation_subjects_pricing.outputs.subject_groups) }}
42102
env:
43103
WORKING_DIRECTORY: ecommerce/pricing
44104
steps:
@@ -50,15 +110,19 @@ jobs:
50110
working-directory: ${{ env.WORKING_DIRECTORY }}
51111
- run: make mutate
52112
working-directory: ${{ env.WORKING_DIRECTORY }}
113+
env:
114+
CI_MUTATE_SUBJECTS: ${{ matrix.subject_group }}
115+
- name: Set Shard Display Index
116+
run: echo "SHARD_DISPLAY_INDEX=$(( ${{ strategy['job-index'] }} + 1 ))" >> $GITHUB_ENV
53117
- uses: 8398a7/action-slack@v3
54118
with:
55119
status: custom
56-
fields: workflow,job,commit,repo,ref,author,took
120+
fields: workflow,commit,repo,ref,author
57121
custom_payload: |
58122
{
59123
attachments: [{
60124
color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning',
61-
text: `${process.env.AS_WORKFLOW}/${process.env.AS_JOB} ${{ job.status }} in ${process.env.AS_TOOK}\n${process.env.AS_COMMIT} in ${process.env.AS_REF}`,
125+
text: `${process.env.AS_WORKFLOW}/${{ github.job }} (shard ${{ env.SHARD_DISPLAY_INDEX }}/${{ strategy.job-total }}) ${{ job.status }}\n${process.env.AS_COMMIT} in ${process.env.AS_REF}`,
62126
}]
63127
}
64128
env:

.github/workflows/rails_application.yml

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,98 @@ jobs:
3939
{
4040
attachments: [{
4141
color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning',
42-
text: `${process.env.AS_WORKFLOW}/${process.env.AS_JOB} ${{ job.status }} in ${process.env.AS_TOOK}\n${process.env.AS_COMMIT} in ${process.env.AS_REF}`,
42+
text: `${process.env.AS_WORKFLOW}/${{ github.job }} ${{ job.status }} in ${process.env.AS_TOOK}\n${process.env.AS_COMMIT} in ${process.env.AS_REF}`,
4343
}]
4444
}
4545
env:
4646
SLACK_WEBHOOK_URL: ${{ secrets.CI_WEBHOOK }}
4747
if: always()
4848
continue-on-error: true
4949

50+
prepare_mutation_subjects_rails:
51+
runs-on: ubuntu-24.04
52+
outputs:
53+
subject_groups: ${{ steps.split_subjects.outputs.subject_groups }}
54+
env:
55+
WORKING_DIRECTORY: rails_application
56+
services:
57+
postgres:
58+
image: postgres:17-alpine
59+
env:
60+
POSTGRES_DB: cqrs-es-sample-with-res_test
61+
POSTGRES_PASSWORD: secret
62+
ports:
63+
- 5432:5432
64+
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
65+
steps:
66+
- uses: actions/checkout@v3
67+
- uses: ruby/setup-ruby@v1
68+
with:
69+
ruby-version: ruby-3.3.7
70+
bundler-cache: true
71+
working-directory: ${{ env.WORKING_DIRECTORY }}
72+
- name: List and split subjects for rails_application
73+
id: split_subjects
74+
working-directory: ${{ env.WORKING_DIRECTORY }}
75+
run: |
76+
# Ensure database is ready before listing subjects
77+
echo "Waiting for PostgreSQL to be ready..."
78+
until pg_isready -h localhost -p 5432 -U "postgres" -d "cqrs-es-sample-with-res_test"; do
79+
sleep 1
80+
done
81+
echo "PostgreSQL is ready."
82+
83+
RAILS_ENV=test bundle exec rails db:prepare
84+
85+
SUBJECT_LIST_OUTPUT=$(RAILS_ENV=test bundle exec mutant environment subject list)
86+
# Skip the first line (e.g., "Subjects in environment: 47") and read subjects into an array
87+
mapfile -t subjects_array < <(echo "$SUBJECT_LIST_OUTPUT" | tail -n +2 | sed 's/\x1b\[[0-9;]*m//g' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | awk 'NF')
88+
89+
mapfile -t subjects_array < <( \
90+
echo "$SUBJECT_LIST_OUTPUT" | \
91+
awk 'NR == 1 {next} /Run options:/ {exit} {print}' | \
92+
sed 's/\x1b\[[0-9;]*m//g' | \
93+
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | \
94+
awk 'NF' \
95+
)
96+
97+
if [ ${#subjects_array[@]} -eq 0 ]; then
98+
echo "No subjects found for rails_application after cleaning. Setting empty subject_groups."
99+
echo "subject_groups=[]" >> $GITHUB_OUTPUT
100+
exit 0
101+
fi
102+
103+
total_subjects=${#subjects_array[@]}
104+
NUM_GROUPS=24 # Define the number of parallel jobs
105+
groups_json_array_content=""
106+
107+
for (( i=0; i<NUM_GROUPS; i++ )); do
108+
current_group_subjects_array=()
109+
for (( j=i; j<total_subjects; j+=NUM_GROUPS )); do
110+
current_group_subjects_array+=("${subjects_array[j]}")
111+
done
112+
113+
if [ ${#current_group_subjects_array[@]} -gt 0 ]; then
114+
group_subjects_string=$(IFS=' '; echo "${current_group_subjects_array[*]}")
115+
116+
if [ -n "$groups_json_array_content" ]; then
117+
groups_json_array_content="$groups_json_array_content,"
118+
fi
119+
escaped_group_subjects_string=$(printf '%s' "$group_subjects_string" | sed 's/"/\\"/g')
120+
groups_json_array_content="$groups_json_array_content\"$escaped_group_subjects_string\""
121+
fi
122+
done
123+
echo "Generated subject_groups for rails_application: [$groups_json_array_content]"
124+
echo "subject_groups=[$groups_json_array_content]" >> $GITHUB_OUTPUT
125+
50126
mutate:
127+
needs: prepare_mutation_subjects_rails
128+
if: ${{ needs.prepare_mutation_subjects_rails.outputs.subject_groups != '[]' && needs.prepare_mutation_subjects_rails.outputs.subject_groups != '' }}
51129
runs-on: ubuntu-24.04
52130
strategy:
53131
fail-fast: false
132+
matrix:
133+
subject_group: ${{ fromJson(needs.prepare_mutation_subjects_rails.outputs.subject_groups) }}
54134
env:
55135
WORKING_DIRECTORY: rails_application
56136
services:
@@ -74,15 +154,19 @@ jobs:
74154
run: bundle exec rails tailwindcss:build
75155
- run: make mutate
76156
working-directory: ${{ env.WORKING_DIRECTORY }}
157+
env:
158+
CI_MUTATE_SUBJECTS: ${{ matrix.subject_group }}
159+
- name: Set Shard Display Index
160+
run: echo "SHARD_DISPLAY_INDEX=$(( ${{ strategy['job-index'] }} + 1 ))" >> $GITHUB_ENV
77161
- uses: 8398a7/action-slack@v3
78162
with:
79163
status: custom
80-
fields: workflow,job,commit,repo,ref,author,took
164+
fields: workflow,commit,repo,ref,author
81165
custom_payload: |
82166
{
83167
attachments: [{
84168
color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning',
85-
text: `${process.env.AS_WORKFLOW}/${process.env.AS_JOB} ${{ job.status }} in ${process.env.AS_TOOK}\n${process.env.AS_COMMIT} in ${process.env.AS_REF}`,
169+
text: `${process.env.AS_WORKFLOW}/${{ github.job }} (shard ${{ env.SHARD_DISPLAY_INDEX }}/${{ strategy.job-total }}) ${{ job.status }}\n${process.env.AS_COMMIT} in ${process.env.AS_REF}`,
86170
}]
87171
}
88172
env:
@@ -115,7 +199,7 @@ jobs:
115199
{
116200
attachments: [{
117201
color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning',
118-
text: `${process.env.AS_WORKFLOW}/${process.env.AS_JOB} ${{ job.status }} in ${process.env.AS_TOOK}\n${process.env.AS_COMMIT} in ${process.env.AS_REF}`,
202+
text: `${process.env.AS_WORKFLOW}/${{ github.job }} ${{ job.status }} in ${process.env.AS_TOOK}\n${process.env.AS_COMMIT} in ${process.env.AS_REF}`,
119203
}]
120204
}
121205
env:

ecommerce/pricing/Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ test:
55
@bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
66

77
mutate:
8-
@RAILS_ENV=test bundle exec mutant run
8+
ifeq ($(CI_MUTATE_SUBJECTS),)
9+
@echo "Running all mutation tests (local or full CI run)"
10+
@env RAILS_ENV=test bundle exec mutant run
11+
else
12+
@echo "Running CI mutation tests for subjects: $(CI_MUTATE_SUBJECTS)"
13+
@env RAILS_ENV=test bundle exec mutant run $(CI_MUTATE_SUBJECTS)
14+
endif
915

1016
.PHONY: install test mutate

rails_application/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ dev:
66
@$(MAKE) -j 10 web css
77

88
mutate: ## Run mutation tests
9+
ifeq ($(CI_MUTATE_SUBJECTS),)
10+
@echo "Running all mutation tests (local or full CI run)"
911
@env RAILS_ENV=test bundle exec mutant run
12+
else
13+
@echo "Running CI mutation tests for subjects: $(CI_MUTATE_SUBJECTS)"
14+
@env RAILS_ENV=test bundle exec mutant run $(CI_MUTATE_SUBJECTS)
15+
endif
1016

1117
test: ## Run unit tests
1218
@bin/rails tailwindcss:build

0 commit comments

Comments
 (0)