├── test ├── helper.rb └── basic_case.rb ├── lib ├── rubytest.yml ├── rubytest │ ├── core_ext │ │ ├── file.rb │ │ ├── exception.rb │ │ ├── string.rb │ │ └── assertion.rb │ ├── autorun.rb │ ├── core_ext.rb │ ├── format │ │ ├── test.rb │ │ ├── dotprogress.rb │ │ ├── abstract.rb │ │ └── abstract_hash.rb │ ├── recorder.rb │ ├── code_snippet.rb │ ├── advice.rb │ ├── runner.rb │ └── config.rb ├── test.rb └── rubytest.rb ├── demo ├── applique │ ├── ae.rb │ └── rubytest.rb ├── 01_test.md └── 02_case.md ├── .yardopts ├── .gitignore ├── Gemfile ├── work ├── consider │ └── rc.rb ├── NOTES.md └── deprecated │ ├── reporters │ ├── tapj.rb │ ├── tapy.rb │ ├── tap.rb │ ├── summary.rb │ ├── html.rb │ ├── progress.rb │ └── outline.rb │ └── cli.rb ├── .travis.yml ├── etc └── test.rb ├── Indexfile ├── try └── raw_example.rb ├── Assembly ├── MANIFEST.txt ├── LICENSE.txt ├── .index ├── README.md ├── HISTORY.md └── .gemspec /test/helper.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/rubytest.yml: -------------------------------------------------------------------------------- 1 | ../.index -------------------------------------------------------------------------------- /demo/applique/ae.rb: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | -------------------------------------------------------------------------------- /demo/applique/rubytest.rb: -------------------------------------------------------------------------------- 1 | require 'rubytest' 2 | 3 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --output-dir doc 2 | --protected 3 | --readme README.md 4 | lib 5 | - 6 | [A-Z]*.* 7 | 8 | -------------------------------------------------------------------------------- /lib/rubytest/core_ext/file.rb: -------------------------------------------------------------------------------- 1 | class File 2 | 3 | # 4 | def self.localname(path) 5 | path.sub(Dir.pwd+'/','') 6 | end 7 | 8 | end 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .reap/digest 3 | .yardoc 4 | doc/ 5 | log/ 6 | pkg/ 7 | tmp/ 8 | web/ 9 | QED.rdoc 10 | QED.md 11 | Gemfile.lock 12 | 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | group :build do 4 | #gem "detroit" 5 | end 6 | 7 | group :test do 8 | gem "qed" 9 | gem "ae" 10 | end 11 | 12 | -------------------------------------------------------------------------------- /lib/test.rb: -------------------------------------------------------------------------------- 1 | # TODO: Do we really even need this `test.rb` file? 2 | 3 | if RUBY_VERSION < '1.9' 4 | require 'rubytest/autorun' 5 | else 6 | require_relative 'rubytest/autorun' 7 | end 8 | 9 | -------------------------------------------------------------------------------- /lib/rubytest/autorun.rb: -------------------------------------------------------------------------------- 1 | require 'rubytest' 2 | 3 | at_exit { 4 | Test.run!(ENV['profile'] || ENV['p']) 5 | #success = Test.run!(ENV['profile'] || ENV['p']) 6 | #exit -1 unless success 7 | } 8 | 9 | -------------------------------------------------------------------------------- /test/basic_case.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helper' 2 | 3 | test = Object.new 4 | 5 | def test.call 6 | end 7 | 8 | def test.to_s 9 | "Basic Test" 10 | end 11 | 12 | $TEST_SUITE << test 13 | 14 | -------------------------------------------------------------------------------- /work/consider/rc.rb: -------------------------------------------------------------------------------- 1 | if Config.rc_file 2 | begin 3 | require 'rc/api' 4 | RC.setup 'rubytest' do |config| 5 | Test.run(config.profile, &config) 6 | end 7 | rescue LoadError 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: "bundle exec qed -Ilib" 4 | rvm: 5 | - 1.9.3 6 | - 2.1.1 7 | - rbx-2 8 | - jruby 9 | matrix: 10 | allow_failures: 11 | - rvm: rbx-2 12 | cache: bundler 13 | 14 | -------------------------------------------------------------------------------- /etc/test.rb: -------------------------------------------------------------------------------- 1 | # This is just here as an example. 2 | Test.configure 'coverage' do |run| 3 | run.loadpath << 'lib' 4 | run.test_files << 'test/*_case.rb' 5 | 6 | run.before do 7 | require 'simplecov' 8 | Simplecov.command_name File.basename($0) 9 | SimpleCov.start do 10 | add_filter 'test/' 11 | coverage_dir 'log/coverage' 12 | end 13 | end 14 | end 15 | 16 | -------------------------------------------------------------------------------- /lib/rubytest/core_ext.rb: -------------------------------------------------------------------------------- 1 | if RUBY_VERSION < '1.9' 2 | require 'rubytest/core_ext/assertion' 3 | require 'rubytest/core_ext/exception' 4 | require 'rubytest/core_ext/file' 5 | require 'rubytest/core_ext/string' 6 | else 7 | require_relative 'core_ext/assertion' 8 | require_relative 'core_ext/exception' 9 | require_relative 'core_ext/file' 10 | require_relative 'core_ext/string' 11 | end 12 | -------------------------------------------------------------------------------- /lib/rubytest/core_ext/exception.rb: -------------------------------------------------------------------------------- 1 | class Exception 2 | 3 | def set_assertion(boolean) 4 | @assertion = boolean 5 | end unless method_defined?(:set_assertion) 6 | 7 | def assertion? 8 | @assertion 9 | end unless method_defined?(:assertion?) 10 | 11 | def set_priority(integer) 12 | @priority = integer.to_i 13 | end unless method_defined?(:set_priority) 14 | 15 | def priority 16 | @priority ||= 0 17 | end unless method_defined?(:priority) 18 | 19 | end 20 | -------------------------------------------------------------------------------- /demo/01_test.md: -------------------------------------------------------------------------------- 1 | ## Defining a Test 2 | 3 | Any object in a test suite that responds to #call, will be executed as 4 | a test. For instance, given an abtriray object defined as follows. 5 | 6 | test = Object.new 7 | 8 | def test.okay 9 | @okay 10 | end 11 | 12 | def test.call 13 | @okay = true 14 | end 15 | 16 | If we pass this to a test runner as part of a test suite, 17 | 18 | config = Test::Config.new(:suite=>[test], :format=>'test') 19 | runner = Test::Runner.new(config) 20 | 21 | success = runner.run 22 | 23 | We will see that the test was called. 24 | 25 | test.assert.okay 26 | 27 | And testing was successful. 28 | 29 | success.assert == true 30 | 31 | -------------------------------------------------------------------------------- /demo/02_case.md: -------------------------------------------------------------------------------- 1 | ## Defining a Test Case 2 | 3 | Any object in a test suite that responds to #each, will be iterated over 4 | and each entry run a test or another sub-case. For instance, given an abitrary 5 | object defined as follows. 6 | 7 | test = Object.new 8 | 9 | def test.okay 10 | @okay 11 | end 12 | 13 | def test.call 14 | @okay = true 15 | end 16 | 17 | And placed into an array. 18 | 19 | tests = [test] 20 | 21 | If we pass this to a test runner as part of a test suite, 22 | 23 | runner = Test::Runner.new(:suite=>[tests], :format=>'test') 24 | 25 | success = runner.run 26 | 27 | We will see that the test was called. 28 | 29 | test.assert.okay 30 | 31 | And testing was successful. 32 | 33 | success.assert == true 34 | 35 | -------------------------------------------------------------------------------- /lib/rubytest/core_ext/string.rb: -------------------------------------------------------------------------------- 1 | class String 2 | 3 | # Preserves relative tabbing. 4 | # The first non-empty line ends up with n spaces before nonspace. 5 | # 6 | # This is a Ruby Facet (http://rubyworks.github.com/facets). 7 | 8 | def tabto(n) 9 | if self =~ /^( *)\S/ 10 | indent(n - $1.length) 11 | else 12 | self 13 | end 14 | end unless method_defined?(:tabto) 15 | 16 | # Indent left or right by n spaces. 17 | # (This used to be called #tab and aliased as #indent.) 18 | # 19 | # This is a Ruby Facet (http://rubyworks.github.com/facets). 20 | 21 | def indent(n, c=' ') 22 | if n >= 0 23 | gsub(/^/, c * n) 24 | else 25 | gsub(/^#{Regexp.escape(c)}{0,#{-n}}/, "") 26 | end 27 | end unless method_defined?(:indent) 28 | 29 | end 30 | 31 | -------------------------------------------------------------------------------- /Indexfile: -------------------------------------------------------------------------------- 1 | --- 2 | name: 3 | rubytest 4 | 5 | version: 6 | 0.8.1 7 | 8 | title: 9 | Rubytest 10 | 11 | summary: 12 | Ruby Universal Test Harness 13 | 14 | description: 15 | Rubytest is a universal test harness for Ruby. It can handle any compliant 16 | test framework, even running tests from multiple frameworks in a single pass. 17 | This is the core component of the system, and is the only part strictly 18 | necessary to run tests. 19 | 20 | resources: 21 | home: http://rubyworks.github.com/rubytest 22 | code: http://github.com/rubyworks/rubytest 23 | mail: http://groups.google.com/group/rubyworks-mailinglist 24 | 25 | repositories: 26 | upstream: git@github.com:rubyworks/rubytest.git 27 | 28 | authors: 29 | - trans 30 | 31 | created: 2011-07-23 32 | 33 | copyrights: 34 | - 2011 RubyWorks (BSD-2-Clause) 35 | 36 | -------------------------------------------------------------------------------- /work/NOTES.md: -------------------------------------------------------------------------------- 1 | # Developer's Notes 2 | 3 | The original specification utilized an `Assertion` base class, defined as 4 | `class Assertion < Exception; end`, to distinguish assertion failures from 5 | regular exceptions. All test frameworks (AFAIK) have some variation of this 6 | class, e.g. Test::Unit has `FailureError`. For this to work, it would be 7 | necessary for all such classes to inherit from the common `Assertion` class. 8 | While likely not difficult to implement, it was determined that utilizing a 9 | common `#assertion` method was more flexible and easier for test frameworks 10 | to support. 11 | 12 | Test/Unit provides for _notifications_. Support for notifications is something 13 | to consider for a future version. 14 | 15 | Feedback on any of these consideration is greatly appreciated. Just 16 | post up an new [issue](http://rubyworks.github/test/issues). 17 | 18 | -------------------------------------------------------------------------------- /try/raw_example.rb: -------------------------------------------------------------------------------- 1 | class RawTest 2 | def initialize(label, &block) 3 | @label = label 4 | @block = block 5 | end 6 | def to_s 7 | @label.to_s 8 | end 9 | def call 10 | @block.call 11 | end 12 | end 13 | 14 | # pass 15 | $TEST_SUITE << RawTest.new("all good")do 16 | end 17 | 18 | # fail 19 | $TEST_SUITE << RawTest.new("not so good")do 20 | e = SyntaxError.new("failure is not an option") 21 | e.set_assertion(true) 22 | raise e 23 | end 24 | 25 | # error 26 | $TEST_SUITE << RawTest.new("not good at all") do 27 | raise "example error" 28 | end 29 | 30 | # todo 31 | $TEST_SUITE << RawTest.new("it will be done") do 32 | raise NotImplementedError, "but we're not there yet" 33 | end 34 | 35 | # omit 36 | $TEST_SUITE << RawTest.new("forget about it") do 37 | e = NotImplementedError.new("it just can't be done") 38 | e.set_assertion(true) 39 | raise e 40 | end 41 | 42 | -------------------------------------------------------------------------------- /lib/rubytest/core_ext/assertion.rb: -------------------------------------------------------------------------------- 1 | class Assertion < Exception 2 | 3 | # New assertion (failure). 4 | # 5 | # @param message [String] the failure message 6 | # @param options [Hash] options such as :backtrace 7 | # 8 | def initialize(message=nil, options={}) 9 | super(message) 10 | backtrace = options[:backtrace] 11 | set_backtrace(backtrace) if backtrace 12 | @assertion = true 13 | end 14 | 15 | # Technically any object that affirmatively responds to #assertion? 16 | # can be taken to be an Assertion. This makes it easier for various 17 | # libraries to work together without having to depend upon a common 18 | # Assertion base class. 19 | def assertion? 20 | true # @assertion 21 | end 22 | 23 | # Parents error message prefixed with "(assertion)". 24 | # 25 | # @return [String] error message 26 | def to_s 27 | '(assertion) ' + super 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /lib/rubytest/format/test.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test::Reporters 4 | 5 | # Test Reporter is used to test Ruby Test itself. 6 | # 7 | class Test < AbstractHash 8 | 9 | # 10 | def initialize(runner) 11 | super(runner) 12 | end 13 | 14 | # 15 | def begin_suite(suite) 16 | super(suite) 17 | end 18 | 19 | # 20 | def begin_case(test_case) 21 | super(test_case) 22 | end 23 | 24 | # 25 | def pass(test) #, backtrace=nil) 26 | super(test) 27 | end 28 | 29 | # 30 | def fail(test, exception) 31 | super(test, exception) 32 | end 33 | 34 | # 35 | def error(test, exception) 36 | super(test, exception) 37 | end 38 | 39 | # 40 | def todo(test, exception) 41 | super(test, exception) 42 | end 43 | 44 | # 45 | def end_suite(suite) 46 | super(suite) 47 | end 48 | 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | github: 3 | gh_pages: web 4 | 5 | gem: 6 | active: true 7 | 8 | dnote: 9 | title: Source Notes 10 | labels: ~ 11 | output: log/notes.html 12 | 13 | yard: 14 | yardopts: true 15 | 16 | qed: 17 | files : demo/ 18 | #exclude : ~ 19 | #loadpath: ~ 20 | #requires: ~ 21 | #live : false 22 | active : false 23 | 24 | qedoc: 25 | files : demo/ 26 | output: QED.rdoc 27 | active: false 28 | 29 | vclog: 30 | output: 31 | - log/changes.html 32 | - log/history.html 33 | active: false 34 | 35 | email: 36 | mailto : 37 | - ruby-talk@ruby-lang.org 38 | - rubyworks-mailinglist@googlegroups.com 39 | #from : <%= ENV['EMAIL_ACCOUNT'] %> 40 | #server : <%= ENV['EMAIL_SERVER'] %> 41 | #port : <%= ENV['EMAIL_PORT'] %> 42 | #account: <%= ENV['EMAIL_ACCOUNT'] %> 43 | #domain : <%= ENV['EMAIL_DOMAIN'] %> 44 | #login : <%= ENV['EMAIL_LOGIN'] %> 45 | #secure : <%= ENV['EMAIL_SECURE'] %> 46 | 47 | -------------------------------------------------------------------------------- /MANIFEST.txt: -------------------------------------------------------------------------------- 1 | #!mast .index .yaropts bin demo lib test *.md *.txt 2 | .index 3 | demo/01_test.md 4 | demo/02_case.md 5 | demo/applique/ae.rb 6 | demo/applique/rubytest.rb 7 | lib/rubytest/advice.rb 8 | lib/rubytest/autorun.rb 9 | lib/rubytest/code_snippet.rb 10 | lib/rubytest/config.rb 11 | lib/rubytest/core_ext/assertion.rb 12 | lib/rubytest/core_ext/exception.rb 13 | lib/rubytest/core_ext/file.rb 14 | lib/rubytest/core_ext/string.rb 15 | lib/rubytest/core_ext.rb 16 | lib/rubytest/format/abstract.rb 17 | lib/rubytest/format/abstract_hash.rb 18 | lib/rubytest/format/dotprogress.rb 19 | lib/rubytest/format/test.rb 20 | lib/rubytest/recorder.rb 21 | lib/rubytest/reporters/html.rb 22 | lib/rubytest/reporters/outline.rb 23 | lib/rubytest/reporters/progress.rb 24 | lib/rubytest/reporters/summary.rb 25 | lib/rubytest/reporters/tap.rb 26 | lib/rubytest/reporters/tapj.rb 27 | lib/rubytest/reporters/tapy.rb 28 | lib/rubytest/runner.rb 29 | lib/rubytest.rb 30 | lib/rubytest.yml 31 | lib/test.rb 32 | test/basic_case.rb 33 | test/helper.rb 34 | README.md 35 | HISTORY.md 36 | LICENSE.txt 37 | -------------------------------------------------------------------------------- /work/deprecated/reporters/tapj.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test::Reporters 4 | 5 | # TAP-J Reporter 6 | # 7 | class Tapj < AbstractHash 8 | 9 | REVISION = 5 10 | 11 | # 12 | def initialize(runner) 13 | require 'json' 14 | super(runner) 15 | end 16 | 17 | # 18 | def begin_suite(suite) 19 | hash = super(suite) 20 | hash['rev'] = REVISION 21 | puts hash.to_json 22 | end 23 | 24 | # 25 | def begin_case(test_case) 26 | puts super(test_case).to_json 27 | end 28 | 29 | # 30 | def pass(test) #, backtrace=nil) 31 | puts super(test).to_json 32 | end 33 | 34 | # 35 | def fail(test, exception) 36 | puts super(test, exception).to_json 37 | end 38 | 39 | # 40 | def error(test, exception) 41 | puts super(test, exception).to_json 42 | end 43 | 44 | # 45 | def todo(test, exception) 46 | puts super(test, exception).to_json 47 | end 48 | 49 | # 50 | def end_suite(suite) 51 | puts super(suite).to_json 52 | puts "..." 53 | end 54 | 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /work/deprecated/reporters/tapy.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test::Reporters 4 | 5 | # TAP-Y Reporter 6 | # 7 | class Tapy < AbstractHash 8 | 9 | REVISION = 5 10 | 11 | # 12 | def initialize(runner) 13 | require 'json' 14 | super(runner) 15 | end 16 | 17 | # 18 | def begin_suite(suite) 19 | hash = super(suite) 20 | hash['rev'] = REVISION 21 | puts hash.to_json 22 | end 23 | 24 | # 25 | def begin_case(test_case) 26 | puts super(test_case).to_yaml 27 | end 28 | 29 | # 30 | def pass(test) #, backtrace=nil) 31 | puts super(test).to_yaml 32 | end 33 | 34 | # 35 | def fail(test, exception) 36 | puts super(test, exception).to_yaml 37 | end 38 | 39 | # 40 | def error(test, exception) 41 | puts super(test, exception).to_yaml 42 | end 43 | 44 | # 45 | def todo(test, exception) 46 | puts super(test, exception).to_yaml 47 | end 48 | 49 | # 50 | def omit(test, exception) 51 | puts super(test, exception).to_yaml 52 | end 53 | 54 | # 55 | def end_suite(suite) 56 | puts super(suite).to_yaml 57 | puts "..." 58 | end 59 | 60 | end 61 | 62 | end 63 | -------------------------------------------------------------------------------- /lib/rubytest/recorder.rb: -------------------------------------------------------------------------------- 1 | module Test 2 | 3 | # Recorder class is an observer that tracks all tests 4 | # that are run and categorizes them according to their 5 | # test status. 6 | class Recorder 7 | 8 | def initialize 9 | @table = Hash.new{ |h,k| h[k] = [] } 10 | end 11 | 12 | def [](key) 13 | @table[key.to_sym] 14 | end 15 | 16 | # 17 | def skip_test(test, reason) 18 | self[:skip] << [test, reason] 19 | end 20 | 21 | # Add `test` to pass set. 22 | def pass(test) 23 | self[:pass] << test 24 | end 25 | 26 | def fail(test, exception) 27 | self[:fail] << [test, exception] 28 | end 29 | 30 | def error(test, exception) 31 | self[:error] << [test, exception] 32 | end 33 | 34 | def todo(test, exception) 35 | self[:todo] << [test, exception] 36 | end 37 | 38 | #def omit(test, exception) 39 | # self[:omit] << [test, exception] 40 | #end 41 | 42 | # Returns true if their are no test errors or failures. 43 | def success? 44 | self[:error].size + self[:fail].size > 0 ? false : true 45 | end 46 | 47 | # Ignore any other signals. 48 | def method_missing(*a) 49 | end 50 | 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (BSD-2-Clause License) 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 20 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - Indexfile 6 | - Gemfile 7 | authors: 8 | - name: trans 9 | email: transfire@gmail.com 10 | organizations: [] 11 | requirements: 12 | - groups: 13 | - test 14 | version: '>= 0' 15 | name: qed 16 | - groups: 17 | - test 18 | version: '>= 0' 19 | name: ae 20 | conflicts: [] 21 | alternatives: [] 22 | resources: 23 | - type: home 24 | uri: http://rubyworks.github.com/rubytest 25 | label: Homepage 26 | - type: code 27 | uri: http://github.com/rubyworks/rubytest 28 | label: Source Code 29 | - type: mail 30 | uri: http://groups.google.com/group/rubyworks-mailinglist 31 | label: Mailing List 32 | repositories: 33 | - name: upstream 34 | scm: git 35 | uri: git@github.com:rubyworks/rubytest.git 36 | categories: [] 37 | copyrights: 38 | - holder: RubyWorks 39 | year: '2011' 40 | license: BSD-2-Clause 41 | customs: [] 42 | paths: 43 | lib: 44 | - lib 45 | name: rubytest 46 | title: Rubytest 47 | version: 0.8.1 48 | summary: Ruby Universal Test Harness 49 | description: Rubytest is a universal test harness for Ruby. It can handle any compliant 50 | test framework, even running tests from multiple frameworks in a single pass. This 51 | is the core component of the system, and is the only part strictly necessary to 52 | run tests. 53 | created: '2011-07-23' 54 | date: '2014-07-19' 55 | -------------------------------------------------------------------------------- /lib/rubytest.rb: -------------------------------------------------------------------------------- 1 | # Ruby Test - Universal Ruby Test Harness 2 | 3 | $TEST_SUITE = [] unless defined?($TEST_SUITE) 4 | $RUBY_IGNORE_CALLERS = [] unless defined?($RUBY_IGNORE_CALLERS) 5 | 6 | #require 'brass' # TODO: Should we require BRASS ? 7 | require 'ansi/core' 8 | 9 | module Test 10 | # Load project index on demand. 11 | def self.index 12 | @index ||= ( 13 | require 'yaml' 14 | __dir__ = File.dirname(__FILE__) 15 | file = File.expand_path('rubytest.yml', __dir__) 16 | YAML.load_file(file) 17 | ) 18 | end 19 | 20 | # Lookup missing constant in project index. 21 | def self.const_missing(name) 22 | index[name.to_s.downcase] || super(name) 23 | end 24 | end 25 | 26 | if RUBY_VERSION < '1.9' 27 | require 'rubytest/core_ext' 28 | require 'rubytest/code_snippet' 29 | require 'rubytest/config' 30 | require 'rubytest/recorder' 31 | require 'rubytest/advice' 32 | require 'rubytest/runner' 33 | require 'rubytest/format/abstract' 34 | require 'rubytest/format/abstract_hash' 35 | else 36 | require_relative 'rubytest/core_ext' 37 | require_relative 'rubytest/code_snippet' 38 | require_relative 'rubytest/config' 39 | require_relative 'rubytest/recorder' 40 | require_relative 'rubytest/advice' 41 | require_relative 'rubytest/runner' 42 | require_relative 'rubytest/format/abstract' 43 | require_relative 'rubytest/format/abstract_hash' 44 | end 45 | 46 | -------------------------------------------------------------------------------- /work/deprecated/reporters/tap.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test::Reporters 4 | 5 | # Classic TAP Reporter 6 | # 7 | # This reporter conforms to v12 of TAP. It could do with some 8 | # imporvements yet, and eventually upgraded to v13 of standard. 9 | class Tap < Abstract 10 | 11 | # 12 | def begin_suite(suite) 13 | @start_time = Time.now 14 | @i = 0 15 | @n = total_count(suite) 16 | puts "1..#{@n}" 17 | end 18 | 19 | def begin_test(test) 20 | @i += 1 21 | end 22 | 23 | # 24 | def pass(test) 25 | puts "ok #{@i} - #{test}" 26 | end 27 | 28 | # 29 | def fail(test, exception) 30 | puts "not ok #{@i} - #{test}" 31 | puts " FAIL #{exception.class}" 32 | puts " #{exception}" 33 | puts " #{clean_backtrace(exception)[0]}" 34 | end 35 | 36 | # 37 | def error(test, exception) 38 | puts "not ok #{@i} - #{test}" 39 | puts " ERROR #{exception.class}" 40 | puts " #{exception}" 41 | puts " " + clean_backtrace(exception).join("\n ") 42 | end 43 | 44 | # 45 | def todo(test, exception) 46 | puts "not ok #{@i} - #{test}" 47 | puts " PENDING" 48 | puts " #{clean_backtrace(exception)[1]}" 49 | end 50 | 51 | # 52 | def omit(test, exception) 53 | puts "ok #{@i} - #{test}" 54 | puts " OMIT" 55 | puts " #{clean_backtrace(exception)[1]}" 56 | end 57 | 58 | end 59 | 60 | end 61 | 62 | -------------------------------------------------------------------------------- /lib/rubytest/code_snippet.rb: -------------------------------------------------------------------------------- 1 | module Test 2 | 3 | # Thanks goes to Suraj N. Kurapati for the origins of this code. 4 | # 5 | class CodeSnippet 6 | 7 | def self.cache(file) 8 | @cache ||= {} 9 | @cache[file] ||= File.exist?(file) ? File.readlines(file) : ['(N/A)'] 10 | end 11 | 12 | # 13 | def self.from_backtrace(backtrace) 14 | backtrace.first =~ /(.+?):(\d+(?=:|\z))/ or return nil 15 | file, line = $1, $2.to_i 16 | new(file, line) 17 | end 18 | 19 | # 20 | def self.from_error(exception) 21 | backtrace = exception.backtrace 22 | from_backtrace(backtrace) 23 | end 24 | 25 | # 26 | def initialize(file, line) 27 | @file = file 28 | @line = (line || 1).to_i 29 | @code = CodeSnippet.cache(file) 30 | end 31 | 32 | # 33 | attr :file 34 | 35 | # 36 | attr :line 37 | 38 | # 39 | attr :code 40 | 41 | # 42 | alias :source :code 43 | 44 | # 45 | def to_str 46 | code[line-1].strip 47 | end 48 | 49 | # 50 | #-- 51 | # TODO: ensure proper alignment by zero-padding line numbers 52 | #++ 53 | def to_s(radius=2) 54 | r = range(radius) 55 | f = " %2s %0#{r.last.to_s.length}d %s" 56 | r.map do |n| 57 | f % [('=>' if n == line), n, code[n-1].chomp] 58 | end.join("\n") 59 | end 60 | 61 | # 62 | def to_a(radius=2) 63 | r = range(radius) 64 | r.map do |n| 65 | code[n-1].chomp 66 | end 67 | end 68 | 69 | # 70 | def to_omap(radius=2) 71 | a = [] 72 | r = range(radius) 73 | r.each do |n| 74 | a << {n => code[n-1].chomp} 75 | end 76 | a 77 | end 78 | 79 | # 80 | def succ 81 | line += 1 82 | end 83 | 84 | private 85 | 86 | # 87 | def range(radius) 88 | [line - radius, 1].max..[line + radius, source.length].min 89 | end 90 | 91 | end 92 | 93 | end 94 | -------------------------------------------------------------------------------- /lib/rubytest/format/dotprogress.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test::Reporters 4 | 5 | # Simple Dot-Progress Reporter 6 | class Dotprogress < Abstract 7 | 8 | def skip_test(unit, reason) 9 | print "S".ansi(:cyan) if runner.verbose? 10 | end 11 | 12 | def pass(unit) 13 | print "." 14 | $stdout.flush 15 | end 16 | 17 | def fail(unit, exception) 18 | print "F".ansi(:red) 19 | $stdout.flush 20 | end 21 | 22 | def error(unit, exception) 23 | print "E".ansi(:red, :bold) 24 | $stdout.flush 25 | end 26 | 27 | def todo(unit, exception) 28 | print "P".ansi(:yellow) 29 | $stdout.flush 30 | end 31 | 32 | def end_suite(suite) 33 | puts; puts 34 | puts timestamp 35 | puts 36 | 37 | if runner.verbose? 38 | unless record[:omit].empty? 39 | puts "SKIPPED\n\n" 40 | record[:skip].each do |test, reason| 41 | puts " #{test}".ansi(:bold) 42 | puts " #{reason}" if String===reason 43 | puts 44 | end 45 | end 46 | end 47 | 48 | unless record[:todo].empty? 49 | puts "PENDING\n\n" 50 | record[:todo].each do |test, exception| 51 | puts " #{test}".ansi(:bold) unless test.to_s.empty? 52 | puts " #{exception}" 53 | puts " #{file_and_line(exception)}" 54 | puts code(exception) 55 | puts 56 | end 57 | end 58 | 59 | unless record[:fail].empty? 60 | puts "FAILURES\n\n" 61 | record[:fail].each do |test_unit, exception| 62 | puts " #{test_unit}".ansi(:bold) 63 | puts " #{exception}" 64 | puts " #{file_and_line(exception)}" 65 | puts code(exception) 66 | puts " " + clean_backtrace(exception).join("\n ") 67 | puts 68 | end 69 | end 70 | 71 | unless record[:error].empty? 72 | puts "ERRORS\n\n" 73 | record[:error].each do |test_unit, exception| 74 | puts " #{test_unit}".ansi(:bold) 75 | puts " #{exception}" 76 | puts " #{file_and_line(exception)}" 77 | puts code(exception) 78 | puts " " + clean_backtrace(exception).join("\n ") 79 | puts 80 | end 81 | end 82 | 83 | puts tally 84 | end 85 | 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /lib/rubytest/advice.rb: -------------------------------------------------------------------------------- 1 | module Test 2 | 3 | # The Advice class is an observer that can be customized to 4 | # initiate before, after and upon procedures for all of RubyTests 5 | # observable points. 6 | # 7 | # Only one procedure is allowed per-point. 8 | # 9 | class Advice 10 | 11 | # 12 | def self.joinpoints 13 | @joinpoints ||= [] 14 | end 15 | 16 | # TODO: Should before and affter hooks be evaluated in the context of test 17 | # object scope? The #scope field has been added to the RubyTest spec 18 | # just in case. 19 | 20 | # 21 | def self.joinpoint(name) 22 | joinpoints << name.to_sym 23 | 24 | class_eval %{ 25 | def #{name}(*args) 26 | procedure = @table[:#{name}] 27 | procedure.call(*args) if procedure 28 | end 29 | } 30 | end 31 | 32 | # 33 | def initialize 34 | @table = {} 35 | end 36 | 37 | # @method begin_suite(suite) 38 | joinpoint :begin_suite 39 | 40 | # @method begin_case(case) 41 | joinpoint :begin_case 42 | 43 | # @method skip_test(test, reason) 44 | joinpoint :skip_test 45 | 46 | # @method begin_test(test) 47 | joinpoint :begin_test 48 | 49 | # @method pass(test) 50 | joinpoint :pass 51 | 52 | # @method fail(test, exception) 53 | joinpoint :fail 54 | 55 | # @method error(test, exception) 56 | joinpoint :error 57 | 58 | # @method todo(test, exception) 59 | joinpoint :todo 60 | 61 | # @method end_test(test) 62 | joinpoint :end_test 63 | 64 | # @method end_case(case) 65 | joinpoint :end_case 66 | 67 | # @method end_suite(suite) 68 | joinpoint :end_suite 69 | 70 | # 71 | #def [](key) 72 | # @table[key.to_sym] 73 | #end 74 | 75 | # Add a procedure to one of the join-points. 76 | def join(type, &block) 77 | type = valid_type(type) 78 | @table[type] = block 79 | end 80 | 81 | # Add a procedure to one of the before join-points. 82 | def join_before(type, &block) 83 | join("begin_#{type}", &block) 84 | end 85 | 86 | # Add a procedure to one of the after join-points. 87 | def join_after(type, &block) 88 | join("end_#{type}", &block) 89 | end 90 | 91 | # Ignore any other signals (precautionary). 92 | def method_missing(*) 93 | end 94 | 95 | private 96 | 97 | #def invoke(symbol, *args) 98 | # if @table.key?(symbol) 99 | # self[symbol].call(*args) 100 | # end 101 | #end 102 | 103 | # 104 | def valid_type(type) 105 | type = message_type(type) 106 | unless self.class.joinpoints.include?(type) 107 | raise ArgumentError, "not a valid advice type -- #{type}" 108 | end 109 | type 110 | end 111 | 112 | # 113 | def message_type(type) 114 | type = type.to_sym 115 | case type 116 | when :each 117 | type = :test 118 | when :all 119 | type = :case 120 | when :begin_each 121 | type = :begin_test 122 | when :begin_all 123 | type = :begin_case 124 | when :end_each 125 | type = :end_test 126 | when :end_all 127 | type = :end_case 128 | end 129 | return type 130 | end 131 | 132 | end 133 | 134 | end 135 | -------------------------------------------------------------------------------- /work/deprecated/reporters/summary.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test::Reporters 4 | 5 | # Summary Reporter 6 | class Summary < Abstract 7 | 8 | # 9 | SEP = ' ' 10 | 11 | # 12 | def begin_suite(suite) 13 | timer_reset 14 | @tc = [] 15 | end 16 | 17 | # 18 | def begin_case(tc) 19 | @tc << tc.to_s.split("\n").first 20 | end 21 | 22 | # 23 | #def report_instance(instance) 24 | # puts 25 | # puts instance #"== #{concern.description}\n\n" unless concern.description.empty? 26 | # #timer_reset 27 | #end 28 | 29 | # 30 | #def begin_test(test) 31 | # context = test.context 32 | # if @instance != context 33 | # @context = context 34 | # puts 35 | # puts " #{context}" 36 | # puts 37 | # end 38 | #end 39 | 40 | # 41 | def pass(test) 42 | print "PASS ".ansi(:green, :bold) 43 | e = @tc + [test.to_s] 44 | puts e.join(SEP).ansi(:green) 45 | end 46 | 47 | # 48 | def fail(test, exception) 49 | print "FAIL ".ansi(:red, :bold) 50 | e = @tc + [test.to_s] 51 | puts e.join(SEP).ansi(:red) 52 | end 53 | 54 | # 55 | def error(test, exception) 56 | print "ERROR ".ansi(:red, :bold) 57 | e = @tc + [test.to_s] 58 | puts e.join(SEP).ansi(:red) 59 | end 60 | 61 | # 62 | def todo(test, exception) 63 | print "TODO ".ansi(:yellow, :bold) 64 | e = @tc + [test.to_s] 65 | puts e.join(SEP).ansi(:yellow) 66 | end 67 | 68 | # 69 | def omit(test) 70 | print "OMIT ".ansi(:cyan, :bold) 71 | e = @tc + [test.to_s] 72 | puts e.join(SEP).ansi(:cyan) 73 | end 74 | 75 | # 76 | def skip_test(test) 77 | print "SKIP ".ansi(:blue, :bold) 78 | e = @tc + [test.to_s] 79 | puts e.join(SEP).ansi(:blue) 80 | end 81 | 82 | # 83 | def end_case(test_case) 84 | @tc.pop 85 | end 86 | 87 | # 88 | def end_suite(suite) 89 | puts 90 | 91 | unless record[:pending].empty? 92 | puts "PENDING:\n\n" 93 | record[:pending].each do |test, exception| 94 | puts " #{test}" 95 | puts " #{file_and_line(exception)}" 96 | puts 97 | end 98 | end 99 | 100 | unless record[:fail].empty? 101 | puts "FAILURES:\n\n" 102 | record[:fail].each do |test, exception| 103 | puts " #{test}" 104 | puts " #{file_and_line(exception)}" 105 | puts " #{exception}" 106 | puts code(exception).to_s 107 | #puts " #{exception.backtrace[0]}" 108 | puts 109 | end 110 | end 111 | 112 | unless record[:error].empty? 113 | puts "ERRORS:\n\n" 114 | record[:error].each do |test, exception| 115 | puts " #{test}" 116 | puts " #{file_and_line(exception)}" 117 | puts " #{exception}" 118 | puts code(exception).to_s 119 | #puts " #{exception.backtrace[0]}" 120 | puts 121 | end 122 | end 123 | 124 | puts timestamp 125 | puts 126 | puts tally 127 | end 128 | 129 | private 130 | 131 | # 132 | def timer 133 | secs = Time.now - @time 134 | @time = Time.now 135 | return "%0.5fs" % [secs.to_s] 136 | end 137 | 138 | # 139 | def timer_reset 140 | @time = Time.now 141 | end 142 | 143 | end 144 | 145 | end 146 | -------------------------------------------------------------------------------- /work/deprecated/reporters/html.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test::Reporters 4 | 5 | # HTML Test Reporter 6 | # 7 | # This reporter is rather simplistic and rough at this point --in need 8 | # of some TLC. Also, it may move to the TAPOUT project rather than be 9 | # a built-in Ruby-Test reporter. 10 | #-- 11 | # TODO: Make this more like a microformat and add timer info. 12 | #++ 13 | class Html < Abstract 14 | 15 | # 16 | def begin_suite(suite) 17 | timer_reset 18 | 19 | @html = [] 20 | @html << %[] 21 | @html << %[] 22 | @html << %[Test Report] 23 | @html << %[ ] 38 | @html << %[] 39 | @html << %[] 40 | @html << %[
] 41 | @html << %[
R U B Y - T E S T
] 42 | @html << %[

Test Report

] 43 | @body = [] 44 | end 45 | 46 | # 47 | def begin_case(tc) 48 | lines = tc.to_s.split("\n") 49 | title = lines.shift 50 | @body << "

" 51 | @body << title 52 | @body << "

" 53 | @body << "
" 54 | @body << lines.join("
") 55 | @body << "
" 56 | end 57 | 58 | # 59 | def begin_test(test) 60 | if test.respond_to?(:topic) 61 | topic = test.topic 62 | if @topic != topic 63 | @topic = topic 64 | @body << "

" 65 | @body << "#{topic}" 66 | @body << "

" 67 | end 68 | end 69 | end 70 | 71 | # 72 | def pass(test) 73 | @body << %[
  • ] 74 | @body << "%s %s" % ["PASS", test.to_s] 75 | @body << %[
  • ] 76 | end 77 | 78 | # 79 | def fail(test, exception) 80 | @body << %[
  • ] 81 | @body << "%s %s" % ["FAIL", test.to_s] 82 | @body << "
    "
     83 |       @body << "  FAIL #{clean_backtrace(exception)[0]}"
     84 |       @body << "  #{exception}"
     85 |       @body << "
    " 86 | @body << %[
  • ] 87 | end 88 | 89 | # 90 | def error(test, exception) 91 | @body << %[
  • ] 92 | @body << "%s %s" % ["ERROR", test.to_s] 93 | @body << "
    "
     94 |       @body << "  ERROR #{exception.class}"
     95 |       @body << "  #{exception}"
     96 |       @body << "  " + clean_backtrace(exception).join("\n        ")
     97 |       @body << "
    " 98 | @body << %[
  • ] 99 | end 100 | 101 | # 102 | def todo(test, exception) 103 | @body << %[
  • ] 104 | @body << "%s %s" % ["PENDING", test.to_s] 105 | @body << %[
  • ] 106 | end 107 | 108 | # 109 | def omit(test, exception) 110 | @body << %[
  • ] 111 | @body << "%s %s" % ["OMIT", test.to_s] 112 | @body << %[
  • ] 113 | end 114 | 115 | # 116 | def end_suite(suite) 117 | @html << "" 118 | @html << %[
    ] 119 | @html << tally 120 | @html << %[
    ] 121 | @html << "" 122 | 123 | @body << "" 124 | @body << %[] 128 | @body << "" 129 | @body << %[
    ] 130 | @body << %[] 131 | @body << "" 132 | @body << %[] 133 | @body << %[] 134 | 135 | puts @html.join("\n") 136 | puts @body.join("\n") 137 | end 138 | 139 | private 140 | 141 | # 142 | def timer 143 | secs = Time.now - @time 144 | @time = Time.now 145 | return "%0.5fs" % [secs.to_s] 146 | end 147 | 148 | # 149 | def timer_reset 150 | @time = Time.now 151 | end 152 | 153 | end 154 | 155 | end 156 | -------------------------------------------------------------------------------- /work/deprecated/reporters/progress.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test::Reporters 4 | 5 | # Progess reporter gives test counter, precentage and times. 6 | # 7 | class Progress < Abstract 8 | 9 | # 10 | def begin_suite(suite) 11 | @tab = 0 12 | @total_count = total_count(suite) 13 | @start_time = Time.now 14 | @test_cache = {} 15 | @count = 0 16 | 17 | max = @total_count.to_s.size 18 | 19 | @layout_head = " %3u%% %#{max}s %#{max}s %8s %11s %1s %s" 20 | @layout = " %3u%% %#{max}u/%#{max}u %8s %11s %1s %s" 21 | 22 | timer_reset 23 | end 24 | 25 | # 26 | def begin_case(tc) 27 | #tabs tc.to_s.ansi(:bold) 28 | show_header(' ', tc.to_s) 29 | @tab += 2 30 | end 31 | 32 | # 33 | def begin_test(test) 34 | if test.respond_to?(:topic) && test.topic 35 | topic = test.topic.to_s.rstrip 36 | @test_cache[topic] ||= ( 37 | show_header(' ', topic) unless topic.empty? 38 | true 39 | ) 40 | end 41 | timer_reset 42 | end 43 | 44 | # 45 | def pass(test) 46 | show_line(".", test, :green) 47 | end 48 | 49 | # 50 | def fail(test, exception) 51 | show_line("F", test, :red) 52 | end 53 | 54 | # 55 | def error(test, exception) 56 | show_line("E", test, :red) 57 | end 58 | 59 | # 60 | def todo(test, exception) 61 | show_line("P", test, :yellow) 62 | end 63 | 64 | # 65 | def omit(test, exception) 66 | show_line("O", test, :cyan) 67 | end 68 | 69 | # 70 | def end_case(tcase) 71 | @tab -= 2 72 | end 73 | 74 | # 75 | def end_suite(suite) 76 | puts 77 | 78 | if runner.verbose? 79 | unless record[:omit].empty? 80 | puts "OMISSIONS:\n\n" 81 | record[:omit].reverse_each do |test, exception| 82 | s = [] 83 | s << "#{test}".ansi(:bold) 84 | s << "#{file_and_line(exception)}" 85 | puts s.join("\n").tabto(4) 86 | puts code(exception).to_s.tabto(7) 87 | puts 88 | end 89 | end 90 | end 91 | 92 | unless record[:todo].empty? 93 | puts "PENDING:\n\n" 94 | record[:todo].reverse_each do |test, exception| 95 | s = [] 96 | s << "#{test}".ansi(:bold) 97 | s << "#{file_and_line(exception)}" 98 | puts s.join("\n").tabto(4) 99 | puts code(exception).to_s.tabto(7) 100 | puts 101 | end 102 | end 103 | 104 | unless record[:fail].empty? 105 | puts "FAILURES:\n\n" 106 | record[:fail].reverse_each do |test, exception| 107 | s = [] 108 | s << "#{test}".ansi(:bold) 109 | s << "#{exception}".ansi(:red) 110 | s << "#{file_and_line(exception)}" 111 | puts s.join("\n").tabto(4) 112 | puts code(exception).to_s.tabto(7) 113 | #puts " #{exception.backtrace[0]}" 114 | puts 115 | end 116 | end 117 | 118 | unless record[:error].empty? 119 | puts "ERRORS:\n\n" 120 | record[:error].reverse_each do |test, exception| 121 | trace = clean_backtrace(exception)[1..-1].map{ |bt| bt.sub(Dir.pwd+'/', '') } 122 | s = [] 123 | s << "#{test}".ansi(:bold) 124 | s << "#{exception.class}".ansi(:red) 125 | s << "#{exception}".ansi(:red) 126 | s << "#{file_and_line(exception)}" 127 | puts s.join("\n").tabto(4) 128 | puts code(exception).to_s.tabto(7) 129 | puts trace.join("\n").tabto(4) unless trace.empty? 130 | puts 131 | end 132 | end 133 | 134 | puts 135 | puts timestamp 136 | puts 137 | puts tally 138 | end 139 | 140 | private 141 | 142 | # 143 | def show_header(status, text) 144 | text = text[0..text.index("\n")||-1] 145 | data = [prcnt, ' ', ' ', clock, timer, status, (' ' * @tab) + text.to_s] 146 | #puts (" " * @tab) + (@layout_head % data) 147 | puts (@layout_head % data).ansi(:bold) 148 | end 149 | 150 | # 151 | def show_line(status, test, color) 152 | @count += 1 153 | data = [prcnt, @count, @total_count, clock, timer, status, (' ' * @tab) + test.to_s] 154 | #puts (" " * @tab) + (@layout % data) 155 | puts (@layout % data).ansi(color) 156 | end 157 | 158 | # 159 | def prcnt 160 | ((@count.to_f / @total_count) * 100).round.to_s 161 | end 162 | 163 | # 164 | def clock 165 | secs = Time.now - @start_time 166 | m, s = secs.divmod(60) 167 | #s, ms = s.divmod(1) 168 | #ms = ms * 1000 169 | return "%u:%02u" % [m, s] 170 | end 171 | 172 | # 173 | def timer 174 | secs = Time.now - @time 175 | @time = Time.now 176 | return "%0.5fs" % secs 177 | end 178 | 179 | # 180 | def timer_reset 181 | @time = Time.now 182 | end 183 | 184 | # 185 | def tabs(str=nil) 186 | if str 187 | puts(str.tabto(@tab)) 188 | else 189 | puts 190 | end 191 | end 192 | 193 | end 194 | 195 | end 196 | -------------------------------------------------------------------------------- /work/deprecated/reporters/outline.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test::Reporters 4 | 5 | # 6 | class Outline < Abstract 7 | 8 | # 9 | def begin_suite(suite) 10 | @tab = 0 11 | @start_time = Time.now 12 | @start_test_cache = {} 13 | 14 | timer_reset 15 | end 16 | 17 | # 18 | def begin_case(tc) 19 | lines = tc.to_s.split("\n") 20 | label = lines.shift 21 | if tc.respond_to?(:type) 22 | tabs "#{tc.type}: #{label}".ansi(:bold) 23 | tabs lines.join("\n"), 2 unless lines.empty? 24 | else 25 | tabs "#{label}".ansi(:bold) 26 | tabs lines.join("\n"), 2 unless lines.empty? 27 | end 28 | @tab += 2 29 | end 30 | 31 | # 32 | def begin_test(test) 33 | if test.respond_to?(:topic) && test.topic 34 | topic = test.topic.to_s 35 | @start_test_cache[topic] ||= ( 36 | tabs "#{topic}" 37 | true 38 | ) 39 | end 40 | timer_reset 41 | end 42 | 43 | # 44 | # 45 | def pass(test) 46 | tabs "#{test}".ansi(:green) 47 | end 48 | 49 | # 50 | def fail(test, exception) 51 | tabs "#{test}".ansi(:red) 52 | 53 | s = [] 54 | s << "#{exception}" 55 | s << "#{file_and_line(exception)}" 56 | s << code(exception) 57 | #puts " #{exception.backtrace[0]}" 58 | tabs s.join("\n"), 4 59 | end 60 | 61 | # 62 | def error(test, exception) 63 | tabs "#{test}".ansi(:red, :bold) 64 | 65 | s = [] 66 | s << "#{exception.class}" 67 | s << "#{exception}" 68 | s << "#{file_and_line(exception)}" 69 | s << code(exception) 70 | #s << trace.join("\n") unless trace.empty? 71 | tabs s.join("\n"), 4 72 | end 73 | 74 | # 75 | def todo(test, exception) 76 | tabs "#{test}".ansi(:yellow) 77 | tabs "#{file_and_line(exception)}", 4 78 | end 79 | 80 | # 81 | def omit(test, exception) 82 | tabs "#{test}".ansi(:cyan) 83 | end 84 | 85 | # 86 | def end_case(tcase) 87 | @tab -= 2 88 | end 89 | 90 | # 91 | def end_suite(suite) 92 | puts 93 | 94 | #unless record[:omit].empty? 95 | # puts "\nOMITTED:\n\n" 96 | # puts record[:omit].map{ |u| u.to_s }.sort.join(' ') 97 | # puts 98 | #end 99 | 100 | #unless record[:todo].empty? 101 | # puts "\nPENDING:\n\n" 102 | # record[:pending].each do |test, exception| 103 | # puts "#{test}".tabto(4) 104 | # puts "#{file_and_line(exception)}".tabto(4) 105 | # puts 106 | # end 107 | #end 108 | 109 | #unless record[:fail].empty? 110 | # puts "\nFAILURES:\n\n" 111 | # record[:fail].reverse_each do |test, exception| 112 | # 113 | # s = [] 114 | # s << "#{test}".ansi(:red) 115 | # s << "#{file_and_line(exception)}".ansi(:bold) 116 | # s << "#{exception}" 117 | # s << code_snippet(exception) 118 | # #puts " #{exception.backtrace[0]}" 119 | # puts s.join("\n").tabto(4) 120 | # end 121 | #end 122 | 123 | #unless record[:error].empty? 124 | # puts "\nERRORS:\n\n" 125 | # record[:error].reverse_each do |test, exception| 126 | # trace = clean_backtrace(exception)[1..-1] 127 | # 128 | # s = [] 129 | # s << "#{test}".ansi(:red, :bold) 130 | # s << "#{exception.class} @ #{file_and_line(exception)}".ansi(:bold) 131 | # s << "#{exception}" 132 | # s << code_snippet(exception) 133 | # #s << trace.join("\n") unless trace.empty? 134 | # puts s.join("\n").tabto(4) 135 | # end 136 | #end 137 | 138 | puts 139 | puts timestamp 140 | puts 141 | puts tally 142 | end 143 | 144 | # 145 | def clock 146 | secs = Time.now - @start_time 147 | return "%0.5fs" % [secs.to_s] 148 | end 149 | 150 | # 151 | def timer 152 | secs = Time.now - @time 153 | @time = Time.now 154 | return "%0.5fs" % [secs.to_s] 155 | end 156 | 157 | # 158 | def timer_reset 159 | @time = Time.now 160 | end 161 | 162 | # 163 | def tabs(str, indent=0) 164 | if str 165 | puts(str.tabto(@tab + indent)) 166 | else 167 | puts 168 | end 169 | end 170 | 171 | end 172 | 173 | end 174 | 175 | 176 | 177 | 178 | =begin 179 | if cover? 180 | 181 | unless uncovered_cases.empty? 182 | unc = uncovered_cases.map do |mod| 183 | yellow(mod.name) 184 | end.join(", ") 185 | puts "\nUncovered Cases: " + unc 186 | end 187 | 188 | unless uncovered_units.empty? 189 | unc = uncovered_units.map do |unit| 190 | yellow(unit) 191 | end.join(", ") 192 | puts "\nUncovered Units: " + unc 193 | end 194 | 195 | #unless uncovered.empty? 196 | # unc = uncovered.map do |unit| 197 | # yellow(unit) 198 | # end.join(", ") 199 | # puts "\nUncovered: " + unc 200 | #end 201 | 202 | unless undefined_units.empty? 203 | unc = undefined_units.map do |unit| 204 | yellow(unit) 205 | end.join(", ") 206 | puts "\nUndefined Units: " + unc 207 | end 208 | 209 | end 210 | =end 211 | 212 | -------------------------------------------------------------------------------- /work/deprecated/cli.rb: -------------------------------------------------------------------------------- 1 | 2 | 3 | module Test 4 | 5 | # Command line interface to test runner. 6 | # 7 | class CLI 8 | 9 | # Test configuration file can be in `etc/test.rb` or `config/test.rb`, or 10 | # `Testfile` or '.test` with optional `.rb` extension, in that order of 11 | # precedence. To use a different file there is the -c/--config option. 12 | GLOB_CONFIG = '{etc/test.rb,config/test.rb,testfile.rb,testfile,.test.rb,.test}' 13 | 14 | # Convenience method for invoking the CLI. 15 | # 16 | # @return nothing 17 | def self.run(*argv) 18 | new.run(*argv) 19 | end 20 | 21 | # Initialize CLI instance. 22 | # 23 | # @return nothing 24 | def initialize 25 | require 'optparse' 26 | 27 | @config = {} 28 | @config_file = nil 29 | #@config = Test.configuration(true) 30 | end 31 | 32 | # Test run configuration. 33 | # 34 | # @return [Config] 35 | def config 36 | @config 37 | end 38 | 39 | # Run tests. 40 | # 41 | # @return nothing 42 | def run(argv=nil) 43 | argv = (argv || ARGV.dup) 44 | 45 | options.parse!(argv) 46 | 47 | config.files.replace(argv) unless argv.empty? 48 | 49 | config.apply_environment_overrides 50 | 51 | #Test.run(config) 52 | runner = Runner.new(config) 53 | begin 54 | success = runner.run 55 | exit -1 unless success 56 | rescue => error 57 | raise error if $DEBUG 58 | $stderr.puts('ERROR: ' + error.to_s) 59 | exit -1 60 | end 61 | end 62 | 63 | # Setup OptionsParser instance. 64 | # 65 | # @return [OptionParser] 66 | def options 67 | this = self 68 | 69 | OptionParser.new do |opt| 70 | opt.banner = "Usage: #{File.basename($0)} [options] [files ...]" 71 | 72 | #opt.separator "PRESET OPTIONS:" 73 | #pnames = profile_names 74 | #unless pnames.empty? 75 | # pnames.each do |pname| 76 | # opt.separator((" " * 40) + "* #{pname}") 77 | # end 78 | #end 79 | #opt.separator "CONFIG OPTIONS:" 80 | 81 | opt.on '-f', '--format NAME', 'report format' do |name| 82 | config.format = name 83 | end 84 | opt.on '-y', '--tapy', 'shortcut for -f tapy' do 85 | config.format = 'tapy' 86 | end 87 | opt.on '-j', '--tapj', 'shortcut for -f tapj' do 88 | config.format = 'tapj' 89 | end 90 | 91 | # tempted to change -T 92 | opt.on '-t', '--tag TAG', 'select tests by tag' do |tag| 93 | config.tags.concat makelist(tag) 94 | end 95 | opt.on '-u', '--unit TAG', 'select tests by software unit' do |unit| 96 | config.units.concat makelist(unit) 97 | end 98 | opt.on '-m', '--match TEXT', 'select tests by description' do |text| 99 | config.match.concat makelist(text) 100 | end 101 | 102 | opt.on '-A', '--autopath', 'automatically add paths to $LOAD_PATH' do |paths| 103 | config.autopath = true 104 | end 105 | opt.on '-I', '--loadpath PATH', 'add given path to $LOAD_PATH' do |paths| 106 | #makelist(paths).reverse_each do |path| 107 | # $LOAD_PATH.unshift path 108 | #end 109 | config.loadpath.concat makelist(paths) 110 | end 111 | opt.on '-C', '--chdir DIR', 'change directory before running tests' do |dir| 112 | config.chdir = dir 113 | end 114 | opt.on '-R', '--chroot', 'change to project root directory before running tests' do 115 | config.chdir = Config.root 116 | end 117 | opt.on '-r', '--require FILE', 'require file' do |file| 118 | #require file 119 | config.requires.concat makelist(file) 120 | end 121 | opt.on '-c', '--config FILE', "require local config file (immediately)" do |file| 122 | config.load_config(file) 123 | end 124 | #opt.on '-T', '--tests GLOB', "tests to run (if none given as arguments)" do |glob| 125 | # config.files << glob 126 | #end 127 | opt.on '-V' , '--verbose', 'provide extra detail in reports' do 128 | config.verbose = true 129 | end 130 | #opt.on('--log PATH', 'log test output to path'){ |path| 131 | # config.log = path 132 | #} 133 | opt.on("--[no-]ansi" , 'turn on/off ANSI colors'){ |v| $ansi = v } 134 | opt.on("--debug" , 'turn on debugging mode'){ $DEBUG = true } 135 | 136 | #opt.separator "COMMAND OPTIONS:" 137 | opt.on('--about' , 'display information about rubytest') do 138 | puts "Ruby Test v%s" % [Test.index['version']] 139 | Test.index['copyrights'].each do |c| 140 | puts "(c) %s %s (%s)" % c.values_at('year', 'holder', 'license') 141 | end 142 | exit 143 | end 144 | opt.on('--version' , 'display rubytest version') do 145 | puts Test::VERSION 146 | exit 147 | end 148 | opt.on('-h', '--help', 'display this help message'){ 149 | puts opt 150 | exit 151 | } 152 | end 153 | end 154 | 155 | # If given a String then split up at `:` and `;` markers. 156 | # Otherwise ensure the list is an Array and the entries are 157 | # all strings and not empty. 158 | # 159 | # @return [Array] 160 | def makelist(list) 161 | case list 162 | when String 163 | list = list.split(/[:;]/) 164 | else 165 | list = Array(list).map{ |path| path.to_s } 166 | end 167 | list.reject{ |path| path.strip.empty? } 168 | end 169 | 170 | end 171 | 172 | end 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Rubytest](http://rubyworks.github.io/rubytest/assets/images/test_pattern.jpg)](http://rubyworks.github.io/rubytest) 2 | 3 | # Rubytest 4 | 5 | [![Home Page](http://img.shields.io/badge/home-page-blue.svg?style=flat)](http://rubyworks.github.io/rubytest) 6 | [![User Guide](http://img.shields.io/badge/user-guide-blue.svg?style=flat)](http://wiki.github.com/rubyworks/rubytest) 7 | [![Github Fork](http://img.shields.io/badge/github-fork-blue.svg?style=flat)](http://http://github.com/rubyworks/rubytest) 8 | [![Build Status](http://img.shields.io/travis/rubyworks/rubytest.svg?style=flat)](http://travis-ci.org/rubyworks/rubytest) 9 | [![Gem Version](http://img.shields.io/gem/v/rubytest.svg?style=flat)](http://rubygems.org/gem/rubytest) 10 | [![Report Issue](http://img.shields.io/github/issues/rubyworks/rubytest.svg?style=flat)](http://github.com/rubyworks/rubytest/issues) 11 | [![Gittip](http://img.shields.io/badge/gittip-$1/wk-green.svg?style=flat)](https://www.gittip.com/on/github/rubyworks/) 12 | 13 | 14 | Rubytest is Ruby's Universal Test Harness. Think of it as a *testing meta-framework*. 15 | It defines a straight-forward specification that any application can use to create 16 | their own testing DSLs. Rubytest can be used for testing end-user applcations directly 17 | or as the backend of a test framework. Since Rubytest controls the backend, multiple test 18 | frameworks can be used in a single test suite, all of which can be run through one uniform 19 | interface in a single process! 20 | 21 | 22 | ## Specification 23 | 24 | The universal access point for testing is the `$TEST_SUITE` global array. A test 25 | framework need only add compliant test objects to `$TEST_SUITE`. 26 | Rubytest will iterate through these objects. If a test object responds to 27 | `#call`, it is run as a test procedure. If it responds to `#each` it is iterated 28 | over as a test case with each entry handled in the same manner. All test 29 | objects must respond to `#to_s` so their description can be used in test 30 | reports. 31 | 32 | Rubytest handles assertions with [BRASS](http://rubyworks.github.com/brass) 33 | compliance. Any raised exception that responds to `#assertion?` in the 34 | affirmative is taken to be a failed assertion rather than simply an error. 35 | A test framework may raise a `NotImplementedError` to have a test recorded 36 | as *todo* --a _pending_ exception to remind the developer of tests that still 37 | need to be written. The `NotImplementedError` is a standard Ruby exception 38 | and a subclass of `ScriptError`. The exception can also set a priority level 39 | to indicate the urgency of the pending test. Priorities of -1 or lower 40 | will generally not be brought to the attention of testers unless explicitly 41 | configured to do so. 42 | 43 | That is the crux of Rubytest specification. Rubytest supports some 44 | additional features that can makes its usage even more convenient. 45 | See the [Wiki](http://github.com/rubyworks/test/wiki) for further details. 46 | 47 | 48 | ## Installation 49 | 50 | Rubytest is available as a Gem package. 51 | 52 | $ gem install rubytest 53 | 54 | Rubytest is compliant with Setup.rb layout standard, so it can 55 | also be installed in an FHS compliant fashion if necessary. 56 | 57 | 58 | ## Running Tests 59 | 60 | There are a few ways to run tests. 61 | 62 | ### Via Command-line Tool 63 | 64 | The easiest way to run tests is via the command line tool. You can read more about 65 | it on its [manpage](http://rubyworks.github.com/rubytest/man/rubytest.1.html), 66 | but we will quickly go over it here. 67 | 68 | The basic usage example is: 69 | 70 | $ rubytest -Ilib test/test_*.rb 71 | 72 | The command line tool takes various options, most of which correspond directly 73 | to the configuration options of the `Test.run/Test.configure` API. Use 74 | `-h/--help` to see them all. 75 | 76 | If you are using a build tool to run your tests, such as Rake or Ergo, shelling 77 | out to `rubytest` is a good way to go as it keeps your test environment as 78 | pristine as possible, e.g. 79 | 80 | desc "run tests" 81 | task :test 82 | sh "rubytest" 83 | end 84 | 85 | ### Via Rake Task 86 | 87 | There is also a Rake plug-in that can be installed called `rubytest-rake`. 88 | Surf over to its [webpage](http://rubyworks.github.com/rubytest-rake) for details. 89 | A basic example in its case, add to ones Rakefile: 90 | 91 | require 'rubytest/rake' 92 | 93 | Test::Rake::TestTask.new :test do |run| 94 | run.requires << 'lemon' 95 | run.test_files = 'test/test_*.rb' 96 | end 97 | 98 | See the Wiki for more detailed information on the different ways to run tests. 99 | 100 | ### Via Runner Scripts 101 | 102 | Out of the box Rubytest doesn't provide any special means for doing so, 103 | you simply write you own runner script using the Rubytest API. 104 | Here is the basic example: 105 | 106 | require 'rubytest' 107 | 108 | Test.run! do |r| 109 | r.loadpath 'lib' 110 | r.test_files 'test/test_*.rb' 111 | end 112 | 113 | Put that in a `test/runner.rb` script and run it with `ruby` or 114 | add `#!/usr/bin/env ruby` at the top and put it in `bin/test` 115 | setting `chmod u+x bin/test`. Either way, you now have your test 116 | runner. 117 | 118 | 119 | ## Requirements 120 | 121 | Rubytest uses the [ANSI](http://rubyworks.github.com/ansi) gem for color output. 122 | 123 | Because of the "foundational" nature of this library we will look at removing 124 | this dependency for future versions, but for early development the 125 | requirements does the job and does it well. 126 | 127 | 128 | ## Development 129 | 130 | Rubytest is still a bit of a "nuby" gem. Please feel OBLIGATED to help improve it ;-) 131 | 132 | Rubytest is a [Rubyworks](http://rubyworks.github.com) project. If you can't 133 | contribute code, you can still help out by contributing to our [development fund](https://www.gittip.com/on/github/rubyworks/). 134 | 135 | 136 | ## Reference Material 137 | 138 | * [Standard Definition Of Unit Test](http://c2.com/cgi/wiki?StandardDefinitionOfUnitTest) 139 | * [BRASS Assertions Standard](http:rubyworks.github.com/brass) 140 | 141 | 142 | ## Copyrights 143 | 144 | Copyright (c) 2011 Rubyworks 145 | 146 | Made available according to the terms of the BSD-2-Clause license. 147 | 148 | See LICENSE.txt for details. 149 | 150 | -------------------------------------------------------------------------------- /lib/rubytest/format/abstract.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test 4 | 5 | # 6 | module Reporters 7 | 8 | # Test Reporter Base Class 9 | class Abstract 10 | 11 | # 12 | def self.inherited(base) 13 | registry << base 14 | end 15 | 16 | # 17 | def self.registry 18 | @registry ||= [] 19 | end 20 | 21 | # 22 | def initialize(runner) 23 | @runner = runner 24 | #@source = {} 25 | 26 | # in case start_suite is overridden 27 | @start_time = Time.now 28 | end 29 | 30 | # 31 | attr :runner 32 | 33 | # 34 | def begin_suite(test_suite) 35 | @start_time = Time.now 36 | end 37 | 38 | # 39 | def begin_case(test_case) 40 | end 41 | 42 | # 43 | def begin_test(test) 44 | end 45 | 46 | # 47 | def skip_case(test_case) 48 | end 49 | 50 | # 51 | def skip_test(test) 52 | end 53 | 54 | # 55 | #def test(test) 56 | #end 57 | 58 | # 59 | def pass(test) 60 | end 61 | 62 | # 63 | def fail(test, exception) 64 | end 65 | 66 | # Report a test error. 67 | def error(test, exception) 68 | end 69 | 70 | # Report a pending test. 71 | def todo(test, exception) 72 | end 73 | 74 | # 75 | def end_test(test) 76 | end 77 | 78 | # 79 | def end_case(test_case) 80 | end 81 | 82 | # 83 | def end_suite(test_suite) 84 | end 85 | 86 | protected 87 | 88 | def record 89 | runner.recorder 90 | end 91 | 92 | # Is coverage information requested? 93 | #def cover? 94 | # runner.cover? 95 | #end 96 | 97 | # Count up the total number of tests. 98 | def total_count(suite) 99 | c = 0 100 | suite.each do |tc| 101 | if tc.respond_to?(:each) 102 | c += total_count(tc) 103 | else 104 | c += 1 105 | end 106 | end 107 | return c 108 | end 109 | 110 | # Common timestamp any reporter can use. 111 | def timestamp 112 | seconds = Time.now - @start_time 113 | 114 | "Finished in %.5fs, %.2f tests/s." % [seconds, total/seconds] 115 | end 116 | 117 | # 118 | def total 119 | @total ||= subtotal 120 | end 121 | 122 | # 123 | def subtotal 124 | [:todo, :pass, :fail, :error, :omit, :skip].inject(0) do |s,r| 125 | s += record[r.to_sym].size; s 126 | end 127 | end 128 | 129 | # TODO: lump skipped and omitted into one group ? 130 | 131 | TITLES = { 132 | :pass => 'passing', 133 | :fail => 'failures', 134 | :error => 'errors', 135 | :todo => 'pending', 136 | :omit => 'omissions', 137 | :skip => 'skipped' 138 | } 139 | 140 | # TODO: Add assertion counts (if reasonably possible). 141 | 142 | # Common tally stamp any reporter can use. 143 | # 144 | # @return [String] tally stamp 145 | def tally 146 | sizes = {} 147 | names = %w{pass error fail todo omit skip}.map{ |n| n.to_sym } 148 | names.each do |r| 149 | sizes[r] = record[r].size 150 | end 151 | 152 | #names.unshift(:tests) 153 | #sizes[:tests] = total 154 | 155 | s = [] 156 | names.each do |n| 157 | next unless sizes[n] > 0 158 | s << tally_item(n, sizes) 159 | end 160 | 161 | 'Executed ' + "#{total}".ansi(:bold) + ' tests with ' + s.join(', ') + '.' 162 | end 163 | 164 | # 165 | def tally_item(name, sizes) 166 | x = [] 167 | x << "%s" % sizes[name].to_s.ansi(:bold) 168 | x << " %s" % TITLES[name].downcase 169 | x << " (%.1f%%)" % ((sizes[name].to_f/total*100)) if runner.verbose? 170 | x.join('') 171 | end 172 | 173 | #-- 174 | # TODO: Matching `bin/ruby-test` is not robust. 175 | #++ 176 | 177 | # Remove reference to lemon library from backtrace. 178 | # 179 | # @param [Exception] exception 180 | # The error that was rasied. 181 | # 182 | # @return [Array] filtered backtrace 183 | def clean_backtrace(exception) 184 | trace = (Exception === exception ? exception.backtrace : exception) 185 | return trace if $DEBUG 186 | trace = trace.reject{ |t| $RUBY_IGNORE_CALLERS.any?{ |r| r =~ t }} 187 | trace = trace.map do |t| 188 | i = t.index(':in') 189 | i ? t[0...i] : t 190 | end 191 | #if trace.empty? 192 | # exception 193 | #else 194 | # exception.set_backtrace(trace) if Exception === exception 195 | # exception 196 | #end 197 | trace.uniq.map{ |bt| File.localname(bt) } 198 | end 199 | 200 | # That an exception, backtrace or source code text and line 201 | # number and return a CodeSnippet object. 202 | # 203 | # @return [CodeSnippet] code snippet 204 | def code(source, line=nil) 205 | case source 206 | when Exception 207 | CodeSnippet.from_backtrace(clean_backtrace(source.backtrace)) 208 | when Array 209 | CodeSnippet.from_backtrace(clean_backtrace(source)) 210 | else 211 | CodeSnippet.new(source, line) 212 | end 213 | end 214 | 215 | # 216 | def file_and_line(exception) 217 | line = clean_backtrace(exception)[0] 218 | return "" unless line 219 | i = line.rindex(':in') 220 | line = i ? line[0...i] : line 221 | File.localname(line) 222 | end 223 | 224 | # 225 | def file_and_line_array(exception) 226 | case exception 227 | when Exception 228 | line = exception.backtrace[0] 229 | else 230 | line = exception[0] # backtrace 231 | end 232 | return ["", 0] unless line 233 | i = line.rindex(':in') 234 | line = i ? line[0...i] : line 235 | f, l = File.localname(line).split(':') 236 | return [f, l.to_i] 237 | end 238 | 239 | # 240 | def file(exception) 241 | file_and_line_array(exception).first 242 | end 243 | 244 | # 245 | def line(exception) 246 | file_and_line_array(exception).last 247 | end 248 | 249 | end 250 | 251 | end 252 | 253 | end 254 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 0.8.0 / 2014-07-18 4 | 5 | We have whittled Rubytest down to it core functionaltiy and spun the rest 6 | off as separate plugin gems. The rubytest-suite gem is a metapackage 7 | loading this gem and a set of common plugins for convenience. 8 | 9 | Changes: 10 | 11 | * Spin-off report formats as separate gems. 12 | * Create rubytest-suite gem for convenience. 13 | 14 | 15 | ## 0.7.0 / 2013-02-18 16 | 17 | Version 0.7 is a significant release. The library has been simplified 18 | by spinning-off both the command-line tool and the Rake task as 19 | `rubytest-cli` and `rubytest-rake` respectively. This was done for a 20 | couple of good reasons: a) It focuses the the library on it's core 21 | functionality and b) and it makes the library suitable for becoming 22 | a Ruby standard library, should that ever become a possibility. 23 | 24 | Changes: 25 | 26 | * Spun off command-line tool as `rubytest-cli`. 27 | * Spun off Rake task as `rubytest-rake`. 28 | 29 | 30 | ## 0.6.1 / 2013-02-16 31 | 32 | Configurations can now supply a before and after procedure to be 33 | run right before or right after tests are run. This can be useful 34 | for setting up coverage tools Simplecov, which has to be setup 35 | before the applicable code is required but after all supporting 36 | test infrustructure is required. This release also fixes 37 | the `-c/--config` option, to prevent name clashes between gems and 38 | local config files. 39 | 40 | Changes: 41 | 42 | * Add before and after config procs. 43 | * Fix -c/--config loading. 44 | * Remove use of DotOpts, it is not good enough yet. 45 | * Move Rake plugin to separate plugin project. 46 | 47 | 48 | ## 0.6.0 / 2013-02-11 49 | 50 | This release of Ruby Test takes a hard step back and reconsiders how 51 | to handle configuration from the ground up. Current users of Ruby Test 52 | will probably have to make some adjustments. Our apologies for the extra 53 | work. But the whole thing simply got more complicated than it needed to 54 | be and it was decided that conventional simplicity, with the option 55 | unconventional complexity, was the best approach. 56 | 57 | To make a long story short... There is no default config file anymore. 58 | The `-p/--profile` command line option has been removed; replaced by 59 | a `-c/--config` option which requires a file relative to the working 60 | directory. In addition the configuration API had been changed from `Test.run` 61 | or `Test.configure`, to adopt the common convention, and it no longer takes 62 | a profile name for an argument. `Test.run` still has the same interface as 63 | `Test.configure` but it will now run tests immediately! So be sure to change 64 | that if you used it the past. Lastly, Ruby Test now supports DotOpts out of 65 | the box, so its easier then ever to setup default command line options. 66 | 67 | Changes: 68 | 69 | * Rename `Test.run` to `Test.configure` and remove profile argument. 70 | * Add new `Test.run` to immediately run tests. 71 | * Add `-c/--config` option for requiring project file. 72 | * Add `-C` and `-R` options for changing directory. 73 | * Add built-in support for DotOpts. 74 | * Deprecate profiles, removing `-p/--profile` cli option. 75 | * Deprecate Test::Runner configuration class methods. 76 | 77 | 78 | ## 0.5.4 / 2013-01-22 79 | 80 | This release simply updates configuraiton code to work with RC v0.4.0. 81 | 82 | Changes: 83 | 84 | * Call RC.configure explicitly. 85 | 86 | 87 | ## 0.5.3 / 2013-01-07 88 | 89 | Adjust Config to look for a project's `.index` file for load path, instead 90 | of a `.ruby` file. Also, this will only happen now if configured to do so, 91 | i.e. via the `-a/--autopath` option on the command line. 92 | 93 | Changes: 94 | 95 | * Fix config.rb to use .index file, instead of .ruby file. 96 | * Automtically prepend $LOAD_PATH only if asked to do so. 97 | 98 | 99 | ## 0.5.2 / 2012-07-20 100 | 101 | Courtier was renamed to RC. And the previous release overlooked the requirement 102 | on RC altogether. This release corrects the issue making it optional. 103 | 104 | Changes: 105 | 106 | * Make RC an optional requirement. 107 | 108 | 109 | ## 0.5.1 / 2012-04-30 110 | 111 | This release adds support for Courtier-based confgiration. 112 | You can now add `config 'rubytest'` entries into a project's 113 | Config.rb file, instead of creating a separate stand-alone 114 | config file just for test configuration. 115 | 116 | Changes: 117 | 118 | * Adds support for Courtier-based configuration. 119 | 120 | 121 | ## 0.5.0 / 2012-03-22 122 | 123 | This release improves the command line interface, the handling of 124 | configuration and provides a mechinism for adding custom test 125 | observers. The later can be used for things like mock library 126 | verifcation steps. 127 | 128 | Changes: 129 | 130 | * Make CLI an actual class. 131 | * Refactor configuration file lookup and support Confection. 132 | * Add customizable test observer via new Advice class. 133 | 134 | 135 | ## 0.4.2 / 2012-03-04 136 | 137 | Minor release to fix detection of pre-existance of Exception core 138 | extensions. 139 | 140 | Changes: 141 | 142 | * Fix detection of Exception extension methods. 143 | 144 | 145 | ## 0.4.1 / 2012-02-27 146 | 147 | RubyTest doesn't necessarily need to require 'brass' --albeit in the future 148 | that might end up being a dependency. For now we'll leave it aside. 149 | 150 | Changes: 151 | 152 | * Remove requirement on brass. 153 | 154 | 155 | ## 0.4.0 / 2012-02-25 156 | 157 | This release primarily consists of implementation tweaks. The most significant 158 | change is in support of exception priorities, which justify the major verison bump. 159 | 160 | Changes: 161 | 162 | * Add preliminary support for exception priorities. 163 | * Capture output for TAP-Y/J reporters. 164 | 165 | 166 | ## 0.3.0 / 2011-12-22 167 | 168 | Technically this is a fairly minor release that improves backtrace output 169 | and prepares the `LOAD_PATH` automtically if a `.ruby` file is present. 170 | However, it is significant in that the name of the gem has been changed 171 | from `test` to `rubytest`. 172 | 173 | Changes: 174 | 175 | * Change gem name to `rubytest`. 176 | * Improve backtrace filtering in reporters. 177 | * Setup `LOAD_PATH` based on .ruby file if present. 178 | 179 | 180 | ## 0.2.0 / 2011-08-10 181 | 182 | With this release Ruby Test is essentially feature complete. Of course there 183 | are plenty of tweaks and improvements yet to come, but Ruby Test is fully usable 184 | at this point. Only one major aspect of the design remains in question --the 185 | way per-testcase "before and after all" advice is handled. Other than that 186 | the API fairly solid, even as this early state of development. Always helps 187 | when you have a spec to go by! 188 | 189 | Changes: 190 | 191 | * Use Config class to look-up .test file. 192 | * Support hard testing, topic and pre-case setup. 193 | * Add autorun.rb runner script. 194 | * Add a test reporter to use for testing Ruby Test itself. 195 | * Improved dotprogess reporter's handling of omissions. 196 | * Add unit selection to test runner. 197 | 198 | 199 | ## 0.1.0 / 2011-07-30 200 | 201 | First release of Ruby Test. 202 | 203 | Changes: 204 | 205 | * It's Your Birthday! 206 | 207 | -------------------------------------------------------------------------------- /lib/rubytest/format/abstract_hash.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Test::Reporters 4 | 5 | # Hash Abstract is a base class for the TAP-Y 6 | # and TAP-J reporters. 7 | # 8 | class AbstractHash < Abstract 9 | 10 | # 11 | # @return [Hash] 12 | # 13 | def begin_suite(suite) 14 | require 'yaml' 15 | require 'stringio' 16 | 17 | @start_time = Time.now 18 | @case_level = 0 19 | @test_index = 0 20 | 21 | now = Time.now.strftime('%Y-%m-%d %H:%M:%S') 22 | 23 | h = { 24 | 'type' => 'suite', 25 | 'start' => now, 26 | 'count' => total_count(suite) 27 | } 28 | 29 | h['seed'] = suite.seed if suite.respond_to?(:seed) 30 | 31 | return h 32 | end 33 | 34 | # 35 | # @return [Hash] 36 | # 37 | def begin_case(test_case) 38 | h = {} 39 | h['type' ] = 'case' 40 | h['level'] = @case_level 41 | 42 | merge_subtype h, test_case 43 | merge_setup h, test_case 44 | merge_label h, test_case 45 | 46 | @case_level += 1 47 | 48 | return h 49 | end 50 | 51 | # 52 | def begin_test(test) 53 | @test_index += 1 54 | 55 | @stdout, @stderr = $stdout, $stderr 56 | $stdout, $stderr = StringIO.new, StringIO.new 57 | end 58 | 59 | # Ruby Test use the term "skip", where as TAP-Y/J uses "omit". 60 | # 61 | # @todo Maybe the terms can ultimately be reconciled. 62 | # 63 | # @return [Hash] 64 | # 65 | def skip_test(test) 66 | h = {} 67 | h['type' ] = 'test' 68 | h['status'] = 'omit' 69 | 70 | merge_subtype h, test 71 | merge_setup h, test 72 | merge_label h, test 73 | #merge_comparison h, test, exception 74 | #merge_coverage h, test 75 | merge_source h, test 76 | #merge_exception h, test, exception 77 | merge_output h 78 | merge_time h 79 | 80 | return h 81 | end 82 | 83 | # 84 | # @return [Hash] 85 | # 86 | def pass(test) #, backtrace=nil) 87 | h = {} 88 | h['type' ] = 'test' 89 | h['status'] = 'pass' 90 | 91 | merge_subtype h, test 92 | merge_setup h, test 93 | merge_label h, test 94 | #merge_comparison h, test, exception 95 | #merge_coverage h, test 96 | merge_source h, test 97 | merge_output h 98 | merge_time h 99 | 100 | return h 101 | end 102 | 103 | # 104 | # @return [Hash] 105 | # 106 | def fail(test, exception) 107 | h = {} 108 | h['type' ] = 'test' 109 | h['status'] = 'fail' 110 | 111 | merge_subtype h, test 112 | merge_priority h, test, exception 113 | merge_setup h, test 114 | merge_label h, test 115 | #merge_comparison h, test, exception 116 | #merge_coverage h, test 117 | merge_source h, test 118 | merge_exception h, test, exception 119 | merge_output h 120 | merge_time h 121 | 122 | return h 123 | end 124 | 125 | # 126 | # @return [Hash] 127 | # 128 | def error(test, exception) 129 | h = {} 130 | h['type' ] = 'test' 131 | h['status'] = 'error' 132 | 133 | merge_subtype h, test 134 | merge_priority h, test, exception 135 | merge_setup h, test 136 | merge_label h, test 137 | #merge_comparison h, test, exception 138 | #merge_coverage h, test 139 | merge_source h, test 140 | merge_exception h, test, exception, true 141 | merge_output h 142 | merge_time h 143 | 144 | return h 145 | end 146 | 147 | # 148 | # @return [Hash] 149 | # 150 | def todo(test, exception) 151 | h = {} 152 | h['type' ] = 'test' 153 | h['status'] = 'todo' 154 | 155 | merge_subtype h, test 156 | merge_priority h, test, exception 157 | merge_setup h, test 158 | merge_label h, test 159 | #merge_comparison h, test, exception 160 | #merge_coverage h, test 161 | merge_source h, test 162 | merge_exception h, test, exception 163 | merge_output h 164 | merge_time h 165 | 166 | return h 167 | end 168 | 169 | # 170 | def end_test(test) 171 | super(test) 172 | $stdout, $stderr = @stdout, @stderr 173 | end 174 | 175 | # 176 | def end_case(test_case) 177 | @case_level -= 1 178 | end 179 | 180 | # 181 | # @return [Hash] 182 | # 183 | def end_suite(suite) 184 | h = { 185 | 'type' => 'final', 186 | 'time' => Time.now - @start_time, 187 | 'counts' => { 188 | 'total' => total, 189 | 'pass' => record[:pass].size, 190 | 'fail' => record[:fail].size, 191 | 'error' => record[:error].size, 192 | 'omit' => record[:omit].size, 193 | 'todo' => record[:todo].size 194 | } 195 | } 196 | return h 197 | end 198 | 199 | private 200 | 201 | # For todo entries in particulr, i.e. NotImplementedError 202 | # exception, the return value represents the "todo" priority 203 | # level. The `Exception#priority` method returns an Integer 204 | # to set the priority level higher or lower, where higher 205 | # the number the more urgent the priority. 206 | # 207 | def merge_priority(hash, test, exception) 208 | level = exception.priority 209 | h['priority'] = level.to_i 210 | end 211 | 212 | # 213 | def merge_subtype(hash, test) 214 | hash['subtype'] = test.type.to_s if test.respond_to?(:type) 215 | end 216 | 217 | # RubyTest uses the term `topic`, where as TAP-Y/J uses `setup`. 218 | def merge_setup(hash, test) 219 | #hash['setup'] = test.setup.to_s if test.respond_to?(:setup) 220 | hash['setup'] = test.topic.to_s if test.respond_to?(:topic) 221 | end 222 | 223 | # Add test description to hash. 224 | def merge_label(hash, test) 225 | hash['label'] = test.to_s.strip 226 | end 227 | 228 | # TODO: This is not presently used. 229 | def merge_comparison(hash, test, exception) 230 | hash['returned'] = exception.returned 231 | hash['expected'] = exception.expected 232 | end 233 | 234 | # Add source location information to hash. 235 | def merge_source(hash, test) 236 | if test.respond_to?('source_location') 237 | file, line = source_location 238 | hash['file' ] = file 239 | hash['line' ] = line 240 | hash['source' ] = code(file, line).to_str 241 | hash['snippet'] = code(file, line).to_omap 242 | end 243 | end 244 | 245 | # Add exception subsection of hash. 246 | def merge_exception(hash, test, exception, bt=false) 247 | hash['exception'] = {} 248 | hash['exception']['file' ] = code(exception).file 249 | hash['exception']['line' ] = code(exception).line 250 | hash['exception']['source' ] = code(exception).to_str 251 | hash['exception']['snippet' ] = code(exception).to_omap 252 | hash['exception']['message' ] = exception.message 253 | hash['exception']['backtrace'] = clean_backtrace(exception) if bt 254 | end 255 | 256 | # TODO: This is still an "idea in progress" for both RybyTest and Tap-Y/J. 257 | # 258 | # There are a number of different types of code coverage. 259 | # 260 | # http://en.wikipedia.org/wiki/Code_coverage 261 | # 262 | # If we were to provide this, we'd have LOC coverage, probably 263 | # given as a list of `file:from-to`, and UNIT coverage, a list 264 | # of classes/modules and methods addressed. 265 | # 266 | def merge_coverage(hash, test) 267 | loc, unit = nil, nil 268 | if test.respond_to?(:loc) 269 | loc = test.loc 270 | end 271 | if test.respond_to?(:unit) 272 | unit = test.unit 273 | end 274 | if loc or unit 275 | hash['coverage'] = {} 276 | hash['coverage']['loc'] = loc if loc 277 | hash['coverage']['unit'] = unit if unit 278 | end 279 | end 280 | 281 | # 282 | def merge_output(hash) 283 | hash['stdout'] = $stdout.string 284 | hash['stderr'] = $stderr.string 285 | end 286 | 287 | # 288 | def merge_time(hash) 289 | hash['time'] = Time.now - @start_time 290 | end 291 | 292 | end 293 | 294 | end 295 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | module Indexer 7 | 8 | # Convert index data into a gemspec. 9 | # 10 | # Notes: 11 | # * Assumes all executables are in bin/. 12 | # * Does not yet handle default_executable setting. 13 | # * Does not yet handle platform setting. 14 | # * Does not yet handle required_ruby_version. 15 | # * Support for rdoc entries is weak. 16 | # 17 | class GemspecExporter 18 | 19 | # File globs to include in package --unless a manifest file exists. 20 | FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined?(FILES) 21 | 22 | # File globs to omit from FILES. 23 | OMIT = "Config.rb" unless defined?(OMIT) 24 | 25 | # Standard file patterns. 26 | PATTERNS = { 27 | :root => '{.index,Gemfile}', 28 | :bin => 'bin/*', 29 | :lib => 'lib/{**/}*', #.rb', 30 | :ext => 'ext/{**/}extconf.rb', 31 | :doc => '*.{txt,rdoc,md,markdown,tt,textile}', 32 | :test => '{test,spec}/{**/}*.rb' 33 | } unless defined?(PATTERNS) 34 | 35 | # For which revision of indexer spec is this converter intended? 36 | REVISION = 2013 unless defined?(REVISION) 37 | 38 | # 39 | def self.gemspec 40 | new.to_gemspec 41 | end 42 | 43 | # 44 | attr :metadata 45 | 46 | # 47 | def initialize(metadata=nil) 48 | @root_check = false 49 | 50 | if metadata 51 | root_dir = metadata.delete(:root) 52 | if root_dir 53 | @root = root_dir 54 | @root_check = true 55 | end 56 | metadata = nil if metadata.empty? 57 | end 58 | 59 | @metadata = metadata || YAML.load_file(root + '.index') 60 | 61 | if @metadata['revision'].to_i != REVISION 62 | warn "This gemspec exporter was not designed for this revision of index metadata." 63 | end 64 | end 65 | 66 | # 67 | def has_root? 68 | root ? true : false 69 | end 70 | 71 | # 72 | def root 73 | return @root if @root || @root_check 74 | @root_check = true 75 | @root = find_root 76 | end 77 | 78 | # 79 | def manifest 80 | return nil unless root 81 | @manifest ||= Dir.glob(root + 'manifest{,.txt}', File::FNM_CASEFOLD).first 82 | end 83 | 84 | # 85 | def scm 86 | return nil unless root 87 | @scm ||= %w{git hg}.find{ |m| (root + ".#{m}").directory? }.to_sym 88 | end 89 | 90 | # 91 | def files 92 | return [] unless root 93 | @files ||= \ 94 | if manifest 95 | File.readlines(manifest). 96 | map{ |line| line.strip }. 97 | reject{ |line| line.empty? || line[0,1] == '#' } 98 | else 99 | list = [] 100 | Dir.chdir(root) do 101 | FILES.split(/\s+/).each do |pattern| 102 | list.concat(glob(pattern)) 103 | end 104 | OMIT.split(/\s+/).each do |pattern| 105 | list = list - glob(pattern) 106 | end 107 | end 108 | list 109 | end.select{ |path| File.file?(path) }.uniq 110 | end 111 | 112 | # 113 | def glob_files(pattern) 114 | return [] unless root 115 | Dir.chdir(root) do 116 | Dir.glob(pattern).select do |path| 117 | File.file?(path) && files.include?(path) 118 | end 119 | end 120 | end 121 | 122 | def patterns 123 | PATTERNS 124 | end 125 | 126 | def executables 127 | @executables ||= \ 128 | glob_files(patterns[:bin]).map do |path| 129 | File.basename(path) 130 | end 131 | end 132 | 133 | def extensions 134 | @extensions ||= \ 135 | glob_files(patterns[:ext]).map do |path| 136 | File.basename(path) 137 | end 138 | end 139 | 140 | def name 141 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 142 | end 143 | 144 | def homepage 145 | page = ( 146 | metadata['resources'].find{ |r| r['type'] =~ /^home/i } || 147 | metadata['resources'].find{ |r| r['name'] =~ /^home/i } || 148 | metadata['resources'].find{ |r| r['name'] =~ /^web/i } 149 | ) 150 | page ? page['uri'] : false 151 | end 152 | 153 | def licenses 154 | metadata['copyrights'].map{ |c| c['license'] }.compact 155 | end 156 | 157 | def require_paths 158 | paths = metadata['paths'] || {} 159 | paths['load'] || ['lib'] 160 | end 161 | 162 | # 163 | # Convert to gemnspec. 164 | # 165 | def to_gemspec 166 | if has_root? 167 | Gem::Specification.new do |gemspec| 168 | to_gemspec_data(gemspec) 169 | to_gemspec_paths(gemspec) 170 | end 171 | else 172 | Gem::Specification.new do |gemspec| 173 | to_gemspec_data(gemspec) 174 | to_gemspec_paths(gemspec) 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Convert pure data settings. 181 | # 182 | def to_gemspec_data(gemspec) 183 | gemspec.name = name 184 | gemspec.version = metadata['version'] 185 | gemspec.summary = metadata['summary'] 186 | gemspec.description = metadata['description'] 187 | 188 | metadata['authors'].each do |author| 189 | gemspec.authors << author['name'] 190 | 191 | if author.has_key?('email') 192 | if gemspec.email 193 | gemspec.email << author['email'] 194 | else 195 | gemspec.email = [author['email']] 196 | end 197 | end 198 | end 199 | 200 | gemspec.licenses = licenses 201 | 202 | requirements = metadata['requirements'] || [] 203 | requirements.each do |req| 204 | next if req['optional'] 205 | next if req['external'] 206 | 207 | name = req['name'] 208 | groups = req['groups'] || [] 209 | 210 | version = gemify_version(req['version']) 211 | 212 | if groups.empty? or groups.include?('runtime') 213 | # populate runtime dependencies 214 | if gemspec.respond_to?(:add_runtime_dependency) 215 | gemspec.add_runtime_dependency(name,*version) 216 | else 217 | gemspec.add_dependency(name,*version) 218 | end 219 | else 220 | # populate development dependencies 221 | if gemspec.respond_to?(:add_development_dependency) 222 | gemspec.add_development_dependency(name,*version) 223 | else 224 | gemspec.add_dependency(name,*version) 225 | end 226 | end 227 | end 228 | 229 | # convert external dependencies into gemspec requirements 230 | requirements.each do |req| 231 | next unless req['external'] 232 | gemspec.requirements << ("%s-%s" % req.values_at('name', 'version')) 233 | end 234 | 235 | gemspec.homepage = homepage 236 | gemspec.require_paths = require_paths 237 | gemspec.post_install_message = metadata['install_message'] 238 | end 239 | 240 | # 241 | # Set gemspec settings that require a root directory path. 242 | # 243 | def to_gemspec_paths(gemspec) 244 | gemspec.files = files 245 | gemspec.extensions = extensions 246 | gemspec.executables = executables 247 | 248 | if Gem::VERSION < '1.7.' 249 | gemspec.default_executable = gemspec.executables.first 250 | end 251 | 252 | gemspec.test_files = glob_files(patterns[:test]) 253 | 254 | unless gemspec.files.include?('.document') 255 | gemspec.extra_rdoc_files = glob_files(patterns[:doc]) 256 | end 257 | end 258 | 259 | # 260 | # Return a copy of this file. This is used to generate a local 261 | # .gemspec file that can automatically read the index file. 262 | # 263 | def self.source_code 264 | File.read(__FILE__) 265 | end 266 | 267 | private 268 | 269 | def find_root 270 | root_files = patterns[:root] 271 | if Dir.glob(root_files).first 272 | Pathname.new(Dir.pwd) 273 | elsif Dir.glob("../#{root_files}").first 274 | Pathname.new(Dir.pwd).parent 275 | else 276 | #raise "Can't find root of project containing `#{root_files}'." 277 | warn "Can't find root of project containing `#{root_files}'." 278 | nil 279 | end 280 | end 281 | 282 | def glob(pattern) 283 | if File.directory?(pattern) 284 | Dir.glob(File.join(pattern, '**', '*')) 285 | else 286 | Dir.glob(pattern) 287 | end 288 | end 289 | 290 | def gemify_version(version) 291 | case version 292 | when /^(.*?)\+$/ 293 | ">= #{$1}" 294 | when /^(.*?)\-$/ 295 | "< #{$1}" 296 | when /^(.*?)\~$/ 297 | "~> #{$1}" 298 | else 299 | version 300 | end 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | Indexer::GemspecExporter.gemspec -------------------------------------------------------------------------------- /lib/rubytest/runner.rb: -------------------------------------------------------------------------------- 1 | module Test 2 | 3 | # Alias for `Test.configure`. 4 | # Use #run! to run tests immediately. 5 | # 6 | def self.run(profile=nil, &config_proc) 7 | configure(profile, &config_proc) 8 | end 9 | 10 | # Configure and run immediately. 11 | # 12 | # @todo Should this method return the success instead of exiting? 13 | # @todo Wrap run in at_exit ? 14 | # 15 | # @return [void] 16 | def self.run!(config=nil, &config_proc) 17 | begin 18 | success = Runner.run(config, &config_proc) 19 | exit -1 unless success 20 | rescue => error 21 | raise error if $DEBUG 22 | $stderr.puts('ERROR: ' + error.to_s) 23 | exit -1 24 | end 25 | end 26 | 27 | # The Test::Runner class handles the execution of tests. 28 | # 29 | class Runner 30 | 31 | # Run tests. 32 | # 33 | # @param [Config,Hash,String,Symbol] config 34 | # Either a Config instance, a hash to construct a Config 35 | # instance with, or a name of a configuration profile. 36 | # 37 | # @return [Boolean] Success of test run. 38 | def self.run(config=nil, &config_proc) #:yield: 39 | runner = Runner.new(config, &config_proc) 40 | runner.run 41 | end 42 | 43 | # Exceptions that are not caught by test runner. 44 | OPEN_ERRORS = [NoMemoryError, SignalException, Interrupt, SystemExit] 45 | 46 | # New Runner. 47 | # 48 | # @param [Config] config 49 | # Config instance. 50 | # 51 | def initialize(config) #:yield: 52 | @config = case config 53 | when Config then config 54 | when Hash then Config.new(config) 55 | else Test.configuration(config) 56 | end 57 | 58 | @config.apply! # apply lazy config block 59 | 60 | yield(@config) if block_given? 61 | 62 | @advice = Advice.new 63 | end 64 | 65 | # Handle all configuration via the config instance. 66 | attr :config 67 | 68 | # Test suite to run. This is a list of compliant test units and test cases. 69 | def suite 70 | config.suite 71 | end 72 | 73 | # 74 | # TODO: Cache or not? 75 | # 76 | def test_files 77 | #@test_files ||= resolve_test_files 78 | resolve_test_files 79 | end 80 | 81 | # Reporter format name, or name fragment, used to look up reporter class. 82 | def format 83 | config.format 84 | end 85 | 86 | # Show extra details in reports. 87 | def verbose? 88 | config.verbose? 89 | end 90 | 91 | # Instance of Advice is a special customizable observer. 92 | def advice 93 | @advice 94 | end 95 | 96 | # Define universal before advice. 97 | def before(type, &block) 98 | advice.join_before(type, &block) 99 | end 100 | 101 | # Define universal after advice. Can be used by mock libraries, 102 | # for example to run mock verification. 103 | def after(type, &block) 104 | advice.join_after(type, &block) 105 | end 106 | 107 | # Define universal upon advice. 108 | # 109 | # See {Advice} for valid join-points. 110 | def upon(type, &block) 111 | advice.join(type, &block) 112 | end 113 | 114 | # The reporter to use for ouput. 115 | attr :reporter 116 | 117 | # Record pass, fail, error and pending tests. 118 | attr :recorder 119 | 120 | # Array of observers, typically this just contains the recorder and 121 | # reporter instances. 122 | attr :observers 123 | 124 | # Run test suite. 125 | # 126 | # @return [Boolean] 127 | # That the tests ran without error or failure. 128 | # 129 | def run 130 | cd_chdir do 131 | Test::Config.load_path_setup if config.autopath? 132 | 133 | ignore_callers 134 | 135 | config.loadpath.flatten.each{ |path| $LOAD_PATH.unshift(path) } 136 | config.requires.flatten.each{ |file| require file } 137 | 138 | # Config before advice occurs after loadpath and require are 139 | # applied and before test files are required. 140 | config.before.call if config.before 141 | 142 | test_files.each do |test_file| 143 | require test_file 144 | end 145 | 146 | @reporter = reporter_load(format) 147 | @recorder = Recorder.new 148 | 149 | @observers = [advice, @recorder, @reporter] 150 | 151 | observers.each{ |o| o.begin_suite(suite) } 152 | run_thru(suite) 153 | observers.each{ |o| o.end_suite(suite) } 154 | 155 | config.after.call if config.after 156 | end 157 | 158 | recorder.success? 159 | end 160 | 161 | private 162 | 163 | # Add to $RUBY_IGNORE_CALLERS. 164 | # 165 | # @todo Improve on this! 166 | def ignore_callers 167 | ignore_path = File.expand_path(File.join(__FILE__, '../../..')) 168 | ignore_regexp = Regexp.new(Regexp.escape(ignore_path)) 169 | 170 | $RUBY_IGNORE_CALLERS ||= {} 171 | $RUBY_IGNORE_CALLERS << ignore_regexp 172 | $RUBY_IGNORE_CALLERS << /bin\/rubytest/ 173 | end 174 | 175 | # 176 | def run_thru(list) 177 | list.each do |t| 178 | if t.respond_to?(:each) 179 | run_case(t) 180 | elsif t.respond_to?(:call) 181 | run_test(t) 182 | else 183 | #run_note(t) ? 184 | end 185 | end 186 | end 187 | 188 | # Run a test case. 189 | # 190 | def run_case(tcase) 191 | if tcase.respond_to?(:skip?) && (reason = tcase.skip?) 192 | return observers.each{ |o| o.skip_case(tcase, reason) } 193 | end 194 | 195 | observers.each{ |o| o.begin_case(tcase) } 196 | 197 | if tcase.respond_to?(:call) 198 | tcase.call do 199 | run_thru( select(tcase) ) 200 | end 201 | else 202 | run_thru( select(tcase) ) 203 | end 204 | 205 | observers.each{ |o| o.end_case(tcase) } 206 | end 207 | 208 | # Run a test. 209 | # 210 | # @param [Object] test 211 | # The test to run, must repsond to #call. 212 | # 213 | def run_test(test) 214 | if test.respond_to?(:skip?) && (reason = test.skip?) 215 | return observers.each{ |o| o.skip_test(test, reason) } 216 | end 217 | 218 | observers.each{ |o| o.begin_test(test) } 219 | begin 220 | success = test.call 221 | if config.hard? && !success # TODO: separate run_test method to speed things up? 222 | raise Assertion, "failure of #{test}" 223 | else 224 | observers.each{ |o| o.pass(test) } 225 | end 226 | rescue *OPEN_ERRORS => exception 227 | raise exception 228 | rescue NotImplementedError => exception 229 | #if exception.assertion? # TODO: May require assertion? for todo in future 230 | observers.each{ |o| o.todo(test, exception) } 231 | #else 232 | # observers.each{ |o| o.error(test, exception) } 233 | #end 234 | rescue Exception => exception 235 | if exception.assertion? 236 | observers.each{ |o| o.fail(test, exception) } 237 | else 238 | observers.each{ |o| o.error(test, exception) } 239 | end 240 | end 241 | observers.each{ |o| o.end_test(test) } 242 | end 243 | 244 | # TODO: Make sure this filtering code is correct for the complex 245 | # condition that that ordered testcases can't have their tests 246 | # filtered individually (since they may depend on one another). 247 | 248 | # Filter cases based on selection criteria. 249 | # 250 | # @return [Array] selected test cases 251 | def select(cases) 252 | selected = [] 253 | if cases.respond_to?(:ordered?) && cases.ordered? 254 | cases.each do |tc| 255 | selected << tc 256 | end 257 | else 258 | cases.each do |tc| 259 | next if tc.respond_to?(:skip?) && tc.skip? 260 | next if !config.match.empty? && !config.match.any?{ |m| m =~ tc.to_s } 261 | 262 | if !config.units.empty? 263 | next unless tc.respond_to?(:unit) 264 | next unless config.units.find{ |u| tc.unit.start_with?(u) } 265 | end 266 | 267 | if !config.tags.empty? 268 | next unless tc.respond_to?(:tags) 269 | tc_tags = [tc.tags].flatten.map{ |t| t.to_s } 270 | next if (config.tags & tc_tags).empty? 271 | end 272 | 273 | selected << tc 274 | end 275 | end 276 | selected 277 | end 278 | 279 | # Get a reporter instance be name fragment. 280 | # 281 | # @return [Reporter::Abstract] 282 | # The test reporter instance. 283 | def reporter_load(format) 284 | format = DEFAULT_REPORT_FORMAT unless format 285 | format = format.to_s.downcase 286 | name = reporter_list.find{ |r| /^#{format}/ =~ r } || format 287 | 288 | begin 289 | require "rubytest/format/#{name}" 290 | rescue LoadError 291 | raise "mistyped or uninstalled report format" unless format 292 | end 293 | 294 | reporter = Test::Reporters.const_get(name.capitalize) 295 | reporter.new(self) 296 | end 297 | 298 | # List of known report formats. 299 | # 300 | # TODO: Could use finder gem to look these up, but that's yet another dependency. 301 | # 302 | KNOWN_FORMATS = %w{ 303 | dotprogress html progress outline summary tap tapy tapj test 304 | } 305 | 306 | # Returns a list of available report types. 307 | # 308 | # @return [Array] 309 | # The names of available reporters. 310 | def reporter_list 311 | return KNOWN_FORMATS.sort 312 | #list = Dir[File.dirname(__FILE__) + '/reporters/*.rb'] 313 | #list = list.map{ |r| File.basename(r).chomp('.rb') } 314 | #list = list.reject{ |r| /^abstract/ =~ r } 315 | #list.sort 316 | end 317 | 318 | # Files can be globs and directories which need to be 319 | # resolved to a list of files. 320 | # 321 | # @return [Array] 322 | def resolve_test_files 323 | list = config.files.flatten 324 | list = list.map{ |f| Dir[f] }.flatten 325 | list = list.map{ |f| File.directory?(f) ? Dir[File.join(f, '**/*.rb')] : f } 326 | list = list.flatten.uniq 327 | list = list.map{ |f| File.expand_path(f) } 328 | list 329 | end 330 | 331 | # Change to directory and run block. 332 | # 333 | # @raise [Errno::ENOENT] If directory does not exist. 334 | def cd_chdir(&block) 335 | if dir = config.chdir 336 | unless File.directory?(dir) 337 | raise Errno::ENOENT, "change directory doesn't exist -- `#{dir}'" 338 | end 339 | Dir.chdir(dir, &block) 340 | else 341 | block.call 342 | end 343 | end 344 | 345 | end 346 | 347 | end 348 | -------------------------------------------------------------------------------- /lib/rubytest/config.rb: -------------------------------------------------------------------------------- 1 | module Test 2 | 3 | # Stores test configurations. 4 | def self.config 5 | @config ||= {} 6 | end 7 | 8 | # Configure test run via a block then will be passed a `Config` instance. 9 | # 10 | # @return [Config] 11 | def self.configure(profile=nil, &block) 12 | if reconfigure? 13 | configuration(profile).apply(profile, &block) 14 | else 15 | config[profile.to_s] = Config.new(&block) 16 | end 17 | end 18 | 19 | # Reconfigure test run via a block then will be passed the {Config} instance. 20 | # Unlike `configure` this does not create a new Config instance, but instead 21 | # augments the current configuration. 22 | # 23 | # @return [Config] 24 | def self.reconfigure? 25 | @reconfigure 26 | end 27 | 28 | # Get the current configuration. 29 | # 30 | # @return [Config] 31 | def self.configuration(profile=nil, reconfigurable=false) 32 | @reconfigure = true if reconfigurable 33 | config[profile.to_s] ||= Config.new 34 | end 35 | 36 | ## 37 | # Encapsulates test run configruation. 38 | # 39 | class Config 40 | 41 | # Default report is in the old "dot-progress" format. 42 | DEFAULT_FORMAT = 'dotprogress' 43 | 44 | # Glob used to find project root directory. 45 | GLOB_ROOT = '{.index,.gemspec,.git,.hg,_darcs,lib/}' 46 | 47 | # 48 | def self.assertionless 49 | @assertionless 50 | end 51 | 52 | # 53 | def self.assertionless=(boolean) 54 | @assertionaless = !!boolean 55 | end 56 | 57 | # Find and cache project root directory. 58 | # 59 | # @return [String] Project's root path. 60 | def self.root 61 | @root ||= ( 62 | glob = GLOB_ROOT 63 | stop = '/' 64 | default = Dir.pwd 65 | dir = Dir.pwd 66 | until dir == stop 67 | break dir if Dir[File.join(dir, glob)].first 68 | dir = File.dirname(dir) 69 | end 70 | dir == stop ? default : dir 71 | ) 72 | end 73 | 74 | # Load and cache a project's `.index` file, if it has one. 75 | # 76 | # @return [Hash] YAML loaded `.index` file, or empty hash. 77 | def self.dotindex 78 | @dotindex ||= ( 79 | file = File.join(root, '.index') 80 | if File.exist?(file) 81 | require 'yaml' 82 | YAML.load_file(file) rescue {} 83 | else 84 | {} 85 | end 86 | ) 87 | end 88 | 89 | # Setup $LOAD_PATH based on project's `.index` file, if an 90 | # index file is not found, then default to `lib/` if it exists. 91 | # 92 | def self.load_path_setup 93 | if load_paths = (dotindex['paths'] || {})['lib'] 94 | load_paths.each do |path| 95 | $LOAD_PATH.unshift(File.join(root, path)) 96 | end 97 | else 98 | typical_load_path = File.join(root, 'lib') 99 | if File.directory?(typical_load_path) 100 | $LOAD_PATH.unshift(typical_load_path) 101 | end 102 | end 103 | end 104 | 105 | # Initialize new Config instance. 106 | def initialize(settings={}, &block) 107 | @format = nil 108 | @autopath = nil 109 | @chdir = nil 110 | @files = [] 111 | @tags = [] 112 | @match = [] 113 | @units = [] 114 | @requires = [] 115 | @loadpath = [] 116 | 117 | #apply_environment 118 | 119 | apply(settings) 120 | 121 | # save for lazy execution 122 | @block = block 123 | end 124 | 125 | # Apply lazy block. 126 | def apply! 127 | @block.call(self) if @block 128 | end 129 | 130 | # Evaluate configuration block. 131 | # 132 | # @return nothing 133 | def apply(hash={}, &block) 134 | hash.each do |k,v| 135 | send("#{k}=", v) 136 | end 137 | block.call(self) if block 138 | end 139 | 140 | # 141 | def name 142 | @name 143 | end 144 | 145 | # 146 | def name=(name) 147 | @name = name.to_s if name 148 | end 149 | 150 | # Default test suite ($TEST_SUITE). 151 | # 152 | # @return [Array] 153 | def suite 154 | @suite ||= $TEST_SUITE 155 | end 156 | 157 | # This is not really for general, but it is useful for Ruby Test's 158 | # own tests, to isolate tests. 159 | def suite=(test_objects) 160 | @suite = Array(test_objects) 161 | end 162 | 163 | # List of test files to run. 164 | # 165 | # @return [Array] 166 | def files(*list) 167 | @files.concat(makelist(list)) unless list.empty? 168 | @files 169 | end 170 | alias test_files files 171 | 172 | # Set the list of test files to run. Entries can be file glob patterns. 173 | # 174 | # @return [Array] 175 | def files=(list) 176 | @files = makelist(list) 177 | end 178 | alias test_files= files= 179 | 180 | # Automatically modify the `$LOAD_PATH`? 181 | # 182 | # @return [Boolean] 183 | def autopath? 184 | @autopath 185 | end 186 | 187 | # Automatically modify the `$LOAD_PATH`? 188 | # 189 | # @return [Boolean] 190 | def autopath=(boolean) 191 | @autopath = !! boolean 192 | end 193 | 194 | # Paths to add to $LOAD_PATH. 195 | # 196 | # @return [Array] 197 | def loadpath(*list) 198 | @loadpath.concat(makelist(list)) unless list.empty? 199 | @loadpath 200 | end 201 | alias :load_path :loadpath 202 | 203 | # Set paths to add to $LOAD_PATH. 204 | # 205 | # @return [Array] 206 | def loadpath=(list) 207 | @loadpath = makelist(list) 208 | end 209 | alias :load_path= :loadpath= 210 | 211 | # Scripts to require prior to tests. 212 | # 213 | # @return [Array] 214 | def requires(*list) 215 | @requires.concat(makelist(list)) unless list.empty? 216 | @requires 217 | end 218 | 219 | # Set the features that need to be required before the 220 | # test files. 221 | # 222 | # @return [Array] 223 | def requires=(list) 224 | @requires = makelist(list) 225 | end 226 | 227 | # Name of test report format, by default it is `dotprogress`. 228 | # 229 | # @return [String] format 230 | def format(name=nil) 231 | @format = name.to_s if name 232 | @format || DEFAULT_FORMAT 233 | end 234 | 235 | # Set test report format. 236 | # 237 | # @param [String] name 238 | # Name of the report format. 239 | # 240 | # @return [String] format 241 | def format=(name) 242 | @format = name.to_s 243 | end 244 | 245 | # Provide extra details in reports? 246 | # 247 | # @return [Boolean] 248 | def verbose? 249 | @verbose 250 | end 251 | 252 | # Set verbose mode. 253 | # 254 | # @return [Boolean] 255 | def verbose=(boolean) 256 | @verbose = !! boolean 257 | end 258 | 259 | # Selection of tags for filtering tests. 260 | # 261 | # @return [Array] 262 | def tags(*list) 263 | @tags.concat(makelist(list)) unless list.empty? 264 | @tags 265 | end 266 | 267 | # Set the list of tags for filtering tests. 268 | # 269 | # @return [Array] 270 | def tags=(list) 271 | @tags = makelist(list) 272 | end 273 | 274 | # Description match for filtering tests. 275 | # 276 | # @return [Array] 277 | def match(*list) 278 | @match.concat(makelist(list)) unless list.empty? 279 | @match 280 | end 281 | 282 | # Set the description matches for filtering tests. 283 | # 284 | # @return [Array] 285 | def match=(list) 286 | @match = makelist(list) 287 | end 288 | 289 | # List of units with which to filter tests. It is an array of strings 290 | # which are matched against module, class and method names. 291 | # 292 | # @return [Array] 293 | def units(*list) 294 | @units.concat(makelist(list)) unless list.empty? 295 | @units 296 | end 297 | 298 | # Set the list of units with which to filter tests. It is an array of 299 | # strings which are matched against module, class and method names. 300 | # 301 | # @return [Array] 302 | def units=(list) 303 | @units = makelist(list) 304 | end 305 | 306 | # Hard is a synonym for assertionless. 307 | # 308 | # @return [Boolean] 309 | def hard? 310 | @hard || self.class.assertionless 311 | end 312 | 313 | # Hard is a synonym for assertionless. 314 | # 315 | # @return [Boolean] 316 | def hard=(boolean) 317 | @hard = !! boolean 318 | end 319 | 320 | # Change to this directory before running tests. 321 | # 322 | # @return [String] 323 | def chdir(dir=nil) 324 | @chdir = dir.to_s if dir 325 | @chdir 326 | end 327 | 328 | # Set directory to change to before running tests. 329 | # 330 | # @return [String] 331 | def chdir=(dir) 332 | @chdir = dir.to_s 333 | end 334 | 335 | # Procedure to call, just before running tests. 336 | # 337 | # @return [Proc,nil] 338 | def before(&proc) 339 | @before = proc if proc 340 | @before 341 | end 342 | 343 | # Procedure to call, just after running tests. 344 | # 345 | # @return [Proc,nil] 346 | def after(&proc) 347 | @after = proc if proc 348 | @after 349 | end 350 | 351 | # The mode is only useful for specialied purposes, such as how 352 | # to run tests via the Rake task. It has no general purpose 353 | # and can be ignored in most cases. 354 | # 355 | # @return [String] 356 | def mode 357 | @mode 358 | end 359 | 360 | # The mode is only useful for specialied purposes, such as how 361 | # to run tests via the Rake task. It has no general purpose 362 | # and can be ignored in most cases. 363 | # 364 | # @return [String] 365 | def mode=(type) 366 | @mode = type.to_s 367 | end 368 | 369 | # Convert configuration to shell options, compatible with the 370 | # rubytest command line. 371 | # 372 | # DEPRECATE: Shell command is considered bad approach. 373 | # 374 | # @return [Array] 375 | def to_shellwords 376 | argv = [] 377 | argv << %[--autopath] if autopath? 378 | argv << %[--verbose] if verbose? 379 | argv << %[--format="#{format}"] if format 380 | argv << %[--chdir="#{chdir}"] if chdir 381 | argv << %[--tags="#{tags.join(';')}"] unless tags.empty? 382 | argv << %[--match="#{match.join(';')}"] unless match.empty? 383 | argv << %[--units="#{units.join(';')}"] unless units.empty? 384 | argv << %[--loadpath="#{loadpath.join(';')}"] unless loadpath.empty? 385 | argv << %[--requires="#{requires.join(';')}"] unless requires.empty? 386 | argv << files.join(' ') unless files.empty? 387 | argv 388 | end 389 | 390 | # Apply environment, overriding any previous configuration settings. 391 | # 392 | # @todo Better name for this method? 393 | # @return nothing 394 | def apply_environment_overrides 395 | @format = env(:format, @format) 396 | @autopath = env(:autopath, @autopath) 397 | @files = env(:files, @files) 398 | @match = env(:match, @match) 399 | @tags = env(:tags, @tags) 400 | @units = env(:units, @units) 401 | @requires = env(:requires, @requires) 402 | @loadpath = env(:loadpath, @loadpath) 403 | end 404 | 405 | # Apply environment as underlying defaults for unset configuration 406 | # settings. 407 | # 408 | # @return nothing 409 | def apply_environment_defaults 410 | @format = env(:format, @format) if @format.nil? 411 | @autopath = env(:autopath, @autopath) if @autopath.nil? 412 | @files = env(:files, @files) if @files.empty? 413 | @match = env(:match, @match) if @match.empty? 414 | @tags = env(:tags, @tags) if @tags.empty? 415 | @units = env(:units, @units) if @units.empty? 416 | @requires = env(:requires, @requires) if @requires.empty? 417 | @loadpath = env(:loadpath, @loadpath) if @loadpath.empty? 418 | end 419 | 420 | # Load configuration file for project. 421 | # 422 | # File names are prefixed with `./` to ensure they are from a local 423 | # source. An extension of `.rb` is assumed if the file lacks an one. 424 | # 425 | # @return [Boolean] true if file was required 426 | def load_config(file) 427 | file = file + '.rb' if File.extname(file) == '' 428 | 429 | if chdir 430 | file = File.join(chdir, file) 431 | else 432 | file = File.join('.', file) 433 | end 434 | 435 | if File.exist?(file) 436 | return require(file) 437 | else 438 | raise "config file not found -- `#{file}'" 439 | end 440 | end 441 | 442 | private 443 | 444 | # Lookup environment variable with name `rubytest_{name}`, 445 | # and transform in according to the type of the given 446 | # default. If the environment variable is not set then 447 | # returns the default. 448 | # 449 | # @return [Object] 450 | def env(name, default=nil) 451 | value = ENV["rubytest_#{name}".downcase] 452 | 453 | case default 454 | when Array 455 | return makelist(value) if value 456 | else 457 | return value if value 458 | end 459 | default 460 | end 461 | 462 | # If given a String then split up at `:` and `;` markers. 463 | # Otherwise ensure the list is an Array and the entries are 464 | # all strings and not empty. 465 | # 466 | # @return [Array] 467 | def makelist(list) 468 | case list 469 | when String 470 | list = list.split(/[:;]/) 471 | else 472 | list = Array(list).map{ |path| path.to_s } 473 | end 474 | list.reject{ |path| path.strip.empty? } 475 | end 476 | 477 | end 478 | 479 | end 480 | --------------------------------------------------------------------------------