1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00

add Pry::Testable, and friends. (#1679)

* add Pry::Testable, and friends.

This commit replaces lib/pry/test/helper.rb by using category modules.
There's now:

* Pry::Testable:
  * Pry::Testable::Evalable
  * Pry::Testable::Mockable
  * Pry::Testable::Utility
  * Pry::Testable::PryTester

'include Pry::Testable' includes all of the above.
For the pry test suite it worked out best to include 'Pry::Testable' at the top-level.

In plugins I've written though this hasn't been the case, and I only normally need:
Pry::Testable::Evalable. So it reduces pollution for the third-party cases, which this
code is intended for since it lives within lib/ of the project.

What do you think?

* breaking change: add set_testenv_variables and unset_testenv_variables

* breaking change: add Pry::Testable::Variables, and rename

constant_scope() to temporary_constants().

* breaking change: rename inject_var as insert_variable, and move its

definition to Pry::Testable::Variables.

* include Pry::Testable::Evalable & include Pry::Testable::Variables into

the top-level, but keep the other two modules spec-local.

* document third argument of insert_variable.

* update CHANGELOG.md

* note changelog entry is a breaking change

* update documentation
This commit is contained in:
r-obert 2017-11-04 05:32:31 +01:00 committed by GitHub
parent 49497498ca
commit 4391aa863e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 288 additions and 210 deletions

View file

@ -2,14 +2,17 @@
#### Features
* Add Pry::Testable, an improved modular replacement for PryTestHelpers.
**breaking change**. [#1679](https://github.com/pry/pry/pull/1679).
* Add a new category module: "Pry::Platform". Loosely related to #1668 below.
[#1670](https://github.com/pry/pry/pull/1670)
* Add `mac_osx?` and `linux?` utility functions to Pry::Helpers::BaseHelpers.
[#1668](https://github.com/pry/pry/pull/1668)
[#1668](https://github.com/pry/pry/pull/1668).
* Add utility functions for drawing colorised text on a colorised background.
[#1673](https://github.com/pry/pry/pull/1673)
[#1673](https://github.com/pry/pry/pull/1673).
#### Bug fixes

View file

@ -1,179 +0,0 @@
require 'pry'
# in case the tests call reset_defaults, ensure we reset them to
# amended (test friendly) values
class << Pry
alias_method :orig_reset_defaults, :reset_defaults
def reset_defaults
orig_reset_defaults
Pry.config.color = false
Pry.config.pager = false
Pry.config.should_load_rc = false
Pry.config.should_load_local_rc= false
Pry.config.should_load_plugins = false
Pry.config.history.should_load = false
Pry.config.history.should_save = false
Pry.config.correct_indent = false
Pry.config.hooks = Pry::Hooks.new
Pry.config.collision_warning = false
end
end
Pry.reset_defaults
# A global space for storing temporary state during tests.
module PryTestHelpers
module_function
# inject a variable into a binding
def inject_var(name, value, b)
Pry.current[:pry_local] = value
b.eval("#{name} = ::Pry.current[:pry_local]")
ensure
Pry.current[:pry_local] = nil
end
def constant_scope(*names)
names.each do |name|
Object.remove_const name if Object.const_defined?(name)
end
yield
ensure
names.each do |name|
Object.remove_const name if Object.const_defined?(name)
end
end
# Open a temp file and yield it to the block, closing it after
# @return [String] The path of the temp file
def temp_file(ext='.rb')
file = Tempfile.new(['pry', ext])
yield file
ensure
file.close(true) if file
File.unlink("#{file.path}c") if File.exist?("#{file.path}c") # rbx
end
def unindent(*args)
Pry::Helpers::CommandHelpers.unindent(*args)
end
def mock_command(cmd, args=[], opts={})
output = StringIO.new
pry = Pry.new(output: output)
ret = cmd.new(opts.merge(pry_instance: pry, :output => output)).call_safely(*args)
Struct.new(:output, :return).new(output.string, ret)
end
def mock_exception(*mock_backtrace)
StandardError.new.tap do |e|
e.define_singleton_method(:backtrace) { mock_backtrace }
end
end
def inner_scope
catch(:inner_scope) do
yield ->{ throw(:inner_scope, self) }
end
end
end
def pry_tester(*args, &block)
if args.length == 0 || args[0].is_a?(Hash)
args.unshift(Pry.toplevel_binding)
end
PryTester.new(*args).tap do |t|
(class << t; self; end).class_eval(&block) if block
end
end
def pry_eval(*eval_strs)
if eval_strs.first.is_a? String
binding = Pry.toplevel_binding
else
binding = Pry.binding_for(eval_strs.shift)
end
pry_tester(binding).eval(*eval_strs)
end
class PryTester
extend Pry::Forwardable
attr_reader :pry, :out
def_delegators :@pry, :eval_string, :eval_string=
def initialize(target = TOPLEVEL_BINDING, options = {})
@pry = Pry.new(options.merge(:target => target))
@history = options[:history]
@pry.inject_sticky_locals!
reset_output
end
def eval(*strs)
reset_output
result = nil
strs.flatten.each do |str|
# Check for space prefix. See #1369.
if str !~ /^\s\S/
str = "#{str.strip}\n"
end
@history.push str if @history
if @pry.process_command(str)
result = last_command_result_or_output
else
result = @pry.evaluate_ruby(str)
end
end
result
end
def push(*lines)
Array(lines).flatten.each do |line|
@pry.eval(line)
end
end
def push_binding(context)
@pry.push_binding context
end
def last_output
@out.string if @out
end
def process_command(command_str)
@pry.process_command(command_str) or raise "Not a valid command"
last_command_result_or_output
end
def last_command_result
result = Pry.current[:pry_cmd_result]
result.retval if result
end
protected
def last_command_result_or_output
result = last_command_result
if result != Pry::Command::VOID_VALUE
result
else
last_output
end
end
def reset_output
@out = StringIO.new
@pry.output = @out
end
end

70
lib/pry/testable.rb Normal file
View file

@ -0,0 +1,70 @@
# good idea ???
# if you're testing pry plugin you should require pry by yourself, no?
require 'pry' if not defined?(Pry)
module Pry::Testable
extend self
require_relative "testable/pry_tester"
require_relative "testable/evalable"
require_relative "testable/mockable"
require_relative "testable/variables"
require_relative "testable/utility"
#
# When {Pry::Testable} is included into another module or class,
# the following modules are also included: {Pry::Testable::Mockable},
# {Pry::Testable::Evalable}, {Pry::Testable::Variables}, and
# {Pry::Testable::Utility}.
#
# @note
# Each of the included modules mentioned above may also be used
# standalone or in a pick-and-mix fashion.
#
# @param [Module] mod
# A class or module.
#
# @return [void]
#
def self.included(mod)
mod.module_eval do
include Pry::Testable::Mockable
include Pry::Testable::Evalable
include Pry::Testable::Variables
include Pry::Testable::Utility
end
end
TEST_DEFAULTS = {
color: false,
pager: false,
should_load_rc: false,
should_load_local_rc: false,
correct_indent: false,
collison_warning: false,
history: {
should_load: false,
should_save: false
}
}
private_constant :TEST_DEFAULTS
#
# Sets various configuration options that make Pry optimal for a test
# environment, see source code for complete details.
#
# @return [void]
#
def self.set_testenv_variables
Pry.config = Pry::Config.from_hash(TEST_DEFAULTS, Pry::Config::Default.new)
Pry.config.hooks = Pry::Hooks.new
end
#
# Reset the Pry configuration to their default values.
#
# @return [void]
#
def self.unset_testenv_variables
Pry.config = Pry::Config.from_hash({}, Pry::Config::Default.new)
end
end

View file

@ -0,0 +1,15 @@
module Pry::Testable::Evalable
def pry_tester(*args, &block)
if args.length == 0 || args[0].is_a?(Hash)
args.unshift(Pry.toplevel_binding)
end
Pry::Testable::PryTester.new(*args).tap do |t|
t.singleton_class.class_eval(&block) if block
end
end
def pry_eval(*eval_strs)
b = String === eval_strs.first ? Pry.toplevel_binding : Pry.binding_for(eval_strs.shift)
pry_tester(b).eval(*eval_strs)
end
end

View file

@ -0,0 +1,14 @@
module Pry::Testable::Mockable
def mock_command(cmd, args=[], opts={})
output = StringIO.new
pry = Pry.new(output: output)
ret = cmd.new(opts.merge(pry_instance: pry, :output => output)).call_safely(*args)
Struct.new(:output, :return).new(output.string, ret)
end
def mock_exception(*mock_backtrace)
StandardError.new.tap do |e|
e.define_singleton_method(:backtrace) { mock_backtrace }
end
end
end

View file

@ -0,0 +1,73 @@
class Pry::Testable::PryTester
extend Pry::Forwardable
attr_reader :pry, :out
def_delegators :@pry, :eval_string, :eval_string=
def initialize(target = TOPLEVEL_BINDING, options = {})
@pry = Pry.new(options.merge(:target => target))
@history = options[:history]
@pry.inject_sticky_locals!
reset_output
end
def eval(*strs)
reset_output
result = nil
strs.flatten.each do |str|
# Check for space prefix. See #1369.
if str !~ /^\s\S/
str = "#{str.strip}\n"
end
@history.push str if @history
if @pry.process_command(str)
result = last_command_result_or_output
else
result = @pry.evaluate_ruby(str)
end
end
result
end
def push(*lines)
Array(lines).flatten.each do |line|
@pry.eval(line)
end
end
def push_binding(context)
@pry.push_binding context
end
def last_output
@out.string if @out
end
def process_command(command_str)
@pry.process_command(command_str) or raise "Not a valid command"
last_command_result_or_output
end
def last_command_result
result = Pry.current[:pry_cmd_result]
result.retval if result
end
protected
def last_command_result_or_output
result = last_command_result
if result != Pry::Command::VOID_VALUE
result
else
last_output
end
end
def reset_output
@out = StringIO.new
@pry.output = @out
end
end

View file

@ -0,0 +1,26 @@
module Pry::Testable::Utility
#
# Creates a Tempfile then unlinks it after the block has yielded.
#
# @yieldparam [String] file
# The path of the temp file
#
# @return [void]
#
def temp_file(ext='.rb')
file = Tempfile.open(['pry', ext])
yield file
ensure
file.close(true) if file
end
def unindent(*args)
Pry::Helpers::CommandHelpers.unindent(*args)
end
def inner_scope
catch(:inner_scope) do
yield ->{ throw(:inner_scope, self) }
end
end
end

View file

@ -0,0 +1,46 @@
module Pry::Testable::Variables
#
# @example
# temporary_constants(:Foo, :Bar) do
# Foo = Class.new(RuntimeError)
# Bar = Class.new(RuntimeError)
# end
# Foo # => NameError
# Bar # => NameError
#
# @param [Array<Symbol>] *names
# An array of constant names that be defined by a block,
# and removed by this method afterwards.
#
# @return [void]
#
def temporary_constants(*names)
names.each do |name|
Object.remove_const name if Object.const_defined?(name)
end
yield
ensure
names.each do |name|
Object.remove_const name if Object.const_defined?(name)
end
end
#
# @param [String] name
# The name of a variable.
#
# @param [String] value
# Its value.
#
# @param [Binding] b
# The binding object to insert a variable into.
#
# @return [void]
#
def insert_variable(name, value, b)
Pry.current[:pry_local] = value
b.eval("#{name} = ::Pry.current[:pry_local]")
ensure
Pry.current[:pry_local] = nil
end
end

View file

@ -461,7 +461,7 @@ describe "Pry::Command" do
before do
@context = Object.new
@set.command "walking-spanish", "down the hall", :takes_block => true do
PryTestHelpers.inject_var(:@x, command_block.call, target)
insert_variable(:@x, command_block.call, target)
end
@set.import Pry::Commands
@ -482,9 +482,9 @@ describe "Pry::Command" do
@set.block_command "walking-spanish",
"litella's been screeching for a blind pig.",
:takes_block => true do |x, y|
PryTestHelpers.inject_var(:@x, x, target)
PryTestHelpers.inject_var(:@y, y, target)
PryTestHelpers.inject_var(:@block_var, command_block.call, target)
insert_variable(:@x, x, target)
insert_variable(:@y, y, target)
insert_variable(:@block_var, command_block.call, target)
end
@t.eval 'walking-spanish john carl| { :jesus }'
@ -516,8 +516,8 @@ describe "Pry::Command" do
describe "arg_string" do
it 'should remove block-related content from arg_string (with one normal arg)' do
@set.block_command "walking-spanish", "down the hall", :takes_block => true do |x, y|
PryTestHelpers.inject_var(:@arg_string, arg_string, target)
PryTestHelpers.inject_var(:@x, x, target)
insert_variable(:@arg_string, arg_string, target)
insert_variable(:@x, x, target)
end
@t.eval 'walking-spanish john| { :jesus }'
@ -527,7 +527,7 @@ describe "Pry::Command" do
it 'should remove block-related content from arg_string (with no normal args)' do
@set.block_command "walking-spanish", "down the hall", :takes_block => true do
PryTestHelpers.inject_var(:@arg_string, arg_string, target)
insert_variable(:@arg_string, arg_string, target)
end
@t.eval 'walking-spanish | { :jesus }'
@ -538,7 +538,7 @@ describe "Pry::Command" do
it 'should NOT remove block-related content from arg_string when :takes_block => false' do
block_string = "| { :jesus }"
@set.block_command "walking-spanish", "homemade special", :takes_block => false do
PryTestHelpers.inject_var(:@arg_string, arg_string, target)
insert_variable(:@arg_string, arg_string, target)
end
@t.eval "walking-spanish #{block_string}"
@ -551,8 +551,8 @@ describe "Pry::Command" do
describe "block_command" do
it "should remove block-related content from arguments" do
@set.block_command "walking-spanish", "glass is full of sand", :takes_block => true do |x, y|
PryTestHelpers.inject_var(:@x, x, target)
PryTestHelpers.inject_var(:@y, y, target)
insert_variable(:@x, x, target)
insert_variable(:@y, y, target)
end
@t.eval 'walking-spanish | { :jesus }'
@ -563,8 +563,8 @@ describe "Pry::Command" do
it "should NOT remove block-related content from arguments if :takes_block => false" do
@set.block_command "walking-spanish", "litella screeching for a blind pig", :takes_block => false do |x, y|
PryTestHelpers.inject_var(:@x, x, target)
PryTestHelpers.inject_var(:@y, y, target)
insert_variable(:@x, x, target)
insert_variable(:@y, y, target)
end
@t.eval 'walking-spanish | { :jesus }'
@ -578,8 +578,8 @@ describe "Pry::Command" do
it "should remove block-related content from arguments" do
@set.create_command "walking-spanish", "punk sanders carved one out of wood", :takes_block => true do
def process(x, y)
PryTestHelpers.inject_var(:@x, x, target)
PryTestHelpers.inject_var(:@y, y, target)
insert_variable(:@x, x, target)
insert_variable(:@y, y, target)
end
end
@ -592,8 +592,8 @@ describe "Pry::Command" do
it "should NOT remove block-related content from arguments if :takes_block => false" do
@set.create_command "walking-spanish", "down the hall", :takes_block => false do
def process(x, y)
PryTestHelpers.inject_var(:@x, x, target)
PryTestHelpers.inject_var(:@y, y, target)
insert_variable(:@x, x, target)
insert_variable(:@y, y, target)
end
end
@ -610,7 +610,7 @@ describe "Pry::Command" do
describe "{} style blocks" do
it 'should accept multiple parameters' do
@set.block_command "walking-spanish", "down the hall", :takes_block => true do
PryTestHelpers.inject_var(:@x, command_block.call(1, 2), target)
insert_variable(:@x, command_block.call(1, 2), target)
end
@t.eval 'walking-spanish | { |x, y| [x, y] }'
@ -623,7 +623,7 @@ describe "Pry::Command" do
it 'should accept multiple parameters' do
@set.create_command "walking-spanish", "litella", :takes_block => true do
def process
PryTestHelpers.inject_var(:@x, command_block.call(1, 2), target)
insert_variable(:@x, command_block.call(1, 2), target)
end
end
@ -649,7 +649,7 @@ describe "Pry::Command" do
describe "block_command" do
it "should expose block in command_block method" do
@set.block_command "walking-spanish", "glass full of sand", :takes_block => true do
PryTestHelpers.inject_var(:@x, command_block.call, target)
insert_variable(:@x, command_block.call, target)
end
@t.eval 'walking-spanish | { :jesus }'
@ -673,7 +673,7 @@ describe "Pry::Command" do
it "should expose block in command_block method" do
@set.create_command "walking-spanish", "homemade special", :takes_block => true do
def process
PryTestHelpers.inject_var(:@x, command_block.call, target)
insert_variable(:@x, command_block.call, target)
end
end

View file

@ -263,7 +263,7 @@ describe "show-doc" do
end
it 'should lookup module name with respect to current context' do
constant_scope(:AlphaClass, :BetaClass) do
temporary_constants(:AlphaClass, :BetaClass) do
# top-level beta
class BetaClass
def alpha
@ -283,7 +283,7 @@ describe "show-doc" do
end
it 'should look up nested modules' do
constant_scope(:AlphaClass) do
temporary_constants(:AlphaClass) do
class AlphaClass
# nested beta
class BetaClass

View file

@ -429,7 +429,7 @@ describe "show-source" do
it 'should lookup module name with respect to current context' do
constant_scope(:AlphaClass, :BetaClass) do
temporary_constants(:AlphaClass, :BetaClass) do
class BetaClass
def alpha
end
@ -447,7 +447,7 @@ describe "show-source" do
end
it 'should lookup nested modules' do
constant_scope(:AlphaClass) do
temporary_constants(:AlphaClass) do
class AlphaClass
class BetaClass
def beta

View file

@ -1,5 +1,5 @@
require 'bundler/setup'
require 'pry/test/helper'
require 'pry/testable'
Bundler.require :default, :test
require_relative 'spec_helpers/mock_pry'
require_relative 'spec_helpers/repl_tester'
@ -26,12 +26,22 @@ if ENV["SET_TRACE_FUNC"]
}
end
puts "Ruby v#{RUBY_VERSION} (#{defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"}), Pry v#{Pry::VERSION}, method_source v#{MethodSource::VERSION}, CodeRay v#{CodeRay::VERSION}, Pry::Slop v#{Pry::Slop::VERSION}"
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
end
config.include PryTestHelpers
config.before(:each) do
Pry::Testable.set_testenv_variables
end
config.after(:each) do
Pry::Testable.unset_testenv_variables
end
config.include Pry::Testable::Mockable
config.include Pry::Testable::Utility
include Pry::Testable::Evalable
include Pry::Testable::Variables
end
puts "Ruby v#{RUBY_VERSION} (#{defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"}), Pry v#{Pry::VERSION}, method_source v#{MethodSource::VERSION}, CodeRay v#{CodeRay::VERSION}, Pry::Slop v#{Pry::Slop::VERSION}"

View file

@ -116,7 +116,7 @@ describe Pry do
describe "custom non-IO object as $stdout" do
it "does not crash pry" do
old_stdout = $stdout
pry_eval = PryTester.new(binding)
pry_eval = pry_tester(binding)
expect(pry_eval.eval("$stdout = Class.new { def write(*) end }.new", ":ok")).to eq(:ok)
$stdout = old_stdout
end