diff options
author | eregon <eregon@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2017-05-07 12:04:49 +0000 |
---|---|---|
committer | eregon <eregon@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2017-05-07 12:04:49 +0000 |
commit | 95e8c48dd3348503a8c7db5d0498894a1b676395 (patch) | |
tree | 9eef7f720314ebaff56845a74e203770e62284e4 /spec/mspec/lib/mspec/commands | |
parent | ed7d803500de38186c74bce94d233e85ef51e503 (diff) |
Add in-tree mspec and ruby/spec
* For easier modifications of ruby/spec by MRI developers.
* .gitignore: track changes under spec.
* spec/mspec, spec/rubyspec: add in-tree mspec and ruby/spec.
These files can therefore be updated like any other file in MRI.
Instructions are provided in spec/README.
[Feature #13156] [ruby-core:79246]
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@58595 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'spec/mspec/lib/mspec/commands')
-rwxr-xr-x | spec/mspec/lib/mspec/commands/mkspec.rb | 155 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/commands/mspec-ci.rb | 79 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/commands/mspec-run.rb | 87 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/commands/mspec-tag.rb | 133 | ||||
-rwxr-xr-x | spec/mspec/lib/mspec/commands/mspec.rb | 163 |
5 files changed, 617 insertions, 0 deletions
diff --git a/spec/mspec/lib/mspec/commands/mkspec.rb b/spec/mspec/lib/mspec/commands/mkspec.rb new file mode 100755 index 0000000000..7a943aa1fe --- /dev/null +++ b/spec/mspec/lib/mspec/commands/mkspec.rb @@ -0,0 +1,155 @@ +#!/usr/bin/env ruby + +require 'rbconfig' +require 'mspec/version' +require 'mspec/utils/options' +require 'mspec/utils/name_map' +require 'mspec/helpers/fs' + +class MkSpec + attr_reader :config + + def initialize + @config = { + :constants => [], + :requires => [], + :base => "core", + :version => nil + } + @map = NameMap.new true + end + + def options(argv=ARGV) + options = MSpecOptions.new "mkspec [options]", 32 + + options.on("-c", "--constant", "CONSTANT", + "Class or Module to generate spec stubs for") do |name| + config[:constants] << name + end + options.on("-b", "--base", "DIR", + "Directory to generate specs into") do |directory| + config[:base] = File.expand_path directory + end + options.on("-r", "--require", "LIBRARY", + "A library to require") do |file| + config[:requires] << file + end + options.on("-V", "--version-guard", "VERSION", + "Specify version for ruby_version_is guards") do |version| + config[:version] = version + end + options.version MSpec::VERSION + options.help + + options.doc "\n How might this work in the real world?\n" + options.doc " 1. To create spec stubs for every class or module in Object\n" + options.doc " $ mkspec\n" + options.doc " 2. To create spec stubs for Fixnum\n" + options.doc " $ mkspec -c Fixnum\n" + options.doc " 3. To create spec stubs for Complex in 'superspec/complex'\n" + options.doc " $ mkspec -c Complex -r complex -b superspec" + options.doc "" + + options.parse argv + end + + def create_directory(mod) + subdir = @map.dir_name mod, config[:base] + + if File.exist? subdir + unless File.directory? subdir + puts "#{subdir} already exists and is not a directory." + return nil + end + else + mkdir_p subdir + end + + subdir + end + + def write_requires(dir, file) + prefix = config[:base] + '/' + raise dir unless dir.start_with? prefix + sub = dir[prefix.size..-1] + parents = '../' * (sub.split('/').length + 1) + + File.open(file, 'w') do |f| + f.puts "require File.expand_path('../#{parents}spec_helper', __FILE__)" + config[:requires].each do |lib| + f.puts "require '#{lib}'" + end + end + end + + def write_version(f) + f.puts "" + if version = config[:version] + f.puts "ruby_version_is #{version} do" + yield " " + f.puts "end" + else + yield "" + end + end + + def write_spec(file, meth, exists) + if exists + out = `#{ruby} #{MSPEC_HOME}/bin/mspec-run --dry-run --unguarded -fs -e '#{meth}' #{file}` + return if out.include?(meth) + end + + File.open file, 'a' do |f| + write_version(f) do |indent| + f.puts <<-EOS +#{indent}describe "#{meth}" do +#{indent} it "needs to be reviewed for spec completeness" +#{indent}end +EOS + end + end + + puts file + end + + def create_file(dir, mod, meth, name) + file = File.join dir, @map.file_name(meth, mod) + exists = File.exist? file + + write_requires dir, file unless exists + write_spec file, name, exists + end + + def run + config[:requires].each { |lib| require lib } + constants = config[:constants] + constants = Object.constants if constants.empty? + + @map.map({}, constants).each do |mod, methods| + name = mod.chop + next unless dir = create_directory(name) + + methods.each { |method| create_file dir, name, method, mod + method } + end + end + + ## + # Determine and return the path of the ruby executable. + + def ruby + ruby = File.join(RbConfig::CONFIG['bindir'], + RbConfig::CONFIG['ruby_install_name']) + + ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR + + return ruby + end + + def self.main + ENV['MSPEC_RUNNER'] = '1' + + script = new + script.options + script.run + end +end diff --git a/spec/mspec/lib/mspec/commands/mspec-ci.rb b/spec/mspec/lib/mspec/commands/mspec-ci.rb new file mode 100644 index 0000000000..225d2bb96d --- /dev/null +++ b/spec/mspec/lib/mspec/commands/mspec-ci.rb @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') + +require 'mspec/version' +require 'mspec/utils/options' +require 'mspec/utils/script' + + +class MSpecCI < MSpecScript + def options(argv=ARGV) + options = MSpecOptions.new "mspec ci [options] (FILE|DIRECTORY|GLOB)+", 30, config + + options.doc " Ask yourself:" + options.doc " 1. How to run the specs?" + options.doc " 2. How to modify the guard behavior?" + options.doc " 2. How to display the output?" + options.doc " 3. What action to perform?" + options.doc " 4. When to perform it?" + + options.doc "\n How to run the specs" + options.chdir + options.prefix + options.configure { |f| load f } + options.name + options.pretend + options.interrupt + + options.doc "\n How to modify the guard behavior" + options.unguarded + options.verify + + options.doc "\n How to display their output" + options.formatters + options.verbose + + options.doc "\n What action to perform" + options.actions + + options.doc "\n When to perform it" + options.action_filters + + options.doc "\n Help!" + options.debug + options.version MSpec::VERSION + options.help + + options.doc "\n Custom options" + custom_options options + + options.doc "\n How might this work in the real world?" + options.doc "\n 1. To simply run the known good specs" + options.doc "\n $ mspec ci" + options.doc "\n 2. To run a subset of the known good specs" + options.doc "\n $ mspec ci path/to/specs" + options.doc "\n 3. To start the debugger before the spec matching 'this crashes'" + options.doc "\n $ mspec ci --spec-debug -S 'this crashes'" + options.doc "" + + patterns = options.parse argv + patterns = config[:ci_files] if patterns.empty? + @files = files patterns + end + + def run + MSpec.register_tags_patterns config[:tags_patterns] + MSpec.register_files @files + + tags = ["fails", "critical", "unstable", "incomplete", "unsupported"] + tags += Array(config[:ci_xtags]) + + require 'mspec/runner/filters/tag' + filter = TagFilter.new(:exclude, *tags) + filter.register + + MSpec.process + exit MSpec.exit_code + end +end diff --git a/spec/mspec/lib/mspec/commands/mspec-run.rb b/spec/mspec/lib/mspec/commands/mspec-run.rb new file mode 100644 index 0000000000..45b26e88ad --- /dev/null +++ b/spec/mspec/lib/mspec/commands/mspec-run.rb @@ -0,0 +1,87 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') + +require 'mspec/version' +require 'mspec/utils/options' +require 'mspec/utils/script' + + +class MSpecRun < MSpecScript + def initialize + super + + config[:files] = [] + end + + def options(argv=ARGV) + options = MSpecOptions.new "mspec run [options] (FILE|DIRECTORY|GLOB)+", 30, config + + options.doc " Ask yourself:" + options.doc " 1. What specs to run?" + options.doc " 2. How to modify the execution?" + options.doc " 3. How to modify the guard behavior?" + options.doc " 4. How to display the output?" + options.doc " 5. What action to perform?" + options.doc " 6. When to perform it?" + + options.doc "\n What specs to run" + options.filters + + options.doc "\n How to modify the execution" + options.chdir + options.prefix + options.configure { |f| load f } + options.name + options.randomize + options.repeat + options.pretend + options.interrupt + + options.doc "\n How to modify the guard behavior" + options.unguarded + options.verify + + options.doc "\n How to display their output" + options.formatters + options.verbose + + options.doc "\n What action to perform" + options.actions + + options.doc "\n When to perform it" + options.action_filters + + options.doc "\n Help!" + options.debug + options.version MSpec::VERSION + options.help + + options.doc "\n Custom options" + custom_options options + + options.doc "\n How might this work in the real world?" + options.doc "\n 1. To simply run some specs" + options.doc "\n $ mspec path/to/the/specs" + options.doc " mspec path/to/the_file_spec.rb" + options.doc "\n 2. To run specs tagged with 'fails'" + options.doc "\n $ mspec -g fails path/to/the_file_spec.rb" + options.doc "\n 3. To start the debugger before the spec matching 'this crashes'" + options.doc "\n $ mspec --spec-debug -S 'this crashes' path/to/the_file_spec.rb" + options.doc "\n 4. To run some specs matching 'this crashes'" + options.doc "\n $ mspec -e 'this crashes' path/to/the_file_spec.rb" + + options.doc "" + + patterns = options.parse argv + @files = files_from_patterns(patterns) + end + + def run + MSpec.register_tags_patterns config[:tags_patterns] + MSpec.register_files @files + + MSpec.process + exit MSpec.exit_code + end +end diff --git a/spec/mspec/lib/mspec/commands/mspec-tag.rb b/spec/mspec/lib/mspec/commands/mspec-tag.rb new file mode 100644 index 0000000000..7582015916 --- /dev/null +++ b/spec/mspec/lib/mspec/commands/mspec-tag.rb @@ -0,0 +1,133 @@ +#!/usr/bin/env ruby + +require 'mspec/version' +require 'mspec/utils/options' +require 'mspec/utils/script' + + +class MSpecTag < MSpecScript + def initialize + super + + config[:tagger] = :add + config[:tag] = 'fails:' + config[:outcome] = :fail + config[:ltags] = [] + end + + def options(argv=ARGV) + options = MSpecOptions.new "mspec tag [options] (FILE|DIRECTORY|GLOB)+", 30, config + + options.doc " Ask yourself:" + options.doc " 1. What specs to run?" + options.doc " 2. How to modify the execution?" + options.doc " 3. How to display the output?" + options.doc " 4. What tag action to perform?" + options.doc " 5. When to perform it?" + + options.doc "\n What specs to run" + options.filters + + options.doc "\n How to modify the execution" + options.configure { |f| load f } + options.name + options.pretend + options.unguarded + options.interrupt + + options.doc "\n How to display their output" + options.formatters + options.verbose + + options.doc "\n What action to perform and when to perform it" + options.on("-N", "--add", "TAG", + "Add TAG with format 'tag' or 'tag(comment)' (see -Q, -F, -L)") do |o| + config[:tagger] = :add + config[:tag] = "#{o}:" + end + options.on("-R", "--del", "TAG", + "Delete TAG (see -Q, -F, -L)") do |o| + config[:tagger] = :del + config[:tag] = "#{o}:" + config[:outcome] = :pass + end + options.on("-Q", "--pass", "Apply action to specs that pass (default for --del)") do + config[:outcome] = :pass + end + options.on("-F", "--fail", "Apply action to specs that fail (default for --add)") do + config[:outcome] = :fail + end + options.on("-L", "--all", "Apply action to all specs") do + config[:outcome] = :all + end + options.on("--list", "TAG", "Display descriptions of any specs tagged with TAG") do |t| + config[:tagger] = :list + config[:ltags] << t + end + options.on("--list-all", "Display descriptions of any tagged specs") do + config[:tagger] = :list_all + end + options.on("--purge", "Remove all tags not matching any specs") do + config[:tagger] = :purge + end + + options.doc "\n Help!" + options.debug + options.version MSpec::VERSION + options.help + + options.doc "\n Custom options" + custom_options options + + options.doc "\n How might this work in the real world?" + options.doc "\n 1. To add the 'fails' tag to failing specs" + options.doc "\n $ mspec tag path/to/the_file_spec.rb" + options.doc "\n 2. To remove the 'fails' tag from passing specs" + options.doc "\n $ mspec tag --del fails path/to/the_file_spec.rb" + options.doc "\n 3. To display the descriptions for all specs tagged with 'fails'" + options.doc "\n $ mspec tag --list fails path/to/the/specs" + options.doc "" + + patterns = options.parse argv + if patterns.empty? + puts options + puts "No files specified." + exit 1 + end + @files = files patterns + end + + def register + require 'mspec/runner/actions' + + case config[:tagger] + when :add, :del + tag = SpecTag.new config[:tag] + tagger = TagAction.new(config[:tagger], config[:outcome], tag.tag, tag.comment, + config[:atags], config[:astrings]) + when :list, :list_all + tagger = TagListAction.new config[:tagger] == :list_all ? nil : config[:ltags] + MSpec.register_mode :pretend + config[:formatter] = false + when :purge + tagger = TagPurgeAction.new + MSpec.register_mode :pretend + MSpec.register_mode :unguarded + config[:formatter] = false + else + raise ArgumentError, "No recognized action given" + end + tagger.register + + super + end + + def run + MSpec.register_tags_patterns config[:tags_patterns] + MSpec.register_files @files + + MSpec.process + exit MSpec.exit_code + end +end + diff --git a/spec/mspec/lib/mspec/commands/mspec.rb b/spec/mspec/lib/mspec/commands/mspec.rb new file mode 100755 index 0000000000..6f1ae8cb6e --- /dev/null +++ b/spec/mspec/lib/mspec/commands/mspec.rb @@ -0,0 +1,163 @@ +#!/usr/bin/env ruby + +require 'mspec/version' +require 'mspec/utils/options' +require 'mspec/utils/script' +require 'mspec/helpers/tmp' +require 'mspec/runner/actions/filter' +require 'mspec/runner/actions/timer' + + +class MSpecMain < MSpecScript + def initialize + super + + config[:loadpath] = [] + config[:requires] = [] + config[:target] = ENV['RUBY'] || 'ruby' + config[:flags] = [] + config[:command] = nil + config[:options] = [] + config[:launch] = [] + end + + def options(argv=ARGV) + config[:command] = argv.shift if ["ci", "run", "tag"].include?(argv[0]) + + options = MSpecOptions.new "mspec [COMMAND] [options] (FILE|DIRECTORY|GLOB)+", 30, config + + options.doc " The mspec command sets up and invokes the sub-commands" + options.doc " (see below) to enable, for instance, running the specs" + options.doc " with different implementations like ruby, jruby, rbx, etc.\n" + + options.configure do |f| + load f + config[:options] << '-B' << f + end + + options.targets + + options.on("--warnings", "Don't supress warnings") do + config[:flags] << '-w' + ENV['OUTPUT_WARNINGS'] = '1' + end + + options.on("-j", "--multi", "Run multiple (possibly parallel) subprocesses") do + config[:multi] = true + config[:options] << "-fy" + end + + options.version MSpec::VERSION do + if config[:command] + config[:options] << "-v" + else + puts "#{File.basename $0} #{MSpec::VERSION}" + exit + end + end + + options.help do + if config[:command] + config[:options] << "-h" + else + puts options + exit 1 + end + end + + options.doc "\n Custom options" + custom_options options + + # The rest of the help output + options.doc "\n where COMMAND is one of:\n" + options.doc " run - Run the specified specs (default)" + options.doc " ci - Run the known good specs" + options.doc " tag - Add or remove tags\n" + options.doc " mspec COMMAND -h for more options\n" + options.doc " example: $ mspec run -h\n" + + options.on_extra { |o| config[:options] << o } + options.parse(argv) + + if config[:multi] + options = MSpecOptions.new "mspec", 30, config + options.all + patterns = options.parse(config[:options]) + @files = files_from_patterns(patterns) + end + end + + def register; end + + def multi_exec(argv) + MSpec.register_files @files + + require 'mspec/runner/formatters/multi' + formatter = MultiFormatter.new + + output_files = [] + processes = [cores, @files.size].min + children = processes.times.map { |i| + name = tmp "mspec-multi-#{i}" + output_files << name + + env = { + "SPEC_TEMP_DIR" => "rubyspec_temp_#{i}", + "MSPEC_MULTI" => i.to_s + } + command = argv + ["-o", name] + $stderr.puts "$ #{command.join(' ')}" if $MSPEC_DEBUG + IO.popen([env, *command], "rb+") + } + + puts children.map { |child| child.gets }.uniq + formatter.start + + until @files.empty? + IO.select(children)[0].each { |io| + reply = io.read(1) + case reply + when '.' + formatter.unload + when nil + raise "Worker died!" + else + while chunk = (io.read_nonblock(4096) rescue nil) + reply += chunk + end + raise reply + end + io.puts @files.shift unless @files.empty? + } + end + + ok = true + children.each { |child| + child.puts "QUIT" + Process.wait(child.pid) + ok &&= $?.success? + } + + formatter.aggregate_results(output_files) + formatter.finish + ok + end + + def run + argv = config[:target].split(/\s+/) + + argv.concat config[:launch] + argv.concat config[:flags] + argv.concat config[:loadpath] + argv.concat config[:requires] + argv << "#{MSPEC_HOME}/bin/mspec-#{ config[:command] || "run" }" + argv.concat config[:options] + + if config[:multi] + exit multi_exec(argv) + else + $stderr.puts "$ #{argv.join(' ')}" + exec(*argv) + end + end +end |