Skip to content

Commit dc8bcb5

Browse files
committed
Harden CI and test helpers for ES version portability
Preparing for Elasticsearch 9.x support exposed several areas where the test infrastructure was tightly coupled to a specific ES version or relied on brittle assumptions. - Promote Elasticsearch to a proper GH Actions service with a built-in health check using the native wait_for_status=yellow parameter, replacing the fragile `sleep 15` / shell-loop approach. GH Actions won't start steps until all services are healthy. - Switch Docker image to docker.elastic.co registry and parameterize the version via ES_VERSION env var. Docker Hub stopped reliably publishing ES images after 8.x; the official Elastic registry is the recommended source going forward. - Rename job from `ruby-3` to `test` — the old name was misleading now that Ruby 4.0 is in the matrix. - Add job-level timeout-minutes (15 for tests, 10 for rubocop) to prevent stuck runs from burning runner minutes until the 6-hour GH default. - Add concurrency group to cancel in-progress CI runs when a PR is updated, avoiding redundant work on rapid pushes. - Switch rubocop output from `--format simple` to `--format github` so offenses appear as inline annotations on PR diffs. - Fix drop_indices to use `format: 'json'` instead of parsing the text-format cat.indices response by column position. The column order is not guaranteed across ES versions, making the previous approach fragile for any future upgrade. - Add canary specs that verify Elastic::Transport error hierarchy, Elasticsearch::API.serializer, and Client constructor remain compatible. These act as early-warning tripwires — if a future ES client upgrade silently changes these APIs, these specs will fail with a clear message rather than causing subtle runtime bugs.
1 parent 8aef6af commit dc8bcb5

5 files changed

Lines changed: 60 additions & 11 deletions

File tree

.github/workflows/ruby.yml

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ on:
1111
reopened # PR was reopened
1212
]
1313

14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
1418
jobs:
15-
ruby-3:
19+
test:
1620
runs-on: ubuntu-latest
21+
timeout-minutes: 15
1722
strategy:
1823
fail-fast: false
1924
matrix:
@@ -32,30 +37,41 @@ jobs:
3237
- '6379:6379'
3338
# Set health checks to wait until redis has started
3439
options: >-
35-
--health-cmd "redis-cli ping"
40+
--health-cmd 'redis-cli ping'
3641
--health-interval 10s
3742
--health-timeout 5s
3843
--health-retries 5
44+
elasticsearch:
45+
image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
46+
ports:
47+
- '9250:9200'
48+
env:
49+
discovery.type: single-node
50+
xpack.security.enabled: 'false'
51+
ES_JAVA_OPTS: '-Xms500m -Xmx500m'
52+
# Set health checks to wait until Elasticsearch has started
53+
options: >-
54+
--health-cmd 'curl -sf http://localhost:9200/_cluster/health?wait_for_status=yellow&timeout=5s'
55+
--health-interval 10s
56+
--health-timeout 5s
57+
--health-retries 10
58+
3959
steps:
4060
- uses: actions/checkout@v6
4161
- uses: ruby/setup-ruby@v1
4262
with:
4363
ruby-version: ${{ matrix.ruby }}
4464
bundler-cache: true
45-
- name: Start containers
46-
run: |
47-
docker compose up elasticsearch_test -d
48-
sleep 15
49-
5065
- name: Tests
5166
run: bundle exec rspec
5267

5368
rubocop:
5469
runs-on: ubuntu-latest
70+
timeout-minutes: 10
5571
steps:
5672
- uses: actions/checkout@v6
5773
- uses: ruby/setup-ruby@v1
5874
with:
5975
ruby-version: 3.4
6076
bundler-cache: true
61-
- run: bundle exec rubocop --format simple
77+
- run: bundle exec rubocop --format github

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 8.0.2 (unreleased)
4+
5+
### Changes
6+
7+
* [#1008](https://github.com/toptal/chewy/pull/1008): Harden CI and test helpers for ES version portability. ([@mattmenefee][])
8+
39
## 8.0.1 (2026-03-12)
410

511
### New Features
@@ -874,6 +880,7 @@
874880
[@marshall]: https://github.com/marshall
875881
[@matchbookmac]: https://github.com/matchbookmac
876882
[@matthee]: https://github.com/matthee
883+
[@mattmenefee]: https://github.com/mattmenefee
877884
[@mattzollinhofer]: https://github.com/mattzollinhofer
878885
[@menglewis]: https://github.com/menglewis
879886
[@mikeyhogarth]: https://github.com/mikeyhogarth

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
elasticsearch_test:
3-
image: "elasticsearch:8.15.0"
3+
image: "docker.elastic.co/elasticsearch/elasticsearch:${ES_VERSION:-8.15.0}"
44
environment:
55
- bootstrap.memory_lock=${ES_MEMORY_LOCK:-false}
66
- "ES_JAVA_OPTS=-Xms${TEST_ES_HEAP_SIZE:-500m} -Xmx${TEST_ES_HEAP_SIZE:-500m}"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require 'spec_helper'
2+
3+
describe 'Elasticsearch compatibility' do
4+
describe 'Elastic::Transport error hierarchy' do
5+
specify 'NotFound is defined' do
6+
expect(Elastic::Transport::Transport::Errors::NotFound).to be < StandardError
7+
end
8+
9+
specify 'BadRequest is defined' do
10+
expect(Elastic::Transport::Transport::Errors::BadRequest).to be < StandardError
11+
end
12+
end
13+
14+
describe 'Elasticsearch::API.serializer' do
15+
specify 'responds to dump' do
16+
expect(Elasticsearch::API.serializer).to respond_to(:dump)
17+
end
18+
end
19+
20+
describe 'Elasticsearch::Client constructor' do
21+
specify 'accepts host configuration' do
22+
client = Elasticsearch::Client.new(host: 'localhost:9200')
23+
expect(client).to be_a(Elasticsearch::Client)
24+
end
25+
end
26+
end

spec/spec_helper.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545

4646
# Low-level substitute for now-obsolete drop_indices
4747
def drop_indices
48-
response = Chewy.client.cat.indices
49-
indices = response.body.lines.map { |line| line.split[2] }
48+
response = Chewy.client.cat.indices(format: 'json')
49+
indices = response.body.map { |entry| entry['index'] }
5050
return if indices.blank?
5151

5252
Chewy.client.indices.delete(index: indices)

0 commit comments

Comments
 (0)