module Tiramisu
Stolen from [Pry](github.com/pry/pry)
Copyright © 2013 John Mair (banisterfiend) Copyright © 2015 Slee Woo (sleewoo)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Constants
- AssertionFailure
- DEFAULT_PATTERN
- GLOBAL_SETUPS
- GenericFailure
- INDENT
- PASTEL
- Skip
Public Instance Methods
# File lib/tiramisu/util/assert_throw.rb, line 31 def assert_expected_symbol_thrown object, expected_symbol return begin [ 'Expected :%s to be thrown at %s' % [expected_symbol, object[:caller]], 'Instead :%s thrown' % object[:thrown] ] end unless expected_symbol == object[:thrown] nil end
# File lib/tiramisu/util/assert_raise.rb, line 21 def assert_raised object return [ 'Expected a exception to be raised at %s' % object[:caller] ] unless object[:raised] nil end
# File lib/tiramisu/util/assert_raise.rb, line 3 def assert_raised_as_expected object, expected_type = nil, expected_message = nil, block = nil f = assert_raised(object) return f if f return assert_raised_as_expected_by_block(object, block) if block if expected_type f = assert_raised_expected_type(object, expected_type) return f if f end if expected_message f = assert_raised_expected_message(object, expected_message) return f if f end nil end
# File lib/tiramisu/util/assert_raise.rb, line 28 def assert_raised_as_expected_by_block object, block return [ 'Looks like wrong or no error raised at %s' % object[:caller], 'See validation block' ] unless block.call(object[:raised]) nil end
# File lib/tiramisu/util/assert_raise.rb, line 44 def assert_raised_expected_message object, expected_message regexp = expected_message.is_a?(Regexp) ? expected_message : /\A#{expected_message}\z/ return [ 'Expected the exception raised at %s' % object[:caller], 'to match "%s"' % regexp.source, 'Instead it looks like', pp(object[:raised].message) ] unless object[:raised].message =~ regexp nil end
# File lib/tiramisu/util/assert_raise.rb, line 36 def assert_raised_expected_type object, expected_type return [ 'Expected a %s to be raised at %s' % [expected_type, object[:caller]], 'Instead a %s raised' % object[:raised].class ] unless object[:raised].class == expected_type nil end
# File lib/tiramisu/util/assert_throw.rb, line 24 def assert_thrown object return [ 'Expected a symbol to be thrown at %s' % object[:caller] ] unless object[:thrown] nil end
# File lib/tiramisu/util/assert_throw.rb, line 3 def assert_thrown_as_expected object, expected_symbol = nil, block = nil f = assert_thrown(object) return f if f return assert_thrown_as_expected_by_block(object, block) if block if expected_symbol f = assert_expected_symbol_thrown(object, expected_symbol) return f if f end nil end
# File lib/tiramisu/util/assert_throw.rb, line 16 def assert_thrown_as_expected_by_block object, block return [ 'Looks like wrong or no symbol thrown at %s' % object[:caller], 'See validating block' ] unless block.call(object[:thrown]) nil end
# File lib/tiramisu.rb, line 34 def assertions @assertions ||= {} end
# File lib/tiramisu/util.rb, line 68 def augment_load_path file # adding ./ $:.unshift(pwd) unless $:.include?(pwd) # adding ./lib/ lib = pwd('lib') unless $:.include?(lib) $:.unshift(lib) if File.directory?(lib) end # adding file's dirname dir = File.dirname(file) $:.unshift(dir) unless $:.include?(dir) end
# File lib/tiramisu/util.rb, line 3 def call_block block {returned: block.call, caller: relative_source_location(block)}.freeze rescue UncaughtThrowError => e {raised: e, thrown: extract_thrown_symbol(e), caller: relative_source_location(block)}.freeze rescue Exception => e {raised: e, caller: relative_source_location(block)}.freeze end
# File lib/tiramisu/util.rb, line 53 def caller_to_source_location caller file, line = caller.split(/:(\d+):in.+/) [relative_location(file), line] end
# File lib/tiramisu.rb, line 54 def define_and_register_a_context label, block, parent units << define_context(label, block, parent) end
# File lib/tiramisu.rb, line 46 def define_and_register_a_spec label, block units << define_spec(label, block) end
# File lib/tiramisu.rb, line 50 def define_context label, block, parent define_unit_class(:context, label, block, [*parent.__ancestors__, parent].freeze) end
# File lib/tiramisu.rb, line 42 def define_spec label, block define_unit_class(:spec, label, block, [].freeze) end
define a class that will hold contexts and tests
@param [String, Symbol] type @param [String, Symbol] label @param [Proc] block @param [Array] ancestors @return [Unit]
# File lib/tiramisu.rb, line 66 def define_unit_class type, label, block, ancestors identity = identity_string(type, label, block).freeze Class.new ancestors.last || Unit do define_singleton_method(:__ancestors__) {ancestors} define_singleton_method(:__identity__) {identity} Tiramisu::GLOBAL_SETUPS.each {|b| class_exec(&b)} # execute given block only after global setups executed and all utility methods defined result = catch(:__tiramisu_skip__) {class_exec(&block)} Tiramisu.skips << result if result.is_a?(Skip) end end
define a module that when included will execute the given block on base
@param [Proc] block @return [Module]
# File lib/tiramisu.rb, line 83 def define_unit_module block block || raise(ArgumentError, 'missing block') Module.new do # any spec/context that will include this module will "inherit" it's logic # # @example # EnumeratorSpec = spec 'Enumerator tests' do # # some tests here # end # # spec Array do # include EnumeratorSpec # end # # spec Hash do # include EnumeratorSpec # end # define_singleton_method(:included) {|b| b.class_exec(&block)} end end
extract thrown symbol from given exception
@param exception
# File lib/tiramisu/util.rb, line 15 def extract_thrown_symbol exception return unless exception.is_a?(Exception) return unless s = exception.message.scan(/uncaught throw\W+(\w+)/).flatten[0] s.to_sym end
stop any code and report a failure
# File lib/tiramisu.rb, line 106 def fail reason, caller throw(:__tiramisu_status__, GenericFailure.new(Array(reason), caller)) end
# File lib/tiramisu/util.rb, line 58 def find_files pattern_or_files return pattern_or_files if pattern_or_files.is_a?(Array) Dir[pwd(pattern_or_files)] end
# File lib/tiramisu/util.rb, line 21 def identity_string type, label, block '%s %s (%s:%s)' % [ blue(type), label.inspect, *relative_source_location(block) ] end
# File lib/tiramisu/util.rb, line 63 def load_file file augment_load_path(file) require(file) end
# File lib/tiramisu/pretty_print.rb, line 29 def pp obj out = '' q = Tiramisu::PrettyPrint.new(out) q.guard_inspect_key { q.pp(obj) } q.flush out end
# File lib/tiramisu/util.rb, line 41 def pretty_backtrace e Array(e.backtrace).map {|l| relative_location(l)} end
# File lib/tiramisu/run.rb, line 3 def progress @progress ||= TTY::ProgressBar.new ':current of :total [:bar]' do |cfg| cfg.total = units.map {|u| u.tests.size}.reduce(:+) || 0 cfg.width = TTY::Screen.width cfg.complete = '.' end end
# File lib/tiramisu/util.rb, line 83 def pwd *args File.join(Dir.pwd, *args.map!(&:to_s)) end
# File lib/tiramisu/util.rb, line 45 def readline caller file, line = caller_to_source_location(caller) return unless file && line lines = ((@__readlinecache__ ||= {})[file] ||= File.readlines(file)) return unless line = lines[line.to_i - 1] line.sub(/(do|\{)\Z/, '').strip end
# File lib/tiramisu/util/refute_throw.rb, line 27 def refute_expected_symbol_thrown object, expected_symbol return [ 'Not expected :%s to be thrown' % expected_symbol, 'at %s' % object[:caller] ] if expected_symbol == object[:thrown] nil end
# File lib/tiramisu/util/refute_raise.rb, line 19 def refute_raised object, should_raise = false if should_raise return [ 'Expected a exception to be raised at %s' % object[:caller] ] unless object[:raised] else return [ 'A unexpected exception raised at %s' % object[:caller], object[:raised] ] if object[:raised] end nil end
# File lib/tiramisu/util/refute_raise.rb, line 3 def refute_raised_as_expected object, expected_type, expected_message, block = nil f = refute_raised(object, expected_type || expected_message) return f if f if expected_type f = refute_raised_expected_type(object, expected_type) return f if f end if expected_message f = refute_raised_expected_message(object, expected_message) return f if f end nil end
# File lib/tiramisu/util/refute_raise.rb, line 40 def refute_raised_expected_message object, expected_message regexp = expected_message.is_a?(Regexp) ? expected_message : /\A#{expected_message}\z/ return [ 'Not expected raised exception to match %s' % regexp.source, object[:raised] ] if object[:raised].message =~ regexp nil end
# File lib/tiramisu/util/refute_raise.rb, line 33 def refute_raised_expected_type object, expected_type return [ 'Not expected a %s to be raised at %s' % [object[:raised].class, object[:caller]], ] if object[:raised].class == expected_type nil end
# File lib/tiramisu/util/refute_throw.rb, line 14 def refute_thrown object, should_throw = false if should_throw return [ 'Expected a symbol to be thrown at %s' % object[:caller] ] unless object[:thrown] else return [ 'Not expected a symbol to be thrown at %s' % object[:caller] ] if object[:thrown] end nil end
# File lib/tiramisu/util/refute_throw.rb, line 3 def refute_thrown_as_expected object, expected_symbol, block = nil f = refute_thrown(object, expected_symbol) return f if f if expected_symbol f = refute_expected_symbol_thrown(object, expected_symbol) return f if f end nil end
# File lib/tiramisu/util.rb, line 37 def relative_location line line.sub(/\A#{pwd}\/+/, '') end
# File lib/tiramisu/util.rb, line 29 def relative_source_location block return unless block [ relative_location(block.source_location[0]), block.source_location[1] ] end
# File lib/tiramisu/run.rb, line 75 def render_AssertionFailure indent, failure progress.log indent + cyan('a: ') + pp(failure.object) progress.log indent + cyan('b: ') + failure.arguments.map {|a| pp(a)}.join(', ') end
# File lib/tiramisu/run.rb, line 71 def render_GenericFailure indent, failure Array(failure.reason).each {|l| progress.log(indent + l.to_s)} end
# File lib/tiramisu/run.rb, line 80 def render_caller indent, caller return unless caller progress.log indent + underline.bright_red(readline(caller)) end
# File lib/tiramisu/run.rb, line 66 def render_exception indent, failure progress.log indent + underline.bright_red([failure.class, failure.message]*': ') pretty_backtrace(failure).each {|l| progress.log(indent + l)} end
# File lib/tiramisu/run.rb, line 46 def render_failure unit, test_uuid, failure indent = '' [*unit.__ancestors__, unit].each do |u| progress.log indent + u.__identity__ indent << INDENT end progress.log indent + test_uuid indent << INDENT case failure when Exception render_exception(indent, failure) when GenericFailure, AssertionFailure render_caller(indent, failure.caller) __send__('render_%s' % failure.class.name.split('::').last, indent, failure) else progress.log(indent + failure.inspect) end progress.log '' end
# File lib/tiramisu/run.rb, line 85 def render_skips return if skips.empty? puts puts bold.magenta('Skips:') skips.each do |skip| puts ' %s (%s)' % [blue(skip['reason'] || 'skip'), relative_location(skip['caller'])] end puts end
# File lib/tiramisu/run.rb, line 11 def run pattern_or_files = DEFAULT_PATTERN find_files(pattern_or_files).shuffle.each do |file| r, w = IO.pipe pid = Kernel.fork do r.close load_file(file) progress.log '' progress.log blue(relative_location(file)) units.shuffle.each do |unit| # exceptions raised inside unit#__run__ will be treated as failures and pretty printed. # any other exceptions will be treated as implementation errors and ugly printed. unit.tests.keys.shuffle.each do |test| status = unit.run(test) if status.is_a?(Skip) w.puts({reason: status.reason, caller: status.caller}.to_json) else unless status == :__tiramisu_passed__ render_failure(unit, unit.tests[test], status) Kernel.exit(1) end end progress.advance end end end _, status = Process.waitpid2(pid) Kernel.exit(1) unless status.success? w.close while skip = r.gets skips << JSON.parse(skip) end end render_skips end
# File lib/tiramisu.rb, line 38 def skips @skips ||= [] end
# File lib/tiramisu.rb, line 30 def units @units ||= [] end