Skip to content

Commit fa0901c

Browse files
committed
Cache API verification results across CI runs
Add a JSON-based cache for GitHub API results (repo existence/rename checks and user/org lookups) that persists across CI runs using GitHub Actions cache. Each cached entry has a 24-hour TTL. On subsequent runs, items verified within the last 24 hours are loaded from cache, avoiding redundant API calls. This is safe because the collections-renames workflow runs hourly to catch any renames. The cache is scoped per matrix test type (collections vs all) and uses the run ID as the cache key with restore-keys fallback to pick up the most recent cache.
1 parent 67774fb commit fa0901c

File tree

3 files changed

+88
-0
lines changed

3 files changed

+88
-0
lines changed

.github/workflows/test.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ jobs:
4747
with:
4848
bundler-cache: true
4949

50+
- name: Restore API cache
51+
if: |
52+
(matrix.test_type == 'collections' && steps.collections.outputs.changed) ||
53+
(matrix.test_type == 'all' && steps.all.outputs.changed)
54+
uses: actions/cache@v4
55+
with:
56+
path: .api-cache.json
57+
key: api-cache-${{ matrix.test_type }}-${{ github.run_id }}
58+
restore-keys: |
59+
api-cache-${{ matrix.test_type }}-
60+
5061
- name: Build and test with Rake
5162
if: |
5263
(matrix.test_type == 'topics' && steps.topics.outputs.changed) ||

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ vendor
88
.bundle
99
.idea
1010
.tool-versions
11+
.api-cache.json

test/test_helper.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,79 @@ def add_message(type, file, line_number, message)
270270
client.messages << "::#{type} file=#{file},line=#{line_number}::#{message}"
271271
end
272272

273+
CACHE_FILE = File.expand_path("../.api-cache.json", File.dirname(__FILE__))
274+
CACHE_TTL_SECONDS = 24 * 60 * 60 # 24 hours
275+
276+
def self.load_api_cache!
277+
return unless File.exist?(CACHE_FILE)
278+
279+
data = JSON.parse(File.read(CACHE_FILE))
280+
now = Time.now.to_i
281+
ttl = CACHE_TTL_SECONDS
282+
283+
if data["repos"]
284+
data["repos"].each do |key, entry|
285+
next if now - entry["cached_at"].to_i > ttl
286+
287+
result = entry["value"]
288+
# Reconstruct a minimal object that responds to .full_name
289+
cached = if result.nil?
290+
nil
291+
else
292+
Struct.new(:full_name).new(result["full_name"])
293+
end
294+
NewOctokit.class_variable_get(:@@repos)[key] = cached
295+
end
296+
end
297+
298+
if data["users"]
299+
data["users"].each do |key, entry|
300+
next if now - entry["cached_at"].to_i > ttl
301+
302+
result = entry["value"]
303+
cached = if result.nil?
304+
nil
305+
else
306+
Struct.new(:login).new(result["login"])
307+
end
308+
NewOctokit.class_variable_get(:@@users)[key] = cached
309+
end
310+
end
311+
rescue JSON::ParserError, StandardError => e
312+
warn "Failed to load API cache: #{e.message}"
313+
end
314+
315+
def self.save_api_cache!
316+
now = Time.now.to_i
317+
repos_data = {}
318+
users_data = {}
319+
320+
NewOctokit.class_variable_get(:@@repos).each do |key, value|
321+
next if key == :skip_requests
322+
323+
repos_data[key.to_s] = {
324+
"cached_at" => now,
325+
"value" => value.nil? ? nil : { "full_name" => value.respond_to?(:full_name) ? value.full_name : value.to_s },
326+
}
327+
end
328+
329+
NewOctokit.class_variable_get(:@@users).each do |key, value|
330+
next if key == :skip_requests
331+
332+
users_data[key.to_s] = {
333+
"cached_at" => now,
334+
"value" => value.nil? ? nil : { "login" => value.respond_to?(:login) ? value.login : value.to_s },
335+
}
336+
end
337+
338+
File.write(CACHE_FILE, JSON.pretty_generate({ "repos" => repos_data, "users" => users_data }))
339+
rescue StandardError => e
340+
warn "Failed to save API cache: #{e.message}"
341+
end
342+
343+
# Load cached API results at startup
344+
load_api_cache!
345+
273346
Minitest.after_run do
274347
warn "Repo checks were rate limited during this CI run" if NewOctokit.repos_skipped?
275348
warn "User checks were rate limited during this CI run" if NewOctokit.users_skipped?
@@ -279,4 +352,7 @@ def add_message(type, file, line_number, message)
279352
NewOctokit.messages.each do |message|
280353
puts message
281354
end
355+
356+
# Persist cache for next CI run
357+
save_api_cache!
282358
end

0 commit comments

Comments
 (0)