Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 4e822a112cfe
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: e2372012f5db
Choose a head ref
  • 8 commits
  • 8 files changed
  • 1 contributor

Commits on May 23, 2015

  1. Copy the full SHA
    68644e3 View commit details
  2. Copy the full SHA
    47ec519 View commit details

Commits on May 24, 2015

  1. Copy the full SHA
    5f1ac55 View commit details
  2. Copy the full SHA
    4ed3010 View commit details
  3. sleep should convert it's "time interval" argument the same way as MRI

    we introduced the conversion for `Timeout#timeout(sec)` but under MRI timeout 
    implementation delegates to sleep so it's really a must have for `Kernel#sleep`
    kares committed May 24, 2015
    Copy the full SHA
    100ac2a View commit details

Commits on May 25, 2015

  1. respondsTo JRuby API under 1.9.3 does not do respond_to_missing? like…

    … MRI does
    
    ... wasn't noticeable too much previously but now Rails 4.2 started using respond_to_missing? on places such as AS::Duration thus it's require for us to get things such as `sleep 5.seconds` working 
    
    (with convertTimeInterval argument parsing resolves #2940)
    kares committed May 25, 2015
    Copy the full SHA
    d246f97 View commit details
  2. Copy the full SHA
    e07e277 View commit details
  3. [travis-ci] re-try sudo: false

    kares committed May 25, 2015
    Copy the full SHA
    e237201 View commit details
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
language: java

sudo: false

before_script:
- unset GEM_PATH GEM_HOME IRBRC
- "export PATH=`pwd`/bin:$PATH"
128 changes: 73 additions & 55 deletions core/src/main/java/org/jruby/RubyBasicObject.java
Original file line number Diff line number Diff line change
@@ -56,6 +56,7 @@
import static org.jruby.runtime.Visibility.*;
import static org.jruby.CompatVersion.*;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.Arity;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.builtin.InstanceVariables;
import org.jruby.runtime.builtin.InternalVariables;
@@ -586,17 +587,59 @@ public RubyClass getType() {
* overridden.
*/
public final boolean respondsTo(String name) {
DynamicMethod method = getMetaClass().searchMethod("respond_to?");
if(method.equals(getRuntime().getRespondToMethod())) {
final Ruby runtime = getRuntime();
if ( runtime.is1_9() ) return respondsTo19(runtime, name);

final DynamicMethod method = getMetaClass().searchMethod("respond_to?");
if ( method.equals(runtime.getRespondToMethod()) ) {
// fastest path; builtin respond_to? which just does isMethodBound
return getMetaClass().isMethodBound(name, false);
} else if (!method.isUndefined()) {
}
final RubySymbol mname = runtime.newSymbol(name);
if ( ! method.isUndefined() ) {
// medium path, invoke user's respond_to? if defined
return method.call(getRuntime().getCurrentContext(), this, metaClass, "respond_to?", getRuntime().newSymbol(name)).isTrue();
} else {
// slowest path, full callMethod to hit method_missing if present, or produce error
return callMethod(getRuntime().getCurrentContext(), "respond_to?", getRuntime().newSymbol(name)).isTrue();
return method.call(runtime.getCurrentContext(), this, metaClass, "respond_to?", mname).isTrue();
}
// slowest path, full callMethod to hit method_missing if present, or produce error
return callMethod(runtime.getCurrentContext(), "respond_to?", mname).isTrue();
}

private boolean respondsTo19(final Ruby runtime, final String name) {
final DynamicMethod respondTo = getMetaClass().searchMethod("respond_to?");
final DynamicMethod respondToMissing = getMetaClass().searchMethod("respond_to_missing?");

if ( respondTo.equals(runtime.getRespondToMethod()) &&
respondToMissing.equals(runtime.getRespondToMissingMethod()) ) {
// fastest path; builtin respond_to? which just does isMethodBound
return getMetaClass().isMethodBound(name, false);
}

final ThreadContext context = runtime.getCurrentContext();
final RubySymbol mname = runtime.newSymbol(name);
final boolean respondToUndefined = respondTo.isUndefined();
if ( ! ( respondToUndefined && respondToMissing.isUndefined() ) ) {
// medium path, invoke user's respond_to?/respond_to_missing? if defined
final DynamicMethod method; final String respondName;
if ( respondToUndefined ) {
method = respondToMissing; respondName = "respond_to_missing?";
} else {
method = respondTo; respondName = "respond_to?";
}
// We have to check and enforce arity
final Arity arity = method.getArity();
if ( arity.isFixed() ) {
if ( arity.required() == 1 ) {
return method.call(context, this, metaClass, respondName, mname).isTrue();
}
//if ( arity.required() != 2 ) {
// throw runtime.newArgumentError(respondName + " must accept 1 or 2 arguments (requires " + arity.getValue() + ")");
//}
}
return method.call(context, this, metaClass, respondName, mname, runtime.getTrue()).isTrue();
}

// slowest path, full callMethod to hit method_missing if present, or produce error
return callMethod(context, "respond_to?", mname).isTrue();
}

/**
@@ -609,20 +652,14 @@ public final boolean respondsToMissing(String name) {
/**
* Does this object respond to the specified message via "method_missing?"
*/
public final boolean respondsToMissing(String name, boolean priv) {
public final boolean respondsToMissing(String name, boolean incPrivate) {
DynamicMethod method = getMetaClass().searchMethod("respond_to_missing?");
// perhaps should try a smart version as for respondsTo above?
if(method.isUndefined()) {
return false;
} else {
return method.call(
getRuntime().getCurrentContext(),
this,
metaClass,
"respond_to_missing?",
getRuntime().newSymbol(name),
getRuntime().newBoolean(priv)).isTrue();
}
if ( method.isUndefined() ) return false;
final Ruby runtime = getRuntime();
return method.call(runtime.getCurrentContext(), this, getMetaClass(),
"respond_to_missing?", runtime.newSymbol(name), runtime.newBoolean(incPrivate)
).isTrue();
}

/**
@@ -1894,51 +1931,32 @@ public void checkFrozen() {
* in both the compiler and the interpreter, the performance
* benefit is important for this method.
*/
public RubyBoolean respond_to_p(IRubyObject mname) {
String name = mname.asJavaString();
public final RubyBoolean respond_to_p(IRubyObject mname) {
final String name = mname.asJavaString();
return getRuntime().newBoolean(getMetaClass().isMethodBound(name, true));
}

public IRubyObject respond_to_p19(IRubyObject mname) {
String name = mname.asJavaString();
IRubyObject respond = getRuntime().newBoolean(getMetaClass().isMethodBound(name, true, true));
if (!respond.isTrue()) {
respond = Helpers.invoke(getRuntime().getCurrentContext(), this, "respond_to_missing?", mname, getRuntime().getFalse());
respond = getRuntime().newBoolean(respond.isTrue());
}
return respond;
public final RubyBoolean respond_to_p19(IRubyObject mname) {
return respond_to_p19(mname, false);
}

/** obj_respond_to
*
* respond_to?( aSymbol, includePriv=false ) -> true or false
*
* Returns true if this object responds to the given method. Private
* methods are included in the search only if the optional second
* parameter evaluates to true.
*
* @return true if this responds to the given method
*
* !!! For some reason MRI shows the arity of respond_to? as -1, when it should be -2; that's why this is rest instead of required, optional = 1
*
* Going back to splitting according to method arity. MRI is wrong
* about most of these anyway, and since we have arity splitting
* in both the compiler and the interpreter, the performance
* benefit is important for this method.
*/
public RubyBoolean respond_to_p(IRubyObject mname, IRubyObject includePrivate) {
public final RubyBoolean respond_to_p(IRubyObject mname, IRubyObject includePrivate) {
String name = mname.asJavaString();
return getRuntime().newBoolean(getMetaClass().isMethodBound(name, !includePrivate.isTrue()));
}

public IRubyObject respond_to_p19(IRubyObject mname, IRubyObject includePrivate) {
String name = mname.asJavaString();
IRubyObject respond = getRuntime().newBoolean(getMetaClass().isMethodBound(name, !includePrivate.isTrue()));
if (!respond.isTrue()) {
respond = Helpers.invoke(getRuntime().getCurrentContext(), this, "respond_to_missing?", mname, includePrivate);
respond = getRuntime().newBoolean(respond.isTrue());
}
return respond;
public final RubyBoolean respond_to_p19(IRubyObject mname, IRubyObject includePrivate) {
return respond_to_p19(mname, includePrivate.isTrue());
}

private RubyBoolean respond_to_p19(IRubyObject mname, final boolean includePrivate) {
final Ruby runtime = getRuntime();
final String name = mname.asJavaString();
if ( getMetaClass().isMethodBound(name, !includePrivate, true) ) return runtime.getTrue();
// MRI (1.9) always passes down a symbol when calling respond_to_missing?
if ( ! (mname instanceof RubySymbol) ) mname = runtime.newSymbol(name);
IRubyObject respond = Helpers.invoke(runtime.getCurrentContext(), this, "respond_to_missing?", mname, runtime.newBoolean(includePrivate));
return runtime.newBoolean( respond.isTrue() );
}

/** rb_obj_id_obsolete
147 changes: 70 additions & 77 deletions core/src/main/java/org/jruby/RubyKernel.java

Large diffs are not rendered by default.

175 changes: 103 additions & 72 deletions core/src/main/java/org/jruby/RubyTime.java

Large diffs are not rendered by default.

14 changes: 10 additions & 4 deletions core/src/main/java/org/jruby/ext/timeout/Timeout.java
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@
import org.jruby.RubyObject;
import org.jruby.RubyRegexp;
import org.jruby.RubyThread;
import org.jruby.RubyTime;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.Helpers;
@@ -101,7 +102,7 @@ public static IRubyObject timeout(ThreadContext context, IRubyObject self, IRuby
@JRubyMethod(module = true)
public static IRubyObject timeout(final ThreadContext context, IRubyObject timeout, IRubyObject seconds, Block block) {
// No seconds, just yield
if (seconds.isNil() || Helpers.invoke(context, seconds, "zero?").isTrue()) {
if ( nilOrZeroSeconds(context, seconds) ) {
return block.yieldSpecific(context);
}

@@ -130,7 +131,7 @@ public static IRubyObject timeout(final ThreadContext context, IRubyObject timeo
@JRubyMethod(module = true)
public static IRubyObject timeout(final ThreadContext context, IRubyObject timeout, IRubyObject seconds, IRubyObject exceptionType, Block block) {
// No seconds, just yield
if (seconds.isNil() || Helpers.invoke(context, seconds, "zero?").isTrue()) {
if ( nilOrZeroSeconds(context, seconds) ) {
return block.yieldSpecific(context);
}

@@ -166,13 +167,18 @@ public static IRubyObject timeout(final ThreadContext context, IRubyObject timeo
}
}

private static boolean nilOrZeroSeconds(final ThreadContext context, final IRubyObject seconds) {
return seconds.isNil() || Helpers.invoke(context, seconds, "zero?").isTrue();
}

private static IRubyObject yieldWithTimeout(ThreadContext context,
final IRubyObject seconds, final Block block,
final Runnable runnable, final AtomicBoolean latch) throws RaiseException {
final double secs = seconds.convertToFloat().getDoubleValue();

final long micros = (long) ( RubyTime.convertTimeInterval(context, seconds) * 1000000 );
Future timeoutFuture = null;
try {
timeoutFuture = timeoutExecutor.schedule(runnable, (long) (secs * 1000000), TimeUnit.MICROSECONDS);
timeoutFuture = timeoutExecutor.schedule(runnable, micros, TimeUnit.MICROSECONDS);
return block.yield(context, seconds);
}
finally {
77 changes: 67 additions & 10 deletions test/test_kernel.rb
Original file line number Diff line number Diff line change
@@ -69,7 +69,7 @@ class ToAryReturnsAnInteger; def to_ary() 2 end; end

def test_Array_should_use_to_ary_and_make_sure_that_it_returns_either_array_or_nil
assert_equal [1], Kernel.Array(ToAryReturnsAnArray.new)
assert_raises(TypeError) { Kernel.Array(ToAryReturnsAnInteger.new) }
assert_raises(TypeError) { Kernel.Array(ToAryReturnsAnInteger.new) }
end

class BothToAryAndToADefined; def to_ary() [3] end; def to_a() raise "to_a() should not be called" end; end
@@ -83,7 +83,7 @@ class NeitherToANorToAryDefined; end
def test_Array_should_return_array_containing_argument_if_the_argument_has_neither_to_ary_nor_to_a
assert_equal [1], Kernel.Array(1)
assert_equal [:foo], Kernel.Array(:foo)
obj = NeitherToANorToAryDefined.new
obj = NeitherToANorToAryDefined.new
assert_equal [obj], Kernel.Array(obj)
end

@@ -158,7 +158,7 @@ def test_throw_should_bubble_up_to_the_right_catch
end
been_at_fred2 = true
end
assert been_at_fred2
assert been_at_fred2
end

def test_invalid_throw_after_inner_catch_should_unwind_the_stack_all_the_way_to_the_top
@@ -208,7 +208,7 @@ def test_catch_stack_should_be_cleaned_up
fail "catch stack should have been cleaned up"
end
end

# chomp
# chomp!

@@ -247,8 +247,8 @@ def test_eval_should_not_bring_local_variables_defined_in_its_input_to_parent_sc

Kernel.eval("new_variable = another_variable = existing_variable")

assert_equal 0, existing_variable
assert_equal 0, another_variable
assert_equal 0, existing_variable
assert_equal 0, another_variable
assert !(defined? new_variable)
end

@@ -261,7 +261,7 @@ def test_eval_should_not_bring_local_variables_defined_in_its_input_to_parent_sc
# fork

def test_format
assert_equal "Hello, world", Kernel.format("Hello, %s", "world")
assert_equal "Hello, world", Kernel.format("Hello, %s", "world")
assert_raises(TypeError) { Kernel.format("%01.3f", nil) }
end

@@ -415,8 +415,8 @@ def test_raise_in_debug_mode

def test_sleep
assert_raises(ArgumentError) { sleep(-10) }
# FIXME: below is true for MRI, but not for JRuby
# assert_raises(TypeError) { sleep "foo" }
assert_raises(TypeError) { sleep "foo" }

assert_equal 0, sleep(0)
t1 = Time.now
sleep(0.1)
@@ -425,6 +425,63 @@ def test_sleep
assert t2 >= t1 + 0.08
end

class SecDuration

def initialize(value); @value = value end

def respond_to?(name)
@value.respond_to?(name)
end

private

def method_missing(method, *args, &block)
@value.send(method, *args, &block)
end

end

def test_sleep_arg
sleep SecDuration.new(0.01)

begin
sleep SecDuration.new(-0.01)
rescue ArgumentError => e
assert_equal "time interval must be positive", e.message
else
fail 'argument error expected'
end

begin
sleep []
rescue TypeError => e
assert_equal "can't convert Array into time interval", e.message
else
fail 'type error expected'
end
end

class SecDuration19

def initialize(value); @value = value end

def respond_to_missing?(name, include_private = false)
@value.respond_to?(name)
end

private

def method_missing(method, *args, &block)
@value.send(method, *args, &block)
end

end

def test_sleep_arg19
sleep(SecDuration19.new(0.10))
assert_raises(ArgumentError) { sleep(SecDuration19.new(-0.01)) }
end if RUBY_VERSION > '1.9'

def test_sprintf
assert_equal 'Hello', Kernel.sprintf('Hello')
end
@@ -737,7 +794,7 @@ def test_backquote4_1
next
end
log "testing #{app}"

if (TESTAPP_DIR =~ /\s/) # spaces in paths, quote!
app = '"' + app + '"'
end
42 changes: 40 additions & 2 deletions test/test_respond_to.rb
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ module ClassMethods
attr_reader :hashable_methods
def method_added(method_name)
@hashable_methods ||= []
@hashable_methods << {:method_name => method_name, :key => method_name.to_s }
@hashable_methods << {:method_name => method_name, :key => method_name.to_s }
end
end
end
@@ -85,7 +85,7 @@ class ABasicObject #:nodoc:
instance_methods.each do |m|
undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$)/
end

attr_accessor :respond_to_called

def method_missing(name, *args)
@@ -107,3 +107,41 @@ def test_respond_to_check_can_trigger_method_missing
end
end
end

class TestRespondToMissing < Test::Unit::TestCase

class Foo
def respond_to_missing?(method, private = false)
return true if method.to_s == 'foo'
super(method, private)
end

def method_missing(method, *args)
return method if method.to_s == 'foo'
super
end
end

def test_respond_to_missing
obj = Foo.new
assert obj.respond_to?(:to_s)
assert ! obj.respond_to?(:fo)
assert obj.respond_to?(:foo)

assert_equal :foo, obj.foo
end

class Bar
def respond_to_missing?(method, private = false)
method.eql? :bar
end
end

def test_respond_to_missing_gets_a_symbol_name
obj = Bar.new
assert obj.respond_to?(:bar)
assert ! obj.respond_to?(:ba)
assert obj.respond_to?('bar')
end

end if RUBY_VERSION > '1.9'
43 changes: 41 additions & 2 deletions test/test_timeout.rb
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
require 'net/http'

class TestTimeout < Test::Unit::TestCase

def test_timeout_for_loop
n = 10000000
assert_raises(Timeout::Error) do
@@ -13,7 +14,7 @@ def test_timeout_for_loop

def do_timeout(time, count, pass_expected, timeout_expected = 0, &block)
pass = timeout = error = 0
count.times do |i|
count.times do
begin
Timeout::timeout(time, &block)
pass += 1
@@ -70,7 +71,7 @@ def test_net_http_timeout
assert_raises Timeout::Error do
http = Net::HTTP.new('8.8.8.8')
http.open_timeout = 0.001
response = http.start do |h|
http.start do |h|
h.request_get '/index.html'
# ensure we timeout even if we're fast
sleep(0.01)
@@ -148,4 +149,42 @@ def test_nested_timeout

assert_equal expected, result
end

class Seconds

attr_reader :value

def initialize(value); @value = value end

def self.===(other); other.is_a?(Seconds) end

def ==(other)
if Seconds === other
other.value == value
else
other == value
end
end

def eql?(other); other.is_a?(Seconds) && self == other end

def divmod(divisor)
value.divmod(divisor)
end

private

def method_missing(method, *args, &block)
value.send(method, *args, &block)
end

end

def test_timeout_interval_argument
assert_equal 42, Timeout::timeout(Seconds.new(2)) { 42 }
assert_raises(Timeout::Error) do
Timeout::timeout(Seconds.new(0.3)) { sleep(0.5) }
end
end

end