-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from DataDog/lloeki/add-rake-test-task
Add rake test task
- Loading branch information
Showing
6 changed files
with
327 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
/.envrc | ||
/vendor/bundle | ||
/Gemfile.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
source 'https://rubygems.org' | ||
|
||
gem 'minitest' | ||
gem 'rake' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# frozen_string_literal: true | ||
|
||
# @type self: Rake::TaskLib | ||
|
||
# load rake tasks from tasks directory | ||
Dir.glob(File.join(__dir__ || Dir.pwd, "tasks", "**", "*.rake")) { |f| import f } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# frozen_string_literal: true | ||
|
||
# @type self: Rake::DSL | ||
|
||
begin | ||
require "minitest/test_task" | ||
rescue LoadError | ||
# Backport "minitest/test_task" for minitest 5.15.0 | ||
require_relative "../vendor/minitest/test_task" | ||
end | ||
|
||
Minitest::TestTask.create(:test) do |t| | ||
t.warning = false | ||
t.test_globs = ["test/**/test_*.rb"] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,301 @@ | ||
require "shellwords" | ||
require "rbconfig" | ||
require "rake/tasklib" | ||
|
||
module Minitest # :nodoc: | ||
|
||
## | ||
# Minitest::TestTask is a rake helper that generates several rake | ||
# tasks under the main test task's name-space. | ||
# | ||
# task <name> :: the main test task | ||
# task <name>:cmd :: prints the command to use | ||
# task <name>:deps :: runs each test file by itself to find dependency errors | ||
# task <name>:slow :: runs the tests and reports the slowest 25 tests. | ||
# | ||
# Examples: | ||
# | ||
# Minitest::TestTask.create | ||
# | ||
# The most basic and default setup. | ||
# | ||
# Minitest::TestTask.create :my_tests | ||
# | ||
# The most basic/default setup, but with a custom name | ||
# | ||
# Minitest::TestTask.create :unit do |t| | ||
# t.test_globs = ["test/unit/**/*_test.rb"] | ||
# t.warning = false | ||
# end | ||
# | ||
# Customize the name and only run unit tests. | ||
# | ||
# NOTE: To hook this task up to the default, make it a dependency: | ||
# | ||
# task default: :unit | ||
|
||
class TestTask < Rake::TaskLib | ||
WINDOWS = RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ # :nodoc: | ||
|
||
## | ||
# Create several test-oriented tasks under +name+. Takes an | ||
# optional block to customize variables. | ||
|
||
def self.create name = :test, &block | ||
task = new name | ||
task.instance_eval(&block) if block | ||
task.process_env | ||
task.define | ||
task | ||
end | ||
|
||
## | ||
# Extra arguments to pass to the tests. Defaults empty but gets | ||
# populated by a number of enviroment variables: | ||
# | ||
# N (-n flag) :: a string or regexp of tests to run. | ||
# X (-e flag) :: a string or regexp of tests to exclude. | ||
# A (arg) :: quick way to inject an arbitrary argument (eg A=--help). | ||
# | ||
# See #process_env | ||
|
||
attr_accessor :extra_args | ||
|
||
## | ||
# The code to load the framework. Defaults to requiring | ||
# minitest/autorun... | ||
# | ||
# Why do I have this as an option? | ||
|
||
attr_accessor :framework | ||
|
||
## | ||
# Extra library directories to include. Defaults to %w[lib test | ||
# .]. Also uses $MT_LIB_EXTRAS allowing you to dynamically | ||
# override/inject directories for custom runs. | ||
|
||
attr_accessor :libs | ||
|
||
## | ||
# The name of the task and base name for the other tasks generated. | ||
|
||
attr_accessor :name | ||
|
||
## | ||
# File globs to find test files. Defaults to something sensible to | ||
# find test files under the test directory. | ||
|
||
attr_accessor :test_globs | ||
|
||
## | ||
# Turn on ruby warnings (-w flag). Defaults to true. | ||
|
||
attr_accessor :warning | ||
|
||
## | ||
# Optional: Additional ruby to run before the test framework is loaded. | ||
|
||
attr_accessor :test_prelude | ||
|
||
## | ||
# Print out commands as they run. Defaults to Rake's +trace+ (-t | ||
# flag) option. | ||
|
||
attr_accessor :verbose | ||
|
||
## | ||
# Use TestTask.create instead. | ||
|
||
def initialize name = :test # :nodoc: | ||
self.extra_args = [] | ||
self.framework = %(require "minitest/autorun") | ||
self.libs = %w[lib test .] | ||
self.name = name | ||
self.test_globs = ["test/**/test_*.rb", | ||
"test/**/*_test.rb"] | ||
self.test_prelude = nil | ||
self.verbose = Rake.application.options.trace | ||
self.warning = true | ||
end | ||
|
||
## | ||
# Extract variables from the environment and convert them to | ||
# command line arguments. See #extra_args. | ||
# | ||
# Environment Variables: | ||
# | ||
# MT_LIB_EXTRAS :: Extra libs to dynamically override/inject for custom runs. | ||
# N :: Tests to run (string or /regexp/). | ||
# X :: Tests to exclude (string or /regexp/). | ||
# A :: Any extra arguments. Honors shell quoting. | ||
# | ||
# Deprecated: | ||
# | ||
# TESTOPTS :: For argument passing, use +A+. | ||
# N :: For parallel testing, use +MT_CPU+. | ||
# FILTER :: Same as +TESTOPTS+. | ||
|
||
def process_env | ||
warn "TESTOPTS is deprecated in Minitest::TestTask. Use A instead" if | ||
ENV["TESTOPTS"] | ||
warn "FILTER is deprecated in Minitest::TestTask. Use A instead" if | ||
ENV["FILTER"] | ||
warn "N is deprecated in Minitest::TestTask. Use MT_CPU instead" if | ||
ENV["N"] && ENV["N"].to_i > 0 | ||
|
||
lib_extras = (ENV["MT_LIB_EXTRAS"] || "").split File::PATH_SEPARATOR | ||
self.libs[0,0] = lib_extras | ||
|
||
extra_args << "-n" << ENV["N"] if ENV["N"] | ||
extra_args << "-e" << ENV["X"] if ENV["X"] | ||
extra_args.concat Shellwords.split(ENV["TESTOPTS"]) if ENV["TESTOPTS"] | ||
extra_args.concat Shellwords.split(ENV["FILTER"]) if ENV["FILTER"] | ||
extra_args.concat Shellwords.split(ENV["A"]) if ENV["A"] | ||
|
||
ENV.delete "N" if ENV["N"] | ||
|
||
# TODO? RUBY_DEBUG = ENV["RUBY_DEBUG"] | ||
# TODO? ENV["RUBY_FLAGS"] | ||
|
||
extra_args.compact! | ||
end | ||
|
||
def define # :nodoc: | ||
desc "Run the test suite. Use N, X, A, and TESTOPTS to add flags/args." | ||
task name do | ||
ruby make_test_cmd, verbose:verbose | ||
end | ||
|
||
desc "Print out the test command. Good for profiling and other tools." | ||
task "#{name}:cmd" do | ||
puts "ruby #{make_test_cmd}" | ||
end | ||
|
||
desc "Show which test files fail when run in isolation." | ||
task "#{name}:isolated" do | ||
tests = Dir[*self.test_globs].uniq | ||
|
||
# 3 seems to be the magic number... (tho not by that much) | ||
bad, good, n = {}, [], (ENV.delete("K") || 3).to_i | ||
file = ENV.delete("F") | ||
times = {} | ||
|
||
tt0 = Time.now | ||
|
||
n.threads_do tests.sort do |path| | ||
t0 = Time.now | ||
output = `#{Gem.ruby} #{make_test_cmd path} 2>&1` | ||
t1 = Time.now - t0 | ||
|
||
times[path] = t1 | ||
|
||
if $?.success? | ||
$stderr.print "." | ||
good << path | ||
else | ||
$stderr.print "x" | ||
bad[path] = output | ||
end | ||
end | ||
|
||
puts "done" | ||
puts "Ran in %.2f seconds" % [ Time.now - tt0 ] | ||
|
||
if file then | ||
require "json" | ||
File.open file, "w" do |io| | ||
io.puts JSON.pretty_generate times | ||
end | ||
end | ||
|
||
unless good.empty? | ||
puts | ||
puts "# Good tests:" | ||
puts | ||
good.sort.each do |path| | ||
puts "%.2fs: %s" % [times[path], path] | ||
end | ||
end | ||
|
||
unless bad.empty? | ||
puts | ||
puts "# Bad tests:" | ||
puts | ||
bad.keys.sort.each do |path| | ||
puts "%.2fs: %s" % [times[path], path] | ||
end | ||
puts | ||
puts "# Bad Test Output:" | ||
puts | ||
bad.sort.each do |path, output| | ||
puts | ||
puts "# #{path}:" | ||
puts output | ||
end | ||
exit 1 | ||
end | ||
end | ||
|
||
task "#{name}:deps" => "#{name}:isolated" # now just an alias | ||
|
||
desc "Show bottom 25 tests wrt time." | ||
task "#{name}:slow" do | ||
sh ["rake #{name} A=-v", | ||
"egrep '#test_.* s = .'", | ||
"sort -n -k2 -t=", | ||
"tail -25"].join " | " | ||
end | ||
end | ||
|
||
## | ||
# Generate the test command-line. | ||
|
||
def make_test_cmd globs = test_globs | ||
tests = [] | ||
tests.concat Dir[*globs].sort.shuffle # TODO: SEED -> srand first? | ||
tests.map! { |f| %(require "#{f}") } | ||
|
||
runner = [] | ||
runner << test_prelude if test_prelude | ||
runner << framework | ||
runner.concat tests | ||
runner = runner.join "; " | ||
|
||
args = [] | ||
args << "-I#{libs.join(File::PATH_SEPARATOR)}" unless libs.empty? | ||
args << "-w" if warning | ||
args << '-e' | ||
args << "'#{runner}'" | ||
args << '--' | ||
args << extra_args.map(&:shellescape) | ||
|
||
args.join " " | ||
end | ||
end | ||
end | ||
|
||
class Work < Queue # :nodoc: | ||
def initialize jobs = [] # :nodoc: | ||
super() | ||
|
||
jobs.each do |job| | ||
self << job | ||
end | ||
|
||
close | ||
end | ||
end | ||
|
||
class Integer # :nodoc: | ||
def threads_do(jobs) # :nodoc: | ||
q = Work.new jobs | ||
|
||
self.times.map { | ||
Thread.new do | ||
while job = q.pop # go until quit value | ||
yield job | ||
end | ||
end | ||
}.each(&:join) | ||
end | ||
end |