-
Notifications
You must be signed in to change notification settings - Fork 600
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CI: Test for the health of all code base URLs #2127
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
# This file is distributed under New Relic's license terms. | ||
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. | ||
# frozen_string_literal: true | ||
|
||
# The unit test class defined herein will verify the health of all URLs found | ||
# in the project source code. | ||
# | ||
# To run the URL health tests by themselves: | ||
# TEST=test/new_relic/healthy_urls_test bundle exec rake test | ||
# | ||
# A file will be scanned for URLs if the file's basename is found in the | ||
# FILENAMES array OR the file's extension is found in the EXTENSIONS array | ||
# unless the file's absolute path matches the IGNORED_FILE_PATTERN regex. | ||
# | ||
# NOTE that CHANGELOG.md is handled with special logic so that only the most | ||
# recent 2 versions mentioned in the changelog are scannned for URLs. | ||
# | ||
# See TIMEOUT for the number of seconds permitted for a GET request to a given | ||
# URL to be completed. | ||
# | ||
# Enable DEBUG for additional verbosity | ||
|
||
require_relative '../test_helper' | ||
|
||
class HealthyUrlsTest < Minitest::Test | ||
ROOT = File.expand_path('../../..', __FILE__).freeze | ||
FILENAMES = %w[ | ||
baselines | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think I'm familiar with this file. Where is it and what does it do? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's at |
||
Brewfile | ||
Capfile | ||
Dockerfile | ||
Envfile | ||
Gemfile | ||
Guardfile | ||
install_mysql55 | ||
LICENSE | ||
mega-runner | ||
newrelic | ||
newrelic_cmd | ||
nrdebug | ||
Comment on lines
+38
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, are these executables as well as files? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, these ones are really just Ruby scripts so they're eligible for having URLs in the source code. For now I went with an allowlist approach of filenames and extensions for specifying what to scan for URLs. In the future we may want to reverse this and have a blocklist of things like image files or perhaps even take a quick peek at the file's encoding and make a judgment call from that. |
||
Rakefile | ||
run_tests | ||
runner | ||
Thorfile | ||
].freeze | ||
EXTENSIONS = %w[ | ||
css | ||
erb | ||
gemspec | ||
haml | ||
html | ||
js | ||
json | ||
md | ||
proto | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make changes to the proto files? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! Not only can we, but we have, and with a URL in fact! There is currently only 1 file at |
||
rake | ||
readme | ||
rb | ||
sh | ||
txt | ||
thor | ||
tt | ||
yml | ||
].freeze | ||
FILE_PATTERN = /(?:^(?:#{FILENAMES.join('|')})$)|\.(?:#{EXTENSIONS.join('|')})$/.freeze | ||
IGNORED_FILE_PATTERN = %r{/(?:coverage|test)/}.freeze | ||
URL_PATTERN = %r{(https?://.*?)[^a-zA-Z0-9/\.\-_#]}.freeze | ||
IGNORED_URL_PATTERN = %r{(?:\{|\(|\$|169\.254|\.\.\.|metadata\.google)} | ||
TIMEOUT = 5 | ||
DEBUG = false | ||
|
||
def test_all_urls | ||
skip_unless_ci_cron | ||
skip_unless_newest_ruby | ||
load_httparty | ||
|
||
urls = gather_urls | ||
errors = urls.each_with_object({}) do |(url, _files), hash| | ||
error = verify_url(url) | ||
hash[url] = error if error | ||
end | ||
|
||
msg = "#{errors.keys.size} URLs were unreachable!\n\n" | ||
msg += errors.map { |url, error| " #{url} - #{error}\n files: #{urls[url].join(',')}" }.join("\n") | ||
|
||
assert_empty errors, msg | ||
end | ||
|
||
private | ||
|
||
def load_httparty | ||
require 'httparty' | ||
rescue | ||
skip 'Skipping URL health tests in this context, as HTTParty is not available' | ||
end | ||
|
||
def real_url?(url) | ||
return false if url.match?(IGNORED_URL_PATTERN) | ||
|
||
true | ||
end | ||
|
||
def gather_urls | ||
Dir.glob(File.join(ROOT, '**', '*')).each_with_object({}) do |file, urls| | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: Can default the Example from docs: https://rubyapi.org/3.2/o/hash Change we can do: Which then would mean that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, @olleolleolle! Implemented with #2132. Using LETTERS = ('a'..'z').to_a
hash1 = LETTERS.each_with_object(Hash.new { |h, k| h[k] = [] }) { |l, h| h[l] << true }
hash2 = LETTERS.each_with_object(Hash.new([])) { |l, h| h[l] += [true] }
hash1 == hash2 |
||
next unless File.file?(file) && File.basename(file).match?(FILE_PATTERN) && !file.match?(IGNORED_FILE_PATTERN) | ||
|
||
changelog_entries_seen = 0 | ||
File.open(file).each do |line| | ||
changelog_entries_seen += 1 if File.basename(file).eql?('CHANGELOG.md') && line.start_with?('##') | ||
break if changelog_entries_seen > 2 | ||
next unless line =~ URL_PATTERN | ||
|
||
url = Regexp.last_match(1).sub(%r{(?:/|\.)$}, '') | ||
if real_url?(url) | ||
urls[url] ||= [] | ||
urls[url] << file | ||
end | ||
end | ||
end | ||
end | ||
|
||
def verify_url(url) | ||
puts "Testing '#{url}'..." if DEBUG | ||
res = HTTParty.get(url, timeout: TIMEOUT) | ||
if res.success? | ||
puts ' OK.' if DEBUG | ||
return | ||
end | ||
|
||
msg = "HTTP #{res.code}: #{res.message}" | ||
puts " FAILED. #{msg}" if DEBUG | ||
msg | ||
rescue StandardError => e | ||
msg = "#{e.class}: #{e.message}" | ||
puts " FAILED. #{msg}" if DEBUG | ||
msg | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't know he wrote memcache-client!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'd be interested in knowing more about the history there. I think that repo is a fork of another fork of the original RubyForge code (which Mike also has commit access to). For whatever reason, it's what's linked to from the Rubygems.org page's "Homepage" link, so I'm using it here.