pax_global_header00006660000000000000000000000064143507354440014523gustar00rootroot0000000000000052 comment=c1dc7bb24ba26d531400cf134ae078bea1a75de2 power_assert-2.0.3/000077500000000000000000000000001435073544400142425ustar00rootroot00000000000000power_assert-2.0.3/.github/000077500000000000000000000000001435073544400156025ustar00rootroot00000000000000power_assert-2.0.3/.github/dependabot.yml000066400000000000000000000001661435073544400204350ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: 'github-actions' directory: '/' schedule: interval: 'weekly' power_assert-2.0.3/.github/workflows/000077500000000000000000000000001435073544400176375ustar00rootroot00000000000000power_assert-2.0.3/.github/workflows/ci.yml000066400000000000000000000016521435073544400207610ustar00rootroot00000000000000on: [ push, pull_request ] jobs: test: name: >- Test (${{ matrix.ruby-version }} / ${{ matrix.os }} / TEST_SYMLINK: ${{ matrix.TEST_SYMLINK }}) strategy: fail-fast: false matrix: ruby-version: [ 3.1, '3.0', 2.7, 2.6, 2.5, head ] os: [ ubuntu-latest, macos-latest ] TEST_SYMLINK: [ yes, no ] runs-on: ${{ matrix.os }} env: TEST_SYMLINK: ${{ matrix.TEST_SYMLINK }} continue-on-error: ${{ matrix.ruby-version == 'head' }} steps: - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - name: Run before_script run: | bundle exec rake before_script - name: Run the test suite run: | bundle exec rake - name: Run after_script run: | bundle exec rake after_script power_assert-2.0.3/.gitignore000066400000000000000000000001271435073544400162320ustar00rootroot00000000000000/.bundle/ /coverage/ /Gemfile.local /Gemfile.lock /GPATH /GRTAGS /GTAGS /pkg/ /vendor/ power_assert-2.0.3/BSDL000066400000000000000000000023451435073544400147150ustar00rootroot00000000000000Copyright (C) 2014 Kazuki Tsujimoto Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. power_assert-2.0.3/COPYING000066400000000000000000000046411435073544400153020ustar00rootroot00000000000000Copyright (C) 2014 Kazuki Tsujimoto You can redistribute it and/or modify it under either the terms of the 2-clause BSDL (see the file BSDL), or the conditions below: 1. You may make and give away verbatim copies of the source form of the software without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may modify your copy of the software in any way, provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or by allowing the author to include your modifications in the software. b) use the modified software only within your corporation or organization. c) give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 3. You may distribute the software in object code or binary form, provided that you do at least ONE of the following: a) distribute the binaries and library files of the software, together with instructions (in the manual page or equivalent) on where to get the original distribution. b) accompany the distribution with the machine-readable source of the software. c) give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 4. You may modify and include the part of the software into any other software (possibly commercial). But some files in the distribution are not written by the author, so that they are not under these terms. For the list of those files and their copying conditions, see the file LEGAL. 5. The scripts and library files supplied as input to or produced as output from the software do not automatically fall under the copyright of the software, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this software. 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. power_assert-2.0.3/Gemfile000066400000000000000000000003461435073544400155400ustar00rootroot00000000000000source "http://rubygems.org" gemspec # https://github.com/redmine/redmine/blob/3.0.4/Gemfile#L101 local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") if File.exist?(local_gemfile) eval_gemfile local_gemfile end power_assert-2.0.3/LEGAL000066400000000000000000000001611435073544400150070ustar00rootroot00000000000000LEGAL NOTICE INFORMATION ------------------------ All the files in this distribution are written by the author. power_assert-2.0.3/README.md000066400000000000000000000051211435073544400155200ustar00rootroot00000000000000# power_assert ## About Power Assert shows each value of variables and method calls in the expression. It is useful for testing, providing which value wasn't correct when the condition is not satisfied. Failure: assert { 3.times.to_a.include?(3) } | | | | | false | [0, 1, 2] # ## Related Projects In general, you don't need to use this library directly. Use following test frameworks or extensions instead. * [test-unit](https://github.com/test-unit/test-unit)(>= 3.0.0) * [Document](http://test-unit.github.io/test-unit/en/Test/Unit/Assertions.html#assert-instance_method) * [minitest-power_assert](https://github.com/hsbt/minitest-power_assert) * [rspec-power_assert](https://github.com/joker1007/rspec-power_assert) * [rspec-matchers-power_assert_matchers](https://github.com/kachick/rspec-matchers-power_assert_matchers) * [pry-power_assert](https://github.com/yui-knk/pry-power_assert) * [pry-byebug-power_assert](https://github.com/k-tsj/pry-byebug-power_assert) * [irb-power_assert](https://github.com/kachick/irb-power_assert) * [power_p](https://github.com/k-tsj/power_p) ## Requirement * CRuby 2.5+ ## Configuration To colorize output messages, add require "power_assert/colorize" to your code. (It requires CRuby 3.0.1+ or irb 1.3.1+) ## Known Limitations * Expressions must be put in one line. Expressions with folded long lines produce nothing report, e.g.: ```ruby assert do # reported func(foo: 0123456789, bar: "abcdefg") end assert do # won't be reported func(foo: 0123456789, bar: "abcdefg") end ``` * Expressions must have one or more method call. Expressions with no method call produce nothing report, e.g.: ```ruby val = false assert do # reported val == true end assert do # won't be reported val end ``` * Returned values from accessor methods, method missing, or "super" produce nothing report, e.g: ```ruby class Foo attr_accessor :val end foo = Foo.new foo.val = false assert do # reported (only the value of "foo" and the literal "true") foo.val == true end assert do # won't be reported foo.val end ``` * Expressions should not have conditional branches. Expressions with such conditional codes may produce nothing report, e.g.: ```ruby condition = true expected = false actual = true assert do # this will fail but nothing reported condition ? expected == actual : expected == actual end ``` ## Reference * [Power Assert in Ruby (at RubyKaigi 2014) // Speaker Deck](https://speakerdeck.com/k_tsj/power-assert-in-ruby) power_assert-2.0.3/Rakefile000066400000000000000000000024101435073544400157040ustar00rootroot00000000000000require "bundler/gem_tasks" require "rake/testtask" task :default => :test Rake::TestTask.new(:test) do |t| # helper(simplecov) must be required before loading power_assert helper_path = File.realpath("test/test_helper.rb") t.ruby_opts = ["-w", "-r#{helper_path}"] t.test_files = FileList["test/**/*_test.rb"].exclude do |i| begin next false unless defined?(RubyVM) RubyVM::InstructionSequence.compile(File.read(i)) false rescue SyntaxError true end end end # ruby/ruby:test/pathname/test_pathname.rb def has_symlink? begin File.symlink("", "") rescue NotImplementedError, Errno::EACCES return false rescue Errno::ENOENT end return true end SYMLINK_DIRS = ["lib", "test"] task :before_script do if ENV["TEST_SYMLINK"] == "yes" and has_symlink? SYMLINK_DIRS.each do |d| File.rename(d, ".#{d}") File.symlink(".#{d}", d) end end end task :after_script do SYMLINK_DIRS.each do |d| if File.symlink?(d) and File.directory?(".#{d}") File.unlink(d) File.rename(".#{d}", d) end unless File.directory?(d) raise "#{d} should be directory" end end end desc "Run the benchmark suite" task :benchmark do Dir.glob("benchmark/bm_*.rb").each do |f| load(f) end end power_assert-2.0.3/benchmark/000077500000000000000000000000001435073544400161745ustar00rootroot00000000000000power_assert-2.0.3/benchmark/bm_yhpg.rb000066400000000000000000000036431435073544400201540ustar00rootroot00000000000000# Yhpg # https://gist.github.com/yancya/37d79e02a91afcfdeed1 # # Author: yancya require_relative 'helper' class Yhpg MAPPING = [*'0'..'9', *'A'..'Z', *'a'..'z'] def initialize(data) @n, @list = data.split(":").tap { |n, list| break [n.to_i, list.split(",").map { |str| Yhpg.decode(str) }] } x_nominee = @list.map { |x, _| x }.map { |x| [x, x + 1] }.flatten.tap { |a| a.push(*[0, 62])}.uniq y_nominee = @list.map { |_, y| y }.map { |y| [y, y + 1] }.flatten.tap { |a| a.push(*[0, 62])}.uniq x_range_patterns = x_nominee.combination(2).map { |a| (a.min..a.max) } y_range_patterns = y_nominee.combination(2).map { |a| (a.min..a.max) } squares = x_range_patterns.product(y_range_patterns) targets = squares.select { |xrange, yrange| @list.select { |p| check(xrange, yrange, p) }.size == @n } @areas = targets.map { |x, y| [(x.max - x.min) * (y.max - y.min), x, y] } end def debug p [@areas.min_by(&:first), @areas.max_by(&:first)] end def Yhpg.decode(str) str.chars.map { |w| MAPPING.index(w) } end def check(xrange, yrange, target) x, y = target (xrange.include?(x) && xrange.include?(x + 1)) && (yrange.include?(y) && yrange.include?(y + 1)) end def output case res = [@areas.map(&:first).min, @areas.map(&:first).max].join(',') when ',' '-' else res end end end [ ["4:00,11,zz,yy,1y,y1", "3600,3721"], # /*05*/ ].each do |(actual, expect)| Benchmark.ips do |x| x.warmup = 1 x.time = 2 x.report("expr") { Yhpg.new(actual).output == expect } x.report("TracePoint.trace { expr }") { TracePoint.new(:return, :c_return) {}.enable { Yhpg.new(actual).output == expect } } x.report("assertion_message { expr }") { assertion_message { Yhpg.new(actual).output == expect } } x.report("assertion_message { !expr }") { assertion_message { not Yhpg.new(actual).output == expect } } x.compare! end end power_assert-2.0.3/benchmark/helper.rb000066400000000000000000000004211435073544400177750ustar00rootroot00000000000000require 'benchmark/ips' require 'power_assert' def assertion_message(source = nil, source_binding = TOPLEVEL_BINDING, &blk) ::PowerAssert.start(source || blk, assertion_method: __callee__, source_binding: source_binding) do |pa| pa.message unless pa.yield end end power_assert-2.0.3/bin/000077500000000000000000000000001435073544400150125ustar00rootroot00000000000000power_assert-2.0.3/bin/console000077500000000000000000000002221435073544400163760ustar00rootroot00000000000000#!/usr/bin/env ruby require 'bundler/setup' require 'power_assert' begin require 'pry' Pry rescue LoadError require 'irb' IRB end.start power_assert-2.0.3/bin/setup000077500000000000000000000001121435073544400160720ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install power_assert-2.0.3/lib/000077500000000000000000000000001435073544400150105ustar00rootroot00000000000000power_assert-2.0.3/lib/power_assert.rb000066400000000000000000000047511435073544400200610ustar00rootroot00000000000000# power_assert.rb # # Copyright (C) 2014 Kazuki Tsujimoto begin unless defined?(Byebug) captured = false TracePoint.new(:return, :c_return) do |tp| captured = true unless tp.return_value and tp.callee_id raise '' end end.enable { __id__ } raise '' unless captured end rescue raise LoadError, 'Fully compatible TracePoint API required' end require 'power_assert/context' require 'power_assert/configuration' require 'power_assert/version' module PowerAssert POWER_ASSERT_LIB_DIR = File.dirname(caller_locations(1, 1).first.path) INTERNAL_LIB_DIRS = {PowerAssert => POWER_ASSERT_LIB_DIR} private_constant :POWER_ASSERT_LIB_DIR, :INTERNAL_LIB_DIRS class << self def start(assertion_proc_or_source, assertion_method: nil, source_binding: TOPLEVEL_BINDING) if respond_to?(:clear_global_method_cache, true) clear_global_method_cache end yield BlockContext.new(assertion_proc_or_source, assertion_method, source_binding) end def trace(frame) begin raise 'Byebug is not started yet' unless Byebug.started? rescue NameError raise "PowerAssert.#{__method__} requires Byebug" end ctx = TraceContext.new(frame._binding) ctx.enable ctx end def app_caller_locations caller_locations.drop_while {|i| internal_file?(i.path) }.take_while {|i| ! internal_file?(i.path) } end def app_context? top_frame = caller_locations.drop_while {|i| i.path.start_with?(POWER_ASSERT_LIB_DIR) }.first top_frame and ! internal_file?(top_frame.path) end private def internal_file?(file) setup_internal_lib_dir(Byebug, :attach, 2) if defined?(Byebug) setup_internal_lib_dir(PryByebug, :start_with_pry_byebug, 2, Pry) if defined?(PryByebug) INTERNAL_LIB_DIRS.find do |_, dir| file.start_with?(dir) end end def setup_internal_lib_dir(lib, mid, depth, lib_obj = lib) unless INTERNAL_LIB_DIRS.key?(lib) INTERNAL_LIB_DIRS[lib] = lib_dir(lib_obj, mid, depth) end rescue NameError end def lib_dir(obj, mid, depth) File.expand_path('../' * depth, obj.method(mid).source_location[0]) end if defined?(RubyVM) CLEAR_CACHE_ISEQ = RubyVM::InstructionSequence.compile('using PowerAssert.const_get(:Empty)') private_constant :CLEAR_CACHE_ISEQ def clear_global_method_cache CLEAR_CACHE_ISEQ.eval end end end module Empty end private_constant :Empty end power_assert-2.0.3/lib/power_assert/000077500000000000000000000000001435073544400175255ustar00rootroot00000000000000power_assert-2.0.3/lib/power_assert/colorize.rb000066400000000000000000000002221435073544400216740ustar00rootroot00000000000000require 'power_assert/configuration' PowerAssert.configure do |c| c.lazy_inspection = true c.colorize_message = true c.inspector = :pp end power_assert-2.0.3/lib/power_assert/configuration.rb000066400000000000000000000020431435073544400227200ustar00rootroot00000000000000module PowerAssert class << self def configuration @configuration ||= Configuration[false, true, false, :p] end def configure yield configuration end end class Configuration < Struct.new(:lazy_inspection, :_redefinition, :colorize_message, :inspector) def colorize_message=(bool) if bool require 'irb/color' if inspector == :pp require 'irb/color_printer' end end super end def lazy_inspection=(bool) unless bool raise 'lazy_inspection option must be enabled when using pp' if inspector == :pp end super end def inspector=(inspector) case inspector when :pp raise 'lazy_inspection option must be enabled when using pp' unless lazy_inspection require 'pp' if colorize_message require 'irb/color_printer' end when :p else raise ArgumentError, "unknown inspector: #{inspector}" end super end end private_constant :Configuration end power_assert-2.0.3/lib/power_assert/context.rb000066400000000000000000000170631435073544400215450ustar00rootroot00000000000000require 'power_assert/configuration' require 'power_assert/enable_tracepoint_events' require 'power_assert/inspector' require 'power_assert/parser' module PowerAssert class Context Value = Struct.new(:name, :value, :lineno, :column, :display_offset) def initialize(base_caller_length) @fired = false @target_thread = Thread.current method_id_set = nil @return_values = [] @trace_return = TracePoint.new(:return, :c_return) do |tp| unless method_id_set next unless Thread.current == @target_thread method_id_set = @parser.method_id_set end method_id = tp.callee_id next if ! method_id_set[method_id] next if tp.event == :c_return and not (@parser.lineno == tp.lineno and @parser.path == tp.path) locs = PowerAssert.app_caller_locations diff = locs.length - base_caller_length if (tp.event == :c_return && diff == 1 || tp.event == :return && diff <= 2) and Thread.current == @target_thread idx = -(base_caller_length + 1) if @parser.path == locs[idx].path and @parser.lineno == locs[idx].lineno val = PowerAssert.configuration.lazy_inspection ? tp.return_value : InspectedValue.new(SafeInspectable.new(tp.return_value).inspect) @return_values << Value[method_id.to_s, val, locs[idx].lineno, nil] end end rescue Exception => e warn "power_assert: [BUG] Failed to trace: #{e.class}: #{e.message}" if e.respond_to?(:full_message) warn e.full_message.gsub(/^/, 'power_assert: ') end end end def message raise 'call #yield or #enable at first' unless fired? @message ||= build_assertion_message(@parser, @return_values).freeze end def message_proc -> { message } end private def fired? @fired end def build_assertion_message(parser, return_values) if PowerAssert.configuration.colorize_message line = IRB::Color.colorize_code(parser.line, ignore_error: true) else line = parser.line end path = detect_path(parser, return_values) return line unless path c2d = column2display_offset(parser.line) return_values, methods_in_path = find_all_identified_calls(return_values, path) return_values.zip(methods_in_path) do |i, j| unless i.name == j.name warn "power_assert: [BUG] Failed to get column: #{i.name}" return line end i.display_offset = c2d[j.column] end refs_in_path = path.find_all {|i| i.type == :ref } ref_values = refs_in_path.map {|i| Value[i.name, parser.binding.eval(i.name), parser.lineno, i.column, c2d[i.column]] } vals = (return_values + ref_values).find_all(&:display_offset).sort_by(&:display_offset).reverse return line if vals.empty? fmt = (0..vals[0].display_offset).map do |i| if vals.find {|v| v.display_offset == i } "%<#{i}>s" else line[i] == "\t" ? "\t" : ' ' end end.join lines = [] lines << line.chomp lines << sprintf(fmt, vals.each_with_object({}) {|v, h| h[:"#{v.display_offset}"] = '|' }).chomp vals.each do |i| inspected_val = SafeInspectable.new(Inspector.new(i.value, i.display_offset)).inspect inspected_val.each_line do |l| map_to = vals.each_with_object({}) do |j, h| h[:"#{j.display_offset}"] = [l, '|', ' '][i.display_offset <=> j.display_offset] end lines << encoding_safe_rstrip(sprintf(fmt, map_to)) end end lines.join("\n") end def detect_path(parser, return_values) return parser.call_paths.flatten.uniq if parser.method_id_set.empty? all_paths = parser.call_paths return_value_names = return_values.map(&:name) uniq_calls = uniq_calls(all_paths) uniq_call = return_value_names.find {|i| uniq_calls.include?(i) } detected_paths = all_paths.find_all do |path| method_names = path.find_all {|ident| ident.type == :method }.map(&:name) break [path] if uniq_call and method_names.include?(uniq_call) return_value_names == method_names end return nil unless detected_paths.length == 1 detected_paths[0] end def uniq_calls(paths) all_calls = enum_count_by(paths.map {|path| path.find_all {|ident| ident.type == :method }.map(&:name).uniq }.flatten) {|i| i } all_calls.find_all {|_, call_count| call_count == 1 }.map {|name, _| name } end def find_all_identified_calls(return_values, path) return_value_num_of_calls = enum_count_by(return_values, &:name) path_num_of_calls = enum_count_by(path.find_all {|ident| ident.type == :method }, &:name) identified_calls = return_value_num_of_calls.find_all {|name, num| path_num_of_calls[name] == num }.map(&:first) [ return_values.find_all {|val| identified_calls.include?(val.name) }, path.find_all {|ident| ident.type == :method and identified_calls.include?(ident.name) } ] end def enum_count_by(enum, &blk) Hash[enum.group_by(&blk).map{|k, v| [k, v.length] }] end def encoding_safe_rstrip(str) str.rstrip rescue ArgumentError, Encoding::CompatibilityError enc = str.encoding if enc.ascii_compatible? str.b.rstrip.force_encoding(enc) else str end end def column2display_offset(str) display_offset = 0 str.each_char.with_object([]) do |c, r| c.bytesize.times do r << display_offset end display_offset += c.ascii_only? ? 1 : 2 # FIXME end end end private_constant :Context class BlockContext < Context def initialize(assertion_proc_or_source, assertion_method, source_binding) super(0) if assertion_proc_or_source.respond_to?(:to_proc) @assertion_proc = assertion_proc_or_source.to_proc line = nil else @assertion_proc = source_binding.eval "Proc.new {#{assertion_proc_or_source}}" line = assertion_proc_or_source end @parser = Parser::DUMMY @trace_call = TracePoint.new(:call, :c_call) do if PowerAssert.app_context? and Thread.current == @target_thread @trace_call.disable locs = PowerAssert.app_caller_locations path = locs.last.path lineno = locs.last.lineno if File.exist?(path) line ||= File.open(path) {|fp| fp.each_line.drop(lineno - 1).first } end if line @parser = Parser.new(line, path, lineno, @assertion_proc.binding, assertion_method.to_s, @assertion_proc) end end end end def yield @fired = true invoke_yield(&@assertion_proc) end private def invoke_yield @trace_return.enable do @trace_call.enable do yield end end end end private_constant :BlockContext class TraceContext < Context def initialize(binding) target_frame, *base = PowerAssert.app_caller_locations super(base.length) path = target_frame.path lineno = target_frame.lineno if File.exist?(path) line = File.open(path) {|fp| fp.each_line.drop(lineno - 1).first } @parser = Parser.new(line, path, lineno, binding) else @parser = Parser::DUMMY end end def enable @fired = true @trace_return.enable end def disable @trace_return.disable end def enabled? @trace_return.enabled? end end private_constant :TraceContext end power_assert-2.0.3/lib/power_assert/enable_tracepoint_events.rb000066400000000000000000000025621435073544400251210ustar00rootroot00000000000000require 'power_assert/configuration' if defined?(RubyVM) if PowerAssert.configuration._redefinition module PowerAssert # set redefined flag basic_classes = [ Integer, Float, String, Array, Hash, Symbol, Time, Regexp, NilClass, TrueClass, FalseClass ] verbose = $VERBOSE begin $VERBOSE = nil [:Fixnum, :Bignum].each do |c| if Object.const_defined?(c) and (c = Object.const_get(c)) != Integer basic_classes << c end end ensure $VERBOSE = verbose end basic_operators = [ :+, :-, :*, :/, :%, :==, :===, :<, :<=, :<<, :[], :[]=, :length, :size, :empty?, :nil?, :succ, :>, :>=, :!, :!=, :=~, :freeze, :-@, :max, :min, # :call (it is just used for block call optimization) :&, :| ] basic_classes.each do |klass| basic_operators.each do |bop| if klass.public_method_defined?(bop) refine(klass) do define_method(bop) {} end end end end # bypass check_cfunc refine BasicObject do def ! end def == end end refine Module do def == end end end end # disable optimization RubyVM::InstructionSequence.compile_option = { specialized_instruction: false } end power_assert-2.0.3/lib/power_assert/inspector.rb000066400000000000000000000031061435073544400220600ustar00rootroot00000000000000require 'power_assert/configuration' begin require 'io/console/size' rescue LoadError end module PowerAssert class InspectedValue def initialize(value) @value = value end def inspect @value end end private_constant :InspectedValue class SafeInspectable def initialize(value) @value = value end def inspect inspected = @value.inspect if Encoding.compatible?(Encoding.default_external, inspected) inspected else begin "#{inspected.encode(Encoding.default_external)}(#{inspected.encoding})" rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError inspected.force_encoding(Encoding.default_external) end end rescue => e "InspectionFailure: #{e.class}: #{e.message.each_line.first}" end end private_constant :SafeInspectable class Inspector def initialize(value, indent) @value = value @indent = indent end def inspect if PowerAssert.configuration.colorize_message if PowerAssert.configuration.inspector == :pp console_width = IO.respond_to?(:console_size) ? IO.console_size[1] : 80 width = [console_width - 1 - @indent, 10].max IRB::ColorPrinter.pp(@value, '', width) else IRB::Color.colorize_code(@value.to_s, ignore_error: true) end else if PowerAssert.configuration.inspector == :pp PP.pp(@value, '') else @value.inspect end end end end private_constant :Inspector end power_assert-2.0.3/lib/power_assert/parser.rb000066400000000000000000000177531435073544400213630ustar00rootroot00000000000000require 'ripper' module PowerAssert class Parser Ident = Struct.new(:type, :name, :column) attr_reader :line, :path, :lineno, :binding def initialize(line, path, lineno, binding, assertion_method_name = nil, assertion_proc = nil) @line = line @line_for_parsing = (valid_syntax?(line) ? line : slice_expression(line)).b @path = path @lineno = lineno @binding = binding @proc_local_variables = binding.eval('local_variables').map(&:to_s) @assertion_method_name = assertion_method_name @assertion_proc = assertion_proc end def idents @idents ||= extract_idents(Ripper.sexp(@line_for_parsing)) end def call_paths collect_paths(idents).uniq end def method_id_set methods = idents.flatten.find_all {|i| i.type == :method } @method_id_set ||= methods.map(&:name).map(&:to_sym).each_with_object({}) {|i, h| h[i] = true } end private def valid_syntax?(str) return true unless defined?(RubyVM) begin verbose, $VERBOSE = $VERBOSE, nil RubyVM::InstructionSequence.compile(str) true rescue SyntaxError false ensure $VERBOSE = verbose end end def slice_expression(str) str = str.chomp str.sub!(/\A\s*(?:if|unless|elsif|case|while|until) /) {|i| ' ' * i.length } str.sub!(/\A\s*(?:\}|\]|end)?\./) {|i| ' ' * i.length } str.sub!(/[\{\.\\]\z/, '') str.sub!(/(?:&&|\|\|)\z/, '') str.sub!(/ (?:do|and|or)\z/, '') str end class Branch < Array end AND_OR_OPS = %i(and or && ||) # # Returns idents as graph structure. # # +--c--b--+ # extract_idents(Ripper.sexp('a&.b(c).d')) #=> a--+ +--d # +--------+ # def extract_idents(sexp) tag, * = sexp case tag when :arg_paren, :assoc_splat, :fcall, :hash, :method_add_block, :string_literal, :return extract_idents(sexp[1]) when :assign, :massign extract_idents(sexp[2]) when :opassign _, _, (_, op_name, (_, op_column)), s0 = sexp extract_idents(s0) + [Ident[:method, op_name.sub(/=\z/, ''), op_column]] when :dyna_symbol if sexp[1][0].kind_of?(Symbol) # sexp[1] can be [:string_content, [..]] while parsing { "a": 1 } extract_idents(sexp[1]) else sexp[1].flat_map {|s| extract_idents(s) } end when :assoclist_from_args, :bare_assoc_hash, :paren, :string_embexpr, :regexp_literal, :xstring_literal sexp[1].flat_map {|s| extract_idents(s) } when :command [sexp[2], sexp[1]].flat_map {|s| extract_idents(s) } when :assoc_new, :dot2, :dot3, :string_content sexp[1..-1].flat_map {|s| extract_idents(s) } when :unary handle_columnless_ident([], sexp[1], extract_idents(sexp[2])) when :binary op = sexp[2] if AND_OR_OPS.include?(op) extract_idents(sexp[1]) + [Branch[extract_idents(sexp[3]), []]] else handle_columnless_ident(extract_idents(sexp[1]), op, extract_idents(sexp[3])) end when :call _, recv, (op_sym, op_name, _), method = sexp with_safe_op = ((op_sym == :@op and op_name == '&.') or op_sym == :"&.") if method == :call handle_columnless_ident(extract_idents(recv), :call, [], with_safe_op) else extract_idents(recv) + (with_safe_op ? [Branch[extract_idents(method), []]] : extract_idents(method)) end when :array sexp[1] ? sexp[1].flat_map {|s| extract_idents(s) } : [] when :command_call [sexp[1], sexp[4], sexp[3]].flat_map {|s| extract_idents(s) } when :aref handle_columnless_ident(extract_idents(sexp[1]), :[], extract_idents(sexp[2])) when :method_add_arg idents = extract_idents(sexp[1]) if idents.empty? # idents may be empty(e.g. ->{}.()) extract_idents(sexp[2]) else if idents[-1].kind_of?(Branch) and idents[-1][1].empty? # Safe navigation operator is used. See :call clause also. idents[0..-2] + [Branch[extract_idents(sexp[2]) + idents[-1][0], []]] else idents[0..-2] + extract_idents(sexp[2]) + [idents[-1]] end end when :args_add_block _, (tag, ss0, *ss1), _ = sexp if tag == :args_add_star (ss0 + ss1).flat_map {|s| extract_idents(s) } else sexp[1].flat_map {|s| extract_idents(s) } end when :vcall _, (tag, name, (_, column)) = sexp if tag == :@ident [Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]] else [] end when :program _, ((tag0, (tag1, (tag2, (tag3, mname, _)), _), (tag4, _, ss))) = sexp if tag0 == :method_add_block and tag1 == :method_add_arg and tag2 == :fcall and (tag3 == :@ident or tag3 == :@const) and mname == @assertion_method_name and (tag4 == :brace_block or tag4 == :do_block) ss.flat_map {|s| extract_idents(s) } else _, (s0, *) = sexp extract_idents(s0) end when :ifop _, s0, s1, s2 = sexp [*extract_idents(s0), Branch[extract_idents(s1), extract_idents(s2)]] when :if, :unless _, s0, ss0, (_, ss1) = sexp [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, ss1 ? ss1.flat_map {|s| extract_idents(s) } : []]] when :if_mod, :unless_mod _, s0, s1 = sexp [*extract_idents(s0), Branch[extract_idents(s1), []]] when :var_ref, :var_field _, (tag, ref_name, (_, column)) = sexp case tag when :@kw if ref_name == 'self' [Ident[:ref, 'self', column]] else [] end when :@ident, :@const, :@cvar, :@ivar, :@gvar [Ident[:ref, ref_name, column]] else [] end when :@ident, :@const, :@op _, method_name, (_, column) = sexp [Ident[:method, method_name, column]] else [] end end def str_indices(str, re, offset, limit) idx = str.index(re, offset) if idx and idx <= limit [idx, *str_indices(str, re, idx + 1, limit)] else [] end end MID2SRCTXT = { :[] => '[', :+@ => '+', :-@ => '-', :call => '(' } def handle_columnless_ident(left_idents, mid, right_idents, with_safe_op = false) left_max = left_idents.flatten.max_by(&:column) right_min = right_idents.flatten.min_by(&:column) bg = left_max ? left_max.column + left_max.name.length : 0 ed = right_min ? right_min.column - 1 : @line_for_parsing.length - 1 mname = mid.to_s srctxt = MID2SRCTXT[mid] || mname re = / #{'\b' if /\A\w/ =~ srctxt} #{Regexp.escape(srctxt)} #{'\b' if /\w\z/ =~ srctxt} /x indices = str_indices(@line_for_parsing, re, bg, ed) if indices.length == 1 or !(right_idents.empty? and left_idents.empty?) ident = Ident[:method, mname, right_idents.empty? ? indices.first : indices.last] left_idents + right_idents + (with_safe_op ? [Branch[[ident], []]] : [ident]) else left_idents + right_idents end end def collect_paths(idents, prefixes = [[]], index = 0) if index < idents.length node = idents[index] if node.kind_of?(Branch) prefixes = node.flat_map {|n| collect_paths(n, prefixes, 0) } else prefixes = prefixes.map {|prefix| prefix + [node] } end collect_paths(idents, prefixes, index + 1) else prefixes end end class DummyParser < Parser def initialize super('', nil, nil, TOPLEVEL_BINDING) end def idents [] end def call_paths [] end end DUMMY = DummyParser.new end private_constant :Parser end power_assert-2.0.3/lib/power_assert/version.rb000066400000000000000000000000531435073544400215350ustar00rootroot00000000000000module PowerAssert VERSION = "2.0.3" end power_assert-2.0.3/power_assert.gemspec000066400000000000000000000024431435073544400203270ustar00rootroot00000000000000# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'power_assert/version' Gem::Specification.new do |s| s.name = 'power_assert' s.version = PowerAssert::VERSION s.authors = ['Kazuki Tsujimoto'] s.email = ['kazuki@callcc.net'] s.homepage = 'https://github.com/ruby/power_assert' s.summary = "Power Assert for Ruby" s.description = "Power Assert shows each value of variables and method calls in the expression. It is useful for testing, providing which value wasn't correct when the condition is not satisfied." s.files = `git ls-files -z`.split("\x0").reject do |f| f.match(%r{\A(?:test|spec|features|benchmark|bin)/}) end s.bindir = 'exe' s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) } s.require_paths = ['lib'] s.add_development_dependency 'test-unit' s.add_development_dependency 'rake' s.add_development_dependency 'simplecov' s.add_development_dependency 'bundler' s.add_development_dependency 'irb', '>= 1.3.1' s.add_development_dependency 'byebug' s.add_development_dependency 'benchmark-ips' s.extra_rdoc_files = ['README.md'] s.rdoc_options = ['--main', 'README.md'] s.licenses = ['BSD-2-Clause', "Ruby"] end power_assert-2.0.3/test/000077500000000000000000000000001435073544400152215ustar00rootroot00000000000000power_assert-2.0.3/test/block_test.rb000066400000000000000000000314031435073544400177000ustar00rootroot00000000000000if defined?(RubyVM) and ! RubyVM::InstructionSequence.compile_option[:specialized_instruction] warn "#{__FILE__}: specialized_instruction is set to false" end require_relative 'test_helper' require 'set' class TestBlockContext < Test::Unit::TestCase include PowerAssertTestHelper class BasicObjectSubclass < BasicObject def foo "foo" end end def Assertion(&blk) ::PowerAssert.start(blk, assertion_method: __callee__) do |pa| pa.yield pa.message end end define_method(:bmethod) do false end sub_test_case 'lazy_inspection' do t do PowerAssert.configure do |c| assert !c.lazy_inspection end assert_equal < | | 3 | false String END "0".class == "3".to_i.times.map {|i| i + 1 }.class } end t do assert_equal '', assertion_message { false } end t do assert_equal < | | | Set | | false | # Set END Set.new == Set.new([0]) } end t do var = [10,20] assert_equal <'; end end.new @obj.to_i = 0 end t do older = < END newer = < END assert_includes [older, newer], assertion_message { @obj.to_i.to_i.to_s } end t do older = < END newer = < END assert_includes [older, newer], assertion_message { true ? @obj.to_i.to_s : @obj.to_i } end end t do th = Thread.start do while true __id__ end end begin 20.times do assert_equal <:.*> # END assertion_message { @o.new.alias_of_iseq } end t do assert_match Regexp.new(<:.*>" | #<#:.*> # END assertion_message { @o.new.alias_of_cfunc } end end sub_test_case 'assertion_message_with_incompatible_encodings' do if Encoding.default_external == Encoding::UTF_8 t do a = "\u3042" def a.inspect super.encode('utf-16le') end assert_equal < [true, :pp], 'colorize_message' => [true, :p ], 'inspector(pp)' => [false, :pp] ) def test_colorized_pp((colorize_message, inspector)) begin PowerAssert.configure do |c| c.lazy_inspection = true c.colorize_message = colorize_message c.inspector = inspector end assert_equal < c, d => e)', [[:method, "b", 2], [:method, "c", 7], [:method, "d", 10], [:method, "e", 15], [:method, "a", 0]]], ['{a: b, c: d}', [[:method, "b", 4], [:method, "d", 10]]], ['{a => b, c => d}', [[:method, "a", 1], [:method, "b", 6], [:method, "c", 9], [:method, "d", 14]]], ['[[a, b], [c, d]]', [[:method, "a", 2], [:method, "b", 5], [:method, "c", 10], [:method, "d", 13]]], ['a b, c { d }', [[:method, "b", 2], [:method, "c", 5], [:method, "a", 0]]], ['assertion_message { a }', [[:method, "a", 20]]], ['a { b }', [[:method, "a", 0]]], ['A(B(c), d)', [[:method, "c", 4], [:method, "B", 2], [:method, "d", 8], [:method, "A", 0]]], ['a(b = c, (d, e = f), G = h)', [[:method, "c", 6], [:method, "f", 17], [:method, "h", 25], [:method, "a", 0]]], ['a(b, *c, d, e, f: g, h: i, **j)', [[:method, "b", 2], [:method, "c", 6], [:method, "d", 9], [:method, "e", 12], [:method, "g", 18], [:method, "i", 24], [:method, "j", 29], [:method, "a", 0]]], ['a == b + c', [[:method, "a", 0], [:method, "b", 5], [:method, "c", 9], [:method, "+", 7], [:method, "==", 2]]], ['var.var(var)', [[:ref, "var", 0], [:ref, "var", 8], [:method, "var", 4]]], ['a(B, @c, @@d, $e, f.self, self)', [[:ref, "B", 2], [:ref, "@c", 5], [:ref, "@@d", 9], [:ref, "$e", 14], [:method, "f", 18], [:method, "self", 20], [:ref, "self", 26], [:method, "a", 0]]], ['a.b c', [[:method, "a", 0], [:method, "c", 4], [:method, "b", 2]]], ['"a#{b}c"', [[:method, "b", 4]]], ['/a#{b}c/', [[:method, "b", 4]]], ['[]', []], ['a[0]', [[:method, "a", 0], [:method, "[]", 1]]], # not supported ['[][]', []], ['{}[]', [[:method, "[]", 2]]], ['!a', [[:method, "a", 1], [:method, "!", 0]]], ['+a', [[:method, "a", 1], [:method, "+@", 0]]], ['-a', [[:method, "a", 1], [:method, "-@", 0]]], ['! a == (+b == -c)', [[:method, "a", 2], [:method, "!", 0], [:method, "b", 9], [:method, "+@", 8], [:method, "c", 15], [:method, "-@", 14], [:method, "==", 11], [:method, "==", 4]]], ['%x{a#{b}c}', [[:method, "b", 6]]], ["a..b", [[:method, "a", 0], [:method, "b", 3]]], ["a...b", [[:method, "a", 0], [:method, "b", 4]]], [':"a#{b}c"', [[:method, "b", 5]]], ['return a, b', [[:method, "a", 7], [:method, "b", 10]]], ['->{}.()', [[:method, "call", 5]]], # not supported ['->{}.().()', []], ['a.(b)', [[:method, "a", 0], [:method, "b", 3], [:method, "call", 2]]], ['a.[](b)', [[:method, "a", 0], [:method, "b", 5], [:method, "[]", 2]]], ['a += b', [[:method, "b", 5], [:method, "+", 2]]], ['a if b', [[:method, "b", 5], [[[:method, "a", 0]], []]], [["b", "a"], ["b"]]], ['a unless b', [[:method, "b", 9], [[[:method, "a", 0]], []]], [["b", "a"], ["b"]]], ['if a then b; c else d; e end', [[:method, "a", 3], [[[:method, "b", 10], [:method, "c", 13]], [[:method, "d", 20], [:method, "e", 23]]]], [["a", "b", "c"], ["a", "d", "e"]]], ['if a then b end', [[:method, "a", 3], [[[:method, "b", 10]], []]], [["a", "b"], ["a"]]], ['unless a then b; c else d; e end', [[:method, "a", 7], [[[:method, "b", 14], [:method, "c", 17]], [[:method, "d", 24], [:method, "e", 27]]]], [["a", "b", "c"], ["a", "d", "e"]]], ['unless a then b end', [[:method, "a", 7], [[[:method, "b", 14]], []]], [["a", "b"], ["a"]]], ['a.b ? c.d : e.f', [[:method, "a", 0], [:method, "b", 2], [[[:method, "c", 6], [:method, "d", 8]], [[:method, "e", 12], [:method, "f", 14]]]], [["a", "b", "c", "d"], ["a", "b", "e", "f"]]], ['a.b ? (c ? d : e) : f.g', [[:method, "a", 0], [:method, "b", 2], [[[:method, "c", 7], [[[:method, "d", 11]], [[:method, "e", 15]]]], [[:method, "f", 20], [:method, "g", 22]]]], [["a", "b", "c", "d"], ["a", "b", "c", "e"], ["a", "b", "f", "g"]]], ['a ? 0 : 0', [[:method, "a", 0], [[], []]], [["a"]]], ['a && b || c', [[:method, "a", 0], [[[:method, "b", 5]], []], [[[:method, "c", 10]], []]], [["a", "b", "c"], ["a", "c"], ["a", "b"], ["a"]]], ['a and b or c', [[:method, "a", 0], [[[:method, "b", 6]], []], [[[:method, "c", 11]], []]], [["a", "b", "c"], ["a", "c"], ["a", "b"], ["a"]]], ].each_with_object({}) {|(source, expected_idents, expected_paths), h| h[source] = [expected_idents, expected_paths, source] } end def test_valid_syntax(*args) _test_parser(*args) end data do [ ['if a', [[:method, "a", 3]]], ['end.a', [[:method, "a", 4]]], ['a.', [[:method, "a", 0]]], ['a&&', [[:method, "a", 0]]], ['a||', [[:method, "a", 0]]], ['a do', [[:method, "a", 0]]], ].each_with_object({}) {|(source, expected_idents, expected_paths), h| h[source] = [expected_idents, expected_paths, source] } end def test_recoverable_invalid_syntax(*args) _test_parser(*args) end end power_assert-2.0.3/test/safe_op_test.rb000066400000000000000000000027571435073544400202340ustar00rootroot00000000000000if defined?(RubyVM) and ! RubyVM::InstructionSequence.compile_option[:specialized_instruction] warn "#{__FILE__}: specialized_instruction is set to false" end require_relative 'test_helper' class TestSafeOp < Test::Unit::TestCase include PowerAssertTestHelper data do [ ['a&.b(c) + d', [[:method, "a", 0], [[[:method, "c", 5], [:method, "b", 3]], []], [:method, "d", 10], [:method, "+", 8]], [["a", "c", "b", "d", "+"], ["a", "d", "+"]]], ['a&.b.c', [[:method, "a", 0], [[[:method, "b", 3]], []], [:method, "c", 5]], [["a", "b", "c"], ["a", "c"]]], ['a&.(b)', [[:method, "a", 0], [[[:method, "b", 4], [:method, "call", 3]], []]], [["a", "b", "call"], ["a"]]], ].each_with_object({}) {|(source, expected_idents, expected_paths), h| h[source] = [expected_idents, expected_paths, source] } end def test_parser(*args) _test_parser(*args) end sub_test_case 'branch' do t do assert_equal < { var = nil; -> { var } }.().binding, 'assertion_message') idents = parser.idents assert_equal expected_idents, map_recursive(idents, &:to_a), source if expected_paths assert_equal expected_paths, map_recursive(parser.call_paths, &:name), source end end def map_recursive(ary, &blk) ary.map {|i| Array === i ? map_recursive(i, &blk) : yield(i) } end def assertion_message(source = nil, source_binding = TOPLEVEL_BINDING, &blk) ::PowerAssert.start(source || blk, assertion_method: __callee__, source_binding: source_binding) do |pa| pa.yield pa.message end end def strip_color(str) str.gsub(/(\001)?\e\[.*?(\d)+m(\002)?/, '') end end RubyVM::InstructionSequence.compile_option = { specialized_instruction: true } power_assert-2.0.3/test/trace_test.rb000066400000000000000000000037521435073544400177120ustar00rootroot00000000000000if defined?(RubyVM) and ! RubyVM::InstructionSequence.compile_option[:specialized_instruction] warn "#{__FILE__}: specialized_instruction is set to false" end require_relative 'test_helper' class TestTraceContext < Test::Unit::TestCase include PowerAssertTestHelper class TestInterface < Byebug::Interface def readline(prompt) 'next' end def do_nothing(*) end alias puts do_nothing alias print do_nothing alias errmsg do_nothing end class << self def startup Byebug::Context.interface = TestInterface.new Byebug::Context.processor = PowerAssertTestHelper::TestProcessor Byebug::Setting[:autosave] = false end def iseq :iseq end define_method(:bmethod) do :bmethod end end setup do Byebug.start end teardown do Byebug.stop end def trace_test(expected_message, file, lineno, binding = TOPLEVEL_BINDING) code = "byebug\n#{expected_message.each_line.first}\n" lineno -= 1 # For 'byebug' at the first line eval(code, binding, file, lineno) pa = Byebug.current_context.__send__(:processor).pa_context assert_equal(expected_message, pa.message) assert_true(pa.enabled?) pa.disable assert_false(pa.enabled?) end def test_iseq trace_test(<