pax_global_header00006660000000000000000000000064151026642020014510gustar00rootroot0000000000000052 comment=26edf65c9932de6062606766878cf57a453af300 power_assert-3.0.1/000077500000000000000000000000001510266420200142265ustar00rootroot00000000000000power_assert-3.0.1/.github/000077500000000000000000000000001510266420200155665ustar00rootroot00000000000000power_assert-3.0.1/.github/dependabot.yml000066400000000000000000000001661510266420200204210ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: 'github-actions' directory: '/' schedule: interval: 'weekly' power_assert-3.0.1/.github/workflows/000077500000000000000000000000001510266420200176235ustar00rootroot00000000000000power_assert-3.0.1/.github/workflows/ci.yml000066400000000000000000000024621510266420200207450ustar00rootroot00000000000000on: [ push, pull_request ] jobs: ruby-versions: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby min_version: 3.1 test: needs: ruby-versions name: >- Test (${{ matrix.ruby-version }} / ${{ matrix.os }} / TEST_SYMLINK: ${{ matrix.TEST_SYMLINK }}) strategy: fail-fast: false matrix: ruby-version: ${{ fromJson(needs.ruby-versions.outputs.versions) }} os: [ ubuntu-latest, macos-latest, windows-latest ] TEST_SYMLINK: [ yes, no ] include: - ruby-version: "3.3" os: "ubuntu-latest" TEST_SYMLINK: yes rubyopt: "--enable-frozen-string-literal" runs-on: ${{ matrix.os }} env: TEST_SYMLINK: ${{ matrix.TEST_SYMLINK }} continue-on-error: ${{ matrix.ruby-version == 'head' }} steps: - uses: actions/checkout@v5 - 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 RUBYOPT="${{ matrix.rubyopt }}" - name: Run after_script run: | bundle exec rake after_script power_assert-3.0.1/.gitignore000066400000000000000000000001271510266420200162160ustar00rootroot00000000000000/.bundle/ /coverage/ /Gemfile.local /Gemfile.lock /GPATH /GRTAGS /GTAGS /pkg/ /vendor/ power_assert-3.0.1/BSDL000066400000000000000000000023451510266420200147010ustar00rootroot00000000000000Copyright (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-3.0.1/COPYING000066400000000000000000000046411510266420200152660ustar00rootroot00000000000000Copyright (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-3.0.1/Gemfile000066400000000000000000000005571510266420200155300ustar00rootroot00000000000000source "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 group :development do gem 'test-unit' gem 'rake' gem 'simplecov' gem 'irb', '>= 1.3.1' gem 'fiddle' gem 'benchmark-ips' end power_assert-3.0.1/LEGAL000066400000000000000000000001611510266420200147730ustar00rootroot00000000000000LEGAL NOTICE INFORMATION ------------------------ All the files in this distribution are written by the author. power_assert-3.0.1/README.md000066400000000000000000000051271510266420200155120ustar00rootroot00000000000000# 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) * [irb-power_assert](https://github.com/kachick/irb-power_assert) * [power_p](https://github.com/k-tsj/power_p) ## Requirement * CRuby 3.1+ ## Configuration To colorize output messages, add require "power_assert/colorize" to your code. (It requires irb 1.3.1+) ## Known Limitations * Expressions must be on a single line. Splitting an assertion across multiple lines prevents any report from being generated, e.g.: ```ruby assert do # Reported func(foo: 0123456789, bar: "abcdefg") end assert do # Not reported func(foo: 0123456789, bar: "abcdefg") end ``` * Expressions must include at least one method call. Assertions without method calls generate no report, e.g.: ```ruby val = false assert do # Reported val == true end assert do # Not reported val end ``` * Return values from `method_missing` or `super` generate no report, e.g.: ```ruby class Foo def method_missing(*) :foo end end foo = Foo.new assert do # Not reported foo.foo end ``` * Avoid conditional branches inside assertions. Conditional logic may prevent a report from being generated, e.g.: ```ruby condition = true expected = false actual = true assert do # This fails, but nothing is reported condition ? expected == actual : expected == actual end ``` * (CRuby 4.0+) `.new` generates no report. Use `.[]` instead, e.g.: ```ruby s = Struct.new(:a) assert do # Not reported s.new(0) end assert do # Reported s[0] end ``` ## Reference * [Power Assert in Ruby (at RubyKaigi 2014) // Speaker Deck](https://speakerdeck.com/k_tsj/power-assert-in-ruby) power_assert-3.0.1/Rakefile000066400000000000000000000023371510266420200157000ustar00rootroot00000000000000require "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 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-3.0.1/benchmark/000077500000000000000000000000001510266420200161605ustar00rootroot00000000000000power_assert-3.0.1/benchmark/bm_yhpg.rb000066400000000000000000000036431510266420200201400ustar00rootroot00000000000000# 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-3.0.1/benchmark/helper.rb000066400000000000000000000004211510266420200177610ustar00rootroot00000000000000require '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-3.0.1/bin/000077500000000000000000000000001510266420200147765ustar00rootroot00000000000000power_assert-3.0.1/bin/console000077500000000000000000000002221510266420200163620ustar00rootroot00000000000000#!/usr/bin/env ruby require 'bundler/setup' require 'power_assert' begin require 'pry' Pry rescue LoadError require 'irb' IRB end.start power_assert-3.0.1/bin/setup000077500000000000000000000001121510266420200160560ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install power_assert-3.0.1/lib/000077500000000000000000000000001510266420200147745ustar00rootroot00000000000000power_assert-3.0.1/lib/power_assert.rb000066400000000000000000000032441510266420200200410ustar00rootroot00000000000000# power_assert.rb # # Copyright (C) 2014 Kazuki Tsujimoto begin captured = false target_thread = Thread.current TracePoint.new(:return, :c_return) do |tp| next unless Thread.current == target_thread captured = true unless tp.return_value and tp.callee_id raise '' end end.enable { __id__ } raise '' unless captured 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) clear_global_method_cache yield Context.new(assertion_proc_or_source, assertion_method, source_binding) 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) INTERNAL_LIB_DIRS.find do |_, dir| file.start_with?(dir) end end 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 module Empty end private_constant :Empty end power_assert-3.0.1/lib/power_assert/000077500000000000000000000000001510266420200175115ustar00rootroot00000000000000power_assert-3.0.1/lib/power_assert/colorize.rb000066400000000000000000000002221510266420200216600ustar00rootroot00000000000000require 'power_assert/configuration' PowerAssert.configure do |c| c.lazy_inspection = true c.colorize_message = true c.inspector = :pp end power_assert-3.0.1/lib/power_assert/configuration.rb000066400000000000000000000020431510266420200227040ustar00rootroot00000000000000module 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-3.0.1/lib/power_assert/context.rb000066400000000000000000000153021510266420200215230ustar00rootroot00000000000000require 'power_assert/parser' require 'power_assert/configuration' require 'power_assert/enable_tracepoint_events' require 'power_assert/inspector' module PowerAssert class Context Value = Struct.new(:name, :value, :lineno, :column, :display_offset) def initialize(assertion_proc_or_source, assertion_method, source_binding) @fired = false @target_thread = Thread.current 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 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 if (tp.event == :c_return && locs.length == 1 || tp.event == :return && locs.length <= 2) and Thread.current == @target_thread if @parser.path == locs.last.path and @parser.lineno == locs.last.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.last.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 at first' unless fired? @message ||= build_assertion_message(@parser, @return_values).freeze end def message_proc -> { message } 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 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 end power_assert-3.0.1/lib/power_assert/enable_tracepoint_events.rb000066400000000000000000000021651510266420200251040ustar00rootroot00000000000000require 'power_assert/configuration' if PowerAssert.configuration._redefinition module PowerAssert # set redefined flag basic_classes = [ Integer, Float, String, Array, Hash, Symbol, Time, Regexp, NilClass, TrueClass, FalseClass ] basic_operators = [ :+, :-, :*, :/, :%, :==, :===, :<, :<=, :<<, :[], :[]=, :length, :size, :empty?, :nil?, :succ, :>, :>=, :!, :!=, :=~, :freeze, :-@, :max, :min, # :call (it is just used for block call optimization) :&, :|, # :default (no specialized instruction for this) :pack, :include?, ] 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 refine Class do def new end end end end # disable optimization RubyVM::InstructionSequence.compile_option = { specialized_instruction: false } power_assert-3.0.1/lib/power_assert/inspector.rb000066400000000000000000000031101510266420200220370ustar00rootroot00000000000000require '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-3.0.1/lib/power_assert/parser.rb000066400000000000000000000165671510266420200213510ustar00rootroot00000000000000require '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) verbose, $VERBOSE = $VERBOSE, nil RubyVM::InstructionSequence.compile(str) true rescue SyntaxError false ensure $VERBOSE = verbose 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) case sexp in [:arg_paren | :assoc_splat | :fcall | :hash | :method_add_block | :string_literal | :return, s, *] extract_idents(s) in [:assign | :massign, _, s] extract_idents(s) in [:opassign, _, [_, op_name, [_, op_column]], s] extract_idents(s) + [Ident[:method, op_name.sub(/=\z/, ''), op_column]] in [:dyna_symbol, [Symbol, *] => s] # s can be [:string_content, [..]] while parsing an expression like { "a": 1 } extract_idents(s) in [:dyna_symbol, ss] ss.flat_map {|s| extract_idents(s) } in [:assoclist_from_args | :bare_assoc_hash | :paren | :string_embexpr | :regexp_literal | :xstring_literal, ss, *] ss.flat_map {|s| extract_idents(s) } in [:command, s0, s1] [s1, s0].flat_map {|s| extract_idents(s) } in [:assoc_new | :dot2 | :dot3 | :string_content, *ss] ss.flat_map {|s| extract_idents(s) } in [:unary, mid, s] handle_columnless_ident([], mid, extract_idents(s)) in [:binary, s0, op, s1] if AND_OR_OPS.include?(op) extract_idents(s0) + [Branch[extract_idents(s1), []]] in [:binary, s0, op, s1] handle_columnless_ident(extract_idents(s0), op, extract_idents(s1)) in [:call, recv, [op_sym, op_name, _], method] 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 in [:array, ss] ss ? ss.flat_map {|s| extract_idents(s) } : [] in [:command_call, s0, _, s1, s2] [s0, s2, s1].flat_map {|s| extract_idents(s) } in [:aref, s0, s1] handle_columnless_ident(extract_idents(s0), :[], extract_idents(s1)) in [:method_add_arg, s0, s1] case extract_idents(s0) in [] # idents(s0) may be empty(e.g. ->{}.()) extract_idents(s1) in [*is0, Branch[is1, []]] # Safe navigation operator is used. See :call clause also. is0 + [Branch[extract_idents(s1) + is1, []]] in [*is, i] is + extract_idents(s1) + [i] end in [:args_add_block, [:args_add_star, ss0, *ss1], _] (ss0 + ss1).flat_map {|s| extract_idents(s) } in [:args_add_block, ss, _] ss.flat_map {|s| extract_idents(s) } in [:vcall, [:@ident, name, [_, column]]] [Ident[@proc_local_variables.include?(name) ? :ref : :method, name, column]] in [:vcall, _] [] in [:program, [[:method_add_block, [:method_add_arg, [:fcall, [:@ident | :@const, ^@assertion_method_name, _]], _], [:brace_block | :do_block, _, ss]]]] ss.flat_map {|s| extract_idents(s) } in [:program, [s, *]] extract_idents(s) in [:ifop, s0, s1, s2] [*extract_idents(s0), Branch[extract_idents(s1), extract_idents(s2)]] in [:if | :unless, s0, ss0, [_, ss1]] [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, ss1.flat_map {|s| extract_idents(s) }]] in [:if | :unless, s0, ss0, _] [*extract_idents(s0), Branch[ss0.flat_map {|s| extract_idents(s) }, []]] in [:if_mod | :unless_mod, s0, s1] [*extract_idents(s0), Branch[extract_idents(s1), []]] in [:var_ref | :var_field, [:@kw, 'self', [_, column]]] [Ident[:ref, 'self', column]] in [:var_ref | :var_field, [:@ident | :@const | :@cvar | :@ivar | :@gvar, ref_name, [_, column]]] [Ident[:ref, ref_name, column]] in [:var_ref | :var_field, _] [] in [:@ident | :@const | :@op, method_name, [_, column]] [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-3.0.1/lib/power_assert/version.rb000066400000000000000000000000531510266420200215210ustar00rootroot00000000000000module PowerAssert VERSION = "3.0.1" end power_assert-3.0.1/power_assert.gemspec000066400000000000000000000017661510266420200203220ustar00rootroot00000000000000# 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.extra_rdoc_files = ['README.md'] s.rdoc_options = ['--main', 'README.md'] s.licenses = ['BSD-2-Clause', "Ruby"] end power_assert-3.0.1/test/000077500000000000000000000000001510266420200152055ustar00rootroot00000000000000power_assert-3.0.1/test/block_test.rb000066400000000000000000000315471510266420200176750ustar00rootroot00000000000000if ! RubyVM::InstructionSequence.compile_option[:specialized_instruction] warn "#{__FILE__}: specialized_instruction is set to false" end require_relative 'test_helper' 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 <= '3.5' < | | | Set | | false | # Set END end assert_equal expected, assertion_message { Set.new == Set.new([0]) } end t do var = [10,20] assert_equal <'; end end.new @obj.to_i = 0 end t do assert_equal < END @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(Encoding::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-3.0.1/test/safe_op_test.rb000066400000000000000000000027321510266420200202110ustar00rootroot00000000000000if ! 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 }